cmux NEXT: Cmd-N's New Tab field keeps the keyboard; Cmd-L in a chat focuses its composer

Merged
AU
11d ago
+164-11

Summary

  • Cmd-N focus. Cmd-N sometimes opened the new workspace with no focus in the New Tab field (chat box). This happened on the current NIGHTLY, 1.0.0-nightly.3968001295301 (8cba1da), where the New Tab page and the agent chat render in Chromium.
    • Cause: CEFTab.setFocused(true) refuses a page that is not shown yet and keeps the request in pendingFocus. Only attach, which runs when the browser is created, ever replayed it.
    • A Cmd-N New Tab page is a prewarmed spare, so its browser already exists. Its focus request can arrive just before its window shows, or while the content lifecycle still hides it (the paint hold). That request was dropped.
    • FocusEffectApplier had already recorded the page as focused (focusedChildWindowPage), so it never asked again.
    • Fix: the request is now kept on the tab and granted when the page shows (CEFPaneHost.present and setContentVisible(true)). A setFocused(false) that arrives first cancels it. The attach path behaves as before.
  • Cmd-L in an agent chat (Focus Location Bar) now focuses the chat's composer. Before, it opened a New Tab page next to the chat. Browser tabs, Markdown tabs and New Tab pages behave as before.
  • Supporting changes. debug.agent_pane gets a read-only composer_focused verb. keyboard-parity-e2e.py now expects Cmd-L in a chat to focus the composer.

Regression proof

CEFDeferredFocusTests drives CEFTab and CEFPaneHost against a spy Chromium shim:

  • A page whose browser already exists asks for focus before its view is in a window: it is granted focus when the view enters one.
  • A page asks while the content lifecycle hides it: it is granted focus when the lifecycle shows it.
  • A request withdrawn before the page shows is never granted.

Command: cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg=suite --arg=Packages/macOS/CmuxNext --arg=<suites>

  • Red, before the fix: test commit 8dbd55d5, step ad216704645bb49d3060b5f9. The test failed with Expectation failed: (FocusSpyShim.granted → []) == [7]. That run also crashed, a defect in the test harness (a tab outlived its unowned runtime), fixed in 95e09618.
  • Green, at the head: 95e09618, step faa022b7f953df63f6fb447d, with CEFDeferredFocusTests,CEFQuitCreationTests,PopupOmnibarKeyTests,CodeEditorKeysTests. Result: all 4 suites passed, including the 3 new tests.

Review

An independent subagent correctness review returned SHIP. Follow-ups it raised, not done in this PR:

  1. Stale focus request. A request deferred through a direct focusChat/focusComposer call (outside FocusEffectApplier) is never withdrawn if the person clicks elsewhere before the page shows. The page could take the keys later when it shows.
    • I tried clearing the request when the view leaves its window (conceal) and reverted it. A prewarmed spare fires that same event when it moves from its holding window into the pane, so clearing there could drop the request this PR keeps.
    • A better fix is to send those direct calls through the focus applier.
  2. Popups. Popups waiting for a window move (awaitsWindowMove) still never replay a deferred request. This predates this PR.
  3. Key hold may replay too early. The New Tab key hold's focusField returns true right after focusChat(), even when Chromium deferred the grant. Held keys may then be replayed before the page has focus. This is unconfirmed.
  4. Harness-setup mode. Cmd-L in an agent pane in harness-setup mode now does nothing instead of opening a New Tab page.

Observed / Not run

  • Observed:
    • The red and green fleet runs above.
    • scripts/cmux-next/prepush-gate.py reported no new violations.
  • Not run:
    • No tagged dev build or GUI dogfood of Cmd-N or Cmd-L in the real app.
    • The race was found by reading the code, not by catching it live. The nightly does not expose debug.focus.
    • keyboard-parity-e2e.py was not run.

No capture: this change is to focus and keyboard behavior; nothing is drawn differently.

Changelog

  • Fixed: Cmd-N reliably puts the cursor in the new workspace's New Tab field.
  • Changed: Cmd-L in an agent chat focuses its message box.

šŸ¤– Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes Cmd-N occasionally leaving the new workspace's New Tab field without the keyboard, and makes Cmd-L in an agent chat focus its composer instead of opening a New Tab page next to it.

Bug Fixes

  • A Cmd-N New Tab page (a prewarmed spare whose browser already exists) could ask for focus before its window showed or while the content lifecycle still hid it; only browser creation used to replay the request, so it was dropped. The request now waits on the tab and is granted when the page shows; a prior setFocused(false) cancels it.
  • Cmd-L in an agent chat focuses the composer now; browser tabs, Markdown tabs, and New Tab pages behave as before. In harness-setup mode it does nothing instead of opening a New Tab page.

Tests

  • Added CEFDeferredFocusTests covering focus requested before the page shows, while the lifecycle hides it, and withdrawn before showing.
  • debug.agent_pane adds a read-only composer_focused verb; keyboard-parity-e2e.py expects Cmd-L in a chat to focus the composer.