Ink (React CLI framework) clears its output using repeated EL2+CUU1
sequences, one per previously-drawn line. When /clear resets Ink's
internal line counter, the next frame only erases a few lines instead
of the full previous output. In a real terminal the old content is in
scrollback and invisible, but pyte's fixed-size screen retains it,
producing ghost content (e.g. duplicated prompts) in SVG screenshots.
Added AltScreen.expand_clear_sequences() which detects runs of 3+
EL2+CUU1 pairs that don't reach row 0 and extends them to erase all
lines up to the top of the screen. Both DockerExecSession and
TerminalSession call this before feeding data to pyte.
Also made on_session_end() idempotent (contextlib.suppress KeyError)
to prevent a race when close_session() and natural session exit both
call it.
Added docs/ink-clear-fix.md with root cause analysis, byte-level
explanation, and reproduction script.
- close_session() now calls on_session_end() to remove the session from
sessions dict and routes, preventing zombie entries that persist after
the container is gone
- _remove_container() now closes the active session before removing the
app from apps_by_slug/apps, so session cleanup can still reference the
app during teardown
- Updated and added tests to verify session tracking cleanup
Allows per-container customization of the auto command. For example:
WEBTERM_DOCKER_AUTO_COMMAND='tmux new-session -ADs {container}'
This creates a tmux session named after the container instead of using
a fixed session name for all containers.
The replay buffer can contain DA1/DA2 terminal attribute responses
(e.g., \x1b[?1;10;0c) that were captured before filtering was added
to the session classes. These responses appear as visible text like
'1;10;0c' when sent to the client on reconnect.
This adds an additional filter pass when sending the replay buffer,
ensuring no DA1 responses reach the client regardless of when they
were captured.