Skip to content

Make shortcut routing focus tests deterministic - #6419

Merged
azooz2003-bit merged 11 commits into
mainfrom
task-shortcut-routing-focus
Jun 19, 2026
Merged

azooz2003-bit merged 11 commits into
mainfrom
task-shortcut-routing-focus

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jun 19, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Route shortcut-focused window lookups through a DEBUG-only focused-window override that tests can pin when AppKit does not make test windows key.
  • Remove the PR 6356 headless-CI skip/probe from AppDelegateShortcutRoutingTests and reset the new DEBUG routing state between tests.
  • Keep the tests job on the hosted GUI-capable pool while the separate MACOS_RUNNER_15/Austin testmanagerd runner-infra issue remains owned by the CI-flakiness workspace.

Testing

  • git diff --check
  • AWS M4 Pro focused AppDelegateShortcutRoutingTests subset, running after runner disk cleanup

Related


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Stabilizes shortcut routing with DEBUG focus/responder overrides and routes all shortcuts through new shortcutRouting* helpers. Restores deterministic focused-terminal key-repair coverage and fixes test responder attachments; tests stay on the hosted GUI runner.

  • Refactors

    • Add shortcutRoutingKeyWindow, shortcutRoutingActiveWindow, and shortcutRoutingFirstResponder and route shortcut commands through them.
    • Add DEBUG-only focused-window and key-repair first-responder overrides with scoped capture; simplify the NSWindow.makeKeyAndOrderFront swizzle seam.
    • Hook the overrides into event/window resolution and the repair path via debugFocusedTerminalKeyRepairObserverForTesting; expose seams for routing checks.
  • Tests

    • Remove CI focus probes/skips; deterministically pin focus/responder via the DEBUG API.
    • Add a repair probe to track repair counts, responder, and forwarded keyDowns; fix keyCode comparison and simulate stranded-responder drift.
    • Fix responder attachments in shortcut tests (e.g., omnibar), temporarily unbind conflicting shortcuts, tweak IME/mentions assertions, and call TextBoxInputTextView.flushPendingSessionDraftAttachmentCopies.

Written for commit 1159deb. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved keyboard shortcut routing (including command palette and escape-related actions) so commands consistently target the intended active window as focus changes.
    • Strengthened focused-terminal keyboard “repair” to prevent typing/shortcut delivery to the wrong responder/window, including more deterministic recovery scenarios.
  • Tests
    • Updated shortcut-routing and repair test coverage to be more deterministic and reduce CI flakiness.

@vercel

vercel Bot commented Jun 19, 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 Jun 19, 2026 4:05am
cmux-staging Building Building Preview, Comment Jun 19, 2026 4:05am

@coderabbitai

coderabbitai Bot commented Jun 19, 2026 •

Copy link
Copy Markdown

Review Change Stack

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

Introduces shortcutRoutingKeyWindow and shortcutRoutingActiveWindow computed properties in AppDelegate to centralize window resolution for shortcut handling, replacing direct NSApp.keyWindow/NSApp.mainWindow references throughout handleCustomShortcut and related helpers. Adds DEBUG-only override infrastructure, observer closures, and NSWindow.makeKeyAndOrderFront swizzles to enable deterministic focused-window capture in tests, then rewrites CI skip logic in the routing test suite to use the new deterministic seams. Updates a CI workflow comment to reflect the current testmanagerd runner-pinning rationale.

Changes

Shortcut Routing Window Centralization and Testing

Layer / File(s) Summary
Shortcut routing extension files: window selection and test infrastructure
Sources/AppDelegate+ShortcutRoutingWindow.swift, Sources/AppDelegate+ShortcutRoutingTesting.swift
Adds two new AppDelegate extension files. AppDelegate+ShortcutRoutingWindow.swift defines shortcutRoutingKeyWindow and shortcutRoutingActiveWindow computed properties (with DEBUG override support) and helpers for first-responder routing, window-to-context mapping, and TabManager resolution. AppDelegate+ShortcutRoutingTesting.swift provides DEBUG-only test infrastructure including the DebugShortcutRoutingFocusedWindowOverrideForTesting container, runtime swizzle of NSWindow.makeKeyAndOrderFront via cmux_makeKeyAndOrderFront, and API methods to manage focused-window capture sessions and reset test state.
AppDelegate keyboard repair hooks and test access widening
Sources/AppDelegate.swift
Adds DEBUG-only debugFocusedTerminalKeyRepairObserverForTesting callback property to receive keyboard-repair diagnostics during tests. Refactors repairFocusedTerminalKeyboardRoutingIfNeeded to accept an optional firstResponderOverride parameter for test injection and to invoke the DEBUG observer before applying repairs. Widens access of resolvedWindow(for:), isMainTerminalWindow(_:), and clearConfiguredShortcutChordState() for test visibility. Installs DEBUG swizzle in test-harness setup and updates shortcut-monitor dispatch to use shortcutRoutingActiveWindow.
Routing helpers and context resolution migrated to shortcut windows
Sources/AppDelegate.swift
Updates command-palette targeting, escape suppression, active palette visibility, shortcut event window resolution (with DEBUG window-number override), context synchronization, sidebar toggle and focus restoration, find-shortcut targeting, new-window source selection, workspace creation, main-context selection, and window-unregister context reassignment to prefer shortcutRoutingKeyWindow and shortcutRoutingActiveWindow over direct NSApp.keyWindow/NSApp.mainWindow fallbacks.
handleCustomShortcut flows migrated to shortcut-routing windows
Sources/AppDelegate.swift
Refactors the primary shortcut-handling method to use shortcut-routing windows for modal/sheet bypass checks, escape targeting, IME bypass, right-sidebar resolution, split execution, devtools preflight, and CMUX action execution. Updates stale menu shortcut suppression and browser find-shortcut bypass to use shortcut-routing window and first-responder selection.
Test probe and helper infrastructure
cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift, cmuxTests/AppDelegateShortcutRoutingTests.swift
Introduces installFocusedTerminalRepairProbeForTesting to wrap repair and keyDown-forward event observers, capturing repair counts and responder identity. Adds helpers to focus the hosted terminal and install simulated responder-drift scenarios (stranded, visible wrong-window, search-disagreeing). Includes repairProbeFirstResponderOwnsTextField predicate for responder identity checking and attachTestResponder(_:to:) helper to inject simulated responders via addSubview.
Test harness setup: CI skip removal and deterministic capture
cmuxTests/AppDelegateShortcutRoutingTests.swift
Eliminates CI headless-window-server flakiness skip mechanism. Updates setUpWithError() to install AppDelegate swizzles and begin DEBUG focused-window capture. Updates tearDown() to end focused-window capture. This replaces CI environment detection with deterministic DEBUG seams.
Responder repair test implementation
cmuxTests/AppDelegateShortcutRoutingTests.swift
Reworks keyboard-repair tests to use simulated stranded/wrong responders, captures repair behavior via DEBUG observer and probes, switches from fixed RunLoop timing to waitUntil for responder convergence, and asserts repairCount == 1 and responder identity in DEBUG. Covers typing repair with lost first-responder, visible same-window responder drift, and focused-terminal search field repair.
Shortcut routing assertions and test behavior updates
cmuxTests/AppDelegateShortcutRoutingTests.swift
Updates workspace activation assertion to check preferredFocusIntentForActivation(), removes unused guard bindings, switches omnibar responder attachment to helper-based insertion, nests temporary shortcuts to isolate Cmd+* conflicts, updates mention-completion and IME Return tests, adds attachment-upload flush call, enhances right-sidebar Cmd+F tests with non-file-mode variant and temporary shortcut unbinding, updates withTemporaryShortcut teardown reset behavior.

CI Infrastructure and Project Configuration

Layer / File(s) Summary
Xcode project: add extension and test-probe files to build
cmux.xcodeproj/project.pbxproj
Adds AppDelegate+ShortcutRoutingTesting.swift, AppDelegate+ShortcutRoutingWindow.swift, and AppDelegateShortcutRoutingRepairProbe.swift to the Xcode project by creating PBXBuildFile, PBXFileReference entries, updating group children, and adding to cmux and cmuxTests target PBXSourcesBuildPhase.
CI workflow: update macOS runner pinning rationale
.github/workflows/ci.yml
Replaces the dated "TEMPORARY (2026-06-18)" comment with explanation that app-host XCTest requires hosted GUI-capable runners to broker testmanagerd control sessions.

Sequence Diagram(s)

sequenceDiagram
  participant Test as XCTest<br/>(AppDelegateShortcutRoutingTests)
  participant Swizzle as NSWindow<br/>cmux_makeKeyAndOrderFront
  participant Override as DebugShortcutRoutingFocusedWindowOverrideForTesting
  participant Router as AppDelegate<br/>shortcutRoutingKeyWindow
  participant Handler as handleCustomShortcut
  participant Repair as repairFocusedTerminalKeyboardRoutingIfNeeded

  Test->>Test: beginFocusedWindowCapture()
  Test->>Test: attachTestResponder() injects wrong responder
  Test->>Handler: Send keyboard event (typing in terminal)
  Handler->>Router: Resolve shortcut routing window
  Router->>Override: Check DEBUG focused-window override
  Override-->>Router: Return override if in capture session
  Router-->>Handler: Return key window for routing
  Handler->>Repair: Detect first-responder mismatch
  Repair->>Repair: Use optional firstResponderOverride in DEBUG
  Repair->>Test: Invoke debugFocusedTerminalKeyRepairObserverForTesting callback
  Test->>Test: Probe captures repairCount and responder
  Test->>Test: waitUntil() confirms first responder restored
  Test->>Test: Assert repairCount == 1 and responder matches simulation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • manaflow-ai/cmux#4442: Introduced debugResetShortcutRoutingStateForTesting and withTemporaryShortcut DEBUG helpers that this PR extends with the new clearFocusedWindowOverride: false parameter.
  • manaflow-ai/cmux#4615: Related changes to AppDelegate shortcut-routing window resolution and target selection logic for routing close shortcuts.
  • manaflow-ai/cmux#5296: Related modifications to focused-terminal keyboard routing repair logic in AppDelegate using the same repairFocusedTerminalKeyboardRoutingIfNeeded method that this PR extends with firstResponderOverride support.

Suggested reviewers

  • Ari4ka

Poem

🐇 Hops through windows, routing true,
No more keyWindow's chaotic view!
shortcutRoutingKeyWindow lights the way,
While DEBUG swizzles chase the flak away.
Repair probes count each fix with care—
Deterministic tests, answered everywhere! 🪟✨


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error Sources/AppDelegate+ShortcutRoutingTesting.swift introduces a shared mutable reference type (DebugShortcutRoutingFocusedWindowOverrideForTesting class) at file scope without actor isolation attribu... Add @MainActor attribute to either the class definition or the global variable to make actor isolation explicit: @MainActor let debugShortcutRoutingFocusedWindowOverrideForTesting = ...
Docstring Coverage ⚠️ Warning Docstring coverage is 3.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and directly describes the main objective of the PR: making shortcut routing focus tests deterministic. It is specific, concise, and accurately reflects the core change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Blocking Runtime ✅ Passed No blocking/timing primitives (Task.sleep, DispatchSemaphore, DispatchQueue.main.sync, etc.) introduced in production code. Test-only RunLoop.main.run usage is deterministic scaffolding explicitly...
Cmux Expensive Synchronous Load ✅ Passed The PR adds shortcut routing helpers (shortcutRoutingKeyWindow, shortcutRoutingActiveWindow, activeTabManagerForCommands, etc.) that perform only lightweight operations: NSWindow/NSResponder proper...
Cmux Cache Substitution Correctness ✅ Passed The PR adds DEBUG-only shortcut routing helpers (shortcutRoutingKeyWindow, shortcutRoutingActiveWindow, shortcutRoutingFirstResponder) wrapped in #if DEBUG for test focus management. These...
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift code, test code, and GitHub Actions YAML changes. Custom check scope (TypeScript, JavaScript, shell, build/runtime scripts) is not applicable; Swift is explicitly covered by...
Cmux Algorithmic Complexity ✅ Passed The PR adds O(n) scans on mainWindowContexts bounded by explicit maxWindowsPerSnapshot=12, which is a tiny fixed-size collection per the rule's pass criteria.
Cmux Swift Concurrency ✅ Passed No problematic legacy async patterns introduced. New code uses synchronous helpers and DEBUG-only testing infrastructure; test-only RunLoop usage for UI determinism is explicitly permitted by rules.
Cmux Swift @Concurrent ✅ Passed All new Swift code is synchronous and @MainActor-isolated; no async functions, no @concurrent annotations, no CPU/network-heavy work without actor hops.
Cmux Swift File And Package Boundaries ✅ Passed All new production files (80 and 73 lines) are under 400-line threshold with clear single responsibilities. AppDelegate.swift gained only 125 lines (net -6), below the 250-line trigger for existing...
Cmux Swiftpm Lockfiles ✅ Passed PR contains no SwiftPM package-reference or dependency changes; Xcode project updates only add source files to build phases, not external package references.
Cmux Swift Logging ✅ Passed No logging violations detected in production Swift code. New files properly guard DEBUG-only code with #if DEBUG. No print/NSLog/debugPrint/dump calls added to app/runtime code, no sensitive data e...
Cmux User-Facing Error Privacy ✅ Passed No user-facing error messages or privacy violations found. All new code is DEBUG-only, test-only, or in developer-facing CI configuration comments (allowed per rules).
Cmux Full Internationalization ✅ Passed No user-facing strings were added in violation of localization rules. New source files are debug-only or test code (allowed). All existing user-facing strings in AppDelegate.swift use String(locali...
Cmux Swiftui State Layout ✅ Passed PR introduces only AppKit test infrastructure and window routing helpers. No SwiftUI state violations: no @Published/@Observable/@StateObject declarations, GeometryReader, or lazy container store r...
Cmux Architecture Rethink ✅ Passed PR introduces DEBUG-only focused-window override and test-only RunLoop synchronization, centralizes shortcut routing (76 calls), creates no production state/observer duplication, and falls back to...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not introduce or materially change any standalone cmux-owned windows. All changes involve routing helpers for existing windows and DEBUG-only test fixtures with no window creation.
Cmux Source Artifacts ✅ Passed All 7 changed files are hand-written source code, tests, configs, or fixtures with no prohibited artifacts (caches, DerivedData, build output, temp folders, or generated logs).
Description check ✅ Passed The pull request description includes all required template sections: a comprehensive Summary explaining the changes and rationale, a Testing section documenting how the changes were validated, and a complete Checklist with all items marked.
✨ 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 task-shortcut-routing-focus

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 Jun 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a DEBUG-only focused-window override seam so shortcut-routing tests can pin the target window deterministically without relying on the OS window server to honor makeKeyAndOrderFront. It replaces every NSApp.keyWindow reference in shortcut-routing paths with a new shortcutRoutingKeyWindow / shortcutRoutingActiveWindow indirection, removes the 50+ test skip entries introduced in PR 6356, and adds a repair-probe fixture for more granular assertion of the key-repair path.

  • AppDelegate+ShortcutRoutingWindow.swift – new file centralising shortcutRoutingKeyWindow, shortcutRoutingActiveWindow, shortcutRoutingFirstResponder, and the DEBUG bridge for repairFocusedTerminalKeyboardRoutingIfNeeded that injects keyRepairFirstResponder overrides in DEBUG builds only.
  • AppDelegate+ShortcutRoutingTesting.swift – new DEBUG-only file with the DebugShortcutRoutingFocusedWindowOverrideForTesting singleton, the cmux_makeKeyAndOrderFront swizzle that captures the key window in test scope, and Begin/End depth-counting helpers used in setUpWithError/tearDown.
  • AppDelegateShortcutRoutingRepairProbe.swift – new test helper that extracts and adds assertion fixtures (repair count, repair responder, forwarded key-down count) for the two stranded/drift repair tests.

Confidence Score: 4/5

Safe to merge with awareness that two production shortcut-routing behavior changes bundled with the test-determinism work need a second look before shipping.

The DEBUG infrastructure itself is clean and well-contained — all override paths are #if DEBUG-guarded and have no effect on release builds. The mechanical substitution of NSApp.keyWindow with shortcutRoutingKeyWindow across 16 call sites is consistent and correct. Two production changes bundled in the same commit warrant extra attention: the reordering of findShortcutTarget before focusForInWindowCommand silently drops the focus side-effect when no find target is available, and the makeKeyAndOrderFront swizzle that captures any NSWindow subclass during test scope can overwrite the intended override if an auxiliary window becomes key mid-test. Both were noted in prior review threads and neither has been addressed in this revision.

Sources/AppDelegate.swift — the find-shortcut reordering and Sources/AppDelegate+ShortcutRoutingTesting.swift — the broad NSWindow swizzle.

Important Files Changed

Filename Overview
Sources/AppDelegate+ShortcutRoutingTesting.swift New DEBUG-only file: singleton override, Begin/End depth counter, and makeKeyAndOrderFront swizzle. The swizzle fires for ALL NSWindow subclasses during capture mode, so any auxiliary window calling makeKeyAndOrderFront after a test pins its window will overwrite the override.
Sources/AppDelegate+ShortcutRoutingWindow.swift New file centralising shortcutRoutingKeyWindow, shortcutRoutingActiveWindow, shortcutRoutingFirstResponder, and the 2-arg repair bridge. shortcutRoutingKeyWindow has a side-effectful mutation (clears override on validation failure) and is called multiple times in some flows.
Sources/AppDelegate.swift 16 NSApp.keyWindow call-sites replaced with shortcutRoutingKeyWindow/shortcutRoutingActiveWindow; repairFocusedTerminalKeyboardRoutingIfNeeded gains a firstResponderOverride param; resolvedWindow(for:) and clearConfiguredShortcutChordState widened from private to internal.
cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift New test-only helper file; clean chained-observer pattern correctly saves/restores both repair and Ghostty key-event observers.
cmuxTests/AppDelegateShortcutRoutingTests.swift Removes the 50+ entry focus-routing CI skip list; setUp/tearDown now use Begin/End capture lifecycle; repair tests use deterministic keyRepairFirstResponder override and waitUntil instead of fixed RunLoop sleeps.
.github/workflows/ci.yml Trims the TEMPORARY comment block on the hosted-runner pin; no functional CI behaviour change.
.github/swift-file-length-budget.tsv Line budgets reduced to reflect actual file shrinkage; correct maintenance of the CI line-length guard.
cmux.xcodeproj/project.pbxproj Adds the three new Swift files to the appropriate app and test targets; routine Xcode project bookkeeping.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Test as XCTest body
    participant Setup as setUpWithError
    participant Delegate as AppDelegate
    participant Override as DebugOverride singleton
    participant Swizzle as cmux_makeKeyAndOrderFront

    Setup->>Override: focusedWindowCaptureDepth plus 1
    Setup->>Delegate: debugResetShortcutRoutingStateForTesting
    Test->>Swizzle: window.makeKeyAndOrderFront fires
    Swizzle->>Override: shouldCaptureFocusedWindow true
    Swizzle->>Delegate: debugSetShortcutRoutingFocusedWindowForTesting
    Test->>Delegate: shortcutRoutingKeyWindow read
    Delegate->>Override: capture mode active, return override window
    Delegate-->>Test: testWindow
    Test->>Delegate: window.sendEvent keyDown
    Delegate->>Delegate: repairFocusedTerminalKeyboardRoutingIfNeeded 2-arg
    Delegate->>Override: read keyRepairFirstResponder override
    Delegate->>Delegate: repairFocusedTerminalKeyboardRoutingIfNeeded 3-arg
    Test->>Setup: tearDown calls debugEndShortcutRoutingFocusedWindowCaptureForTesting
    Setup->>Override: depth clamped to 0
    Setup->>Delegate: debugResetShortcutRoutingStateForTesting clears all state
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Test as XCTest body
    participant Setup as setUpWithError
    participant Delegate as AppDelegate
    participant Override as DebugOverride singleton
    participant Swizzle as cmux_makeKeyAndOrderFront

    Setup->>Override: focusedWindowCaptureDepth plus 1
    Setup->>Delegate: debugResetShortcutRoutingStateForTesting
    Test->>Swizzle: window.makeKeyAndOrderFront fires
    Swizzle->>Override: shouldCaptureFocusedWindow true
    Swizzle->>Delegate: debugSetShortcutRoutingFocusedWindowForTesting
    Test->>Delegate: shortcutRoutingKeyWindow read
    Delegate->>Override: capture mode active, return override window
    Delegate-->>Test: testWindow
    Test->>Delegate: window.sendEvent keyDown
    Delegate->>Delegate: repairFocusedTerminalKeyboardRoutingIfNeeded 2-arg
    Delegate->>Override: read keyRepairFirstResponder override
    Delegate->>Delegate: repairFocusedTerminalKeyboardRoutingIfNeeded 3-arg
    Test->>Setup: tearDown calls debugEndShortcutRoutingFocusedWindowCaptureForTesting
    Setup->>Override: depth clamped to 0
    Setup->>Delegate: debugResetShortcutRoutingStateForTesting clears all state
Loading

Reviews (4): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread Sources/AppDelegate.swift
Comment on lines 6989 to 7018
"fr=\(beforeResponder)"
)
#endif
let target = context.keyboardFocusCoordinator.findShortcutTarget(
currentResponder: window?.firstResponder
)
guard target != .none else {
#if DEBUG
dlog(
"find.shortcut.app.end target=\(target) result=0 " +
"targetWin={\(debugWindowToken(window))} fr=\(beforeResponder)"
)
#endif
return false
}

if let window {
mainWindowVisibilityController.focusForInWindowCommand(window, reason: .findShortcut)
}

let target = context.keyboardFocusCoordinator.findShortcutTarget(
currentResponder: window?.firstResponder
)
let result: Bool
switch target {
case .rightSidebarFileSearch:
result = context.keyboardFocusCoordinator.focusFileSearch()
case .mainPanelFind:
result = context.tabManager.startSearch()
case .none:
result = false
return false
}
#if DEBUG

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 focusForInWindowCommand silently dropped when find target is .none

The reordering moves findShortcutTarget before focusForInWindowCommand, then guards early on .none. This means pressing the Find shortcut in a window state where no target is available no longer calls focusForInWindowCommand(window, reason: .findShortcut). Previously the window was always brought forward regardless of whether find had a target. Whether that was intentional or accidental, this is an unannounced production behavior change bundled into a test-determinism PR — if any caller relied on the focus side-effect even when find returns false, those paths silently regress here.

Comment thread Sources/AppDelegate.swift Outdated
Comment on lines +16530 to +16536
}

private extension NSWindow {
#if DEBUG
@objc func cmux_makeKeyAndOrderFront(_ sender: Any?) {
cmux_makeKeyAndOrderFront(sender)
AppDelegate.shared?.debugSetShortcutRoutingFocusedWindowForTesting(self)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Broad swizzle overwrites focus override on every makeKeyAndOrderFront call

cmux_makeKeyAndOrderFront fires for ALL NSWindow subclasses in DEBUG builds. If any auxiliary window (settings panel, alert sheet, a secondary test window's makeKeyAndOrderFront from setup helpers, etc.) calls makeKeyAndOrderFront after a test pins its target window, the override is silently replaced with that auxiliary window. The validation in shortcutRoutingKeyWindow then clears the stale non-cmux override, but the test's intended window has already been erased. Tests that open two windows or share setup helpers that call makeKeyAndOrderFront after the assertion window will see .none routing rather than the pinned override, producing exactly the kind of non-deterministic failures this PR is meant to fix.

@azooz2003-bit
azooz2003-bit force-pushed the task-shortcut-routing-focus branch from d2742fe to c1d1885 Compare June 19, 2026 02:47

@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

Caution

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

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

12827-12838: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve the shortcut window before inspecting first responder state.

These checks run before synchronizeShortcutRoutingContext(event:), but they read shortcutRoutingKeyWindow?.firstResponder. For key events that identify the target by windowNumber while event.window is nil, this can inspect another focused window, clearing browserAddressBarFocusedPanelId or bypassing IME handling for the wrong shortcut context. Use the event-resolved window once and reuse it for these first-responder checks.

🐛 Proposed fix
+        let shortcutWindowForFirstResponderChecks = resolvedShortcutEventWindow(event) ?? shortcutRoutingActiveWindow
         if browserAddressBarFocusedPanelId != nil,
-           cmuxOwningGhosttyView(for: shortcutRoutingKeyWindow?.firstResponder) != nil {
+           cmuxOwningGhosttyView(for: shortcutWindowForFirstResponderChecks?.firstResponder) != nil {
 `#if` DEBUG
             let stalePanelToken = browserAddressBarFocusedPanelId.map { String($0.uuidString.prefix(5)) } ?? "nil"
-            let firstResponderType = shortcutRoutingKeyWindow?.firstResponder.map { String(describing: type(of: $0)) } ?? "nil"
+            let firstResponderType = shortcutWindowForFirstResponderChecks?.firstResponder.map { String(describing: type(of: $0)) } ?? "nil"
             cmuxDebugLog(
                 "browser.focus.addressBar.staleClear panel=\(stalePanelToken) " +
                 "reason=terminal_first_responder fr=\(firstResponderType)"
@@
         if !normalizedFlags.contains(.command),
-           let ghosttyView = cmuxOwningGhosttyView(for: shortcutRoutingKeyWindow?.firstResponder),
+           let ghosttyView = cmuxOwningGhosttyView(for: shortcutWindowForFirstResponderChecks?.firstResponder),
            ghosttyView.hasMarkedText() {
             return false
         }
 
-        let shortcutWindowForMarkedText = resolvedShortcutEventWindow(event) ?? event.window ?? shortcutRoutingActiveWindow
+        let shortcutWindowForMarkedText = shortcutWindowForFirstResponderChecks

Also applies to: 12903-12909

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/AppDelegate.swift` around lines 12827 - 12838, The code inspects
shortcutRoutingKeyWindow?.firstResponder before the window context has been
properly resolved, which can cause it to check the wrong window when
event.window is nil and the target is identified by windowNumber. This leads to
incorrectly clearing browserAddressBarFocusedPanelId or bypassing IME handling
for the wrong context. Resolve the shortcut window from the event first (before
these checks run), then reuse that resolved window reference when inspecting
firstResponder state instead of directly accessing
shortcutRoutingKeyWindow?.firstResponder. Apply this fix to both occurrences
mentioned in the comment (the one in the diff and the one at lines 12903-12909).
cmuxTests/AppDelegateShortcutRoutingTests.swift (1)

3991-4003: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Restore the contentView binding before using it.

Line 4003 references contentView, but the guard no longer binds it, so this file will not compile. Add let contentView = window.contentView back to the guard.

🐛 Proposed fix
-        guard let window = window(withId: windowId),
+        guard let window = window(withId: windowId),
+              let contentView = window.contentView,
               let manager = appDelegate.tabManagerFor(windowId: windowId),
               let workspace = manager.selectedWorkspace,
               let browserPanelId = manager.openBrowser(inWorkspace: workspace.id) else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/AppDelegateShortcutRoutingTests.swift` around lines 3991 - 4003,
The contentView property is being accessed when calling addSubview on line 4003,
but it is not bound in the guard statement. Add contentView as an additional
binding in the guard statement by including let contentView = window.contentView
alongside the existing bindings for window, manager, workspace, and
browserPanelId. This will ensure contentView is properly initialized before it
is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/AppDelegate.swift`:
- Around line 13520-13523: The performSplitShortcut call is using a fallback to
shortcutRoutingActiveWindow when event.window is nil, which can cause the wrong
workspace to be mutated if the routing context had a specific window resolved.
Replace the preferredWindow parameter value from the pattern event.window ??
shortcutRoutingActiveWindow with resolvedShortcutEventWindow(event) to properly
preserve the resolved window through the split action. This same fix should be
applied to all other similar shortcut call sites mentioned in the comment (lines
13534-13537, 14450, 14919-14924, 15096-15112) and within configured-action
execution, use resolvedWindow(for: context) instead.

---

Outside diff comments:
In `@cmuxTests/AppDelegateShortcutRoutingTests.swift`:
- Around line 3991-4003: The contentView property is being accessed when calling
addSubview on line 4003, but it is not bound in the guard statement. Add
contentView as an additional binding in the guard statement by including let
contentView = window.contentView alongside the existing bindings for window,
manager, workspace, and browserPanelId. This will ensure contentView is properly
initialized before it is used.

In `@Sources/AppDelegate.swift`:
- Around line 12827-12838: The code inspects
shortcutRoutingKeyWindow?.firstResponder before the window context has been
properly resolved, which can cause it to check the wrong window when
event.window is nil and the target is identified by windowNumber. This leads to
incorrectly clearing browserAddressBarFocusedPanelId or bypassing IME handling
for the wrong context. Resolve the shortcut window from the event first (before
these checks run), then reuse that resolved window reference when inspecting
firstResponder state instead of directly accessing
shortcutRoutingKeyWindow?.firstResponder. Apply this fix to both occurrences
mentioned in the comment (the one in the diff and the one at lines 12903-12909).
🪄 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: 045207ae-7925-4306-8f3b-be3219f26d9c

📥 Commits

Reviewing files that changed from the base of the PR and between c44bf76 and d2742fe.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • Sources/AppDelegate.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift

Comment thread Sources/AppDelegate.swift

@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

Caution

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

⚠️ Outside diff range comments (1)
cmuxTests/AppDelegateShortcutRoutingTests.swift (1)

6862-6925: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Make the simulated drift responder own focus before dispatch.

Both same-window drift tests inject strayView into the DEBUG repair seam, but the actual window.firstResponder is still the terminal/search responder when window.sendEvent runs. That means the final focus/string assertions can pass even if the repair path stops restoring AppKit focus. Set strayView as the real first responder before sending the key event.

Proposed test tightening
         XCTAssertFalse(
             terminalPanel.hostedView.responderMatchesPreferredKeyboardFocus(strayView),
             "Expected the simulated responder to disagree with the focused terminal"
         )
+        XCTAssertTrue(window.makeFirstResponder(strayView), "Expected simulated responder drift before typing")
+        XCTAssertTrue(window.firstResponder === strayView)
         XCTAssertFalse(
             terminalPanel.hostedView.responderMatchesPreferredKeyboardFocus(strayView),
             "Expected the simulated responder to disagree with terminal search focus"
         )
+        XCTAssertTrue(window.makeFirstResponder(strayView), "Expected simulated responder drift before typing")
+        XCTAssertTrue(window.firstResponder === strayView)

As per coding guidelines, tests must assert on causality rather than only observing that a path was reached.

Also applies to: 11037-11088

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/AppDelegateShortcutRoutingTests.swift` around lines 6862 - 6925,
The test injects strayView into the DEBUG repair seam via
debugSetShortcutRoutingKeyRepairFirstResponderForTesting but does not actually
set it as the window's real first responder before calling window.sendEvent.
This means the test can pass even if the repair mechanism fails to restore
focus, since the actual first responder is still the terminal. Before
dispatching the key event with window.sendEvent(keyDown), explicitly set
strayView as the window's first responder by calling
window.makeFirstResponder(strayView) so the test validates that the repair path
correctly restores focus from the wrong responder back to the terminal.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/AppDelegate`+ShortcutRoutingWindow.swift:
- Around line 33-50: The method activeTabManagerForCommands directly iterates
over mainWindowContexts.values while calling resolvedWindow(for:), which can
cause mutation-during-enumeration issues since resolvedWindow(for:) may mutate
mainWindowContexts during iteration. To fix this, snapshot the values before
iterating by wrapping mainWindowContexts.values with Array() in the same way it
is correctly done at line 70, ensuring the dictionary values are captured as an
immutable array before the first call to resolvedWindow(for:).

---

Outside diff comments:
In `@cmuxTests/AppDelegateShortcutRoutingTests.swift`:
- Around line 6862-6925: The test injects strayView into the DEBUG repair seam
via debugSetShortcutRoutingKeyRepairFirstResponderForTesting but does not
actually set it as the window's real first responder before calling
window.sendEvent. This means the test can pass even if the repair mechanism
fails to restore focus, since the actual first responder is still the terminal.
Before dispatching the key event with window.sendEvent(keyDown), explicitly set
strayView as the window's first responder by calling
window.makeFirstResponder(strayView) so the test validates that the repair path
correctly restores focus from the wrong responder back to the terminal.
🪄 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: 627e7ee9-b8cb-4636-a204-8eb0260586e7

📥 Commits

Reviewing files that changed from the base of the PR and between d2742fe and a452990.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • Sources/AppDelegate+ShortcutRoutingTesting.swift
  • Sources/AppDelegate+ShortcutRoutingWindow.swift
  • Sources/AppDelegate.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/AppDelegateShortcutRoutingTests.swift

Comment on lines +33 to +50
func activeTabManagerForCommands(preferredWindow: NSWindow? = nil) -> TabManager? {
if let context = contextForMainWindow(preferredWindow) {
return context.tabManager
}
if let context = contextForMainWindow(shortcutRoutingKeyWindow) {
return context.tabManager
}
if let context = contextForMainWindow(NSApp.mainWindow) {
return context.tabManager
}
if let activeManager = tabManager,
let activeContext = liveMainWindowContext(for: activeManager) {
return activeContext.tabManager
}
return mainWindowContexts.values.first { context in
resolvedWindow(for: context) != nil
}?.tabManager
}

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 | 🟠 Major | ⚡ Quick win

Snapshot mainWindowContexts.values before iterating with resolvedWindow(for:).

Line 70 correctly uses Array(mainWindowContexts.values) before calling resolvedWindow(for:), but the fallback at lines 47-49 iterates the dictionary directly. Since resolvedWindow(for:) may reindex/mutate mainWindowContexts, this can cause mutation-during-enumeration issues.

🔧 Suggested fix
-        return mainWindowContexts.values.first { context in
+        return Array(mainWindowContexts.values).first { context in
             resolvedWindow(for: context) != nil
         }?.tabManager

Based on learnings: "avoid iterating mainWindowContexts.values directly while calling resolvedWindow(for:). Since resolvedWindow(for:) may reindex/mutate mainWindowContexts, this can cause mutation-during-enumeration issues. Instead, snapshot first with Array(mainWindowContexts.values)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/AppDelegate`+ShortcutRoutingWindow.swift around lines 33 - 50, The
method activeTabManagerForCommands directly iterates over
mainWindowContexts.values while calling resolvedWindow(for:), which can cause
mutation-during-enumeration issues since resolvedWindow(for:) may mutate
mainWindowContexts during iteration. To fix this, snapshot the values before
iterating by wrapping mainWindowContexts.values with Array() in the same way it
is correctly done at line 70, ensuring the dictionary values are captured as an
immutable array before the first call to resolvedWindow(for:).

Source: Learnings

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift`:
- Around line 43-46: In the restore closure where
appDelegate.debugFocusedTerminalKeyRepairObserverForTesting and
GhosttyNSView.debugGhosttySurfaceKeyEventObserver are being reset, add a call to
addTeardownBlock with the same cleanup logic to automatically register the
cleanup with XCTest's teardown mechanism. This ensures cleanup happens even if a
caller forgets to explicitly call the returned restore closure in a defer
statement, preventing observer leaks between tests.
- Around line 12-37: The function installFocusedTerminalRepairProbeForTesting
accesses the actor-isolated property
GhosttyNSView.debugGhosttySurfaceKeyEventObserver without proper actor
isolation, which violates Swift concurrency rules. Add the `@MainActor` attribute
to the function signature of installFocusedTerminalRepairProbeForTesting to
match the MainActor isolation of the debugGhosttySurfaceKeyEventObserver
property.
🪄 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: 96c98740-c75d-48aa-af85-cc58fb08ad7d

📥 Commits

Reviewing files that changed from the base of the PR and between a452990 and 46cd45b.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (4)
  • Sources/AppDelegate+ShortcutRoutingTesting.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift

Comment on lines +12 to +37
func installFocusedTerminalRepairProbeForTesting(
appDelegate: AppDelegate,
keyCode: UInt32
) -> (
repairCount: () -> Int,
repairResponder: () -> NSResponder?,
forwardedKeyDownCount: () -> Int,
restore: () -> Void
) {
var repairCount = 0
var repairResponder: NSResponder?
let previousRepairObserver = appDelegate.debugFocusedTerminalKeyRepairObserverForTesting
appDelegate.debugFocusedTerminalKeyRepairObserverForTesting = { window, event, responder in
previousRepairObserver?(window, event, responder)
guard UInt32(event.keyCode) == keyCode else { return }
repairCount += 1
repairResponder = responder
}

var forwardedKeyDownCount = 0
let previousKeyEventObserver = GhosttyNSView.debugGhosttySurfaceKeyEventObserver
GhosttyNSView.debugGhosttySurfaceKeyEventObserver = { keyEvent in
previousKeyEventObserver?(keyEvent)
guard keyEvent.action == GHOSTTY_ACTION_PRESS, keyEvent.keycode == keyCode else { return }
forwardedKeyDownCount += 1
}

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the test file and examine its context
fd -t f "AppDelegateShortcutRoutingTests.swift" --type f

Repository: manaflow-ai/cmux

Length of output: 108


🏁 Script executed:

# Find GhosttyNSView to see the declaration of debugGhosttySurfaceKeyEventObserver
fd -type f "GhosttyNSView.swift" -o "GhosttyNSView.mm" -o "*Ghostty*View*"

Repository: manaflow-ai/cmux

Length of output: 231


🏁 Script executed:

# Search for the debugGhosttySurfaceKeyEventObserver declaration
rg "debugGhosttySurfaceKeyEventObserver" -A 2 -B 2 --type swift

Repository: manaflow-ai/cmux

Length of output: 20911


🏁 Script executed:

# Find and examine AppDelegateShortcutRoutingTests class
rg -t swift "class AppDelegateShortcutRoutingTests" -A 20

Repository: manaflow-ai/cmux

Length of output: 2169


🏁 Script executed:

# Check if the test file exists and examine the helper function
cat -n cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift 2>/dev/null | head -50

Repository: manaflow-ai/cmux

Length of output: 2176


🏁 Script executed:

# Search for GhosttyNSView.swift in the Sources directory
find . -name "GhosttyNSView.swift" -o -name "*GhosttyNSView*" 2>/dev/null | grep -v ".build" | head -10

Repository: manaflow-ai/cmux

Length of output: 157


🏁 Script executed:

# Broader search for debugGhosttySurfaceKeyEventObserver
rg "debugGhosttySurfaceKeyEventObserver" -B 5 -A 3

Repository: manaflow-ai/cmux

Length of output: 32306


🏁 Script executed:

# Let's search for all files mentioning this observer
rg -l "debugGhosttySurfaceKeyEventObserver" --type swift

Repository: manaflow-ai/cmux

Length of output: 374


🏁 Script executed:

# Check for files in the Packages directory since GhosttyKit might be there
find . -path "*Packages*" -name "*.swift" -type f | xargs grep -l "debugGhosttySurfaceKeyEventObserver" 2>/dev/null | head -5

Repository: manaflow-ai/cmux

Length of output: 42


🏁 Script executed:

# Look for the GhosttyTerminalView or related Swift files that might contain GhosttyNSView
fd "GhosttyTerminalView" -t f --type swift

Repository: manaflow-ai/cmux

Length of output: 233


🏁 Script executed:

# Search the entire codebase for `@MainActor` declarations near this observer
rg "`@MainActor`" -B 2 -A 2 | grep -A 5 -B 5 "debugGhosttySurfaceKeyEventObserver"

Repository: manaflow-ai/cmux

Length of output: 824


🏁 Script executed:

# Let's check what files are in cmuxTests
ls -la cmuxTests/*.swift | head -20

Repository: manaflow-ai/cmux

Length of output: 2026


🏁 Script executed:

# Check if AppDelegateShortcutRoutingRepairProbe.swift exists
cat -n cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift 2>&1

Repository: manaflow-ai/cmux

Length of output: 2176


🏁 Script executed:

# Search for AppDelegateShortcutRoutingRepairProbe in all Swift files
rg "AppDelegateShortcutRoutingRepairProbe" -t swift

Repository: manaflow-ai/cmux

Length of output: 42


🏁 Script executed:

# Check if the file exists at all
find . -name "*AppDelegateShortcutRouting*" -type f 2>/dev/null

Repository: manaflow-ai/cmux

Length of output: 166


🏁 Script executed:

# Search for installFocusedTerminalRepairProbeForTesting
rg "installFocusedTerminalRepairProbeForTesting" -B 5 -A 30

Repository: manaflow-ai/cmux

Length of output: 9984


🏁 Script executed:

# Search for GhosttyNSView anywhere in the codebase
rg "GhosttyNSView" -t swift | head -20

Repository: manaflow-ai/cmux

Length of output: 2096


🏁 Script executed:

# Let's search for the pattern more broadly
rg "debugFocusedTerminalKeyRepairObserverForTesting" -B 5 -A 10

Repository: manaflow-ai/cmux

Length of output: 9204


🏁 Script executed:

# Check what test files exist
find cmuxTests -name "*.swift" -type f | sort

Repository: manaflow-ai/cmux

Length of output: 11427


🏁 Script executed:

# Check if there are recent files or this is a new PR file
git ls-files cmuxTests/ | grep -i "repair\|probe"

Repository: manaflow-ai/cmux

Length of output: 114


🏁 Script executed:

# Check git status for new files
git status --short | head -20

Repository: manaflow-ai/cmux

Length of output: 89


🏁 Script executed:

# Search for mentions of GhosttyNSView with `@MainActor`
rg "`@MainActor`.*GhosttyNSView|class GhosttyNSView" -A 10

Repository: manaflow-ai/cmux

Length of output: 1815


🏁 Script executed:

# Look for any observer pattern definitions
rg "debugGhosttySurfaceKeyEventObserver\s*=" -B 10 -A 5

Repository: manaflow-ai/cmux

Length of output: 47542


🏁 Script executed:

# Check if there are any untracked or staged files matching the pattern
git status --short | grep -i "appdel.*shortcut.*repair"

Repository: manaflow-ai/cmux

Length of output: 42


🏁 Script executed:

# Look for any file with similar naming that might exist
fd -type f ".*Shortcut.*" -path "*/cmuxTests/*"

Repository: manaflow-ai/cmux

Length of output: 231


🏁 Script executed:

# Check if this is in a staged or unstaged change
git diff --name-only 2>/dev/null | grep -i repair

Repository: manaflow-ai/cmux

Length of output: 42


🏁 Script executed:

# Look for AppDelegate related test files
fd -type f "AppDelegate.*Test" -path "*/cmuxTests/*"

Repository: manaflow-ai/cmux

Length of output: 231


🏁 Script executed:

# Check if the code snippet references are pointing to an existing test class
rg "AppDelegateShortcutRoutingTests" -t swift -A 5

Repository: manaflow-ai/cmux

Length of output: 1340


🏁 Script executed:

# Look for other test helper patterns to understand the established pattern
rg "extension.*Tests.*{" -A 10 --type swift | grep -A 10 "func.*Probe\|func.*Observer\|func.*Testing" | head -40

Repository: manaflow-ai/cmux

Length of output: 162


🏁 Script executed:

# Search for existing `@MainActor` uses in test files
rg "`@MainActor`" --type swift -B 2 -A 2 | grep -B 5 -A 5 "cmuxTests"

Repository: manaflow-ai/cmux

Length of output: 50373


🏁 Script executed:

# Look for manual observer cleanup patterns in existing tests
rg "previousObserver\|previousKeyEvent" -A 5 -B 5 --type swift

Repository: manaflow-ai/cmux

Length of output: 42


Mark this helper @MainActor to match actor-isolated observer APIs.

This function reads and writes GhosttyNSView.debugGhosttySurfaceKeyEventObserver (declared @MainActor static var in GhosttyTerminalView.swift) from a nonisolated context. Swift concurrency rules require an explicit @MainActor annotation on the function to match the isolation of the property it accesses.

Suggested fix
 extension AppDelegateShortcutRoutingTests {
+    `@MainActor`
     func installFocusedTerminalRepairProbeForTesting(
         appDelegate: AppDelegate,
         keyCode: UInt32
     ) -> (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func installFocusedTerminalRepairProbeForTesting(
appDelegate: AppDelegate,
keyCode: UInt32
) -> (
repairCount: () -> Int,
repairResponder: () -> NSResponder?,
forwardedKeyDownCount: () -> Int,
restore: () -> Void
) {
var repairCount = 0
var repairResponder: NSResponder?
let previousRepairObserver = appDelegate.debugFocusedTerminalKeyRepairObserverForTesting
appDelegate.debugFocusedTerminalKeyRepairObserverForTesting = { window, event, responder in
previousRepairObserver?(window, event, responder)
guard UInt32(event.keyCode) == keyCode else { return }
repairCount += 1
repairResponder = responder
}
var forwardedKeyDownCount = 0
let previousKeyEventObserver = GhosttyNSView.debugGhosttySurfaceKeyEventObserver
GhosttyNSView.debugGhosttySurfaceKeyEventObserver = { keyEvent in
previousKeyEventObserver?(keyEvent)
guard keyEvent.action == GHOSTTY_ACTION_PRESS, keyEvent.keycode == keyCode else { return }
forwardedKeyDownCount += 1
}
`@MainActor`
func installFocusedTerminalRepairProbeForTesting(
appDelegate: AppDelegate,
keyCode: UInt32
) -> (
repairCount: () -> Int,
repairResponder: () -> NSResponder?,
forwardedKeyDownCount: () -> Int,
restore: () -> Void
) {
var repairCount = 0
var repairResponder: NSResponder?
let previousRepairObserver = appDelegate.debugFocusedTerminalKeyRepairObserverForTesting
appDelegate.debugFocusedTerminalKeyRepairObserverForTesting = { window, event, responder in
previousRepairObserver?(window, event, responder)
guard UInt32(event.keyCode) == keyCode else { return }
repairCount += 1
repairResponder = responder
}
var forwardedKeyDownCount = 0
let previousKeyEventObserver = GhosttyNSView.debugGhosttySurfaceKeyEventObserver
GhosttyNSView.debugGhosttySurfaceKeyEventObserver = { keyEvent in
previousKeyEventObserver?(keyEvent)
guard keyEvent.action == GHOSTTY_ACTION_PRESS, keyEvent.keycode == keyCode else { return }
forwardedKeyDownCount += 1
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift` around lines 12 - 37,
The function installFocusedTerminalRepairProbeForTesting accesses the
actor-isolated property GhosttyNSView.debugGhosttySurfaceKeyEventObserver
without proper actor isolation, which violates Swift concurrency rules. Add the
`@MainActor` attribute to the function signature of
installFocusedTerminalRepairProbeForTesting to match the MainActor isolation of
the debugGhosttySurfaceKeyEventObserver property.

Comment on lines +43 to +46
restore: {
appDelegate.debugFocusedTerminalKeyRepairObserverForTesting = previousRepairObserver
GhosttyNSView.debugGhosttySurfaceKeyEventObserver = previousKeyEventObserver
}

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 | ⚡ Quick win

Avoid cleanup leaks by auto-registering restore in teardown.

Relying only on a returned restore closure is brittle; one missed defer path can leak observers into later tests and reintroduce flakiness. Register cleanup via addTeardownBlock inside this helper as a safety net.

Suggested hardening
         return (
             repairCount: { repairCount },
             repairResponder: { repairResponder },
             forwardedKeyDownCount: { forwardedKeyDownCount },
             restore: {
                 appDelegate.debugFocusedTerminalKeyRepairObserverForTesting = previousRepairObserver
                 GhosttyNSView.debugGhosttySurfaceKeyEventObserver = previousKeyEventObserver
             }
         )
+        // Alternative pattern:
+        // addTeardownBlock { [weak appDelegate] in
+        //     appDelegate?.debugFocusedTerminalKeyRepairObserverForTesting = previousRepairObserver
+        //     GhosttyNSView.debugGhosttySurfaceKeyEventObserver = previousKeyEventObserver
+        // }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift` around lines 43 - 46,
In the restore closure where
appDelegate.debugFocusedTerminalKeyRepairObserverForTesting and
GhosttyNSView.debugGhosttySurfaceKeyEventObserver are being reset, add a call to
addTeardownBlock with the same cleanup logic to automatically register the
cleanup with XCTest's teardown mechanism. This ensures cleanup happens even if a
caller forgets to explicitly call the returned restore closure in a defer
statement, preventing observer leaks between tests.

@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

Caution

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

⚠️ Outside diff range comments (1)
cmuxTests/AppDelegateShortcutRoutingTests.swift (1)

4072-4077: ⚠️ Potential issue | 🔴 Critical

Fix the undeclared contentView reference.

Line 4076 calls contentView.addSubview(field), but contentView is not declared in the test. Replace with the existing attachTestResponder helper already used elsewhere in this file.

Proposed fix
         field.identifier = browserOmnibarTextFieldIdentifier
         field.panelId = browserPanelId
         field.stringValue = "ㄉㄚˋ"
-        contentView.addSubview(field)
+        attachTestResponder(field, to: window)
         BrowserOmnibarNativeFieldRegistry.shared.register(field, panelId: browserPanelId)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/AppDelegateShortcutRoutingTests.swift` around lines 4072 - 4077,
The test code contains an undeclared reference to contentView on the line
calling contentView.addSubview(field) in the OmnibarNativeTextField setup block.
Replace the contentView.addSubview(field) call with the attachTestResponder
helper function that is already being used elsewhere in this test file, passing
the field object as the appropriate parameter to properly attach the test
responder instead of relying on the undefined contentView variable.
♻️ Duplicate comments (1)
Sources/AppDelegate+ShortcutRoutingWindow.swift (1)

50-52: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Snapshot mainWindowContexts.values before iterating with resolvedWindow(for:).

This fallback iterates the dictionary directly while calling resolvedWindow(for:), which may mutate mainWindowContexts and cause mutation-during-enumeration issues. Line 73 in liveMainWindowContext correctly uses Array(mainWindowContexts.values).

🔧 Suggested fix
-        return mainWindowContexts.values.first { context in
+        return Array(mainWindowContexts.values).first { context in
             resolvedWindow(for: context) != nil
         }?.tabManager
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/AppDelegate`+ShortcutRoutingWindow.swift around lines 50 - 52, The
code iterates directly over mainWindowContexts.values while calling
resolvedWindow(for:), which may mutate the mainWindowContexts dictionary during
enumeration and cause a runtime crash. Fix this by creating a snapshot of the
dictionary values before iterating, similar to the pattern used in
liveMainWindowContext on line 73. Wrap mainWindowContexts.values with Array() to
create an immutable copy before calling first on it, ensuring the dictionary
cannot be mutated during the enumeration with resolvedWindow(for:).

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift`:
- Line 21: The current implementation uses RunLoop.main.run(until:
Date(timeIntervalSinceNow: 0.05)) which is a fixed wall-clock sleep pattern that
violates test guidelines and can cause flaky tests. Replace this fixed-duration
wait with a predicate-based waiting mechanism that checks for an actual
readiness condition or completion signal. Instead of sleeping for a fixed 0.05
seconds, implement a wait that polls or observes for the actual completion of
the async operation you are testing (such as a delegate callback, published
property, or completion flag), allowing the test to proceed immediately once the
expected state is reached rather than always waiting the full duration.

---

Outside diff comments:
In `@cmuxTests/AppDelegateShortcutRoutingTests.swift`:
- Around line 4072-4077: The test code contains an undeclared reference to
contentView on the line calling contentView.addSubview(field) in the
OmnibarNativeTextField setup block. Replace the contentView.addSubview(field)
call with the attachTestResponder helper function that is already being used
elsewhere in this test file, passing the field object as the appropriate
parameter to properly attach the test responder instead of relying on the
undefined contentView variable.

---

Duplicate comments:
In `@Sources/AppDelegate`+ShortcutRoutingWindow.swift:
- Around line 50-52: The code iterates directly over mainWindowContexts.values
while calling resolvedWindow(for:), which may mutate the mainWindowContexts
dictionary during enumeration and cause a runtime crash. Fix this by creating a
snapshot of the dictionary values before iterating, similar to the pattern used
in liveMainWindowContext on line 73. Wrap mainWindowContexts.values with Array()
to create an immutable copy before calling first on it, ensuring the dictionary
cannot be mutated during the enumeration with resolvedWindow(for:).
🪄 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: b3b4f191-11f9-44c2-9475-1931065e9e83

📥 Commits

Reviewing files that changed from the base of the PR and between 46cd45b and b2010f3.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (5)
  • Sources/AppDelegate+ShortcutRoutingTesting.swift
  • Sources/AppDelegate+ShortcutRoutingWindow.swift
  • Sources/AppDelegate.swift
  • cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift

hostedView.setVisibleInUI(true)
hostedView.setActive(true)
hostedView.moveFocus()
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05))

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 | 🟠 Major | ⚡ Quick win

Replace fixed run-loop sleep with predicate-based waiting.

Line 21 uses a fixed wall-clock wait, which is exactly the flaky sleep-then-assert pattern the test guidelines prohibit. Wait on a real readiness predicate instead.

Suggested change
-        RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05))
-        XCTAssertTrue(
-            hostedView.isSurfaceViewFirstResponder(),
-            "Expected terminal surface to own first responder before repair test"
-        )
+        XCTAssertTrue(
+            waitUntil(timeout: 1.0) {
+                hostedView.isSurfaceViewFirstResponder()
+            },
+            "Expected terminal surface to own first responder before repair test"
+        )

As per coding guidelines, tests must not use fixed sleep-style waits for async readiness and should assert causality via real completion/predicate signals.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/AppDelegateShortcutRoutingRepairProbe.swift` at line 21, The
current implementation uses RunLoop.main.run(until: Date(timeIntervalSinceNow:
0.05)) which is a fixed wall-clock sleep pattern that violates test guidelines
and can cause flaky tests. Replace this fixed-duration wait with a
predicate-based waiting mechanism that checks for an actual readiness condition
or completion signal. Instead of sleeping for a fixed 0.05 seconds, implement a
wait that polls or observes for the actual completion of the async operation you
are testing (such as a delegate callback, published property, or completion
flag), allowing the test to proceed immediately once the expected state is
reached rather than always waiting the full duration.

Source: Coding guidelines

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

4139-4155: ⚠️ Potential issue | 🔴 Critical

Replace out-of-scope contentView reference that causes compilation error.

Line 4154 uses contentView.addSubview(field), but this function does not define contentView locally. All adjacent omnibar tests in this file correctly use the attachTestResponder(_:to:) helper. This inconsistency will cause a compilation failure.

Proposed fix
-        contentView.addSubview(field)
+        attachTestResponder(field, to: window)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/AppDelegateShortcutRoutingTests.swift` around lines 4139 - 4155,
The line referencing contentView.addSubview(field) uses an undefined variable
contentView that is not in scope, causing a compilation error. Replace this line
with a call to the attachTestResponder(_:to:) helper function, passing field and
the appropriate target (the browserPanel), which is the consistent pattern used
by other omnibar tests in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cmuxTests/AppDelegateShortcutRoutingTests.swift`:
- Around line 4139-4155: The line referencing contentView.addSubview(field) uses
an undefined variable contentView that is not in scope, causing a compilation
error. Replace this line with a call to the attachTestResponder(_:to:) helper
function, passing field and the appropriate target (the browserPanel), which is
the consistent pattern used by other omnibar tests in this file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 66a000f4-ad27-48c6-9f03-02ad2c94b507

📥 Commits

Reviewing files that changed from the base of the PR and between b2010f3 and 57e75bf.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (1)
  • cmuxTests/AppDelegateShortcutRoutingTests.swift

…-focus

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	cmux.xcodeproj/project.pbxproj
@azooz2003-bit
azooz2003-bit merged commit 5fdf30c into main Jun 19, 2026
30 checks passed
@azooz2003-bit
azooz2003-bit deleted the task-shortcut-routing-focus branch June 19, 2026 04:19

This branch was successfully deployed

1 active deployment
Preview – cmux — 1159deb4 Deployed Jun 19, 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