Skip to content

tmux-style server-side screen model for terminals (kills the replay-artifact class for good) #231

@parsakhaz

Description

@parsakhaz

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions