Skip to content

fix: make Ctrl+P command palette navigation remappable (#1713) - #3335

Merged
austinywang merged 27 commits into
mainfrom
issue-1713-ctrl-p-remap-unbind
May 5, 2026
Merged

austinywang merged 27 commits into
mainfrom
issue-1713-ctrl-p-remap-unbind

Conversation

@austinywang

@austinywang austinywang commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1713

Root cause

Ctrl+P command palette navigation lived in commandPaletteSelectionDeltaForKeyboardNavigation as a hardcoded keyCode == 35 / Ctrl+P branch, outside KeyboardShortcutSettings. Remapping or clearing keybinding settings could not stop cmux from consuming Ctrl+P while the palette was interactive.

Fix

  • Adds commandPaletteNext and commandPalettePrevious as first-class KeyboardShortcutSettings actions, defaulting to Ctrl+N and Ctrl+P.
  • Adds an explicit unbound shortcut state and a Settings clear button so cleared shortcuts do not fall back to defaults.
  • Allows settings.json unbinding with null, an empty string, none, unbound, or disabled.
  • Routes command palette selection movement through configured shortcuts, so unbound Ctrl+P can pass through to the active terminal keyDown path.
  • Updates shortcut docs, schema, and localized labels.
  • Fixes tagged dev reloads on this machine by selecting a native-arch Zig for the Ghostty CLI helper, reusing the Xcode-built helper, and retrying LaunchServices with open -n -g when plain open -g returns -600.

Test plan

  • Added regression tests in commit 98b29a7d before the fix commit.
  • Ran jq empty Resources/Localizable.xcstrings web/messages/en.json web/messages/ja.json web/data/cmux-settings.schema.json.
  • Ran git diff --check.
  • Ran bash -n scripts/build-ghostty-cli-helper.sh scripts/reload.sh.
  • Verified scripts/build-ghostty-cli-helper.sh --target aarch64-macos selects /opt/homebrew/bin/zig and produces an arm64 helper even when /usr/local/bin is first in PATH.
  • Ran ./scripts/reload.sh --tag issue-1713-ctrl-p-remap-unbind --launch; the tagged dev app built and launched successfully.

Manual verification

The tagged app now launches locally. Accessibility automation is blocked on this machine by Apple event error -1743, so UI-driven Settings verification is still not automated from this shell.


Note

Medium Risk
Changes global shortcut lookup/routing for the command palette (including chord handling and terminal pass-through), which can affect key handling across the app. Also updates dev build/reload scripts for Zig selection and app launching, which may impact local developer workflows.

Overview
Command palette navigation is now fully configurable. Adds new shortcut actions commandPaletteNext/commandPalettePrevious (defaults ⌃N/⌃P), moves navigation detection into CommandPaletteShortcutRouting, and updates AppDelegate/command palette field editor handling so remapped or unbound keys are no longer hardcoded and can pass through to the focused terminal.

Shortcut settings now treat “unbound” as a first-class state. Introduces KeyboardShortcutSettingsLookup with shortcutIfBound, prevents writes/swaps when a shortcut is managed by cmux.json, updates UI to show a “Managed in cmux.json” subtitle and disable recording, and expands config/schema/docs to allow unbinding via null, empty string, and additional aliases (including disabled).

Tests and tooling updates. Adds regression tests for remapping/unbinding/managed-file behavior, updates localized strings/docs/site data, and improves scripts to select an arch-appropriate zig, preserve an Xcode-built Ghostty helper when present, and retry LaunchServices with open -n -g on failure.

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

Summary by CodeRabbit

  • New Features

    • Command Palette navigation shortcuts: next/previous added (defaults ⌃N / ⌃P), with chord support and plain-arrow navigation; unbinding lets keys pass through to the terminal.
    • Settings UI shows when a shortcut is managed by a settings file and disables recording for those shortcuts.
  • Documentation

    • Updated docs and README to show new shortcuts and expanded unbind options (null, empty, "none", "clear", "unbound", "disabled").
  • Localization

    • Added localized labels/subtitles for the new shortcuts and managed-state notice.
  • Tests

    • Added tests for remapping, unbinding, managed-file behavior, and routing.

@vercel

vercel Bot commented Apr 30, 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 5, 2026 3:29am
cmux-staging Building Building Preview, Comment May 5, 2026 3:29am

@coderabbitai

coderabbitai Bot commented Apr 30, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds configurable command-palette navigation (next/previous) with unbind support and settings-file managed overrides, routes those shortcuts into palette selection (or forwards unhandled keys to the focused terminal), updates persistence/parsing/UI/tests/docs, and refactors Zig selection + helper build/reload scripts.

Changes

Command Palette Shortcut Customization

Layer / File(s) Summary
Data Shape & API
Sources/KeyboardShortcutSettings.swift
Adds Action.commandPaletteNext/commandPalettePrevious, default Ctrl+N/Ctrl+P, unbound display/persistence handling, settings-file-managed guards, and swap eligibility changes.
Lookup Helpers
Sources/KeyboardShortcutSettingsLookup.swift
Adds shortcutIfBound(for:), shortcut(for:), menuShortcut(for:), isManagedBySettingsFile(_), unbindShortcut(for:), and settingsFileManagedSubtitle(for:) to resolve shortcuts with settings-file overrides and return nil for unbound.
Routing Core
Sources/App/CommandPaletteShortcutRouting.swift, Sources/App/ShortcutRoutingSupport.swift
Introduces commandPaletteSelectionDeltaForKeyboardNavigation(...) and commandPaletteSelectionDeltaForFieldEditorCommand(...) implementing device-independent modifier normalization, plain arrow navigation, and matching against bound next/previous shortcuts (replacing the removed helper).
App Shortcut Handling
Sources/AppDelegate.swift
When a palette-scoped window is active, intercepts configured .commandPaletteNext/.commandPalettePrevious, posts .commandPaletteMoveSelection with delta ±1, and arms chorded shortcuts for those actions.
Field Editor & Forwarding
Sources/ContentView.swift, Sources/GhosttyTerminalView.swift
Search-field coordinator computes selection delta or forwards unhandled navigation keys via new forwardKeyDownToSurface(_:) to the focused terminal surface.
Persistence / Parsing
Sources/KeyboardShortcutSettingsFileStore.swift
parseShortcutBindingValue treats NSNull and empty arrays as .unbound; StoredShortcut.isUnboundConfigToken now treats "disabled" as unbound.
Controls / UI Wiring
Sources/KeyboardShortcutSettingsControls.swift, Sources/cmuxApp.swift
Recorder control shows managed-by-file subtitle and disables when managed; swap flow respects swap result and reloads effective shortcut from settings.
Tests
cmuxTests/* (CommandPaletteShortcutCustomizationTests.swift, AppDelegateShortcutRoutingTests.swift, ShortcutUnbindingTests.swift, WorkspaceUnitTests.swift)
Adds tests for remapping, unbinding, chorded shortcuts, swap refusal against managed conflicts, UI presentation when managed, and routing behavior.
Localization & Docs
Resources/Localizable.xcstrings, web/data/cmux-shortcuts.ts, web/messages/*.json, web/app/.../docs/*, README.md
Adds localized labels, managed-by-file subtitle, documents commandPaletteNext/commandPalettePrevious, and documents null/aliases for unbinding.
Project File
GhosttyTabs.xcodeproj/project.pbxproj
Adds new Swift sources and tests to Xcode project files.

Build Script & Reload Refactor

Layer / File(s) Summary
Zig selection logic
scripts/build-ghostty-cli-helper.sh
Adds zig_binary_arch, target_arch_for_triple, and select_zig_for_target to choose a Zig executable (honors CMUX_ZIG, probes common locations, and matches binary-reported arch).
Build invocation & target handling
scripts/build-ghostty-cli-helper.sh
Always resolves zig_bin, computes native vs cross compile, omits -Dtarget when Zig's native arch matches requested target, and adjusts build arguments accordingly.
Helper placement & reload flow
scripts/reload.sh
Builds ghostty helper directly into the app bundle via the helper script output (instead of copying from zig-out), removes per-script target branching, and refactors launch to LAUNCH_CMD/LAUNCH_RETRY_CMD with retry behavior.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SettingsUI as Settings UI
    participant KSS as KeyboardShortcutSettings
    participant FileStore as SettingsFileStore
    participant App as AppDelegate/Runtime
    participant CP as Command Palette
    participant Terminal

    User->>SettingsUI: Remap or unbind Ctrl+P
    SettingsUI->>KSS: setShortcut / unbindShortcut(for:.commandPalettePrevious)
    KSS->>FileStore: persist override or mark unbound / managed

    User->>App: Press Ctrl+P in window
    App->>App: commandPaletteSelectionDeltaForKeyboardNavigation(event)
    alt Matches bound previous shortcut
        App->>CP: post .commandPaletteMoveSelection (delta: -1)
    else No match (unbound/cleared)
        App->>Terminal: forward key event to focused terminal surface
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 I hopped through keys and mapped the way,

Ctrl+P now listens when you say—
Remap or clear, let terminals be free,
A nibble of choice, a carrot for thee,
The palette bows, the rabbit taps its key.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (2 errors, 1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error The PR introduces Swift 6 actor isolation violations. Unisolated functions with default parameters invoke MainActor-bound code without declaring their isolation requirements. Mark commandPaletteSelectionDeltaForKeyboardNavigation and static methods in KeyboardShortcutSettingsLookup.swift with @MainActor since they access MainActor-bound resources.
Cmux Architecture Rethink ❌ Error PR violates architectural guidelines by introducing observer-based owner for state already owned by KeyboardShortcutSettings, creating split lifecycle ownership and fragile synchronization. Refactor to compute values on-demand from KeyboardShortcutSettings via derived properties without @State, eliminate .onReceive observer, and establish single source of truth.
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.
Out of Scope Changes check ❓ Inconclusive The PR includes some out-of-scope changes to dev build scripts (scripts/build-ghostty-cli-helper.sh and scripts/reload.sh) that address Zig/build tooling but are not directly required by issue #1713. Clarify whether the Zig binary selection and build/reload script changes are necessary fixes for the #1713 issue or should be in a separate PR.
Cmux Swift File And Package Boundaries ❓ Inconclusive The check references a non-existent rule file .github/review-bot-rules/swift-file-package-boundaries.md, preventing proper assessment of file organization compliance. Create the missing rule file or reference an existing one that defines file size limits, responsibility mixing restrictions, and package boundary requirements.
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: make Ctrl+P command palette navigation remappable' directly and clearly summarizes the main objective of the PR.
Linked Issues check ✅ Passed The PR directly addresses all coding requirements from issue #1713: Ctrl+P is now remappable via KeyboardShortcutSettings, can be unbound, and will propagate to terminals when unbound.
Cmux Swift Blocking Runtime ✅ Passed The PR introduces no blocking or timing-based synchronization patterns in its production code changes.
Cmux Swift Concurrency ✅ Passed PR introduces keyboard shortcut routing and navigation features without adding legacy async patterns, background queues, or fire-and-forget Tasks.
Cmux Swift @Concurrent ✅ Passed PR introduces only synchronous functions and one properly annotated @MainActor synchronous function. No async/await concurrency violations found.
Cmux Swift Logging ✅ Passed No logging violations detected. All modified and new Swift source files contain zero instances of forbidden logging statements (print, debugPrint, dump, NSLog).
Cmux Swiftui State Layout ✅ Passed PR's SwiftUI changes comply with swiftui-state-layout rules: @State uses modern pattern, mutations occur only in lifecycle callbacks, no problematic GeometryReader or lazy list patterns.
Description check ✅ Passed PR description covers root cause, fix details, test plan, and manual verification comprehensively.
✨ 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-1713-ctrl-p-remap-unbind

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 Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR promotes command palette navigation (next/previous) to first-class KeyboardShortcutSettings actions, removes the hardcoded Ctrl+P/Ctrl+N handling in ShortcutRoutingSupport.swift, and routes palette selection movement through the configured shortcuts so unbound keys can pass through to the terminal. It also introduces an explicit unbound shortcut state, expands settings-file unbind parsing (empty array, "disabled"), adds managed-file guards in the settings UI, and fixes actor isolation on Coordinator (@MainActor) and the commandPaletteSelectionDeltaForFieldEditorCommand annotation — resolving three issues flagged in previous review rounds. The dev-tooling updates (Zig arch selection, app-launch retry) are unrelated to the shortcut changes and appear self-contained.

Confidence Score: 5/5

Safe to merge; all three previously flagged issues are addressed and no new routing or isolation mistakes were found.

The three prior review findings — actor isolation on Coordinator, managed-shortcut value leaking into UserDefaults via onChange, and missing @mainactor on commandPaletteSelectionDeltaForFieldEditorCommand — are all fixed in this PR. The new routing logic (chord arming, field-editor passthrough, unbound detection) follows existing patterns correctly. SystemWideHotkeySettings.shortcut() now returns .unbound for a managed-unbound binding, but the hotkey registration path already gates on a non-nil carbonHotKeyRegistration and calls unregisterHotKey() when that is nil, so the behavior is correct.

No files require special attention.

Important Files Changed

Filename Overview
Sources/App/CommandPaletteShortcutRouting.swift New file; contains the configurable-shortcut overloads of commandPaletteSelectionDeltaForKeyboardNavigation and commandPaletteSelectionDeltaForFieldEditorCommand with correct @mainactor annotations.
Sources/App/ShortcutRoutingSupport.swift Removes the hardcoded Ctrl+P/Ctrl+N detection block; the remaining routing-gate helpers are unchanged.
Sources/ContentView.swift Coordinator is now @mainactor (fixing the previously flagged Swift 6 isolation error), editorTextDidChangeObserver is nonisolated(unsafe) to allow deinit access, and the unhandled-navigation-key path is wired to forwardCommandPaletteUnhandledNavigationKeyToFocusedTerminal.
Sources/KeyboardShortcutSettings.swift Adds commandPaletteNext/commandPalettePrevious actions and defaults; moves shortcut(for:), menuShortcut(for:), and isManagedBySettingsFile to the Lookup extension; adds managed-file guards to setShortcut and swapShortcutConflict (now returns Bool); SystemWideHotkeySettings.shortcut() correctly returns managed value (including .unbound, which the hotkey registration path handles gracefully via carbonHotKeyRegistration being nil).
Sources/KeyboardShortcutSettingsControls.swift ShortcutSettingRow now tracks isManagedBySettingsFile as @State; onChange guard prevents persisting managed values to UserDefaults, fixing the previously flagged leak; swapConflictingShortcut is gated on didSwap return value.
Sources/KeyboardShortcutSettingsLookup.swift New extension file consolidating shortcutIfBound, shortcut(for:), menuShortcut(for:), isManagedBySettingsFile, and settingsFileManagedSubtitle; correctly gates on managed/unbound state.
Sources/KeyboardShortcutSettingsFileStore.swift Empty JSON array is now treated as an unbound binding; minimal targeted change.
Sources/AppDelegate.swift Replaces the old nonisolated commandPaletteSelectionDeltaForKeyboardNavigation call with the explicit-shortcut overload; adds a chord-arming block for palette navigation; correct guard via shouldRouteConfiguredPaletteSelection.
Sources/GhosttyTerminalView.swift Adds forwardKeyDownToSurface helper to GhosttySurfaceScrollView, used for key pass-through when palette navigation shortcuts are unbound.
Sources/cmuxApp.swift GlobalHotkeySection now tracks isManagedBySettingsFile state and passes subtitle/isDisabled to ShortcutRecorderSettingsControl, consistent with the ShortcutSettingRow pattern.

Sequence Diagram

sequenceDiagram
    participant User
    participant AppDelegate
    participant Coordinator as Coordinator (Field Editor)
    participant CommandPaletteRouting
    participant KSSLookup as KeyboardShortcutSettings
    participant Terminal

    User->>AppDelegate: keyDown event (global monitor)
    AppDelegate->>KSSLookup: shortcutIfBound(.commandPaletteNext / .commandPalettePrevious)
    KSSLookup-->>AppDelegate: StoredShortcut? (managed or UserDefaults or default)
    AppDelegate->>CommandPaletteRouting: commandPaletteSelectionDeltaForKeyboardNavigation(flags, chars, keyCode, nextShortcut, previousShortcut)
    alt Shortcut matches (non-chord)
        CommandPaletteRouting-->>AppDelegate: delta (±1)
        AppDelegate->>AppDelegate: post .commandPaletteMoveSelection
    else Chord shortcut
        AppDelegate->>AppDelegate: armConfiguredShortcutChordIfNeeded / matchConfiguredShortcut
        AppDelegate->>AppDelegate: post .commandPaletteMoveSelection
    else No match
        AppDelegate->>AppDelegate: continue normal key routing
    end

    User->>Coordinator: NSTextFieldDelegate doCommandBy
    Coordinator->>CommandPaletteRouting: commandPaletteSelectionDeltaForFieldEditorCommand(selector, event)
    CommandPaletteRouting->>KSSLookup: shortcutIfBound(.commandPaletteNext / .commandPalettePrevious)
    KSSLookup-->>CommandPaletteRouting: StoredShortcut?
    alt Shortcut matches event
        CommandPaletteRouting-->>Coordinator: delta (±1)
        Coordinator->>Coordinator: onMoveSelection(delta)
    else Unbound / no match (moveDown or moveUp selector)
        CommandPaletteRouting-->>Coordinator: nil
        Coordinator->>Terminal: forwardKeyDownToSurface(event)
    end
Loading

Reviews (15): Last reviewed commit: "Clarify nullable shortcut schema binding" | Re-trigger Greptile

@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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Sources/KeyboardShortcutSettingsFileStore.swift (1)

838-844: ⚡ Quick win

Use the trimmed string for shortcut parsing to avoid whitespace-sensitive failures.

You already compute trimmed; parsing the original stroke can make " ctrl+p " behavior depend on parser internals.

Suggested change
-                shortcut = StoredShortcut.parseConfig(stroke)
+                shortcut = StoredShortcut.parseConfig(trimmed)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/KeyboardShortcutSettingsFileStore.swift` around lines 838 - 844, The
code computes a trimmed string but then calls StoredShortcut.parseConfig with
the original stroke, which can cause whitespace-sensitive failures; update the
parsing call to use the trimmed variable instead (i.e., replace
StoredShortcut.parseConfig(stroke) with StoredShortcut.parseConfig(trimmed)) so
that parsing and the empty/"none"/"unbound"/"disabled" check use the same
normalized input for assigning shortcut.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/messages/ja.json`:
- Line 390: Update the "chordsCallout" localization string to document all
supported unbind sentinels by adding "unbound" and "disabled" to the existing
list (which currently mentions null, empty string, and "none"); locate the
"chordsCallout" entry in web/messages/ja.json and modify its text so the callout
lists: null, empty string, "none", "unbound", and "disabled" as valid values for
unassigning actions.

---

Nitpick comments:
In `@Sources/KeyboardShortcutSettingsFileStore.swift`:
- Around line 838-844: The code computes a trimmed string but then calls
StoredShortcut.parseConfig with the original stroke, which can cause
whitespace-sensitive failures; update the parsing call to use the trimmed
variable instead (i.e., replace StoredShortcut.parseConfig(stroke) with
StoredShortcut.parseConfig(trimmed)) so that parsing and the
empty/"none"/"unbound"/"disabled" check use the same normalized input for
assigning shortcut.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ebf28471-57fb-4828-9e4b-118512904339

📥 Commits

Reviewing files that changed from the base of the PR and between f5a6d00 and 01c82cf.

📒 Files selected for processing (13)
  • README.md
  • Resources/Localizable.xcstrings
  • Sources/App/ShortcutRoutingSupport.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • web/app/[locale]/docs/configuration/page.tsx
  • web/app/[locale]/docs/keyboard-shortcuts/page.tsx
  • web/data/cmux-settings.schema.json
  • web/data/cmux-shortcuts.ts
  • web/messages/en.json
  • web/messages/ja.json

Comment thread web/messages/ja.json Outdated
Comment thread Sources/KeyboardShortcutSettings.swift Outdated

@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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/build-ghostty-cli-helper.sh`:
- Around line 36-83: The universal-build logic currently decides the "native"
Zig arch using command -v zig which can differ from the Zig chosen by
build_helper; update the universal branch to call select_zig_for_target "" to
pick the actual Zig binary (respecting CMUX_ZIG and fallbacks) and then call
zig_binary_arch on that result to get ZIG_ARCH, replacing the direct command -v
zig / zig_binary_arch usage so the native leg matches the Zig used by
build_helper.

In `@scripts/reload.sh`:
- Around line 611-619: The script treats any executable at GHOSTTY_HELPER_DEST
as valid (using -x) even when it's a skip stub created by
scripts/build-ghostty-cli-helper.sh when CMUX_SKIP_ZIG_BUILD=1; update reload.sh
around the GHOSTTY_HELPER_DEST check so that if CMUX_SKIP_ZIG_BUILD is not set
to 1 you either (a) detect the stub marker written by
build-ghostty-cli-helper.sh inside the file and force a rebuild, or (b) always
overwrite Contents/Resources/bin/ghostty by invoking
scripts/build-ghostty-cli-helper.sh --output "$GHOSTTY_HELPER_DEST" when Zig
builds are enabled; reference the CMUX_SKIP_ZIG_BUILD flag, GHOSTTY_HELPER_DEST
variable and the build-ghostty-cli-helper.sh script to locate where to add the
stub-marker check or unconditional overwrite.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 144c4b5b-3beb-4325-8e85-fc7c88ae9144

📥 Commits

Reviewing files that changed from the base of the PR and between 01c82cf and a63050f.

📒 Files selected for processing (2)
  • scripts/build-ghostty-cli-helper.sh
  • scripts/reload.sh

Comment thread scripts/build-ghostty-cli-helper.sh
Comment thread scripts/reload.sh
Comment thread Sources/KeyboardShortcutSettings.swift Outdated

@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/KeyboardShortcutSettingsControls.swift (1)

5-28: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

isDisabled and subtitle can go stale when the settings file manages a shortcut at the same value already showing in the UI.

isDisabled and subtitle are both computed directly inside body from KeyboardShortcutSettings.isManagedBySettingsFile(action) / settingsFileManagedSubtitle(for:). SwiftUI only re-evaluates body when a @State or @Binding changes. The .onReceive handler only mutates shortcut when latest != shortcut. So if cmux.json newly manages an action but sets it to the same value already showing (e.g., locks ctrl+n to its default), shortcut doesn't change, body is never re-invoked, and isDisabled remains false — the row appears editable even though all writes to it are silently no-oped.

The same staleness applies in reverse when the file is removed.

Fix: track the managed state as @State and update it unconditionally in .onReceive:

🛡️ Proposed fix
 struct ShortcutSettingRow: View {
     let action: KeyboardShortcutSettings.Action
     `@State` private var shortcut: StoredShortcut
+    `@State` private var isManagedByFile: Bool
 
     init(action: KeyboardShortcutSettings.Action) {
         self.action = action
         _shortcut = State(initialValue: KeyboardShortcutSettings.shortcut(for: action))
+        _isManagedByFile = State(initialValue: KeyboardShortcutSettings.isManagedBySettingsFile(action))
     }
 
     var body: some View {
         ShortcutRecorderSettingsControl(
             action: action,
             shortcut: $shortcut,
-            subtitle: KeyboardShortcutSettings.settingsFileManagedSubtitle(for: action),
+            subtitle: isManagedByFile ? KeyboardShortcutSettings.settingsFileManagedSubtitle(for: action) : nil,
             displayString: { action.displayedShortcutString(for: $0) },
-            isDisabled: KeyboardShortcutSettings.isManagedBySettingsFile(action)
+            isDisabled: isManagedByFile
         )
         .onChange(of: shortcut) { _, newValue in
             KeyboardShortcutSettings.setShortcut(newValue, for: action)
         }
         .onReceive(NotificationCenter.default.publisher(for: KeyboardShortcutSettings.didChangeNotification)) { _ in
             let latest = KeyboardShortcutSettings.shortcut(for: action)
             if latest != shortcut {
                 shortcut = latest
             }
+            let managed = KeyboardShortcutSettings.isManagedBySettingsFile(action)
+            if managed != isManagedByFile {
+                isManagedByFile = managed
+            }
         }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/KeyboardShortcutSettingsControls.swift` around lines 5 - 28, The
body’s computed isDisabled and subtitle can become stale because they’re not
tied to any `@State/`@Binding; add `@State` properties (e.g., `@State` private var
isManaged and `@State` private var managedSubtitle) and initialize them from
KeyboardShortcutSettings.isManagedBySettingsFile(action) and
settingsFileManagedSubtitle(for: action), use those state vars in the
ShortcutRecorderSettingsControl isDisabled and subtitle parameters, and in the
.onReceive(NotificationCenter.default.publisher(for:
KeyboardShortcutSettings.didChangeNotification)) handler always update both
isManaged and managedSubtitle (in addition to updating shortcut when needed) so
the view re-renders whenever the settings file changes even if the shortcut
value itself is unchanged.
🤖 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/KeyboardShortcutSettingsControls.swift`:
- Around line 5-28: The body’s computed isDisabled and subtitle can become stale
because they’re not tied to any `@State/`@Binding; add `@State` properties (e.g.,
`@State` private var isManaged and `@State` private var managedSubtitle) and
initialize them from KeyboardShortcutSettings.isManagedBySettingsFile(action)
and settingsFileManagedSubtitle(for: action), use those state vars in the
ShortcutRecorderSettingsControl isDisabled and subtitle parameters, and in the
.onReceive(NotificationCenter.default.publisher(for:
KeyboardShortcutSettings.didChangeNotification)) handler always update both
isManaged and managedSubtitle (in addition to updating shortcut when needed) so
the view re-renders whenever the settings file changes even if the shortcut
value itself is unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b47e1375-d51e-4233-afeb-c616f5ab04f1

📥 Commits

Reviewing files that changed from the base of the PR and between a63050f and 2843ca7.

📒 Files selected for processing (10)
  • README.md
  • Resources/Localizable.xcstrings
  • Sources/App/ShortcutRoutingSupport.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/KeyboardShortcutSettingsControls.swift
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • scripts/build-ghostty-cli-helper.sh
  • scripts/reload.sh
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/build-ghostty-cli-helper.sh

Comment thread Sources/App/CommandPaletteShortcutRouting.swift
Ctrl+P command palette navigation is currently hardcoded outside KeyboardShortcutSettings, so clearing or remapping shortcut settings cannot let terminal apps receive ^P. The tests codify the desired settings-backed behavior before changing the router.

Constraint: Preserve the two-commit regression-test-then-fix workflow requested for issue #1713
Rejected: Only source-grep the hardcoded path | would not prove remap/unbind behavior through the app shortcut router
Confidence: medium
Scope-risk: narrow
Tested: Not run locally; red commit intentionally references the missing settings API/action for the fix commit to satisfy
Not-tested: Local unit test execution and manual cat -v reproduction, because the tagged reload build failed in the Ghostty helper before launch
Command palette result navigation now uses first-class shortcut actions instead of hardcoded Ctrl+N/Ctrl+P checks. Shortcut bindings can persist an explicit unbound state, the Settings UI exposes a Clear control, and settings.json can unbind with null, an empty string, none, unbound, or disabled.

Constraint: Ctrl+P must pass through to terminal panes when the command palette previous shortcut is cleared
Constraint: Keep default behavior as Ctrl+N/Ctrl+P for users without custom settings
Rejected: Copy PR #1736 directly | it did not represent unbound state and risked falling back to defaults
Rejected: Remove only the Ctrl+P keyCode branch | would make remapping impossible
Confidence: medium
Scope-risk: moderate
Tested: jq empty on updated JSON resources and schema; git diff --check
Not-tested: Local unit tests and manual app verification; tagged reload currently fails in Ghostty helper before launch
The dev reload was failing on macOS 26 because Xcode's script phase found the x86_64 Homebrew Zig first and cross-linked the arm64 Ghostty CLI helper. Zig 0.15.x then failed to resolve libc, CoreFoundation, CoreText, and Objective-C symbols before the app could launch. The helper builder now selects a Zig binary that matches the requested target architecture when one is installed, while reload.sh reuses the helper produced by the app build instead of running a second raw zig build afterward.

LaunchServices can also return -600 for the tagged app immediately after a rebuild. reload.sh now retries the launch with open -n -g so the required --launch command exits successfully once the app is built.

Constraint: Dev builds must run through scripts/reload.sh rather than direct xcodebuild.

Rejected: Require CMUX_SKIP_ZIG_BUILD=1 for this branch | the required launch command cannot pass that environment override and it would replace the real helper with a stub.

Confidence: high

Scope-risk: narrow

Tested: bash -n scripts/build-ghostty-cli-helper.sh scripts/reload.sh

Tested: PATH with /usr/local/bin first still built an arm64 Ghostty helper via /opt/homebrew/bin/zig

Tested: ./scripts/reload.sh --tag issue-1713-ctrl-p-remap-unbind --launch succeeded and launched the tagged app
Rebasing the Ctrl+P shortcut work onto current main overlapped with main's existing StoredShortcut.unbound declaration. Keeping one declaration preserves the existing storage shape and avoids a duplicate type member.

Constraint: origin/main already defines StoredShortcut.unbound

Confidence: high

Scope-risk: narrow

Tested: jq empty Resources/Localizable.xcstrings web/messages/en.json web/messages/ja.json web/data/cmux.schema.json web/data/cmux-settings.schema.json; bash -n scripts/build-ghostty-cli-helper.sh scripts/reload.sh; git diff --check

Not-tested: local unit tests per repository policy
The Ctrl+P remap/unbind fix added runtime coverage and helper logic to files that are already tracked by the Swift file-length guard. Move the new coverage and lookup/routing helpers into small focused files so CI can enforce the existing budget without accepting new long-file debt.

Constraint: workflow-guard-tests rejects growth in tracked Swift files over the budget

Rejected: Refresh the Swift file-length budget | would accept new long-file debt for a localized shortcut fix

Confidence: high

Scope-risk: narrow

Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Tested: jq empty Resources/Localizable.xcstrings web/messages/en.json web/messages/ja.json web/data/cmux.schema.json web/data/cmux-settings.schema.json

Tested: bash -n scripts/build-ghostty-cli-helper.sh scripts/reload.sh

Tested: plutil -lint GhosttyTabs.xcodeproj/project.pbxproj

Not-tested: XCTest execution; repository policy keeps tests in CI
The shortcut customization paths need to respect cmux.json as the source of truth across AppKit field-editor routing, chord dispatch, Settings conflict handling, and the system-wide hotkey registrar. This keeps remapped or unbound command-palette navigation from being resurrected by AppKit move commands, prevents Settings from pretending it can swap immutable managed shortcuts, and ensures the registered global hotkey matches the managed value shown in Settings.

Constraint: AppKit field editors translate Ctrl+P/Ctrl+N into moveUp:/moveDown: before normal key routing can pass them to the terminal.

Constraint: cmux.json-managed shortcuts must remain authoritative over UserDefaults-backed Settings UI values.

Rejected: Only change AppDelegate shortcut matching | the focused command palette search field still handles moveUp:/moveDown: locally.

Confidence: high

Scope-risk: moderate

Directive: Keep command-palette navigation routing shared between the AppDelegate monitor and field-editor delegate when adding future shortcut customization.

Tested: git diff --check; ./scripts/reload.sh --tag review-shortcuts; ./scripts/reload.sh --tag review-shortcuts --launch

Not-tested: Full XCTest suite per repo policy; added regression tests for CI coverage.
Comment thread Sources/App/CommandPaletteShortcutRouting.swift
Comment thread Sources/AppDelegate.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 4, 2026

@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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmuxTests/AppDelegateShortcutRoutingTests.swift`:
- Around line 3713-3763: The test is missing an XCTestExpectation/wait for the
asynchronous .commandPaletteMoveSelection notification; update the
NotificationCenter handler to create an XCTestExpectation (e.g.,
moveExpectation), call moveExpectation.fulfill() when you append the delta, and
then call wait(for: [moveExpectation], timeout: 1.0) before asserting
observedDeltas and observedWindow; reference the existing handler/variables
(observedDeltas, observedWindow, moveToken) and the expectation name
(moveExpectation) so the assertions at the end run only after the notification
is received.

In `@cmuxTests/CommandPaletteShortcutCustomizationTests.swift`:
- Around line 15-27: The test suite must snapshot and clear the two UserDefaults
keys for command-palette shortcuts to isolate persisted state: in
setUpWithError() read and store the current values for
"shortcut.commandPaletteNext" and "shortcut.commandPalettePrevious" (e.g. into
properties like savedCommandPaletteNext / savedCommandPalettePrevious), then
remove those keys from UserDefaults so default-argument lookups in
commandPaletteSelectionDeltaForFieldEditorCommand(...) and
debugHandleCustomShortcut(...) are deterministic; in tearDown() restore the
saved values (or remove if nil) and then restore
KeyboardShortcutSettings.settingsFileStore as before. Ensure you use
UserDefaults.standard for the get/remove/set operations and handle optional
values when restoring.

In `@Resources/Localizable.xcstrings`:
- Around line 5-7: Add the missing locale entries for the three new keys
("shortcut.commandPaletteNext.label", "shortcut.commandPalettePrevious.label",
"settings.shortcuts.managedByFile") by adding 17 locale objects: ar, bs, da, de,
es, fr, it, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant using the same
"localizations" -> "<locale>": { "stringUnit": { "state": "translated", "value":
"<English text>" } } structure; for ar, bs, da, pl, th, tr use English text and
state "translated" (lower-confidence locales per note), and for the remaining
locales also include the English value and "translated" state so the catalog
supports all 19 locales for each of the three keys.

In `@Sources/AppDelegate.swift`:
- Around line 10675-10694: The new direct handling of
.commandPaletteNext/.commandPalettePrevious bypasses the existing routing guard
and allows shortcuts to be consumed even when inline text mode should prevent
them; update the early return block that checks
commandPaletteInteractiveInTargetWindow and commandPaletteShortcutWindow to
first call shouldRouteCommandPaletteSelectionNavigation(...) (the same check
used elsewhere) and only perform matchConfiguredShortcut(..., action:
.commandPaletteNext/.commandPalettePrevious) and post
.commandPaletteMoveSelection notifications when that guard returns true,
ensuring the single source of truth for routing is respected.

In `@web/app/`[locale]/docs/configuration/page.tsx:
- Around line 82-84: Update the explanatory guidance text for shortcuts.bindings
to document the null/empty-string unbinding form and its aliases: explicitly
state that setting a binding to null (or an empty string) removes/unbinds the
shortcut (same effect as the shown "commandPalettePrevious": null example), and
mention that both null and "" are accepted aliases for unbinding. Edit the
explanation block in web/app/[locale]/docs/configuration/page.tsx where
shortcuts.bindings forms are described so it describes three cases (single
string, two-item array, and null/empty-string unbind) and includes a brief
example note referencing shortcuts.bindings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a300ff83-4026-44c2-aab3-0610c023544c

📥 Commits

Reviewing files that changed from the base of the PR and between 2843ca7 and dd6ba36.

📒 Files selected for processing (25)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • README.md
  • Resources/Localizable.xcstrings
  • Sources/App/CommandPaletteShortcutRouting.swift
  • Sources/App/ShortcutRoutingSupport.swift
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/KeyboardShortcutSettingsControls.swift
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • Sources/KeyboardShortcutSettingsLookup.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • cmuxTests/CommandPaletteShortcutCustomizationTests.swift
  • cmuxTests/ShortcutUnbindingTests.swift
  • cmuxTests/WorkspaceUnitTests.swift
  • scripts/build-ghostty-cli-helper.sh
  • scripts/reload.sh
  • web/app/[locale]/docs/configuration/page.tsx
  • web/app/[locale]/docs/keyboard-shortcuts/page.tsx
  • web/data/cmux-shortcuts.ts
  • web/data/cmux.schema.json
  • web/messages/en.json
  • web/messages/ja.json
💤 Files with no reviewable changes (1)
  • Sources/App/ShortcutRoutingSupport.swift

Comment thread cmuxTests/AppDelegateShortcutRoutingTests.swift Outdated
Comment thread cmuxTests/CommandPaletteShortcutCustomizationTests.swift
Comment thread Resources/Localizable.xcstrings Outdated
Comment thread Sources/AppDelegate.swift Outdated
Comment thread Sources/KeyboardShortcutSettingsControls.swift Outdated
Comment thread web/app/[locale]/docs/configuration/page.tsx
The shortcut remap/unbind work added behavior-level coverage and small routing changes, but the checked-in per-file Swift length budget still held pre-change counts for those files. This refreshes the budget to the measured current counts and also tightens entries where files shrank, so the guard remains exact rather than loosely raised.

Constraint: CI enforces per-file Swift length budgets from .github/swift-file-length-budget.tsv
Rejected: Split the new shortcut regression tests | the added coverage exercises distinct entrypoints and splitting would add churn without reducing behavior risk
Confidence: high
Scope-risk: narrow
Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv
Not-tested: Full GitHub Actions matrix not run locally
GitHub validates pull requests against the synthetic merge ref, not the branch tip alone. origin/main advanced after the local budget refresh, and the merge ref keeps CLI/cmux.swift at the previous 20530-line budget even though the branch tip alone is shorter. Restoring that budget keeps the guard aligned with the ref CI actually checks.

Constraint: Pull request CI checks refs/pull/<id>/merge, which includes current origin/main
Rejected: Keep the branch-tip-only CLI/cmux.swift reduction | it fails the synthetic merge ref while main still carries the larger file
Confidence: high
Scope-risk: narrow
Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv
Not-tested: Full GitHub Actions matrix not run locally
The PR was functionally green but still had requested changes from review bots. This keeps command-palette next/previous routing behind the same inline-text guard as arrow navigation, prevents chord prefixes from being treated as complete palette moves, mirrors settings-file managed state into SwiftUI row state, and tightens the affected tests/docs/localization coverage.

Constraint: CodeRabbit marked the PR changes-requested on routing guards, test isolation, localization coverage, SwiftUI managed-state redraws, and docs
Rejected: Leave direct next/previous handling as a separate path | it bypassed the shared inline-text routing guard
Rejected: Treat chord prefixes as keyboard-navigation matches | prefixes should arm chords without moving palette selection
Confidence: high
Scope-risk: moderate
Tested: jq empty Resources/Localizable.xcstrings
Tested: jq localization count for the three new keys is 19 each
Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv
Tested: git diff --check
Tested: ./scripts/reload.sh --tag fix-ci-review
Not-tested: Local web typecheck is blocked by this checkout's TypeScript toolchain rejecting existing moduleResolution=bundler before checking changed TSX; GitHub web-typecheck will verify after push
The PR branch needed the latest main branch before CI iteration. The merge kept main's relocated shortcut validation presentation while preserving this branch's managed-shortcut swap protections.

Constraint: origin/main already moved ShortcutRecorderValidationPresentation into KeyboardShortcutSettingsControls.swift

Rejected: Keep duplicate definitions in both files | Swift module emission fails with an invalid redeclaration

Confidence: medium

Scope-risk: moderate

Directive: Keep context-aware conflict presentation and settings-file managed swap blocking together when touching shortcut recorder validation

Tested: git diff --cached --check

Not-tested: tagged reload build is still queued behind the shared xcodebuild lock
The branch needed the latest main after origin/main advanced during PR iteration. The resolution keeps both command-palette shortcut customization and main's bare-space shortcut routing/schema work.

Constraint: main split shortcut schema validation into unbound, first-stroke, and stroke definitions while this branch added disabled as an unbind alias

Rejected: Pick either project-file side | each side added distinct source/test files that must remain in the Xcode target

Confidence: medium

Scope-risk: moderate

Directive: Keep disabled/null unbind support aligned between Swift parsing, schema, docs, and localized copy

Tested: git diff --cached --check; python3 -m json.tool web/data/cmux.schema.json; plutil -lint GhosttyTabs.xcodeproj/project.pbxproj

Not-tested: tagged reload build is still queued behind the shared xcodebuild lock
Comment thread Sources/ContentView.swift Outdated
let onMoveSelection: (Int) -> Void
let onUnhandledNavigationKey: (NSEvent) -> Bool

final class Coordinator: NSObject, NSTextFieldDelegate {

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.

P1 @MainActor-isolated callees invoked from non-isolated Coordinator

Coordinator (line 4866) carries no actor annotation, so it is nonisolated under Swift 6 strict concurrency. Lines 4905 and 4911 both access NSApp.currentEvent, which is a @MainActor-isolated property, and line 4905 also calls commandPaletteSelectionDeltaForFieldEditorCommand, which is annotated @MainActor. Calling @MainActor declarations from a nonisolated synchronous context is a data-race error under Swift 6 strict concurrency checking — the compiler will reject both sites. The NSTextFieldDelegate callback is guaranteed by AppKit to run on the main thread at runtime, so adding @MainActor to Coordinator is the correct fix and will not change behavior.

Suggested change
final class Coordinator: NSObject, NSTextFieldDelegate {
@MainActor final class Coordinator: NSObject, NSTextFieldDelegate {

File Used: .github/review-bot-rules/swift-actor-isolation.md (source)

The command palette search coordinator runs on AppKit delegate callbacks and calls main-actor shortcut routing helpers. Marking the coordinator itself as main-actor isolated keeps the Swift concurrency contract explicit after merging current main.

Constraint: AppKit delegate callbacks for this NSTextField are delivered on the main thread

Rejected: Wrap individual calls in Task or DispatchQueue | would make synchronous key handling asynchronous

Confidence: high

Scope-risk: narrow

Tested: git diff --check

Not-tested: Tagged reload build is still queued behind the shared Xcode build lock; CI is pending
Main-actor isolation on the command palette coordinator exposed the NotificationCenter teardown path as nonisolated. The observer token is explicitly nonisolated for deinit cleanup, while notification callbacks hop through MainActor.assumeIsolated because they are delivered on the main queue.

Constraint: Deinit cannot call the coordinator's main-actor-isolated detach method synchronously

Rejected: Drop observer cleanup | would leave a block observer registered after coordinator teardown

Confidence: medium

Scope-risk: narrow

Tested: git diff --check

Not-tested: Tagged reload build has not completed after this follow-up yet; CI is pending
The main-actor coordinator cleanup can stay behaviorally identical without increasing ContentView's tracked file length. Compacting the deinit and notification callback keeps the workflow guard within its existing budget.

Constraint: workflow-guard-tests enforces .github/swift-file-length-budget.tsv for ContentView.swift

Rejected: Refresh the file-length budget | unnecessary for a compact follow-up

Confidence: high

Scope-risk: narrow

Tested: git diff --check; wc -l Sources/ContentView.swift is below the checked-in budget

Not-tested: CI rerun still pending
Greptile flagged the pure keyboard-navigation helper because its default arguments read effective shortcut settings. The helper now defaults only to static built-in shortcuts, while AppDelegate and the main-actor field editor coordinator pass effective settings-file-aware shortcuts explicitly.

Constraint: Nonisolated default argument expressions must not touch main-actor settings state

Rejected: Mark every caller and test path @mainactor | broader than needed for a pure matching helper

Confidence: high

Scope-risk: narrow

Tested: git diff --check; checked tracked Swift file lengths remain within .github/swift-file-length-budget.tsv

Not-tested: Tagged reload and CI rerun are pending after this commit
origin/main advanced while the PR iteration was green, so the branch was updated again before rerunning verification. The merge was clean and preserved the explicit palette shortcut lookup fix while taking the latest localization and sidebar/session updates from main.

Constraint: User requested repeated git pull from origin main before completing the PR iteration

Rejected: Leave the default auto-merge message | it does not satisfy the repo's Lore commit protocol

Confidence: high

Scope-risk: moderate

Tested: git diff --check HEAD

Not-tested: Tagged reload and CI rerun are pending after this merge
austinywang and others added 2 commits May 4, 2026 19:51
AppKit can invoke the command-palette field editor delegate with only moveUp: and no current key event. That fallback was still interpreted as command-palette previous after Ctrl+P was unbound. The fallback now only maps moveUp:/moveDown: when the matching default shortcut is still active, and tests cover the nil-event unbound and remapped cases.

Constraint: A selector-only AppKit callback cannot distinguish Ctrl+P from an arrow key without the original key event

Rejected: Always consume moveUp:/moveDown: without an event | breaks explicit unbind/remap semantics

Confidence: high

Scope-risk: narrow

Tested: git diff --check; ./scripts/reload.sh --tag issue-1713-ctrl-p-remap-unbind --launch

Not-tested: CI rerun is pending after this commit
origin/main advanced while fixing the command-palette Ctrl+P unbind fallback. The clean merge keeps the nil-event fallback fix on top of the latest main changes before rerunning build and PR checks.

Constraint: User requested the PR branch stay pulled from origin main while iterating

Rejected: Leave the default auto-merge message | it does not satisfy the repo's Lore commit protocol

Confidence: high

Scope-risk: moderate

Tested: git diff --check HEAD

Not-tested: Tagged reload and CI rerun are pending after this merge

Co-authored-by: OmX <omx@oh-my-codex.dev>
Comment thread Sources/App/CommandPaletteShortcutRouting.swift Outdated
Cursor found that the no-argument keyboard-navigation helper still defaulted to static Ctrl+N/Ctrl+P bindings. The pure matcher now requires explicit shortcuts, while the convenience overload resolves the effective settings-aware bindings and tests cover unbound and remapped default lookup paths.

Constraint: Command-palette shortcut routing must respect user unbinds and remaps from every helper entrypoint

Rejected: Keep hardcoded default parameters on the pure matcher | future callers could consume unbound Ctrl+P again

Confidence: high

Scope-risk: narrow

Tested: git diff --check; python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Not-tested: Local XCTest execution per repo policy; tagged reload and CI rerun are pending after this commit

Co-authored-by: OmX <omx@oh-my-codex.dev>

@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 e1b2e9a. Configure here.

Comment thread web/data/cmux.schema.json
Cursor identified that shortcut binding null support could be represented as an overlapping nullable oneOf. The schema now keeps shortcutBinding non-null and introduces shortcutBindingNullable as the only wrapper that adds null, so shortcut bindings can accept null without ambiguous oneOf matching.

Constraint: shortcuts.bindings must accept null for unbinding while remaining valid JSON Schema

Rejected: Leave null directly in shortcutBinding | makes nullable wrapper composition ambiguous for future schema edits

Confidence: high

Scope-risk: narrow

Tested: python3 -m json.tool web/data/cmux.schema.json; focused Python assertion for shortcutBindingNullable shape; git diff --check

Not-tested: Full web/schema test suite not run; user asked to address Cursorbot comments only

Co-authored-by: OmX <omx@oh-my-codex.dev>

This branch was successfully deployed

1 active deployment
Preview – cmux — c3c00e88 Deployed May 5, 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.

Ctrl+P cannot be remapped to a different action or unbound

1 participant