- Rules first in logging.md: R1-R12 plus a 27-event registry, every
site validated against the source before implementation.
- DebugLog: file/stdout/stderr sinks, sentence/json formats,
wipe-on-first-write for roots, append for TAI_CHILD, per-row
commit, prune to 20000 rows, never raises.
- Vault logs table with six indexes plus FTS triggers, sealed at
rest, read back through Store.log_query.
- Hooks: turns, completions (backend, model, ms, tokens, texts),
tool dispatch, approvals, vault mutations, scheduler, serve,
CAP turns, telegram updates, uncaught exceptions.
- Secrets never logged: redact plus LOG_MASKED_ARGS.
- 13 new tests (1222 + 16 green).
The loop now sleeps until woken (no 1 s heartbeat); SIGCHLD and a drained pty writer wake it, the latter also fixes a busy writer callback. Partial layout no longer climbs the whole tree for all-auto panels: arrange() skips panels whose box is unchanged and that nothing under them asked about (a pending flag that survives hidden ancestors), measured() keeps measures until a touch drops them, and only really arranged panels are diffed; one leaf growing in a 301-panel all-auto tree costs 0.29 ms instead of 4.6 ms. A scroll offset written out of range arranges that panel for its clamp. TuiWatched is the base for state objects (TuiStyle and TuiSpan are ones): a write to a public field touches every panel holding it, directly or through a watched list, dict or set. TuiTerminal keeps converted screen rows keyed by content (200x50 scroll-by-one frame 16.0 ms to 1.8 ms). The terminal scroll uses left/right margins (DECLRMM) when the startup DECRQM probe says the terminal has them, so the sidebar beside a transcript is not touched (3-line scroll at 80x24: 1354 B to 734 B in the emulator); TuiVtScreen implements the margins and answers DECRQM. The scroll cost model counts runs and style switches the way diff_row writes them and its constants were swept over 1200 recorded frames. animate() of x, y, w, h now starts from the drawn place and writes whole cells.
Not verified on a real terminal: the margin path (tmux has no DECLRMM). Remaining gaps are listed in tui.md.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Each bash/zsh/fish instance resolves a stable dated id (no install hook);
transcripts and turn state scope per session so every new shell starts
fresh, while vault data stays shared. Parent links accrete in the
shell_sessions table with stub rows for outer shells, and /ctx renders
the ancestor path plus children. TAI_SESSION pins or overrides the scope.
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>
Copy mode was compared with the installed tmux 3.6 by differential fuzzing and 842 recorded results instead of being written from memory: the cursor stands between characters and remembers its column (lastcx/lastsx), pages are rows - 2, searches land at the match end for emacs keys and the match start for vi keys and scroll by tmux's rule, a failed search is a status message, emacs selections are half open and vi selections include the cursor character and the line end, select-word on blanks takes the previous word, copy-line and copy-end-of-line start a fresh selection. Window selection keeps a history (last, then previous with wrap when the current window dies), swap-window keeps the current window current, new-session -d -x -y sizes the window as given. Server loop logs and survives a failing iteration (50 in a row end it).
mplex.md is the specification (architecture, wire format, caps, every option, command and default key, what was checked against tmux, known differences); MplexSpecTests fails when it drifts from the code. New: MplexCopyModeVersusTmuxTests (tmux-recorded fixtures), MplexSessionTests, MplexServerLoopTests.
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>
- New ask_user core tool: mid-turn user question with option picking,
refusing cleanly in autonomous mode, for background workers, and
without a tty; system prompt points at it instead of prose questions.
- Structural loop control for prompt-JSON CLI backends (opencode,
grok, claude, codex, gemini): shared cli_json_turn repairs a
missing JSON envelope exactly once, then accepts prose; prompt
contract hardened against narrate-without-calling.
- research/agentic-tool-loop-design: 74 sources plus 2 conclusions
backing the loop-control decisions; INDEX updated to 6 subjects.
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.
Selective staging after the banner commit shifted hunk offsets and
this hunk fell outside the cutoff. It completes the previous commit:
without it the paste path references a missing name.
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.