From 88c1dbc5d6af07a6dfc731d22f8a4f817ae37be9 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:00:01 -0800 Subject: [PATCH 1/5] Fix omnibar focus thrash when another text field takes focus --- Sources/Panels/BrowserPanelView.swift | 37 ++++++++++++++++++- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 29 +++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index f91855dd..46f9147c 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -2181,6 +2181,13 @@ struct OmnibarSuggestion: Identifiable, Hashable { } } +func browserOmnibarShouldReacquireFocusAfterEndEditing( + suppressWebViewFocus: Bool, + nextResponderIsOtherTextField: Bool +) -> Bool { + suppressWebViewFocus && !nextResponderIsOtherTextField +} + private final class OmnibarNativeTextField: NSTextField { var onPointerDown: (() -> Void)? var onHandleKeyEvent: ((NSEvent, NSTextView?) -> Bool)? @@ -2293,6 +2300,29 @@ private struct OmnibarTextFieldRepresentable: NSViewRepresentable { } } + private func nextResponderIsOtherTextField(window: NSWindow?) -> Bool { + guard let window, let field = parentField else { return false } + let responder = window.firstResponder + + if let editor = responder as? NSTextView, + let delegateField = editor.delegate as? NSTextField { + return delegateField !== field + } + + if let textField = responder as? NSTextField { + return textField !== field + } + + return false + } + + private func shouldReacquireFocusAfterEndEditing(window: NSWindow?) -> Bool { + return browserOmnibarShouldReacquireFocusAfterEndEditing( + suppressWebViewFocus: parent.shouldSuppressWebViewFocus(), + nextResponderIsOtherTextField: nextResponderIsOtherTextField(window: window) + ) + } + func controlTextDidBeginEditing(_ obj: Notification) { if !parent.isFocused { DispatchQueue.main.async { @@ -2305,15 +2335,18 @@ private struct OmnibarTextFieldRepresentable: NSViewRepresentable { func controlTextDidEndEditing(_ obj: Notification) { if parent.isFocused { - if parent.shouldSuppressWebViewFocus() { + if shouldReacquireFocusAfterEndEditing(window: parentField?.window) { guard pendingFocusRequest != true else { return } pendingFocusRequest = true DispatchQueue.main.async { [weak self] in guard let self else { return } self.pendingFocusRequest = nil guard self.parent.isFocused else { return } - guard self.parent.shouldSuppressWebViewFocus() else { return } guard let field = self.parentField, let window = field.window else { return } + guard self.shouldReacquireFocusAfterEndEditing(window: window) else { + self.parent.onFieldLostFocus() + return + } // Check both the field itself AND its field editor (which becomes // the actual first responder when the text field is being edited). let fr = window.firstResponder diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index c875cf11..d74c082b 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -6012,3 +6012,32 @@ final class TerminalControllerSocketTextChunkTests: XCTestCase { ) } } + +final class BrowserOmnibarFocusPolicyTests: XCTestCase { + func testReacquiresFocusWhenWebViewSuppressionIsActiveAndNextResponderIsNotAnotherTextField() { + XCTAssertTrue( + browserOmnibarShouldReacquireFocusAfterEndEditing( + suppressWebViewFocus: true, + nextResponderIsOtherTextField: false + ) + ) + } + + func testDoesNotReacquireFocusWhenAnotherTextFieldAlreadyTookFocus() { + XCTAssertFalse( + browserOmnibarShouldReacquireFocusAfterEndEditing( + suppressWebViewFocus: true, + nextResponderIsOtherTextField: true + ) + ) + } + + func testDoesNotReacquireFocusWhenWebViewSuppressionIsInactive() { + XCTAssertFalse( + browserOmnibarShouldReacquireFocusAfterEndEditing( + suppressWebViewFocus: false, + nextResponderIsOtherTextField: false + ) + ) + } +} From 011abb62c9208170243c9f99f452baeb21975b48 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:10:02 -0800 Subject: [PATCH 2/5] Refine command palette focus restore and shortcut gating --- Sources/AppDelegate.swift | 54 ++++++++ Sources/ContentView.swift | 51 +++++++- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 120 ++++++++++++++++++ 3 files changed, 224 insertions(+), 1 deletion(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index ef7215c0..28c72ae1 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -315,6 +315,42 @@ func commandPaletteSelectionDeltaForKeyboardNavigation( return nil } +func shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: Bool, + normalizedFlags: NSEvent.ModifierFlags, + chars: String, + keyCode: UInt16 +) -> Bool { + guard isCommandPaletteVisible else { return false } + guard normalizedFlags.contains(.command) else { return false } + + let normalizedChars = chars.lowercased() + + if normalizedFlags == [.command] { + if normalizedChars == "a" + || normalizedChars == "c" + || normalizedChars == "v" + || normalizedChars == "x" + || normalizedChars == "z" + || normalizedChars == "y" { + return false + } + + switch keyCode { + case 51, 117, 123, 124: + return false + default: + break + } + } + + if normalizedFlags == [.command, .shift], normalizedChars == "z" { + return false + } + + return true +} + enum BrowserZoomShortcutAction: Equatable { case zoomIn case zoomOut @@ -2578,6 +2614,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return true } + if shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: activeCommandPaletteWindow() != nil, + normalizedFlags: normalizedFlags, + chars: chars, + keyCode: event.keyCode + ) { + return true + } + if normalizedFlags == [.command], chars == "q" { return handleQuitShortcutWarning() } @@ -3100,6 +3145,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent NotificationCenter.default.post(name: .browserFocusAddressBar, object: panel.id) } + func focusedBrowserAddressBarPanelId() -> UUID? { + browserAddressBarFocusedPanelId + } + + @discardableResult + func requestBrowserAddressBarFocus(panelId: UUID) -> Bool { + focusBrowserAddressBar(panelId: panelId) + } + private func shouldBypassAppShortcutForFocusedBrowserAddressBar( flags: NSEvent.ModifierFlags, chars: String diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 55e8ec22..dd0b9520 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1169,9 +1169,15 @@ struct ContentView: View { } } + private enum CommandPaletteRestoreFocusIntent { + case panel + case browserAddressBar + } + private struct CommandPaletteRestoreFocusTarget { let workspaceId: UUID let panelId: UUID + let intent: CommandPaletteRestoreFocusIntent } private enum CommandPaletteInputFocusTarget { @@ -4137,6 +4143,14 @@ struct ContentView: View { return false } + static func shouldRestoreBrowserAddressBarAfterCommandPaletteDismiss( + focusedPanelIsBrowser: Bool, + focusedBrowserAddressBarPanelId: UUID?, + focusedPanelId: UUID + ) -> Bool { + focusedPanelIsBrowser && focusedBrowserAddressBarPanelId == focusedPanelId + } + private func syncCommandPaletteDebugStateForObservedWindow() { guard let window = observedWindow ?? NSApp.keyWindow ?? NSApp.mainWindow else { return } AppDelegate.shared?.setCommandPaletteVisible(isCommandPalettePresented, for: window) @@ -4178,9 +4192,15 @@ struct ContentView: View { private func presentCommandPalette(initialQuery: String) { if let panelContext = focusedPanelContext { + let shouldRestoreBrowserAddressBar = Self.shouldRestoreBrowserAddressBarAfterCommandPaletteDismiss( + focusedPanelIsBrowser: panelContext.panel.panelType == .browser, + focusedBrowserAddressBarPanelId: AppDelegate.shared?.focusedBrowserAddressBarPanelId(), + focusedPanelId: panelContext.panelId + ) commandPaletteRestoreFocusTarget = CommandPaletteRestoreFocusTarget( workspaceId: panelContext.workspace.id, - panelId: panelContext.panelId + panelId: panelContext.panelId, + intent: shouldRestoreBrowserAddressBar ? .browserAddressBar : .panel ) } else { commandPaletteRestoreFocusTarget = nil @@ -4236,18 +4256,47 @@ struct ContentView: View { } tabManager.focusTab(target.workspaceId, surfaceId: target.panelId, suppressFlash: true) + if let context = focusedPanelContext, + context.workspace.id == target.workspaceId, + context.panelId == target.panelId { + restoreCommandPaletteInputFocusIfNeeded(target: target, attemptsRemaining: 6) + return + } + guard attemptsRemaining > 0 else { return } DispatchQueue.main.asyncAfter(deadline: .now() + 0.03) { guard !isCommandPalettePresented else { return } if let context = focusedPanelContext, context.workspace.id == target.workspaceId, context.panelId == target.panelId { + restoreCommandPaletteInputFocusIfNeeded(target: target, attemptsRemaining: 6) return } restoreCommandPaletteFocus(target: target, attemptsRemaining: attemptsRemaining - 1) } } + private func restoreCommandPaletteInputFocusIfNeeded( + target: CommandPaletteRestoreFocusTarget, + attemptsRemaining: Int + ) { + guard !isCommandPalettePresented else { return } + guard target.intent == .browserAddressBar else { return } + guard attemptsRemaining > 0 else { return } + guard let appDelegate = AppDelegate.shared else { return } + + if appDelegate.requestBrowserAddressBarFocus(panelId: target.panelId) { + return + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.03) { + restoreCommandPaletteInputFocusIfNeeded( + target: target, + attemptsRemaining: attemptsRemaining - 1 + ) + } + } + private func resetCommandPaletteSearchFocus() { applyCommandPaletteInputFocusPolicy(.search) } diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index d74c082b..3239d633 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -1581,6 +1581,126 @@ final class CommandPaletteKeyboardNavigationTests: XCTestCase { } } +final class CommandPaletteOpenShortcutConsumptionTests: XCTestCase { + func testDoesNotConsumeWhenPaletteIsNotVisible() { + XCTAssertFalse( + shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: false, + normalizedFlags: [.command], + chars: "n", + keyCode: 45 + ) + ) + } + + func testConsumesAppCommandShortcutsWhenPaletteIsVisible() { + XCTAssertTrue( + shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: true, + normalizedFlags: [.command], + chars: "n", + keyCode: 45 + ) + ) + XCTAssertTrue( + shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: true, + normalizedFlags: [.command], + chars: "t", + keyCode: 17 + ) + ) + XCTAssertTrue( + shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: true, + normalizedFlags: [.command, .shift], + chars: ",", + keyCode: 43 + ) + ) + } + + func testAllowsClipboardAndUndoShortcutsForPaletteTextEditing() { + XCTAssertFalse( + shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: true, + normalizedFlags: [.command], + chars: "v", + keyCode: 9 + ) + ) + XCTAssertFalse( + shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: true, + normalizedFlags: [.command], + chars: "z", + keyCode: 6 + ) + ) + XCTAssertFalse( + shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: true, + normalizedFlags: [.command, .shift], + chars: "z", + keyCode: 6 + ) + ) + } + + func testAllowsArrowAndDeleteEditingCommandsForPaletteTextEditing() { + XCTAssertFalse( + shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: true, + normalizedFlags: [.command], + chars: "", + keyCode: 123 + ) + ) + XCTAssertFalse( + shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: true, + normalizedFlags: [.command], + chars: "", + keyCode: 51 + ) + ) + } +} + +final class CommandPaletteRestoreFocusStateMachineTests: XCTestCase { + func testRestoresBrowserAddressBarWhenPaletteOpenedFromFocusedAddressBar() { + let panelId = UUID() + XCTAssertTrue( + ContentView.shouldRestoreBrowserAddressBarAfterCommandPaletteDismiss( + focusedPanelIsBrowser: true, + focusedBrowserAddressBarPanelId: panelId, + focusedPanelId: panelId + ) + ) + } + + func testDoesNotRestoreBrowserAddressBarWhenFocusedPanelIsNotBrowser() { + let panelId = UUID() + XCTAssertFalse( + ContentView.shouldRestoreBrowserAddressBarAfterCommandPaletteDismiss( + focusedPanelIsBrowser: false, + focusedBrowserAddressBarPanelId: panelId, + focusedPanelId: panelId + ) + ) + } + + func testDoesNotRestoreBrowserAddressBarWhenAnotherPanelHadAddressBarFocus() { + XCTAssertFalse( + ContentView.shouldRestoreBrowserAddressBarAfterCommandPaletteDismiss( + focusedPanelIsBrowser: true, + focusedBrowserAddressBarPanelId: UUID(), + focusedPanelId: UUID() + ) + ) + } +} + final class CommandPaletteRenameSelectionSettingsTests: XCTestCase { private let suiteName = "cmux.tests.commandPaletteRenameSelection.\(UUID().uuidString)" From 0dee87826816f8a1a28ceb86a04d972e5168f407 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:14:32 -0800 Subject: [PATCH 3/5] Fix light-mode sidebar typing indicator contrast --- Sources/ContentView.swift | 26 +++++++++++++-- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 32 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index dd0b9520..7a6c5d8d 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -28,6 +28,19 @@ private func coloredCircleImage(color: NSColor) -> NSImage { return image } +func sidebarStatusPillActiveForegroundNSColor( + colorScheme: ColorScheme, + opacity: CGFloat +) -> NSColor { + let clampedOpacity = max(0, min(opacity, 1)) + switch colorScheme { + case .dark: + return NSColor.white.withAlphaComponent(clampedOpacity) + default: + return NSColor.black.withAlphaComponent(clampedOpacity) + } +} + struct ShortcutHintPillBackground: View { var emphasis: Double = 1.0 @@ -6768,12 +6781,13 @@ private struct SidebarStatusPillsRow: View { let onFocus: () -> Void @State private var isExpanded: Bool = false + @Environment(\.colorScheme) private var colorScheme var body: some View { VStack(alignment: .leading, spacing: 2) { Text(statusText) .font(.system(size: 10)) - .foregroundColor(isActive ? .white.opacity(0.8) : .secondary) + .foregroundColor(isActive ? activePrimaryTextColor : .secondary) .lineLimit(isExpanded ? nil : 3) .truncationMode(.tail) .multilineTextAlignment(.leading) @@ -6796,13 +6810,21 @@ private struct SidebarStatusPillsRow: View { } .buttonStyle(.plain) .font(.system(size: 10, weight: .semibold)) - .foregroundColor(isActive ? .white.opacity(0.65) : .secondary.opacity(0.9)) + .foregroundColor(isActive ? activeSecondaryTextColor : .secondary.opacity(0.9)) .frame(maxWidth: .infinity, alignment: .leading) } } .help(statusText) } + private var activePrimaryTextColor: Color { + Color(nsColor: sidebarStatusPillActiveForegroundNSColor(colorScheme: colorScheme, opacity: 0.8)) + } + + private var activeSecondaryTextColor: Color { + Color(nsColor: sidebarStatusPillActiveForegroundNSColor(colorScheme: colorScheme, opacity: 0.65)) + } + private var statusText: String { entries .map { entry in diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 3239d633..99a27709 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -773,6 +773,38 @@ final class BrowserPanelOmnibarPillBackgroundColorTests: XCTestCase { } } +final class SidebarStatusPillActiveForegroundColorTests: XCTestCase { + func testLightModeUsesBlackWithRequestedOpacity() { + guard let color = sidebarStatusPillActiveForegroundNSColor( + colorScheme: .light, + opacity: 0.8 + ).usingColorSpace(.sRGB) else { + XCTFail("Expected sRGB-convertible color") + return + } + + XCTAssertEqual(color.redComponent, 0, accuracy: 0.001) + XCTAssertEqual(color.greenComponent, 0, accuracy: 0.001) + XCTAssertEqual(color.blueComponent, 0, accuracy: 0.001) + XCTAssertEqual(color.alphaComponent, 0.8, accuracy: 0.001) + } + + func testDarkModeUsesWhiteWithRequestedOpacity() { + guard let color = sidebarStatusPillActiveForegroundNSColor( + colorScheme: .dark, + opacity: 0.65 + ).usingColorSpace(.sRGB) else { + XCTFail("Expected sRGB-convertible color") + return + } + + XCTAssertEqual(color.redComponent, 1, accuracy: 0.001) + XCTAssertEqual(color.greenComponent, 1, accuracy: 0.001) + XCTAssertEqual(color.blueComponent, 1, accuracy: 0.001) + XCTAssertEqual(color.alphaComponent, 0.65, accuracy: 0.001) + } +} + final class BrowserDeveloperToolsShortcutDefaultsTests: XCTestCase { func testSafariDefaultShortcutForToggleDeveloperTools() { let shortcut = KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.defaultShortcut From 666ef75d1eaff174dbd22d42160d2762bfaa3c48 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:22:39 -0800 Subject: [PATCH 4/5] Fix light-mode typing indicator contrast in active sidebar --- Sources/ContentView.swift | 41 ++++++++++--------- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 24 ++++++----- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 7a6c5d8d..a57755cf 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -28,17 +28,14 @@ private func coloredCircleImage(color: NSColor) -> NSImage { return image } -func sidebarStatusPillActiveForegroundNSColor( - colorScheme: ColorScheme, - opacity: CGFloat +func sidebarActiveForegroundNSColor( + opacity: CGFloat, + appAppearance: NSAppearance? = NSApp?.effectiveAppearance ) -> NSColor { let clampedOpacity = max(0, min(opacity, 1)) - switch colorScheme { - case .dark: - return NSColor.white.withAlphaComponent(clampedOpacity) - default: - return NSColor.black.withAlphaComponent(clampedOpacity) - } + let bestMatch = appAppearance?.bestMatch(from: [.darkAqua, .aqua]) + let baseColor: NSColor = (bestMatch == .darkAqua) ? .white : .black + return baseColor.withAlphaComponent(clampedOpacity) } struct ShortcutHintPillBackground: View { @@ -5929,11 +5926,13 @@ private struct TabItemView: View { } private var activePrimaryTextColor: Color { - usesInvertedActiveForeground ? .white : .primary + usesInvertedActiveForeground ? Color(nsColor: sidebarActiveForegroundNSColor(opacity: 1.0)) : .primary } private func activeSecondaryColor(_ opacity: Double = 0.75) -> Color { - usesInvertedActiveForeground ? .white.opacity(opacity) : .secondary + usesInvertedActiveForeground + ? Color(nsColor: sidebarActiveForegroundNSColor(opacity: CGFloat(opacity))) + : .secondary } private var activeUnreadBadgeFillColor: Color { @@ -6676,11 +6675,16 @@ private struct TabItemView: View { private func logLevelColor(_ level: SidebarLogLevel, isActive: Bool) -> Color { if isActive { switch level { - case .info: return .white.opacity(0.5) - case .progress: return .white.opacity(0.8) - case .success: return .white.opacity(0.9) - case .warning: return .white.opacity(0.9) - case .error: return .white.opacity(0.9) + case .info: + return Color(nsColor: sidebarActiveForegroundNSColor(opacity: 0.5)) + case .progress: + return Color(nsColor: sidebarActiveForegroundNSColor(opacity: 0.8)) + case .success: + return Color(nsColor: sidebarActiveForegroundNSColor(opacity: 0.9)) + case .warning: + return Color(nsColor: sidebarActiveForegroundNSColor(opacity: 0.9)) + case .error: + return Color(nsColor: sidebarActiveForegroundNSColor(opacity: 0.9)) } } switch level { @@ -6781,7 +6785,6 @@ private struct SidebarStatusPillsRow: View { let onFocus: () -> Void @State private var isExpanded: Bool = false - @Environment(\.colorScheme) private var colorScheme var body: some View { VStack(alignment: .leading, spacing: 2) { @@ -6818,11 +6821,11 @@ private struct SidebarStatusPillsRow: View { } private var activePrimaryTextColor: Color { - Color(nsColor: sidebarStatusPillActiveForegroundNSColor(colorScheme: colorScheme, opacity: 0.8)) + Color(nsColor: sidebarActiveForegroundNSColor(opacity: 0.8)) } private var activeSecondaryTextColor: Color { - Color(nsColor: sidebarStatusPillActiveForegroundNSColor(colorScheme: colorScheme, opacity: 0.65)) + Color(nsColor: sidebarActiveForegroundNSColor(opacity: 0.65)) } private var statusText: String { diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 99a27709..8484c957 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -773,12 +773,13 @@ final class BrowserPanelOmnibarPillBackgroundColorTests: XCTestCase { } } -final class SidebarStatusPillActiveForegroundColorTests: XCTestCase { - func testLightModeUsesBlackWithRequestedOpacity() { - guard let color = sidebarStatusPillActiveForegroundNSColor( - colorScheme: .light, - opacity: 0.8 - ).usingColorSpace(.sRGB) else { +final class SidebarActiveForegroundColorTests: XCTestCase { + func testLightAppearanceUsesBlackWithRequestedOpacity() { + guard let lightAppearance = NSAppearance(named: .aqua), + let color = sidebarActiveForegroundNSColor( + opacity: 0.8, + appAppearance: lightAppearance + ).usingColorSpace(.sRGB) else { XCTFail("Expected sRGB-convertible color") return } @@ -789,11 +790,12 @@ final class SidebarStatusPillActiveForegroundColorTests: XCTestCase { XCTAssertEqual(color.alphaComponent, 0.8, accuracy: 0.001) } - func testDarkModeUsesWhiteWithRequestedOpacity() { - guard let color = sidebarStatusPillActiveForegroundNSColor( - colorScheme: .dark, - opacity: 0.65 - ).usingColorSpace(.sRGB) else { + func testDarkAppearanceUsesWhiteWithRequestedOpacity() { + guard let darkAppearance = NSAppearance(named: .darkAqua), + let color = sidebarActiveForegroundNSColor( + opacity: 0.65, + appAppearance: darkAppearance + ).usingColorSpace(.sRGB) else { XCTFail("Expected sRGB-convertible color") return } From 89756bad658666794f6e0fe41d00e5a0aebfdfe6 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:28:48 -0800 Subject: [PATCH 5/5] Fix command palette caret color in light mode --- Sources/ContentView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index a57755cf..dc0adaaf 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -2465,7 +2465,7 @@ struct ContentView: View { TextField(commandPaletteSearchPlaceholder, text: $commandPaletteQuery) .textFieldStyle(.plain) .font(.system(size: 13, weight: .regular)) - .tint(.white) + .tint(Color(nsColor: sidebarActiveForegroundNSColor(opacity: 1.0))) .focused($isCommandPaletteSearchFocused) .onSubmit { runSelectedCommandPaletteResult(visibleResults: visibleResults) @@ -2618,7 +2618,7 @@ struct ContentView: View { TextField(target.placeholder, text: $commandPaletteRenameDraft) .textFieldStyle(.plain) .font(.system(size: 13, weight: .regular)) - .tint(.white) + .tint(Color(nsColor: sidebarActiveForegroundNSColor(opacity: 1.0))) .focused($isCommandPaletteRenameFocused) .backport.onKeyPress(.delete) { modifiers in handleCommandPaletteRenameDeleteBackward(modifiers: modifiers)