Skip to content

Keep cmux browser Find shortcuts authoritative - #2356

Merged
austinywang merged 4 commits into
mainfrom
issue-2342-cmdf-browser-passthrough
Mar 30, 2026
Merged

austinywang merged 4 commits into
mainfrom
issue-2342-cmdf-browser-passthrough

Conversation

@austinywang

@austinywang austinywang commented Mar 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • route browser-first Find-family shortcuts through web content for native web-app handlers
  • add regression coverage for the Find shortcut family, including unclaimed Cmd+E replay and visible browser find-bar ownership
  • keep the visible cmux browser find bar authoritative for Cmd+F/Cmd+G/Cmd+Shift+G/Cmd+Shift+F and avoid replaying unclaimed Find shortcuts into WebKit twice

Verification

  • ./scripts/reload.sh --tag find-routing-review

Notes

  • commit 1 adds the browser-first routing and regression coverage
  • commit 2 addresses the review regressions so CI can show the red/green transition across commits

Summary by cubic

Routes the Find shortcut family to web content first so native web apps can handle them, while keeping the cmux browser find bar authoritative when visible and preventing double delivery; skips this preflight when Web Inspector is focused. Addresses Linear issue 2342.

  • New Features

    • Browser-first routing for Cmd+F/Cmd+G/Cmd+Shift+G/Cmd+Shift+F/Cmd+E; visible cmux browser find bar keeps ownership.
    • Skip browser-first routing when a Web Inspector responder is focused.
    • Goto-split recorder now tracks page title/URL and browser find visibility/selection; supports CMUX_UI_TEST_GOTO_SPLIT_BROWSER_URL.
  • Bug Fixes

    • Suppress NSWindow/WebKit replays to avoid sending unclaimed Find shortcuts twice.
    • More robust shortcut detection across keyboard layouts: use normalized characters when ASCII is produced, fall back to keyCode only for non‑ASCII; added regression/UI tests for routing, inspector focus, and non‑Latin input.

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Browser Find shortcuts (Cmd+F, Cmd+E, Cmd+G, Cmd+Shift+F, Cmd+Shift+G) now route to web content first.
    • When the browser find overlay is visible, these shortcuts remain owned by the browser instead of the app menu.
    • UI test configuration now supports custom browser URLs via environment settings.
    • Enhanced UI test recording captures browser find visibility and selection state.
  • Tests

    • Added comprehensive test coverage for Find shortcut routing behavior across keyboard layouts.
    • New UI tests validate keyboard shortcut handling in browser and app contexts.

@vercel

vercel Bot commented Mar 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 Mar 30, 2026 10:08am

@coderabbitai

coderabbitai Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds keyboard shortcut routing for browser Find commands (Cmd+F/E/G variants), implementing a preflight policy that routes these shortcuts to WebKit first before falling back to cmux menu handling. It includes ownership detection to determine browser find overlay visibility, routing suppression during overlay presence, and expanded unit and UI test coverage for this behavior.

Changes

Cohort / File(s) Summary
Browser Find Routing Core
Sources/AppDelegate.swift, Sources/Panels/CmuxWebView.swift
Implemented Find-family key-equivalent detection (Cmd+F/E/G, Cmd+Shift+F/G) with a preflight routing policy via shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst(). Added BrowserPanel ownership resolution and browser find bar visibility checks. Updated key-equivalent handling to deliver matching Find shortcuts to the focused browser web view while suppressing double-observation.
Unit Test Coverage
cmuxTests/AppDelegateShortcutRoutingTests.swift
Added centralized makeKeyEvent() helper and three new "Non-Latin keyboard layout shortcut tests" covering browser-first routing eligibility, non-Latin input fallback, and unrelated shortcut exclusion.
UI Test Enhancement
cmuxUITests/MenuKeyEquivalentRoutingUITests.swift
Reworked UI tests to validate Cmd/Cmd+Shift key-equivalent routing and cmux find ownership. Extended launchWithBrowserSetup() with configurable browser URL via CMUX_UI_TEST_GOTO_SPLIT_BROWSER_URL. Added three new public tests with helper methods for data-URI HTML pages and focus tracking, plus diagnostic goto-split assertions.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant NSWindow as NSWindow
    participant AppDelegate as AppDelegate
    participant CmuxWebView as CmuxWebView
    participant WebKit as WebKit
    participant BrowserPanel as BrowserPanel
    
    User->>NSWindow: Press Cmd+F
    NSWindow->>AppDelegate: cmux_performKeyEquivalent()
    
    AppDelegate->>AppDelegate: shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst()?
    
    alt Browser Find Overlay Visible
        AppDelegate->>CmuxWebView: performKeyEquivalent(with:)
        CmuxWebView->>WebKit: super.performKeyEquivalent()
        WebKit-->>CmuxWebView: handled = true
        CmuxWebView-->>AppDelegate: true (suppress menu)
    else Browser Find Overlay Hidden
        AppDelegate->>BrowserPanel: browserFindBarIsVisible()?
        BrowserPanel-->>AppDelegate: false
        AppDelegate->>AppDelegate: Use fallback routing (menu/cmux)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 A keyboard dance, so swift and true,
Cmd+F finds, now routed through WebKit's view,
Ownership checks, when find bars glow bright,
No double-observation—just shortcuts done right! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective of the pull request—making the cmux browser Find shortcuts authoritative while implementing browser-first routing.
Description check ✅ Passed The pull request description follows the required template with all major sections completed: Summary explains the changes clearly, Verification includes the test command, and a checklist is present with items marked.

✏️ 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-2342-cmdf-browser-passthrough

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

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR teaches cmux to let web content see Find-family shortcuts (Cmd+F / Cmd+G / Cmd+Shift+G / Cmd+Shift+F / Cmd+E) before the cmux menu fallback, enabling native web-app shortcuts like VS Code's Cmd+F while ensuring the visible cmux browser find bar keeps ownership of navigation shortcuts.

Key design decisions:

  • A new browserFindCommandEquivalent classifier and shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst gate are added as top-level free functions so unit tests can exercise them without an app delegate.
  • The CmuxWebView.performKeyEquivalent early-dispatch path sets replayedBrowserFindShortcutIntoWebContent = true so the tail super.performKeyEquivalent call is skipped, preventing WebKit from receiving the same event twice.
  • The NSWindow override mirrors this protection: it calls firstResponderWebView.performKeyEquivalent (which internally drives menu dispatch) and then unconditionally returns true, preventing the normal cmux_performKeyEquivalent view-walk from dispatching the event a second time.
  • The browserFindBarIsVisible visibility check uses MainActor.assumeIsolated correctly — all callers are on the main thread via AppKit key-event dispatch.
  • Three new UI tests wire up inline HTML data: pages to verify web-content passthrough, no-double-replay for unclaimed Cmd+E, and find-bar ownership of Cmd+G / Cmd+Shift+F.

Findings:

  • The clickBrowserPane helper uses a 150 ms settle delay, half the 300 ms used in the existing refocusWebView helper. All post-click assertions use proper waitForGotoSplitMatch timeouts so the risk is low, but aligning to 300 ms reduces potential flakiness on slow CI.

Confidence Score: 5/5

Safe to merge — routing logic is sound, double-dispatch is correctly prevented at both the window and web-view layers, and all remaining feedback is P2.

No P0 or P1 issues found. The browser-find preflight logic correctly prevents WebKit from observing the same key equivalent twice (via the replayedBrowserFindShortcutIntoWebContent flag in CmuxWebView and the unconditional return true in the window override). Visibility-based ownership is enforced on the main thread with MainActor.assumeIsolated. The single finding — a 150 ms settle delay in the clickBrowserPane test helper vs. the 300 ms precedent — is purely a test-robustness style note and does not block correctness.

No files require special attention.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Adds browser-first Find shortcut routing infrastructure: browserFindCommandEquivalent, shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst, browserFindBarIsVisible, and browserPanelOwning helpers; intercepts Find-family shortcuts in the window's performKeyEquivalent before the normal menu path; adds gotoSplitUITestRecorder timer and extra UI-test state fields for find-bar ownership coverage.
Sources/Panels/CmuxWebView.swift Adds a browser-find preflight inside performKeyEquivalent: when a Find-family shortcut is identified and the cmux find bar is not visible, calls super.performKeyEquivalent once before the menu path, then skips the tail super call to prevent a double WebKit dispatch.
cmuxTests/AppDelegateShortcutRoutingTests.swift Adds three unit tests for shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst: recognizes the full Find shortcut family, falls back to keyCode for non-Latin input, and excludes non-Find shortcuts. Also adds a makeKeyEvent helper to construct synthetic NSEvent instances.
cmuxUITests/MenuKeyEquivalentRoutingUITests.swift Adds three UI regression tests (web-content Cmd+F passthrough, no double Cmd+E replay, visible find-bar ownership of Cmd+G/Cmd+Shift+F). Extends launchWithBrowserSetup to accept a custom URL via CMUX_UI_TEST_GOTO_SPLIT_BROWSER_URL. The clickBrowserPane helper uses a 150 ms sleep that is half the 300 ms used in the existing refocusWebView helper — may be marginally flaky on slow CI.

Sequence Diagram

sequenceDiagram
    participant AppKit as AppKit Dispatch
    participant Win as CmuxWindow.performKeyEquivalent
    participant Vis as BrowserFindVisibility Check
    participant WV as CmuxWebView.performKeyEquivalent
    participant WK as WebKit (super)
    participant Menu as NSApp.mainMenu

    AppKit->>Win: performKeyEquivalent(Cmd+F / Cmd+G etc.)
    Win->>Vis: shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst(event, webView)
    alt Find bar IS visible (Cmd+F/G/Shift+G/Shift+F)
        Vis-->>Win: false
        Win->>Menu: menu.performKeyEquivalent (via cmux_performKeyEquivalent)
        Menu-->>Win: true (cmux find bar handles it)
    else Find bar NOT visible (or Cmd+E)
        Vis-->>Win: true
        Win->>WV: firstResponderWebView.performKeyEquivalent(event)
        WV->>Vis: shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst (re-check)
        Vis-->>WV: true
        WV->>WK: super.performKeyEquivalent(event) [first and only WebKit call]
        alt Web page claims shortcut (e.g. VS Code Cmd+F)
            WK-->>WV: true
            WV-->>Win: true
        else Web page does not claim shortcut
            WK-->>WV: false
            WV->>Menu: menu.performKeyEquivalent (cmux find bar opens)
            Menu-->>WV: true
            WV-->>Win: true
        else Nothing claims shortcut (e.g. bare Cmd+E)
            WK-->>WV: false
            Menu-->>WV: false
            Note over WV: replayedBrowserFindShortcutIntoWebContent=true → skip 2nd WebKit call
            WV-->>Win: false
        end
        Note over Win: Always return true to suppress double WebKit replay
        Win-->>AppKit: true
    end
Loading

Comments Outside Diff (1)

  1. cmuxUITests/MenuKeyEquivalentRoutingUITests.swift, line 330-334 (link)

    P2 clickBrowserPane settle delay is half of refocusWebView's

    RunLoop.current.run(until: Date().addingTimeInterval(0.15)) gives 150 ms for the click to settle, while the existing refocusWebView helper uses 300 ms. On slower CI runners this is the only gap between the click and the next app.typeKey(...) call. Because every assertion that follows uses a proper waitForGotoSplitMatch(timeout:) budget the practical risk is low, but aligning with the 300 ms precedent would reduce flake risk:

Reviews (1): Last reviewed commit: "Keep cmux browser Find shortcuts authori..." | 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: 2

Caution

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

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

251-298: ⚠️ Potential issue | 🟠 Major

Add state tracking to prevent Find shortcut re-delivery through keyDown.

The replayedBrowserFindShortcutIntoWebContent guard only suppresses the trailing super.performKeyEquivalent(with:) at lines 297–298. If WebKit declines the preflighted Find shortcut and cmux also declines it, performKeyEquivalent(with:) still returns false, so the same event flows to keyDown(with:). There, super.keyDown(with:) re-exposes unclaimed Cmd+E/Find-family events to WebKit a second time.

The replayedBrowserFindShortcutIntoWebContent flag is local to performKeyEquivalent and inaccessible in keyDown. Pass this state via a property or event annotation so keyDown can suppress Find shortcuts that were already preflighted.

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

In `@Sources/Panels/CmuxWebView.swift` around lines 251 - 298, The preflight flag
replayedBrowserFindShortcutIntoWebContent is local to performKeyEquivalent so
keyDown cannot tell the event was already offered to WebKit; add an instance
property (e.g. var didPreflightBrowserFindShortcut: Bool) on the CmuxWebView
class, set it true when you preflight in performKeyEquivalent (where
replayedBrowserFindShortcutIntoWebContent is currently set), and then in
keyDown(with:) check that property to suppress re-delivery to WebKit (and reset
it to false once the event is consumed or discarded). Ensure the property is
cleared after handling each event to avoid permanently blocking real Find
shortcuts.
🧹 Nitpick comments (1)
cmuxUITests/MenuKeyEquivalentRoutingUITests.swift (1)

321-326: Prefer deterministic focus confirmation over fixed 150ms sleep after pane click.

Line 325 uses a timing sleep, which can be flaky on slower CI runners. Waiting for a concrete state signal (for example, focused panel change) would make this helper more stable.

♻️ Suggested stabilization
     private func clickBrowserPane(app: XCUIApplication, browserPanelId: String) {
         let browserPane = app.otherElements["BrowserPanelContent.\(browserPanelId)"].firstMatch
         XCTAssertTrue(browserPane.waitForExistence(timeout: 6.0), "Expected browser pane content for click target")
         browserPane.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).click()
-        RunLoop.current.run(until: Date().addingTimeInterval(0.15))
+        XCTAssertTrue(
+            waitForGotoSplitMatch(timeout: 2.0) { data in
+                data["focusedPanelId"] == browserPanelId
+            },
+            "Expected browser pane to become focused after click"
+        )
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxUITests/MenuKeyEquivalentRoutingUITests.swift` around lines 321 - 326,
The helper clickBrowserPane currently uses a fixed RunLoop delay after clicking
(RunLoop.current.run(until: Date().addingTimeInterval(0.15))) which is flaky;
update clickBrowserPane to replace the hard sleep with a deterministic wait that
polls for a concrete focus signal after the click (e.g., waitUntil browserPane
reports focused/selected or an app-level “focused panel” element/attribute
changes), using browserPane (from BrowserPanelContent.\(browserPanelId)) and a
reasonable timeout; implement polling (short interval loop) that returns when
browserPane.isFocused / isSelected / accessibilityValue indicates focus (or when
the app’s focused-panel indicator updates) to stabilize tests on slow CI.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmuxUITests/MenuKeyEquivalentRoutingUITests.swift`:
- Around line 132-137: The current test only samples once after
RunLoop.current.run(until: Date().addingTimeInterval(0.5)) which can miss a late
second WebKit replay; change the assertion to observe stability over a window by
polling loadGotoSplit() multiple times (e.g., every 0.05–0.1s for a total of
~0.5–1s) and fail if any sample's ["browserPageTitle"] deviates from "cmde-1".
Update the block around RunLoop.current.run and XCTAssertEqual to loop, call
loadGotoSplit() repeatedly, and assert stability across all samples (reference:
RunLoop.current.run, loadGotoSplit(), XCTAssertEqual).

In `@Sources/AppDelegate.swift`:
- Around line 1877-1884: The matches(_ chars:keyCode:) function currently falls
back to event.keyCode even when KeyboardLayout.normalizedCharacters(for:)
produced a non-matching ASCII character, causing physical-key shortcuts to
misfire on non‑QWERTY layouts; change the logic so you only use the
event.keyCode fallback when the normalizedCharacters result is unavailable/empty
(e.g. normalizedChars.isEmpty) rather than whenever normalizedChars !=
chars—keep the initial normalizedChars == chars fast path, and otherwise if
normalizedChars.isEmpty return event.keyCode == keyCode, else return false.

---

Outside diff comments:
In `@Sources/Panels/CmuxWebView.swift`:
- Around line 251-298: The preflight flag
replayedBrowserFindShortcutIntoWebContent is local to performKeyEquivalent so
keyDown cannot tell the event was already offered to WebKit; add an instance
property (e.g. var didPreflightBrowserFindShortcut: Bool) on the CmuxWebView
class, set it true when you preflight in performKeyEquivalent (where
replayedBrowserFindShortcutIntoWebContent is currently set), and then in
keyDown(with:) check that property to suppress re-delivery to WebKit (and reset
it to false once the event is consumed or discarded). Ensure the property is
cleared after handling each event to avoid permanently blocking real Find
shortcuts.

---

Nitpick comments:
In `@cmuxUITests/MenuKeyEquivalentRoutingUITests.swift`:
- Around line 321-326: The helper clickBrowserPane currently uses a fixed
RunLoop delay after clicking (RunLoop.current.run(until:
Date().addingTimeInterval(0.15))) which is flaky; update clickBrowserPane to
replace the hard sleep with a deterministic wait that polls for a concrete focus
signal after the click (e.g., waitUntil browserPane reports focused/selected or
an app-level “focused panel” element/attribute changes), using browserPane (from
BrowserPanelContent.\(browserPanelId)) and a reasonable timeout; implement
polling (short interval loop) that returns when browserPane.isFocused /
isSelected / accessibilityValue indicates focus (or when the app’s focused-panel
indicator updates) to stabilize tests on slow CI.
🪄 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: 56d632b2-9af7-47b7-9c04-5670c4c89dc7

📥 Commits

Reviewing files that changed from the base of the PR and between 29c0f52 and 80d99b9.

📒 Files selected for processing (4)
  • Sources/AppDelegate.swift
  • Sources/Panels/CmuxWebView.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • cmuxUITests/MenuKeyEquivalentRoutingUITests.swift

Comment on lines +132 to +137
RunLoop.current.run(until: Date().addingTimeInterval(0.5))
XCTAssertEqual(
loadGotoSplit()?["browserPageTitle"],
"cmde-1",
"Expected Cmd+E to avoid a second WebKit replay. data=\(loadGotoSplit() ?? [:])"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Single delayed snapshot can miss late double-replay of Cmd+E.

Line 132 and Line 133 only check one point-in-time after 0.5s. A delayed second replay can slip past this check and still pass. Prefer asserting stability across a full observation window.

🔧 Proposed reliability fix
-        RunLoop.current.run(until: Date().addingTimeInterval(0.5))
-        XCTAssertEqual(
-            loadGotoSplit()?["browserPageTitle"],
-            "cmde-1",
-            "Expected Cmd+E to avoid a second WebKit replay. data=\(loadGotoSplit() ?? [:])"
-        )
+        let replayDetected = waitForCondition(timeout: 1.5) {
+            (self.loadGotoSplit()?["browserPageTitle"] ?? "") != "cmde-1"
+        }
+        XCTAssertFalse(
+            replayDetected,
+            "Expected Cmd+E to avoid a second WebKit replay during the observation window. data=\(loadGotoSplit() ?? [:])"
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxUITests/MenuKeyEquivalentRoutingUITests.swift` around lines 132 - 137,
The current test only samples once after RunLoop.current.run(until:
Date().addingTimeInterval(0.5)) which can miss a late second WebKit replay;
change the assertion to observe stability over a window by polling
loadGotoSplit() multiple times (e.g., every 0.05–0.1s for a total of ~0.5–1s)
and fail if any sample's ["browserPageTitle"] deviates from "cmde-1". Update the
block around RunLoop.current.run and XCTAssertEqual to loop, call
loadGotoSplit() repeatedly, and assert stability across all samples (reference:
RunLoop.current.run, loadGotoSplit(), XCTAssertEqual).

Comment thread Sources/AppDelegate.swift

@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

@austinywang
austinywang merged commit 867c93e into main Mar 30, 2026
14 checks passed
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
* Route browser Find shortcuts through web content first

* Keep cmux browser Find shortcuts authoritative

* Add browser Find inspector regression test

* Fix browser Find routing follow-ups

This branch was successfully deployed

1 active deployment
Preview — 29178440 Deployed Mar 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.

1 participant