Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
0a8c4cb
test: require foreground-only global search shortcut
Jul 22, 2026
ebe79a9
fix: scope global search shortcut to foreground
Jul 22, 2026
c21223c
test: avoid production shortcut policy seam
Jul 23, 2026
a41fcd3
test: stay within hotkey policy file budget
Jul 23, 2026
315daae
test: cover foreground-scoped global search shortcut
Jul 23, 2026
92ae691
test: launch global search coverage in UI test mode
Jul 23, 2026
4a2aaca
test: force main-window mode for global search UI coverage
Jul 23, 2026
15dd2f9
test: verify background global search delivery directly
Jul 23, 2026
bca1833
test: continue after headless activation failure
Jul 23, 2026
16cef50
test: launch cmux without foreground activation
Jul 23, 2026
0975a77
test: post background shortcut through system event tap
Jul 23, 2026
3cc930d
test: log background shortcut injection
Jul 23, 2026
ee533ca
test: give Finder a keyboard target
Jul 24, 2026
f63bab7
test: use foreground app for shortcut delivery
Jul 24, 2026
79164e7
test: compile shortcut probe inside sandbox
Jul 24, 2026
6c4bbd9
test: build foreground shortcut probe target
Jul 24, 2026
019b3aa
test: activate foreground shortcut probe
Jul 24, 2026
125880a
Merge origin/main into issue-8561-global-search-background-hotkey
Jul 24, 2026
c8c74bd
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 24, 2026
cc2a88a
Fix Dock session restore import
Jul 24, 2026
352f65b
Finish Dock session restore build repair
Jul 24, 2026
a40bc9a
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 24, 2026
54acae1
Fix WindowDock isolation warning
Jul 24, 2026
1a6baa3
Fix WindowDock snapshot actor isolation
Jul 24, 2026
dcf2f0d
test: cover foreground global search toggling
Jul 24, 2026
bb5e8b1
perf: gate shortcut context behind stroke match
Jul 24, 2026
8261522
test: cover Option-only global search routing
Jul 24, 2026
4385da2
fix: preserve Option-only global search bindings
Jul 24, 2026
82ed28e
test: cover Option-only global search chord routing
Jul 24, 2026
b568932
fix: route Option-only global search chord prefixes
Jul 24, 2026
6eac2ae
test: keep global search lookup off typing path
Jul 24, 2026
68f7aae
perf: cache foreground global search binding
Jul 24, 2026
b5dbe85
Fix shortcut probe activation in UI tests
Jul 24, 2026
2627981
test: preserve foreground shortcut input ownership
Jul 24, 2026
e1f4318
fix: preserve focused input ownership for global search
Jul 24, 2026
00a5ce5
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 24, 2026
43e40ba
test: launch shortcut probe through workspace
Jul 24, 2026
cdbda5f
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 24, 2026
9bdc02f
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 24, 2026
a6f9fe7
test: cover final global search ownership gaps
Jul 24, 2026
a6fb09a
Fix merged sidebar suspension test import
Jul 24, 2026
385a12a
test: cover global search chord palette precedence
Jul 24, 2026
59cba9b
Fix merged sidebar table test import
Jul 24, 2026
8e1a2bb
fix: preserve foreground global search input ownership
Jul 24, 2026
827861d
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 24, 2026
661de99
test: isolate foreground shortcut verification
Jul 24, 2026
00fe273
test: preserve focused input for global search remaps
Jul 24, 2026
5aeef7c
fix: preserve focused input for global search remaps
Jul 24, 2026
0570749
fix: defer global search to shared input gates
Jul 24, 2026
b0389d6
test: preserve global search shortcut priority
Jul 24, 2026
d5d9816
test: fix global search priority harness
Jul 24, 2026
b8ac163
fix: respect focused shortcut owners for global search
Jul 24, 2026
6535c54
test: harden global search shortcut fixtures
Jul 24, 2026
50d3043
test: reject inert global search media bindings
Jul 24, 2026
05dcae6
fix: reject unsupported global search media bindings
Jul 24, 2026
c07f93c
fix: validate shortcuts before conflicts
Jul 24, 2026
caa99c8
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 24, 2026
bfa654c
test: expose shortcut probe status to UI automation
Jul 24, 2026
10f1510
test: remove unreliable inter-app UI harness
Jul 25, 2026
7b8f383
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 25, 2026
044d41d
test: preserve browser editing before search chords
Jul 25, 2026
0332842
test: verify opt-in Carbon hotkey registration
Jul 25, 2026
9060ec4
fix: preserve focused browser editing before search chords
Jul 25, 2026
972beed
test: restore hotkey settings from teardown
Jul 25, 2026
bf27d69
test: focus browser through product transition
Jul 25, 2026
9d159c9
test: cover visible global search shortcut routing
Jul 25, 2026
d7aa02b
fix: route visible global search popover shortcuts
Jul 25, 2026
3a9f219
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 25, 2026
f3468c6
test: wait for global search popover dismissal
Jul 25, 2026
9c61096
perf: keep popover lookup off ordinary input
Jul 25, 2026
3f27207
test: cover scoped visible global search toggles
Jul 25, 2026
d152133
fix: let visible search own its scoped toggle
Jul 25, 2026
4125458
test: preserve command palette editing ownership
Jul 25, 2026
9f4a5a1
test: stabilize browser shortcut focus harness
Jul 25, 2026
cc7e720
fix: preserve command palette editing shortcuts
Jul 25, 2026
e917d43
test: isolate global hotkey registration probe
Jul 25, 2026
de93f04
test: release omnibar focus before browser shortcut check
Jul 25, 2026
fa60bb0
test: preserve visible search query editing ownership
Jul 25, 2026
35a417a
fix: preserve visible search editing shortcuts
Jul 25, 2026
925c401
test: make browser shortcut focus deterministic
Jul 25, 2026
6d06843
fix: keep search visibility off typing fast path
Jul 25, 2026
3501f6c
test: preserve search editing and media key validation
Jul 25, 2026
3d48f54
test: exercise settings media key rejection in app target
Jul 25, 2026
f71dc91
fix: preserve search editing and shortcut validation
Jul 25, 2026
423c4a2
test: cover global search settings consistency
Jul 25, 2026
7422db6
fix: unify global search shortcut validation
Jul 25, 2026
aa5897b
test: cover global search compatibility gaps
Jul 25, 2026
7f79228
fix: preserve global search editing compatibility
Jul 25, 2026
6fec459
test: cover bare space in visible global search
Jul 25, 2026
baaeb96
fix: keep bare space in visible global search
Jul 25, 2026
f70fcb4
test: cover option dead keys in global search
Jul 25, 2026
76a4595
fix: preserve global search dead-key input
Jul 25, 2026
4510e20
test: reject invalid persisted global search shortcuts
Jul 25, 2026
eba16e9
test: fix global search policy suite isolation
Jul 25, 2026
726ed9e
test: make global search cleanup nonisolated
Jul 25, 2026
d6e2312
test: cover default global search hotkey collision
Jul 25, 2026
83a7f42
fix: enforce persisted shortcut policy
Jul 25, 2026
fab2a97
test: cover shortcut ownership edge cases
Jul 25, 2026
40be8ab
fix: share global search input ownership policy
Jul 25, 2026
d5aab58
test: cover global search local monitor routing
Jul 25, 2026
1e2a1c2
test: fix global search suite project wiring
Jul 25, 2026
ebc8cc8
test: isolate global search monitor chain suite
Jul 25, 2026
951e8e4
fix: route visible global search shortcuts before popover input
Jul 25, 2026
ff2c167
fix: import settings recorder state for search routing
Jul 25, 2026
03bf429
test: use valid search monitor shortcut fixture
Jul 25, 2026
e977ff9
test: cover effective shortcut display policy
Jul 25, 2026
a184573
fix: unify effective shortcut resolution
Jul 25, 2026
697dc6c
fix: avoid search visibility work for unrelated keys
Jul 25, 2026
c75ffcb
test: cover global search shortcut policy edges
Jul 25, 2026
5e348b6
fix: harden global search shortcut policy
Jul 25, 2026
9303fab
test: fix managed shortcut notification coverage
Jul 25, 2026
a205c5f
test: cover shortcut policy review regressions
Jul 25, 2026
f958414
fix: preserve managed shortcut policy invariants
Jul 25, 2026
120b72c
fix: route system-wide shortcut through shared policy
Jul 25, 2026
da7fc36
test: cover invalid managed shortcut display
Jul 25, 2026
5d27490
fix: preserve invalid managed shortcut ownership
Jul 25, 2026
d9dafb8
test: cover shortcut policy closeout regressions
Jul 25, 2026
aef6b0d
fix: unify shortcut persistence and registration policy
Jul 25, 2026
eb9f82b
test: cover shared shortcut catalog parity
Jul 25, 2026
4ce7fec
test: disambiguate shared shortcut catalog
Jul 25, 2026
364f189
fix: keep shared shortcut catalog complete
Jul 26, 2026
adad276
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 26, 2026
40e2d26
chore: keep shared shortcut catalog below file limit
Jul 26, 2026
256c624
test: cover system-wide hotkey settings policy
Jul 26, 2026
c5fc201
fix: align system-wide shortcut registration policy
Jul 26, 2026
3ec16e1
test: cover visible search editing chord suffix
Jul 26, 2026
2cbd54e
test: cover promoted search chord suffix state
Jul 26, 2026
c0a037c
fix: preserve promoted search chord state
Jul 26, 2026
4422307
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 26, 2026
383a096
test: cover legacy global hotkey registration migration
Jul 26, 2026
91a1a29
fix: preserve legacy global hotkey migration
Jul 26, 2026
f9de8c7
refactor: observe shortcut updates with Observation
Jul 26, 2026
332c4b5
fix: cache foreground search shortcut routing
Jul 26, 2026
5f811c1
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 26, 2026
1f7bf6b
fix: make legacy hotkey migration single-pass
Jul 26, 2026
6d11270
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 27, 2026
b030c5f
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 27, 2026
9f9535c
Fix legacy hotkey migration test ordering
Jul 27, 2026
96ea2d5
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 27, 2026
bbcbf81
Fix merged feature flag isolation warning
Jul 27, 2026
0e2af9e
Merge remote-tracking branch 'origin/main' into issue-8561-global-sea…
Jul 27, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ public struct KeyboardShortcutsCatalogSection: SettingCatalogSection {
defaultValue: [:]
)

/// Read projection that preserves every managed action ID even when its
/// binding is malformed. Derived from ``bindings`` so the catalog retains
/// one stored declaration for the `shortcuts.bindings` path.
public var bindingSnapshot: JSONKey<ShortcutBindingsSnapshot> {
JSONKey(
id: bindings.id,
defaultValue: ShortcutBindingsSnapshot(
bindings: [:],
managedActionIDs: []
)
)
}

/// Per-action focus predicates (`shortcuts.when`), keyed by action id, as
/// raw expression strings. The app target owns parsing/evaluation; the
/// Settings UI only needs to know which actions are context-scoped so its
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ extension ShortcutAction {
case .toggleBrowserDeveloperTools: return ShortcutStroke(key: "i", command: true, option: true)
case .showBrowserJavaScriptConsole: return ShortcutStroke(key: "c", command: true, option: true)
case .toggleBrowserFocusMode: return ShortcutStroke(key: "\r", command: true, option: true)
case .toggleBrowserDesignMode: return ShortcutStroke(key: "d", command: true, option: true, control: true)
case .toggleReactGrab: return ShortcutStroke(key: "g", command: true, shift: true)
case .diffViewerScrollDown: return ShortcutStroke(key: "j")
case .diffViewerScrollUp: return ShortcutStroke(key: "k")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ extension ShortcutAction {
case .toggleBrowserDeveloperTools: return "Toggle Browser Developer Tools"
case .showBrowserJavaScriptConsole: return "Show Browser JavaScript Console"
case .toggleBrowserFocusMode: return "Enter Browser Focus Mode"
case .toggleBrowserDesignMode: return String(localized: "shortcut.toggleBrowserDesignMode.label", defaultValue: "Toggle Browser Design Mode")
case .toggleReactGrab: return "Toggle React Grab"
case .diffViewerScrollDown:
return String(localized: "shortcut.diffViewerScrollDown.label", defaultValue: "Viewers: Scroll Down")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ extension ShortcutAction {
.markdownZoomIn, .markdownZoomOut, .markdownZoomReset,
.find, .findInDirectory, .findNext, .findPrevious,
.hideFind, .useSelectionForFind, .toggleBrowserDeveloperTools,
.showBrowserJavaScriptConsole, .toggleBrowserFocusMode, .toggleReactGrab,
.showBrowserJavaScriptConsole, .toggleBrowserFocusMode,
.toggleBrowserDesignMode, .toggleReactGrab,
.diffViewerScrollDown, .diffViewerScrollUp,
.diffViewerScrollHalfPageDown, .diffViewerScrollHalfPageUp,
.diffViewerScrollDownEmacs, .diffViewerScrollUpEmacs,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
extension ShortcutAction {
/// Whether this action rejects `shortcut` because it contains a system-defined media key.
///
/// Global Search is handled by the foreground AppKit key path, which cannot
/// receive media-key events. Other actions retain their existing validation.
///
/// - Parameter shortcut: The shortcut being validated.
/// - Returns: `true` when the shortcut cannot execute for this action.
public func rejectsSystemDefinedMediaKey(_ shortcut: StoredShortcut) -> Bool {
rejectsSystemDefinedMediaKey(
firstKey: shortcut.first.key,
secondKey: shortcut.second?.key
)
}

/// Whether this action rejects shortcut keys from a legacy binding representation.
///
/// - Parameters:
/// - firstKey: The persisted key identifier for the first stroke.
/// - secondKey: The persisted key identifier for the optional chord suffix.
/// - Returns: `true` when either key is a media key this action cannot execute.
public func rejectsSystemDefinedMediaKey(
firstKey: String,
secondKey: String?
) -> Bool {
guard self == .globalSearch else { return false }
return firstKey.lowercased().hasPrefix("media.")
|| secondKey?.lowercased().hasPrefix("media.") == true
}

/// Whether the system reserves this complete shortcut before the action can execute it.
///
/// Command-Period is AppKit's standard cancel keystroke for modal alerts
/// and open/save panels. The system-wide Show/Hide action must not capture
/// that first instinctive cancel press.
///
/// - Parameter shortcut: The shortcut being validated.
/// - Returns: `true` when the shortcut is reserved for system interaction.
public func rejectsSystemReservedShortcut(
_ shortcut: StoredShortcut
) -> Bool {
guard self == .showHideAllWindows, !shortcut.hasChord else {
return false
}
let first = shortcut.first.canonicalized()
return first.key == "."
&& first.command
&& !first.shift
&& !first.option
&& !first.control
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ public enum ShortcutAction: String, CaseIterable, Sendable, Hashable, SettingCod
case toggleBrowserDeveloperTools
case showBrowserJavaScriptConsole
case toggleBrowserFocusMode
/// Toggles design-mode editing for the focused browser.
case toggleBrowserDesignMode
case toggleReactGrab
/// Scrolls the focused diff viewer down one step.
case diffViewerScrollDown
Expand Down Expand Up @@ -245,7 +247,8 @@ extension ShortcutAction {

/// Whether this action supports a two-stroke shortcut chord.
public var allowsChordShortcut: Bool {
self != .fileExplorerOpenSelection
self != .showHideAllWindows
&& self != .fileExplorerOpenSelection
&& self != .fileExplorerOpenSelectionFinderAlias
&& self != .cycleTextBoxSubmitAction
}
Expand All @@ -272,7 +275,8 @@ extension ShortcutAction {
return .and(.not(.atom(.browserFocus)), .not(.atom(.sidebarFocus)))
case .browserBack, .browserForward, .browserReload, .browserHardReload,
.toggleBrowserDeveloperTools, .showBrowserJavaScriptConsole, .toggleBrowserFocusMode,
.diffViewerOpenFileSearch, .diffViewerNextFile, .diffViewerPreviousFile:
.toggleBrowserDesignMode, .diffViewerOpenFileSearch, .diffViewerNextFile,
.diffViewerPreviousFile:
return .atom(.browserFocus)
case .diffViewerScrollDown, .diffViewerScrollUp,
.diffViewerScrollHalfPageDown, .diffViewerScrollHalfPageUp,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/// The action-owned validity of a persisted shortcut's shape.
///
/// Persistence adapters use this result before accepting a binding so Settings,
/// `cmux.json`, and legacy `UserDefaults` cannot disagree about whether an
/// action can execute the stored shortcut.
public enum ShortcutBindingPolicyResult: Sendable, Equatable {
/// The action can execute the shortcut shape.
case accepted

/// The action requires a modifier on this first stroke.
case bareFirstStrokeNotAllowed

/// The system-wide action requires Command, Option, or Control.
case primaryModifierRequired

/// The action does not support two-stroke shortcuts.
case chordNotAllowed

/// The foreground action cannot receive a system-defined media key.
case systemDefinedMediaKeyNotAllowed

/// The system reserves this shortcut before the action can execute it.
case systemReservedShortcutNotAllowed
}

extension ShortcutAction {
/// Validates the representation-independent shape of a persisted shortcut.
///
/// This policy intentionally excludes conflicts with other actions because
/// conflict checks require the caller's complete effective-binding snapshot.
///
/// - Parameter shortcut: The shortcut loaded or proposed by a persistence adapter.
/// - Returns: The action-owned validity of the shortcut shape.
public func shortcutBindingPolicyResult(
for shortcut: StoredShortcut
) -> ShortcutBindingPolicyResult {
guard !shortcut.isUnbound else { return .accepted }

if shortcut.hasChord && !allowsChordShortcut {
return .chordNotAllowed
}
if rejectsSystemDefinedMediaKey(shortcut) {
return .systemDefinedMediaKeyNotAllowed
}
if rejectsSystemReservedShortcut(shortcut) {
return .systemReservedShortcutNotAllowed
}

let first = shortcut.first
if self == .showHideAllWindows,
!first.command,
!first.option,
!first.control {
return .primaryModifierRequired
}
let supportsLegacyBareSpace = first.key.lowercased() == "space"
&& self != .globalSearch
guard allowsBareFirstStroke
|| first.hasAnyModifier
|| supportsLegacyBareSpace else {
return .bareFirstStrokeNotAllowed
}

return .accepted
}

/// Resolves a persisted candidate into the shortcut the action executes.
///
/// Unsupported Show/Hide candidates fail closed because silently registering
/// its default would create an unexpected system-wide hotkey. Other actions
/// fall back to their valid built-in default.
///
/// - Parameters:
/// - candidate: The configured shortcut, or `nil` when no override exists.
/// - conflictsWithReservedShortcut: Whether a normalized shortcut is reserved
/// by a higher-priority system-wide binding.
/// - Returns: The executable shortcut, or `nil` when the action is unbound.
public func effectivePersistedShortcut(
_ candidate: StoredShortcut?,
conflictsWithReservedShortcut: (StoredShortcut) -> Bool = { _ in false }
) -> StoredShortcut? {
effectivePersistedShortcut(
candidate,
normalizing: { shortcut in
shortcutBindingPolicyResult(for: shortcut) == .accepted
? shortcut.canonicalized()
: nil
},
conflictsWithReservedShortcut: conflictsWithReservedShortcut
)
}

/// Resolves a persisted candidate using the consumer's runtime normalizer.
///
/// - Parameters:
/// - candidate: The configured shortcut, or `nil` when no override exists.
/// - normalizing: Returns the executable representation of a shortcut, or
/// `nil` when the consumer cannot execute it.
/// - conflictsWithReservedShortcut: Whether a normalized shortcut is reserved
/// by a higher-priority system-wide binding.
/// - Returns: The executable shortcut, or `nil` when the action is unbound.
public func effectivePersistedShortcut(
_ candidate: StoredShortcut?,
normalizing: (StoredShortcut) -> StoredShortcut?,
conflictsWithReservedShortcut: (StoredShortcut) -> Bool
) -> StoredShortcut? {
if let candidate {
if candidate.isUnbound {
return nil
}
if let normalized = normalizing(candidate),
!conflictsWithReservedShortcut(normalized) {
return normalized
}
if self == .showHideAllWindows {
return nil
}
}

guard let defaultShortcut,
!defaultShortcut.isUnbound,
let normalizedDefault = normalizing(defaultShortcut),
!conflictsWithReservedShortcut(normalizedDefault) else {
return nil
}
return normalizedDefault
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Foundation

/// A decoded shortcut-binding map plus every action ID managed by that map.
///
/// Shortcut values are decoded independently so one malformed binding cannot
/// hide valid siblings. ``managedActionIDs`` retains malformed entries as
/// managed, allowing consumers to suppress lower-precedence legacy values.
public struct ShortcutBindingsSnapshot: Sendable, Equatable, SettingCodable {
/// Successfully decoded bindings, keyed by shortcut action ID.
public let bindings: [String: StoredShortcut]

/// Every action ID present in the persisted map, including invalid entries.
public let managedActionIDs: Set<String>

/// Creates a shortcut-binding snapshot.
///
/// - Parameters:
/// - bindings: Successfully decoded shortcut bindings.
/// - managedActionIDs: Every action ID present in the persisted map.
public init(
bindings: [String: StoredShortcut],
managedActionIDs: Set<String>
) {
self.bindings = bindings
self.managedActionIDs = managedActionIDs
}

/// Decodes a property-list binding map while retaining every action ID.
public static func decodeFromUserDefaults(_ raw: Any?) -> ShortcutBindingsSnapshot? {
decode(raw, using: StoredShortcut.decodeFromUserDefaults(_:))
}

/// Encodes the successfully decoded bindings for property-list storage.
public func encodeForUserDefaults() -> Any {
bindings.mapValues { $0.encodeForUserDefaults() }
}

/// Decodes a JSON binding map while retaining every action ID.
public static func decodeFromJSON(_ raw: Any?) -> ShortcutBindingsSnapshot? {
decode(raw, using: StoredShortcut.decodeFromJSON(_:))
}

/// Encodes the successfully decoded bindings for JSON storage.
public func encodeForJSON() -> Any {
bindings.mapValues { $0.encodeForJSON() }
}

private static func decode(
_ raw: Any?,
using decodeValue: (Any?) -> StoredShortcut?
) -> ShortcutBindingsSnapshot? {
guard let dictionary = raw as? [String: Any] else { return nil }

var bindings: [String: StoredShortcut] = [:]
bindings.reserveCapacity(dictionary.count)
for (actionID, rawValue) in dictionary {
bindings[actionID] = decodeValue(rawValue)
}
return ShortcutBindingsSnapshot(
bindings: bindings,
managedActionIDs: Set(dictionary.keys)
)
}
}
Loading
Loading