Repository navigation
fix(restore): a terminal's last frame stays through a slow attach, the quit and a daemon restart (dogfood port) - #31851
Conversation
… and a daemon restart (red) Red tests for the dogfood-only restore fixes (cp/dogfood-v2-2-12-opt 0bb9b66, d22c177, b09da9e, 3d2b2ee, bfc0f69): - TerminalLaunchFrameTests.aLastFrameStaysUntilTheTerminalsContentDrawsHoweverLongTheAttachTakes: the hold gives up after 3 s and the pane shows its empty surface. - TerminalLaunchFrameTests.aQuitDuringASlowAttachSavesTheHeldFrameNotTheEmptySurface: the quit saves the surface under the hold, not the frame the pane shows. - TerminalRestartCarriesFrameTests: after a daemon restart (a new generation) the tab's new view shows an empty pane; a tab that shows another terminal must not show the old frame.
…e quit and a daemon restart Ports 0bb9b66 (= d70bdd0), d22c177 (= faeb78e), b09da9e (= 13c71aa), 3d2b2ee (= e0d7c39) and bfc0f69 (= 091de45) from cp/dogfood-v2-2-12-opt, onto feat's own frame hold (feat has no renderer-reported presentation or MainDeadline; the volatile frame stays in TerminalSurfaceView). - TerminalFrameHold.begin(lastFrame:) ended a held last frame after 3 s when no content had come. A relaunch whose daemon restores hundreds of terminals answers an attach seconds late, so the pane showed its empty surface (a cursor box) under "Restoring..." until the attach came back. The last frame now stays until the session's first content is parsed and drawn (Chrome keeps a tab's last frame until the new content paints); a failed link says so in the banner over it. - The quit saved each terminal's surface: a quit during a slow attach saved the empty surface under the hold, and the next launch showed that as the terminal's last frame. The hold keeps its frame typed (HeldFrame: a presented IOSurface or a decoded picture), and TerminalSession.shownFrameInBackground returns it while the hold is on screen, else the surface's snapshot. The quit's save and the hover card use it. - A daemon restart gives the terminal a new generation, so TabContentCache makes a new view of the tab's terminal, and the old view's held frame went with it: an empty pane for 0.6-4.8 s at 600 workspaces. The cache now hands the old view's shown frame (carriedFrame: the held frame, else the surface's presented frame once it drew content) to the new view (holdCarriedFrame), only when the view shows the same daemon terminal (DaemonService.terminalIdentity(of:): its term_ id or the tab's content id while the daemon still adopts it, else its surface handle; for a remote tab, its terminal and resource). A carried surface frame is reclaimed and given away (TerminalSurfaceView.giveAway), so the closed old surface never marks it volatile under the new view. - The view keys (terminalViewValidity, the terminal identity) are pure functions of the tab and the daemon, so they move from CmuxNextApp to CmuxNextDaemonService (DaemonService+TerminalViews). Tests (red at 8acaf62): TerminalLaunchFrameTests aLastFrameStaysUntilTheTerminalsContentDrawsHoweverLongTheAttachTakes, aQuitDuringASlowAttachSavesTheHeldFrameNotTheEmptySurface; TerminalRestartCarriesFrameTests.
… identity at the confirm A Cmd-D split's view is made for the provisional tab (a surface handle only) and keeps its session when the daemon's tab replaces it; its identity now follows to the daemon terminal, so a later daemon restart carries its frame too.
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Found 2 test failures on Blacksmith runners: Failures
|
|
Review (independent review subagent): FIX at 9acc0c7 Read-only review of the diff 9ce095b..9acc0c7 and the dogfood commits it ports (0bb9b66, d22c177, b09da9e, 3d2b2ee, bfc0f69). Nothing was compiled or run locally. The core port is correct: the identity check (machine prefix, then the Must fix
Should fix
Low / follow-up
Checked, no defect
|
… a closed session drops it, the tests need no sleep - The hover card returned no picture for a held session (its fresh surface shows no output yet); it now pictures the held frame, scaled to the card's size off the main thread. - TerminalSession.close() ends the frame hold: with no time limit, it went only with the object. - TerminalLaunchFrameTests checks that no timer ends the last frame (TerminalFrameHold.hasDeadline) instead of sleeping 3.5 s; anotherTerminalInTheTabDoesNotShowTheOldTerminalsFrame keeps the tab id (tabResourceID) so its assertion runs.
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Shipped in:
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/41399258629 at 00ae302 failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
Review (independent review subagent, delta): SHIP at 5747137 Read-only review of the repair delta 9acc0c7..5747137 and of the whole PR 9ce095b..5747137. The new red ref 3a160fb was also checked: it is base plus the head's tests plus a read-only Earlier FIX findings: all fixed
Nit 7 (the doc link Preview cache with
|
|
Non-green checks at 5747137, classified: |
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Summary
Ports 0bb9b66 (= d70bdd0), d22c177 (= faeb78e), b09da9e (= 13c71aa), 3d2b2ee (= e0d7c39) and bfc0f69 (= 091de45) from cp/dogfood-v2-2-12-opt (audit handoff/dogfood-only-fixes.md, row 6). Feat has its own simpler frame hold (no renderer-reported presentation, no MainDeadline), so this is a port onto feat's code, not a cherry-pick.
What the person sees on feat without it:
TerminalFrameHold.begin(lastFrame:)ended the hold aftercontentLimit(3 s). Now the last frame stays until the session's first content is parsed and drawn (Chrome keeps a tab's last frame until new content paints). A failed link still says so in the banner over it.HeldFrame: a presented IOSurface or a decoded picture) andTerminalSession.shownFrameInBackgroundreturns it while the hold is on screen, else the surface snapshot. The quit's save and the hover card use it.TabContentCachemakes a new view of each tab's terminal and the old view's frame goes with it: an empty pane for 0.6-4.8 s at 600 workspaces (dogfood bench-restore). The cache now hands the old view's shown frame to the new view (carriedFrame(for:)/showsTerminal(_:carrying:)), only when the view shows the same daemon terminal (DaemonService.terminalIdentity(of:): itsterm_id, or the tab's content id while the restarted daemon still adopts it, else its surface handle; for a remote tab, its terminal and resource). A carried surface frame is reclaimed and given away (TerminalSurfaceView.giveAway), so the closed old surface never marks it volatile under the new view.The view keys (
terminalViewValidity, the identity) are pure functions of the tab and the daemon, so they move from CmuxNextApp to CmuxNextDaemonService (DaemonService+TerminalViews.swift); CmuxNextApp lines -3. No main-thread blocking: the held surface is rendered in a detached task, asTerminalLastFrame.snapshotdoes.Testing
Head 5747137. Red ref 3a160fb (branch fix/dogfood-port-carried-frame-red: feat 9ce095b + the head's tests + the read-only
TerminalFrameHold.hasDeadlineaccessor, no fix).Focused command (fleet):
package-test-lane.sh suite Packages/macOS/CmuxNext TerminalLaunchFrameTests,TerminalRestartCarriesFrameTestsviaproof-step.py.aLastFrameStaysUntilTheTerminalsContentDrawsHoweverLongTheAttachTakes(a 3 s timer is armed),aQuitDuringASlowAttachSavesTheHeldFrameNotTheEmptySurface(no frame saved),aDaemonRestartKeepsTheTerminalsLastFrameOnScreenandaTerminalStillAdoptingKeepsItsLastFrame(the new view holds nothing).anotherTerminalInTheTabDoesNotShowTheOldTerminalsFrameis the guard against a false carry: it passes on both (it fails only if the identity check is removed).Observed / Not run
Observed: the fleet red and green runs above (Swift Testing output read per test).
Not run: a live daemon restart in a tagged build (bench-restore restart); no test carries an IOSurface frame (all carry tests use a decoded launch frame) or a remote-terminal frame; the hover card path is covered by reading only. Follow-up (review, non-blocking): a held surface on a hidden session stays non-volatile while held.
Changelog
Fixed: After a daemon restart, a slow attach or a quit during one, a terminal keeps showing its last content instead of an empty pane
Proof
No capture: the change keeps an existing frame on screen longer during a restart or slow attach; a still image cannot show the difference, and the tests above check the held frame directly.