Tool-call cards and grouping, with the state a result never arrived in

P1b (docs/RUST.md). `transcript-ui/src/tool.rs` draws a card per tool
call and a group per run: collapsed, a card is its name and the one-line
summary `parse_tool_input` derives; open, it is the description, the
input (highlighted, on the verbatim surface) and the output, capped with
a "Show all N lines". A run is one surface with a heading and a chevron
bar at its foot, so it closes from either end.

Three things worth knowing.

**A collapsed card lays out its summary line and nothing else.** The
fixture's tool outputs are tens of kilobytes and a collapsed card never
builds a widget for one -- `collapsed_cards_shape_only_their_summary_
lines` opens a three-card group over 88 kB of output each and asserts the
text-shape count equals the same group's over three bytes (17 either
way; 17 against 20 when the discipline is deliberately broken, so the
test is real).

**A result arriving replaces one card.** `ToolRow::apply_calls` is the
group's half of `RowBlocks::apply_delta`'s rule, and `build_row` now
hands back one `TailRow` -- blocks for a message, cards for a run --
rather than two mechanisms chosen at each call site.

**Every tap is a tap**: `GestureOutcome::Tapped` out of the `DragArbiter`
`Selection` already owns, so a drag that started on a card scrolls the
transcript instead of opening it.

Three defects found by looking at the render, all recorded with their
repro in docs/IRIS_TODO.md: a `Span` of padded children inside another
`Span` places them a slot out of step (worked around by building the
group as one span, which costs the 4dp inset); `scrollable_on(Axis::X)`
on a non-editable text draws nothing, so a card's command is clipped
rather than pannable; and `NotoSans-Regular` has no U+25B8/25BE/25B4 at
all, so the expander mark is set in the monospace face.

Screenshots: docs/bench/p1b-2026-09-06/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Fable 5.1 committed 2026-09-06 22:49:51 -04:00
1 parent b332873894
commit 7e7cbb5402
13 files changed
+356 -111

No files matched your search

+45
View File
@@ -8,6 +8,51 @@ capability that moved. Small and trivial changes do not go here.
An entry gives the date, what changed, why, and a short before/after where
it helps judge the change without the session that made it. Newest first.
## 2026-09-06: tool cards, `ToolState`, and a screen that knows whether its session is working
`transcript_ui::tool` is new: a card per tool call, a group per run
(P1b). Three things in the public surface follow from it.
**`client_core::transcript_fold::ToolState`** is what a card colours
itself by -- `Running`, `Deciding`, `Succeeded`, `Failed`, `NoResult` --
built by `ToolState::of(&item, session_working)`. The pair it exists for
is `Succeeded` against `NoResult`: a call that finished having printed
nothing and a call whose result never arrived both leave an empty
`output`, and drawing them the same way states a verdict nobody reached.
Only the session's own status separates them, which is why `of` takes it.
**`event_model::Event::ToolEnd` gained `is_error`** (`#[serde(default)]`,
so an older transcript still parses), and
`client_core::transcript_fold::TranscriptItem::ToolRun` gained `failed`.
Without them a result was everything a card knew and a broken call drew
exactly as confidently as one that worked -- the missing state, not a
wrong one. Every construction site of both had to gain a field; the value
comes from the CLI's own `tool_result`, read in one place
(`import::tool_result_is_error`) by both the live translator and the
import replay.
**`TranscriptScreen::set_session_working(rsc, bool)`** is new, and is the
only thing that writes it. Before: a card with no result was drawn the
same whether its turn was still going or had been interrupted. After:
only the *newest* row can say "running", because every row behind it
belongs to a turn that has ended, and changing the flag redraws that one
row rather than the screen. `TranscriptScreen::expand_tail_tools(rsc,
bool)` joins it, answering whether there was a tool run to act on -- a
group's expanded appearance is otherwise unreachable from anything that
cannot press the screen.
**`transcript_ui::row::build_row` now returns a `TailRow`** rather than an
`Option<RowBlocks>`: `Blocks` for a message (a delta costs the last
markdown block) or `Tools` for a run (an arriving result costs one card).
One mechanism for "what can this row change cheaply", asked of the row
rather than decided again at each call site. It also takes the row's own
`working` flag.
Two smaller ones. `client_core::tool_summary::parse_tool_input` is
`ToolInput.kt`'s subject/description/timeout/rest split, and
`client_core::durations::format_millis` is `Durations.kt`'s -- both pure,
both with the Kotlin's own tests ported.
## 2026-09-06: a tap is its own gesture outcome, and opening a URL is a backend capability
Three related additions, all for following a markdown link.