A full-frame rewrite belongs to incoming data or to something the user did — a
scroll, a keystroke, a resize. It has no business being on a metronome. The 1 Hz
refresh exists for the header alone (the activity pulse, the battery sweep, the
low-battery blink), and on an emoji-bearing screen it was dropping pt's cached
frame every tick, which erases the terminal and redraws all of it to move one
braille cell in the corner.
The requirement a mis-measured glyph imposes is narrower than the answer was.
prompt_toolkit's cursor is *relative*, and the drift is confined to a row: a
width-2 glyph the terminal draws in one cell leaves everything right of it on that
row a column left of pt's model, so a later paint that jumps into the row writes
one past the content it meant to overwrite. But stepping down a row is a carriage
return — pt writes "\r\n" and then counts forward from a true column 0 — so the
misalignment can never reach the row below. Rewriting a row whole, from column 0,
is the entire fix; erasing the screen was never part of it.
So the frame's changed rows are rewritten, and nothing else is: the background
composes one line per terminal row, so its own diff *is* the row list. A ticking
header repaints row 0 and leaves the board under it alone. An idle frame changes
no rows at all.
Two cases still fall back to the whole frame, both of them user-driven and
occasional rather than timed: a float, whose centred box the layout places (so its
lines map to no rows we know) — a dialog opening, changing, or closing — and a
frame whose height moved, where no row mapping holds at all.
Verified against pt's own renderer, and on a composed Trophy case: 24 lines for 24
rows, and a pulse tick reports exactly row 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>