fix(focus): a reopened window's focused terminal is presented first, and keys typed before its surface wait for it (REOPEN-F4)

Merged
LA
4d ago
+356-70

Summary

The first key a person types into a reopened window's focused terminal can arrive before that terminal's surface exists, and then it reaches no view. On the feat tip (d3feb066), 10 of 15 Cmd-Q keep + reopen cycles on cmux21 lost that key (REOPEN-F4, STATUS 4618). The 15 input journals show the rule: the key was kept if and only if the focused pane was the first pane the window showed.

Why it happens: a reopened window shows its panes one new surface per frame (ContentPresentationScheduler), and LayoutRootView.updateVisibility reported the panes that came on screen in a Set's hash order. So the focused terminal got the first frame in about 1 of 3 launches; in the others its surface came 100-200 ms later, after the key. Nothing held a key for a focused terminal whose surface was still to come (the hold existed only for a selection change, PaneSelectionPresenter).

Lawrence's rule (cmux-ad 02:35Z): a restored window gives the keyboard to the restored focused terminal, and any key that arrives before that is held for that terminal (the 1193 lane design) and delivered in order, never dropped.

What changes:

  • LayoutRootView reports panes that come on screen together with the focused pane first, then in layout order (never hash order).
  • ContentPresentationScheduler gives a queued focused pane the next frame's surface. The other panes keep their order and their pace (one new surface per frame); nothing else waits longer.
  • CreationInputCoordinator holds a key-down while the focus names a live terminal of the shown workspace whose surface is not shown yet. The held keys are that terminal's lane (CreationInputBuffer.awaitSurface(of:)), and they run again in order once its view has the keyboard.
  • Bound, when the surface never comes: holdLimit (2 s, as every creation hold). Then the keys go to that terminal's view if it has one, else to its daemon terminal by id (send-key, in order). A terminal that closed takes its keys with it, with a log line. They never go to another view. I chose delivery over a drop: the keys are the person's input for a terminal that exists; the daemon accepts them even with no view, as the 1193 lanes do.
  • Pays for the new App lines (arch ratchet app_lines): NoActivateKeyboardGuard (AppKit only, no App types) moves to CmuxNextFocusUI (the Focus row of the destination map). Its AppKit host and the waived NoActivateApplicationReference stay in the App. Net CmuxNextApp −94 lines.

Culprit (lead question, STATUS 4872): the dogfood line passes because it has its own fix of the sidebar half, 99c70f714de5 (ShellWindow.refusesOrderInKeyView, never ported to feat); feat's equivalent #30356 never reached the app because of the stale CCmuxAppFFI pin, which #31684 fixes. This PR reverts nothing: it covers the other half, a key that comes before the focused terminal's surface exists.

Review follow-up (SHIP at 2adbcf97731, findings 1 and 2, fixed in 785c45d9414): a Ctrl/Cmd chord that no app shortcut takes (Ctrl-C, Ctrl-L, Ctrl-R) waits too, also as the first key of an empty hold; app shortcuts (Cmd-T, Cmd-W, Cmd-Q) still act at once. A second cold terminal the focus moves to meanwhile gets its own lane.

The other half of REOPEN-F4 (the focus itself went to the sidebar list that AppKit picks as the window comes forward, because the app's CCmuxAppFFI pin predates #30356) is #31684. The two PRs are independent; both are needed for the journey.

Owner note: LayoutRootView is PL-motion-core's (OWNERS.md); the change is only the order of the presence reports.

Testing

Regression tests (committed first, 19119b7f89b, red on the base):

  • FocusedPaneShowsFirstTests (layout): six panes come on screen together, the focused one last in layout order; the reports must be the focused pane, then layout order.
  • FocusedPanePresentedFirstTests (scheduler, manual frames): the queued focused pane gets the next frame's surface; the others follow one per frame in their order.
  • ColdFocusedTerminalTypeAheadTests (real daemon): a workspace with two terminals, the right one focused, hibernates; with the frame's surface budget spent, the window shows it cold, and a line is typed in that same turn. The line must run whole and in order in the focused terminal and never reach the other one.

Fleet runs, 5 runs each (package-test-lane.sh suite, each suite named 5 times, through proof-step.py). Every ref carries the cmux-tui/Cargo.lock of base-red fix #31654 on top (proof only: the stale lock of #31462 stops the same-tree daemon the real-daemon tests need):

Observed / Not run

  • Observed: the 15 cycle journals of /work/tmp/reopen-video/rounds/r2-feat-marks-quit-keep-hid-light/ (kit cp/reopen-video-kit@d76e5b72c7a): kept iff the focused pane showed first (15 of 15). prepush-gate.py: no new violations; app_lines −94.
  • End-result proof: journey reopen-first-key (kit ff80174a568, review SHIP; REOPEN VIDEO owner, STATUS 4951) GREEN 6 of 6 rounds on builds 6a7e734413dd87dcd6be176e (785c45d9414 + #31684) and 82cfb423248c0d329e2189f2 (436e881ddea + #31684): 30 Cmd-Q keep reopens with 60 marker keys, each reached its terminal once; 6 kill-all reopens (REOPEN-F5), the line typed 2 s after the terminals showed ran each time. Base feat d3feb066: 3 of 3 red.
  • Not run: the key-dispatch bench before/after (review note 4: each key-down now asks awaitedSurface; its common case is two dictionary reads and a tab compare).
  • Not covered: a key typed before the reopened window is key at all (no key window: AppKit delivers it to no window). No lost cycle shows this (every key came 60-120 ms after the window became key).

Changelog

Fixed: The first keys typed into a reopened window reach its focused terminal instead of being lost

Proof

No capture: input routing with no visual change; the proof is the test runs and the reopen journey.