Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions Sources/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10067,12 +10067,19 @@ final class Workspace: Identifiable, ObservableObject {
maybeAutoFocusBrowserAddressBarOnPanelFocus(browserPanel, trigger: trigger)
}

if trigger == .terminalFirstResponder,
panels[panelId] is TerminalPanel {
beginEventDrivenLayoutFollowUp(
reason: "workspace.focusPanel.terminal",
terminalFocusPanelId: panelId
)
if let terminalPanel = panels[panelId] as? TerminalPanel {
// Always set up a focus follow-up when the terminal is not yet first responder.
// The .terminalFirstResponder trigger always needs it (reentrant focus assertion).
// The .standard trigger needs it when the surface view isn't ready yet (e.g.
// new workspace via Cmd+T where the portal hasn't attached to a window yet).
let needsFocusFollowUp = trigger == .terminalFirstResponder
|| !terminalPanel.hostedView.isSurfaceViewFirstResponder()
if needsFocusFollowUp {
beginEventDrivenLayoutFollowUp(
reason: "workspace.focusPanel.terminal",
terminalFocusPanelId: panelId
)
}
}
}

Expand Down Expand Up @@ -11811,7 +11818,23 @@ extension Workspace: BonsplitDelegate {
if bonsplitController.allPaneIds.contains(pane) {
normalizePinnedTabs(in: pane)
}
scheduleTerminalGeometryReconcile()
// When a pane was auto-closed (e.g. N→1 panes), the SwiftUI split view rebuilds
// asynchronously, transiently detaching the surviving terminal surface. The synchronous
// ensureFocus from applyTabSelection may succeed on the old layout but the first responder
// is lost when SwiftUI tears down the split and recreates a single-pane wrapper. Include
// the surviving panel in the layout follow-up so the retry loop re-applies focus once the
// view is reattached (#2665).
if !isDetaching && !bonsplitController.allPaneIds.contains(pane),
let survivingPanelId = focusedPanelId,
terminalPanel(for: survivingPanelId) != nil {
beginEventDrivenLayoutFollowUp(
reason: "workspace.paneCollapse",
terminalFocusPanelId: survivingPanelId,
includeGeometry: true
)
} else {
scheduleTerminalGeometryReconcile()
}
if !isDetaching {
scheduleFocusReconcile()
}
Expand Down Expand Up @@ -11931,7 +11954,19 @@ extension Workspace: BonsplitDelegate {
}
}

scheduleTerminalGeometryReconcile()
// Same pane-collapse focus fix as didCloseTab (#2665): the SwiftUI split view
// rebuild can transiently detach the surviving terminal, losing first responder.
if shouldScheduleFocusReconcile,
let survivingPanelId = focusedPanelId,
terminalPanel(for: survivingPanelId) != nil {
beginEventDrivenLayoutFollowUp(
reason: "workspace.paneCollapse",
terminalFocusPanelId: survivingPanelId,
includeGeometry: true
)
} else {
scheduleTerminalGeometryReconcile()
}
if shouldScheduleFocusReconcile {
scheduleFocusReconcile()
}
Expand Down
Loading