This commit is contained in:
austinpower1258 2026-02-19 23:52:09 -08:00
parent 463c6baabb
commit b739e918c9
4 changed files with 80 additions and 4 deletions

View file

@ -967,6 +967,16 @@ struct ContentView: View {
workspaceHandoffFallbackTask?.cancel()
workspaceHandoffFallbackTask = nil
let retiring = retiringWorkspaceId
// Hide terminal portal views for the retiring workspace BEFORE clearing
// retiringWorkspaceId. Once cleared, reconcileMountedWorkspaceIds unmounts
// the workspace but dismantleNSView intentionally doesn't hide portal views
// (to avoid blackouts during transient bonsplit dismantles). Hiding here
// prevents stale portal-hosted terminals from covering browser panes.
if let retiring, let workspace = tabManager.tabs.first(where: { $0.id == retiring }) {
workspace.hideAllTerminalPortalViews()
}
retiringWorkspaceId = nil
tabManager.completePendingWorkspaceUnfocus(reason: reason)
#if DEBUG

View file

@ -4089,6 +4089,14 @@ struct GhosttyTerminalView: NSViewRepresentable {
coordinator.lastBoundHostId = hostId
}
TerminalWindowPortalRegistry.synchronizeForAnchor(host)
} else {
// Bind is deferred until host moves into a window. Update the
// existing portal entry's visibleInUI now so that any portal sync
// that runs before the deferred bind completes won't hide the view.
TerminalWindowPortalRegistry.updateEntryVisibility(
for: hostedView,
visibleInUI: coordinator.desiredIsVisibleInUI
)
}
}
}

View file

@ -209,6 +209,29 @@ final class WindowTerminalPortal: NSObject {
}
}
/// Hide a portal entry without detaching it. Updates visibleInUI to false and
/// sets isHidden = true so subsequent synchronizeHostedView calls keep it hidden.
/// Used when a workspace is permanently unmounted (vs. transient bonsplit dismantles).
func hideEntry(forHostedId hostedId: ObjectIdentifier) {
guard var entry = entriesByHostedId[hostedId] else { return }
guard entry.visibleInUI else { return }
entry.visibleInUI = false
entriesByHostedId[hostedId] = entry
entry.hostedView?.isHidden = true
#if DEBUG
dlog("portal.hideEntry hosted=\(portalDebugToken(entry.hostedView)) reason=workspaceUnmount")
#endif
}
/// Update the visibleInUI flag on an existing entry without rebinding.
/// Used when a deferred bind is pending this ensures synchronizeHostedView
/// won't hide a view that updateNSView has already marked as visible.
func updateEntryVisibility(forHostedId hostedId: ObjectIdentifier, visibleInUI: Bool) {
guard var entry = entriesByHostedId[hostedId] else { return }
entry.visibleInUI = visibleInUI
entriesByHostedId[hostedId] = entry
}
func bind(hostedView: GhosttySurfaceScrollView, to anchorView: NSView, visibleInUI: Bool, zPriority: Int = 0) {
guard ensureInstalled() else { return }
@ -329,12 +352,17 @@ final class WindowTerminalPortal: NSObject {
return
}
guard let anchorView = entry.anchorView, let window else {
// Only hide if the entry is not marked visibleInUI. When a workspace is
// remounting, updateNSView sets visibleInUI=true before the deferred bind
// provides an anchor hiding here would race with that and cause a flash.
if !entry.visibleInUI {
#if DEBUG
if !hostedView.isHidden {
dlog("portal.hidden hosted=\(portalDebugToken(hostedView)) value=1 reason=missingAnchorOrWindow")
}
if !hostedView.isHidden {
dlog("portal.hidden hosted=\(portalDebugToken(hostedView)) value=1 reason=missingAnchorOrWindow")
}
#endif
hostedView.isHidden = true
hostedView.isHidden = true
}
return
}
guard anchorView.window === window else {
@ -587,6 +615,23 @@ enum TerminalWindowPortalRegistry {
portal.synchronizeHostedViewForAnchor(anchorView)
}
static func hideHostedView(_ hostedView: GhosttySurfaceScrollView) {
let hostedId = ObjectIdentifier(hostedView)
guard let windowId = hostedToWindowId[hostedId],
let portal = portalsByWindowId[windowId] else { return }
portal.hideEntry(forHostedId: hostedId)
}
/// Update the visibleInUI flag on an existing portal entry without rebinding.
/// Called when a bind is deferred (host not yet in window) to prevent stale
/// portal syncs from hiding a view that is about to become visible.
static func updateEntryVisibility(for hostedView: GhosttySurfaceScrollView, visibleInUI: Bool) {
let hostedId = ObjectIdentifier(hostedView)
guard let windowId = hostedToWindowId[hostedId],
let portal = portalsByWindowId[windowId] else { return }
portal.updateEntryVisibility(forHostedId: hostedId, visibleInUI: visibleInUI)
}
static func viewAtWindowPoint(_ windowPoint: NSPoint, in window: NSWindow) -> NSView? {
let portal = portal(for: window)
return portal.viewAtWindowPoint(windowPoint)

View file

@ -966,6 +966,19 @@ final class Workspace: Identifiable, ObservableObject {
triggerNotificationFocusFlash(panelId: panelId, requiresSplit: false, shouldFocus: true)
}
// MARK: - Portal Lifecycle
/// Hide all terminal portal views for this workspace.
/// Called before the workspace is unmounted to prevent portal-hosted terminal
/// views from covering browser panes in the newly selected workspace.
func hideAllTerminalPortalViews() {
for panel in panels.values {
guard let terminal = panel as? TerminalPanel else { continue }
terminal.hostedView.setVisibleInUI(false)
TerminalWindowPortalRegistry.hideHostedView(terminal.hostedView)
}
}
// MARK: - Utility
/// Create a new terminal panel (used when replacing the last panel)