Repository navigation
Conversation
When a row above the viewport changed in the same frame that the document grew, the main-screen renderer escalated to a full redraw, which clears the screen and scrollback (CSI 2J + CSI 3J) and replays the whole document. While a response streams, a running tool's spinner row sits above the viewport and ticks every 200 ms as tokens are appended, so this ran several times a second. In iTerm2, especially in a tall native-fullscreen window, history disappeared during streaming and came back only after a resize. Growth does not break the viewport mapping: the tail moves down by the appended line count, and rewriting the visible window from the clamp point sends that many line feeds through the bottom row, so the terminal scrolls as far as a plain append would. Only a shrink still needs the full redraw, because rows left the document and the terminal cannot scroll back. The per-frame viewport output is unchanged. As with the existing same-length clamp, rows that already scrolled into scrollback keep the content they had when they left the screen.
xdimtech
requested review from
MelodyVAR,
Uking-xxx,
ZouR-Ma and
liuedcson
as code owners
October 22, 2026 07:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
While a response streams in regular (main-screen) mode, terminal history disappears and only comes back after the window is resized. It reproduces reliably in iTerm2 in a tall native-fullscreen window.
Root cause: in
TuiMainScreen.doRender, the viewport clamp handles "the first changed row is above the previous viewport". When the document length also changed in that frame, it escalated tofullRender(true). That clears the screen and scrollback (\x1b[2J\x1b[H\x1b[3J) and replays the whole document.During streaming that condition holds on almost every frame. A running tool's row has scrolled above the viewport, and its spinner/elapsed clock ticks every 200 ms (
STEP_SPINNER_INTERVAL_MS) while tokens are appended below. With the headlessVirtualTerminal, 120 streamed frames with a 5 Hz row above the viewport caused 5 scrollback wipes. A long message whose first row also changes caused a wipe on every frame (29 of 30). In iTerm2 the user sees history vanish while each replay is written.\x1b[3Jalso deletes the shell output from before Step started, and that part never comes back.Changes
packages/tui/src/tui-main-screen.ts: the clamp now escalates to a full redraw only when the document shrank (or a Kitty image block straddles the clamp point, as before). It no longer escalates on any length change.Growth keeps the viewport mapping intact. The tail moves down by the appended line count, and rewriting the visible window from the clamp point sends exactly that many line feeds through the bottom row. The terminal therefore scrolls as far as a plain append would. A shrink still needs the redraw, because rows left the document and the terminal cannot scroll back.
fullRenderitself is untouched. Resizes,clearOnShrink, Ctrl+L and image fallbacks behave exactly as before.Tests (
packages/tui/test/main-screen-offscreen-change.test.ts):\x1b[3J.\x1b[3J, history still in scrollback, and the footer on the last row.docs/tui-rendering-pipeline.md: section 5, step 8 now describes the new clamp rule.Behavior and compatibility
TuiAltScreen) behavior change.Validation
All of the following ran on Linux, on top of
stepfun-ai/Step-Code@dff413f:pnpm run check: passed, and Biome left the working tree unchanged.node --test test/*.test.tsinpackages/tui: 942 passed, 0 failed.npx vitest run test/edit-tool-no-full-redraw.test.tsinapps/cli: 3 passed.pnpm test: passed (exit 0). Script tests 32/32;packages/tui942/942;apps/cli746/746;packages/coding-agent4900 passed / 55 skipped;packages/agent-core539 passed / 1 skipped;packages/providers461 passed / 6 skipped; contracts, config, telemetry and lark-adapter all passed.stepwithscripts/build-binaries.sh --platform linux-x64 --offline-model-data, installed it withinfra/release/install.sh, and ran it in a 120×40 tmux pane. The test streamed 300 lines through!shell output, then resized the pane height (40 → 28 → 45). The welcome block and the streamed rows stayed in history throughout.Limitations
\x1b[2J…\x1b[3Jreplay), and those were verified gone in xterm.js and tmux. Confirmation in iTerm2 native fullscreen is still pending.clearOnShrinkstill does a full redraw, which clears scrollback as before.🤖 Generated with Claude Code