Problem
tea.ClearScreen (added for #236) erases the renderer cell buffer, but it does not re-enter alternate-screen / input / cursor modes. Combined with the unchanged-view skip in cursedRenderer.flush, an app cannot recover when something outside the renderer mutates the tty.
Reproduced against v2.0.5 (and the exported API is unchanged through v2.0.9):
- Program is in the alternate screen with a stable frame.
- A child (or any other writer) emits
ESC[?1049l. The physical terminal is now on the primary screen; the renderer still believes its full alt-screen frame is present.
- The next content change paints only the cell delta (a few bytes). No
1049h.
tea.ClearScreen with a byte-identical view writes nothing on a live tick (flush returns early because !starting && viewEquals(lastView, view)).
- The next changed frame after ClearScreen redraws text, still without re-entering the alternate screen.
Program.RestoreTerminal already proves the repair: renderer.start() replays modes from lastView, sets starting = true so the skip cannot fire, then flush emits a full frame. There is no in-loop equivalent that does this without leaving the alternate screen.
This is the same failure class #236 pointed at: a tool writes to the tty, the display is corrupt, and the app needs a forced redraw. ClearScreen never re-acquired modes, so it did not close that case.
Proposed API
Handled next to clearScreenMsg. The renderer, in one locked transaction:
reset() — drop cell-diff assumptions (fresh uv.TerminalRenderer)
- replay modes from
lastView (the body of start(), extracted so it can run under the existing lock)
- set
starting = true so the next flush cannot skip an unchanged view
MoveTo(0, 0) + Erase() — a fresh renderer thinks the screen is blank, so cells that are blank in our frame would otherwise leave stale content in place
A nil lastView (very early startup) is a no-op on modes plus erase.
This is not implemented by toggling View.AltScreen or writing escapes around the renderer: both leave the cell cache wrong.
ClearScreen stays as-is for wiping visual clutter in inline mode. Repaint is the recovery operation.
Tests
ClearScreen + identical alt-screen view does not emit ESC[?1049h (the neutralized-fix baseline).
Repaint + identical alt-screen view does re-enter the alternate screen and redraw the content.
- Proof is against renderer output bytes, not
Model.View(). Send+Quit cannot prove this: flush(closing=true) already bypasses the skip.
Happy to send a PR.
Problem
tea.ClearScreen(added for #236) erases the renderer cell buffer, but it does not re-enter alternate-screen / input / cursor modes. Combined with the unchanged-view skip incursedRenderer.flush, an app cannot recover when something outside the renderer mutates the tty.Reproduced against v2.0.5 (and the exported API is unchanged through v2.0.9):
ESC[?1049l. The physical terminal is now on the primary screen; the renderer still believes its full alt-screen frame is present.1049h.tea.ClearScreenwith a byte-identical view writes nothing on a live tick (flushreturns early because!starting && viewEquals(lastView, view)).Program.RestoreTerminalalready proves the repair:renderer.start()replays modes fromlastView, setsstarting = trueso the skip cannot fire, thenflushemits a full frame. There is no in-loop equivalent that does this without leaving the alternate screen.This is the same failure class #236 pointed at: a tool writes to the tty, the display is corrupt, and the app needs a forced redraw. ClearScreen never re-acquired modes, so it did not close that case.
Proposed API
Handled next to
clearScreenMsg. The renderer, in one locked transaction:reset()— drop cell-diff assumptions (freshuv.TerminalRenderer)lastView(the body ofstart(), extracted so it can run under the existing lock)starting = trueso the next flush cannot skip an unchanged viewMoveTo(0, 0)+Erase()— a fresh renderer thinks the screen is blank, so cells that are blank in our frame would otherwise leave stale content in placeA nil
lastView(very early startup) is a no-op on modes plus erase.This is not implemented by toggling
View.AltScreenor writing escapes around the renderer: both leave the cell cache wrong.ClearScreenstays as-is for wiping visual clutter in inline mode.Repaintis the recovery operation.Tests
ClearScreen+ identical alt-screen view does not emitESC[?1049h(the neutralized-fix baseline).Repaint+ identical alt-screen view does re-enter the alternate screen and redraw the content.Model.View(). Send+Quit cannot prove this:flush(closing=true)already bypasses the skip.Happy to send a PR.