The session list overflows after a handful of sessions, and its scrollbar
was drawn permanently in a column that is at rest most of the time.
SidebarRoot now tracks the pointer over the whole column and rebinds
ui-theme's scrollbar indirection pair to `transparent` while it is
outside, keeping the thumb for 2s after the pointer leaves so it does not
blink out on the way past. Rebinding colour leaves the list's
`scrollbar-gutter: stable` reservation in force, so revealing the bar
moves no row.
ui-theme's gate now states the widened contract: a rebind targets an -l2
token pair or `transparent`, and nothing else.
The goal scenario asserted a paused revision-2 goal, but the pause is
appended only after cancellation reaches idle — after turn/end. Under
parallel snapshot files the subprocess could dispose before the pause
record persisted, folding the log to an active revision-1 goal.
Add a waitForEventAfterTurnEnd input step (the turn-end/title waiters'
shape, parameterized by event type) and use it in the goal scenario to
hold the subprocess open until the goal-state record lands.
The snapshot config serialized all 13 files for every mode, but the
fixture-writing rationale only holds for record (real API quota per
scenario) and refresh (write-back harvests volatile values from
fixtures on disk). Replay — the keyless default and the CI mode — is
read-only with a unique temp dir per scenario subprocess, and the
suite factory already runs replay scenarios concurrently in-file.
Narrow the serialization to non-replay modes: pnpm test:snapshot drops
from ~49s to ~33s wall; record/refresh stay fully serial.
Resuming a long session (196k events, 2.2k steps, 1.8k tool cards) took
~12s to render and ~800ms to echo one keystroke:
- Every step's timing footer called stepTimingAt, which replayed the whole
event log per footer - O(steps x events) on the initial render.
- pi-tui re-renders every component each frame and relies on per-component
line caches, but ToolCardComponent/ContextCardComponent built throwaway
Text/Markdown instances inside render(width), re-wrapping every settled
card's output on every keystroke.
Replace the per-footer replay with one shared StepTimingTracker per chat
mount (single O(events) cursor over the append-only log), and cache card
rows by width via CardLineCache, dropped by every state mutator and
invalidate().
Measured (tmux 200x50, 196k-event session): resume prompt-ready ~12s -> ~7.6s;
per-keystroke echo ~800ms median -> ~11ms.
Round-four nits: the acp-snapshot README said 'the suite's hasPwsh probe
follows the executor's own resolution' — hasPwsh is caller-supplied, and the
resolvePwshPath-following behavior belongs to the shipped acp-agent caller;
reword to 'the caller-supplied hasPwsh probe (the shipped acp-agent suite
follows the executor's own resolution...)' on both language sides. And the
tool-bash README loses its only explicit pairing sentence with the dialect
guard reverted, so state the contract plainly: 'The tool contract is
bash-dialect — mount a bash-parsing executor' (both languages, pairing
re-recorded).
The host-variance paragraph documented posixOnly and pinsNativeWindowsStdout
but not the pwshOnly flag and SnapshotSuiteOptions.hasPwsh probe added with
the pwsh-tool-turn scenario; add the parallel sentence on both language
sides and re-record the pairing.