follow-up to #230. that issue stops new garbage at the source; this one is the architectural fix that makes the whole artifact class impossible.
the realization
tmux repaints near-perfectly when you reattach from a different-sized client because tmux is not a recorder, it's a terminal emulator in the middle. it parses every escape sequence into a server-side screen model (cell grid + history with soft-wrap flags), and on attach it re-wraps soft-wrapped lines to the new width, pokes the live program with a resize so the prompt/TUI repaints itself, then synthesizes FRESH escape sequences from the model to paint the client. it never replays the original byte stream. redraw-from-model instead of replay-from-recording is the entire trick.
(footnote: even tmux's scrollback keeps hard-wrapped redraws from narrow sessions. nobody can reflow line breaks the shell emitted as output. but the visible screen and all soft-wrapped history restore perfectly.)
where pane stands
we're halfway there. xterm.js in the renderer IS that screen model: it tracks soft wraps and reflows them on live resizes, which is why sash drags look fine. we deviate from tmux in one place: persistence/restore replays the raw byte recording (TerminalPanel.tsx ~406-415 reset+replay, restore ~830-852), which faithfully reproduces whatever rendering was baked into the bytes.
proposal
run a headless terminal emulator in the main process per pty (@xterm/headless exists for exactly this):
- main process feeds pty output into the headless instance, which becomes the authoritative grid + scrollback
- renderers paint from serialized snapshots of that model (serialize addon works on headless) instead of replaying raw bytes, then stream the live tail
- on restore/refresh/restart: snapshot at current size, soft wraps reflow, no replay artifacts ever
- alt-screen TUIs keep the existing behavior (resize-and-let-the-program-repaint, already special-cased at
TerminalPanel.tsx ~397-404)
what makes this a real project, not a quick fix
- the raw-replay path is load-bearing today: app-restart persistence, the refresh-on-activate pattern, 50k-line scrollback
- memory cost: one headless emulator per terminal panel in the main process, scrollback lives there
- the db persistence story changes: persist the serialized model (or rebuild it from raw bytes once at startup) instead of/alongside the raw stream
- need to be careful that the renderer's live xterm and the headless model can't drift (single writer: main process)
do #230 first, it's 90% of the user-visible pain for a fraction of this effort.
follow-up to #230. that issue stops new garbage at the source; this one is the architectural fix that makes the whole artifact class impossible.
the realization
tmux repaints near-perfectly when you reattach from a different-sized client because tmux is not a recorder, it's a terminal emulator in the middle. it parses every escape sequence into a server-side screen model (cell grid + history with soft-wrap flags), and on attach it re-wraps soft-wrapped lines to the new width, pokes the live program with a resize so the prompt/TUI repaints itself, then synthesizes FRESH escape sequences from the model to paint the client. it never replays the original byte stream. redraw-from-model instead of replay-from-recording is the entire trick.
(footnote: even tmux's scrollback keeps hard-wrapped redraws from narrow sessions. nobody can reflow line breaks the shell emitted as output. but the visible screen and all soft-wrapped history restore perfectly.)
where pane stands
we're halfway there. xterm.js in the renderer IS that screen model: it tracks soft wraps and reflows them on live resizes, which is why sash drags look fine. we deviate from tmux in one place: persistence/restore replays the raw byte recording (
TerminalPanel.tsx~406-415 reset+replay, restore ~830-852), which faithfully reproduces whatever rendering was baked into the bytes.proposal
run a headless terminal emulator in the main process per pty (
@xterm/headlessexists for exactly this):TerminalPanel.tsx~397-404)what makes this a real project, not a quick fix
do #230 first, it's 90% of the user-visible pain for a fraction of this effort.