Skip to content

Fix screen damage when the terminal is resized with an inline-image chart - #31

Merged
planadecu merged 3 commits into
mainfrom
fix/resize-stale-blank
Sep 29, 2026
Merged

planadecu merged 3 commits into
mainfrom
fix/resize-stale-blank

Conversation

@planadecu

@planadecu planadecu commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Problem

Found while reviewing glassnode-terminal PR #13, in VS Code and iTerm2 (inline images) with Ink incrementalRendering. After a terminal resize with a chart on screen, parts of the host UI were wiped and stayed wiped:

  • Width shrink (160 → 110 columns): the start of rows 8–29 was wiped: list panes, borders and the status bar.
  • Height shrink (30 → 24 rows): the data pane's bottom border and the status bar were blanked from column 82 onwards.

Root cause

  1. A stale draw after resize. When a resize settled, the render effect ran once more with the old committed size. It drew an old-size image and recorded the new terminal size for it. The next commit then saw a size change and blanked that stale area.
  2. Blanking outside the frame. The blank ran as tall as the old image. Rows below Ink's cursor line got a negative "move up", which is a no-op, so every one of those rows was written onto the cursor's own line (the host's status bar). Blank rows could also be wider than the new terminal and wrap into the start of the next row.

Fix (src/InkUPlot.tsx), revised after review

  • Skip drawing while the committed size lags the requested size. The committed update re-runs the effect at the right size.
  • Wait for Ink to write its frame before placing the image (added after review). Ink throttles frame writes, and the PNG is often ready first, so the image was placed relative to the cursor of the previous frame. That misplaced it after height changes in non-fullscreen layouts, a race that already existed on main and that the stale draw removed above had been hiding. Image writes now await useApp().waitUntilRenderFlush() (Ink ≥ 7, or one throttle interval on older Ink) before reading the anchor.
  • Blanking:
    • It's skipped only when the width shrank (Ink cleared the screen and the terminal reflowed) or a fullscreen frame's row count changed (its anchor moved). Skipping on every size change, as this PR first did, left old image cells behind after a width grow or a non-fullscreen height change.
    • It's clipped at the right edge.
    • It never targets rows below Ink's cursor line, or above the screen's top line.
  • iTerm2's 120 ms redraw skips while resizing or when the recorded size is stale.

Verification

  • New tests in test/inline-cleanup.test.tsx:

    • no blanking after the terminal size changes
    • blanking is clipped at the right edge
    • blanking never goes below the cursor line

    All three fail on main.

  • Added after review:

    • blanking still happens after a width grow and after a non-fullscreen height change
    • blanking is skipped when a fullscreen frame's row count changes
    • test/render-flush.test.tsx: no image is written until waitUntilRenderFlush resolves
  • 92 tests pass.

  • An image-aware emulator (it paints inline images as cells that text writes and erases remove, like xterm.js):

    • A non-fullscreen harness in several layouts (full-width, half-height, centred, fullscreen, and a height change on a keypress), resized both ways, in VS Code and iTerm2 modes: the image lands directly below the header at the new size, with 0 stale image cells. That includes the keypress case, which is broken on main.
  • glassnode-terminal in a pseudo-terminal plus terminal emulator (pyte), with the chart shown and the terminal resized from 160×30 to 110×30, 160×24, 200×30 and 160×40, in both VS Code and iTerm2 modes:

    • every row keeps its pane borders and the status bar is intact
    • the chart is redrawn once at the new size
    • the same checker flags damage on 0.2.14 at 110×30 and 160×24
    • also a rapid three-step resize: 0 stale image cells in every case
    • Tab, switching to table and back, typing in search, and the log view: the chart is fully visible afterwards, with no stale cells and an intact frame

planadecu and others added 3 commits September 29, 2026 23:15
…hart

Found reviewing glassnode-terminal (VS Code / iTerm2, Ink incrementalRendering):
after a resize, rows outside the chart were wiped and stayed wiped.

- When a resize settled, the render effect ran once more with the old committed
  size, stamping an old-size image recorded against the new terminal size; the
  next commit then blanked that stale area. Skip drawing while the committed
  size lags the requested one (the committed update re-runs it at the right size).
- Blanking now skips when the terminal size changed since the stamp (the
  terminal reflowed and the host repainted), never writes past the right edge
  (a wrapped blank wipes the next row), and never targets rows below Ink's
  cursor line ("moving up" a negative amount is a no-op, so those rows all
  landed on the cursor's own line, e.g. the host's status bar).
- The iTerm2 redraw interval skips while resizing or at stale coordinates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
From review of this PR:
- Ink throttles frame writes and the PNG is often ready first, so the image
  was placed relative to the cursor of the *previous* frame (misplaced after
  height changes in non-fullscreen layouts; the removed stale draw used to hide
  this race). Out-of-band draws now await useApp().waitUntilRenderFlush()
  (Ink >= 7; one throttle interval on older Ink) before reading the anchor.
- Skipping the blank on any size change left old image cells behind on width
  grow / non-fullscreen height change (Ink doesn't clear, unchanged lines are
  not rewritten). Skip only when the width shrank (Ink cleared and the terminal
  reflowed) or a fullscreen frame's row count changed (its anchor moved).
- Blank rows above the screen's top line are skipped (cursor-up clamps, so they
  all rewrote row 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
…ncremental rendering

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
@planadecu
planadecu merged commit f207aa9 into main Sep 29, 2026
5 checks passed
@planadecu
planadecu deleted the fix/resize-stale-blank branch September 29, 2026 21:54
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.

1 participant