# HTM Design and Architecture ## Summary HTM is a terminal multiplexer whose presentation belongs to the terminal emulator rather than to a full-screen multiplexer UI. The terminal emulator renders native tabs and splits; the persistent `htmd` daemon owns the layout model and one shell PTY per pane; the short-lived `htm` process bridges the two over tmux control mode (`tmux -CC`). The design separates terminal-specific UI from durable process state. A terminal emulator can detach, crash, or restart without terminating shells, and can reconstruct its UI from control-mode snapshot notifications. See [HTM and tmux Control Mode](htm-terminal-protocol.md) for how a terminal talks to `htm`, the [tmux Control Mode wiki](https://github.com/tmux/tmux/wiki/Control-Mode) for the protocol itself, and [`htm` to `htmd` IPC Protocol](htm-ipc-protocol.md) for the local process boundary. ## Goals - Let terminal emulators represent multiplexer tabs and splits with their own native UI, using the same control-mode dialect they already use with tmux. - Keep shell processes and layout state alive when the foreground terminal disconnects. - Carry arbitrary terminal input and output without interpreting it in the multiplexer (beyond what `capture-pane` needs). - Support Unix PTYs and Windows ConPTY behind one pane abstraction. - Recover a usable session with a small, deterministic attachment sequence. - Avoid unbounded buffering and input/output deadlocks under backpressure. ## Non-goals - HTM does not provide remote transport; Eternal Terminal's existing remote protocol is a separate layer. - HTM does not render terminal contents or split decorations. - HTM does not persist sessions across daemon or machine restarts. - HTM does not currently support multiple simultaneous UI clients for one daemon. - HTM is not a full tmux server: copy-mode, format subscriptions, linked windows, and many tmux commands are absent or no-ops. ## Architecture ```mermaid flowchart LR subgraph Emulator[Terminal emulator process] UI[Native tabs and splits] Parsers[One terminal parser per pane] Adapter[tmux -CC adapter] UI <--> Parsers UI <--> Adapter Parsers <--> Adapter end subgraph Foreground[Foreground PTY] H[htm client bridge] Raw[Raw console mode and lifecycle] H --- Raw end subgraph Daemon[Persistent per-user htmd] Server[HtmServer
single-client IPC] State[MultiplexerState
sessions, windows, panes] Server <--> State end subgraph PaneProcesses[Pane processes] P1[TerminalHandler
PTY or ConPTY] S1[Shell / application] P2[TerminalHandler
PTY or ConPTY] S2[Shell / application] P1 <--> S1 P2 <--> S2 end Adapter <-->|tmux -CC over PTY| H H <-->|local stream socket| Server State <--> P1 State <--> P2 ``` ### Terminal emulator adapter The adapter owns presentation and control-mode parsing. It launches `htm`, detects DCS `ESC P 1000 p`, reconstructs native tabs and splits from `%window-add` / `%layout-change` (or `list-windows`), and maps tmux pane IDs (`%n`) to emulator surfaces. It also owns one terminal parser per pane so escape-sequence state cannot leak between panes. This component is intentionally outside the Eternal Terminal repository for real integrations (iTerm2's built-in tmux gateway, WezTerm, Hyper's hyper-htm plugin, and so on). The repository's PTY and terminal-specific tests act as reference adapters. ### `htm`: foreground bridge `htm` is coupled to the terminal emulator's PTY lifetime. It: - configures stdin/stdout for raw virtual-terminal I/O and restores them on exit; - finds, starts, or replaces the per-user daemon; - connects to the local IPC endpoint; - writes the tmux `-CC` DCS, then forwards terminal input to `htmd` and daemon output to the terminal; - writes ST (`ESC \`) on normal or abnormal termination; - applies bounded, nonblocking queues on Unix to avoid a blocked terminal output path starving terminal input. `htm` does not own multiplexer state and can be replaced at any time. ### `htmd`: persistent daemon `htmd` owns one `HtmServer` and one `MultiplexerState`. It listens on a per-user local socket, accepts one active bridge, parses control-mode command lines, mutates the layout, and polls pane handlers for output and process exit. The daemon survives bridge disconnection. Accepting a new bridge evicts the old one and invokes recovery. It stops when the last pane closes or when `kill-server` is received. ### `MultiplexerState`: authoritative model The state model uses tmux identity and topology: - sessions (`$id`), each with a name and an ordered list of windows; - windows (`@id`), each with a name, cell size, and a split tree; - splits, stacked (`[…]`) or side-by-side (`{…}`), with proportional sizes; - panes (`%id`), each with a parent, cell geometry, `TerminalHandler`, and a libvterm `PaneScreen` for `capture-pane`. The daemon is authoritative for topology and allocates IDs. Closing a pane collapses a split when only one child remains. Layout is serialized with tmux's checksummed layout string. ### `TerminalHandler`: platform PTY abstraction Each pane has one handler and one child shell: - Unix uses `forkpty`, a nonblocking PTY master, and the user's `$SHELL` (or `/bin/sh`). - Windows uses ConPTY and `$SHELL`, `%COMSPEC%`, or `cmd.exe` in that order. The handler passes input bytes through unchanged, returns newly available output bytes, applies cell-size changes, and observes child exit. The child receives `HTM_VERSION` in its environment. ## State and data flow ### Startup and normal operation ```mermaid sequenceDiagram participant T as Terminal emulator participant H as htm participant D as htmd participant M as MultiplexerState participant P as Pane PTY / ConPTY T->>H: Start htm in a PTY H->>D: Connect or start daemon then connect H-->>T: DCS ESC P 1000 p D-->>H: Empty %begin/%end, then snapshot notifications H-->>T: Forward control-mode stream T->>H: refresh-client -C COLSxROWS T->>H: send-keys -t %pane … H->>D: Forward lines D->>M: Route bytes by pane ID M->>P: Write input bytes P-->>M: Shell output bytes M-->>D: %output %pane … D-->>H: Write lines H-->>T: Forward lines ``` The stream is asynchronous. Output from different panes can interleave at line boundaries, but bytes within a pane remain ordered. ### Detach and recovery ```mermaid stateDiagram-v2 [*] --> Running: htmd creates initial session Running --> Detached: htm disconnects Detached --> Detached: pane shells continue Detached --> Recovering: new htm connects Recovering --> Running: snapshot notifications sent Running --> Recovering: replacement htm evicts old client Running --> Stopped: final pane closes or kill-server Detached --> Stopped: final pane closes or kill-server Stopped --> [*] ``` Recovery sends current topology rather than replaying layout mutations. An adapter must treat `%window-add` / `%layout-change` (or a fresh `list-*`) as authoritative and idempotently reconcile its UI. Detached pane output is not replayed as `%output`. ## Key design decisions ### Native terminal UI via tmux -CC HTM transports topology and pane streams instead of drawing a character-cell user interface. Using tmux control mode means iTerm2, WezTerm, Hyper, and other existing `-CC` clients can attach without a private opcode set. The cost is that HTM must stay close enough to tmux for those parsers. ### tmux IDs instead of positional routing All operations address `$session`, `@window`, and `%pane`. Positions change when windows are reordered or splits collapse; IDs keep pane streams and UI surfaces correctly paired. Nested layouts serialize as tmux layout strings rather than process-local pointers. ### Snapshot recovery without output replay A layout snapshot avoids a durable mutation journal and acknowledgement protocol. Reattached UIs get windows and splits immediately. The tradeoff is that a reconnect does not resume pane output at an exact byte offset; `capture-pane` is available when an integrator needs the current screen. ### One active client Single-client ownership avoids focus arbitration, concurrent layout edits, and output acknowledgement across several UIs. Replacement semantics make recovery predictable. Collaborative viewing would require explicit client identities, mutation ordering, and per-client delivery state. ### Backpressure at the bridge Terminal emulators can stop reading while their UI thread is busy. A blocking write in `htm` must not prevent it from accepting input, or both sides can deadlock. The Unix bridge therefore queues bytes, caps queues at 256 KiB, and lets socket backpressure reach the daemon. tmux-style `pause-after` / `%extended-output` is available when a client opts in. Equivalent bounded/nonblocking behavior is an important requirement for every platform implementation. ### Compatibility version, not a private handshake `#{version}` reports `3.5a` so GUI clients enable modern `refresh-client` and layout flags. Unknown commands already return `%error`, so new commands can be added without a private capability exchange. Incompatible changes to notification syntax still need an explicit client-by-client decision. ## Invariants - Exactly one `TerminalHandler` exists for every live pane. - Every pane and split has exactly one parent; every window has one root child. - Session, window, and pane IDs are unique in their namespaces. - A split's child and size arrays have equal lengths and matching order. - Input/output bytes for a pane are routed only by that pane's ID. - The daemon, not the attached UI, is authoritative after recovery. - Notifications are never emitted inside an open `%begin`/`%end` block. ## Security and trust boundary The IPC peer can create shells, inject arbitrary keystrokes, resize terminals, and terminate panes. It is therefore equivalent to controlling the user's interactive shell session. The endpoint must remain local and accessible only within the intended user boundary. The current protocol has no authentication or integrity protection of its own. Endpoint permissions, process ownership, and operating-system local-socket security are part of the design. A future multi-user or remote transport must add authentication before reusing the message layer. ## Testing strategy The implementation is tested at complementary layers: - unit tests validate layout mutation, control-mode parsing, reconnects, pane routing, close behavior, and malformed commands; - PTY end-to-end tests run real `htm` and `htmd` processes and exercise the DCS / `%session-changed` / command stream a terminal sees; - terminal-specific opt-in tests validate native integration for iTerm2, Hyper, WezTerm, and Ghostty; - stress tests verify concurrent pane output and continued input progress when terminal output is backed up; - Windows tests cover the same portable control-mode/state behavior with ConPTY and local `AF_UNIX` transport. Any command or notification change should add a unit test, a real-process PTY test, and—when it affects UI semantics—a terminal-specific end-to-end case. ## Known limitations and evolution The largest architectural gaps are authenticated IPC, crash-persistent sessions, exact output replay positions, multi-client support, and remaining tmux surface area (copy-mode, format subscriptions, linked windows, a broader `list-commands` set). These should not be added as silent breaks of notification syntax that existing `-CC` clients already parse. A compatible evolution path is: 1. keep speaking tmux control mode and report a version string those clients understand; 2. add commands that today's clients ignore or probe via `list-commands`; 3. extend notifications only in forms known clients already accept (or behind a `refresh-client -f` flag); 4. only then introduce features that require incompatible lifecycle semantics.