Skip to content

Fix editable shortcuts from settings.json - #3462

Merged
austinywang merged 7 commits into
mainfrom
issue-3461-shortcut-file-managed-editable
May 4, 2026
Merged

austinywang merged 7 commits into
mainfrom
issue-3461-shortcut-file-managed-editable

Conversation

@austinywang

@austinywang austinywang commented May 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Treat cmux.json shortcut bindings as lower-priority defaults so Settings UI edits stored in UserDefaults win without rewriting the file
  • Remove the managed-file subtitle/disabled state from shortcut recorder rows and delete the unused localized string
  • Allow shortcut conflict swaps, reset behavior, and system-wide hotkey fallback to work consistently for file-backed shortcut defaults
  • Keep startup parsing independent of the live settings-file store so launch does not re-enter shortcut store initialization

Fixes #3461

Verification

  • git diff --check
  • jq empty Resources/Localizable.xcstrings
  • ./scripts/reload.sh --tag issue-3461-shortcut-file-managed-editable

Commit structure

  • d28dc22a: failing regression tests only
  • 6d364889: implementation fix
  • 3ded8224: parser/startup regression fix
  • 7d22d1b5: move parser regression test out of oversized file
  • ea1a48e8: failing regression expectation for editable file-backed shortcuts
  • 29422515: implementation/localization cleanup

Note

Low Risk
Low risk: test-only change that adds coverage around keyboard shortcut settings parsing behavior without modifying production logic.

Overview
Adds a new regression test in KeyboardShortcutSettingsFileStoreMigrationTests to ensure parsing shortcuts from a settings file does not consult the live/global KeyboardShortcutSettings.settingsFileStore, guarding against recursive initialization and verifying numbered-shortcut canonicalization (e.g., cmd+2 -> workspace 1).

Reviewed by Cursor Bugbot for commit 7d22d1b. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Treat settings.json shortcuts as defaults so Settings edits saved in UserDefaults win without rewriting the file, and remove the managed/disabled UI. Startup parsing is now independent of the live store to avoid recursive init; numbered shortcuts still canonicalize. Fixes #3461.

  • Bug Fixes
    • Resolution order: UserDefaults > settings.json > built-in defaults (for app shortcuts and the system-wide hotkey).
    • File-backed shortcuts stay editable; conflict swaps and reset work. Reset reveals the file default.
    • Remove the “managed by file” subtitle and disabled state; delete settings.shortcuts.managedByFile.
    • Parse file-backed shortcuts at startup without consulting the live store; keep numbered-shortcut canonicalization. Add tests for precedence/UI override, swap/reset, no managed subtitle, and parser independence.
    • Move the numbered-shortcut parser regression test to KeyboardShortcutSettingsFileStoreMigrationTests.swift to satisfy the CI file-length budget.

Written for commit 2942251. Summary will update on new commits.

Summary by CodeRabbit

  • UI Changes

    • Removed “managed by file” subtitle and disabled state from shortcut controls so managed shortcuts are editable in the UI.
    • Removed the corresponding localization entry for the managed-by-file subtitle.
  • Behavior Changes

    • Persisted (user) shortcuts now take precedence over settings-file values; settings-file values can be overridden from the UI.
  • Tests

    • Added/updated tests to validate parsing and the new override precedence.

The settings file is a default source, not a lock on shortcut editing. This regression test captures the expected UI override precedence and the absence of a managed subtitle before the production code is changed.

Constraint: Issue #3461 requires a failing regression test before the fix.

Confidence: high

Scope-risk: narrow

Tested: Not run; regression-only commit is intended to fail before the fix.

Not-tested: Full unit suite and app build deferred until fix commit.
File-configured shortcuts are defaults, not an authority boundary. The shortcut resolver now checks UserDefaults first, then the settings file, then built-in defaults, so Settings edits and conflict swaps use the existing UI persistence path without rewriting settings.json.

Constraint: Issue #3461 requires file-set shortcuts to remain editable from Settings.

Rejected: Keep file precedence with override tombstones | adds a second exception layer for the same shortcut value.

Confidence: high

Scope-risk: narrow

Directive: Do not use settings-file provenance to disable shortcut UI writes; it is only a lower-priority default source.

Tested: git diff --check; jq empty Resources/Localizable.xcstrings

Not-tested: Local unit suite per repo testing policy; final tagged reload build still pending.
@vercel

vercel Bot commented May 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 4, 2026 4:46am
cmux-staging Building Building Preview, Comment May 4, 2026 4:46am

@coderabbitai

coderabbitai Bot commented May 4, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Reorders keyboard-shortcut resolution to prefer persisted UserDefaults over settings-file overrides, removes UI disabling/subtitle for settings-file-managed shortcuts, allows swap/persist even when actions were file-managed, adds tests (including one ensuring file-store parsing doesn't consult live store), and deletes a localization key.

Changes

Shortcut resolution, UI, and swap behavior

Layer / File(s) Summary
Behavior / Data Shape
Sources/KeyboardShortcutSettings.swift
Change: shortcut(for:) now reads/decodes from UserDefaults first and falls back to settingsFileStore.override(...) only on decode failure; removed settingsFileManagedSubtitle(...).
Core Logic
Sources/KeyboardShortcutSettings.swift
Removed managed-by-file gating from swapShortcutConflict(...) so swaps/persistence proceed regardless of file-managed status.
Validation Presentation
Sources/KeyboardShortcutSettings.swift
ShortcutRecorderValidationPresentation initializer signature changed: removed isManagedBySettingsFile parameter; canSwapConflict(...) no longer blocks swaps based on managed status.
UI Wiring
Sources/KeyboardShortcutSettingsControls.swift, Sources/cmuxApp.swift
Removed passing subtitle and isDisabled derived from settings-file managed state to ShortcutRecorderSettingsControl; controls no longer render a managed subtitle or disable editing.
Tests Updated
cmuxTests/WorkspaceUnitTests.swift
Updated tests to reflect inverse precedence (persisted UserDefaults win) and changed reset behavior for UI overrides to fall back to defaults.
Localization
Resources/Localizable.xcstrings
Removed settings.shortcuts.managedByFile localization key and its translations.

File-store parsing isolation test

Layer / File(s) Summary
Test Setup & Isolation
cmuxTests/KeyboardShortcutSettingsFileStoreMigrationTests.swift
Adds testManagedShortcutParsingDoesNotConsultLiveStore which saves/restores KeyboardShortcutSettings.settingsFileStore, writes separate temporary live and canonical JSON files, instantiates a file store pointing only to the canonical file, calls KeyboardShortcutSettings.resetAll(), and asserts the parsed override comes from the canonical file only.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through code with eager feet,
Swapped a check and tuned a beat.
File and live now know their place,
UI unlocked — no subtitle trace.
Tests sing true beneath the moonlit seat. 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix editable shortcuts from settings.json' clearly describes the main change: enabling shortcuts configured in settings.json to be editable in the UI.
Linked Issues check ✅ Passed The changes directly address issue #3461 requirements: shortcuts from settings.json are now editable, the managed subtitle/disabled state is removed, file shortcuts act as defaults (UserDefaults takes precedence), and conflict swaps work consistently.
Out of Scope Changes check ✅ Passed All code changes are directly related to fixing editable shortcuts from settings.json. Modifications include test additions, localization string removal, shortcut settings logic updates, and UI control updates—all scoped to the linked issue.
Description check ✅ Passed Pull request provides comprehensive coverage of objectives, verification steps, and commit structure. All required template sections are present and well-populated.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-3461-shortcut-file-managed-editable

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the shortcut priority order so that Settings UI edits (stored in UserDefaults) win over settings.json-defined shortcuts without modifying the file. The lookup order is now UserDefaults → settingsFile → action.defaultShortcut, and the guards that previously blocked setShortcut/swapShortcutConflict for file-managed actions are removed along with the associated "Managed in settings.json" UI affordances.

Confidence Score: 4/5

Safe to merge; the priority inversion is correctly implemented, tests are isolated via resetAll(), and no production call site is broken.

Only P2 findings (dead-code stub settingsFileManagedSubtitle that always returns nil). The core logic change is well-tested with new cases covering override, swap, and reset interactions.

Sources/KeyboardShortcutSettings.swift — the permanently-nil settingsFileManagedSubtitle stub (and its mirror in SystemWideHotkeySettings) can be cleaned up.

Important Files Changed

Filename Overview
Sources/KeyboardShortcutSettings.swift Inverts shortcut lookup priority (UserDefaults > settingsFile > default), removes managed-file guards from setShortcut/swapShortcutConflict, and drops isManagedBySettingsFile checks from validation UI — logic is correct but settingsFileManagedSubtitle is now permanently dead.
Sources/cmuxApp.swift Removes subtitle, isDisabled, and isManagedBySettingsFile call sites from ShortcutSettingRow, ShortcutRecorderSettingsControl, and GlobalHotkeySection — straightforward cleanup with no new logic.
cmuxTests/WorkspaceUnitTests.swift Rewrites and expands tests to cover the new priority order (UserDefaults wins over file, reset reveals file default, swap works for file-managed shortcuts); setUp/tearDown call resetAll() to isolate UserDefaults state.
Resources/Localizable.xcstrings Removes the now-unused settings.shortcuts.managedByFile localization key (EN, JA, KO).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["shortcut(for: action)"] --> B{UserDefaults\nhas value?}
    B -- Yes --> C[Return UserDefaults shortcut]
    B -- No --> D{settings.json\nhas override?}
    D -- Yes --> E[Return settingsFile shortcut]
    D -- No --> F[Return action.defaultShortcut]

    G["setShortcut(_:for:)"] --> H[Write to UserDefaults\nalways — no managed guard]

    I["resetShortcut(for:)"] --> J[Remove UserDefaults entry]
    J --> K[settingsFile default\nnow visible again]
Loading

Reviews (1): Last reviewed commit: "Let UI shortcut edits override file defa..." | Re-trigger Greptile

Comment thread Sources/KeyboardShortcutSettings.swift Outdated
Comment on lines 666 to 668
static func settingsFileManagedSubtitle(for _: Action) -> String? {
nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Dead code stub can be removed

settingsFileManagedSubtitle(for:) now unconditionally returns nil, making it dead code. Since it's no longer referenced from cmuxApp.swift or any production call site (only in the stub on SystemWideHotkeySettings at line 818 and in test assertions), both can be deleted. Keeping a permanently-nil function risks future callers incorrectly assuming it carries meaning.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/KeyboardShortcutSettings.swift (1)

632-660: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ignore semantically invalid persisted overrides before they win precedence.

These paths now return any decodable UserDefaults payload ahead of settings.json and built-in defaults. For .showHideAllWindows and numbered actions, a stale or hand-edited value can therefore mask a valid file/default shortcut indefinitely; for the global hotkey, that also means registration stays disabled instead of falling back.

Suggested fix
 fileprivate static func persistedShortcut(
     for action: Action,
     defaults: UserDefaults = .standard
 ) -> StoredShortcut? {
     guard let data = defaults.data(forKey: action.defaultsKey),
-          let shortcut = try? JSONDecoder().decode(StoredShortcut.self, from: data) else {
+          let shortcut = try? JSONDecoder().decode(StoredShortcut.self, from: data) else {
         return nil
     }
-    return shortcut
+    return storedShortcutForPersistence(shortcut, action: action)
 }

Also applies to: 844-850

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/KeyboardShortcutSettings.swift` around lines 632 - 660,
persistedShortcut(for:) and shortcut(for:) currently accept any decodable
StoredShortcut from UserDefaults and let it take precedence over
settingsFileStore.override(for:) and action.defaultShortcut, which allows
semantically invalid/stale overrides (e.g., for .showHideAllWindows, numbered
actions, or global hotkey) to disable or mask valid shortcuts; update
shortcut(for:) (and the analogous logic at the other location mentioned) to
validate the decoded StoredShortcut before returning it — call the existing
validation logic (or reproduce the same checks you use when loading
settingsFileStore overrides) to ensure the persisted shortcut is semantically
valid for the given Action, and only return/persist it if valid, otherwise
ignore the persisted value and fall back to settingsFileStore.override(for:) or
action.defaultShortcut.
🧹 Nitpick comments (2)
cmuxTests/WorkspaceUnitTests.swift (2)

1044-1053: ⚡ Quick win

Add isManagedBySettingsFile assertion after the live UI override.

The test verifies that the UI shortcut wins and the settings file is not rewritten, but it doesn't confirm that isManagedBySettingsFile(.newWindow) remains true after setShortcut. The companion test testPersistedShortcutOverridesSettingsFileDefault (line 1015) checks this invariant for the pre-load scenario; a symmetric check here would catch an implementation regression where a live edit accidentally clears the managed state (which would silently break the reset-reveal behavior tested elsewhere).

♻️ Proposed addition
     XCTAssertEqual(KeyboardShortcutSettings.shortcut(for: .newWindow), uiShortcut)
+    XCTAssertTrue(KeyboardShortcutSettings.isManagedBySettingsFile(.newWindow))
     XCTAssertEqual(
         try String(contentsOf: settingsFileURL, encoding: .utf8),
         originalSettingsFileContents
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/WorkspaceUnitTests.swift` around lines 1044 - 1053, After verifying
the UI shortcut override, add an assertion that the managed-state is preserved:
call KeyboardShortcutSettings.isManagedBySettingsFile(.newWindow) after
KeyboardShortcutSettings.setShortcut(uiShortcut, for: .newWindow) and assert it
remains true so live UI overrides don't clear the settings-file-managed flag
(relate to KeyboardShortcutSettings.shortcut(for:), setShortcut(_:for:), and
isManagedBySettingsFile(_:)).

1118-1127: ⚡ Quick win

Verify the settings file is not mutated by swapShortcutConflict.

testUserDefaultsShortcutOverrideWinsOverSettingsFileShortcut captures originalSettingsFileContents and asserts file immutability after a single setShortcut call. The same contract applies to swapShortcutConflict (both shortcuts are file-backed; the PR specifically calls out that swap behavior must work consistently for file-backed defaults without rewriting the file), but this test has no equivalent check. A regression where swapShortcutConflict writes back to the file would go undetected.

♻️ Proposed addition
+    let originalSettingsFileContents = try String(contentsOf: settingsFileURL, encoding: .utf8)
+
     KeyboardShortcutSettings.settingsFileStore = KeyboardShortcutSettingsFileStore(
         primaryPath: settingsFileURL.path,
         fallbackPath: nil,
         startWatching: false
     )

     KeyboardShortcutSettings.swapShortcutConflict(
         proposedShortcut: proposedNewWindowShortcut,
         currentAction: .newWindow,
         conflictingAction: .newTab,
         previousShortcut: previousNewWindowShortcut
     )

     XCTAssertEqual(KeyboardShortcutSettings.shortcut(for: .newWindow), proposedNewWindowShortcut)
     XCTAssertEqual(KeyboardShortcutSettings.shortcut(for: .newTab), previousNewWindowShortcut)
+    XCTAssertEqual(
+        try String(contentsOf: settingsFileURL, encoding: .utf8),
+        originalSettingsFileContents
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/WorkspaceUnitTests.swift` around lines 1118 - 1127, Add an
assertion that the settings file is not mutated by
KeyboardShortcutSettings.swapShortcutConflict: before calling
swapShortcutConflict in the test, capture the file contents exactly as done in
testUserDefaultsShortcutOverrideWinsOverSettingsFileShortcut (e.g.
originalSettingsFileContents) and after the swap assert the on-disk contents are
equal to originalSettingsFileContents; keep the existing assertions that
KeyboardShortcutSettings.shortcut(for: .newWindow) and .newTab reflect the
in-memory swap but ensure swapShortcutConflict does not rewrite the file-backed
settings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@Sources/KeyboardShortcutSettings.swift`:
- Around line 632-660: persistedShortcut(for:) and shortcut(for:) currently
accept any decodable StoredShortcut from UserDefaults and let it take precedence
over settingsFileStore.override(for:) and action.defaultShortcut, which allows
semantically invalid/stale overrides (e.g., for .showHideAllWindows, numbered
actions, or global hotkey) to disable or mask valid shortcuts; update
shortcut(for:) (and the analogous logic at the other location mentioned) to
validate the decoded StoredShortcut before returning it — call the existing
validation logic (or reproduce the same checks you use when loading
settingsFileStore overrides) to ensure the persisted shortcut is semantically
valid for the given Action, and only return/persist it if valid, otherwise
ignore the persisted value and fall back to settingsFileStore.override(for:) or
action.defaultShortcut.

---

Nitpick comments:
In `@cmuxTests/WorkspaceUnitTests.swift`:
- Around line 1044-1053: After verifying the UI shortcut override, add an
assertion that the managed-state is preserved: call
KeyboardShortcutSettings.isManagedBySettingsFile(.newWindow) after
KeyboardShortcutSettings.setShortcut(uiShortcut, for: .newWindow) and assert it
remains true so live UI overrides don't clear the settings-file-managed flag
(relate to KeyboardShortcutSettings.shortcut(for:), setShortcut(_:for:), and
isManagedBySettingsFile(_:)).
- Around line 1118-1127: Add an assertion that the settings file is not mutated
by KeyboardShortcutSettings.swapShortcutConflict: before calling
swapShortcutConflict in the test, capture the file contents exactly as done in
testUserDefaultsShortcutOverrideWinsOverSettingsFileShortcut (e.g.
originalSettingsFileContents) and after the swap assert the on-disk contents are
equal to originalSettingsFileContents; keep the existing assertions that
KeyboardShortcutSettings.shortcut(for: .newWindow) and .newTab reflect the
in-memory swap but ensure swapShortcutConflict does not rewrite the file-backed
settings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7c2085d-1f11-4492-8182-e08f7f620426

📥 Commits

Reviewing files that changed from the base of the PR and between 0c532c0 and 6d36488.

📒 Files selected for processing (4)
  • Resources/Localizable.xcstrings
  • Sources/KeyboardShortcutSettings.swift
  • Sources/cmuxApp.swift
  • cmuxTests/WorkspaceUnitTests.swift
💤 Files with no reviewable changes (1)
  • Resources/Localizable.xcstrings

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6d36488. Configure here.

Comment thread Sources/KeyboardShortcutSettings.swift
Settings-file shortcut parsing ran through the same conflict resolver used by interactive shortcut recording. During app startup that resolver can query KeyboardShortcutSettings.settingsFileStore while CmuxSettingsFileStore.shared is still initializing, which trips libdispatch's recursive once guard.

The parser now applies only action-local normalization for managed settings file values, preserving numbered shortcut canonicalization and invalid managed global-hotkey behavior without consulting the live shortcut store.

Constraint: Startup parsing must not depend on the KeyboardShortcutSettings.settingsFileStore singleton it is initializing
Rejected: Add an initialization flag around conflict detection | preserves the circular dependency and risks parser behavior changing by call timing
Confidence: high
Scope-risk: narrow
Directive: Keep settings-file parsing independent from live shortcut lookup; conflict validation belongs in interactive recording/persistence paths
Tested: ./scripts/reload.sh --tag codex-launch --launch; process stayed alive after startup
Not-tested: Local XCTest suite per repo policy
Pulled origin/main after committing the startup parser fix and resolved overlapping settings shortcut conflicts in favor of the newer main structure. The pulled main line moves shortcut recorder/settings-row UI into dedicated files, treats cmux.json shortcuts as managed settings, and adds settings navigation/search surfaces.

The parser keeps the non-recursive managed-file normalization path from main and retains the numbered-shortcut regression coverage adapted to cmux.json.

Constraint: Pull was requested after the local crash fix commit
Rejected: Keep the older inline settings modal shortcut controls | main has already split them into dedicated components and added managed-file UI behavior
Confidence: medium
Scope-risk: broad
Directive: Settings modal appearance differences after this merge are expected from origin/main's SettingsNavigation, SettingsShellLab, and KeyboardShortcutSettingsControls changes
Tested: git diff --cached --check
Not-tested: Local XCTest/build suite per repo policy
The PR failed CI's Swift file-length budget because the new managed-shortcut parser regression was added to WorkspaceUnitTests.swift, which is already budgeted exactly at its current size.

Move the same behavioral coverage into KeyboardShortcutSettingsFileStoreMigrationTests.swift, a smaller settings-file test file already included in the Xcode project, and restore WorkspaceUnitTests.swift to its budgeted length.

Constraint: CI enforces .github/swift-file-length-budget.tsv and rejects growth in oversized Swift files
Rejected: Refresh the file-length budget | this regression test can live in a smaller settings-file test file without accepting new debt
Confidence: high
Scope-risk: narrow
Directive: Keep new settings-file parser coverage out of WorkspaceUnitTests.swift unless the file is split or the budget is intentionally refreshed
Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv; git diff --check
Not-tested: Local XCTest suite per repo policy

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cmuxTests/KeyboardShortcutSettingsFileStoreMigrationTests.swift (1)

123-127: ⚡ Quick win

Pass additionalFallbackPaths: [] to both test stores to ensure full isolation.

Neither KeyboardShortcutSettingsFileStore initializer call in this test passes additionalFallbackPaths: [], so both inherit the default — which includes CmuxSettingsFileStore.defaultApplicationSupportFallbackPath. On a developer machine (or a CI agent that has run cmux before), a real settings file at that path could bleed into the test's resolved shortcuts, potentially producing a false pass or false fail on the key: "1" assertion. This is especially important for a test whose explicit purpose is store isolation.

♻️ Proposed fix
-        KeyboardShortcutSettings.settingsFileStore = KeyboardShortcutSettingsFileStore(
-            primaryPath: liveSettingsFileURL.path,
-            fallbackPath: nil,
-            startWatching: false
-        )
+        KeyboardShortcutSettings.settingsFileStore = KeyboardShortcutSettingsFileStore(
+            primaryPath: liveSettingsFileURL.path,
+            fallbackPath: nil,
+            additionalFallbackPaths: [],
+            startWatching: false
+        )
-        let store = KeyboardShortcutSettingsFileStore(
-            primaryPath: settingsFileURL.path,
-            fallbackPath: nil,
-            startWatching: false
-        )
+        let store = KeyboardShortcutSettingsFileStore(
+            primaryPath: settingsFileURL.path,
+            fallbackPath: nil,
+            additionalFallbackPaths: [],
+            startWatching: false
+        )

Also applies to: 141-145

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/KeyboardShortcutSettingsFileStoreMigrationTests.swift` around lines
123 - 127, The test creates two KeyboardShortcutSettingsFileStore instances but
omits additionalFallbackPaths, so they inherit
CmuxSettingsFileStore.defaultApplicationSupportFallbackPath and may read real
user data; update both initializer calls for KeyboardShortcutSettingsFileStore
(the one assigned to KeyboardShortcutSettings.settingsFileStore and the other
later in the test) to pass additionalFallbackPaths: [] to ensure full isolation
of the stores and deterministic test behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmuxTests/KeyboardShortcutSettingsFileStoreMigrationTests.swift`:
- Around line 123-127: The test creates two KeyboardShortcutSettingsFileStore
instances but omits additionalFallbackPaths, so they inherit
CmuxSettingsFileStore.defaultApplicationSupportFallbackPath and may read real
user data; update both initializer calls for KeyboardShortcutSettingsFileStore
(the one assigned to KeyboardShortcutSettings.settingsFileStore and the other
later in the test) to pass additionalFallbackPaths: [] to ensure full isolation
of the stores and deterministic test behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 236308ef-43b6-4a56-bce6-3d38b4fac9ec

📥 Commits

Reviewing files that changed from the base of the PR and between e6dfd6b and 7d22d1b.

📒 Files selected for processing (1)
  • cmuxTests/KeyboardShortcutSettingsFileStoreMigrationTests.swift

The PR now expects settings-file shortcut bindings to act as defaults rather than locks. These assertions cover UserDefaults precedence, UI edits, reset fallback, and removal of the managed-file subtitle expectation.

Constraint: Regression-test policy asks for a failing test commit before the fix commit
Confidence: high
Scope-risk: narrow
Directive: Keep settings-file shortcut tests behavior-level rather than source-shape checks
Tested: Not run independently; this commit is intended to fail before the implementation commit
Not-tested: Full unit suite for this intermediate failing commit
Settings-file shortcut bindings now provide fallback defaults after UserDefaults, so Settings edits persist without rewriting cmux.json and reset reveals the file-provided value again. The recorder rows no longer show or enforce the old managed-file disabled state, and the unused localized subtitle string is removed.

Constraint: Settings UI edits must not rewrite cmux.json while still allowing reset to reveal the file default
Rejected: Treat cmux.json shortcuts as managed immutable values | blocked user edits from Settings and conflicted with issue #3461
Confidence: high
Scope-risk: narrow
Directive: Preserve UserDefaults > cmux.json > built-in default shortcut precedence
Tested: git diff --check; jq empty Resources/Localizable.xcstrings; ./scripts/reload.sh --tag issue-3461-shortcut-file-managed-editable
Not-tested: Local XCTest suite per repo policy; CI will run PR checks

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cmuxTests/WorkspaceUnitTests.swift (1)

1064-1073: ⚡ Quick win

Consider adding an isManagedBySettingsFile assertion after setShortcut to make the invariant explicit.

After line 1067, the test has verified that setShortcut stores the UI-edited value over the file default, but it does not directly assert that the action is still considered file-managed at that point. The reset path at line 1073 provides indirect evidence (it returns managedShortcut, which only happens if the file-managed status is preserved), but an explicit check would make the intent self-documenting.

🔍 Suggested addition
        XCTAssertEqual(KeyboardShortcutSettings.shortcut(for: .newTab), editedShortcut)
+       XCTAssertTrue(KeyboardShortcutSettings.isManagedBySettingsFile(.newTab),
+                     "setShortcut must not strip file-managed status")

        KeyboardShortcutSettings.resetShortcut(for: .newTab)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/WorkspaceUnitTests.swift` around lines 1064 - 1073, Add an explicit
assertion that the shortcut remains file-managed after calling
KeyboardShortcutSettings.setShortcut(editedShortcut, for: .newTab) by checking
KeyboardShortcutSettings.isManagedBySettingsFile(for: .newTab) immediately after
the set; this makes the invariant explicit before calling resetShortcut(for:
.newTab) and complements the existing XCTAssertEqual that verifies the stored
value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmuxTests/WorkspaceUnitTests.swift`:
- Around line 1064-1073: Add an explicit assertion that the shortcut remains
file-managed after calling KeyboardShortcutSettings.setShortcut(editedShortcut,
for: .newTab) by checking KeyboardShortcutSettings.isManagedBySettingsFile(for:
.newTab) immediately after the set; this makes the invariant explicit before
calling resetShortcut(for: .newTab) and complements the existing XCTAssertEqual
that verifies the stored value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7abfabc5-0c3e-43d7-b13e-b9effcaa9f93

📥 Commits

Reviewing files that changed from the base of the PR and between 7d22d1b and 2942251.

📒 Files selected for processing (5)
  • Resources/Localizable.xcstrings
  • Sources/KeyboardShortcutSettings.swift
  • Sources/KeyboardShortcutSettingsControls.swift
  • Sources/cmuxApp.swift
  • cmuxTests/WorkspaceUnitTests.swift
💤 Files with no reviewable changes (1)
  • Resources/Localizable.xcstrings

This branch was successfully deployed

1 active deployment
Preview – cmux — 29422515 Deployed May 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shortcuts set via settings.json show "Managed in cmux.json" and become uneditable in the UI

1 participant