fix(launch): every visible terminal shows its last frame in a reopen's first frame; launch metric is the last pane
Summary
After Cmd-Q (keep sessions) and reopen, the window's first frame showed only the first terminal with its last content; every other visible terminal pane was empty for 125-200 ms (REOPEN-F1, REOPEN VIDEO films on cmux24, 5 of 5 cycles; last content of every pane at 377-419 ms after main(), target 300 ms). The app's own mark terminal.launch_frame_shown is once-only, so it reported the first pane and hid this.
Cause. ContentPresentationScheduler makes at most one new surface (or page) per display frame, on purpose (surface creation compiles Metal pipelines on the main thread). A terminal's saved frame from the last quit (TerminalLaunchFrames, read and decoded off the main thread before NSApplication.run) was drawn only on that pane's surface, so each pane after the first stayed alpha-held (LaunchReveal) until its own surface came frames later.
Fix. A pane the scheduler keeps for a later frame shows what it has without a new surface (PresentablePane.showWhileWaiting): a visible terminal shows its saved frame as one layer in its pane (TerminalLaunchFrames.showAhead; no decode on the main thread, O(1) per pane). When its surface comes, showSelected hands the same frame to the session's frame hold in the same transaction (one frame on screen, never two or none). The one-new-surface-per-frame budget stays; nothing waits or is delayed.
Metric (rule: per visible terminal, the reopen number is the max). Each terminal of the launch's first window frame is recorded apart: debug.timings gets launch_terminals ({tab: {asked_ms, frame_shown_ms, missed}}) and launch_terminals_frame_shown_max_ms, and the new launch mark launch.visible_terminals_drawn is the time the LAST pane showed its frame (marked after first_window_frame_committed). terminal.launch_frame_shown stays as the first pane's, for older readers only. bench-startup.py, launch-connect-journey.py and scripts/journeys/ext_restore.py read the max now; the REOPEN VIDEO kit already reads these names.
App lines: the wiring adds 15 (PaneController hook, scheduler call, debug.timings rows, prefetch and close wiring; the logic is in CmuxNextTerminal), and this PR moves TerminalTargetGate (no behavior change) from CmuxNextApp to CmuxNextDaemon, which owns TerminalAttachment.Target (-56). Net app_lines falls; no exception.
Testing
Focused command (fleet): cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg=suite --arg=Packages/macOS/CmuxNext --arg=ReopenEveryTerminalFirstFrameTests (two PaneControllers come on screen in one pass with saved frames: both must show a frame in the same turn, debug.timings must report each and the max, and the waiting pane's surface must take its frame over).
- Red on base (the same test on a test-only commit c704c850 over the feat tip, fleet step 0e02a31b34cbef41d7237756): FAILS with 4 issues: the visible panes show no saved frame in the reopen's first turn, and debug.timings has no
launch_terminalsrows (shown.count 0 == 2). (Also red at 9ad3e14b, step 69f61c020a472503d1b0e1c6.) - Green at 114aa3d8 (fleet step eb6a1f1bc0f7c88f70cb6c2e): ReopenEveryTerminalFirstFrameTests passes; TerminalLaunchFrameTests 7 of 7 pass (3 new: the hand-off, the max, a void ask-ahead). Green on the head 6842ea99 (fleet step 57a439c5ee2f9776848133aa): both suites pass again (1 + 7 tests).
Observed / Not run
- Observed: the fleet red and green runs above; prepush gate (guardrails, arch ratchet: app_lines -41).
- Two earlier heads failed on the fleet and are fixed: d6259739 did not compile (PaneController lacked
import CmuxNextTerminal), 3e46c4d6's test read a main-actor constant from a Sendable closure. - Not run: a filmed reopen of this build (asked of the REOPEN VIDEO owner through STATUS; the film is the end-result proof); bench-startup.py light and STRESS before/after (perf-hot launch-first-frame); the Home page column, which is a page and still waits for its page (not in scope here).
Changelog
Fixed: After reopening cmux, every visible terminal shows its last content in the first frame, not only the focused one
Proof
No capture: the change is in the first ~200 ms after launch; the REOPEN VIDEO film of the fixed build is the proof (STATUS, REOPEN VIDEO owner).