Closes every gap listed under "Lazy rendering gaps" in tui.md.
State a panel keeps in a list, dict or set (nested, depth cap 4) or a TuiStyle is watched, so an in-place mutation repaints the panel with no touch(). Layout arranges only the subtrees that asked, from the nearest ancestor with a fixed size, and a touched auto-sized panel is measured again so text that grows relays out; app.verify checks every partial pass against a full arrange. A log that scrolls moves the terminal's rows (DECSTBM, IND, RI) when an exact cost count says it writes less, and keeps each line's converted row (cap 4096), so a 3-line scroll at 200x50 writes 1.8 KB instead of 7.9 KB and a line appended while pinned costs 0.6 ms. The main loop sleeps until a timer, an animation, a half-read key or a wake() (touch from any thread, pty, SIGWINCH, quit, end of input): 5 s idle at 200x50 is 7 loop iterations and 4 ms CPU instead of 147 and 20 ms. The chat composer resizes from a TuiChange event and its spinner timer only exists during a turn, instead of 0.2 s and 0.08 s polls.
Found by the new random-tree storm and fixed: a padding, border or alignment change that left the rect the same left stale cells (it was in v1.34.0); a bordered, padded log built a pad row too wide. Known and documented, not changed: animate() on x, y, w, h cannot work because positions and sizes read ints only.
Tests: TuiObservedTests (every mutator), TuiLayoutTests (random trees, partial equals full), TuiScrollTests (VT replay, overlays, resizes), TuiLoopTests (the real _main on pipes: wakeups, latency, timers, signals); twelve mutants of the new code were each caught. VERSION is left to the pending 1.35.0 work in the same file.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Panels cache their painted cells and repaint only after touch(); any attribute write on a mounted panel is the change signal. The compositor builds a frame front to back with per-row coverage (a fully covered panel is never painted), keeps the screen it believes is shown, and recomposes and diffs only damaged rects, with a layout pass that diffs panel geometry. Anonymous dirty_paint()/dirty_layout() still drop every cache, so unaudited callers are slow, never wrong. Also: TuiStyle.key() cached, measure() memoized per layout pass, hit map built on first mouse event, damage recorded under a lock for turn threads, rect count capped at 64.
The original compositor is kept as compose_reference; TAI_TUI_VERIFY=1 checks every frame against it. New TuiLazyTests: seeded 400-step storm replayed through the VT emulator, mutation checks, thread race, laziness counters.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Leading @tags on any turn now route and pin: @provider pins the
backend, @model-id pins a model, @botname routes one turn (bare
@bot switches sticky), tags combine freely, @auto clears the pins,
and @path references a file by absolute path. Selections save into
the dir_defaults table keyed by directory and re-apply on next boot
there. Completion for all four kinds in the REPL (readline) and in
TUI textboxes (mentions= source on TuiTextBox/TuiPromptDialog).
Bump 1.22.0 -> 1.22.1.
Paste: enable CSI ? 2004 mode, parse ESC[200~...ESC[201~ into one
TuiPaste event routed to the focused panel, literal body, over-cap
pastes dropped whole. TextBox inserts with CR/CRLF normalized,
Terminal queues bytes to the pty, unmarked CRLF coalesces to one
Enter, idle flush no longer shreds slow pastes.
Complete: new TuiAutocomplete popup owned by TuiTextBox via
complete=, typing filters, arrows move, Enter accepts, Esc
dismisses. tui_complete_command feeds COMMANDS with HELP_TEXT
descriptions. Demo prompt dialog wired as the showcase.