fix(desktop): garbage-collect persisted terminal scrollback in localStorage
Problem
Terminal scrollback is persisted to renderer localStorage (terminal-buffer:<id> + terminal-dims:<id>) so panes can restore across remounts and relaunches. Those entries were only deleted on an explicit terminal kill (terminalRuntimeRegistry.dispose()). Terminals that disappear any other way — PTY exit, workspace deleted, host reset, crash, org switch — leaked their snapshot forever.
Measured on a real install after ~2 months: 945 orphaned buffers / 23.7 MB, enough that Chromium's synchronous localStorage load wedged the renderer (652% CPU, 6.31 GB RSS, unresponsive AX queries).
Fix (two layers)
1. Destroyed PTY ⇒ scrollback deleted, immediately.
- host-service tags attach errors for permanently-destroyed sessions (not found / disposed / exited) with
code: "session-gone"on the existing error message (additive, version-skew safe). - The renderer transport tracks that signal plus live
exitmessages assessionEnded, distinct from_terminated(which also covers access denials where the PTY may still be alive). The signal clears the persisted snapshot on the spot, and every persistence path — detach, release, parked eviction — clears instead of re-persisting a dead session's buffer. A successful re-attach resets the flag (session re-created under the same id).
2. Boot-time GC bounds the store no matter how a terminal dies.
- Every persist/restore stamps the terminal in a single
terminal-buffer-persisted-atindex. - At renderer boot, before any terminal mounts,
pruneExpiredTerminalState()removes: unstamped entries (all pre-existing bloat, cleaned on first launch after this ships — a one-time scrollback-restore loss), entries older than a 14-day TTL (covers deaths the renderer never observes, e.g. workspace deleted while the app is closed), and oldest entries beyond a 2M-char (~4 MB) total budget.
Pruning deliberately does not cross-reference pane layouts: v2WorkspaceLocalState is org-scoped and cache-first, so reading it for GC could delete valid buffers for evicted orgs / not-ready collections.
Relationship to #5965
Complementary, not overlapping fixes: #5965 guards the TanStack-DB localStorage collections write path against the QuotaExceededError rollback→retry loop once the store is already full, and reclaims orphaned snapshots only reactively on that quota path. This PR stops the terminal-snapshot store from growing unbounded in the first place and ties snapshot lifetime to PTY lifetime. Both touch terminal-runtime(-registry).ts, so whichever lands second needs a small rebase.
Testing
terminal-buffer-gc.test.ts: legacy pruning, TTL, size-budget eviction order, index hygiene, dims-only orphans, corrupt-index recovery. Core tests verified to fail when their behavior is mutated out.terminal-ws-transport.test.ts: exit ⇒sessionEnded+ callback fired once;session-goneattach error ⇒ same; plain error ⇒ untouched; re-attach resets.terminal-runtime-registry.test.ts: release of an ended session disposes withclearand never serializes.- Full terminal lib suite: 304 pass.
bun run typecheck --filter=desktop --filter=@superset/host-serviceandbun run lintclean.
Summary by CodeRabbit
- New Features
- Added automatic garbage collection for persisted terminal scrollback and dimensions at desktop startup, including TTL-based pruning and a size cap.
- Introduced persisted-state tracking so terminal history retention is updated as terminals are persisted, restored, or cleared.
- Bug Fixes
- Improved session-ended handling to reliably clear persisted terminal state and avoid retaining snapshots after terminal termination.
- Enhanced persistence cleanup to remove stale entries and keep the persisted-at index consistent.
- Improved robustness when persisted GC index data is missing or corrupted.