Skip to content

fix(tui): prevent live footer redraws from polluting scrollback - #34

Merged
TaewoooPark merged 3 commits into
mainfrom
codex/fix-28-footer-scrollback
Sep 24, 2026
Merged

TaewoooPark merged 3 commits into
mainfrom
codex/fix-28-footer-scrollback

Conversation

@TaewoooPark

@TaewoooPark TaewoooPark commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

Fixes #28.

On an 80×8 terminal, a 45-line streamed reply pushed redraws into scrollback: the first reply line appeared 91 times in a built-CLI PTY reproduction. Footer rows could also exceed the viewport or terminal width, so clearing them by row count left stale content behind.

The patch reserves cleared space before painting, uses cursor movement instead of LF between footer rows, and clips the live footer to height minus one parked-cursor row. Clipped content remains live and is committed in full exactly once when settled, including when completion arrives with a resize. Large input/approval blocks keep their cursor visible; menus size their selection window to the available height. Narrow terminals use an unboxed composer, bounded prompts/placeholders, tabs expanded to four display-only spaces (original draft preserved), and a display-only fallback for a glyph wider than the viewport.

Width measurement uses pinned get-east-asian-width Unicode data (including wide emoji missing from the old ranges). MOTIF_AMBIGUOUS_WIDTH=2 is documented for terminals configured to display Ambiguous characters in two columns; the default remains one. Wide-ambiguous mode uses ASCII box decoration. @xterm/headless is a development-only dependency for physical terminal regression tests.

Validation:

  • 17 physical xterm buffer tests all fail on main 9fb0d50 and pass here: repeated streaming, cursor starting at the bottom, LF-free repaint, widths 1/2/3/4/7/8/20/80, controlled Ambiguous-width=2, oversized drafts/approval/secret prompts, selected menu visibility, resize, and completion at a resize boundary.
  • Six additional physical-buffer tab tests cover columns 1/2/3/7/20/80 and caret placement; eight added regressions in total cover display width, source preservation and terminal behavior. The final physical-buffer suite has 23 cases.
  • Visible Chrome xterm.js 6 + actual macOS PTY + built CLI + local SSE fixture: the same 45-line reply now has each line exactly once, scrollback 4083→45, and no live footer residue in history. Also checked 80×24→80×6 approval/cancel and 7-column Hangul/emoji input and cursor placement. Pasting A\tBCDE at 7 columns reproduced an extra physical row before the tab follow-up; afterward, the composer has two rows and the caret is at (6,1), with the original tab preserved.
  • pnpm test: 737 tests across 44 files. Frozen-lockfile install, typecheck, build, tool lint, and diff whitespace checks pass locally.
  • GitHub CI passes all five jobs: Node 20/22, Linux ARM64, Python, and template goldens.
  • Tests use a local dummy endpoint, not live model credentials. The wide-ambiguous test installs an independent fixture width policy; this does not claim native IME or every terminal's grapheme/font behavior.

Related: @wooksong compared #35 with this implementation and confirmed that there were no additional changes to carry over; #35 was subsequently closed. Thank you for the parallel investigation and comparison.

Integration for release 0.3.4: merged current main (including #29 and #33) into this branch, resolving the overlapping screen.ts changes while preserving #29's unchanged-footer cache/synchronized output/height invalidation and this PR's clipping/reservation/resize behavior. The resulting functional source is byte-identical to the previously tested combined build; only its version strings differ. Frozen install, typecheck, build, tool lint, and all 770 tests across 45 files pass locally. The combined build also passed eight installed-CLI PTY scenarios and visible terminal checks. All five integration CI jobs passed on commit 0de6bc29a89f49f93b8840b15ee4847afb423805 before merge.

Review-window update: the original deadline was 2026-09-25 00:30 KST (2026-09-24 15:30 UTC). With no outstanding change requests, the maintainer is wrapping up slightly earlier and preparing this fix for 0.3.4. Draft #36 is excluded from this release.

@wooksong

Copy link
Copy Markdown
Contributor

I was already working on #28 and opened #35 in parallel, so I didn't see this before opening it. Happy to close #35 if you'd rather land this one.

@TaewoooPark

Copy link
Copy Markdown
Owner Author

Thanks for working on this in parallel, @wooksong, and for being so thoughtful about it! I'm keeping both PRs open for now, as mentioned on Discord.

If you're comfortable with it, would you mind reviewing #34? If you spot any changes or tests implemented only in #35 that would be useful here, we can bring them over as additional commits to this PR. Once those parts are carried over and you're happy with the result, please feel free to close #35 yourself.

There's no rush to close it in the meantime. I really appreciate your work and would be glad to bring the useful parts of both contributions together!

@wooksong

Copy link
Copy Markdown
Contributor

I compared this with #35. This PR already covers the same problem more broadly, and the pasted-tab expansion is already in fff3354. The remaining difference is the width-calculation approach, which would conflict if brought over as-is, so I don't have anything to add.

@TaewoooPark

Copy link
Copy Markdown
Owner Author

Thank you, @wooksong, for investigating #28 in parallel and comparing #35 with this implementation. Your confirmation that there were no additional changes to carry over was very helpful.

The announced review window has not quite ended, but there are no outstanding change requests, so I am wrapping this up slightly early and merging it for 0.3.4. I resolved the overlap with the already merged #29 while preserving its redraw cache and synchronized output, then reran frozen install, typecheck, build, tool lint and all 770 tests across 45 files. The integrated functional source also matches the combined build previously exercised in real PTY/visible-terminal scenarios.

All five CI jobs pass on integration commit 0de6bc29a89f49f93b8840b15ee4847afb423805. This resolves #28. Release 0.3.4 will include #29, #33 and #34; Draft #36 remains open and is excluded.

@TaewoooPark
TaewoooPark merged commit 0f5cbb4 into main Sep 24, 2026
5 checks passed
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.

Prevent TUI footer redraws from polluting scrollback

2 participants