gitstack
closed

Inline mode leaves stale composer rows ("ghosts") when the terminal grows taller

#119
cnzhanglu · 2026-08-06T02:01:12Z view on GitHub

Summary

In inline mode (WithInlineHeight), resizing the terminal window taller than the viewport height leaves stale composer rows above the new viewport — the old input band stays on screen as "ghosts" until it scrolls away.

Reproduction

  1. Run a go-tui app in inline mode (e.g. a bottom-pinned input widget).
  2. Let some content print above the widget so the viewport is at the bottom of a N-row terminal.
  3. Grow the terminal by more than the inline height (drag the window edge).
  4. The old widget rows (input line etc.) remain visible above the newly positioned widget.

Root cause

On a resize, renderInline's full redraw does SetCursor(0, inlineStartRow); ClearToEnd() — it only clears from the new viewport start row downward. When the terminal grows, the old viewport rows lie above the new start row and are never erased. Each growth step leaves another stale band, so dragging produces many ghosts.

Additionally, renderFrame only re-syncs inline geometry when the buffer width changes; when only the height changes (and the ResizeEvent is still queued), the composer can be drawn at a stale row for a frame.

Proposed fix

  • Clear from min(prevInlineStartRow, inlineStartRow) (the union of old/new viewport top rows) before repainting.
  • Re-sync inline geometry against the live terminal size on every frame, forcing a full redraw whenever it differs.

This mirrors how Codex (openai/codex) handles inline viewports on resize: clear_after_position(min(old.y, new.y)) then full repaint.

I have a fix + tests ready in PR #120.

grindlemire grindlemire · 2026-08-05T23:48:07Z

Thanks for the report! Looking into it.

grindlemire grindlemire · 2026-08-06T01:15:10Z

Thanks for the report and the analysis!

I fixed this in #123 instead of merging #120. It uses your core idea: on the next full redraw, clear from the old start row instead of the new one, so the stale band gets erased. The implementation is smaller, though. A one-shot marker recorded during resize handling, with no new constructor wiring, which sidesteps an initialization edge case in the prevInlineStartRow approach.

#123 also fixes the related #122 (negative start row on terminals shorter than the inline height).

grindlemire grindlemire · 2026-08-06T02:01:12Z

I rolled the other fix you had into main via #123. I'll cut a new release with these changes