Skip to content

feat(web): F5 persist serialized xterm state for one-write reload restore (WI-129) - #672

Closed
thedancingdeveloper wants to merge 1 commit into
feat/terminal-attach-h2-corpusfrom
feat/terminal-attach-f5-serialize-state
Closed

feat(web): F5 persist serialized xterm state for one-write reload restore (WI-129)#672
thedancingdeveloper wants to merge 1 commit into
feat/terminal-attach-h2-corpusfrom
feat/terminal-attach-f5-serialize-state

Conversation

@thedancingdeveloper

Copy link
Copy Markdown
Contributor

Sixth PR of the terminal-attach-budget initiative (WI-121). Stacked on #671 (H2); all land on feat/terminal-attach-budget.

What this does (F5 / WI-129)

On reload a pane re-parsed its cached raw scrollback into xterm — the per-pane parse cost F1 bounds but still a full re-parse. F5 persists the pane's xterm screen + capped scrollback via @xterm/addon-serialize and restores it in a single term.write (no raw re-parse), then reattaches with the F1-bounded resume_from delta.

Cache format — backward-compatible union (deviation from the plan, explained)

The plan called for a v2 { serialized } format with a DB bump that drops v1. But terminalPrewarm.ts also writes the cache and has no xterm to serialize (it's a headless pre-warm) — it must keep writing raw bytes. So an entry now holds either a serialized screen (live pane, fast path) or raw bytes (pre-warm, live-pane fallback, and any pre-F5 entry). Raw stays a first-class restore path, which is strictly more compatible than the plan's wipe — no DB version bump or migration needed. The ArrayBuffer detection is now realm-safe.

Restore composes with F3

restoreCache() writes a serialized entry in one go, or re-parses a ground-state raw tail through the shared queue. A parked tab (F3) defers the whole entry and restores on first activation. Serialized scrollback is capped at 2000 lines (SERIALIZE_MAX_SCROLLBACK), well under the 5000-line buffer, to bound the cache entry and the restore write.

Tests

  • serializeRestore.test.ts — over the real corpus (H2), serialize → restore → reserialize is a fixed point (screen + scrollback preserved) through the same core VT parser (@xterm/headless); the cap holds. This also lands H2's deferred serialize()-compare fidelity.
  • terminalCacheRoundtrip.test.ts (fake-indexeddb) — serialized and raw entries round-trip, raw is ground-state trimmed, a malformed entry reads as cold.
  • terminalPrewarmAttach updated for the { data } payload shape.

Verification

  • pnpm typecheck clean; pnpm vitest938 passed (10 new); pnpm build bundles addon-serialize.

The H3 mocked before/after reload-budget measurement lands with H3's spec (WI-124).

🤖 Generated with Claude Code

https://claude.ai/code/session_01UtEFLJAhLiq4NfZCN2vuBb

…tore

On reload a pane re-parsed its cached raw scrollback tail into xterm — the
per-pane parse cost F1 already bounds, but still a full re-parse. F5 persists
the pane's xterm screen + a capped scrollback via @xterm/addon-serialize and
restores it in a single term.write, no raw re-parse, then reattaches with the
F1-bounded resume_from delta.

Cache format (terminalCache.ts): an entry now holds EITHER a serialized screen
(a live pane, the fast path) OR raw bytes. Raw is still written by the headless
pre-warm path (terminalPrewarm has no xterm to serialize) and as a live-pane
fallback when serialization is unavailable, and it still reads a pre-F5 entry —
so the format change needs no DB version bump or migration (a deviation from
the plan's "bump + drop v1", forced by the pre-warm path the plan did not
account for; keeping raw as a first-class shape is strictly more compatible).
The ArrayBuffer check is now realm-safe.

Restore (Terminal.tsx): restoreCache() writes a serialized entry in one go, or
re-parses a ground-state raw tail through the shared queue, and it composes with
F3 — a parked tab defers the whole entry and restores on first activation. The
serialized scrollback is capped at 2000 lines (SERIALIZE_MAX_SCROLLBACK), well
under the 5000-line buffer, to bound the cache entry and the restore write.

Tests:
- serializeRestore.test.ts: over the real corpus (H2), serialize→restore→
  reserialize is a fixed point (screen + scrollback preserved) through the same
  core VT parser (@xterm/headless); the cap holds. This also lands H2's deferred
  serialize()-compare fidelity.
- terminalCacheRoundtrip.test.ts (fake-indexeddb): serialized and raw entries
  round-trip, raw is ground-state trimmed, a malformed entry reads as cold.
- terminalPrewarmAttach updated for the { data } payload shape.

Verification: pnpm typecheck clean; pnpm vitest 938 passed (10 new); pnpm build
bundles addon-serialize. WI-129.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtEFLJAhLiq4NfZCN2vuBb
@thedancingdeveloper

Copy link
Copy Markdown
Contributor Author

Cleanup: superseded by the single promotion PR #676. All of this work is already integrated into feat/terminal-attach-budget and is reviewable there per-commit (each commit is one initiative item with a full message). Closing to keep the initiative to one PR; the commit is preserved.

@thedancingdeveloper
thedancingdeveloper deleted the feat/terminal-attach-f5-serialize-state branch September 9, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant