@lotics/ui 5.3.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -75,16 +75,18 @@ Pick by capability, not by name. (→ the source file for the API.)
75
75
  - **Files** — `FileDropzone`, `FileThumbnail` / `FileThumbnailGrid`, `FilePreview` /
76
76
  `FileGalleryModal`, `ImageGallery`; for gated CRUD compose locally with `useSelectionMode` +
77
77
  `shareOrDownloadFiles` + `rotateImageToBlob` (see §Data entry → Attachments).
78
- - **Specialized work surfaces** — `ScanField` (scan/verify), `StepList` (guided run),
79
- `RemainderMeter` + `AllocationRow` (allocation), `Timeline`, `Calendar`, `Gantt`,
80
- `comments_thread`.
78
+ - **Specialized work surfaces** — `ScanField` (scan/verify), `Stepper` (a guided run / progress
79
+ sequence done · current · upcoming, horizontal OR vertical), `RemainderMeter` + `AllocationRow`
80
+ (allocation), `Timeline` (a heterogeneous event LOG — icons + expandable details, not progress),
81
+ `Calendar`, `Gantt`, `comments_thread`.
81
82
  - **AI surfaces** — `Composer` (the adaptive command/chat composer — a compact pill when empty that
82
83
  expands for long text + attachments; the surface that triggers agent work),
83
84
  `AgentRun` (the live streaming work feed) + `AgentProgress` (its compact, floating, expandable
84
85
  form — a composer's "working" state), `ReviewCard` (THE single-proposal review card — compositional
85
86
  body via `children`: a `RecordFields` record (header-first) or a `MatchSides` pairing (body-first))
86
- + `Confidence`; `ChangeReview` (the BATCH review engine — whole-set OR 1-by-1; compositional via
87
- `renderItem`, with `ChangeDiff` for the before→after body),
87
+ + `Confidence`; `ChangeReview` (the BATCH review list — whole-set OR 1-by-1; compositional via
88
+ `renderItem`, with `ChangeDiff` for the before→after body) + `ChangeReviewActions` (its commit bar,
89
+ pinned by the host in a `DialogFooter`/`DrawerFooter` or inline),
88
90
  `Clarify` (the agent asks back — selectable `ChoiceList` options),
89
91
  `Sources` (provenance chips for AI output). For STRUCTURED AI output the review
90
92
  surfaces above don't cover: `SpecList` (the "exact breakdown" — a labeled-value spec sheet, the
@@ -263,9 +265,13 @@ Compose the surfaces as a loop, and reach for the right one by job:
263
265
  attaching HOLDS the file for review; the human presses Send. The canvas (`tpl_dieline`) uses it this
264
266
  way; while running the host swaps `Composer` for `AgentProgress` (same pill geometry, so it reads as
265
267
  a morph).
266
- - **Show the work** — `AgentRun`: a live feed of the agent's steps (reasoning, tool calls) on a
267
- spine, the running node a pulsing ink dot (a done step settles to a hollow ring). Transparent
268
- work, NEVER a bare spinner — the
268
+ - **Show the work** — `AgentRun`: a live feed of the agent's work its streamed narration (with a
269
+ blinking caret) + tool calls as a vertical `Stepper` of neutral dots (the latest pulsing while live,
270
+ a white-check dot once done). No title, no avatar just the work. A terminal "Done" step (the
271
+ `complete` node) appears ONLY once the run finishes. A step can carry `peek` content to glance into
272
+ via a popover. Tool labels resolve from the raw tool name via a built-in default map + an optional
273
+ `labelForTool` override (localize THERE — the kit stays English). `doneLabel` localizes the terminal.
274
+ Transparent work, NEVER a bare spinner — the
269
275
  intelligence is legible. On a canvas/composer app reach for `AgentProgress` — `AgentRun`
270
276
  collapsed into a floating pill (avatar + current step) that EXPANDS on press; the composer
271
277
  morphs into it while running, and reveals again when done.
@@ -281,8 +287,13 @@ Compose the surfaces as a loop, and reach for the right one by job:
281
287
  → `tpl_match`). Stack several (rank by confidence) for a shortlist or a document's records.
282
288
  - **`ChangeReview`** — THE batch engine for a SET you decide each then commit together. `renderItem`
283
289
  draws each proposal (a `ChangeDiff` before→after body, a record, an editable form), 1-by-1
284
- Keep/Drop collapses each, "Accept all" + Apply commits only the kept (e.g. ingested rate lines —
285
- `tpl_ratedesk`); whole-set mode applies/discards the lot.
290
+ Keep/Drop collapses each (e.g. ingested rate lines — `tpl_ratedesk`); whole-set mode reviews the
291
+ lot. It is the LIST only — the commit bar (Accept all / Apply / Discard) is the SEPARATE
292
+ **`ChangeReviewActions`**, which the host places where commit bars belong (a `DialogFooter` /
293
+ `DrawerFooter`, or inline under the list). Don't bundle a commit into a scroll body — like every
294
+ other footer in the kit it's the container's to pin (`tpl_ratedesk` puts it in the `DialogFooter`).
295
+ `ChangeReviewActions` reads the same `items`/`statusOf`, so it owns Accept-all + the kept count —
296
+ never re-derive them in the host.
286
297
  The body atoms (`RecordFields`, `MatchSides`, `ChangeDiff`) are pure presentation — usable inside
287
298
  either engine OR standalone (a live-edit field card). The decide / accept-all / apply mechanics are
288
299
  never re-implemented per surface.
@@ -359,9 +370,11 @@ controls — while **status/severity/diffs use functional colour** the way the r
359
370
  - **Confidence** — a 3-tick meter + word, emerald / amber / zinc by level.
360
371
  - **Finding severity** — a coloured dot `Badge` + metric: red critical, amber warning, blue note,
361
372
  emerald on-track (+ stacking order, most severe first).
362
- - **AgentRun** — `Timeline`-style tinted-disc nodes that fade in: a running step spins **blue**, a
363
- finished step is an **emerald check**, a failure a **red** alert; the header reads `Working` (blue
364
- spinner) / `Done` (emerald check) / `Stopped` (red).
373
+ - **Stepper / AgentRun nodes** — progress dots on a spine: `current` a **pulsing accent ring** (white
374
+ centre), `done` a filled accent dot + **white check**, `upcoming` a faint **grey** ring, the terminal
375
+ `complete` a **blackish ring + black check**, `warning` **amber**. Default accent is neutral ink
376
+ (`color` themes it); `AgentRun` keeps the neutral default + appends the `complete` "Done" node only
377
+ when the run finishes.
365
378
  - **ChangeReview diffs** — the old value struck in **red**, the new value in **green**.
366
379
  - **Discrepancy** — the agent's pick gets a **blue** highlight; `Mismatch` reads amber, `Resolved`
367
380
  emerald.
@@ -578,8 +591,8 @@ inline_time_picker (the Inline* family — per-field editors on `inline_edit`'s
578
591
  list · list_item · menu_button · menu_list_item · detail_row · pressable_row · action_menu ·
579
592
  floating_action_bar · filter_pill · column_filter (ColumnFilter — the typed per-column filter pill +
580
593
  columnFilterToConditions; for a register filtering on several columns) · chip_group · search_input ·
581
- sort_header · table · pagination · accordion · stepper ·
582
- step_progress · step_list · timeline · drawer (+ DrawerFooter) · dialog · popover · tooltip ·
594
+ sort_header · table · pagination · accordion · stepper (Stepper + Step — done/current/upcoming/warning/complete progress on a track (horizontal) or spine (vertical); compound `<Step status>children` OR data `steps[]`+`current`; the guided-run / agent-feed primitive — subsumes the old StepList) ·
595
+ step_progress · timeline (heterogeneous event LOG — per-row icon + expandable details, models the past; NOT progress) · drawer (+ DrawerFooter) · dialog · popover · tooltip ·
583
596
  command_menu · alert · peek · empty_state · completion_state · callout (Callout · CalloutTitle ·
584
597
  CalloutText · CalloutActions) · kpi_card · kpi_strip · metric · trend_chip · sparkline ·
585
598
  bar_chart · line_chart · pie_chart · ring_gauge · progress_bar · stacked_progress_bar · breakdown ·
@@ -590,7 +603,7 @@ spacer · stack · section_card · page_header · page_content · calendar (cale
590
603
  comments_thread · agent_run (live streaming work feed) · agent_progress (its compact floating
591
604
  expandable form — a composer's working state) · composer (Composer — the adaptive command/chat
592
605
  composer: a compact pill that expands for long text + attachments) · review_card (ReviewCard — THE single-proposal review card; compositional body via children + a generic actions footer; header-first with a title/eyebrow, body-first without) · confidence (calibrated high/med/low) ·
593
- record_fields (RecordFields — the editable extracted-record BODY for a ReviewCard, or a standalone live-edit field card) · change_review (ChangeReview — the BATCH review engine, whole-set OR 1-by-1; compositional `renderItem` body + the `ChangeDiff` before→after helper) ·
606
+ record_fields (RecordFields — the editable extracted-record BODY for a ReviewCard, or a standalone live-edit field card) · change_review (ChangeReview — the BATCH review LIST, whole-set OR 1-by-1; compositional `renderItem` body + the `ChangeDiff` before→after helper, `labels` localizes Undo/Applied/Discarded/counter — PLUS `ChangeReviewActions`, the separate commit bar the host pins in a `DialogFooter`/`DrawerFooter` or inline so it never scrolls away) ·
594
607
  clarify (the agent asks back, via ChoiceList) · choice_list (ChoiceList — selectable answer options,
595
608
  the agent's quick-reply surface) · sources (provenance chips, per-kind glyphs) · spec_list (SpecList — the exact
596
609
  labeled-value breakdown, the panel of an answer) · match_sides (MatchSides — the two-sided pairing BODY for a body-first ReviewCard reconciliation; composable side slots + the MatchSideText default) ·
@@ -4,7 +4,7 @@ import { colors } from "@lotics/ui/colors";
4
4
  import { Text } from "@lotics/ui/text";
5
5
  import { DotsIndicator } from "@lotics/ui/dots_indicator";
6
6
  import { Composer } from "@lotics/ui/composer";
7
- import { ChangeReview, ChangeDiff, type ChangeReviewItemStatus } from "@lotics/ui/change_review";
7
+ import { ChangeReview, ChangeReviewActions, ChangeDiff, type ChangeReviewItemStatus } from "@lotics/ui/change_review";
8
8
 
9
9
  // ─────────────────────────────────────────────────────────────────────────────
10
10
  // Template · AI assistant — a CHAT workflow, a different shape from the design
@@ -115,30 +115,42 @@ export function TplAssistant() {
115
115
  ) : null}
116
116
  {m.text ? <Text size="sm">{m.text}</Text> : null}
117
117
  {m.changes ? (
118
- <ChangeReview
119
- items={m.changes}
120
- getKey={(c, i) => `${m.id}:${i}`}
121
- statusOf={(_, i) => decisions[`${m.id}:${i}`] ?? "pending"}
122
- renderItem={(c) => (
123
- <>
124
- <Text size="xs" color="muted" weight="medium">{c.label}</Text>
125
- <ChangeDiff before={c.before} after={c.after} />
126
- </>
127
- )}
128
- renderSummary={(c, kept) => (
129
- <View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
130
- <Text size="sm" weight="medium" numberOfLines={1}>{c.label}</Text>
131
- <Text size="sm" numberOfLines={1} color={kept ? "default" : "muted"} style={[{ flex: 1 }, kept ? undefined : { textDecorationLine: "line-through" }]}>{kept ? c.after : (c.before ?? c.after)}</Text>
118
+ <View style={{ gap: 14 }}>
119
+ <ChangeReview
120
+ items={m.changes}
121
+ getKey={(c, i) => `${m.id}:${i}`}
122
+ statusOf={(_, i) => decisions[`${m.id}:${i}`] ?? "pending"}
123
+ renderItem={(c) => (
124
+ <>
125
+ <Text size="xs" color="muted" weight="medium">{c.label}</Text>
126
+ <ChangeDiff before={c.before} after={c.after} />
127
+ </>
128
+ )}
129
+ renderSummary={(c, kept) => (
130
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
131
+ <Text size="sm" weight="medium" numberOfLines={1}>{c.label}</Text>
132
+ <Text size="sm" numberOfLines={1} color={kept ? "default" : "muted"} style={[{ flex: 1 }, kept ? undefined : { textDecorationLine: "line-through" }]}>{kept ? c.after : (c.before ?? c.after)}</Text>
133
+ </View>
134
+ )}
135
+ status={m.applied ? "applied" : "open"}
136
+ onAcceptItem={active ? (i) => decide(m.id, i, "accepted") : undefined}
137
+ onRejectItem={active ? (i) => decide(m.id, i, "rejected") : undefined}
138
+ onUndoItem={active ? (i) => setDecisions((d) => { const n = { ...d }; delete n[`${m.id}:${i}`]; return n; }) : undefined}
139
+ />
140
+ {/* Inline (no dialog) — the commit bar sits under the list behind a divider. */}
141
+ {active && !m.applied ? (
142
+ <View style={{ borderTopWidth: 1, borderTopColor: colors.zinc[200], paddingTop: 14, flexDirection: "row" }}>
143
+ <ChangeReviewActions
144
+ items={m.changes}
145
+ statusOf={(_, i) => decisions[`${m.id}:${i}`] ?? "pending"}
146
+ applyLabel="Apply accepted"
147
+ onAcceptItem={(i) => decide(m.id, i, "accepted")}
148
+ onApply={() => applyMsg(m.id)}
149
+ onDiscard={() => setMessages((ms) => ms.map((x) => (x.id === m.id ? { ...x, applied: true } : x)))}
150
+ />
132
151
  </View>
133
- )}
134
- status={m.applied ? "applied" : "open"}
135
- applyLabel="Apply accepted"
136
- onAcceptItem={active ? (i) => decide(m.id, i, "accepted") : undefined}
137
- onRejectItem={active ? (i) => decide(m.id, i, "rejected") : undefined}
138
- onUndoItem={active ? (i) => setDecisions((d) => { const n = { ...d }; delete n[`${m.id}:${i}`]; return n; }) : undefined}
139
- onApply={active ? () => applyMsg(m.id) : undefined}
140
- onDiscard={active ? () => setMessages((ms) => ms.map((x) => (x.id === m.id ? { ...x, applied: true } : x))) : undefined}
141
- />
152
+ ) : null}
153
+ </View>
142
154
  ) : null}
143
155
  </View>
144
156
  </View>
@@ -133,7 +133,7 @@ export function TplExtract() {
133
133
 
134
134
  {phase === "extracting" ? (
135
135
  <Card>
136
- <AgentRun title="Extracting line items" steps={steps} state={streaming ? "streaming" : "done"} />
136
+ <AgentRun steps={steps} state={streaming ? "streaming" : "done"} />
137
137
  </Card>
138
138
  ) : null}
139
139
 
@@ -201,7 +201,7 @@ export function TplLookup() {
201
201
 
202
202
  <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 16 }}>
203
203
  {/* current run */}
204
- <AgentRun title="Classifying the goods" steps={steps} state={streaming ? "streaming" : "done"} />
204
+ <AgentRun steps={steps} state={streaming ? "streaming" : "done"} />
205
205
 
206
206
  <View style={{ gap: 8 }}>
207
207
  <Text size="xs" color="muted" weight="medium">Nearest matches</Text>
@@ -11,7 +11,7 @@ import { Divider } from "@lotics/ui/divider";
11
11
  import { Popover, PopoverContent, PopoverFooter, PopoverTrigger } from "@lotics/ui/popover";
12
12
  import { ProgressBar } from "@lotics/ui/progress_bar";
13
13
  import { ScanField } from "@lotics/ui/scan_field";
14
- import { StepList, type StepListItem } from "@lotics/ui/step_list";
14
+ import { Stepper, Step, type StepStatus } from "@lotics/ui/stepper";
15
15
  import { CompletionState } from "@lotics/ui/completion_state";
16
16
 
17
17
  // ─────────────────────────────────────────────────────────────────────────────
@@ -155,18 +155,6 @@ function Complete({ lines, onReset }: { lines: PickLine[]; onReset: () => void }
155
155
  // The right rail — the whole run at a glance: done · now · up next. Read-only;
156
156
  // the run is sequential, so position is shown, not chosen.
157
157
  function RunPath({ lines, currentIndex }: { lines: PickLine[]; currentIndex: number }) {
158
- const steps: StepListItem[] = lines.map((l, i) => ({
159
- id: l.id,
160
- status: l.status === "short" ? "warning" : l.status === "picked" ? "done" : i === currentIndex ? "current" : "pending",
161
- title: l.bin,
162
- subtitle: l.item,
163
- trailing:
164
- l.status === "short" ? (
165
- <Badge variant="dot" label={`${l.picked ?? 0} of ${l.qty}`} color="amber" />
166
- ) : (
167
- <Text size="sm" color="muted" tabular>{`×${l.qty}`}</Text>
168
- ),
169
- }));
170
158
  return (
171
159
  <Card style={{ padding: 0 }}>
172
160
  <View style={{ paddingHorizontal: 16, paddingVertical: 12 }}>
@@ -174,7 +162,27 @@ function RunPath({ lines, currentIndex }: { lines: PickLine[]; currentIndex: num
174
162
  </View>
175
163
  <Divider />
176
164
  <View style={{ paddingHorizontal: 16, paddingVertical: 14 }}>
177
- <StepList steps={steps} accessibilityLabel="Pick path" />
165
+ <Stepper orientation="vertical" accessibilityLabel="Pick path">
166
+ {lines.map((l, i) => {
167
+ const status: StepStatus =
168
+ l.status === "short" ? "warning" : l.status === "picked" ? "done" : i === currentIndex ? "current" : "upcoming";
169
+ return (
170
+ <Step key={l.id} status={status}>
171
+ <View style={{ flexDirection: "row", alignItems: "flex-start", gap: 12, flex: 1 }}>
172
+ <View style={{ flex: 1, gap: 1 }}>
173
+ <Text size="sm">{l.bin}</Text>
174
+ <Text size="xs" color="muted" numberOfLines={1}>{l.item}</Text>
175
+ </View>
176
+ {l.status === "short" ? (
177
+ <Badge variant="dot" label={`${l.picked ?? 0} of ${l.qty}`} color="amber" />
178
+ ) : (
179
+ <Text size="sm" color="muted" tabular>{`×${l.qty}`}</Text>
180
+ )}
181
+ </View>
182
+ </Step>
183
+ );
184
+ })}
185
+ </Stepper>
178
186
  </View>
179
187
  </Card>
180
188
  );
@@ -17,7 +17,7 @@ import { AgentRun, type AgentRunStep } from "@lotics/ui/agent_run";
17
17
  import { Table, TableRow, TableCell, type TableColumn } from "@lotics/ui/table";
18
18
  import { InlineNumberInput } from "@lotics/ui/inline_number_input";
19
19
  import { CompletionState } from "@lotics/ui/completion_state";
20
- import { ChangeReview, type ChangeReviewItemStatus } from "@lotics/ui/change_review";
20
+ import { ChangeReview, ChangeReviewActions, type ChangeReviewItemStatus } from "@lotics/ui/change_review";
21
21
  import { Dialog, DialogHeader, DialogHeaderTitle, DialogScrollArea, DialogFooter } from "@lotics/ui/dialog";
22
22
  import { formatDate } from "@lotics/ui/format_date";
23
23
  import { colors, solid, type ColorName } from "@lotics/ui/colors";
@@ -238,7 +238,7 @@ export function TplRatedesk() {
238
238
  </View>
239
239
  ) : null}
240
240
 
241
- {phase === "reading" ? <AgentRun title="Reading carrier sheets" steps={runSteps} state="streaming" /> : null}
241
+ {phase === "reading" ? <AgentRun steps={runSteps} state="streaming" /> : null}
242
242
 
243
243
  {phase === "review" ? (
244
244
  <ChangeReview
@@ -254,12 +254,9 @@ export function TplRatedesk() {
254
254
  <Text size="xs" color="muted" numberOfLines={1} tabular style={[{ flex: 1 }, kept ? undefined : { textDecorationLine: "line-through" }]}>{l.carrier} · {l.unit} · sell {usd(l.sell)}</Text>
255
255
  </View>
256
256
  )}
257
- applyLabel={`Apply ${lines.filter((l) => decisions[l.id] === "accepted").length} kept`}
258
257
  onAcceptItem={(i) => decide(lines[i].id, "accepted")}
259
258
  onRejectItem={(i) => decide(lines[i].id, "rejected")}
260
259
  onUndoItem={(i) => decide(lines[i].id, null)}
261
- onApply={applyAccepted}
262
- onDiscard={reset}
263
260
  />
264
261
  ) : null}
265
262
 
@@ -268,21 +265,29 @@ export function TplRatedesk() {
268
265
  ) : null}
269
266
  </DialogScrollArea>
270
267
 
271
- {/* idle + done carry footer actions; reading streams and review owns its
272
- own Accept-all / Apply footer inside ChangeReview */}
273
- {phase === "idle" || phase === "done" ? (
268
+ {/* idle / review / done carry footer actions; reading just streams. The
269
+ review commit bar pins HERE via ChangeReviewActions — it never scrolls
270
+ away inside the list. */}
271
+ {phase === "idle" ? (
274
272
  <DialogFooter>
275
- {phase === "idle" ? (
276
- <>
277
- {files.length > 0 ? <Button title="Clear" color="muted" shape="rounded" onPress={() => setFiles([])} /> : null}
278
- <Button title={files.length ? `Read ${files.length} sheets` : "Read sheets"} color="primary" shape="rounded" disabled={!files.length} onPress={() => { setRevealed(0); setPhase("reading"); }} />
279
- </>
280
- ) : (
281
- <>
282
- <Button title="Import more" color="secondary" shape="rounded" onPress={reset} />
283
- <Button title="Done" color="primary" shape="rounded" onPress={closeImport} />
284
- </>
285
- )}
273
+ {files.length > 0 ? <Button title="Clear" color="muted" shape="rounded" onPress={() => setFiles([])} /> : null}
274
+ <Button title={files.length ? `Read ${files.length} sheets` : "Read sheets"} color="primary" shape="rounded" disabled={!files.length} onPress={() => { setRevealed(0); setPhase("reading"); }} />
275
+ </DialogFooter>
276
+ ) : phase === "review" ? (
277
+ <DialogFooter>
278
+ <ChangeReviewActions
279
+ items={lines}
280
+ statusOf={(l) => decisions[l.id] ?? "pending"}
281
+ onAcceptItem={(i) => decide(lines[i].id, "accepted")}
282
+ onApply={applyAccepted}
283
+ onDiscard={reset}
284
+ applyLabel={`Apply ${lines.filter((l) => decisions[l.id] === "accepted").length} kept`}
285
+ />
286
+ </DialogFooter>
287
+ ) : phase === "done" ? (
288
+ <DialogFooter>
289
+ <Button title="Import more" color="secondary" shape="rounded" onPress={reset} />
290
+ <Button title="Done" color="primary" shape="rounded" onPress={closeImport} />
286
291
  </DialogFooter>
287
292
  ) : null}
288
293
  </Dialog>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "5.3.0",
3
+ "version": "5.5.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./tokens": "./src/tokens.ts",
@@ -139,7 +139,6 @@
139
139
  "./table": "./src/table.tsx",
140
140
  "./detail_row": "./src/detail_row.tsx",
141
141
  "./scan_field": "./src/scan_field.tsx",
142
- "./step_list": "./src/step_list.tsx",
143
142
  "./completion_state": "./src/completion_state.tsx",
144
143
  "./remainder_meter": "./src/remainder_meter.tsx",
145
144
  "./allocation_row": "./src/allocation_row.tsx",
@@ -4,7 +4,7 @@ import { colors } from "./colors";
4
4
  import { Text } from "./text";
5
5
  import { WaveAvatar } from "./wave_avatar";
6
6
  import { PressableHighlight } from "./pressable_highlight";
7
- import { AgentRun, type AgentRunStep } from "./agent_run";
7
+ import { AgentRun, resolveToolMeta, type AgentRunStep } from "./agent_run";
8
8
 
9
9
  export interface AgentProgressProps {
10
10
  steps: AgentRunStep[];
@@ -12,6 +12,11 @@ export interface AgentProgressProps {
12
12
  /** The collapsed-pill label. Defaults to the running step's label (or
13
13
  * "Working…" / "Done"). */
14
14
  label?: string;
15
+ /** The agent's streamed text — forwarded to the expanded `AgentRun`. */
16
+ text?: string;
17
+ /** Localize / override a tool step's label by raw tool name — applied to both
18
+ * the collapsed pill and the expanded feed. */
19
+ labelForTool?: (toolName: string) => string | undefined;
15
20
  defaultExpanded?: boolean;
16
21
  }
17
22
 
@@ -24,11 +29,16 @@ export interface AgentProgressProps {
24
29
  * label, so the surface stays calm until you ask to see the steps.
25
30
  */
26
31
  export function AgentProgress(props: AgentProgressProps) {
27
- const { steps, state = "streaming", label, defaultExpanded } = props;
32
+ const { steps, state = "streaming", label, text, labelForTool, defaultExpanded } = props;
28
33
  const [expanded, setExpanded] = useState(defaultExpanded ?? false);
29
34
 
30
35
  const running = steps.filter((s) => s.status === "running").at(-1);
31
- const compact = label ?? running?.label ?? (state === "done" ? "Done" : state === "error" ? "Stopped" : "Working…");
36
+ const runningLabel = running
37
+ ? running.kind === "tool"
38
+ ? resolveToolMeta(running.label, labelForTool).label
39
+ : running.label
40
+ : undefined;
41
+ const compact = label ?? runningLabel ?? (state === "done" ? "Done" : state === "error" ? "Stopped" : "Working…");
32
42
  const streaming = state === "streaming";
33
43
 
34
44
  return (
@@ -36,7 +46,7 @@ export function AgentProgress(props: AgentProgressProps) {
36
46
  {expanded ? (
37
47
  <View style={styles.panel}>
38
48
  <ScrollView style={{ maxHeight: 260 }} contentContainerStyle={{ padding: 16 }}>
39
- <AgentRun steps={steps} state={state} />
49
+ <AgentRun steps={steps} state={state} text={text} labelForTool={labelForTool} />
40
50
  </ScrollView>
41
51
  </View>
42
52
  ) : null}