docs GitHub

Design systemDraft

Tool row

Every tool call in a transcript is one quiet line: an icon, a past-tense verb, a mono summary and what it cost. A run of calls folds into one row that opens to them. Drawn on "Session, phone and desktop", "Session · the composer, like Claude Code" and "Plan approval and modes".

SurfaceImplementing fileStatus
Deckdeck/chat/core/grouping.js, deck/chat/core/tool-detail.js, deck/chat/chat.css (work/chat)partial
Appapps/app/src/session/Rows.tsx RunRow (work/mobile)partial
Lumenlocal/capsule/native/Sources/UI/CapsuleView.swift ToolRows (work/capsule-pro)partial

Anatomy

A flex row, gap 8, no border, no fill, --text-2:

  1. Icon, 16, from the stroke set: file (Read, Edited, Wrote), terminal (Ran, Shell), search (Searched), globe (Fetched), agents (sub-agent), chev-r when the row opens to detail. Running: the spinner (14, 1.5 ring --rule-strong, arc --text-2) replaces the icon.
  2. Verb, base size: Read, Edited, Wrote, Ran, Searched, Fetched, Running, Editing.
  3. Summary, JetBrains Mono 12/16, --text-2, one line, ellipsis in the middle for paths.
  4. Right meta, pushed right, meta size: +12 −4 (--text-2), "0:18" elapsed while running (--text-2), "9 s" or "moved to background" (--label), "you · 0.2 s" for a ! line.

Folded run. Consecutive calls fold: chev-r, "Read 6 files, searched 2 patterns", right meta the total time. Running: "Running npm run build · 0:42". A run of one stays a plain row. A plan, a todo list and a Handoff row (below) never fold, alone or into a run beside ordinary tool calls: each always renders as its own line, in the flow, never absorbed into a summary.

Expanded detail. Opening a row shows, indented to the summary, a block on --code-bg with a 1 px --rule top border: the full command and output (terminal type), or the diff (see diff), or key and value pairs for other tools. A ! shell row always shows its output, capped at 12 lines with "Show all".

Variants

  • Plain (one call), folded run, running, failed, opened.
  • Shell line you ran with !: verb "Shell", meta "you · 0.2 s", then one line under the block: "Ran in ~/work/harlow-legal. kit sees the output on its next turn."
  • Handoff (a session calling team_ask or @role, ADR 0031/teammates): the teammate's own avatar tile (avatar.md), never the generic sub-agent icon, since this row is naming a specific teammate, not an anonymous sub-agent call. Summary line, left to right: verb "Asked" while it waits, "Replied" once it lands, the teammate's role name in --text 600 (not mono, it's a name: "design"), then a "Teammate" tag (chip.md, avatar.md's tile-is-never-bare rule), then the rest of the line in --text-2: "Asked design Teammate to make the intake form calmer." Always its own row (see Folded run, above: never absorbed into a run summary), always present in the flow; "collapsed" here only ever means the reply detail is shut, the row itself is never hidden. Opening it shows the reply. The one difference from every other row's detail: it's not code or a diff, so it does not sit on --code-bg in mono, it renders as plain turn prose (turn.md's Prose spec, read size, --text), because a teammate's reply is words, not a tool's output. No colour change to the row, the tile or the detail on account of which teammate answered (avatar.md, "no colour per agent"): distinct-without-colour is the tile, the name and the tag, together, every time. Right meta while waiting: nothing (no elapsed timer; a teammate's own pace is its business, not a thing to watch tick up). Failed (refused, timed out): crossed circle, meta "no answer", same as any other failed row.

Sizes

DesktopPhone
Row height28 (--control-xs)32 (--control-sm), hit area 44
Verbbase 13/18base 13/18
Summarymono 12/16mono 12/16
Detail blockmono 12/18, padding 8 12same, scrolls sideways inside

States

  • Default. --text-2, no fill.
  • Hover (desktop, rows that open). Fill --hover, radius --radius-field (8).
  • Focus. 2 px --focus outline, offset 2.
  • Running. Spinner, "Running" or "Editing", bone is never used for the text; the elapsed time counts up once a second so quiet work never looks stalled.
  • Failed. Crossed circle icon in --text, verb stays, meta "failed · exit 1". Never violet, never a border.
  • Canceled (turn stopped). Meta "stopped", icon unchanged, --label.
  • Opened. Chevron rotated 90; detail below.
  • Background. Right meta "moved to background"; the task continues in the composer's background pill.

Keyboard and touch

  • A row that opens is a button: Enter or Space toggles. Arrow keys do not move between rows.
  • Deep links (?tool=<id>) open the row and scroll it into view.
  • Phone: tap anywhere on the row; long press copies the summary.

Motion

Open and close with a --motion-panel (220) height reveal (grid rows 0fr to 1fr), chevron at --motion-tap (120). The spinner stops under reduced motion and shows a still ring.

Copy

Verbs past tense when done, present participle while running. Counts in words: "Edited 3 files, ran 2 commands". Never tool names from the SDK (no "Bash", "TodoWrite"), never capitals.

Accessibility

  • Row: button with aria-expanded when it opens, else a plain listitem.
  • Label: verb plus the full summary ("Ran npm test, 18 seconds").
  • The spinner has aria-hidden; the row carries "running" in its label.
  • Summary --text-2 on --bg passes AA at 12.

Gaps

Deck (work/chat)

  • .cv-tool is a bordered card (38 high, --panel, radius 8); spec: a borderless 28 line.
  • Failed uses --beacon-ink and a beacon border; spec: crossed circle in --text.
  • Running state word is lowercase in the signal colour; spec: spinner plus --text-2 verb.
  • Meta is 11 px; spec: 12.

App (work/mobile)

  • No icon, no elapsed timer, no detail; "Hide"/"Show" text instead of a chevron.
  • Status mark dot instead of the icon.

Lumen (work/capsule-pro)

  • SF Symbols instead of the stroke set; no folding, no detail.