@artooi/ag-ui-web-component 0.47.0 → 0.48.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +135 -1
  2. package/README.md +171 -15
  3. package/dist/ag-ui-web-component.bundle.js +591 -176
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +27 -2
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +36 -3
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/events/bar_height_detail.d.ts +6 -0
  10. package/dist/core/events/bar_height_detail.d.ts.map +1 -0
  11. package/dist/dom/highlight_overlay.d.ts +2 -1
  12. package/dist/dom/highlight_overlay.d.ts.map +1 -1
  13. package/dist/dom/ring_color.d.ts +8 -0
  14. package/dist/dom/ring_color.d.ts.map +1 -1
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +926 -52
  18. package/dist/index.js.map +4 -4
  19. package/dist/tools/chat_surface_tools.d.ts +8 -1
  20. package/dist/tools/chat_surface_tools.d.ts.map +1 -1
  21. package/dist/ui/history/conversation_history.d.ts +16 -3
  22. package/dist/ui/history/conversation_history.d.ts.map +1 -1
  23. package/dist/ui/placement/collapse_styles.d.ts.map +1 -1
  24. package/dist/ui/placement/dock_styles.d.ts +2 -0
  25. package/dist/ui/placement/dock_styles.d.ts.map +1 -0
  26. package/dist/ui/placement/panel_placement.d.ts +11 -0
  27. package/dist/ui/placement/panel_placement.d.ts.map +1 -1
  28. package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -1
  29. package/dist/ui/placement/surface_for.d.ts +21 -17
  30. package/dist/ui/placement/surface_for.d.ts.map +1 -1
  31. package/dist/ui/progress/tool_group_styles.d.ts.map +1 -1
  32. package/dist/ui/progress/tool_step_styles.d.ts.map +1 -1
  33. package/dist/ui/shell/build_launcher.d.ts +15 -4
  34. package/dist/ui/shell/build_launcher.d.ts.map +1 -1
  35. package/dist/ui/shell/token_styles.d.ts.map +1 -1
  36. package/dist/ui/styles.d.ts.map +1 -1
  37. package/dist/ui/themes/scheme_styles.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/constants.ts +30 -2
  40. package/src/core/ag_ui_chat.ts +326 -26
  41. package/src/core/events/bar_height_detail.ts +5 -0
  42. package/src/dom/highlight_overlay.ts +2 -1
  43. package/src/dom/ring_color.ts +141 -4
  44. package/src/index.ts +4 -0
  45. package/src/tools/chat_surface_tools.ts +18 -8
  46. package/src/ui/history/conversation_history.ts +51 -7
  47. package/src/ui/placement/collapse_styles.ts +248 -2
  48. package/src/ui/placement/dock_styles.ts +91 -0
  49. package/src/ui/placement/panel_placement.ts +71 -3
  50. package/src/ui/placement/small_viewport_styles.ts +9 -0
  51. package/src/ui/placement/surface_for.ts +29 -18
  52. package/src/ui/progress/tool_group_styles.ts +49 -0
  53. package/src/ui/progress/tool_step_styles.ts +5 -3
  54. package/src/ui/shell/build_launcher.ts +32 -5
  55. package/src/ui/shell/token_styles.ts +28 -1
  56. package/src/ui/styles.ts +4 -0
  57. package/src/ui/themes/scheme_styles.ts +10 -1
  58. package/src/version.ts +1 -1
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.48.0] — 2026-11-07
11
+
12
+ ### Added
13
+
14
+ - **Choose how a step group draws its calls.** A new `data-tool-group-display`
15
+ attribute and `toolGroupDisplay` property, one of `full`, `compact`,
16
+ `minimal` or `inline`, set how an opened group draws its calls under
17
+ `grouped`. Each draws the regions the flat card of that mode draws, as a row
18
+ of the group. It is read only under `grouped`, is live, and leaves an
19
+ approval's arguments and buttons on screen in every mode. Exported as
20
+ `TOOL_GROUP_DISPLAY` with the `ToolGroupDisplayMode` type. **Opt-in**: an
21
+ absent or unknown value reads as `full`, which is what a group drew before,
22
+ so a host sees no change until it sets the attribute.
23
+ - **Collapse into a bottom bar.** A new live `collapse-to` attribute chooses
24
+ what a collapsed panel becomes. `bar` draws a strip along the bottom edge in
25
+ place of the round launcher, with the title, the unread badge, a running
26
+ mark and a history button that opens and closes the conversation list as a
27
+ popover;
28
+ the panel rises from the bar's right end. `none` hides the collapsed widget
29
+ so the host's own toolbar drives it, and the `anchor` attribute or property
30
+ names the host element the panel rises from. Under both, the panel's height
31
+ stops at the bar or the anchor, so `--ag-ui-inset` is not needed, and a
32
+ docked panel can be resized but not moved. The read-only `barHeight`
33
+ property and the `ag-ui-bar-height` event (`BAR_HEIGHT_EVENT`, detail
34
+ `BarHeightDetail`) give the host the height to pad its page by. Under
35
+ `none` the element dispatches `ag-ui-toggle` once at connect with the state
36
+ it mounted in. On a phone the open panel stays full-bleed and the bar shows
37
+ only while collapsed. New parts `bar` and `bar-history`, and
38
+ `describeSurface()` reports `collapseTo`. **Opt-in**: absent,
39
+ `collapse-to` reads as `auto`, which leaves every placement as it was.
40
+ - **Clear a host toolbar under `collapse-to="none"`.** The new
41
+ `--ag-ui-anchor-gap` token sets the gap between the panel and the anchor it
42
+ rises from, for a host whose button sits inside a taller toolbar; a gap
43
+ wider than the gutter also comes off the panel's max height, so a tall panel
44
+ stays on screen. **Opt-in**: absent, the gap stays 8px.
45
+
46
+ ### Changed
47
+
48
+ - **The chat surface tools describe every collapsed shape.** With
49
+ `data-page-actions` including `chat`, the `minimise_chat` tool no longer
50
+ promises a launcher: its description names the launcher, the rail, the
51
+ bottom bar or the page's own control, and its refusal on a placement with no
52
+ collapsed state no longer mentions a launcher. The report `describeSurface()`
53
+ returns, and so `read_chat_surface`, gains `collapseTo`, and a move
54
+ refused on a docked panel says it is docked. This reaches every host that
55
+ enables the chat tools, whatever `collapse-to` is: an agent prompt that
56
+ quoted the old wording sees new text. No host action is needed.
57
+ - **Tested against `@ag-ui/client` and `@ag-ui/core` 1.0.4.** The lockfile moves
58
+ from 1.0.3, and the declared range (`>=1.0.0 <2`) is unchanged, so a fresh
59
+ install of 0.47.0 already resolved 1.0.4. The element passes `threadId` to
60
+ the agent rather than reading the one 1.0.4 now creates on first read, and it
61
+ never edits a message the agent emits, which 1.0.4 now documents as
62
+ read-only. `zod` stays an undeclared peer of `@ag-ui/core`, as it was in
63
+ 1.0.3. **No opt-in** and no behavioural change.
64
+
65
+ - **The demo shows the 0.47.0 additions.** The playground gains a card under
66
+ the form with a control and a readout for each: the `above-composer` slot
67
+ (also a checkbox on the full-page route, where it travels with the centred
68
+ composer), the thread API (`threadId`, `newChat()`, `switchThread()` and a
69
+ log of `ag-ui-thread` events), the `tool-card-status-*` and `tool-group-*`
70
+ parts (drawn in one colour each, with a census of the parts in the
71
+ transcript) and the `--ag-ui-ring` token. Demo only, nothing shipped
72
+ changes, and **a host owes nothing**.
73
+
74
+ ### Fixed
75
+
76
+ - **A collapsed embedded chat no longer shows the above-composer slot.** The
77
+ collapse hid the composer and its stack but not the `above-composer` slot, so
78
+ a host's row stayed on screen under the header bar. The slot is hidden with
79
+ the composer and returns on expand. No opt-in: a host that kept content in
80
+ that slot to stay visible while collapsed should use the footer slot, which
81
+ collapse leaves alone.
82
+
83
+ - **`switchThread()` keeps the old thread when the store's load rejects.** The element
84
+ used to point at the new thread id, show an empty transcript and fire no
85
+ `ag-ui-thread`, so it named a thread it was not showing. It now stays on the thread
86
+ it was on, draws it again without an event, and the returned promise rejects with
87
+ the store's error. A second `switchThread()` for the thread still loading now
88
+ settles with that load, after the replay, instead of resolving at once. No host
89
+ opt-in.
90
+ - **`newChat()` before the element connects waits for the connect.** It used to mint a
91
+ thread in the unscoped store and fire `ag-ui-thread` for a chat that was not yet
92
+ shown, leaving `threadId` non-null against the README. Now `threadId` is `null` and
93
+ nothing fires until the element connects, which starts the new chat and fires one
94
+ `ag-ui-thread` with reason `new`. A host that called `newChat()` early and relied on
95
+ the immediate event or thread id reads them after connect instead.
96
+
97
+ - **Rings hold 3:1 against what they are drawn on.** The step group head's
98
+ focus ring was the accent at 55% over transparent, so a light host accent
99
+ fell to 1.6:1 on the default light panel (Django admin's `#79aec8`). Unset,
100
+ `--ag-ui-ring` is now two parts accent to three parts ink, which holds 3:1
101
+ against the panel for any accent in every theme and scheme; Quiet's ring
102
+ turns from a translucent grey to near-ink. The field highlight ring, when it
103
+ falls back to the chat's own accent, is now held to 3:1 against the surface
104
+ behind the target: a dark widget on a light page drew its near-white accent
105
+ at 1.1:1, and now draws the light half of its accent pair, or the accent
106
+ stepped toward black or white. **No opt-in is needed**, and a host's
107
+ `--ag-ui-ring`, `--ag-ui-highlight-ring-color` or page `--ag-ui-accent` is
108
+ still drawn exactly as given.
109
+
110
+ - **The docs no longer advise `--ag-ui-font: inherit`.** The README and the
111
+ 0.45.0 entry said it would keep the page's font under any theme. It does
112
+ nothing: a CSS-wide keyword in a custom property applies to the custom
113
+ property itself, so it takes the parent's `--ag-ui-font`, which nothing sets,
114
+ and the theme's stack wins. Set `--ag-ui-font` to the page's family or to a
115
+ page token instead, for example `--ag-ui-font: var(--body-font)`. A host that
116
+ followed the old advice has to change it to get the page's font.
117
+
118
+ - **A chat never dragged no longer inherits a dragged size.** The size a drag
119
+ writes inline on the host was left undeclared on every other chat, so a chat
120
+ slotted into a dragged one, or under any page rule naming the private
121
+ property, came out at that size. It is now declared on `:host` like every
122
+ other private property. **No opt-in is needed.**
123
+
124
+ - **Picking a thread from the history drawer no longer leaves an unhandled
125
+ rejection when the store cannot load it.** The drawer's pick dropped the promise
126
+ `switchThread()` returns, so a store whose load rejected surfaced as an unhandled
127
+ rejection on the page. The pick now ends that rejection itself, and the thread on
128
+ screen stays as it was. No host opt-in.
129
+
130
+ - **A gated call with no arguments no longer draws an empty arguments box.** Under
131
+ `data-tool-display="compact"` and `"inline"`, the rule that shows a deferred call's
132
+ arguments outranked the `hidden` attribute the card sets when there are none, so an
133
+ empty box sat above the Approve button. The rule now skips a hidden section. No host
134
+ opt-in.
135
+
136
+ - **The theme toggle flips the scheme the panel is drawn in.** With no `light` or
137
+ `dark` word in `theme`, as under `auto` on a dark system or when a host rule sets
138
+ the element's `color-scheme`, the first press wrote `dark` over a panel that was
139
+ already dark, and the glyph offered dark. The toggle and its glyph now read the
140
+ drawn scheme. A `light` or `dark` word also now holds against a host rule that
141
+ sets `color-scheme` on the element, which used to win over it. No host opt-in.
142
+
10
143
  ## [0.47.0] — 2026-11-07
11
144
 
12
145
  ### Added
@@ -4846,7 +4979,8 @@ hosts that both arrange the page the way it expects.
4846
4979
  ### Notes
4847
4980
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
4848
4981
 
4849
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.47.0...HEAD
4982
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.48.0...HEAD
4983
+ [0.48.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.47.0...v0.48.0
4850
4984
  [0.47.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.46.0...v0.47.0
4851
4985
  [0.46.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.45.0...v0.46.0
4852
4986
  [0.45.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.44.0...v0.45.0
package/README.md CHANGED
@@ -48,6 +48,7 @@ No framework, no Django, no admin specifics live here. Downstream consumers (e.g
48
48
  - [Page-action tools](#page-action-tools)
49
49
  - [New chat and collapse](#new-chat-and-collapse)
50
50
  - [Collapsing to the launcher](#collapsing-to-the-launcher)
51
+ - [Collapsing to a bottom bar](#collapsing-to-a-bottom-bar)
51
52
  - [Tool-call display modes](#tool-call-display-modes)
52
53
  - [Delegated sub-agents](#delegated-sub-agents)
53
54
  - [Markdown rendering](#markdown-rendering)
@@ -178,6 +179,7 @@ another origin, add `credentials="include"` too; see
178
179
  | `credentials` | `credentials` | Cookie policy for every request the element makes: `omit` / `same-origin` / `include`. Unset means the browser default (`same-origin`), which sends no cookies cross-origin. See [Authenticating requests](#authenticating-requests). |
179
180
  | `title-text` | — | Header label; defaults to `"Assistant"`. Live: writing it after the element connects re-labels the header. See [When each attribute is read](#when-each-attribute-is-read). |
180
181
  | `data-tool-display` | `toolDisplay` | How tool calls are drawn: `grouped` (the default: a run's consecutive calls fold into one row) / `inline` / `minimal` / `compact` / `full` (per-call cards; `full` was the default before `grouped`). An unknown value reads as `grouped`. See [Tool-call display modes](#tool-call-display-modes). |
182
+ | `data-tool-group-display` | `toolGroupDisplay` | How an opened step group draws its calls under `grouped`: `full` (the default) / `compact` / `minimal` / `inline`. Ignored under the four card modes. An absent or unknown value reads as `full`. See [Choosing how a group draws its calls](#choosing-how-a-group-draws-its-calls). |
181
183
  | `data-text-animation` | — | Incoming-text reveal: `none` (default) / `fade` / `word`. |
182
184
  | `data-prompt-chips` | — | Present (bare, or any value but `"false"`) to surface skills as chips. |
183
185
  | `data-slash-commands` | — | Present (bare, or any value but `"false"`) to enable the `/`-command palette. |
@@ -206,6 +208,8 @@ another origin, add `credentials="include"` too; see
206
208
  | `data-side` | — | CSS-only, for `placement="sidebar"`: which edge it docks to — `right` (default) / `left`. |
207
209
  | `data-answer-well` | — | CSS-only boolean: box each assistant turn (its text, tool cards, and thinking) in one bordered "well". Off by default. See [The answer well](#the-answer-well). |
208
210
  | `collapsed` | `collapsed` | Reflected boolean; collapses the widget to its [launcher](#collapsing-to-the-launcher) (a rail under `placement="sidebar"`, the header bar under `embedded`). Persisted per tab. `placement="page"` has no collapsed state and ignores it. |
211
+ | `collapse-to` | — | What the panel collapses to: `auto` (the default; absent or any other value reads the same) leaves every placement as it is, `bar` draws a strip along the bottom edge in place of the round launcher, `none` hides the collapsed widget so the host's own control brings it back. Live. See [Collapsing to a bottom bar](#collapsing-to-a-bottom-bar). |
212
+ | `anchor` | `anchor` | Under `collapse-to="none"`, a selector for the host's element the panel rises from, right-aligned to it. The `anchor` property takes an element instead and wins over the attribute. Live. |
209
213
  | `data-dragging` | — | **Written by the element, not by you.** Stamped on whichever handle a gesture is currently using, so the styles can react and so the element knows not to re-place the widget under a drag in progress. Cleared on `pointerup` and on `pointercancel`. |
210
214
  | `data-expand-corner` | — | **Written by the element, not by you.** It stamps the corner a dragged or agent-moved panel opens from, so the collapse animation starts where the panel actually is. Listed because the element reads its own stamp back; setting it yourself is overwritten on the next move. |
211
215
  | `data-greeting` | — | CSS-only: `off` turns the [greeting layout](#the-greeting-on-an-empty-page) off under `placement="page"`, where it is on by default. Any other value turns it on under `placement="embedded"`. No other placement has it. |
@@ -234,7 +238,7 @@ same way: `icon-send`, `icon-stop`, `icon-attach`, `icon-voice`.
234
238
 
235
239
  The element observes two groups of attributes, and they behave differently once it is in the DOM.
236
240
  Nothing outside those groups is observed: a CSS-only attribute (`theme`, `density`, `data-side`,
237
- `data-answer-well`) is read by the stylesheet rather than by script, and `endpoint`,
241
+ `data-answer-well`, `data-tool-group-display`) is read by the stylesheet rather than by script, and `endpoint`,
238
242
  `data-tool-display`, `data-text-animation`, `data-runs-url`, `data-page-actions`,
239
243
  `data-message-actions`, `data-max-tool-rounds`, `data-unread-badge`, `data-launcher-drag` and
240
244
  `data-quote-selection` are re-read at each use, so a late write to any of those simply takes effect. The one attribute in
@@ -242,7 +246,7 @@ neither camp is `data-launcher-icon-url`: it is read while the element connects,
242
246
  below, but is not observed, so a late write is inert and says nothing.
243
247
 
244
248
  **Live attributes.** Written at any time, before or after the element connects, and acted on
245
- either way: `title-text`, `placement`, `credentials`, `user-key`, `user-name`.
249
+ either way: `title-text`, `placement`, `collapse-to`, `anchor`, `credentials`, `user-key`, `user-name`.
246
250
 
247
251
  **Connect-time attributes.** Read once, while the element connects, to decide what chrome exists at
248
252
  all — the tray, the mic, the skills menu, the header mark. Writing one afterwards has **no effect**;
@@ -265,8 +269,8 @@ element after the fact.
265
269
  `approveWithEdits`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`, `getPageMap`,
266
270
  `autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`, `navigationResult`,
267
271
  `skillContext`, `toolSummaries`, `formatToolPayload`, `formatRelativeTime`, `strings`,
268
- `resolvePageTarget`, `sharedState`, `extraStyles` ([unstable](#d-extrastyles-the-last-mile)), plus the read-only `unread`, `unhandledActivityTypes`, `threadId` and `running`, and
269
- the attribute mirrors `endpoint` / `userKey` / `userName` / `toolDisplay` / `collapsed` /
272
+ `resolvePageTarget`, `sharedState`, `anchor` (an element, which wins over the attribute's selector), `extraStyles` ([unstable](#d-extrastyles-the-last-mile)), plus the read-only `unread`, `unhandledActivityTypes`, `threadId`, `running` and `barHeight`, and
273
+ the attribute mirrors `endpoint` / `userKey` / `userName` / `toolDisplay` / `toolGroupDisplay` / `collapsed` /
270
274
  `credentials`.
271
275
 
272
276
  `headers` and `getHeaders` authenticate **every** request the element makes, not only the agent
@@ -1065,9 +1069,19 @@ dark scheme. When a call passes no `color`, the first of these that is set wins:
1065
1069
  1. `--ag-ui-highlight-ring-color` on the target (so `:root` reaches every ring);
1066
1070
  2. `--ag-ui-highlight-ring-color` on the chat — on `ag-ui-chat` or anything above it;
1067
1071
  3. the target's `--ag-ui-accent`, so a page that themes its accent keeps the rings it has;
1068
- 4. the chat's resolved accent, its theme's default included;
1072
+ 4. the chat's resolved accent, its theme's default included, held to 3:1 against the page (below);
1069
1073
  5. the package indigo, when no chat is on the page at all.
1070
1074
 
1075
+ The chat's own accent follows the widget's scheme, not the page's, so a dark widget's near-white
1076
+ accent would all but vanish on a light page. When the ring falls to step 4 it is measured against
1077
+ **the surface behind the target**: the background of the target's parent, and of each element
1078
+ above it until one is opaque, composited over the page canvas (which follows the root's
1079
+ `color-scheme`). The target's own fill is not that surface, because the ring is drawn outside its
1080
+ box: a white field on a dark page is ringed on the dark page. Background images are not read. The
1081
+ ring takes the first of these that holds 3:1 there (WCAG 2.4.13): the accent as the chat resolves
1082
+ it, the other half of the chat's `light-dark()` pair, or the accent stepped toward black or white.
1083
+ Steps 1 to 3 are a host's own choice and are drawn as given, contrast or not.
1084
+
1071
1085
  ```css
1072
1086
  /* Rings in the brand colour, whatever the widget's accent. */
1073
1087
  ag-ui-chat { --ag-ui-highlight-ring-color: #0f766e; }
@@ -1160,9 +1174,11 @@ matching JS API:
1160
1174
  widget is collapsed, and the page is not scrolled to it. A restore still loading or replaying
1161
1175
  the conversation being left stops there, so nothing of it is drawn into the new chat, and a
1162
1176
  run it was about to resume after a reload is not resumed: coming back to that conversation
1163
- later answers the interrupted call as not finished.
1177
+ later answers the interrupted call as not finished. Called before the element connects (or while
1178
+ it is out of the document), it waits: `threadId` stays `null` and nothing fires until the
1179
+ element connects, which then starts the new chat, so the one `ag-ui-thread` event is a `new`.
1164
1180
  - `describeSurface()` — where the panel is and what can be done to it: placement, collapsed,
1165
- whether it can be moved, whether it fills the screen, its box and the viewport. `movable` folds
1181
+ what it collapses to (`collapseTo`), whether it can be moved, whether it fills the screen, its box and the viewport. `movable` folds
1166
1182
  the two reasons a move can fail into the one answer a caller needs.
1167
1183
  - `moveTo(corner)` — send the panel to `top-left` / `top-right` / `bottom-left` / `bottom-right`,
1168
1184
  returning whether it went. It takes the axes the same way a user drag does, so the launcher
@@ -1194,7 +1210,9 @@ one on screen with the drawer's own paths:
1194
1210
  the drawer does. Returns a `Promise<void>` that resolves once the thread is drawn. **A run in
1195
1211
  flight is cancelled**, as the drawer and `newChat()` cancel it; read `running` first if you
1196
1212
  would rather wait. Switching to the thread already on screen does nothing, and so does a call
1197
- before the element connects.
1213
+ before the element connects; a second call for the thread that is still loading settles with
1214
+ that load, after the replay. If the store rejects the load, the element stays on the thread it
1215
+ was on, redraws it, fires no `ag-ui-thread`, and the promise rejects with the store's error.
1198
1216
  - `deleteThread(threadId)` — delete a stored thread, as the drawer row's delete does. Deleting
1199
1217
  the thread on screen cancels its run and puts a fresh thread in its place; deleting another
1200
1218
  leaves the screen alone. Prefer it to `conversationStore.clear()`, which deletes the record and
@@ -1382,6 +1400,105 @@ ag-ui-chat {
1382
1400
  }
1383
1401
  ```
1384
1402
 
1403
+ ### Collapsing to a bottom bar
1404
+
1405
+ `collapse-to` chooses what a collapsed panel becomes, for a host whose layout has a bottom
1406
+ toolbar. It is opt-in: absent, `auto`, or any other value leaves every placement as the
1407
+ sections above describe. It is live, like `placement`.
1408
+
1409
+ - **`bar`: the component's own bar.** A strip along the whole bottom edge of the viewport, in
1410
+ place of the round launcher: the title, the [unread badge](#the-unread-badge), and a running
1411
+ indicator while an interaction is in flight. Pressing the strip toggles the panel, which rises
1412
+ from the bar's right end with the bar still beneath it as the minimise target. The bar's
1413
+ history button opens the conversation list as a popover above the bar, without expanding the
1414
+ panel, its outer edge in line with the panel's on a wide viewport, and a second press closes
1415
+ it; `openThreads()` while collapsed opens it too. Expanding the panel closes the popover, so the
1416
+ panel opens on the conversation. It replaces the launcher only, so it applies to the corner
1417
+ placements, `side` and `full`: `sidebar` keeps its rail, `embedded` its header bar, and `page`
1418
+ has no collapsed state at all.
1419
+ - **`none`: your own bar.** Collapsed is hidden: nothing of the widget is left to see or hit,
1420
+ under every collapsible placement. Your bar's button drives the [public
1421
+ API](#driving-the-conversation-from-your-own-chrome) (`collapsed`, `toggleCollapsed()`), and
1422
+ `anchor` names the element the panel rises from, right-aligned to it. An anchor that resolves
1423
+ to nothing opens the panel from the placement's own corner, with one console warning.
1424
+
1425
+ ```html
1426
+ <nav class="toolbar">
1427
+ <!-- ... -->
1428
+ <button id="agent-button" type="button">Agent</button>
1429
+ </nav>
1430
+ <ag-ui-chat endpoint="/agent/" collapse-to="none" anchor="#agent-button"></ag-ui-chat>
1431
+ <script type="module">
1432
+ const chat = document.querySelector("ag-ui-chat");
1433
+ const button = document.getElementById("agent-button");
1434
+ button.addEventListener("click", () => chat.toggleCollapsed());
1435
+ // Under none the element announces the state it mounted in, once, so the
1436
+ // button can be labelled before anyone presses it.
1437
+ chat.addEventListener("ag-ui-toggle", (e) => {
1438
+ button.setAttribute("aria-pressed", String(!e.detail.collapsed));
1439
+ });
1440
+ </script>
1441
+ ```
1442
+
1443
+ **Clearing your toolbar.** Under `none` the panel stands `--ag-ui-anchor-gap` (default `8px`)
1444
+ above the anchor's top edge. The anchor is the button, not the toolbar around it, so a button
1445
+ sitting inside a taller toolbar puts the panel's foot across the toolbar's top border. Set the gap
1446
+ to the button's distance from the toolbar's top plus the clearance you want:
1447
+
1448
+ ```css
1449
+ /* The button stands 7px below the toolbar's top edge; clear it by 8px. */
1450
+ ag-ui-chat {
1451
+ --ag-ui-anchor-gap: 15px;
1452
+ }
1453
+ ```
1454
+
1455
+ A gap wider than the gutter is taken off the panel's max height as well, so a tall panel still
1456
+ stops at the top of the window.
1457
+
1458
+ Under `none` the element dispatches `ag-ui-toggle` once as it connects, with whichever state it
1459
+ mounted in, collapsed or open. Under `auto` and `bar` it does not, as before. That event touches
1460
+ neither the stored state nor the unread count.
1461
+
1462
+ **The page's space is yours.** The bar is fixed over the page, so a host pads its own page by
1463
+ the bar's height. The component never writes your CSS, and a custom property cannot carry the
1464
+ height up to your page (it inherits down, never up), so it hands you a number:
1465
+ `barHeight` (CSS px, read-only) and an `ag-ui-bar-height` event (the `BAR_HEIGHT_EVENT`
1466
+ constant, detail `{ height }`) on every change, including a font-size change. Both read `0`
1467
+ where no bar is drawn: under `auto` and `none`, and on a phone while the panel is open.
1468
+
1469
+ ```js
1470
+ const chat = document.querySelector("ag-ui-chat");
1471
+ const pad = () => {
1472
+ document.documentElement.style.setProperty("--chat-bar-height", `${chat.barHeight}px`);
1473
+ };
1474
+ chat.addEventListener("ag-ui-bar-height", pad);
1475
+ pad();
1476
+ ```
1477
+
1478
+ ```css
1479
+ body {
1480
+ padding-bottom: var(--chat-bar-height, 0px);
1481
+ }
1482
+ ```
1483
+
1484
+ **No inset needed.** Under `bar` and `none` you do not set `--ag-ui-inset` to keep the panel off
1485
+ your toolbar: its max height is the viewport less the bar's height (or, under `none`, the
1486
+ distance to the anchor's top) and one gutter, and its bottom sits on the bar or the anchor.
1487
+
1488
+ **Docked means not moved.** A docked panel cannot be dragged by its header or launcher, and
1489
+ `moveTo` refuses, because a position the user chose would take it away from the bar or the
1490
+ anchor. Resizing still works on wide viewports: the edges standing on the bar or the anchor
1491
+ stay put and the free ones follow the grip, and `resetSize()` still drops the dragged size.
1492
+
1493
+ **Phones.** At 600px or less the open panel stays full-bleed, over the bar, and the header's
1494
+ minimise control is the way back; the bar shows only while collapsed, so no second strip sits
1495
+ at the bottom edge while the keyboard is up.
1496
+
1497
+ The bar is styled through the parts `bar` (the strip's wrapper, holding the launcher and the
1498
+ history button) and `bar-history`, and the launcher's own parts (`launcher`, `rail-label`,
1499
+ `launcher-badge`) inside it.
1500
+
1501
+
1385
1502
  ---
1386
1503
 
1387
1504
  ## Charts
@@ -1557,7 +1674,7 @@ absent or unknown value reads as `grouped`):
1557
1674
 
1558
1675
  - `grouped` — the default. A run's consecutive tool calls fold into one quiet row, **"Worked
1559
1676
  through 5 steps"**, with a note such as **"1 failed"** beside it, and the calls one click down
1560
- as `full` cards. Anything else in the turn (text, a confirmation, a question) ends the group, and
1677
+ as `full` cards, or in the mode `data-tool-group-display` names. Anything else in the turn (text, a confirmation, a question) ends the group, and
1561
1678
  the next call starts a new one; a call's own rendered output does not. A group of one call shows that call's own label. See
1562
1679
  [The step group](#the-step-group).
1563
1680
  - `inline` — the lightest mode: a single status row (icon + summary, no card chrome) with the
@@ -1591,6 +1708,25 @@ each card is built: switching **to** `grouped` folds only the calls drawn afterw
1591
1708
  switching **away** from it flattens the groups already drawn, by CSS, into exactly the cards the
1592
1709
  new mode builds (the group stops generating a box and its row is hidden).
1593
1710
 
1711
+ ### Choosing how a group draws its calls
1712
+
1713
+ Under `grouped`, an opened group draws its calls as `full` cards unless the
1714
+ `data-tool-group-display` attribute (or `toolGroupDisplay` property) names another of the card
1715
+ modes: `compact`, `minimal` or `inline`. Each draws the regions the flat card of that mode draws,
1716
+ laid out as a row of the group's list rather than as a boxed card. An absent or unknown value
1717
+ reads as `full`, so nothing changes for a host that does not set it.
1718
+
1719
+ ```html
1720
+ <ag-ui-chat endpoint="/agent/" data-tool-group-display="compact"></ag-ui-chat>
1721
+ ```
1722
+
1723
+ It is read only under `grouped`. Under one of the four card modes the groups are flattened into
1724
+ that mode's cards and the inner value is ignored; switching back to `grouped` applies it again.
1725
+ It is live like the outer attribute: changing it restyles groups already in the transcript.
1726
+
1727
+ A call waiting on an approval shows its arguments and its Approve and Deny buttons in every inner
1728
+ mode, as it does in every card mode, and Approve takes focus when the group opens for it.
1729
+
1594
1730
  ### The step group
1595
1731
 
1596
1732
  Under `grouped` the row reads, by state:
@@ -2844,6 +2980,7 @@ re-export point. Internal modules import from leaf paths.
2844
2980
  | `SubmitDetail` | type | `detail` shape of the submit event. |
2845
2981
  | `ThreadDetail` | type | `detail` shape of the `ag-ui-thread` event (`{ threadId, previousThreadId, reason }`). |
2846
2982
  | `ToggleDetail` | type | `detail` shape of the `ag-ui-toggle` event (`{ collapsed }`). |
2983
+ | `BarHeightDetail` | type | `detail` shape of the `ag-ui-bar-height` event (`{ height }`). |
2847
2984
  | `UnreadDetail` | type | `detail` shape of the `ag-ui-unread` event (`{ unread }`). |
2848
2985
  | `AttachmentsDetail` | type | `detail` shape of the `ag-ui-attachments` event (`{ attachments, pending }`). |
2849
2986
  | `StateDetail` | type | `detail` shape of the `ag-ui-state` event (`{ state }`). |
@@ -2876,7 +3013,7 @@ re-export point. Internal modules import from leaf paths.
2876
3013
  | `PAGE_ACTIONS` | const | The page-action opt-in tokens (`scroll` / `drag`). |
2877
3014
  | `createChatSurfaceTools(surface)` | function | Build the opt-in `read_chat_surface` / `move_chat` / `minimise_chat` / `restore_chat` tools, which let the agent move the panel it is speaking from. |
2878
3015
  | `ChatSurface` | type | The narrow port those tools drive — `describeSurface` / `moveTo` / `setCollapsed`. The element satisfies it. |
2879
- | `ChatSurfaceReport` | type | What `describeSurface()` answers: placement, collapsed, collapsible, movable, draggable, fullBleed, and the panel's box against the viewport it sits in. |
3016
+ | `ChatSurfaceReport` | type | What `describeSurface()` answers: placement, collapsed, collapseTo, collapsible, movable, draggable, fullBleed, and the panel's box against the viewport it sits in. |
2880
3017
  | `ChatCorner` | type | `"top-left"` / `"top-right"` / `"bottom-left"` / `"bottom-right"` — the argument to `moveTo`. |
2881
3018
  | `CHAT_CORNERS` | const | Those four, as a list. |
2882
3019
  | `isChatCorner(value)` | function | Whether a string names one of them. |
@@ -2951,6 +3088,7 @@ re-export point. Internal modules import from leaf paths.
2951
3088
  | --- | --- | --- |
2952
3089
  | `ToolCallCard` | class | A live tool-call card for the transcript. |
2953
3090
  | `ToolCallStatus` / `SettledStatus` / `ToolDisplayMode` | type | Card lifecycle states + display mode. |
3091
+ | `ToolGroupDisplayMode` | type | How an opened step group draws its calls (`TOOL_GROUP_DISPLAY`'s values). |
2954
3092
  | `ToolPayloadFormatter` | type | Draws one region of a card's body (`AgUiChat.formatToolPayload`); `null` falls through to the built-in pretty-print. |
2955
3093
  | `ToolPayload` | type | The region being drawn: `arguments` (the parsed record) or `result` (the raw string and its outcome). |
2956
3094
  | `ToolCallCardOptions` | type | Per-card wiring beyond name / args / label / strings — currently `formatPayload`. |
@@ -2994,6 +3132,7 @@ re-export point. Internal modules import from leaf paths.
2994
3132
  | `SUBMIT_EVENT` | The submit CustomEvent name. |
2995
3133
  | `THREAD_EVENT` | The conversation-changed CustomEvent name (`ag-ui-thread`). |
2996
3134
  | `TOGGLE_EVENT` | The collapse-toggle CustomEvent name (`ag-ui-toggle`). |
3135
+ | `BAR_HEIGHT_EVENT` | The bottom bar's height CustomEvent name (`ag-ui-bar-height`). |
2997
3136
  | `UNREAD_EVENT` | The unread-count CustomEvent name (`ag-ui-unread`). |
2998
3137
  | `RUN_FINISHED_EVENT` | The interaction-finished CustomEvent name (`ag-ui-run-finished`). |
2999
3138
  | `CUSTOM_AGENT_EVENT` | The agent-`CUSTOM` CustomEvent name (`ag-ui-custom`). |
@@ -3013,6 +3152,7 @@ re-export point. Internal modules import from leaf paths.
3013
3152
  | `MESSAGE_ACTIONS` | The message-action tokens `data-message-actions` selects by (`copy` / `retry` / `feedback`). |
3014
3153
  | `TOOL_CALL_STATUS` | Tool-call card status constants. |
3015
3154
  | `TOOL_DISPLAY` | Tool-call display-mode constants (`inline` / `minimal` / `compact` / `full`). |
3155
+ | `TOOL_GROUP_DISPLAY` | How an opened step group draws its calls under `grouped` (`inline` / `minimal` / `compact` / `full`). |
3016
3156
  | `TOOL_OUTCOME` | How a tool call ended, as a server states it on `TOOL_CALL_RESULT` (`success` / `failed` / `denied`). |
3017
3157
  | `X_CONFIRM_KEY` | Confirmation-prompt key: on a tool's JSON Schema for a client-side confirmation, and in an AG-UI interrupt's `metadata` for a server-side approval. |
3018
3158
  | `X_SUMMARY_KEY` | JSON-Schema key carrying a short tool-card label. |
@@ -3124,11 +3264,20 @@ first, so each stays an override point. The tokens the themes add:
3124
3264
  - **Charts:** `--ag-ui-chart-1` to `--ag-ui-chart-6`. A host's value beats the theme's.
3125
3265
  - **Status marks:** `--ag-ui-ico-check`, `--ag-ui-ico-error`, `--ag-ui-ico-declined`, mask images
3126
3266
  drawn in the state's colour, and `--ag-ui-ico-chevron`, the step group's disclosure chevron.
3127
- - **Focus:** `--ag-ui-ring`, the step group head's focus outline (the accent at 55%).
3267
+ - **Focus:** `--ag-ui-ring`, the step group head's focus outline. Unset, it is two parts accent
3268
+ to three parts ink (`--ag-ui-fg`), which holds 3:1 against the panel for any accent.
3128
3269
 
3129
3270
  The font is part of the theme: Quiet, Soft and Ledger set their own stack (Inter, then the system
3130
- UI font), and Classic inherits the page's font as the component always did. Set
3131
- `--ag-ui-font: inherit` to take the page's font under any theme.
3271
+ UI font), and Classic inherits the page's font as the component always did. To take the page's font
3272
+ under any theme, set `--ag-ui-font` to the page's family, or to the page's own font token:
3273
+
3274
+ ```css
3275
+ ag-ui-chat { --ag-ui-font: var(--body-font); } /* or the family itself */
3276
+ ```
3277
+
3278
+ `--ag-ui-font: inherit` does not do this. A CSS-wide keyword in a custom property applies to the
3279
+ custom property itself, so it takes the `--ag-ui-font` of the parent, which nothing sets, and the
3280
+ theme's stack wins.
3132
3281
 
3133
3282
  #### Text on the accent
3134
3283
 
@@ -3318,6 +3467,10 @@ keeps between itself and that box. The size cap subtracts the same one, so a
3318
3467
  panel grown to its limit reaches the far edge of the usable box and no further —
3319
3468
  set it to `0` for a panel flush against the corner.
3320
3469
 
3470
+ `--ag-ui-anchor-gap` (default `8px`) is the gap between the panel and the button
3471
+ it rises from under `collapse-to="none"`; see [Collapsing to a bottom
3472
+ bar](#collapsing-to-a-bottom-bar).
3473
+
3321
3474
  `--ag-ui-keyboard-inset` overrides the lift an on-screen keyboard earns. The
3322
3475
  widget measures the hidden band and publishes it as
3323
3476
  `--ag-ui-visual-viewport-inset-bottom`; state this one instead to outrank that
@@ -3454,7 +3607,7 @@ component sets, so a new one cannot ship undocumented.
3454
3607
  | --- | --- |
3455
3608
  | Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `greeting`, `pending`, `stopped`, `jump-latest`, and one per resize grip: `resize-handle` plus `resize-handle-top`, `resize-handle-bottom`, `resize-handle-left`, `resize-handle-right`, `resize-handle-top-left`, `resize-handle-top-right`, `resize-handle-bottom-left`, `resize-handle-bottom-right` |
3456
3609
  | Header buttons | `header-button` on each, plus `history-button`, `checkpoints-button`, `new-button`, `collapse-button`, `theme-toggle` |
3457
- | Collapsed widget | `launcher`, `launcher-icon`, `launcher-badge`, `rail-label` |
3610
+ | Collapsed widget | `launcher`, `launcher-icon`, `launcher-badge`, `rail-label`, `bar` (the [bottom bar](#collapsing-to-a-bottom-bar)'s wrapper, around the launcher), `bar-history` |
3458
3611
  | Answers | `answer` (the per-turn group), `message` (plus `message-user`, `message-assistant`), `code-copy` |
3459
3612
  | Reasoning | `thoughts`, `thoughts-toggle`, `thoughts-body`, `thoughts-label` |
3460
3613
  | Follow-up suggestions | `suggestions`, `suggestion-chip` |
@@ -3687,7 +3840,10 @@ mic appears even with no `data-transcribe-url`.
3687
3840
  `slot="header-actions"`. For convenience, the boolean `data-theme-toggle` attribute adds a built-in
3688
3841
  light⇄dark toggle to the header (part `theme-toggle`) that flips the scheme word of `theme` and
3689
3842
  persists the choice per tab. Only the scheme changes: `soft` becomes `soft dark`, `soft dark`
3690
- becomes `soft light`, and `auto` becomes `dark`, an explicit choice. Only the scheme is saved, too:
3843
+ becomes `soft light`, and `auto` becomes the opposite of what the system shows, an explicit
3844
+ choice. With no scheme word the toggle flips the scheme the panel is drawn in, so a host rule
3845
+ that sets the element's `color-scheme` (to follow the page's own dark mode) is flipped from,
3846
+ and the written word then holds against that rule. Only the scheme is saved, too:
3691
3847
  on the next load it is put beside whatever theme name the markup carries, so `dark` chosen on a
3692
3848
  `soft` page restores as `classic dark` on a `classic` one. Off by default so it never competes with
3693
3849
  a host-supplied control.