Skip to content

Fix Cmd+I and notifications shortcut conflict - #1135

Closed
austinywang wants to merge 3 commits into
mainfrom
issue-1091-cmd-c-notifications
Closed

austinywang wants to merge 3 commits into
mainfrom
issue-1091-cmd-c-notifications

Conversation

@austinywang

@austinywang austinywang commented Mar 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • move Notifications back to Cmd+Shift+I and make Cmd+I the default Developer Tools shortcut
  • prefer semantic command characters over physical-key fallbacks so Dvorak-style Cmd+C no longer collides with Cmd+I app shortcuts
  • keep the JavaScript console on Option+Cmd+C and refresh the regression tests/comments around shortcut defaults

Closes #1091

Summary by CodeRabbit

  • Bug Fixes

    • Improved keyboard shortcut handling for non-US keyboard layouts (e.g., Dvorak)
    • Corrected Developer Tools shortcut to Cmd+I
    • Adjusted Show Notifications to use Shift+Cmd+I
  • Tests

    • Enhanced keyboard shortcut routing tests for international keyboard layout scenarios

@vercel

vercel Bot commented Mar 10, 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 Mar 13, 2026 3:20am

@coderabbitai

coderabbitai Bot commented Mar 10, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR fixes keyboard shortcut routing by introducing layout-aware character matching that differentiates semantic characters from physical key codes. It reassigns showNotifications to Cmd+Shift+I and toggleBrowserDeveloperTools to Cmd+I, adds Dvorak keyboard support, and updates corresponding tests.

Changes

Cohort / File(s) Summary
Core Shortcut Implementation
Sources/AppDelegate.swift, Sources/KeyboardShortcutSettings.swift
Refactored shortcut matching logic to prefer layout-aware semantic characters over physical key codes, with fallback paths for non-text shortcuts. Updated default shortcuts: showNotifications now uses Cmd+Shift+I (shifted), toggleBrowserDeveloperTools uses Cmd+I (no option). Removed Safari-specific references.
Shortcut Routing Tests
cmuxTests/AppDelegateShortcutRoutingTests.swift
Renamed tests to reflect Dvorak-aware behavior and introduced explicit StoredShortcut usage. Updated NSEvent construction to separate semantics (characters) from layout-derived input (charactersIgnoringModifiers), enabling proper routing under non-US keyboard layouts.
Default Shortcut Tests
cmuxTests/CmuxWebViewKeyEquivalentTests.swift
Renamed test class from BrowserDeveloperToolsShortcutDefaultsTests to BrowserShortcutDefaultsTests. Updated method names and expectations: toggleDeveloperTools option modifier now false, showJavaScriptConsole option now false. Added new test for showNotifications default shortcut validation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 Shift+i makes notifications pop,
Plain i for dev tools—shortcuts now stop!
Dvorak or ANSI, we've got the key,
Layout-aware matching, smooth as can be. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description covers what changed and why, but is missing Testing and Demo Video sections required by the template, and lacks specific verification details. Add Testing section documenting manual verification, how Cmd+I and Cmd+Shift+I work correctly, and Dvorak layout testing; include Demo Video link or note if not applicable.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix Cmd+I and notifications shortcut conflict' directly addresses the main objective of resolving the keyboard shortcut conflict by reassigning Notifications to Cmd+Shift+I and making Cmd+I the Developer Tools shortcut.
Linked Issues check ✅ Passed The PR successfully addresses the core requirement from #1091 by reassigning Notifications to Cmd+Shift+I and making Cmd+I the Developer Tools shortcut, while preserving Dvorak layout support.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the Cmd+C/Cmd+I conflict: AppDelegate shortcut routing, KeyboardShortcutSettings defaults, and comprehensive test updates for the new mappings.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue-1091-cmd-c-notifications
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 4 files

@greptile-apps

greptile-apps Bot commented Mar 10, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR resolves a shortcut conflict (issue #1091) by redistributing the I-key shortcuts: showNotifications moves from Cmd+I → Cmd+Shift+I, and toggleBrowserDeveloperTools moves from Option+Cmd+I → Cmd+I (aligning with Chrome/Firefox conventions). The JavaScript Console shortcut (Option+Cmd+C) is unchanged. The debug probe in developerToolsShortcutProbeKind is correctly split into two if blocks to reflect the new modifier sets, and all regression tests have been refreshed — including preserving the Dvorak Cmd+C-on-physical-I coverage.

Key changes:

  • KeyboardShortcutSettings.swift: showNotifications → Cmd+Shift+I; toggleBrowserDeveloperTools → Cmd+I; stale Safari-comment on showBrowserJavaScriptConsole removed
  • AppDelegate.swift: debug-only developerToolsShortcutProbeKind split and shortcut comments updated
  • AppDelegateShortcutRoutingTests.swift: Dvorak test now explicitly overrides showNotifications to the old Cmd+I value so the key-collision scenario can still be exercised; notification trigger test renamed and updated to Cmd+Shift+I
  • CmuxWebViewKeyEquivalentTests.swift: new testDefaultShortcutForShowNotifications added; developer-tools default test updated; Safari-specific test names replaced with generic names

Issues found:

  • testDefaultShortcutForShowNotifications is placed inside the BrowserDeveloperToolsShortcutDefaultsTests class — class name does not reflect the mixed content
  • During the Dvorak test, both showNotifications (temporarily overridden to Cmd+I) and toggleBrowserDeveloperTools (new default Cmd+I) share the same binding; the test still passes correctly but the comment doesn't acknowledge the dual-binding situation

Confidence Score: 4/5

  • This PR is safe to merge; the shortcut remapping is correct and all routing logic and tests are consistent with the new bindings.
  • The core logic changes are straightforward and correct. The only concerns are cosmetic/organisational: a test placed in a misnamed class and a test comment that doesn't fully explain the dual Cmd+I binding within its scope. No functional regressions are introduced.
  • No files require special attention beyond the minor test-organisation nits in cmuxTests/CmuxWebViewKeyEquivalentTests.swift and cmuxTests/AppDelegateShortcutRoutingTests.swift.

Important Files Changed

Filename Overview
Sources/KeyboardShortcutSettings.swift Correctly reassigns showNotifications from Cmd+I → Cmd+Shift+I and toggleBrowserDeveloperTools from Option+Cmd+I → Cmd+I; stale Safari-specific comments removed.
Sources/AppDelegate.swift Debug probe developerToolsShortcutProbeKind correctly split into two separate if blocks matching the new shortcut modifiers; shortcut-handling comments updated accurately.
cmuxTests/AppDelegateShortcutRoutingTests.swift Tests updated to use Cmd+Shift+I for showNotifications; Dvorak test now explicitly overrides showNotifications to the old Cmd+I, inadvertently creating two Cmd+I shortcuts within the test scope — functionally harmless but potentially confusing.
cmuxTests/CmuxWebViewKeyEquivalentTests.swift Default-shortcut assertions updated correctly; new testDefaultShortcutForShowNotifications test is logically correct but organisationally misplaced inside BrowserDeveloperToolsShortcutDefaultsTests.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    KE[Key Event received\nin AppDelegate] --> SN{Cmd+Shift+I?\nshowNotifications}
    SN -- yes --> SNA[Toggle Notifications Popover\nreturn true]
    SN -- no --> BDT{Cmd+I?\ntoggleBrowserDeveloperTools}
    BDT -- yes --> BDTA[Toggle Dev Tools\nNSSound.beep if not handled\nreturn true]
    BDT -- no --> BJC{Option+Cmd+C?\nshowBrowserJavaScriptConsole}
    BJC -- yes --> BJCA[Show JS Console\nNSSound.beep if not handled\nreturn true]
    BJC -- no --> OTHER[Continue to other\nshortcut checks]
Loading

Comments Outside Diff (1)

  1. cmuxTests/AppDelegateShortcutRoutingTests.swift, line 361-388 (link)

    Duplicate Cmd+I shortcut within test scope

    After this PR toggleBrowserDeveloperTools defaults to Cmd+I. The withTemporaryShortcut here additionally overrides showNotifications to the old Cmd+I default, meaning both actions are mapped to Cmd+I simultaneously during the test body.

    The negative assertion (Dvorak Cmd+C physical-I returns false) still passes because matchShortcut uses charactersIgnoringModifiers ("c") and correctly rejects both shortcuts. However, the test comment only references showNotifications and does not acknowledge the toggleBrowserDeveloperTools shadow, which could confuse a reader who wonders why the test doesn't also assert the positive case (real Cmd+I routing to the developer tools shortcut).

    Consider adding a brief comment clarifying that both shortcuts are Cmd+I in this scope, or splitting the concern into a separate test for toggleBrowserDeveloperTools.

Last reviewed commit: 19f85f4

Comment on lines +1247 to +1254
func testDefaultShortcutForShowNotifications() {
let shortcut = KeyboardShortcutSettings.Action.showNotifications.defaultShortcut
XCTAssertEqual(shortcut.key, "i")
XCTAssertTrue(shortcut.command)
XCTAssertTrue(shortcut.shift)
XCTAssertFalse(shortcut.option)
XCTAssertFalse(shortcut.control)
}

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.

Test added to mismatched class

testDefaultShortcutForShowNotifications tests the showNotifications action, but it has been placed inside the BrowserDeveloperToolsShortcutDefaultsTests class. That class name (and its two existing tests) is scoped to the browser developer-tools shortcuts. Adding an unrelated notification shortcut test here will make the suite harder to navigate over time.

Consider either renaming the class (e.g. BrowserAndNotificationShortcutDefaultsTests) or moving the new test to an existing class that covers general shortcut defaults.

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

9247-9308: ⚠️ Potential issue | 🟠 Major

Don't short-circuit before the layout-aware branch runs.

Line 9278 returns false for Command-letter shortcuts before shortcutLayoutCharacterProvider(...) is consulted. That makes the new semantic-layout path unreachable whenever either event string is populated, so alternate input sources that still surface physical or Option-transformed characters can still miss Cmd+I / ⌥Cmd+C.

Suggested fix
         let hasEventChars =
             !(eventCharacters?.isEmpty ?? true)
             || !(eventCharsIgnoringModifiers?.isEmpty ?? true)
-        if hasEventChars,
-           flags.contains(.command),
-           !flags.contains(.control),
-           shouldRequireCharacterMatchForCommandShortcut(shortcutKey: shortcutKey) {
-            return false
-        }
 
         // Match using the current keyboard layout so Command shortcuts stay character-based
         // across layouts (QWERTY, Dvorak, etc.) instead of being tied to ANSI physical keys.
         let layoutCharacter = shortcutLayoutCharacterProvider(event.keyCode, event.modifierFlags)
         if shortcutCharacterMatches(
@@
         ) {
             return true
         }
+
+        if hasEventChars,
+           flags.contains(.command),
+           !flags.contains(.control),
+           shouldRequireCharacterMatchForCommandShortcut(shortcutKey: shortcutKey) {
+            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 9247 - 9308, The early return that
blocks layout-aware matching is too aggressive: instead of returning false
immediately in the block that checks hasEventChars && flags.contains(.command)
&& !flags.contains(.control) &&
shouldRequireCharacterMatchForCommandShortcut(shortcutKey:), compute or retrieve
the layoutCharacter via shortcutLayoutCharacterProvider(event.keyCode,
event.modifierFlags) first and only short-circuit when that layoutCharacter is
empty (i.e., no semantic layout character is available); in practice move or
duplicate the shortcutLayoutCharacterProvider call so the check becomes
conditional on layoutCharacter?.isEmpty (so the layout-aware branch still runs
when a semantic character exists) while preserving the existing strictness for
command-letter shortcuts when no layout character is present.
🧹 Nitpick comments (1)
Sources/AppDelegate.swift (1)

8958-8968: Keep the debug probe semantic too.

This probe still treats ANSI key codes 34 and 8 as literal Cmd+I / ⌥Cmd+C. On Dvorak, semantic Cmd+C can still arrive on ANSI 34, so the new debug trace will label copy as a developer-tools candidate again. Reusing matchShortcut(...) here keeps the diagnostics aligned with the real routing logic.

Suggested change
-        let chars = (event.charactersIgnoringModifiers ?? "").lowercased()
-        let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
-        if flags == [.command] {
-            if chars == "i" || event.keyCode == 34 {
-                return "toggle.literal"
-            }
-        }
-        if flags == [.command, .option] {
-            if chars == "c" || event.keyCode == 8 {
-                return "console.literal"
-            }
-        }
+        if matchShortcut(
+            event: event,
+            shortcut: StoredShortcut(key: "i", command: true, shift: false, option: false, control: false)
+        ) {
+            return "toggle.literal"
+        }
+        if matchShortcut(
+            event: event,
+            shortcut: StoredShortcut(key: "c", command: true, shift: false, option: true, control: false)
+        ) {
+            return "console.literal"
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 8958 - 8968, The debug probe
currently treats ANSI key codes (event.keyCode == 34 / 8) as literal shortcuts
which breaks semantics on non‑ANSI layouts; replace the direct keyCode checks in
the block that returns "toggle.literal" and "console.literal" so they call the
existing matchShortcut(...) helper (passing flags, chars and/or event as its
signature requires) instead of comparing event.keyCode, and only return the
literal probe label when matchShortcut(...) indicates the semantic shortcut
matches; keep the existing flags and chars checks but remove the hardcoded
numeric keyCode comparisons to ensure the probe uses the same semantic routing
logic as the app.
🤖 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/AppDelegate.swift`:
- Around line 9247-9308: The early return that blocks layout-aware matching is
too aggressive: instead of returning false immediately in the block that checks
hasEventChars && flags.contains(.command) && !flags.contains(.control) &&
shouldRequireCharacterMatchForCommandShortcut(shortcutKey:), compute or retrieve
the layoutCharacter via shortcutLayoutCharacterProvider(event.keyCode,
event.modifierFlags) first and only short-circuit when that layoutCharacter is
empty (i.e., no semantic layout character is available); in practice move or
duplicate the shortcutLayoutCharacterProvider call so the check becomes
conditional on layoutCharacter?.isEmpty (so the layout-aware branch still runs
when a semantic character exists) while preserving the existing strictness for
command-letter shortcuts when no layout character is present.

---

Nitpick comments:
In `@Sources/AppDelegate.swift`:
- Around line 8958-8968: The debug probe currently treats ANSI key codes
(event.keyCode == 34 / 8) as literal shortcuts which breaks semantics on
non‑ANSI layouts; replace the direct keyCode checks in the block that returns
"toggle.literal" and "console.literal" so they call the existing
matchShortcut(...) helper (passing flags, chars and/or event as its signature
requires) instead of comparing event.keyCode, and only return the literal probe
label when matchShortcut(...) indicates the semantic shortcut matches; keep the
existing flags and chars checks but remove the hardcoded numeric keyCode
comparisons to ensure the probe uses the same semantic routing logic as the app.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2437a7bc-40e8-440c-9342-a4d9ff04f1be

📥 Commits

Reviewing files that changed from the base of the PR and between dc1f714 and 2aa419d.

📒 Files selected for processing (4)
  • Sources/AppDelegate.swift
  • Sources/KeyboardShortcutSettings.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • cmuxTests/CmuxWebViewKeyEquivalentTests.swift

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview — 2aa419d8 Deployed Mar 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cmd+C opens Notifications panel instead of copying text

3 participants