@artooi/ag-ui-web-component 0.47.0 → 0.49.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 (60) hide show
  1. package/CHANGELOG.md +205 -1
  2. package/README.md +235 -16
  3. package/dist/ag-ui-web-component.bundle.js +647 -181
  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 +44 -5
  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 +1184 -69
  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 +16 -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 +32 -19
  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/quiet_styles.d.ts.map +1 -1
  38. package/dist/ui/themes/scheme_styles.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/constants.ts +30 -2
  41. package/src/core/ag_ui_chat.ts +335 -28
  42. package/src/core/events/bar_height_detail.ts +5 -0
  43. package/src/dom/highlight_overlay.ts +2 -1
  44. package/src/dom/ring_color.ts +141 -4
  45. package/src/index.ts +4 -0
  46. package/src/tools/chat_surface_tools.ts +13 -5
  47. package/src/ui/history/conversation_history.ts +51 -7
  48. package/src/ui/placement/collapse_styles.ts +248 -2
  49. package/src/ui/placement/dock_styles.ts +95 -0
  50. package/src/ui/placement/panel_placement.ts +300 -16
  51. package/src/ui/placement/small_viewport_styles.ts +9 -0
  52. package/src/ui/placement/surface_for.ts +58 -22
  53. package/src/ui/progress/tool_group_styles.ts +52 -2
  54. package/src/ui/progress/tool_step_styles.ts +5 -3
  55. package/src/ui/shell/build_launcher.ts +32 -5
  56. package/src/ui/shell/token_styles.ts +58 -1
  57. package/src/ui/styles.ts +4 -0
  58. package/src/ui/themes/quiet_styles.ts +19 -3
  59. package/src/ui/themes/scheme_styles.ts +10 -1
  60. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,208 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.49.0] — 2026-11-07
11
+
12
+ ### Added
13
+
14
+ - **`--ag-ui-header-btn-hover`, the header buttons' hover square.** Drawn
15
+ exactly as given under Quiet and a host's own theme word, whether or not
16
+ `--ag-ui-header-fg` is set. Unset, the square follows the header text as
17
+ below, or is the panel's hover shade when that is unset too. A host that
18
+ prefers the lightening square of its old `::part(header-button):hover`
19
+ rule sets this token to that colour instead of keeping the rule.
20
+
21
+ ### Changed
22
+
23
+ - **A docked panel moves and remembers where it was put.** Under
24
+ `collapse-to="bar"`, and under `collapse-to="none"` with or without an
25
+ `anchor`, a corner panel on a wide viewport now moves by its header and by
26
+ `moveTo`, like the default corner panel. Minimising and reopening, and a
27
+ reload, bring it back where it was dragged, and a panel never dragged still
28
+ opens at the dock. A dragged panel stays inside the viewport and above the
29
+ bar, or above the anchor's top under `none` (the anchor gap no longer
30
+ applies once dragged), and a stored spot is held there if the window or the
31
+ bar changed since. Once dragged off, every resize edge follows its grip.
32
+ `resetSize()` returns a dragged docked panel to the dock and forgets the
33
+ stored spot; under `auto` it still drops only the size. The spot is stored
34
+ apart from the corner panel's, so a spot dragged under `auto` never lands on
35
+ a docked panel. On a phone the panel stays full-bleed and does not move.
36
+ `describeSurface()` and `read_chat_surface` report `movable: true` and
37
+ `draggable: true` for it on a wide viewport, and `move_chat` no longer
38
+ refuses with "the panel is docked". `side` and `full` under `bar`, and
39
+ `sidebar` and `embedded` under `none`, still own their position; they now
40
+ report `draggable: true` where they reported `false`, so a refused move
41
+ there names the placement rather than the dock or the page. **A host has
42
+ nothing to do**: every host using `bar` or `none` gets the movable panel on
43
+ upgrade. A host that wants the 0.48.0 fixed dock sets
44
+ `data-launcher-drag="false"`, which turns the header drag and `moveTo` off
45
+ here as it does on a corner panel.
46
+
47
+ ### Fixed
48
+
49
+ - **Quiet's header buttons follow `--ag-ui-header-fg`.** The accent recipe
50
+ (0.45.0) put the accent on the header, but Quiet drew the history, new-chat,
51
+ theme and collapse buttons in its third ink at 0.85 opacity, about 1.2:1 on
52
+ a `#3d5afe` header, and hovered them on the panel's near-white or near-black
53
+ shade. Once a host sets `--ag-ui-header-fg`, the buttons draw in it at full
54
+ opacity, and the hover square is the opposite of that text at 18% (darker
55
+ under white text, lighter under dark), so a hovered button reads at least
56
+ what the host's header pair reads: white on `#3d5afe` is 5.1:1 at rest and
57
+ about 7:1 hovered. Browsers without relative colour syntax (Firefox 120
58
+ to 127, Safari 17.5 and 17.6) mix the square from the text itself instead.
59
+ This applies under Quiet and under a host's own theme word, which starts
60
+ from Quiet, in every scheme; Soft, Ledger, Classic and code are unchanged.
61
+ Unset, nothing changes. **No opt-in is needed.** A host that
62
+ wrote `ag-ui-chat::part(header-button) { color: var(--ag-ui-header-fg) }`
63
+ and the `:hover` background beside it may delete both: kept, the colour
64
+ rule is harmless, but the page's 18% tint of the text still beats the new
65
+ square, and on a mid-tone accent it takes the hovered glyph under 4.5:1
66
+ (3.75:1 on `#3d5afe`). Delete the hover background at least; to keep a
67
+ lightening square, set `--ag-ui-header-btn-hover` to it. With the header
68
+ text set, the header buttons no longer read `--ag-ui-hover`; the rest of
69
+ the panel still does. A host that sets neither `--ag-ui-header-fg`
70
+ nor `--ag-ui-tool-fg`, the Django admin among them, sees no change.
71
+ - **`--ag-ui-tool-fg` reaches the calls in a step group.** Under the grouped
72
+ default (0.46.0) every call in a group was drawn in the second ink whatever
73
+ the token said, so the recipe's tool colour reached nothing. A set
74
+ `--ag-ui-tool-fg` now colours grouped calls under every theme; unset, they
75
+ keep the second ink, Classic included. **No opt-in is needed**, but a host
76
+ that set the token and kept the grouped default sees its calls in that
77
+ colour from this release.
78
+
79
+ ## [0.48.0] — 2026-11-07
80
+
81
+ ### Added
82
+
83
+ - **Choose how a step group draws its calls.** A new `data-tool-group-display`
84
+ attribute and `toolGroupDisplay` property, one of `full`, `compact`,
85
+ `minimal` or `inline`, set how an opened group draws its calls under
86
+ `grouped`. Each draws the regions the flat card of that mode draws, as a row
87
+ of the group. It is read only under `grouped`, is live, and leaves an
88
+ approval's arguments and buttons on screen in every mode. Exported as
89
+ `TOOL_GROUP_DISPLAY` with the `ToolGroupDisplayMode` type. **Opt-in**: an
90
+ absent or unknown value reads as `full`, which is what a group drew before,
91
+ so a host sees no change until it sets the attribute.
92
+ - **Collapse into a bottom bar.** A new live `collapse-to` attribute chooses
93
+ what a collapsed panel becomes. `bar` draws a strip along the bottom edge in
94
+ place of the round launcher, with the title, the unread badge, a running
95
+ mark and a history button that opens and closes the conversation list as a
96
+ popover;
97
+ the panel rises from the bar's right end. `none` hides the collapsed widget
98
+ so the host's own toolbar drives it, and the `anchor` attribute or property
99
+ names the host element the panel rises from. Under both, the panel's height
100
+ stops at the bar or the anchor, so `--ag-ui-inset` is not needed, and a
101
+ docked panel can be resized but not moved. The read-only `barHeight`
102
+ property and the `ag-ui-bar-height` event (`BAR_HEIGHT_EVENT`, detail
103
+ `BarHeightDetail`) give the host the height to pad its page by. Under
104
+ `none` the element dispatches `ag-ui-toggle` once at connect with the state
105
+ it mounted in. On a phone the open panel stays full-bleed and the bar shows
106
+ only while collapsed. New parts `bar` and `bar-history`, and
107
+ `describeSurface()` reports `collapseTo`. **Opt-in**: absent,
108
+ `collapse-to` reads as `auto`, which leaves every placement as it was.
109
+ - **Clear a host toolbar under `collapse-to="none"`.** The new
110
+ `--ag-ui-anchor-gap` token sets the gap between the panel and the anchor it
111
+ rises from, for a host whose button sits inside a taller toolbar; a gap
112
+ wider than the gutter also comes off the panel's max height, so a tall panel
113
+ stays on screen. **Opt-in**: absent, the gap stays 8px.
114
+
115
+ ### Changed
116
+
117
+ - **The chat surface tools describe every collapsed shape.** With
118
+ `data-page-actions` including `chat`, the `minimise_chat` tool no longer
119
+ promises a launcher: its description names the launcher, the rail, the
120
+ bottom bar or the page's own control, and its refusal on a placement with no
121
+ collapsed state no longer mentions a launcher. The report `describeSurface()`
122
+ returns, and so `read_chat_surface`, gains `collapseTo`, and a move
123
+ refused on a docked panel says it is docked. This reaches every host that
124
+ enables the chat tools, whatever `collapse-to` is: an agent prompt that
125
+ quoted the old wording sees new text. No host action is needed.
126
+ - **Tested against `@ag-ui/client` and `@ag-ui/core` 1.0.4.** The lockfile moves
127
+ from 1.0.3, and the declared range (`>=1.0.0 <2`) is unchanged, so a fresh
128
+ install of 0.47.0 already resolved 1.0.4. The element passes `threadId` to
129
+ the agent rather than reading the one 1.0.4 now creates on first read, and it
130
+ never edits a message the agent emits, which 1.0.4 now documents as
131
+ read-only. `zod` stays an undeclared peer of `@ag-ui/core`, as it was in
132
+ 1.0.3. **No opt-in** and no behavioural change.
133
+
134
+ - **The demo shows the 0.47.0 additions.** The playground gains a card under
135
+ the form with a control and a readout for each: the `above-composer` slot
136
+ (also a checkbox on the full-page route, where it travels with the centred
137
+ composer), the thread API (`threadId`, `newChat()`, `switchThread()` and a
138
+ log of `ag-ui-thread` events), the `tool-card-status-*` and `tool-group-*`
139
+ parts (drawn in one colour each, with a census of the parts in the
140
+ transcript) and the `--ag-ui-ring` token. Demo only, nothing shipped
141
+ changes, and **a host owes nothing**.
142
+
143
+ ### Fixed
144
+
145
+ - **A collapsed embedded chat no longer shows the above-composer slot.** The
146
+ collapse hid the composer and its stack but not the `above-composer` slot, so
147
+ a host's row stayed on screen under the header bar. The slot is hidden with
148
+ the composer and returns on expand. No opt-in: a host that kept content in
149
+ that slot to stay visible while collapsed should use the footer slot, which
150
+ collapse leaves alone.
151
+
152
+ - **`switchThread()` keeps the old thread when the store's load rejects.** The element
153
+ used to point at the new thread id, show an empty transcript and fire no
154
+ `ag-ui-thread`, so it named a thread it was not showing. It now stays on the thread
155
+ it was on, draws it again without an event, and the returned promise rejects with
156
+ the store's error. A second `switchThread()` for the thread still loading now
157
+ settles with that load, after the replay, instead of resolving at once. No host
158
+ opt-in.
159
+ - **`newChat()` before the element connects waits for the connect.** It used to mint a
160
+ thread in the unscoped store and fire `ag-ui-thread` for a chat that was not yet
161
+ shown, leaving `threadId` non-null against the README. Now `threadId` is `null` and
162
+ nothing fires until the element connects, which starts the new chat and fires one
163
+ `ag-ui-thread` with reason `new`. A host that called `newChat()` early and relied on
164
+ the immediate event or thread id reads them after connect instead.
165
+
166
+ - **Rings hold 3:1 against what they are drawn on.** The step group head's
167
+ focus ring was the accent at 55% over transparent, so a light host accent
168
+ fell to 1.6:1 on the default light panel (Django admin's `#79aec8`). Unset,
169
+ `--ag-ui-ring` is now two parts accent to three parts ink, which holds 3:1
170
+ against the panel for any accent in every theme and scheme; Quiet's ring
171
+ turns from a translucent grey to near-ink. The field highlight ring, when it
172
+ falls back to the chat's own accent, is now held to 3:1 against the surface
173
+ behind the target: a dark widget on a light page drew its near-white accent
174
+ at 1.1:1, and now draws the light half of its accent pair, or the accent
175
+ stepped toward black or white. **No opt-in is needed**, and a host's
176
+ `--ag-ui-ring`, `--ag-ui-highlight-ring-color` or page `--ag-ui-accent` is
177
+ still drawn exactly as given.
178
+
179
+ - **The docs no longer advise `--ag-ui-font: inherit`.** The README and the
180
+ 0.45.0 entry said it would keep the page's font under any theme. It does
181
+ nothing: a CSS-wide keyword in a custom property applies to the custom
182
+ property itself, so it takes the parent's `--ag-ui-font`, which nothing sets,
183
+ and the theme's stack wins. Set `--ag-ui-font` to the page's family or to a
184
+ page token instead, for example `--ag-ui-font: var(--body-font)`. A host that
185
+ followed the old advice has to change it to get the page's font.
186
+
187
+ - **A chat never dragged no longer inherits a dragged size.** The size a drag
188
+ writes inline on the host was left undeclared on every other chat, so a chat
189
+ slotted into a dragged one, or under any page rule naming the private
190
+ property, came out at that size. It is now declared on `:host` like every
191
+ other private property. **No opt-in is needed.**
192
+
193
+ - **Picking a thread from the history drawer no longer leaves an unhandled
194
+ rejection when the store cannot load it.** The drawer's pick dropped the promise
195
+ `switchThread()` returns, so a store whose load rejected surfaced as an unhandled
196
+ rejection on the page. The pick now ends that rejection itself, and the thread on
197
+ screen stays as it was. No host opt-in.
198
+
199
+ - **A gated call with no arguments no longer draws an empty arguments box.** Under
200
+ `data-tool-display="compact"` and `"inline"`, the rule that shows a deferred call's
201
+ arguments outranked the `hidden` attribute the card sets when there are none, so an
202
+ empty box sat above the Approve button. The rule now skips a hidden section. No host
203
+ opt-in.
204
+
205
+ - **The theme toggle flips the scheme the panel is drawn in.** With no `light` or
206
+ `dark` word in `theme`, as under `auto` on a dark system or when a host rule sets
207
+ the element's `color-scheme`, the first press wrote `dark` over a panel that was
208
+ already dark, and the glyph offered dark. The toggle and its glyph now read the
209
+ drawn scheme. A `light` or `dark` word also now holds against a host rule that
210
+ sets `color-scheme` on the element, which used to win over it. No host opt-in.
211
+
10
212
  ## [0.47.0] — 2026-11-07
11
213
 
12
214
  ### Added
@@ -4846,7 +5048,9 @@ hosts that both arrange the page the way it expects.
4846
5048
  ### Notes
4847
5049
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
4848
5050
 
4849
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.47.0...HEAD
5051
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.49.0...HEAD
5052
+ [0.49.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.48.0...v0.49.0
5053
+ [0.48.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.47.0...v0.48.0
4850
5054
  [0.47.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.46.0...v0.47.0
4851
5055
  [0.46.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.45.0...v0.46.0
4852
5056
  [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. Under either, a corner panel still moves by its header and `moveTo` on a wide viewport, and remembers where it was dragged. 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,17 +1174,22 @@ 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
1169
1185
  travels with it and switching placement hands them back. Returns `false` rather than pretending
1170
- when the placement owns its position or the panel fills the screen.
1186
+ when the placement owns its position or the panel fills the screen. Under `collapse-to="bar"`
1187
+ or `"none"` it moves the panel within the room above the bar or the anchor.
1171
1188
  - `resetSize()` — drop a size the user dragged, and its stored copy, so the placement's own
1172
1189
  size and your `--ag-ui-width` / `--ag-ui-height` apply again. For a host that enters a sized
1173
1190
  state of its own (an expanded mode): a user's drag outranks host tokens, so call this first.
1191
+ Under `collapse-to="bar"` or `"none"` it also returns a panel the user dragged off its dock to
1192
+ the dock and forgets the stored spot; a corner panel under `auto` keeps its dragged position.
1174
1193
  - `setCollapsed(collapsed)` / `toggleCollapsed()` — collapse or expand the widget. The state is
1175
1194
  reflected as the boolean `collapsed` attribute/property and persisted per-tab in
1176
1195
  `sessionStorage`, so it survives a reload.
@@ -1194,7 +1213,9 @@ one on screen with the drawer's own paths:
1194
1213
  the drawer does. Returns a `Promise<void>` that resolves once the thread is drawn. **A run in
1195
1214
  flight is cancelled**, as the drawer and `newChat()` cancel it; read `running` first if you
1196
1215
  would rather wait. Switching to the thread already on screen does nothing, and so does a call
1197
- before the element connects.
1216
+ before the element connects; a second call for the thread that is still loading settles with
1217
+ that load, after the replay. If the store rejects the load, the element stays on the thread it
1218
+ was on, redraws it, fires no `ag-ui-thread`, and the promise rejects with the store's error.
1198
1219
  - `deleteThread(threadId)` — delete a stored thread, as the drawer row's delete does. Deleting
1199
1220
  the thread on screen cancels its run and puts a fresh thread in its place; deleting another
1200
1221
  leaves the screen alone. Prefer it to `conversationStore.clear()`, which deletes the record and
@@ -1382,6 +1403,126 @@ ag-ui-chat {
1382
1403
  }
1383
1404
  ```
1384
1405
 
1406
+ ### Collapsing to a bottom bar
1407
+
1408
+ `collapse-to` chooses what a collapsed panel becomes, for a host whose layout has a bottom
1409
+ toolbar. It is opt-in: absent, `auto`, or any other value leaves every placement as the
1410
+ sections above describe. It is live, like `placement`.
1411
+
1412
+ - **`bar`: the component's own bar.** A strip along the whole bottom edge of the viewport, in
1413
+ place of the round launcher: the title, the [unread badge](#the-unread-badge), and a running
1414
+ indicator while an interaction is in flight. Pressing the strip toggles the panel, which rises
1415
+ from the bar's right end with the bar still beneath it as the minimise target. The bar's
1416
+ history button opens the conversation list as a popover above the bar, without expanding the
1417
+ panel, its outer edge in line with the panel's on a wide viewport, and a second press closes
1418
+ it; `openThreads()` while collapsed opens it too. Expanding the panel closes the popover, so the
1419
+ panel opens on the conversation. It replaces the launcher only, so it applies to the corner
1420
+ placements, `side` and `full`: `sidebar` keeps its rail, `embedded` its header bar, and `page`
1421
+ has no collapsed state at all.
1422
+ - **`none`: your own bar.** Collapsed is hidden: nothing of the widget is left to see or hit,
1423
+ under every collapsible placement. Your bar's button drives the [public
1424
+ API](#driving-the-conversation-from-your-own-chrome) (`collapsed`, `toggleCollapsed()`), and
1425
+ `anchor` names the element the panel rises from, right-aligned to it. An anchor that resolves
1426
+ to nothing opens the panel from the placement's own corner, with one console warning.
1427
+
1428
+ ```html
1429
+ <nav class="toolbar">
1430
+ <!-- ... -->
1431
+ <button id="agent-button" type="button">Agent</button>
1432
+ </nav>
1433
+ <ag-ui-chat endpoint="/agent/" collapse-to="none" anchor="#agent-button"></ag-ui-chat>
1434
+ <script type="module">
1435
+ const chat = document.querySelector("ag-ui-chat");
1436
+ const button = document.getElementById("agent-button");
1437
+ button.addEventListener("click", () => chat.toggleCollapsed());
1438
+ // Under none the element announces the state it mounted in, once, so the
1439
+ // button can be labelled before anyone presses it.
1440
+ chat.addEventListener("ag-ui-toggle", (e) => {
1441
+ button.setAttribute("aria-pressed", String(!e.detail.collapsed));
1442
+ });
1443
+ </script>
1444
+ ```
1445
+
1446
+ **Clearing your toolbar.** Under `none` the panel stands `--ag-ui-anchor-gap` (default `8px`)
1447
+ above the anchor's top edge. The anchor is the button, not the toolbar around it, so a button
1448
+ sitting inside a taller toolbar puts the panel's foot across the toolbar's top border. Set the gap
1449
+ to the button's distance from the toolbar's top plus the clearance you want:
1450
+
1451
+ ```css
1452
+ /* The button stands 7px below the toolbar's top edge; clear it by 8px. */
1453
+ ag-ui-chat {
1454
+ --ag-ui-anchor-gap: 15px;
1455
+ }
1456
+ ```
1457
+
1458
+ A gap wider than the gutter is taken off the panel's max height as well, so a tall panel still
1459
+ stops at the top of the window.
1460
+
1461
+ Under `none` the element dispatches `ag-ui-toggle` once as it connects, with whichever state it
1462
+ mounted in, collapsed or open. Under `auto` and `bar` it does not, as before. That event touches
1463
+ neither the stored state nor the unread count.
1464
+
1465
+ **The page's space is yours.** The bar is fixed over the page, so a host pads its own page by
1466
+ the bar's height. The component never writes your CSS, and a custom property cannot carry the
1467
+ height up to your page (it inherits down, never up), so it hands you a number:
1468
+ `barHeight` (CSS px, read-only) and an `ag-ui-bar-height` event (the `BAR_HEIGHT_EVENT`
1469
+ constant, detail `{ height }`) on every change, including a font-size change. Both read `0`
1470
+ where no bar is drawn: under `auto` and `none`, and on a phone while the panel is open.
1471
+
1472
+ ```js
1473
+ const chat = document.querySelector("ag-ui-chat");
1474
+ const pad = () => {
1475
+ document.documentElement.style.setProperty("--chat-bar-height", `${chat.barHeight}px`);
1476
+ };
1477
+ chat.addEventListener("ag-ui-bar-height", pad);
1478
+ pad();
1479
+ ```
1480
+
1481
+ ```css
1482
+ body {
1483
+ padding-bottom: var(--chat-bar-height, 0px);
1484
+ }
1485
+ ```
1486
+
1487
+ **No inset needed.** Under `bar` and `none` you do not set `--ag-ui-inset` to keep the panel off
1488
+ your toolbar: its max height is the viewport less the bar's height (or, under `none`, the
1489
+ distance to the anchor's top) and one gutter, and its bottom sits on the bar or the anchor.
1490
+
1491
+ **Docked, and still movable.** On a wide viewport a docked corner panel (the corner placements
1492
+ under `bar`, or under `none` with or without an anchor) moves by its header, and `moveTo` moves
1493
+ it, as it would the corner panel it is; `describeSurface()` reports `movable: true`. There is no
1494
+ launcher to drag: the bar and your own control stay where they are.
1495
+
1496
+ - **Above the dock.** A dragged panel stays inside the viewport and above the bar's top edge, so it
1497
+ never covers the bar it minimises to. Under `none` with an anchor it stays above the anchor's
1498
+ top edge the same way, because the anchor is your own way back and has to stay in view;
1499
+ `--ag-ui-anchor-gap` no longer applies once the panel is dragged, and the screen-edge margin
1500
+ (8px) does. Under `none` with no anchor only the viewport holds it, as for a corner panel.
1501
+ - **Remembered like a corner panel.** Minimising and reopening brings it back where it was
1502
+ dragged, and so does a reload. The spot is kept apart from the one a corner panel stores under
1503
+ `auto`, so switching `collapse-to` never puts one surface's spot on the other. A spot that no
1504
+ longer fits, because the window shrank or the bar grew, is held inside the room that is left
1505
+ and comes back whole when there is room again.
1506
+ - **Resizing.** A panel standing on its dock keeps the edges on the bar or the anchor still and the
1507
+ free ones follow the grip. Once dragged off, every edge follows its grip, as on a corner panel.
1508
+ - **Back to the dock.** `resetSize()` drops the dragged size and returns the panel to its dock,
1509
+ forgetting the stored spot, so a reload opens it there too.
1510
+ - **Keeping it fixed.** `data-launcher-drag="false"` turns the header drag and `moveTo` off here as
1511
+ everywhere else, and the panel stays on its dock.
1512
+
1513
+ Placements that own their position (`side` and `full` under `bar`, `sidebar` and `embedded`
1514
+ under `none`) do not move, as without `collapse-to`.
1515
+
1516
+ **Phones.** At 600px or less the open panel stays full-bleed, over the bar, and the header's
1517
+ minimise control is the way back; the bar shows only while collapsed, so no second strip sits
1518
+ at the bottom edge while the keyboard is up. The panel does not move there, and a spot dragged on
1519
+ a wide viewport does not apply: it comes back when the window is wide again.
1520
+
1521
+ The bar is styled through the parts `bar` (the strip's wrapper, holding the launcher and the
1522
+ history button) and `bar-history`, and the launcher's own parts (`launcher`, `rail-label`,
1523
+ `launcher-badge`) inside it.
1524
+
1525
+
1385
1526
  ---
1386
1527
 
1387
1528
  ## Charts
@@ -1557,7 +1698,7 @@ absent or unknown value reads as `grouped`):
1557
1698
 
1558
1699
  - `grouped` — the default. A run's consecutive tool calls fold into one quiet row, **"Worked
1559
1700
  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
1701
+ 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
1702
  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
1703
  [The step group](#the-step-group).
1563
1704
  - `inline` — the lightest mode: a single status row (icon + summary, no card chrome) with the
@@ -1591,6 +1732,25 @@ each card is built: switching **to** `grouped` folds only the calls drawn afterw
1591
1732
  switching **away** from it flattens the groups already drawn, by CSS, into exactly the cards the
1592
1733
  new mode builds (the group stops generating a box and its row is hidden).
1593
1734
 
1735
+ ### Choosing how a group draws its calls
1736
+
1737
+ Under `grouped`, an opened group draws its calls as `full` cards unless the
1738
+ `data-tool-group-display` attribute (or `toolGroupDisplay` property) names another of the card
1739
+ modes: `compact`, `minimal` or `inline`. Each draws the regions the flat card of that mode draws,
1740
+ laid out as a row of the group's list rather than as a boxed card. An absent or unknown value
1741
+ reads as `full`, so nothing changes for a host that does not set it.
1742
+
1743
+ ```html
1744
+ <ag-ui-chat endpoint="/agent/" data-tool-group-display="compact"></ag-ui-chat>
1745
+ ```
1746
+
1747
+ It is read only under `grouped`. Under one of the four card modes the groups are flattened into
1748
+ that mode's cards and the inner value is ignored; switching back to `grouped` applies it again.
1749
+ It is live like the outer attribute: changing it restyles groups already in the transcript.
1750
+
1751
+ A call waiting on an approval shows its arguments and its Approve and Deny buttons in every inner
1752
+ mode, as it does in every card mode, and Approve takes focus when the group opens for it.
1753
+
1594
1754
  ### The step group
1595
1755
 
1596
1756
  Under `grouped` the row reads, by state:
@@ -2844,6 +3004,7 @@ re-export point. Internal modules import from leaf paths.
2844
3004
  | `SubmitDetail` | type | `detail` shape of the submit event. |
2845
3005
  | `ThreadDetail` | type | `detail` shape of the `ag-ui-thread` event (`{ threadId, previousThreadId, reason }`). |
2846
3006
  | `ToggleDetail` | type | `detail` shape of the `ag-ui-toggle` event (`{ collapsed }`). |
3007
+ | `BarHeightDetail` | type | `detail` shape of the `ag-ui-bar-height` event (`{ height }`). |
2847
3008
  | `UnreadDetail` | type | `detail` shape of the `ag-ui-unread` event (`{ unread }`). |
2848
3009
  | `AttachmentsDetail` | type | `detail` shape of the `ag-ui-attachments` event (`{ attachments, pending }`). |
2849
3010
  | `StateDetail` | type | `detail` shape of the `ag-ui-state` event (`{ state }`). |
@@ -2876,7 +3037,7 @@ re-export point. Internal modules import from leaf paths.
2876
3037
  | `PAGE_ACTIONS` | const | The page-action opt-in tokens (`scroll` / `drag`). |
2877
3038
  | `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
3039
  | `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. |
3040
+ | `ChatSurfaceReport` | type | What `describeSurface()` answers: placement, collapsed, collapseTo, collapsible, movable, draggable, fullBleed, and the panel's box against the viewport it sits in. |
2880
3041
  | `ChatCorner` | type | `"top-left"` / `"top-right"` / `"bottom-left"` / `"bottom-right"` — the argument to `moveTo`. |
2881
3042
  | `CHAT_CORNERS` | const | Those four, as a list. |
2882
3043
  | `isChatCorner(value)` | function | Whether a string names one of them. |
@@ -2951,6 +3112,7 @@ re-export point. Internal modules import from leaf paths.
2951
3112
  | --- | --- | --- |
2952
3113
  | `ToolCallCard` | class | A live tool-call card for the transcript. |
2953
3114
  | `ToolCallStatus` / `SettledStatus` / `ToolDisplayMode` | type | Card lifecycle states + display mode. |
3115
+ | `ToolGroupDisplayMode` | type | How an opened step group draws its calls (`TOOL_GROUP_DISPLAY`'s values). |
2954
3116
  | `ToolPayloadFormatter` | type | Draws one region of a card's body (`AgUiChat.formatToolPayload`); `null` falls through to the built-in pretty-print. |
2955
3117
  | `ToolPayload` | type | The region being drawn: `arguments` (the parsed record) or `result` (the raw string and its outcome). |
2956
3118
  | `ToolCallCardOptions` | type | Per-card wiring beyond name / args / label / strings — currently `formatPayload`. |
@@ -2994,6 +3156,7 @@ re-export point. Internal modules import from leaf paths.
2994
3156
  | `SUBMIT_EVENT` | The submit CustomEvent name. |
2995
3157
  | `THREAD_EVENT` | The conversation-changed CustomEvent name (`ag-ui-thread`). |
2996
3158
  | `TOGGLE_EVENT` | The collapse-toggle CustomEvent name (`ag-ui-toggle`). |
3159
+ | `BAR_HEIGHT_EVENT` | The bottom bar's height CustomEvent name (`ag-ui-bar-height`). |
2997
3160
  | `UNREAD_EVENT` | The unread-count CustomEvent name (`ag-ui-unread`). |
2998
3161
  | `RUN_FINISHED_EVENT` | The interaction-finished CustomEvent name (`ag-ui-run-finished`). |
2999
3162
  | `CUSTOM_AGENT_EVENT` | The agent-`CUSTOM` CustomEvent name (`ag-ui-custom`). |
@@ -3013,6 +3176,7 @@ re-export point. Internal modules import from leaf paths.
3013
3176
  | `MESSAGE_ACTIONS` | The message-action tokens `data-message-actions` selects by (`copy` / `retry` / `feedback`). |
3014
3177
  | `TOOL_CALL_STATUS` | Tool-call card status constants. |
3015
3178
  | `TOOL_DISPLAY` | Tool-call display-mode constants (`inline` / `minimal` / `compact` / `full`). |
3179
+ | `TOOL_GROUP_DISPLAY` | How an opened step group draws its calls under `grouped` (`inline` / `minimal` / `compact` / `full`). |
3016
3180
  | `TOOL_OUTCOME` | How a tool call ended, as a server states it on `TOOL_CALL_RESULT` (`success` / `failed` / `denied`). |
3017
3181
  | `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
3182
  | `X_SUMMARY_KEY` | JSON-Schema key carrying a short tool-card label. |
@@ -3124,11 +3288,20 @@ first, so each stays an override point. The tokens the themes add:
3124
3288
  - **Charts:** `--ag-ui-chart-1` to `--ag-ui-chart-6`. A host's value beats the theme's.
3125
3289
  - **Status marks:** `--ag-ui-ico-check`, `--ag-ui-ico-error`, `--ag-ui-ico-declined`, mask images
3126
3290
  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%).
3291
+ - **Focus:** `--ag-ui-ring`, the step group head's focus outline. Unset, it is two parts accent
3292
+ to three parts ink (`--ag-ui-fg`), which holds 3:1 against the panel for any accent.
3128
3293
 
3129
3294
  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.
3295
+ UI font), and Classic inherits the page's font as the component always did. To take the page's font
3296
+ under any theme, set `--ag-ui-font` to the page's family, or to the page's own font token:
3297
+
3298
+ ```css
3299
+ ag-ui-chat { --ag-ui-font: var(--body-font); } /* or the family itself */
3300
+ ```
3301
+
3302
+ `--ag-ui-font: inherit` does not do this. A CSS-wide keyword in a custom property applies to the
3303
+ custom property itself, so it takes the `--ag-ui-font` of the parent, which nothing sets, and the
3304
+ theme's stack wins.
3132
3305
 
3133
3306
  #### Text on the accent
3134
3307
 
@@ -3153,6 +3326,45 @@ fallback white (2.5:1), as it always was. What they miss is the correction for a
3153
3326
  mid-tone brand accent can fall under 4.5:1 there, under any theme. If you support those browsers
3154
3327
  and set a mid-tone accent, set `--ag-ui-on-accent` yourself. There is no script path.
3155
3328
 
3329
+ #### Keeping a brand accent on the header
3330
+
3331
+ Quiet draws the header, the launcher and the tool names in the panel's own colours, so a host that
3332
+ sets only `--ag-ui-accent` gets a neutral header. To put the accent back without leaving Quiet, set
3333
+ the surfaces to the accent and the text on them to a colour that reads on it:
3334
+
3335
+ ```css
3336
+ ag-ui-chat {
3337
+ --ag-ui-header-bg: #3d5afe;
3338
+ --ag-ui-header-fg: #ffffff;
3339
+ --ag-ui-launcher-bg: #3d5afe;
3340
+ --ag-ui-launcher-fg: #ffffff;
3341
+ --ag-ui-tool-fg: #3d5afe;
3342
+ }
3343
+ ```
3344
+
3345
+ - **The header buttons follow `--ag-ui-header-fg`.** Unset, Quiet draws them in its third ink at
3346
+ 0.85 opacity, on a hover square of the panel's hover shade. Once you set the header text they
3347
+ draw in it at full opacity, and the hover square is the opposite of that text at about a fifth
3348
+ (a darker square under white text, a lighter one under dark text), so a hovered button reads at
3349
+ least what your header pair reads. The same holds under a theme word of your own, which starts
3350
+ from Quiet. No `::part(header-button)` rule is needed. The opposite is computed with relative
3351
+ colour syntax; the browsers without it (see the fallback above) mix the square from the text
3352
+ itself, which on a mid-tone header can take the hovered glyph under your pair (white on
3353
+ `#3d5afe`: 3.75:1).
3354
+ - **`--ag-ui-header-btn-hover` sets the hover square outright** under Quiet and a theme word of your
3355
+ own, whether or not the header text is set, and is drawn exactly as given. Classic, Soft and
3356
+ Ledger draw their own squares and do not read it. For a square that lightens under white text
3357
+ instead, at the cost of contrast on a mid-tone header:
3358
+
3359
+ ```css
3360
+ ag-ui-chat { --ag-ui-header-btn-hover: color-mix(in srgb, #ffffff 18%, transparent); }
3361
+ ```
3362
+ - **`--ag-ui-tool-fg` colours every tool call**, in a step group as well as in the card displays.
3363
+ Unset, a grouped call is drawn in the second ink (`--ag-ui-ink-2`) under every theme.
3364
+ - Pick the pair yourself: `--ag-ui-header-fg` is not derived from the header colour the way
3365
+ `--ag-ui-on-accent` is from the accent. Under Soft and Ledger the header buttons keep their own
3366
+ third ink whatever you set.
3367
+
3156
3368
  #### Overriding a theme, lightest layer first
3157
3369
 
3158
3370
  Four layers, each documented and each lighter than the next. Reach for the first one that does
@@ -3318,6 +3530,10 @@ keeps between itself and that box. The size cap subtracts the same one, so a
3318
3530
  panel grown to its limit reaches the far edge of the usable box and no further —
3319
3531
  set it to `0` for a panel flush against the corner.
3320
3532
 
3533
+ `--ag-ui-anchor-gap` (default `8px`) is the gap between the panel and the button
3534
+ it rises from under `collapse-to="none"`; see [Collapsing to a bottom
3535
+ bar](#collapsing-to-a-bottom-bar).
3536
+
3321
3537
  `--ag-ui-keyboard-inset` overrides the lift an on-screen keyboard earns. The
3322
3538
  widget measures the hidden band and publishes it as
3323
3539
  `--ag-ui-visual-viewport-inset-bottom`; state this one instead to outrank that
@@ -3454,7 +3670,7 @@ component sets, so a new one cannot ship undocumented.
3454
3670
  | --- | --- |
3455
3671
  | 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
3672
  | 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` |
3673
+ | 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
3674
  | Answers | `answer` (the per-turn group), `message` (plus `message-user`, `message-assistant`), `code-copy` |
3459
3675
  | Reasoning | `thoughts`, `thoughts-toggle`, `thoughts-body`, `thoughts-label` |
3460
3676
  | Follow-up suggestions | `suggestions`, `suggestion-chip` |
@@ -3687,7 +3903,10 @@ mic appears even with no `data-transcribe-url`.
3687
3903
  `slot="header-actions"`. For convenience, the boolean `data-theme-toggle` attribute adds a built-in
3688
3904
  light⇄dark toggle to the header (part `theme-toggle`) that flips the scheme word of `theme` and
3689
3905
  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:
3906
+ becomes `soft light`, and `auto` becomes the opposite of what the system shows, an explicit
3907
+ choice. With no scheme word the toggle flips the scheme the panel is drawn in, so a host rule
3908
+ that sets the element's `color-scheme` (to follow the page's own dark mode) is flipped from,
3909
+ and the written word then holds against that rule. Only the scheme is saved, too:
3691
3910
  on the next load it is put beside whatever theme name the markup carries, so `dark` chosen on a
3692
3911
  `soft` page restores as `classic dark` on a `classic` one. Off by default so it never competes with
3693
3912
  a host-supplied control.