Skip to content

Fix command palette focus after terminal find#2089

Merged
lawrencecchen merged 10 commits intomainfrom
task-find-command-palette-focus
Mar 26, 2026
Merged

Fix command palette focus after terminal find#2089
lawrencecchen merged 10 commits intomainfrom
task-find-command-palette-focus

Conversation

@lawrencecchen
Copy link
Copy Markdown
Contributor

@lawrencecchen lawrencecchen commented Mar 25, 2026

Summary

  • add regression coverage for command-palette focus-stealer classification
  • treat terminal-owned inputs under GhosttySurfaceScrollView as blocked while the command palette is visible
  • keep terminal find from reclaiming focus after Cmd+F, then Cmd+P or Cmd+Shift+P

Testing

  • ./scripts/reload.sh --tag task-find-command-palette-focus
  • Added targeted cmux-unit coverage in cmuxTests/ShortcutAndCommandPaletteTests.swift
  • Local xcodebuild -scheme cmux-unit runs in this environment hit an existing SwiftUI/AppKit window-layout crash before the old window-based harness can complete, so rely on PR CI for the new regression

Task

  • after cmd+f, then cmd+p or cmd+shift+p the focus and typing is wrong

Summary by cubic

Prevents terminal and browser views from stealing focus when the command palette is open, and restores the browser find field (with caret) after a workspace round‑trip. Also avoids redundant DevTools visibility publishes when the inspector is already hidden.

  • Bug Fixes
    • Treat GhosttySurfaceScrollView, GhosttyNSView, WKWebView, and terminal-owned text inputs as blocked while the palette is visible.
    • Centralize focus-stealer checks via isCommandPaletteFocusStealingTerminalOrBrowserResponder and isCommandPaletteFocusStealingTerminalOrBrowserView.
    • Restore browser find focus and caret after creating a new workspace and switching back by routing reactivation through TabManager.focusPanel and focusing the native field at the end of the query.
    • Add cmux-unit tests for focus classification (including NSTextView delegate-not-a-view fallback) and the Cmd+F → Cmd+P regression.
    • Avoid republishing hidden Developer Tools intent by gating changes through setPreferredDeveloperToolsVisible; add a regression test.
    • Add a UI test to ensure the browser find field keeps focus and its query across workspace changes.

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

Summary by CodeRabbit

  • Refactor

    • Centralized detection of embedded browser/terminal responders and views that should steal focus; focus restoration now routes through normal panel/workspace focus machinery.
  • Stability

    • Developer-tools visibility updates made idempotent to avoid state churn.
    • Search/find focus now reliably places the caret at the end and respects newly added gating so mounted search fields only focus when their workspace/panel is actually active.
  • Tests

    • Added unit and UI tests covering focus classification, developer-tools sync, and find-field persistence across workspace round-trips.

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 26, 2026 0:12am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51a568ba-38ca-4670-8d20-b857be3877ce

📥 Commits

Reviewing files that changed from the base of the PR and between e9a58aa and add66f4.

📒 Files selected for processing (3)
  • Sources/Find/SurfaceSearchOverlay.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/Panels/BrowserPanelView.swift

📝 Walkthrough

Walkthrough

Refactors command-palette focus detection into two module-level helpers that traverse responder/delegate and view/superview chains, expands view-level detection to include GhosttySurfaceScrollView, updates AppDelegate to use the helpers, adds targeted tests, and makes related focus/devtools and TabManager focus-restoration changes.

Changes

Cohort / File(s) Summary
Focus-stealing helpers & AppDelegate
Sources/AppDelegate.swift
Added isCommandPaletteFocusStealingTerminalOrBrowserResponder(_:) and isCommandPaletteFocusStealingTerminalOrBrowserView(_:); isFocusStealingResponderWhileCommandPaletteVisible(_:) now delegates to them. Responder-level checks match GhosttyNSView/WKWebView; view-level detection now also recognizes GhosttySurfaceScrollView.
Focus classification tests
cmuxTests/ShortcutAndCommandPaletteTests.swift
Added CommandPaletteFocusStealerClassificationTests covering Ghostty surface, nested NSTextField in terminal-hosted scroll view, standalone NSTextField, and NSTextView with non-NSView delegate scenarios.
TabManager — focus restoration flow
Sources/TabManager.swift
focusSelectedTabPanel(previousTabId:) now resolves panelId via lastFocusedPanelByTab or tab focusedPanelId, early-returns if invalid, and reactivates via tab.focusPanel(panelId) instead of direct panel.focus()/terminal-specific routing.
BrowserPanel — devtools visibility state
Sources/Panels/BrowserPanel.swift
Added private setPreferredDeveloperToolsVisible(_:) to guard updates and replaced direct assignments to preferredDeveloperToolsVisible across inspector/transition paths with the guarded setter.
Browser developer-tools sync test
cmuxTests/BrowserConfigTests.swift
Added import Combine and testSyncDoesNotRepublishHiddenDeveloperToolsIntentWhenInspectorAlreadyHidden to assert syncDeveloperToolsPreferenceFromInspector() does not republish when inspector intent remains hidden.
Browser find field caret helper
Sources/Find/BrowserSearchOverlay.swift
Added Coordinator.focusField(_:in:) to call window.makeFirstResponder and then set the NSTextView selection to the end; replaced direct makeFirstResponder calls with this helper.
Surface search gating & terminal focus
Sources/Find/SurfaceSearchOverlay.swift, Sources/GhosttyTerminalView.swift
Added canApplyFocusRequest: () -> Bool through overlay/representable; GhosttySurfaceScrollView exposes canApplyMountedSearchFieldFocusRequest() that gates mounted search-field focus requests based on workspace/tab/panel focus state.
BrowserPanel view gating
Sources/Panels/BrowserPanelView.swift
Introduced canApplyBrowserFindFieldFocusRequest(_:) to require isPanelFocusedInModel() in addition to existing predicates before applying browser find-field focus.
UI tests — workspace/command-palette round-trip
cmuxUITests/BrowserPaneNavigationKeybindUITests.swift
Added testBrowserFindFieldKeepsFocusAfterNewWorkspaceRoundTrip, two shared-scenario tests, SplitFindOwner enum, and helper functions to exercise find-field focus/value persistence across create/switch workspace flows and command-palette interactions.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CommandPalette
  participant TabManager
  participant Tab
  participant AppDelegate
  participant Panel
  participant View

  User->>CommandPalette: open + create "New Workspace"
  CommandPalette->>TabManager: request create & switch
  TabManager->>Tab: focusSelectedTabPanel(previousTabId)
  Tab->>TabManager: resolve panelId (lastFocusedPanelByTab or focusedPanelId)
  TabManager->>Tab: tab.focusPanel(panelId)
  Tab->>Panel: activate panel
  Panel->>AppDelegate: ask current responder
  AppDelegate->>AppDelegate: isCommandPaletteFocusStealingTerminalOrBrowserResponder(responder)
  AppDelegate->>View: isCommandPaletteFocusStealingTerminalOrBrowserView(view) (may traverse delegate/superview)
  alt focus stealer detected
    Panel->>View: avoid stealing focus / restore previous field
  else not a stealer
    Panel->>View: allow focus to requested field
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped the responder trails and sniffed each view,

I learned when Ghostty scrolls would tug attention askew,
Two helpers I stitched to trace delegate and superview,
Tests hopped in to catch each sly cue,
Now carets settle snug and find fields greet true. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix command palette focus after terminal find' directly corresponds to the main objective: preventing terminal find from reclaiming focus when the command palette is opened via Cmd+P or Cmd+Shift+P.
Description check ✅ Passed The PR description includes a clear Summary section covering what changed and why, and a Testing section explaining how the changes were tested with script tags and unit test coverage.

✏️ 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 task-find-command-palette-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.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 25, 2026

Greptile Summary

This PR fixes a focus regression where the terminal find bar (SurfaceSearchOverlay, mounted under GhosttySurfaceScrollView) could reclaim focus after the user opened the command palette via Cmd+P/Cmd+Shift+P following a Cmd+F. The core fix is extending the "focus-stealer" classification to also block views that are descendants of GhosttySurfaceScrollView, not just GhosttyNSView and WKWebView.

  • Fix: isCommandPaletteFocusStealingTerminalOrBrowserView now checks GhosttySurfaceScrollView in addition to GhosttyNSView and WKWebView, both directly and via superview traversal.
  • Refactor: The two detection predicates are promoted to top-level free functions so the unit tests can call them directly without going through AppDelegate.
  • Dead code: isTerminalOrBrowserView is now a private method with no callers — it delegates straight to the new free function but is never invoked; safe to delete.
  • Regression test commit structure: Follows the two-commit policy (test-first, then fix), which is correct per project policy.
  • Test gap (minor): The NSTextView (non-field-editor) delegate-check branch does not fall through to the superview walk if the delegate is not an NSView. This is not a regression from this PR (same logic existed before), and the current find bar is an NSTextField so the fix applies cleanly — but worth noting for future overlay implementations.

Confidence Score: 5/5

  • Safe to merge — targeted, well-tested fix with no functional regressions; only cleanup remains.
  • The fix is minimal and correctly scoped: one additional type check added to the view-hierarchy walk. The logic is straightforwardly correct, regression tests follow the project's two-commit policy and exercise observable runtime behavior, and the PR description matches the code. The two P2 comments (dead code, NSTextView fall-through) are non-blocking cleanup items that don't affect the fix or existing behavior.
  • No files require special attention.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Refactors focus-stealer detection into two top-level free functions, adds GhosttySurfaceScrollView to the blocked-view set (the core fix), and delegates the existing private methods to those functions. One private method (isTerminalOrBrowserView) is now dead code with no callers.
cmuxTests/ShortcutAndCommandPaletteTests.swift Adds CommandPaletteFocusStealerClassificationTests with three behavioral unit tests covering: GhosttyNSView as a direct stealer, NSTextField inside GhosttySurfaceScrollView as a stealer, and an unrelated NSTextField as a non-stealer. Tests exercise observable runtime behavior through the new free functions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[NSResponder arrives while\ncommand palette is visible] --> B{is GhosttyNSView\nor WKWebView?}
    B -- Yes --> BLOCK[Return true: block focus steal]
    B -- No --> C{is NSTextView\nand NOT field editor\nwith NSView delegate?}
    C -- Yes --> D[Check delegate view hierarchy]
    D --> E{delegate or ancestor\nis GhosttyNSView /\nGhosttySurfaceScrollView /\nWKWebView?}
    E -- Yes --> BLOCK
    E -- No --> ALLOW[Return false: allow focus]
    C -- No --> F{is NSView?}
    F -- Yes --> G[Check self + superview chain]
    G --> H{self or ancestor\nis GhosttyNSView /\nGhosttySurfaceScrollView /\nWKWebView?}
    H -- Yes --> BLOCK
    H -- No --> ALLOW
    F -- No --> ALLOW

    style BLOCK fill:#f66,color:#fff
    style ALLOW fill:#6a6,color:#fff
Loading

Reviews (1): Last reviewed commit: "fix: block terminal find from stealing p..." | Re-trigger Greptile

Comment thread Sources/AppDelegate.swift Outdated
Comment thread Sources/AppDelegate.swift
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@Sources/AppDelegate.swift`:
- Around line 45-53: The current logic returns early for NSTextView responders
using textView.delegate as NSView which can skip classifying the NSTextView
itself; change the flow in the responder handling so you first attempt to
classify via the delegate path (call
isCommandPaletteFocusStealingTerminalOrBrowserView(delegateView) only if
delegateView exists) but do not return immediately—if that check fails, then
fall back to classifying the text view itself by calling
isCommandPaletteFocusStealingTerminalOrBrowserView(textView) (still skipping
field editors via textView.isFieldEditor). Ensure both paths are evaluated in
order for responder instances of NSTextView and preserve the existing NSView
branch for non-textView responders.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f3fabc99-76af-4084-a20e-ef74677ac87c

📥 Commits

Reviewing files that changed from the base of the PR and between 7ffa447 and 3e7f4ed.

📒 Files selected for processing (2)
  • Sources/AppDelegate.swift
  • cmuxTests/ShortcutAndCommandPaletteTests.swift

Comment thread Sources/AppDelegate.swift
Comment on lines +45 to +53
if let textView = responder as? NSTextView,
!textView.isFieldEditor,
let delegateView = textView.delegate as? NSView {
return isCommandPaletteFocusStealingTerminalOrBrowserView(delegateView)
}

if let view = responder as? NSView {
return isCommandPaletteFocusStealingTerminalOrBrowserView(view)
}
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

Avoid delegate-only classification for NSTextView responders.

The early return on Line 48 can miss terminal/browser-hosted text views when delegate is an unrelated NSView. Check delegate path first, then fall back to classifying the text view itself.

Proposed fix
-    if let textView = responder as? NSTextView,
-       !textView.isFieldEditor,
-       let delegateView = textView.delegate as? NSView {
-        return isCommandPaletteFocusStealingTerminalOrBrowserView(delegateView)
-    }
+    if let textView = responder as? NSTextView, !textView.isFieldEditor {
+        if let delegateView = textView.delegate as? NSView,
+           isCommandPaletteFocusStealingTerminalOrBrowserView(delegateView) {
+            return true
+        }
+        return isCommandPaletteFocusStealingTerminalOrBrowserView(textView)
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 45 - 53, The current logic returns
early for NSTextView responders using textView.delegate as NSView which can skip
classifying the NSTextView itself; change the flow in the responder handling so
you first attempt to classify via the delegate path (call
isCommandPaletteFocusStealingTerminalOrBrowserView(delegateView) only if
delegateView exists) but do not return immediately—if that check fails, then
fall back to classifying the text view itself by calling
isCommandPaletteFocusStealingTerminalOrBrowserView(textView) (still skipping
field editors via textView.isFieldEditor). Ensure both paths are evaluated in
order for responder instances of NSTextView and preserve the existing NSView
branch for non-textView responders.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@cmuxUITests/BrowserPaneNavigationKeybindUITests.swift`:
- Around line 920-923: The test assumes Cmd+1 returns to the original workspace;
instead either seed the placement explicitly for the UI test or navigate back to
the original workspace by identity: capture the original workspace identifier
before creating the new workspace, then restore focus by selecting that
workspace (rather than calling app.typeKey("1", modifierFlags: [.command]) ),
and assert the BrowserFindSearchTextField (restoredFindField) appears;
alternatively set WorkspacePlacementSettings.current() to a deterministic
placement at test setup so index-based switching is stable.
- Around line 909-913: The test is pressing Return before the command-palette
results have converged to the new "New Workspace" action; update the assertion
to explicitly wait for the result row that represents the "New Workspace" item
rather than just CommandPaletteResultRow.0 (which may be a seeded row). After
typing into paletteSearchField, wait for a result whose label/text contains "New
Workspace" (e.g., query the descendants for a cell/staticText with that string
or use a predicate on the element returned from CommandPaletteResultRow.0 to
check its label) and assert its existence with a timeout before sending the
Return key; keep using the shared
scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:) behavior but
ensure the test verifies the specific "New Workspace" result is present/selected
first.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f5abc0c8-89cb-4c69-90f0-f8e7192aaac5

📥 Commits

Reviewing files that changed from the base of the PR and between 3e7f4ed and 10ca61c.

📒 Files selected for processing (4)
  • Sources/Panels/BrowserPanel.swift
  • Sources/TabManager.swift
  • cmuxTests/BrowserConfigTests.swift
  • cmuxUITests/BrowserPaneNavigationKeybindUITests.swift

Comment on lines +909 to +913
paletteSearchField.typeText("New Workspace")

let firstResultRow = app.descendants(matching: .any).matching(identifier: "CommandPaletteResultRow.0").firstMatch
XCTAssertTrue(firstResultRow.waitForExistence(timeout: 5.0), "Expected command palette results for New Workspace")
app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])
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

Wait for the actual “New Workspace” result before submitting.

CommandPaletteResultRow.0 can already exist from the palette’s initial seed, so waitForExistence alone does not prove the query has converged to the new-workspace action. Pressing Return here can intermittently execute whatever stale row is still selected instead.

Based on learnings: scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:) is shared command‑palette infrastructure across all submenus; brief initial flashes are expected and should not be changed in a feature-scoped PR.

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

In `@cmuxUITests/BrowserPaneNavigationKeybindUITests.swift` around lines 909 -
913, The test is pressing Return before the command-palette results have
converged to the new "New Workspace" action; update the assertion to explicitly
wait for the result row that represents the "New Workspace" item rather than
just CommandPaletteResultRow.0 (which may be a seeded row). After typing into
paletteSearchField, wait for a result whose label/text contains "New Workspace"
(e.g., query the descendants for a cell/staticText with that string or use a
predicate on the element returned from CommandPaletteResultRow.0 to check its
label) and assert its existence with a timeout before sending the Return key;
keep using the shared
scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:) behavior but
ensure the test verifies the specific "New Workspace" result is present/selected
first.

Comment on lines +920 to +923
app.typeKey("1", modifierFlags: [.command])

let restoredFindField = app.textFields["BrowserFindSearchTextField"].firstMatch
XCTAssertTrue(restoredFindField.waitForExistence(timeout: 6.0), "Expected browser find field after returning to workspace 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

Don’t assume the original workspace is always on Cmd+1.

This only returns to the original browser when new workspaces insert after the current one. WorkspacePlacementSettings.current() also allows .top, in which case Cmd+1 stays on the newly created workspace and this test fails even though focus restoration is correct. Please seed placement explicitly for the UI test or navigate back by workspace identity instead of a fixed index.

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

In `@cmuxUITests/BrowserPaneNavigationKeybindUITests.swift` around lines 920 -
923, The test assumes Cmd+1 returns to the original workspace; instead either
seed the placement explicitly for the UI test or navigate back to the original
workspace by identity: capture the original workspace identifier before creating
the new workspace, then restore focus by selecting that workspace (rather than
calling app.typeKey("1", modifierFlags: [.command]) ), and assert the
BrowserFindSearchTextField (restoredFindField) appears; alternatively set
WorkspacePlacementSettings.current() to a deterministic placement at test setup
so index-based switching is stable.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
cmuxUITests/BrowserPaneNavigationKeybindUITests.swift (2)

915-920: ⚠️ Potential issue | 🟠 Major

Don’t use Cmd+1 as the round-trip back to the original workspace.

These tests still depend on workspace insertion order. If new workspaces are placed at the top, Cmd+1 keeps the test on the newly created workspace, so the assertions fail for the wrong reason. Waiting only for the palette to dismiss also does not prove the midpoint switch completed. Please seed placement explicitly for this UI test, or navigate back by the original workspace’s identity after the new workspace becomes active.

Also applies to: 1141-1143

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

In `@cmuxUITests/BrowserPaneNavigationKeybindUITests.swift` around lines 915 -
920, Don’t rely on app.typeKey("1", modifierFlags: [.command]) to return to the
original workspace; instead seed workspace placement or navigate by workspace
identity: capture the original workspace’s accessibility identifier before
creating the new workspace, wait until the new workspace is active (e.g., assert
the new workspace element is selected/exists), then programmatically activate
the original workspace element (tap or call .click()) and assert it becomes
active; replace the app.typeKey("1", modifierFlags: [.command]) call and the
simple palette dismissal check (waitForNonExistence(paletteSearchField)) with
these identity-based waits and activation steps so the test doesn’t depend on
workspace insertion order.

904-913: ⚠️ Potential issue | 🟡 Minor

Wait for the actual “New Workspace” result before pressing Return.

CommandPaletteResultRow.0 can already exist from the palette’s seeded results, so waitForExistence alone does not prove the query has converged. Return can still execute a stale selection here. Please make openCommandPaletteForNewWorkspace(_:) wait until row 0 actually resolves to “New Workspace”, and route testBrowserFindFieldKeepsFocusAfterNewWorkspaceRoundTrip() through that helper so the fix lives in one place.

🛠️ Suggested consolidation
-        app.typeKey("p", modifierFlags: [.command, .shift])
-
-        let paletteSearchField = app.textFields["CommandPaletteSearchField"].firstMatch
-        XCTAssertTrue(paletteSearchField.waitForExistence(timeout: 5.0), "Expected command palette search field")
-        paletteSearchField.click()
-        paletteSearchField.typeText("New Workspace")
-
-        let firstResultRow = app.descendants(matching: .any).matching(identifier: "CommandPaletteResultRow.0").firstMatch
-        XCTAssertTrue(firstResultRow.waitForExistence(timeout: 5.0), "Expected command palette results for New Workspace")
-        app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])
+        openCommandPaletteForNewWorkspace(app)
-        let firstResultRow = app.descendants(matching: .any).matching(identifier: "CommandPaletteResultRow.0").firstMatch
-        XCTAssertTrue(firstResultRow.waitForExistence(timeout: 5.0), "Expected command palette results for New Workspace")
+        XCTAssertTrue(
+            waitForCondition(timeout: 5.0) {
+                let firstResultRow = app.descendants(matching: .any)
+                    .matching(identifier: "CommandPaletteResultRow.0")
+                    .firstMatch
+                return firstResultRow.exists &&
+                    firstResultRow.label.localizedCaseInsensitiveContains("New Workspace")
+            },
+            "Expected command palette to converge to the New Workspace result"
+        )
         app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])

Based on learnings: scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:) is shared command-palette infrastructure across all submenus, so brief seeded flashes are expected here.

Also applies to: 1177-1187

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

In `@cmuxUITests/BrowserPaneNavigationKeybindUITests.swift` around lines 904 -
913, The test is pressing Return before the command-palette results have
converged (seeded rows can make CommandPaletteResultRow.0 appear stale); update
openCommandPaletteForNewWorkspace(_:) to wait until the first result row's
label/text exactly equals "New Workspace" (polling the firstMatch's value/label
or an accessibility attribute) before sending the Return key, and change
testBrowserFindFieldKeepsFocusAfterNewWorkspaceRoundTrip() to call that helper
so the wait logic is centralized; keep existing use of CommandPaletteResultRow.0
and the shared refresh behavior
(scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:)) but gate the
Return on the content match rather than mere existence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@cmuxUITests/BrowserPaneNavigationKeybindUITests.swift`:
- Around line 915-920: Don’t rely on app.typeKey("1", modifierFlags: [.command])
to return to the original workspace; instead seed workspace placement or
navigate by workspace identity: capture the original workspace’s accessibility
identifier before creating the new workspace, wait until the new workspace is
active (e.g., assert the new workspace element is selected/exists), then
programmatically activate the original workspace element (tap or call .click())
and assert it becomes active; replace the app.typeKey("1", modifierFlags:
[.command]) call and the simple palette dismissal check
(waitForNonExistence(paletteSearchField)) with these identity-based waits and
activation steps so the test doesn’t depend on workspace insertion order.
- Around line 904-913: The test is pressing Return before the command-palette
results have converged (seeded rows can make CommandPaletteResultRow.0 appear
stale); update openCommandPaletteForNewWorkspace(_:) to wait until the first
result row's label/text exactly equals "New Workspace" (polling the firstMatch's
value/label or an accessibility attribute) before sending the Return key, and
change testBrowserFindFieldKeepsFocusAfterNewWorkspaceRoundTrip() to call that
helper so the wait logic is centralized; keep existing use of
CommandPaletteResultRow.0 and the shared refresh behavior
(scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:)) but gate the
Return on the content match rather than mere existence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c74b8713-7bb3-4eea-97ec-d6bea96ebad0

📥 Commits

Reviewing files that changed from the base of the PR and between d6313ce and e9a58aa.

📒 Files selected for processing (1)
  • cmuxUITests/BrowserPaneNavigationKeybindUITests.swift

@lawrencecchen lawrencecchen merged commit 8a3ab6b into main Mar 26, 2026
15 checks passed
Jesssullivan added a commit to Jesssullivan/cmux that referenced this pull request Mar 26, 2026
Ingests all upstream fixes since 2026-03-22 including:
- Fix Cmd+N crash: retain snapshot workspaces (manaflow-ai#2183, manaflow-ai#2181, manaflow-ai#2178, manaflow-ai#2173)
- Fix browser pane restore after reopen (manaflow-ai#2141)
- Fix Ghostty resize_split keybind (manaflow-ai#1899)
- Reduce shell integration prompt latency (manaflow-ai#2109)
- Fix command palette focus after terminal find (manaflow-ai#2089)
- Add Codex CLI hooks (manaflow-ai#2103)
- Add cmux.json custom commands (manaflow-ai#2011)
- Fix window position restore on relaunch (manaflow-ai#2129)

Conflict resolution:
- BrowserPanel.swift: accepted upstream configureWebViewConfiguration()
  refactor (already includes our forMainFrameOnly:true CAPTCHA fix from PR manaflow-ai#1877)

Fork-specific files preserved:
- Sources/Panels/WebAuthn{Coordinator,BridgeJavaScript}.swift
- Sources/FIDO2/module.modulemap
- vendor/ctap2 submodule
- cmux.entitlements (with camera/audio-input removed)
- cmux.embedded.entitlements
- .github/workflows/fork-{ci,release}.yml
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
* test: cover command palette focus guard

* fix: block terminal find from stealing palette focus

* test: cover text view focus-stealer fallback

* Add regression for hidden DevTools sync republish loop

* Avoid redundant DevTools visibility publishes

* test: cover browser find focus after workspace round-trip

* fix: restore browser find focus after workspace round-trip

* fix: keep browser find caret on workspace return

* Add workspace round-trip split find regressions

* Keep inactive find overlays from stealing focus

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
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