@@ -63,7 +63,7 @@ down the left edge (`userBubbleLines` in `src/tui/stream.ts`). Each bubble
6363keeps one empty bar row above and below its text so the operator's voice
6464stays easy to find while scrolling through denser assistant and tool rows —
6565the pad is part of the bubble itself, not an extra turn-boundary gap, and
66- assistant/tool rows are unchanged.
66+ assistant rows are unchanged; tool rows are lanes (see Tool lanes below) .
6767
6868Parent live reasoning paints through the existing thinking row — never a
6969third mid-turn stream lane. While ` inference.thinking.delta ` arrives,
@@ -75,6 +75,63 @@ same one row per turn (`reasoning-fold`); `inference.text.delta` grows the
7575open assistant streaming row in place. Worker spawn_agent-row thinking is a
7676separate path and is unchanged by this preview.
7777
78+ ## Tool lanes
79+
80+ Transcript tool rows group by tool, not by sentence. A call for a tool the
81+ previous row already represents folds onto that row instead of opening a new
82+ one (` src/tui/tool-rows.ts ` ); the row narrates the newest call's subject and,
83+ while calls are in flight, carries a dim count chip (` · ×N ` ) after the
84+ subject. ` spawn_agent ` never folds — each dispatch is its own live anchor —
85+ and ` manage_tasks ` paints no transcript row at all.
86+
87+ When the lane settles (its last outstanding answer landed), the head rewrites
88+ to a past-tense count over the latest subject — ` Grepped ×3 · "corbits" ` —
89+ with the count taken over calls, never over payload items (a lane of three
90+ greps says ` ×3 ` even if the payloads returned forty matches in total; nothing
91+ here can substantiate a payload total). Each call's own answer stays behind
92+ the expand arrow. For an edit lane the per-call ` +n/-n path ` addenda remain
93+ in the expanded body, so folding never buries which file each call touched.
94+ Single-call rows (count <= 1) render exactly as they did before lanes.
95+
96+ The past tense is a map keyed by raw tool name (` src/tui/tool-formatter.ts ` :
97+ ` grep ` → ` Grepped ` , ` read_file ` → ` Read ` , ` write_file ` → ` Wrote ` ,
98+ ` edit_file ` → ` Edited ` , ` run_shell ` → ` Ran ` , ` list_dir ` → ` Listed ` ,
99+ ` search_files ` → ` Searched ` , …); an unknown tool falls back to its display
100+ name.
101+
102+ Because a lane's row identity moves to the newest call, a lane also carries
103+ the call ids it absorbed (` memberIds ` , newest appended). A
104+ result resolves its lane when its call id is the row's own id ** or** one of
105+ its members — this is what pairs a resumed transcript's parallel batch
106+ (call, call, result, result) correctly. An id matching nothing still answers
107+ nothing: it is appended as its own row, never folded onto the newest
108+ same-name lane.
109+
110+ Alt+C on a lane copies the most recent call's full output (see the Alt+C
111+ bullet under Clipboard and mouse).
112+
113+ ### Shell output lanes
114+
115+ A pending ` run_shell ` row shows the command head and elapsed clock as today,
116+ plus up to three dim tail lines of the command's live output and — when the
117+ feed window holds more than three lines — the same dim ` ⋯ +N lines ` elision
118+ marker as the settle preview, ` N ` counting lines within the live window (the
119+ feed keeps only the most recent 8 KiB). The output
120+ travels through a polled bounded feed (` src/session/shell-output-feed.ts ` ),
121+ not a reactor event: the plugin appends chunks (capped at an 8 KiB tail per
122+ call, emitting at most once per 100 ms plus a final flush at settle), and
123+ the product host's sticky poll reads the feed snapshot every 200 ms and
124+ repaints the pending row frame-coalesced. Nothing from the feed is
125+ persisted. When the feed is not wired (tests, the demo shell), the row
126+ renders exactly as before — silent degradation.
127+
128+ At settle the live tail is replaced by a preview of the full output: its
129+ last three lines plus a dim ` ⋯ +N lines ` elision marker when more were
130+ produced (the marker carries the count; the "N lines" stat is not painted
131+ on shell rows). A non-zero exit adds an ` exit N ` stat; a zero exit adds
132+ none. The existing expand idiom (Alt+E / click / the row arrow) reveals the
133+ full output, hiding the preview; the idiom and the arrow are unchanged.
134+
78135The prompt box's border carries the metadata that would otherwise cost a
79136titlebar row: the model label sits right-aligned in the top rule as
80137` profile · model · effort ` (empty segments omitted), and a
@@ -249,7 +306,10 @@ clocks.
249306` runtime-bridge ` paints each ` spawn_agent ` call as a transcript stream row for
250307** spawn / final / fail anchors** . While the agents strip is sticky, sticky-poll
251308` syncAgentProgress ` rewrites are gated off so the transcript is not a dual live
252- rail. Ordinary in-flight tool rows keep their own elapsed clock
309+ rail. A ` spawn_agent ` call never folds into a tool lane: each dispatch keeps
310+ its own transcript row for its whole lifetime, because the row is the live
311+ progress anchor, not just a call record. Ordinary in-flight tool rows keep
312+ their own elapsed clock
253313(` syncToolElapsed ` ) without the current-tool suffix.
254314
255315### Unprompted fleet reports
@@ -725,7 +785,9 @@ running its own selection. Two chords cover remaining copy needs:
725785 (` enterCopyMode ` ) that resolves through the system clipboard port
726786 (` src/tui/system-clipboard.ts ` — a native helper binary per
727787 platform, ` pbcopy ` /` clip ` /` wl-copy ` /` xclip ` /` xsel ` , falling back to an OSC
728- 52 escape sequence when no helper is available, e.g. over SSH).
788+ 52 escape sequence when no helper is available, e.g. over SSH). On a
789+ coalesced tool lane the copy resolves to the most recent call's full
790+ output; a single-call row copies its own output, exactly as before.
729791
730792Arrow keys never scroll anything — inside the prompt they are caret motion
731793or, at the buffer's edges, prompt-history recall; inside an open overlay's
@@ -787,6 +849,10 @@ terminal. It cannot observe:
787849
788850- ** Real paint.** Tests assert on the shell's in-memory row/rect state, not
789851 on what a terminal emulator actually draws to a screen buffer.
852+ - ** The live shell tail's wall clock.** The feed itself is pure and the
853+ cadence is injectable, so the live tail is headless-testable by driving a
854+ fake feed and a fake clock through the same sync path the sticky poll
855+ uses; what the harness cannot see is real-time emission timing.
790856- ** Modifier reporting.** Whether a real terminal can report Shift+Enter,
791857 Alt+letter, or similar modifier combinations depends on the terminal
792858 negotiating the kitty keyboard protocol (or an equivalent) with the actual
0 commit comments