fix(render): force per-frame clear in alt-screen to kill log-update drift#640
Merged
Conversation
…rift ink@7.0.2's log-update tracks previousLineCount as logical newlines, not visual rows. When any rendered line wraps because its visual width exceeds terminal columns — which happens on CJK / ambiguous-width content under East Asian terminals — the clear region on the next frame undershoots the actual on-screen footprint and the bottom of the previous frame survives. The reported symptom on Windows Terminal at 120×30 with zh-CN content is a duplicated status bar (two horizontal separators stacked) and stale residue creeping up from below. shouldClearTerminalForFrame already handles the overflow case, but it gates on Yoga's outputHeight — which can itself undercount visual rows for the same width-misjudgment reason, so the fallback misses exactly the cases that trigger the bug. Fix: in alt-screen mode, take the clearTerminal path unconditionally. Alt-screen has no scrollback, so per-frame clear costs nothing visible; incrementalRendering is already off (chat.tsx:571) so we're paying the full-frame stdout cost regardless. Patched via patch-package so the fix survives reinstalls until the same shape lands upstream. Closes #639
This was referenced May 11, 2026
Closed
Merged
ChasLui
pushed a commit
to ChasLui/DeepSeek-Reasonix
that referenced
this pull request
May 23, 2026
…rift (esengine#640) ink@7.0.2's log-update tracks previousLineCount as logical newlines, not visual rows. When any rendered line wraps because its visual width exceeds terminal columns — which happens on CJK / ambiguous-width content under East Asian terminals — the clear region on the next frame undershoots the actual on-screen footprint and the bottom of the previous frame survives. The reported symptom on Windows Terminal at 120×30 with zh-CN content is a duplicated status bar (two horizontal separators stacked) and stale residue creeping up from below. shouldClearTerminalForFrame already handles the overflow case, but it gates on Yoga's outputHeight — which can itself undercount visual rows for the same width-misjudgment reason, so the fallback misses exactly the cases that trigger the bug. Fix: in alt-screen mode, take the clearTerminal path unconditionally. Alt-screen has no scrollback, so per-frame clear costs nothing visible; incrementalRendering is already off (chat.tsx:571) so we're paying the full-frame stdout cost regardless. Patched via patch-package so the fix survives reinstalls until the same shape lands upstream. Closes esengine#639
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.
Summary
ink@7.0.2'srenderInteractiveFrameso that, in alt-screen mode, every frame is preceded byclearTerminalinstead oferaseLines(previousLineCount). Alt-screen has no scrollback, so the per-frame clear is invisible;incrementalRenderingis already disabled (src/cli/commands/chat.tsx:571) so we were already paying the full-frame stdout cost.patch-package+"postinstall": "patch-package"so the diff survives reinstalls.ink@7.0.2is the current latest on npm — no upstream release to bump to.Why the existing fallback didn't catch it
shouldClearTerminalForFrameinnode_modules/ink/build/ink.js:83-102already clears whenoutputHeight > viewportRows. ButoutputHeightcomes from Yoga, which measures viastring-width's default ambiguous-width = 1. On East Asian terminals that render those characters as width 2, Yoga undercounts and the fallback never fires — exactly the case that triggers the user-visible drift.The underlying log-update bookkeeping (
node_modules/ink/build/log-update.js:52) trackspreviousLineCount = lines.length(logical newlines), so when actual visual rows > logical rows,eraseLines(N)undershoots and the top of the previous frame survives. That's what the reporter's screenshot shows: a duplicated status bar with two horizontal separators stacked at the bottom of the viewport.Closes #639
Test plan
npm run verify— 2560 passed, 2 skippednpx patch-package ink;patches/ink+7.0.2.patchis the only diff againstnode_modules/ink/build/ink.jsUpstream
Worth filing the same change against
vadimdemedes/inkonce we have the reporter's confirmation — but we ship the local patch in the meantime so users don't wait on upstream review.