| 1 | # Terminal runtime |
| 2 | |
| 3 | The environment server owns PTYs, session lifetime, and retained output. Every |
| 4 | client, including the desktop renderer, attaches through the environment connection. |
| 5 | This lets clients reconnect or share a running session. Renderer choices stay local |
| 6 | to each client and do not change terminal contracts. |
| 7 | |
| 8 | Terminal I/O stays independent of chat traffic so stalled streams and queued |
| 9 | compression cannot block keystrokes. The native desktop's own backend uses a |
| 10 | MessagePort and inherited pipes; other environments use a lazy second WebSocket. |
| 11 | Both paths share terminal handlers, ticket authorization, and session supervision. |
| 12 | Metadata stays on the primary connection so an idle sidebar opens neither path. |
| 13 | WSL uses WebSockets because its launcher does not preserve inherited descriptors. |
| 14 | |
| 15 | ## Output and retention |
| 16 | |
| 17 | [Terminal history](../../apps/server/src/terminal/Manager.ts) is incremental. |
| 18 | PTY callbacks append new chunks; live events carry only those chunks. Materializing |
| 19 | or copying full scrollback on every callback makes output cost grow with retained |
| 20 | history, so snapshots and coalesced persistence are the materialization boundaries. |
| 21 | Persistence queues the mutable history buffer and reads its latest value when the |
| 22 | write runs. Clear, restart, and close must drain writes before completing their |
| 23 | lifecycle boundary. |
| 24 | |
| 25 | Server history is capped at 5,000 lines and 8 MiB of UTF-8 text per terminal, so a |
| 26 | long unterminated line cannot bypass retention. Eviction removes the oldest output |
| 27 | without splitting Unicode code points; live output is not truncated. Release |
| 28 | discarded chunk references immediately, even if array compaction happens later. |
| 29 | Client buffers have a separate 512 KiB cap. Measure throughput with full scrollback |
| 30 | when changing this path. |
| 31 | |
| 32 | Restoration must read only the bounded tail of current or legacy history files, |
| 33 | skip any incomplete UTF-8 prefix, and apply the line limit. Close the read handle |
| 34 | before rewriting the capped file. Reading whole old logs would defeat the memory |
| 35 | bound during startup. |
| 36 | |
| 37 | ## Renderer ownership |
| 38 | |
| 39 | Android keeps its native libghostty-vt adapter. Web uses the vendored |
| 40 | `@clo/terminal` package, whose Ghostty ABI pin and engine artifact live together in |
| 41 | [`packages/terminal/vendor`](../../packages/terminal/vendor). Its DOM renderer owns |
| 42 | frames and native selection; the T3 adapter owns environment transport and local |
| 43 | interaction policy. One WebAssembly instance serves each browser tab, with separate |
| 44 | terminal handles freed on disposal. The Android pin remains independent at |
| 45 | [`native/libghostty-vt/VERSION`](../../native/libghostty-vt/VERSION). |
| 46 | |
| 47 | Restoring scrollback must not send terminal replies to the current shell. Historical |
| 48 | device queries can otherwise provoke fresh replies that appear as junk at the |
| 49 | prompt. The server strips query/response traffic from retained history, and the |
| 50 | [web adapter](../../apps/web/src/terminal/TerminalSurface.ts) suppresses its PTY data |
| 51 | sink during replay. Preserve both protections when changing retention or renderer code. |