fix(app): a script's new workspace does not take the person's keys from Home's composer
Summary
With Home showing, keys typed for a new terminal could land in Home's Chief message box, and Return sent the shell command to the Chief as a chat message (dogfood v2.2.3, P0 journey scripted-newtab-keeps-person-keys). A script's newTab with focus could also take the window from a person typing in a field. Both are fixed:
- The person's own new terminal holds their keys until it shows. A terminal-first
newTabfrom the person (menu, palette, key), shown in the window they type in, opens a key hold (CreationInputCoordinator.beginWorkspace). Keys wait until the window shows that workspace. If the create fails or the workspace never shows, those keys are dropped, never typed into whatever has the focus. - The window's keyboard stays the new terminal's until the person's next intent. That is a click, Escape, a focus move by mouse, keyboard or palette, or another of their actions (
KeyboardClaims). A timeout never ends it, and neither does automation. While the claim holds, no primary input takes the keyboard by itself (NSWindow.pendingCreationOwnsKeyboard), and keys that run no binding are kept from the window. - A script never takes the person's window. A script's, the CLI's, MCP's or the socket's
newTabwith focus is created without view change. Once it exists, the window shows it only if no text input has the person's keyboard in the active key window (WindowController.hasTextInputKeyboard: an editable text view or field, an input method composing, or an editable page element). A person who runscmux open --focusfrom a terminal or from another app still gets the workspace shown. - Who asked is the run's own origin. A script's nested
newTab, built with the default user invocation, opens no New Tab page and no key hold.
Created but not shown (open behavior note, big-changes.md). When a script's new workspace arrives while the person types in a field, the workspace is created and listed in the sidebar, and the window stays where the person is typing. The check runs when the workspace exists, not when the script asks for it, so a person who started typing meanwhile keeps their input. This PR adds no badge or toast for a workspace that is listed but not shown. Its sidebar row is the only sign. That stays an open UX question.
Follow-up (lead decision). If the person's own claimed create fails, this PR keeps the claim until their next intent, so keys typed meanwhile are kept from the window and nothing says why (review finding 1). A follow-up PR, red first, ends the claim when its own create fails and shows the failure through the create-failure notice, which says the typed keys were not delivered. Keys held for that create still never reach an agent's composer.
Ported from cp/fix-chief-composer-focus (perf merge 943ac395981), changed to fit feat's code. Feat has no KeyRouter modes, so the hold is read from CreationInputCoordinator.isHolding(in:).
App size. CmuxNextApp shrinks by 27 lines over the PR. The new types are in CmuxNextFocusUI (KeyboardClaims, the new-workspace tickets of CreationInputBuffer, NSWindow.firstResponderTypesText) and CmuxNextFocus (FocusEvent.isPersonsOwn). Commit b1fc9a31be1 only moves five focus and key files (DecidedKeyEvents, ShortcutHintModifierPolicy, PaneNavigationHintPlan, BrowserFocusModeEscapeExit, NSResponder.ownsTypedText) from CmuxNextApp to CmuxNextFocusUI. Its only edits are the public access words and the public inits that a move across modules needs. So the fix commit can be read on its own.
Owner note. Windows/WindowController.swift is in the PL-switch area. The change is 4 lines: the focus coordinator's personIntent hook and ending the window's claim in teardown(). No switch path changes.
Commits: c65b87ae738 (red test), b1fc9a31be1 (move only), bd887fafd57 (fix), af500efe389 (test only: the keys test closes the Home's Chief Settings popover panel it made).
Testing
- Red:
HomePendingTerminalFocusTestson c65b87ae738 (feat plus the test), fleet step9324962aac1d94d400bb65c2on aws-m4pro-9,CMUX_TEST_REQUIRE_GUI=1: failed with the tests run, showing the P0.keysTypedForAPersonsNewTerminalOverHomeNeverReachTheChiefMessageBoxfound no hold, Home's message box took the keyboard, and "echo ok" went into the Chief box. - Green, first run: the same 3 suites on bd887fafd57, fleet step
f28ce3bdca7354ac9eaa15b5on aws-m4pro-7. Every test passed (HomePendingTerminalFocusTests 5/5, PersonTerminalKeyboardClaimTests 8/8, CreationInputHoldTests 6/6, 0 skipped). The suite failed only on the window-leak check: the keys test left the Home's Chief SettingsNativePopupPanelopen. af500efe389 closes it in the test's teardown. The leak check is unchanged. Green rerun on af500efe389: fleet step2549eaa093d29b23b5c0d47bon aws-m4pro-2,CMUX_TEST_REQUIRE_GUI=1: all 3 suites passed (HomePendingTerminalFocusTests, PersonTerminalKeyboardClaimTests, CreationInputHoldTests), 0 skipped, no window leak, no FLAKY, exit 0. - Compile:
cmux-ci compile-check --app --package CmuxNext --recipe-version 2on bd887fafd57, job7c2baf4ef55ccb7ebbec6281: CmuxNext, app and cmux-tui ok. On af500efe389: job706ef390c8e5167d9d836c9e: CmuxNext, app and cmux-tui ok. HomePendingTerminalFocusTestsneeds this tree's cmux-tui. It used to skip without it (skips audit row 3). Its suite trait now also runs it whenCMUX_TEST_REQUIRE_GUI=1, so a run without the binary fails atwithApp's#require(SameTreeDaemonBinary.url)and does not skip.package-test-lane.shalready counts a "needs the same-tree cmux-tui" skip as no result.
Observed / Not run
- Observed (display, at the PR head 7688e8b9a7e2): fleet step
899cd57b1887b17c35461ea7on a gui-session-v1 worker (cmuxs-Mac-mini), in cmux's console session (launchctl asuser 501),CMUX_TEST_REQUIRE_GUI=1: HomePendingTerminalFocusTests 5/5, PersonTerminalKeyboardClaimTests 8/8, CreationInputHoldTests 7/7, SecondCreationKeysTests 11/11. All 4 suites passed, exit 0 (log read withcmux-ci log; added by the ready-queue unblocker). - Observed: the compile job, the red step and the first green step above (logs read with
cmux-ci log --full), andprepush-gate.py("no new violations",app_lines -27). - Observed: the green rerun and the compile on af500efe389 (logs read).
- Not run: a P0 journey or display run of
scripted-newtab-keeps-person-keyson a feat build, and a live check in a tagged build. No perf claim.
Changelog
Fixed: Keys typed for a new terminal while Home shows no longer reach Home's message box, and a script's new workspace no longer takes the window from a person typing in a field
Proof
No capture: keyboard routing and focus behavior with no visual change. The evidence is the fleet test steps above.