Skip to content

fix: make Cmd+D new-tab shortcut rebindable via KeyboardShortcutSettings (#2524) - #3338

Merged
austinywang merged 6 commits into
mainfrom
issue-2524-cmd-d-rebindable
May 1, 2026
Merged

austinywang merged 6 commits into
mainfrom
issue-2524-cmd-d-rebindable

Conversation

@austinywang

@austinywang austinywang commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

Closes #2524

Root cause

Cmd+D was not owned by a single shortcut layer. KeyboardShortcutSettings had split actions, but Cmd+D could still be consumed by stale SwiftUI/AppKit menu equivalents after remap/clear, and forwarding Cmd+D into Ghostty still hit Ghostty's default super+d=new_split:right binding. That meant terminal apps never saw Cmd+D even when the cmux binding was cleared or moved to Cmd+J.

Fix

  • Persist an explicit unbound shortcut (StoredShortcut.unbound) and show it as None in the shortcut recorder.
  • Add a localized Clear control to the standard keyboard shortcut recorder, while keeping the global hotkey row non-clearable.
  • Ensure split menu items and shortcut dispatch read the current KeyboardShortcutSettings value instead of stale defaults.
  • Suppress stale default cmux menu equivalents after a shortcut is remapped or cleared.
  • Unbind Ghostty's default super+d and super+shift+d split fallbacks so cmux-owned split shortcuts are governed only by KeyboardShortcutSettings.
  • Document empty-string unbinding in settings.json schema/docs.

Test commits

  • 24c32365 - failing regression test for cleared Cmd+D pass-through
  • ba39f7e6 - implementation fix

Verification

  • git diff --check
  • bash -n scripts/reload.sh
  • bash -n scripts/build-ghostty-cli-helper.sh
  • python3 -m json.tool Resources/Localizable.xcstrings >/dev/null
  • python3 -m json.tool web/data/cmux-settings.schema.json >/dev/null
  • ./scripts/reload.sh --tag issue-2524-cmd-d-rebindable --launch
  • On the launched tagged dev app via debug socket:
    • Remapped Split Right to Cmd+J, simulated Cmd+D, pane count stayed at 15.
    • Simulated Cmd+J, pane count increased to 16.
    • Cleared Split Right, simulated Cmd+D, pane count stayed at 16.
    • Reset Split Right to default, simulated Cmd+D, pane count increased to 17.

Not run locally

  • XCUITests are CI-only by project policy.
  • Local cmux-unit through xcodebuild was not run because this task explicitly forbids direct xcodebuild.
  • SwiftPM test invocation cannot currently build this app test target because CMUXDebugLog is not exposed through Package.swift.
  • Settings UI desktop automation was blocked by macOS Apple Events permission error -1743; the recorder integration is covered in source and the runtime shortcut behavior was verified on the launched tagged build.

Note

Medium Risk
Modifies key event routing and menu-equivalent suppression in AppDelegate/window dispatch, which can affect global keyboard handling and terminal input. Includes Ghostty keybind overrides and config encoding changes that could impact shortcut persistence and default behavior.

Overview
Ensures cmux split shortcuts (notably Cmd+D/Cmd+Shift+D) are fully owned by KeyboardShortcutSettings: shortcuts can now be explicitly unbound and, when cleared or remapped, the old default menu equivalents are suppressed and the key event can be forwarded to the focused terminal instead of creating a split.

Updates config/CLI/schema/docs to support unbinding via empty string (and tokens like clear/none/unbound), and adjusts encoding/decoding accordingly. Ghostty configs now unbind its default super+d split fallbacks, context menu split items pick up the configured shortcuts, build scripts select a Zig binary matching the requested arch, and new tests cover stale-menu and cleared/remapped Cmd+D routing behavior.

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


Summary by cubic

Make the Cmd+D split shortcut fully rebindable and clearable. When cleared, Cmd+D now passes through to terminal apps instead of splitting.

  • Bug Fixes

    • Route split shortcuts solely through KeyboardShortcutSettings; suppress stale AppKit menu equivalents in both app and window dispatch after remap/clear, and forward the event to the focused terminal.
    • Unbind Ghostty fallback keybinds super+d and super+shift+d so cleared/remapped splits don’t fire.
    • Context menu items now read current shortcuts instead of hardcoding Cmd+D.
    • Refresh Swift file length budget to keep CI guard passing after test additions.
  • New Features

    • Add explicit “unbound” shortcut state; show “None” and a “Clear” button in the recorder (global hotkey row remains non-clearable).
    • Support unbinding via settings and CLI using an empty string or none/clear/unbound; accept action ids split_right and split_down. Docs and schema updated.
    • Reload/build scripts choose a Zig binary matching the target arch to avoid helper build failures.

Written for commit c20f6f2. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Keyboard shortcuts can now be unbound using "clear", "unbound", "none", or empty string syntax
    • Split navigation commands can now be configured with custom keyboard shortcuts instead of using defaults
  • Bug Fixes

    • Fixed handling of stale keyboard shortcuts when remapped or cleared to prevent unexpected menu actions
  • Documentation

    • Updated configuration schema descriptions and documentation examples for keyboard shortcut settings

The regression captures the desired shortcut contract before the implementation exists: clearing the split-right shortcut must make Cmd-D fall through instead of creating another pane.

Constraint: Local direct xcodebuild is forbidden for this task; verification runs through allowed reload/CI paths.

Confidence: medium

Scope-risk: narrow

Tested: Not run locally; this test-only commit is expected to fail before the implementation adds the unbound shortcut state.

Not-tested: Local cmux-unit execution because the user explicitly forbade direct xcodebuild.
Cmd-D was registered in multiple layers: KeyboardShortcutSettings covered the app-level matcher, while SwiftUI/AppKit menu equivalents and Ghostty's default super+d split binding could still create a split after the setting was cleared or remapped. Persist an explicit unbound shortcut, expose Clear in the keyboard shortcut recorder, source split menus from KeyboardShortcutSettings, suppress stale default menu equivalents, and remove Ghostty's default split fallbacks so KeyboardShortcutSettings is the single owner of split shortcuts.

The reload helper also now selects a target-matching Zig binary for Ghostty helper builds, because the required reload command otherwise picked the x86 Zig from /usr/local on this arm64 host and failed before launch.

Constraint: Direct xcodebuild is forbidden; build verification uses reload.sh only

Rejected: Treat clear as reset | reset restores Cmd-D and keeps terminal apps from receiving it

Rejected: Only suppress the Swift app shortcut matcher | stale AppKit menu equivalents and Ghostty super+d still split

Rejected: Leave Ghostty defaults active | forwarded Cmd-D still triggers Ghostty new_split:right

Confidence: high

Scope-risk: moderate

Tested: git diff --check; bash -n scripts/reload.sh; bash -n scripts/build-ghostty-cli-helper.sh; Localizable.xcstrings JSON parse; cmux settings schema JSON parse; ./scripts/reload.sh --tag issue-2524-cmd-d-rebindable --launch; debug socket remap Cmd+J kept Cmd+D pane count at 15 and Cmd+J increased it to 16; debug socket clear kept Cmd+D pane count at 16; debug socket default restored Cmd+D split to 17 panes

Not-tested: Local cmux-unit via xcodebuild because explicitly forbidden; SwiftPM test path cannot build app target because CMUXDebugLog is not in Package.swift; XCUITests are CI-only; Settings UI automation blocked by macOS Apple Events permission -1743

Related: #2524
@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 Apr 30, 2026 11:53pm
cmux-staging Building Building Preview, Comment Apr 30, 2026 11:53pm

@coderabbitai

coderabbitai Bot commented Apr 30, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR makes the hardcoded Cmd+D split-right shortcut configurable by implementing stale menu shortcut suppression logic. Events matching outdated menu shortcuts are forwarded directly to the ghostty terminal view instead of the menu system, while supporting unbinding and remapping the shortcut through configuration.

Changes

Cohort / File(s) Summary
Core keyboard routing
Sources/AppDelegate.swift
Adds shouldSuppressStaleCmuxMenuShortcut helper to detect and suppress stale Cmd-key menu shortcuts by comparing against current configured shortcuts and action defaults. Forwards suppressed events directly to ghostty view instead of normal menu routing.
Shortcut configuration
Sources/CmuxConfig.swift, Sources/KeyboardShortcutSettings.swift
Enables unbinding via empty strings and "clear"/"unbound" tokens. Removes hardcoded Cmd+D reservation. Adds public clearShortcut(for:) helper for unsetting action shortcuts.
Terminal UI and split navigation
Sources/GhosttyTerminalView.swift, Sources/TerminalController.swift
Loads cmux-owned keybind overrides that unbind super+d/super+shift+d. Updates split context-menu shortcuts to derive from KeyboardShortcutSettings. Expands terminal controller debug parsing to recognize split navigation target names and "unbound"/"clear"/"default"/"reset" combos.
Build infrastructure
scripts/build-ghostty-cli-helper.sh, scripts/reload.sh
Detects host CPU architecture and selects matching Zig binary for cross-compilation, dropping -Dtarget when native architecture matches target.
Testing
cmuxTests/AppDelegateShortcutRoutingTests.swift
Introduces MenuActionProbe and extends Ghostty probe view to verify cleared/remapped Cmd+D bypasses stale menu routing and forwards to focused view as keyDown.
Documentation
web/app/[locale]/docs/keyboard-shortcuts/page.tsx, web/data/cmux-settings.schema.json
Updates keyboard shortcut schema descriptions and examples to document unbinding via empty string, none, clear, and unbound tokens. Adds splitRight example binding.

Sequence Diagram(s)

sequenceDiagram
    participant AppKit as NSApplication/NSWindow
    participant Delegate as AppDelegate
    participant GhosttyView as ghosttyView
    participant Menu as NSMenu
    
    AppKit->>Delegate: sendEvent(keyDown) / performKeyEquivalent
    Delegate->>Delegate: shouldSuppressStaleCmuxMenuShortcut(event)?
    alt Stale Cmd-key menu shortcut detected
        Delegate->>GhosttyView: Forward keyDown event
        Delegate-->>AppKit: Return early (suppress menu routing)
    else Current or non-Cmd shortcut
        Delegate->>AppKit: Proceed with normal routing
        AppKit->>Menu: Route to responder chain
    end
    GhosttyView->>GhosttyView: Process keyDown for terminal app
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Fix cmd+` window cycling bouncing back #1243: Modifies AppDelegate Command-key routing with a helper (shouldRouteCommandEquivalentDirectlyToMainMenu) that centralizes whether Cmd events should bypass the main menu, directly paralleling the new shouldSuppressStaleCmuxMenuShortcut logic.
  • Add reset-terminal terminal menu workaround #2349: Changes GhosttyTerminalView context-menu shortcut handling (key equivalents and menu-shortcut helpers) to derive shortcuts from settings, similar to this PR's applyConfiguredMenuShortcut approach.
  • Allow keyboard shortcuts to be unbound #3334: Implements the same unbound-shortcut semantics (treating StoredShortcut.isUnbound as non-matching) and touches identical shortcut-parsing/serialization codepaths in AppDelegate and KeyboardShortcutSettings.

Poem

🐰 Cmd+D now hops where users wish it to go,
No longer chained to one fixed path below.
The stale shortcuts fade as config takes the reign,
Menu ghosts are banished—freedom to the terminal domain! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% 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: make Cmd+D new-tab shortcut rebindable via KeyboardShortcutSettings (#2524)' clearly summarizes the main change: making the hardcoded Cmd+D shortcut rebindable through the settings system.
Description check ✅ Passed The PR description includes comprehensive Summary, Testing, and Verification sections covering root cause, implemented fixes, test commits, and manual verification steps. All template sections are present and well-filled.
Linked Issues check ✅ Passed The PR implementation fulfills all primary coding objectives from issue #2524: Cmd+D is now listed/discoverable in KeyboardShortcutSettings, can be remapped or unbound, and when unbound, the keystroke propagates to terminal applications instead of creating a split.
Out of Scope Changes check ✅ Passed All changes directly support the core objective of making Cmd+D fully rebindable and clearable. Build script improvements (Zig binary selection) and documentation updates are justified supporting changes that enable the feature to work correctly.

✏️ 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-2524-cmd-d-rebindable

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
Review rate limit: 6/8 reviews remaining, refill in 14 minutes and 22 seconds.

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

Bring the PR branch up to date with origin/main and resolve the only content conflict in AppDelegate.swift by preserving both main's minimal-mode/right-sidebar routing and the stale shortcut suppression needed for cleared or remapped Cmd-D passthrough.

Constraint: User explicitly requested pulling from origin main and resolving merge conflicts

Rejected: Rebase branch | pull request already uses the required two visible test/fix commits, and the user specifically requested pull from origin main

Confidence: high

Scope-risk: moderate

Tested: git diff --check; bash -n scripts/reload.sh; bash -n scripts/build-ghostty-cli-helper.sh; Localizable.xcstrings JSON parse; cmux settings schema JSON parse; no unresolved merge paths

Not-tested: Local xcodebuild tests remain forbidden by task instruction; final tagged reload to follow

Related: #2524

@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

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

Inline comments:
In `@Sources/AppDelegate.swift`:
- Around line 11884-11907: The function shouldSuppressStaleCmuxMenuShortcut is
currently returning true even when a non-terminal responder (e.g., browser
address bar) is focused; change it so it only considers stale shortcut
suppression when the current key responder belongs to a terminal view: check the
key window's firstResponder (or walk the responder chain) and confirm it is a
GhosttyNSView or a descendant/owned responder before running the existing
shortcut-matching loops; if the responder is not terminal-owned, return false
early. Use symbols mentioned (shouldSuppressStaleCmuxMenuShortcut,
matchesKeyboardShortcutEvent, KeyboardShortcutSettings.Action, GhosttyNSView) to
locate and implement the responder-type check.
🪄 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: 8e86f827-238f-4465-b385-155f200f8a37

📥 Commits

Reviewing files that changed from the base of the PR and between ff3ba32 and ba39f7e.

📒 Files selected for processing (12)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/CmuxConfig.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/TerminalController.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • scripts/build-ghostty-cli-helper.sh
  • scripts/reload.sh
  • web/app/[locale]/docs/keyboard-shortcuts/page.tsx
  • web/data/cmux-settings.schema.json

Comment thread Sources/AppDelegate.swift
@greptile-apps

greptile-apps Bot commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes Cmd+D's split shortcut fully rebindable by introducing StoredShortcut.unbound, suppressing stale AppKit menu key equivalents when a cmux shortcut is remapped or cleared, and unbinding Ghostty's own super+d/super+shift+d fallbacks so the Ghostty layer never intercepts a cmux-owned shortcut that the user has cleared.

The overall approach is well-structured: the shouldSuppressStaleCmuxMenuShortcut interception in NSApplication.sendEvent is the correct gating layer and is covered by testApplicationSendEventSuppressesRemappedCmdDStaleMenuShortcut; the performKeyEquivalent override is defense-in-depth for direct calls and correctly returns true to claim the event when forwarding to a Ghostty view.

Confidence Score: 4/5

Safe to merge; all three findings are P2 — no runtime breakage in the changed paths.

No P0 or P1 issues found. The core suppression logic is sound and is covered by three targeted regression tests. Two P2s flag a silent socket-API semantic change and a test that always fails in non-Debug builds. The third P2 notes a documentation gap around the performKeyEquivalent return false branch.

Sources/TerminalController.swift (socket "clear" semantics change) and cmuxTests/AppDelegateShortcutRoutingTests.swift (non-DEBUG XCTFail branch).

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Adds shouldSuppressStaleCmuxMenuShortcut to intercept stale menu key equivalents after a shortcut is remapped/cleared, with guards for isUnbound in three matchConfiguredShortcut helpers; the suppression logic is sound but the performKeyEquivalent override's return false branch relies on NSApplication.sendEvent already having blocked the event in normal flow.
Sources/KeyboardShortcutSettings.swift Introduces StoredShortcut.unbound / isUnbound, clearShortcut(for:), onClearButtonPressed in the recorder, conflict-check guards for unbound shortcuts, and config parsing for empty-string/none/clear/unbound values — all internally consistent.
Sources/GhosttyTerminalView.swift Adds loadCmuxOwnedGhosttyKeybindOverrides to unbind Ghostty's default super+d / super+shift+d split fallbacks (applied to both main and fallback config), and fixes context menu items to read current KeyboardShortcutSettings values instead of stale hardcoded key equivalents.
Sources/TerminalController.swift Adds split_right/split_down socket command targets; splits "clear" (→ unbound) from "default"/"reset" (→ original default) — a semantic breaking change for socket clients previously using "clear" as a synonym for reset.
cmuxTests/AppDelegateShortcutRoutingTests.swift Four new regression tests covering cleared/remapped Cmd+D routing at the debugHandleCustomShortcut, performKeyEquivalent, and sendEvent levels; the first test always calls XCTFail in non-DEBUG Release builds via its #else branch.
Sources/CmuxConfig.swift Adds encoding of unbound shortcuts as "" in the JSON config and updates the decode error message to mention the empty-string unbinding syntax.
Sources/cmuxApp.swift Adds allowsClearing: Bool = true to ShortcutRecorderSettingsControl and explicitly passes allowsClearing: false to the global hotkey row, keeping the Clear button absent for non-rebindable hotkeys.
scripts/build-ghostty-cli-helper.sh Adds architecture-aware zig binary selection so cross-architecture builds pick the native zig binary, with a graceful fallback to the default zig if no arch-specific binary is found.
Resources/Localizable.xcstrings Adds shortcut.recorder.clear ("Clear" / "クリア") and shortcut.unbound ("None" / "なし") with correct English and Japanese translations in manual extraction state.

Sequence Diagram

sequenceDiagram
    participant User
    participant NSApp as NSApplication.sendEvent
    participant AppDelegate
    participant NSWindow as NSWindow.performKeyEquivalent
    participant Menu as NSMenu (stale Cmd+D)
    participant GhosttyView as GhosttyNSView

    User->>NSApp: Cmd+D keyDown
    NSApp->>AppDelegate: shouldSuppressStaleCmuxMenuShortcut?
    Note over AppDelegate: Check 1: current shortcut matches? NO (cleared/remapped)<br/>Check 2: default shortcut matches? YES (Cmd+D was default)
    AppDelegate-->>NSApp: true (suppress)
    alt Ghostty view focused
        NSApp->>GhosttyView: keyDown(Cmd+D) forwarded to terminal
    else No Ghostty view
        NSApp-->>User: event dropped
    end
    Note over NSApp: Never reaches cmux_applicationSendEvent

    rect rgb(200, 240, 200)
        Note over NSWindow: Defense-in-depth path
        NSWindow->>AppDelegate: shouldSuppressStaleCmuxMenuShortcut?
        AppDelegate-->>NSWindow: true
        alt Ghostty view is first responder
            NSWindow->>GhosttyView: keyDown(Cmd+D)
            NSWindow-->>User: return true
        else Non-Ghostty first responder
            NSWindow-->>User: return false
        end
    end
Loading

Comments Outside Diff (3)

  1. cmuxTests/AppDelegateShortcutRoutingTests.swift, line 1329-1331 (link)

    P2 #else XCTFail always fails in Release builds

    The #else branch calls XCTFail("debugHandleCustomShortcut is only available in DEBUG") unconditionally, meaning this test is guaranteed to fail whenever the test target is compiled in a non-Debug configuration. The test quality policy requires tests to verify observable runtime behavior through executable paths; this #else branch instead hard-codes a failure with no fallback behavioral assertion. If CI ever exercises this target in Release configuration (e.g., a release-scheme test job), the test will always fail regardless of correctness.

    Consider either confining the entire test to #if DEBUG with #endif, or providing a Release-compatible assertion that verifies the same behavior through a non-DEBUG seam.

  2. Sources/TerminalController.swift, line 12118-12124 (link)

    P2 Silent breaking change in socket "clear" semantics

    Previously "clear" was synonymous with "default"/"reset" (both called resetShortcut). After this change "clear" calls clearShortcut (→ unbound), while only "default" and "reset" keep the old reset-to-default behaviour. Any socket client or script that used "clear" intending to restore the default shortcut will silently get an unbound action instead. The diff does not update any socket protocol documentation or changelog entry for this semantic change.

    If the intent is to keep "clear" as an alias for "reset to default," the two branches can stay merged. If the split is intentional, the change should be noted in socket command docs and the TerminalController.swift call-site comment.

  3. Sources/AppDelegate.swift, line 13161-13174 (link)

    P2 return false in non-Ghostty branch of performKeyEquivalent does not suppress menu in real AppKit dispatch

    When shouldSuppressStaleCmuxMenuShortcut returns true but the first responder is not a Ghostty view, the override returns false. In AppKit's event dispatch pipeline, a false return from performKeyEquivalent causes AppKit to proceed to check the main menu — meaning the stale menu item can still fire for non-terminal windows in this path.

    In normal app usage this is safe because NSApplication.sendEvent returns early before cmux_applicationSendEvent is reached, so the window's performKeyEquivalent is never called. But the test testWindowPerformKeyEquivalentSuppressesRemappedCmdDStaleMenuShortcut calls performKeyEquivalent directly and asserts menuProbe.callCount == 0, which passes trivially because direct calls do not trigger AppKit's menu scanning — the assertion does not verify the menu won't fire in real dispatch. A comment documenting that the return false branch is intentionally safe only because NSApplication.sendEvent is the gating layer would prevent future regressions.

Reviews (1): Last reviewed commit: "Merge origin/main before final Cmd-D sho..." | Re-trigger Greptile

CI failed in workflow-guard-tests because the shortcut regression tests and pass-through fix intentionally grew several already-budgeted Swift files. Refresh the checked-in budget with scripts/swift_file_length_budget.py so the guard reflects the accepted debt from this PR and the current origin/main merge.

Constraint: User requested making the PR pass CI/CD after merging origin/main

Rejected: Remove the regression coverage | the added tests are the proof that clearing/remapping Cmd-D no longer splits

Confidence: high

Scope-risk: narrow

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

Not-tested: xcodebuild/unit tests locally remain forbidden by task instruction

Related: #3338
Bring the Cmd-D shortcut PR branch up to current origin/main and resolve the generated Swift file length budget conflict by regenerating .github/swift-file-length-budget.tsv from the merged tree.

Constraint: User requested resolving PR #3338 merge conflicts

Rejected: Hand-edit budget conflict markers | the budget is generated from current Swift line counts

Confidence: high

Scope-risk: moderate

Tested: git diff --check; no unresolved merge paths; no real conflict marker lines; python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv; ./tests/test_ci_swift_file_length_budget.sh; Localizable.xcstrings JSON parse; cmux settings schema JSON parse; bash -n scripts/reload.sh; bash -n scripts/build-ghostty-cli-helper.sh

Not-tested: Local xcodebuild/unit tests remain forbidden by task instruction; final tagged reload to follow

Related: #3338
Bring PR #3338 up to current origin/main. Main moved the keyboard shortcut recorder and settings rows into dedicated files, so this resolution adopts that split while preserving this PR's Cmd-D passthrough behavior, Ghostty default unbind overlay, debug clear shortcut path, and settings JSON clear/unbound compatibility.

Constraint: User requested resolving GitHub PR merge conflicts
Rejected: Keep old recorder definitions in KeyboardShortcutSettings/cmuxApp | main now owns the split-file implementation and duplicate SwiftUI types would conflict
Confidence: high
Scope-risk: moderate
Tested: git diff --check; no unresolved merge paths; no true conflict markers; python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv; ./tests/test_ci_swift_file_length_budget.sh; Localizable.xcstrings JSON parse; cmux settings schema JSON parse; bash -n scripts/reload.sh; bash -n scripts/build-ghostty-cli-helper.sh
Not-tested: Local xcodebuild/unit tests remain forbidden by task instruction; final tagged reload to follow
Related: #3338

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

♻️ Duplicate comments (1)
Sources/AppDelegate.swift (1)

12368-12391: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Limit stale-shortcut suppression to terminal-owned responders.

This helper can still return true while a browser/address-bar/non-terminal responder is focused. Downstream, Line 13512 then returns early, finds no GhosttyNSView, and drops the key instead of letting the current responder handle it. Clearing or remapping Cmd+D can still swallow the shortcut outside terminal panes.

Suggested fix
 func shouldSuppressStaleCmuxMenuShortcut(event: NSEvent) -> Bool {
     guard event.type == .keyDown else { return false }
     if event.window is NSPanel || NSApp.keyWindow is NSPanel || NSApp.modalWindow != nil || NSApp.keyWindow?.attachedSheet != nil {
         return false
     }
     let flags = event.modifierFlags
         .intersection(.deviceIndependentFlagsMask)
         .subtracting([.numericPad, .function, .capsLock])
     guard flags.contains(.command) else { return false }
+    let responder = event.window?.firstResponder
+        ?? NSApp.keyWindow?.firstResponder
+        ?? NSApp.mainWindow?.firstResponder
+    guard cmuxOwningGhosttyView(for: responder) != nil else { return false }

     for action in KeyboardShortcutSettings.Action.allCases where action != .showHideAllWindows {
         let currentShortcut = KeyboardShortcutSettings.shortcut(for: action)
         if matchesKeyboardShortcutEvent(event, action: action, shortcut: currentShortcut) {
             return false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 12368 - 12391,
shouldSuppressStaleCmuxMenuShortcut currently can return true even when a
non-terminal responder (e.g., browser/address-bar) is focused, causing terminal
shortcuts to be swallowed; before concluding suppression (the loop that checks
action.defaultShortcut), verify the focused responder belongs to a terminal
view: inspect the key window's firstResponder (or event.window?.firstResponder)
and ensure it is a GhosttyNSView or has a GhosttyNSView ancestor; if not, return
false. Update shouldSuppressStaleCmuxMenuShortcut to perform this responder
ownership check (using GhosttyNSView type checks or a small helper that walks
responder.superview/superchain) before returning true while still using
matchesKeyboardShortcutEvent and KeyboardShortcutSettings.Action.defaultShortcut
for matching.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Sources/AppDelegate.swift`:
- Around line 12368-12391: shouldSuppressStaleCmuxMenuShortcut currently can
return true even when a non-terminal responder (e.g., browser/address-bar) is
focused, causing terminal shortcuts to be swallowed; before concluding
suppression (the loop that checks action.defaultShortcut), verify the focused
responder belongs to a terminal view: inspect the key window's firstResponder
(or event.window?.firstResponder) and ensure it is a GhosttyNSView or has a
GhosttyNSView ancestor; if not, return false. Update
shouldSuppressStaleCmuxMenuShortcut to perform this responder ownership check
(using GhosttyNSView type checks or a small helper that walks
responder.superview/superchain) before returning true while still using
matchesKeyboardShortcutEvent and KeyboardShortcutSettings.Action.defaultShortcut
for matching.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4b4aa90-0786-4286-818c-4597bce3aac4

📥 Commits

Reviewing files that changed from the base of the PR and between 42a1361 and 6e0278d.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (2)
  • Sources/AppDelegate.swift
  • Sources/KeyboardShortcutSettings.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/KeyboardShortcutSettings.swift

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

Comment thread Sources/AppDelegate.swift
action: KeyboardShortcutSettings.Action
) -> Int? {
let shortcut = KeyboardShortcutSettings.shortcut(for: action)
guard !shortcut.isUnbound else { return nil }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant isUnbound guard check is dead code

Low Severity

The !shortcut.isUnbound condition in the guard at line 12201 is redundant because the early guard at line 12193 already returns nil if shortcut.isUnbound is true. Any execution reaching line 12201 is guaranteed to have isUnbound == false, making that part of the compound guard dead code that adds confusion about whether the property could change between the two checks.

Additional Locations (1)
Fix in Cursor Fix in Web

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

ShubhamPatilsd pushed a commit to emergent-inc/mosaic that referenced this pull request Jul 9, 2026
…ngs (#2524) (#3338)

* Prove cleared Cmd-D split shortcut should pass through

The regression captures the desired shortcut contract before the implementation exists: clearing the split-right shortcut must make Cmd-D fall through instead of creating another pane.

Constraint: Local direct xcodebuild is forbidden for this task; verification runs through allowed reload/CI paths.

Confidence: medium

Scope-risk: narrow

Tested: Not run locally; this test-only commit is expected to fail before the implementation adds the unbound shortcut state.

Not-tested: Local cmux-unit execution because the user explicitly forbade direct xcodebuild.

* Make cleared split shortcuts persist as pass-through

Cmd-D was registered in multiple layers: KeyboardShortcutSettings covered the app-level matcher, while SwiftUI/AppKit menu equivalents and Ghostty's default super+d split binding could still create a split after the setting was cleared or remapped. Persist an explicit unbound shortcut, expose Clear in the keyboard shortcut recorder, source split menus from KeyboardShortcutSettings, suppress stale default menu equivalents, and remove Ghostty's default split fallbacks so KeyboardShortcutSettings is the single owner of split shortcuts.

The reload helper also now selects a target-matching Zig binary for Ghostty helper builds, because the required reload command otherwise picked the x86 Zig from /usr/local on this arm64 host and failed before launch.

Constraint: Direct xcodebuild is forbidden; build verification uses reload.sh only

Rejected: Treat clear as reset | reset restores Cmd-D and keeps terminal apps from receiving it

Rejected: Only suppress the Swift app shortcut matcher | stale AppKit menu equivalents and Ghostty super+d still split

Rejected: Leave Ghostty defaults active | forwarded Cmd-D still triggers Ghostty new_split:right

Confidence: high

Scope-risk: moderate

Tested: git diff --check; bash -n scripts/reload.sh; bash -n scripts/build-ghostty-cli-helper.sh; Localizable.xcstrings JSON parse; cmux settings schema JSON parse; ./scripts/reload.sh --tag issue-2524-cmd-d-rebindable --launch; debug socket remap Cmd+J kept Cmd+D pane count at 15 and Cmd+J increased it to 16; debug socket clear kept Cmd+D pane count at 16; debug socket default restored Cmd+D split to 17 panes

Not-tested: Local cmux-unit via xcodebuild because explicitly forbidden; SwiftPM test path cannot build app target because CMUXDebugLog is not in Package.swift; XCUITests are CI-only; Settings UI automation blocked by macOS Apple Events permission -1743

Related: manaflow-ai/cmux#2524

* Refresh Swift length budget for Cmd-D shortcut tests

CI failed in workflow-guard-tests because the shortcut regression tests and pass-through fix intentionally grew several already-budgeted Swift files. Refresh the checked-in budget with scripts/swift_file_length_budget.py so the guard reflects the accepted debt from this PR and the current origin/main merge.

Constraint: User requested making the PR pass CI/CD after merging origin/main

Rejected: Remove the regression coverage | the added tests are the proof that clearing/remapping Cmd-D no longer splits

Confidence: high

Scope-risk: narrow

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

Not-tested: xcodebuild/unit tests locally remain forbidden by task instruction

Related: manaflow-ai/cmux#3338

This branch was successfully deployed

1 active deployment
Preview – cmux — 6e0278de Deployed Apr 30, 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.

Cmd+D creates a new tab in pane; no way to unbind or remap this shortcut

1 participant