Skip to content

fix(tui): preserve scrollback on content-driven full redraws - #7718

Closed
mjapple8 wants to merge 2 commits into
earendil-works:mainfrom
mjapple8:fix/tui-preserve-scrollback
Closed

fix(tui): preserve scrollback on content-driven full redraws#7718
mjapple8 wants to merge 2 commits into
earendil-works:mainfrom
mjapple8:fix/tui-preserve-scrollback

Conversation

@mjapple8

@mjapple8 mjapple8 commented Aug 6, 2026

Copy link
Copy Markdown

Problem

In normal-screen mode, pi-tui renders conversation history into the terminal's scrollback buffer so users can scroll up to read earlier output. When any rendered line above the previous viewport changes (e.g. streaming markdown re-flowing while the message grows past the visible area), TuiMainScreen.doRender() falls back to fullRender(true), which unconditionally emitted \x1b[2J\x1b[H\x1b[3J — and \x1b[3J wipes the entire scrollback.

Result: while viewing a session, the view "jumps to the top" and scrollback (conversation history, shell history) disappears; during streaming the redraw fires repeatedly so the view keeps jumping.

This is the same class of bug reported in #7304, #4626 and #5576. An earlier fix (PR #4204) targeted the old TUI class in packages/tui/src/tui.ts; it was lost during the refactor that split the renderer into tui-main-screen.ts / tui-alt-screen.ts.

Fix

Split the clear flag of fullRender into three modes:

mode behavior used for
false first render; writes everything initial frame
true clear viewport (\x1b[2J\x1b[H) but PRESERVE scrollback; write only the last height lines so earlier content isn't pushed into scrollback a second time content-driven redraws: firstChanged < prevViewportTop, height change, clear-on-shrink
"scrollback" clear viewport and scrollback (\x1b[2J\x1b[H\x1b[3J); write everything width changes only (wrapping changes make prior scrollback visually wrong)

maxLinesRendered is now reset only on a scrollback wipe; otherwise it keeps tracking growth since scrollback still holds the content.

Tests

Added packages/tui/test/tui-scrollback-preserve.test.ts with three cases (verified locally against a real xterm.js headless terminal):

  1. Preserves shell scrollback across a content-driven full redraw — pre-TUI shell history survives when a line above the viewport changes (fails on the old code: all history wiped).
  2. Does not duplicate content into scrollback on a viewport-only redraw.
  3. Clears scrollback on width change — stale wrapped scrollback is still removed when wrapping is invalidated.

When a line above the previous viewport changes (e.g. streaming markdown
re-flowing while the message has grown past the visible area), the renderer
fell back to fullRender(true), which unconditionally emitted ESC[2J ESC[H ESC[3J
and wiped the terminal's scrollback buffer. In normal-screen mode pi-tui
relies on terminal scrollback to keep conversation history scrollable, so
users reading earlier output lost it all at once.

Split fullRender's clear flag into three modes:
- false: first render, writes everything
- true: clear viewport but PRESERVE scrollback, write only the last height
  lines so earlier content is not pushed into scrollback a second time
- "scrollback": clear viewport AND scrollback, write everything (reserved
  for width changes where prior wrapping is invalid)
…edraws

Covers the content-driven full redraw path (line above the viewport
changes): pre-TUI shell history must survive, earlier content must not be
duplicated into scrollback, and width changes still clear stale scrollback.
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

This PR was auto-closed. Only contributors approved with lgtm can open PRs. Open an issue first and ask a maintainer for approval.

Maintainers review auto-closed issues daily. Issues that do not meet the quality bar in CONTRIBUTING.md will not be reopened or receive a reply.

If a maintainer replies lgtmi, your future issues will stay open. If a maintainer replies lgtm, your future issues and PRs will stay open. The command must be at the start of the reply (optionally after one or more @username mentions) or at the end.

See CONTRIBUTING.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant