fix(focus): pin CCmuxAppFFI to #30356's release: a sidebar AppKit picks never takes a restored terminal's keyboard (REOPEN-F4, F5)
Summary
After a relaunch, the first key a person types into the restored terminal can go to the sidebar list instead. On the feat tip (d3feb066), 10 of 15 Cmd-Q keep + reopen cycles on cmux21 lost the first key (REOPEN-F4, STATUS 4618). After a kill-all reopen, the line typed about 2 s after the terminals settled never reached the shell in 2 of 3 runs (REOPEN-F5).
The cause: the restored window comes forward before its focused terminal's surface is shown, and AppKit makes the sidebar list its first key view. The input journal of every lost cycle shows responder sidebar, programmatic 41-55 ms after the window became key. Then the focus stays on the sidebar (keyboard: false): the later contentPresented and restoredPane events do not move it back.
#30356 fixed this rule in cmux-focus-core: a sidebar responder the platform picks never takes a pane's keyboard. But the app links the reducer only through the pinned CCmuxAppFFI, and the pin (ae1499abb29) is older than #30356. The release cmux-app-ffi-382e54b7 exists (2026-11-01 07:40Z). Its bot repin #26434 stayed red because of base reds of that day (ActionCatalog counts, generated action surfaces, an xcstrings plural error), not because of the pin. So scripts/cmux-next/check-app-ffi-pin.sh has been red on every feat push since 382e54b7d14.
This PR pins CCmuxAppFFI to cmux-app-ffi-382e54b7 (the change from #26434, on today's base) and adds the Swift tests that #30356 left for its PR 2. It supersedes #26434. check-app-ffi-pin.sh --verify-release passes at the head. The only FFI input change since the old pin is #30356 itself.
Why the dogfood line passes (lead question, STATUS 4872): it has its own fix of the same fault, 99c70f714de5 (perf/chrome-parity, 2026-10-27, ShellWindow.refusesOrderInKeyView: AppKit's first-key-view pick is refused while a window is ordered in). That commit was never ported to feat. Feat fixed the fault its own way in #30356, which this PR finally brings into the app. So no feat commit broke a working path: the feat app never had either fix.
The other half of REOPEN-F4 is a key typed before the focused terminal's surface exists (the panes show in hash order, one new surface per frame). That half goes in a second PR: the focused pane is presented first, and a key for a focused terminal with no surface yet waits for it (the 1193 hold).
Testing
Regression tests, WindowFocusRestoreTests (CmuxNextAppTests):
theSidebarAppKitPicksAsARestoredWindowComesForwardNeverTakesTheRestoredTerminal: the journal's order of a lost cycle (restored record, topology, window key, programmatic sidebar responder,contentPresented). The focus must stay on the restored terminal. A mouse click on the sidebar must still move the focus there.aKillAllReopenWithAHomeColumnKeepsTheTerminalFocusedLongAfterItsSurfaceShows: the F5 shape (Home page column + one terminal). After the same events the focus and the published context must stay on the terminal.
Fleet runs (scripts/ci/package-test-lane.sh suite Packages/macOS/CmuxNext with WindowFocusRestoreTests named 5 times, through proof-step.py). The refs carry the cmux-tui/Cargo.lock of base-red fix #31654 on top (proof only; the stale lock of #31462 stops every cmux-tui build on the base):
- Green: job 4352d472a5ebfff4ada6211e at b683e9d1705 (= head 124b6680c40 + the lock file): 5 of 5 runs passed, all 3 tests each.
- Red: job b25aa3d641b88dcecb28fe52 at 3d0a0a646c3 (= test commit e2b6fe3723e on the old pin + the lock file): both new tests fail 5 of 5; the old test aRelaunchedWindowReturnsEachWorkspaceToItsFocusedPane passes 5 of 5.
- End-result proof: journey reopen-first-key (kit ff80174a568; REOPEN VIDEO owner, STATUS 4951) GREEN 6 of 6 rounds on builds with this PR + #31707 (30 Cmd-Q keep reopens, 60 marker keys each reached its terminal; 6 kill-all reopens, the typed line ran each time); base feat d3feb066 3 of 3 red.
- CI
cmux-next swift testred at the base-merge head adbd64afa75 is a base finding: on the fleet, SidebarRenameInPlaceTests, NewTabHoldCancellationTests and SSHRecoveryShotsTests fail 3 of 3 at this head (job 15cb5f66ea83c91c8b30c23d) and 3 of 3 at its feat parent f6036b45e4b (job 3789d04de3a2f758a4aad1df); ToolbarButtonsStateTests passes 3 of 3 at both (a load timing in CI).
Observed / Not run
- Observed: the 15 cycle journals in
/work/tmp/reopen-video/rounds/r2-feat-marks-quit-keep-hid-light/(kitcp/reopen-video-kit@d76e5b72c7a): lost iffresponder sidebar, programmaticcame before the key (10 of 15), kept iff the focused pane showed first (5 of 15).check-app-ffi-pin.sh --verify-releasepasses at the head.prepush-gate.py: no new violations. - Head compile (adbd64afa75): job 9b19b4c52b3df16c988d8e65 passed (CmuxNext, app, cmux-tui).
Changelog
Fixed: After cmux reopens, typing goes to the restored terminal instead of the sidebar
Proof
No capture: a focus routing fix with no visual change; the proof is the test runs and the reopen journey.