# Cold-park reveal cost — measured Companion to [`terminal-hidden-view-parking.md`](./terminal-hidden-view-parking.md). Quantifies the reveal latency a user pays when returning to a terminal whose hidden view was cold-parked, so the keep-warm policy is tuned from data rather than guessed. Motivated by field reports that "terminal mounting is jumpy — switching to a worktree after a while takes ~1s". ## How to reproduce ```bash pnpm bench:cold-park-reveal -- --cycles=12 # empty shell pnpm bench:cold-park-reveal -- --cycles=10 --scrollback-lines=5000 pnpm bench:cold-park-reveal -- --cycles=10 --scrollback-lines=20000 ``` The bench (`tools/benchmarks/terminal-cold-park-reveal-bench.mjs`) drives the real dev app over CDP. Each cycle times a **cold** reveal (tab confirmed parked via `window.__terminalParkingDebug.parkedTabIds()` before revealing) and a **warm** reveal (revealed inside the hot-retain window, confirmed not parked), so the cold−warm delta is the isolated cost of parking. It shrinks the 30s hysteresis with `ORCA_E2E_TERMINAL_PARKING_DELAY_MS` and roots userData under a short `~/.ocpb` path — the default `/var/folders` tmp path overruns the macOS 104-char Unix-socket limit, the daemon fails to bind, and terminals fall back to non-snapshot PTYs that never park. Phases per reveal (ms from the switch-back click): `activationMs` (store active worktree flips), `ptyBindMs` (revealed pane has a bound pty — xterm remounted and attached), `paintSettleMs` (two RAFs after ptyBind). ## Results (macOS, dev app, medians / p95) | Scrollback | warm paintSettle | cold paintSettle | **cold-park penalty** | | ---------- | ---------------- | ---------------- | --------------------- | | empty shell | 104 ms | 264 / 298 ms | **+160 ms** | | 5 000 lines | 131 ms | 318 / 423 ms | **+187 ms** | | 20 000 lines | 102 ms | 266 / 286 ms | **+164 ms** | All cold samples confirmed parked (`parkedConfirmed = N/N`); all warm samples confirmed not parked. ## Finding **The cold-park reveal penalty is ~160–190 ms and effectively flat across buffer size.** Growing scrollback 0 → 20 000 lines did not grow the penalty (the 20k run is within noise of empty). So the cost is **fixed remount overhead** — React subtree teardown/rebuild, fresh xterm construction, WebGL attach, fit, and the reattach reset — **not** daemon snapshot replay, which scales fine. Implications for tuning: - The lever is **how often a remount happens**, not how much is replayed. Widening the keep-warm working set (worktree hot-retain limit is 4, tabs 12) and never parking the last-active tab per worktree remove remounts entirely for the common switch-away-and-back, which is worth more than shaving the ~170 ms remount itself. - Pre-warming a predicted reveal (sidebar hover, jump-palette selection) hides the fixed cost behind the navigation instead of eliminating it. - This synthetic terminal is a plain shell. A live agent TUI in the alternate screen pays additional reattach-reset + mode-rehydrate work on reveal (see the Grok-scrollback reattach path), so real-world cold reveals of a busy agent pane can exceed these numbers — the ~170 ms here is a floor for the mount overhead, not a ceiling for the full experience. ## Is parking worth having? (benefit side) `tools/benchmarks/terminal-cold-park-resource-bench.mjs` (`pnpm bench:cold-park-resource`) A/Bs the `terminalHiddenViewParking` setting in one session: mount N worktree terminals, background all but one, then read resources with parking off vs on. Measured at 8 backgrounded worktrees (5 000-line scrollback each): | metric | parking off | parking on | released | | ------ | ----------- | ---------- | -------- | | mounted pane managers | 9 | 1 | **8** | | DOM nodes | 2 941 | 1 389 | **1 552** | | attached WebGL contexts | 1 | 1 | **0** | So parking's unique win is **releasing every hidden terminal's mounted view** — 8 xterm instances + their DOM subtrees (~1 550 nodes) here. At xterm's default 5 000-row scrollback, each terminal buffer alone is ≥ ~4–5 MB of typed-array cell data (12 bytes/cell × 80 cols × 5 000 rows), so the renderer-memory floor is on the order of **several MB per hidden terminal** — tens of MB across a many-worktree session, before DOM and addon overhead. Two honest caveats on the numbers: - The **WebGL-context budget is NOT parking's win.** Even with parking off, only one context was attached across 9 mounted terminals — the separate WebGL-suspend-on-hide path (`suspendRendering`) already releases hidden panes' contexts. So the #6874 context-exhaustion protection stands with or without parking; parking's benefit is renderer memory + DOM, not contexts. - The measured JS-heap delta was small (~1.7 MB) because detached xterm typed-array buffers GC lazily and the first measurement ran before they were reclaimed. The bench was upgraded to force multi-pass GC and read whole-app process memory (`window.api.memory.getSnapshot`), but a machine under disk + memory pressure could not complete the upgraded run (esbuild dev-service crashes). Re-run the upgraded bench on a healthy machine to get the settled whole-app memory figure; the pane-manager / DOM release above is the robust, reproducible part. ### Verdict Parking earns its keep for the **many-worktree power user** (the reporter's profile): it is the only mechanism that frees hidden terminals' view memory and DOM, which is the dominant renderer cost at scale. It does **not** provide the GPU-context protection (that is suspend-on-hide's job). The reveal price is a flat ~170 ms remount. Net: keep it, and tune the keep-warm caps so the common few-worktree rotation never pays the remount. ### Tuning applied Because the reveal cost is a fixed remount, the levers are all about **frequency**, not replay size. Three changes to `terminal-hidden-view-parking.ts`: - **Worktree hot-retain limit 4 → 8.** Eight covers the ordinary working set; at the ~4–5 MB renderer floor per hidden worktree that is ~16–20 MB worst case to eliminate remounts for the common rotation. Beyond 8, parking still reclaims for the heavy tail it was built for. - **Hot-retain TTL 5 min → 15 min** (worktree and tab). The cap, not the clock, is the primary evictor now; 5 min was aggressive enough that a meeting-length absence parked the whole warm set. The tab count cap stays 12 (already generous). - **Last-active exemption.** The single most-recently-hidden worktree/tab is never parked, regardless of TTL or cap, so returning to the view you just left is always instant. Implemented in `selectIdsBeyondHotRetain`; it holds one warm slot and counts against the cap. Not done (deliberately): pre-warm on predicted reveal — it hides the cost behind navigation rather than removing it and adds hover/prediction complexity; and the 30 s cold-park delay is unchanged (it is the quick-flip guard).