@artooi/ag-ui-web-component 0.24.0 → 0.25.1

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/CHANGELOG.md CHANGED
@@ -7,6 +7,139 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.25.1] — 2026-08-24
11
+
12
+ ### Fixed
13
+
14
+ - **Two runs that opened on the same sentence read alike in the checkpoint
15
+ panel.** A row leads with the run's first user message and hides the short id
16
+ once it has one, on the argument that the words are the identity. A real run
17
+ index then answered with five runs all opening on "what is on the board?", and
18
+ those rows were as indistinguishable as five timestamps used to be. A preview
19
+ identifies a run only while it is that run's own, so the id comes back for the
20
+ rows whose preview another row repeats and stays hidden for the rows whose words
21
+ are their own — one panel now shows some rows with an id and some without, each
22
+ carrying the best identity it has. Previews are compared as the row renders
23
+ them: whitespace collapsed, case kept, since two spellings a person can tell
24
+ apart are two labels. No new part and no new fallback — `checkpoint-id` is the
25
+ one the time-only row has always used.
26
+
27
+ ## [0.25.0] — 2026-08-14
28
+
29
+ ### Added
30
+
31
+ - **Every deferred call is asked about in its own card, and all of them at once.**
32
+ A run can defer more than one call, and the wire takes a different answer for
33
+ each — so the UI has to let a person give one, which means saying which card is
34
+ which. Importing three rows gated three `create_event` calls and produced three
35
+ identical prompts: the text comes from the tool, so all three read "Add this
36
+ event to the board?", and nothing else on the card named the row. They were also
37
+ serial, each appearing only once the previous was answered, appended below the
38
+ three calls they gated — so a person answering the first could not compare them
39
+ or tell that two more were coming. **A batch gate that can only be answered
40
+ uniformly is one you have to answer blind.**
41
+
42
+ Each prompt now renders into the tool card of the call it gates, under that
43
+ call's own arguments, and every card opens at once. No new wire field was needed:
44
+ the arguments were already there, and the call site already held the card. A
45
+ card being asked about shows its arguments in every `data-tool-display` mode,
46
+ since a density setting must not hide the answer to "which one is this". New
47
+ part: `tool-card-approval`.
48
+
49
+ - **A status for "deferred, awaiting a decision".** `TOOL_CALL_STATUS` was
50
+ `pending | done | error | declined`, so a gated call sat at `pending` and read
51
+ **running…** while the stream was over and the server idle. A status enum missing
52
+ a state does not omit that state, it renders it as whichever neighbour is
53
+ closest, and this one claimed the opposite of the truth. Gated cards now read
54
+ `waiting for you` (`data-status="deferred"`, `strings.toolDeferred`), with a
55
+ steady dot instead of a spinner, and go back to `pending` on approval — because
56
+ then the tool really is running. A **frontend** tool such as `ask_user` keeps
57
+ saying "running…" while its card is open: the browser is executing it.
58
+
59
+ - **The checkpoint rows lead with what the run was about.** A run index that
60
+ answers `GET runs/` with a `preview` — the run's first user message — and the
61
+ panel now shows it, with the time demoted to a chip beside it (new part:
62
+ `checkpoint-time`). A row with no preview keeps the old time-plus-short-id
63
+ layout, so an older server is unaffected.
64
+
65
+ - **`toggleCheckpoints()` and `closeCheckpoints()`.** The built-in ⭯ control now
66
+ calls the toggle, so pressing it a second time dismisses the panel;
67
+ `openCheckpoints()` keeps its open-only meaning for a host that wants exactly
68
+ that.
69
+ - **The checkpoint rows show a short run id.** A time is not an identity: two runs
70
+ of the same minute both read "just now", and choosing between them was choosing
71
+ blind. Eight characters of the id, muted, beside the time — with the whole id on
72
+ its own `title` rather than on the row's label, so hovering a row no longer
73
+ raises a full UUID over it. Now the fallback rather than the rule: a row that
74
+ arrives with a `preview` leads with the words instead (see above).
75
+
76
+ ### Changed
77
+
78
+ - **`approvalRenderer` is called once per interrupt, concurrently.** It used to be
79
+ awaited one at a time. A host that can only ask one question at a time should
80
+ queue inside its renderer.
81
+
82
+ ### Fixed
83
+
84
+ - **The README's parts list was missing an entire feature, and now a test reads
85
+ it.** Part names are declared public API there, and an undocumented part is not a
86
+ part: a host cannot style what it cannot know exists, and a name guessed wrong
87
+ fails silently. Absent were all twelve checkpoint-panel parts plus
88
+ `checkpoints-button`, and five strays from other features (`code-copy`,
89
+ `resize-handle`, `run-notice-icon`, `run-notice-text`, `skill-item-token`). The
90
+ list drifted because nothing read it, so the fix is not only the missing names:
91
+ it is now a table, spelled out in full rather than as `tool-card` *plus* `-icon`
92
+ shorthand, and a test collects every part the source sets and fails on one the
93
+ table does not name. The handful assembled at runtime are enumerated in that
94
+ test, and adding another such call site fails it too.
95
+
96
+ - **The checkpoint panel could not be closed by the control that opened it.**
97
+ The button called `open()`, which returns early when the panel is already open,
98
+ so the first gesture anyone tries did nothing. Escape worked, and answering a row
99
+ worked, and that was all. It now toggles, and a pointer landing anywhere else in
100
+ the widget dismisses it — the thread drawer has a backdrop that swallows such a
101
+ click, and this popover has none.
102
+
103
+ - **The thread drawer and the checkpoint popover could be open at once.** Opening
104
+ either now closes the other. The pointer case was already covered by the
105
+ click-away above, but a host driving its own chrome through `openThreads()` /
106
+ `openCheckpoints()` raises no pointer event, and the drawer would slide open
107
+ underneath a popover still floating over it.
108
+
109
+ - **The panel read as a list of clickable rows.** Each row painted itself on hover
110
+ while nothing about the row was pressable: the two buttons that *were* pressable
111
+ sat on that highlight as transparent outlines. The row now carries a resting
112
+ surface and no hover at all, and the actions carry the filled-primary /
113
+ outlined-secondary pair the confirmation and approval cards already use, plus
114
+ `:active` and — new — a visible `:focus-visible` ring, in a panel that traps
115
+ focus and is reached by Tab.
116
+
117
+ - **A narrow panel crushed the timestamp to nothing.** The row is a flex line whose
118
+ only flexible child is the label, so it absorbs every fixed-width element the row
119
+ gains; at 320px the label kept its text, reported it correctly, and measured 0px.
120
+ This is the tool-call head's defect (0.24.0) one panel along, and it is now
121
+ measured in a real browser at both widths — happy-dom lays out no boxes and
122
+ called the broken row a pass.
123
+
124
+ - **The ⭯ glyph is now ↺.** The old one has no font behind it in most browsers and
125
+ rendered as an unreadable mark at 14px. A header control nobody can name is one
126
+ nobody presses.
127
+
128
+ - **The composer painted a paperclip that could not upload anything.** The element
129
+ sets `hidden` on the attach button until a host gives it somewhere to upload —
130
+ an `uploadHandler` or `data-attachments-url` — but the button's own
131
+ `display: inline-flex` beat the UA stylesheet's rule for the hidden property, so
132
+ it stayed visible and clickable, opening a file picker whose file had nowhere to
133
+ go. This is the third instance of one trap: the attachment tray was fixed in
134
+ 0.23.0 and carries a comment about it two rules away in the same file. Found in
135
+ the framework gallery, where three of the four host apps were in exactly that
136
+ state and nobody had noticed the clip was inert.
137
+
138
+ **The mic does not share it**, and the asymmetry is pinned by a test rather than
139
+ assumed: the clip is created and hidden, while the voice wiring returns before
140
+ constructing anything, leaving an empty slot that measures nothing. A
141
+ hidden-state rule for the mic would match no element, so there is one rule.
142
+
10
143
  ## [0.24.0] — 2026-08-13
11
144
 
12
145
  ### Added
@@ -1421,7 +1554,9 @@ hosts that both arrange the page the way it expects.
1421
1554
  ### Notes
1422
1555
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
1423
1556
 
1424
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.24.0...HEAD
1557
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.25.1...HEAD
1558
+ [0.25.1]: https://github.com/Artui/ag-ui-web-component/compare/v0.25.0...v0.25.1
1559
+ [0.25.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.24.0...v0.25.0
1425
1560
  [0.24.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.23.1...v0.24.0
1426
1561
  [0.23.1]: https://github.com/Artui/ag-ui-web-component/compare/v0.23.0...v0.23.1
1427
1562
  [0.23.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.22.0...v0.23.0
package/README.md CHANGED
@@ -546,19 +546,31 @@ approval card below is for.
546
546
  ### Server-side tool approval (interrupts)
547
547
 
548
548
  When the server gates a destructive tool (e.g. django-ag-ui's `ToolGuard`), the tool **defers**
549
- instead of executing and the run finishes on an AG-UI *interrupt*. The element then appends an
549
+ instead of executing and the run finishes on an AG-UI *interrupt*. The element then renders an
550
550
  **inline approval card** (a `<div class="approval">`) via
551
- [`requestApproval`](src/ui/approval_card.ts), next to the pending tool-call card:
551
+ [`requestApproval`](src/ui/approval_card.ts) **inside the tool card of the call it gates**:
552
552
 
553
553
  - **Approve** → the run resumes and the server runs the tool; its result streams back into the
554
554
  same card.
555
555
  - **Deny** → the run resumes carrying a `cancelled` answer, so the model learns the tool was
556
- declined; the pending card settles as declined.
556
+ declined; the card settles as declined.
557
557
 
558
558
  This uses the AG-UI protocol's own interrupt/resume mechanism (`RunAgentInput.resume[]`) — the
559
559
  wire stays vanilla AG-UI. A **Stop** while an approval card is open denies every open card and
560
560
  cancels the run. No configuration is needed on the client; the gate is enabled server-side.
561
561
 
562
+ **A run can defer several calls, and each is answered on its own.** Importing three rows defers
563
+ three `create_event` calls, and the wire takes a different answer for each. So every question is
564
+ asked **at once**, each in its own tool card, above that call's own arguments — which is what says
565
+ *which* call it is about. The prompt cannot: it comes from the tool, so all three read "Add this
566
+ event to the board?". While the run waits, those cards read **`waiting for you`**
567
+ (`data-status="deferred"`), not "running…" — nothing is running, the stream is over and the server
568
+ is idle. A card asking a question shows its arguments in every `data-tool-display` mode, since
569
+ hiding them would hide the answer to "which one is this".
570
+
571
+ A **frontend** tool such as `ask_user` stays at "running…" while its card is open, because the
572
+ browser really is running it. Only a deferred call was claiming something untrue.
573
+
562
574
  **What the card asks.** An AG-UI interrupt carries the question as `message`, and the default is the
563
575
  call spelled out — `Approve create_event({"title": "Design sync", …})?` — which is accurate and not
564
576
  something to put in front of a person. A server can supply its own wording as **`x-confirm` in the
@@ -584,10 +596,12 @@ if your page renders the data it touched, listen for
584
596
  [`ag-ui-run-finished`](#host-seams-the-spa-story) and refetch.
585
597
 
586
598
  Like the question card, the approval card is customizable at three levels: **text** (`strings`:
587
- `approveAction` / `approvalPrompt` / `approve` / `deny`), **CSS** (`::part()`: `approval`,
588
- `approval-body`, `approval-actions`, `approval-button`, `approval-approve`, `approval-deny`), and
589
- **full replacement** via `chat.approvalRenderer` given the request (`message` + `toolName`) and
590
- a Stop `AbortSignal`, render your own UI and resolve `true`/`false`:
599
+ `approveAction` / `approvalPrompt` / `approve` / `deny` / `toolDeferred`), **CSS** (`::part()`:
600
+ `approval`, `approval-body`, `approval-actions`, `approval-button`, `approval-approve`,
601
+ `approval-deny`, and `tool-card-approval` for the region inside the card), and **full replacement**
602
+ via `chat.approvalRenderer` given the request (`message` + `toolName`) and a Stop `AbortSignal`,
603
+ render your own UI and resolve `true`/`false`. A renderer is called **once per interrupt,
604
+ concurrently**, so a host that can only ask one thing at a time should queue inside it:
591
605
 
592
606
  ```js
593
607
  chat.approvalRenderer = (request, { signal }) =>
@@ -1228,10 +1242,18 @@ component at the run index and a ⭯ button appears in the header:
1228
1242
 
1229
1243
  The panel lists runs the server marked **continuable** — those with a saved
1230
1244
  snapshot to seed from. A run that never reached a provider-valid boundary has
1231
- none, so it isn't offered: resuming it would start from nothing. Each row shows
1232
- when the run started (the id is on hover, for correlating with server logs) and
1233
- marks a run that branched from another, so a fork doesn't read as a duplicate
1234
- of its parent.
1245
+ none, so it isn't offered: resuming it would start from nothing.
1246
+
1247
+ A row leads with the run's **first user message**, from the index's `preview`
1248
+ field, and shows when it started beside it. That is what makes the list a list of
1249
+ conversations: a time is not an identity, since two runs a minute apart both read
1250
+ "just now", and a run id is not something a person recognises. Where the server
1251
+ sends no preview — an older index, or a run that opened on an image with no
1252
+ caption — the row falls back to the time plus the first eight characters of the
1253
+ id (full id on hover, for correlating with server logs), and so does a row whose
1254
+ preview another row repeats: words shared with a second row identify neither, and
1255
+ one board tends to be asked about more than once. Either way a run that branched
1256
+ from another is marked, so a fork doesn't read as a duplicate of its parent.
1235
1257
 
1236
1258
  Type the next turn in the composer, then pick a row:
1237
1259
 
@@ -1388,7 +1410,7 @@ re-export point. Internal modules import from leaf paths.
1388
1410
  | `ThreadMeta` | type | A thread-drawer row (`{ threadId, title, updatedAt, preview }`). |
1389
1411
  | `NavigationCheckpoint` | type | The pre-reload checkpoint marker. |
1390
1412
  | `RunIndex` | class | Reads a `data-runs-url` run index and derives its resume / fork endpoints. |
1391
- | `RunRow` | type | One run index row (`{ run_id, thread_id, parent_run_id, started_at, continuable }`). |
1413
+ | `RunRow` | type | One run index row (`{ run_id, thread_id, parent_run_id, started_at, continuable, preview? }`). |
1392
1414
  | `CheckpointMenu` | class | The *Continue a run* panel. |
1393
1415
  | `CheckpointVerb` | type | `"resume" | "fork"`. |
1394
1416
 
@@ -1555,30 +1577,28 @@ ag-ui-chat::part(send) { text-transform: uppercase; }
1555
1577
  ag-ui-chat::part(tool-card) { font-family: var(--my-mono); }
1556
1578
  ```
1557
1579
 
1558
- Available parts: `panel`, `header`, `title`, `icon`, `header-controls`, `header-button`
1559
- (plus `history-button` / `new-button` / `collapse-button` / `theme-toggle`), `messages`,
1560
- `answer` (the per-turn group), `thoughts` (plus `thoughts-toggle` / `thoughts-body` /
1561
- `thoughts-label`), `message`
1562
- (plus `message-user` / `message-assistant`), `empty`, `pending`, `stopped` (the "⏹ Stopped" note),
1563
- `tool-card`
1564
- (plus `tool-card-head` / `-icon` / `-name` / `-status` / `-args` / `-toggle` / `-result`),
1565
- `confirm` (plus `confirm-body` /
1566
- `-args` / `-actions` / `-button` / `-cancel` / `-confirm`),
1567
- `approval` (plus `approval-body` / `-actions` / `-button` / `-approve` / `-deny`),
1568
- `question` (plus `question-body` / `-options` / `-choice` / `-choice-text` / `-radio` / `-input` /
1569
- `-actions` / `-button`), `composer` (plus `composer-surface` / `composer-tools`), `input`, `send`,
1570
- `attach-button`, `voice-button`,
1571
- the attachment chips `attachment-tray` and `attachment-chips` (the read-only chips on sent
1572
- bubbles) with the shared chip parts `attachment-chip` (plus `-icon` / `-name` / `-size` / `-bar` /
1573
- `-bar-fill` / `-retry` / `-remove`),
1574
- the skills UI (`skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`,
1575
- `skill-item-desc`, and the missing-placeholder `skill-hint`),
1576
- `launcher`, `launcher-icon`, `launcher-badge`, and the drawer parts
1577
- (`drawer`, `drawer-backdrop`, `drawer-panel`, `drawer-header`, `drawer-title`, `drawer-new`,
1578
- `drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`, `drawer-row-title`,
1579
- `drawer-row-time`, `drawer-row-preview`, `drawer-row-actions`, `drawer-row-rename`,
1580
- `drawer-row-delete`, `drawer-rename-input`, `drawer-confirm`, `drawer-confirm-label`,
1581
- `drawer-confirm-yes`, `drawer-confirm-no`).
1580
+ Every part, by the feature it belongs to. Spelled out rather than abbreviated: the list used to
1581
+ read `tool-card` *plus* `-icon` / `-name`, which is compact and is also how an entire feature went
1582
+ missing from it for two releases. A test reads this table and compares it with the parts the
1583
+ component sets, so a new one cannot ship undocumented.
1584
+
1585
+ | Feature | Parts |
1586
+ | --- | --- |
1587
+ | Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `pending`, `stopped`, `resize-handle` |
1588
+ | Header buttons | `header-button` on each, plus `history-button`, `checkpoints-button`, `new-button`, `collapse-button`, `theme-toggle` |
1589
+ | Collapsed widget | `launcher`, `launcher-icon`, `launcher-badge` |
1590
+ | Answers | `answer` (the per-turn group), `message` (plus `message-user`, `message-assistant`), `code-copy` |
1591
+ | Reasoning | `thoughts`, `thoughts-toggle`, `thoughts-body`, `thoughts-label` |
1592
+ | Run notices | `run-notice` (plus `run-notice-interrupted`, `run-notice-attachment-pending`, `run-notice-compaction`, `run-notice-skill`), `run-notice-icon`, `run-notice-text` |
1593
+ | Tool cards | `tool-card`, `tool-card-head`, `tool-card-icon`, `tool-card-name`, `tool-card-status`, `tool-card-decision`, `tool-card-toggle`, `tool-card-body`, `tool-card-section` (plus `tool-card-args-section`, `tool-card-result-section`), `tool-card-section-label` (plus `tool-card-args-label`, `tool-card-result-label`), `tool-card-args`, `tool-card-result`, `tool-card-approval` |
1594
+ | Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`) |
1595
+ | Server-side approval | `approval`, `approval-body`, `approval-actions`, `approval-button` (plus `approval-approve`, `approval-deny`) |
1596
+ | Typed question | `question`, `question-body`, `question-options`, `question-choice`, `question-choice-text`, `question-radio`, `question-input`, `question-actions`, `question-button` |
1597
+ | Composer | `composer`, `composer-surface`, `composer-tools`, `input`, `send`, `attach-button`, `voice-button` |
1598
+ | Attachments | `attachment-tray`, `attachment-chips` (the read-only chips on sent bubbles), and the shared chip parts `attachment-chip`, `attachment-chip-icon`, `attachment-chip-name`, `attachment-chip-size`, `attachment-chip-bar`, `attachment-chip-bar-fill`, `attachment-chip-retry`, `attachment-chip-remove` |
1599
+ | Skills | `skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`, `skill-item-desc`, `skill-item-token`, `skill-hint` (the missing-placeholder hint) |
1600
+ | Thread drawer | `drawer`, `drawer-backdrop`, `drawer-panel`, `drawer-header`, `drawer-title`, `drawer-new`, `drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`, `drawer-row-title`, `drawer-row-time`, `drawer-row-preview`, `drawer-row-actions`, `drawer-row-rename`, `drawer-row-delete`, `drawer-rename-input`, `drawer-confirm`, `drawer-confirm-label`, `drawer-confirm-yes`, `drawer-confirm-no` |
1601
+ | Checkpoints panel | `checkpoints`, `checkpoints-header`, `checkpoints-title`, `checkpoints-list`, `checkpoints-empty`, `checkpoint-row`, `checkpoint-label`, `checkpoint-time`, `checkpoint-id`, `checkpoint-branch`, `checkpoint-action` (plus `checkpoint-resume`, `checkpoint-fork`) |
1582
1602
 
1583
1603
  > **Hiding `::part(header)` hides the controls inside it.** The history, checkpoints, new-chat,
1584
1604
  > theme and collapse buttons are all children of the header, so a host that renders its own title