diff --git a/Sources/RightSidebarPanelView.swift b/Sources/RightSidebarPanelView.swift index d1575b2b624c..227b2eb1db19 100644 --- a/Sources/RightSidebarPanelView.swift +++ b/Sources/RightSidebarPanelView.swift @@ -82,6 +82,7 @@ struct RightSidebarPanelView: View { guard let responder = window.firstResponder else { return false } return AppDelegate.shared?.isRightSidebarFocusResponder(responder, in: window) == true } + @Environment(\.cmuxAccentColor) private var cmuxAccent @State private var focusShortcutHintMonitor = WindowScopedShortcutHintModifierMonitor(activation: .commandOnly) @State private var closeShortcutHintMonitor = WindowScopedShortcutHintModifierMonitor(activation: .commandOnly) @State private var hasMountedRightSidebarContent = false @@ -162,6 +163,12 @@ struct RightSidebarPanelView: View { } var body: some View { + // Share the content clip and focus stroke without adding layout space. + let boundary = UnevenRoundedRectangle( + bottomTrailingRadius: 16, + topTrailingRadius: 16, + style: .continuous + ) VStack(spacing: 0) { modeBar .rightSidebarChromeBottomBorder( @@ -170,6 +177,17 @@ struct RightSidebarPanelView: View { contentForMode .frame(maxWidth: .infinity, maxHeight: .infinity) } + .clipShape(boundary) + .overlay { + boundary + .strokeBorder( + Color(nsColor: cmuxAccent.nsColor(isDark: windowAppearance.resolvedColorScheme == .dark)), + lineWidth: 1 + ) + .opacity(fileExplorerState.rightSidebarOwnsInputFocus ? 1 : 0) + .allowsHitTesting(false) + .accessibilityHidden(true) + } .rightSidebarButtonBorderShape() .frame(maxWidth: .infinity, maxHeight: .infinity) // Keep every mode (including Dock and AppKit-backed file rows) on the diff --git a/Sources/WorkspaceContentView.swift b/Sources/WorkspaceContentView.swift index 9e85bd6275eb..941075af03ce 100644 --- a/Sources/WorkspaceContentView.swift +++ b/Sources/WorkspaceContentView.swift @@ -14,6 +14,13 @@ private enum WorkspaceTitlebarInteractionMetrics { } enum WorkspacePanelVisibilityPolicy { + static func mainContentIsFocused( + isWorkspaceInputActive: Bool, + rightSidebarOwnsInputFocus: Bool + ) -> Bool { + isWorkspaceInputActive && !rightSidebarOwnsInputFocus + } + nonisolated static func panelVisibleInUI( isWorkspaceVisible: Bool, paneHasSelectedTab: Bool, @@ -217,7 +224,14 @@ struct WorkspaceContentView: View { } }() - let bonsplitView = BonsplitView(controller: workspace.bonsplitController) { tab, paneId in + let isMainContentFocused = WorkspacePanelVisibilityPolicy.mainContentIsFocused( + isWorkspaceInputActive: isWorkspaceInputActive, + rightSidebarOwnsInputFocus: rightSidebarOwnsInputFocus + ) + let bonsplitView = BonsplitView( + controller: workspace.bonsplitController, + isMainContentFocused: isMainContentFocused + ) { tab, paneId in // Content for each tab in bonsplit let _ = Self.debugPanelLookup(tab: tab, workspace: workspace) if let panel = workspace.panel(for: tab.id) { @@ -229,7 +243,7 @@ struct WorkspaceContentView: View { let isFocusedPanel = isWorkspaceInputActive && workspace.focusedPanelId == panel.id // Gated focus for the ring/active state only: the main pane yields // its focus ring while the right sidebar (Dock) owns focus. - let isFocused = isFocusedPanel && !rightSidebarOwnsInputFocus + let isFocused = isMainContentFocused && workspace.focusedPanelId == panel.id let selectedTab = workspace.bonsplitController.selectedTab(inPane: paneId) let isSelectedInPane = selectedTab?.id == tab.id let isVisibleInUI = Self.panelVisibleInUI( diff --git a/cmuxTests/WorkspaceContentViewVisibilityTests.swift b/cmuxTests/WorkspaceContentViewVisibilityTests.swift index b80487067bfa..ede1c362b711 100644 --- a/cmuxTests/WorkspaceContentViewVisibilityTests.swift +++ b/cmuxTests/WorkspaceContentViewVisibilityTests.swift @@ -655,6 +655,28 @@ final class WorkspaceContentViewVisibilityTests { ) } + @Test + func mainContentFocusYieldsToRightSidebarWithoutChangingWorkspaceVisibility() { + #expect( + WorkspacePanelVisibilityPolicy.mainContentIsFocused( + isWorkspaceInputActive: true, + rightSidebarOwnsInputFocus: false + ) + ) + #expect( + !WorkspacePanelVisibilityPolicy.mainContentIsFocused( + isWorkspaceInputActive: true, + rightSidebarOwnsInputFocus: true + ) + ) + #expect( + !WorkspacePanelVisibilityPolicy.mainContentIsFocused( + isWorkspaceInputActive: false, + rightSidebarOwnsInputFocus: false + ) + ) + } + @Test func testRenderedVisiblePanelPolicyPrefersSelectedTabOverStaleFocusedPanel() { let paneId = UUID() diff --git a/vendor/bonsplit b/vendor/bonsplit index c5cb2924055e..de63f80ddf65 160000 --- a/vendor/bonsplit +++ b/vendor/bonsplit @@ -1 +1 @@ -Subproject commit c5cb2924055e8a63573313cee8a0a3e96d264fa6 +Subproject commit de63f80ddf65f7b6bba2897538355267cc896c7e