Files
orca/tests/e2e
Neil 98299d879b fix(terminal): persist a parked remote pane's scrollback across a hard restart (#21295) (#21367)
* fix(terminal): route a parked pane's scrollback patch to the remote host's partition

A park capture changes only terminalLayoutsByTabId, so its debounced session
patch carries no tabsByWorktree. splitWorkspaceSessionByHost built its
tab->worktree index from the patch alone, resolved nothing, and routed every
layout to the 'local' partition, where main's pruneLocalTerminalScrollbackBuffers
strips scrollback it cannot attribute to a remote worktree. The remote host's
runtime:<id> partition never received the capture, so anything parked since the
last clean checkpoint was lost on a crash, SIGKILL, or a forced kill during an
app update (#21295).

Route tab-keyed patch fields with the renderer's live tab catalogs as a fallback
when the payload names no tab rows. Payload rows still win, so full-payload
writes are byte-identical. Once routed to runtime:<id>, main merges the
partition's own prior tabsByWorktree and the prune preserves.

Proven by tests/e2e/paired-remote-terminal-parked-scrollback-restart.spec.ts: a
hard kill (no checkpoint) then relaunch, asserting the capture is in the remote
host's partition on disk. Mutation: reverting the routing fix turns that
assertion red and fails the 3 catalog-dependent unit routing tests.

(cherry picked from commit 58a344c1d4)

* test(terminal): read both scrollback homes in the restart spec, and ratchet the resolver to the cap's home list

The restart spec read only buffersByLeafId, but the ordinary park now writes
localOnlyScrollbackByTabId, so its own proof reported a false zero and both tests failed for the
wrong reason. Both readers now go through resolveLeafScrollbackBuffers: the on-disk reader calls
it directly (it is a pure function), and the store reader — which runs inside page.evaluate —
reaches it through a new window.__terminalParkingDebug.resolveLeafScrollback(tabId) handle.

resolveTabScrollbackBuffers is typed off TERMINAL_SCROLLBACK_SESSION_HOMES and its unit test
enumerates that constant, so adding a third home fails to compile and fails a test until the
resolver reads it — the 'no consumer reads a home directly' invariant becomes enforceable.

The clean-quit control no longer asserts tokenAfterReveal (measured true, true, false on identical
product code; the live host can serve the reveal from its own tail). It keeps the five
deterministic fields and logs the reveal; the hard-kill test still asserts it, because there the
host is forced unavailable and the reveal must come from the client copy.

(cherry picked from commit 0cd3db1489)

* docs(persistence): pin why the local-only scrollback home stays outside full normalization

The two scrollback homes look symmetric (TERMINAL_SCROLLBACK_SESSION_HOMES), so the missing key
reads as an oversight. It is load-bearing: adding it would route the field through the fail-closed
strip and reintroduce the loss this branch fixes. The renderer prunes it with attribution before
the patch is sent, so the cap still holds without main as a second line.

(cherry picked from commit 1092e35b0a)
2026-09-20 22:57:33 -07:00
..
2026-06-10 17:23:12 -07:00