Uh oh!
There was an error while loading. Please reload this page.
fix(cli): anchor Ctrl+O/Ctrl+T expansion to the live viewport so toggles never clear scrollback - #1130
Merged
Conversation
…les never clear scrollback Expansion becomes per-entry state stamped from the expandAll defaults at creation. The global toggles retarget only entries whose first rendered line sits inside pi-tui's live viewport: entries above it live in terminal scrollback, which cannot be rewritten, so resizing one forced pi-tui's differential renderer into a full redraw that emitted ESC[3J, clearing pre-Maka scrollback and resetting the scroll position. renderMakaPiTranscript records each entry's first line and MakaPiLayoutComponent records the viewport top (monotonic max, mirroring pi-tui's previousViewportTop, which never scrolls back up short of a full redraw), so overestimation only makes the toggles more conservative. Fixes#1097
…e-anchors The monotonic-max viewport estimate never fell after pi-tui re-anchored its real viewport in a full redraw, so after /new, a session switch, a deep rewind, or a terminal resize every entry of the shorter document sat permanently "above the viewport" and Ctrl+O/Ctrl+T found nothing to toggle. MakaPiLayoutComponent now mirrors pi-tui's own rule: reset the estimate to the document tail exactly when pi-tui full-redraws (width or height change, a changed line above the current top, or a shrink below it) and keep it monotonic otherwise, so shallow truncations still hold the old top just like pi-tui's deleted-lines path does. Found by external review of #1130.
This was referenced Jul 16, 2026
Four gaps between the shadow diff and pi-tui's real doRender, all found by a focused external re-review of 992be01: - After a wholesale transcript replacement, entry positions are unknown until the next render; a toggle in that window could rewrite lines above the still-scrolled real viewport. entryFirstLine is now undefined in that state and the toggles stay inert until a render records positions. - Under Termux pi-tui does not full-redraw on height changes; it keeps the buffer and recomputes its top from it. Re-anchoring to the document tail there could fall below the real viewport after a shallow truncation. The shadow now mirrors the buffer-derived formula when TERMUX_VERSION is set. - pi-tui diffs normalized lines (Thai/Lao AM decomposition); the shadow compared raw lines and could false-reset below a viewport pi-tui kept. The shadow now stores and compares normalized lines. - The deep-truncation boundary was off by one (< vs <=, pi-tui redraws at targetRow < top), and a pure truncation clearing more than a screenful also full-redraws (extraLines > height); both now re-anchor. The geometry tests now open a top-above-tail gap first so each rule is pinned independently, plus Termux, exact-boundary, and replacement-window cases.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Jul 16, 2026
Astro-Han added a commit
that referenced
this pull request
Jul 16, 2026
…ing redraws (#1139) * fix(cli): freeze off-screen entry renders to prevent scrollback-clearing redraws (#1135) Off-screen transcript entries live in terminal scrollback, which is immutable. Five event paths mutated their rendered lines in place, forcing pi-tui's differential renderer into a full redraw (ESC[2J ESC[H ESC[3J) that clears pre-Maka scrollback and resets the scroll position: - refreshRunningShellRunElapsed: 1s ticker rewrote off-screen running cards every second - shell-run settle/ownership: applied status/result changes to off-screen Bash cards - setThinking: late thinking_complete replaced streamed text in place - tool_result poll-card splice: removed an off-screen entry, shifting subsequent line numbers - tool_progress/tool_output_delta: appended to expanded off-screen cards The fix introduces a single render-layer freeze: when an entry's first line sits above the live viewport (already recorded by #1130's renderGeometry), renderTranscriptEntryMemoized serves the cached render matching the current width instead of re-rendering. The underlying entry state still updates — only the visual output is frozen, so session switch, resume, and re-expand stay correct. The splice path gets a separate entryInLiveViewport guard: splicing an off-screen entry shifts line numbers above the viewport, which the freeze alone cannot prevent. The stale entry stays in place until the next session switch. The ticker skips off-screen running cards entirely, so refreshRunningShellRunElapsed returns false when no visible card needs an update and the 1s interval stops. * fix(cli): address review findings — freeze only fully off-screen entries Codex review found three issues in the initial freeze approach: P1: Freezing an entry whose first line is in scrollback but whose tail is still visible stopped streaming text from appearing. The invariant is that scrollback lines are immutable, not that the entire entry is frozen. Now only entries sitting entirely above the viewport (firstLine + height <= viewportTop) are frozen; straddling entries re-render normally — append-only changes fall inside the viewport, so pi-tui's firstChanged never touches scrollback. P2: The ticker skip stopped the 1s interval when all running cards were off-screen, but a resize making them visible again never restarted it. Removed the skip: the render freeze already prevents visual changes for off-screen cards, and the extra render cycle is negligible. P2: The splice guard left off-screen poll cards as stale 'running' entries. A width-change full redraw would re-render them as duplicate running cards. Now the guard also updates the entry's status to 'done' with the folded result, so a future full redraw renders it correctly. Added a streaming-past-viewport test that verifies a long assistant reply keeps appending visible content after crossing the viewport boundary, with no ESC[3J. * fix(cli): handle zero-height cache and hidden poll cards from second review Two findings from the second Codex review: 1. Zero-height cache gap: a blank thinking_delta renders zero lines; when pushed off-screen and then thinking_complete writes non-empty text, the entryHeight=0 check prevented the freeze, inserting new lines into scrollback. Now entries whose first line is above the viewport are frozen regardless of cached height. 2. Poll card duplication: off-screen Read/StopBackgroundTask poll cards that were kept in place (to avoid line-number shifts) but marked done would re-render as duplicate cards on a width-change full redraw. Now they get a hidden flag that makes them contribute zero lines, so a future full redraw skips them entirely. They are cleaned on the next session switch.
This was referenced Aug 22, 2026
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 freeto 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
Closes#1097.
A global Ctrl+O / Ctrl+T toggle resized every tool/thinking block, including ones scrolled above the live viewport. Terminal scrollback cannot be rewritten, so pi-tui's differential renderer fell back to a full redraw (
firstChanged < viewportTop) that emitsESC[2J ESC[H ESC[3J, clearing pre-Maka scrollback and resetting the user's scroll position.Since scrollback is immutable, the only correct behaviors for an off-screen block are "clear and re-emit" (the bug), "append a duplicate transcript" (worse), or "leave it untouched". This PR implements the third:
entry.expanded), stamped from theexpandAllTools/expandAllThinkingdefaults at creation; render and the memo cache read only the entry.renderMakaPiTranscriptrecords each entry's first line;MakaPiLayoutComponentrecords the viewport top in transcript coordinates (the transcript is the first composed child). The viewport top is a monotonic max, mirroring pi-tui's privatepreviousViewportTop, which never scrolls back up short of a full redraw — overestimating only makes the toggles more conservative.compositeOverlaysnever lengthens an over-viewport buffer, so overlays cannot break the estimate.The runner's "Known limit" comment is replaced by the new invariant. A follow-up upstream PR to pi-tui exposing a viewport getter can later replace the local computation with the exact value; it is not on this fix's critical path.
Verification
packages/cli:tsc --noEmitclean,npm test458/458 (9 new), rootnpm run lintclean.runMakaPiTuiwith a 24-row fake terminal: an early tool card scrolled above the viewport, Ctrl+O expands the visible card, and the whole output stream contains noESC[3J. Reverting the viewport anchoring makes this test fail, confirming it locks the regression.Review hardening
An external review pass surfaced a real regression in the first commit: the viewport-top estimate was a pure monotonic max, but pi-tui re-anchors its real viewport whenever it full-redraws, so after
/new, a session switch, a deep rewind, or a terminal resize the estimate stayed high and the toggles found nothing to target.MakaPiLayoutComponentnow shadows pi-tui's exact full-redraw rule (size change, changed line above the top, shrink below the top → re-anchor to the document tail; anything else → monotonic), withpi-tui-layout.test.tslocking all four geometry behaviors including the shallow-truncation case where pi-tui keeps its viewport and the estimate must too.