Files
stablyai--orca/docs/reference/terminal-hidden-view-parking.md
2026-07-13 13:05:33 +08:00

6.1 KiB

Terminal Hidden View Parking

Status: Shipped — Phase 1 of the terminal model/view architecture, kill switch terminalHiddenViewParking (default on). See terminal-model-view-contract.md for the invariants this design extends and the full phase list.

Problem

Hidden terminal panes keep a full renderer xterm instance alive (buffer, scrollback, DOM, addons). At many-worktree scale this is the dominant renderer memory cost, and it forces every hidden byte through renderer-side write/skip decisions. The main-process model (daemon + runtime headless emulators) already ingests every byte and can serve restorable snapshots, so the renderer view for a long-hidden pane is redundant state.

A previous attempt shipped and was reverted the same day. The post-mortem finding: parking unmounted the pane component, which also tore down the renderer's PTY byte parsers — and those parsers are the only source of bell notifications, title-transition agent-complete notifications, and tab titles. A parked worktree whose agent finished would never notify. This design keeps those side effects alive while parked.

Design

Park policy (renderer)

A pure policy module decides which hidden terminal tabs may park:

  • Cold-park hysteresis: a tab must be hidden for 30s before parking.
  • Hot-retain working set: recently visible worktrees/tabs are retained (15 minutes; up to 8 worktrees / 12 tabs) so quick tab switches never pay a re-hydrate. The single most-recently-hidden (last-active) worktree/tab is exempt from both the retain TTL and the count cap, so switching back to the view you just left is always instant regardless of how long you were away. See terminal-cold-park-reveal-cost.md for the data behind these caps.
  • Eligibility excludes: visible panes, hidden-measuring startup probes, activity-portal panes, tabs with pending startup commands or pending activation spawns, floating-panel tabs, and any tab whose PTY is not snapshot-backed (remote-runtime remote: PTYs and SSH PTYs are excluded).
  • Kill switch: settings.terminalHiddenViewParking === false disables parking entirely.

Park mechanics

Parking a tab unmounts its TerminalPane React subtree (the overlay layer renders null for parked tabs). This is the same teardown that tab-group moves already exercise: transports detach but the PTY session, daemon model, and tab state all survive. The xterm instance, its buffers, DOM, and WebGL/addon resources are released.

Parked watcher (the piece the reverted attempt lacked)

While a tab is parked, a pane-less watcher (parked-terminal-byte-watcher.ts) keeps the pane's side effects alive. Its consumption mode is decided once at watcher start:

  • Main side-effect authority on (default): the watcher is purely fact-driven — it registers exactly one pty:sideEffect fact consumer and parses no bytes. Titles, agent working/idle/exited transitions, BEL attention, and PR links arrive as main-tracker facts and drive the same policy callbacks a mounted pane uses. With the hidden-delivery gate also on, the watcher marks the PTY hidden so main stops renderer byte delivery entirely; the DECSET 2031 color-scheme subscribe arrives as main's 2031-subscribe fact and the watcher replies out-of-band via transport.sendInput.
  • Kill switch off: the watcher subscribes to raw bytes through the dispatcher sidecar mechanism (the same mechanism background agent launches use) and runs the transport-level byte parsers with no xterm — OSC 0/1/2 titles (all-titles ordering, live-path normalization), the title-transition agent tracker (completion notification, prompt-cache timer), the OSC-aware stateful BEL detector, the GitHub PR link scan, and a dedicated DECSET 2031 byte responder (parked-terminal-mode2031-responder.ts, whose subscribeToPtyData registration doubles as the delivery-interest signal).

The two modes drive one shared policy-callback block, so flipping the kill switch never changes notification semantics. Main's synthetic agent-title/permission frames feed the main tracker directly and arrive as facts; the legacy synthetic pty:data copy exists only in kill-switch-off mode.

Out of scope while parked: OSC 52 clipboard writes. Terminal queries inside hidden-dropped chunks are answered by main's model responder (terminal-query-authority.md); in kill-switch-off byte mode only the 2031 reply is answered and Command Code output is not scraped, matching the pre-gate status quo.

Reveal

Revealing a parked tab remounts the pane subtree and rides the existing reattach path: fresh xterm via openTerminal (unicode provider activation before any write), daemon model snapshot > relay replay > cold restore precedence, replay-guarded so snapshot-embedded queries never answer, then POST_REPLAY_REATTACH_RESET hygiene, fit, and PTY resize. The watcher is disposed before the pane handlers re-register.

Invariants

  1. PTY reads never stop; parking only changes renderer-side view lifetime.
  2. Bell, agent-completion, title, and PR-link side effects keep working while parked (watcher parity tests).
  3. Reveal shows model-correct output (visual gates: hidden TUI restore, long table, rendering golden) and accepts input immediately.
  4. Sleep/wake, pane close, and PTY restart while parked must not leak watchers or strand parked state.
  5. Memory: parked tabs hold no xterm buffers; renderer memory scales with visible panes.

Relation to later phases (all shipped)

Side-effect authority in main (Phase 3) replaced the watcher's byte parsing with the pty:sideEffect fact consumer; the hidden-delivery gate (Phase 4) stops hidden byte delivery in main, moving the parked 2031 reply from the byte sidecar to the 2031-subscribe fact; the model query responder (Phase 5) answers queries in hidden-dropped chunks. The watcher's byte-parser mode survives only behind the kill switches. Parking still excludes remote-runtime and SSH PTYs (no local snapshot to restore from); the watcher would return as a byte parser only if remote-runtime tabs — whose bytes never transit local main — ever became parkable.