@artooi/ag-ui-web-component 0.48.0 → 0.50.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 (40) hide show
  1. package/CHANGELOG.md +192 -1
  2. package/README.md +70 -7
  3. package/dist/ag-ui-web-component.bundle.js +171 -107
  4. package/dist/ag-ui-web-component.bundle.js.map +3 -3
  5. package/dist/core/ag_ui_chat.d.ts +8 -2
  6. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  7. package/dist/index.js +591 -82
  8. package/dist/index.js.map +3 -3
  9. package/dist/tools/chat_surface_tools.d.ts +8 -5
  10. package/dist/tools/chat_surface_tools.d.ts.map +1 -1
  11. package/dist/ui/placement/dock_styles.d.ts.map +1 -1
  12. package/dist/ui/placement/panel_placement.d.ts +18 -1
  13. package/dist/ui/placement/panel_placement.d.ts.map +1 -1
  14. package/dist/ui/placement/surface_for.d.ts +15 -6
  15. package/dist/ui/placement/surface_for.d.ts.map +1 -1
  16. package/dist/ui/progress/tool_group_styles.d.ts.map +1 -1
  17. package/dist/ui/shell/frame_styles.d.ts.map +1 -1
  18. package/dist/ui/shell/token_styles.d.ts.map +1 -1
  19. package/dist/ui/themes/classic_tokens.d.ts.map +1 -1
  20. package/dist/ui/themes/ledger_styles.d.ts.map +1 -1
  21. package/dist/ui/themes/ledger_tokens.d.ts.map +1 -1
  22. package/dist/ui/themes/quiet_styles.d.ts.map +1 -1
  23. package/dist/ui/themes/soft_styles.d.ts.map +1 -1
  24. package/dist/ui/themes/soft_tokens.d.ts.map +1 -1
  25. package/package.json +1 -1
  26. package/src/core/ag_ui_chat.ts +56 -8
  27. package/src/tools/chat_surface_tools.ts +11 -10
  28. package/src/ui/placement/dock_styles.ts +5 -1
  29. package/src/ui/placement/panel_placement.ts +587 -82
  30. package/src/ui/placement/surface_for.ts +33 -8
  31. package/src/ui/progress/tool_group_styles.ts +3 -2
  32. package/src/ui/shell/frame_styles.ts +22 -2
  33. package/src/ui/shell/token_styles.ts +31 -0
  34. package/src/ui/themes/classic_tokens.ts +1 -0
  35. package/src/ui/themes/ledger_styles.ts +4 -3
  36. package/src/ui/themes/ledger_tokens.ts +1 -0
  37. package/src/ui/themes/quiet_styles.ts +6 -3
  38. package/src/ui/themes/soft_styles.ts +4 -3
  39. package/src/ui/themes/soft_tokens.ts +1 -0
  40. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,195 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.50.0] — 2026-11-08
11
+
12
+ ### Changed
13
+
14
+ - **The themes demo sets `--ag-ui-header-btn-hover`.** A new control on its
15
+ page draws the header buttons' hover square in a colour you pick. The suite
16
+ now tests that square as a browser without relative colour syntax draws it,
17
+ mixed from the header text, which only a string check saw before. Demo and
18
+ tests only, nothing shipped changes. **A host has nothing to do.**
19
+
20
+ ### Fixed
21
+
22
+ - **A corner panel that was moved goes full-bleed on a phone, and stays there.**
23
+ Under `collapse-to="auto"`, a panel moved by its header, its launcher or
24
+ `moveTo` on a wide window kept an inline `--ag-ui-inset` when the window
25
+ narrowed below the small-viewport breakpoint, which held it 8px off two edges
26
+ and past the other two. It now fills the screen, with no inline inset, while
27
+ its launcher stays on the screen for when it is collapsed. A header drag no
28
+ longer moves a panel that is the screen, as `moveTo` already refused to, and its
29
+ cursor stops offering one. The
30
+ place the user put the panel is now kept as they stated it: a window that
31
+ narrows, by any amount, holds the panel inside it without changing the record,
32
+ and widening gives that place back, as a reload already did. A gesture made in
33
+ the narrower window states a new one. **A host has nothing to do.**
34
+ - **A corner panel remembers the corner a resize grip moved.** Under
35
+ `collapse-to="auto"`, growing a moved panel by a grip on its free edges, which
36
+ moves its top-left corner while the layout holds the opposite one, was lost on
37
+ the next reopen or reload: the panel came back at its old top-left, grown away
38
+ from its launcher. It now comes back as it was drawn, a host's maximum included,
39
+ and `resetSize()` keeps the panel where the size came off. The top-left stays as the
40
+ grip left it while the host's own `--ag-ui-width` or `--ag-ui-height` is in force at
41
+ that moment, and the stored spot stays as it was under `data-launcher-drag="false"`.
42
+ **A host has nothing to do.**
43
+ - **`read_chat_surface` reports the page's setting for `draggable`, whatever the
44
+ placement.** A sidebar, embedded, side, page or full panel under `collapse-to="auto"`
45
+ reported `draggable: false` with `movable: false`, so `move_chat` answered that
46
+ the page had turned off moving the panel when the placement owned its position,
47
+ while the same placement under `collapse-to="none"` or `"bar"` reported `true`.
48
+ `draggable` is now `true` for every placement unless the page sets
49
+ `data-launcher-drag="false"`, and whether this panel moves stays in `movable`.
50
+ The refusal names the placement. **A host has nothing to do.**
51
+ - **Every theme's header buttons follow `--ag-ui-header-fg`.** 0.49.0 did this
52
+ for Quiet alone, so the accent recipe still fell short under the others:
53
+ white on a `#3d5afe` header read 1.1 to 1.6:1 at rest under Soft and Ledger,
54
+ and 4.16:1 at rest and 3.75:1 hovered under Classic and code. Once a host sets
55
+ `--ag-ui-header-fg`, the buttons draw in it at full opacity under every theme
56
+ and scheme, and the hover square is the opposite of that text at 18%: that
57
+ pair now reads 5.13:1 at rest and 6.95:1 hovered under all of them. What
58
+ changes for a host that sets it, by theme. Soft: the glyph was the third ink,
59
+ the accent's ink hovered, on a square of `--ag-ui-tint-2`. Ledger: the glyph
60
+ was the third ink, the full ink hovered, on a square of `--ag-ui-hover`.
61
+ Classic and code: the glyph already was the header text, but at 0.85 opacity,
62
+ and the square was 18% white, so under white text it now darkens where it
63
+ lightened. Browsers without relative colour syntax (Firefox 120 to 127,
64
+ Safari 17.5 and 17.6) mix the square from the text itself, as under Quiet, at
65
+ 3.75:1 hovered for that pair.
66
+ **`--ag-ui-header-btn-hover`, inert until now under Soft, Ledger, Classic and
67
+ code, works under them**, as it does under Quiet: drawn exactly as given,
68
+ whether or not the header text is set. A host that set it for Quiet and also
69
+ wears one of these themes will now see it. With neither token set, every
70
+ theme's header buttons are exactly as they were, whatever the header colour
71
+ or `--ag-ui-hover`. **No opt-in is needed.** A page's own
72
+ `::part(header-button)` rules still win: a host that wrote one to get here may
73
+ delete it, and should at least delete a `:hover` background of 18% of the text,
74
+ which keeps the glyph at 3.75:1 on a mid-tone accent. A host that sets neither
75
+ token, the Django admin among them, sees no change.
76
+ - **A corner panel a host's own size makes the window stands at 0,0.** A host that
77
+ fills a wide window with `--ag-ui-width` and `--ag-ui-height` found a panel the
78
+ user had moved standing at 8,8 after a window resize, 8px past the right and
79
+ bottom edges. It now exactly fills the window, from the start of whatever the
80
+ viewport inset tokens reserve, as a
81
+ phone's full-bleed panel does, and comes back to where the user put it when the
82
+ host's size drops. A host's own `--ag-ui-inset` rule that already stands the box
83
+ on the screen is left alone. **A host has nothing to do.**
84
+ - **A panel placed only by a resize grip goes full-bleed on a phone.** A grip on
85
+ an edge the layout holds moves an unmoved corner panel without storing a spot,
86
+ and that inline inset stayed on when the window narrowed, which held the panel
87
+ off two edges and past the other two. It now fills the screen with no inline
88
+ inset, and widening gives the place back. An inset the host wrote inline stays
89
+ where the host wrote it. An unmoved panel a host's own size makes the window
90
+ stands at 0,0 as well. **A host has nothing to do.**
91
+ - **`data-launcher-drag="false"` on a moved panel hands it to the host's CSS.**
92
+ Turned on after the user had moved the panel, the attribute left an inline
93
+ inset behind for good, which put the panel off the screen on a phone. Nothing
94
+ moves when it goes on. The next window resize or reopen rests the panel where
95
+ the host's CSS puts it, as a reload under the attribute always did, and on a
96
+ phone that is full-bleed. The stored spot stays, and comes back at the first
97
+ restore after the attribute comes off. **A host has nothing to do.**
98
+ - **Undoing a move after the surface changed leaves everything in agreement.** A
99
+ move undone once the panel had gone from a corner to a sidebar, a side panel, a
100
+ page or a dock wrote the corner's inset back onto the new surface, which pinned
101
+ a sidebar or a page off the screen and took a docked panel off its dock, and
102
+ left the stored spot on the move the reader had just undone. Going the other way
103
+ left a docked panel's stored spot on the move too. The undo now puts both
104
+ records back, in memory and in storage, and leaves the new surface standing where
105
+ its own layout puts it; the spot comes back with the surface it belongs to.
106
+ **A host has nothing to do.**
107
+ - **A moved panel answers a host's size when the size changes.** A change to
108
+ `--ag-ui-width` or `--ag-ui-height` fires no event, so a panel the user had moved
109
+ stayed as the last restore left it until the next window resize or reopen. Reopened
110
+ under a host's size that then dropped, it stood short of its spot, at 716,232 for a
111
+ spot of 376,96, with the stored spot still right; sized up with no reopen, it could
112
+ grow past the top of the window. It now goes back to its spot as the size drops, and
113
+ under a bigger size it stands where a reopen already put it, held on the screen from
114
+ that spot, rather than growing away from the corner the layout held. A window-filling
115
+ size stands it at 0,0 at once, with no resize needed. **A host has nothing to do.**
116
+ - **A moved panel answers a viewport inset token when the token changes.** A page
117
+ that publishes `--ag-ui-viewport-inset-top` from its own `ResizeObserver`, as the
118
+ README recommends, does so after the window's resize event, so the restore that event
119
+ starts read the bar at its old height. Widening a window past the width where the bar
120
+ shrinks left the panel 35px below its spot on the playground, top 224 for a spot of
121
+ 189, and narrowing left it under a bar that had grown. It now settles from the token
122
+ as it stands, a frame later, on any of the four viewport inset tokens and however
123
+ late the page writes them. **A host has nothing to do.**
124
+ - **The header's cursor no longer offers a drag that cannot happen.** A panel that
125
+ had become the screen on a narrow window with a mouse, or a docked panel under
126
+ `collapse-to="bar"` or `"none"` that a host's own size had made the window, showed
127
+ `cursor: move` although its header could not move it. The cursor is now `auto` there,
128
+ and stays `move` everywhere the drag is allowed. **A host has nothing to do.**
129
+
130
+ ## [0.49.0] — 2026-11-07
131
+
132
+ ### Added
133
+
134
+ - **`--ag-ui-header-btn-hover`, the header buttons' hover square.** Drawn
135
+ exactly as given under Quiet and a host's own theme word, whether or not
136
+ `--ag-ui-header-fg` is set. Unset, the square follows the header text as
137
+ below, or is the panel's hover shade when that is unset too. A host that
138
+ prefers the lightening square of its old `::part(header-button):hover`
139
+ rule sets this token to that colour instead of keeping the rule.
140
+
141
+ ### Changed
142
+
143
+ - **A docked panel moves and remembers where it was put.** Under
144
+ `collapse-to="bar"`, and under `collapse-to="none"` with or without an
145
+ `anchor`, a corner panel on a wide viewport now moves by its header and by
146
+ `moveTo`, like the default corner panel. Minimising and reopening, and a
147
+ reload, bring it back where it was dragged, and a panel never dragged still
148
+ opens at the dock. A dragged panel stays inside the viewport and above the
149
+ bar, or above the anchor's top under `none` (the anchor gap no longer
150
+ applies once dragged), and a stored spot is held there if the window or the
151
+ bar changed since. Once dragged off, every resize edge follows its grip.
152
+ `resetSize()` returns a dragged docked panel to the dock and forgets the
153
+ stored spot; under `auto` it still drops only the size. The spot is stored
154
+ apart from the corner panel's, so a spot dragged under `auto` never lands on
155
+ a docked panel. On a phone the panel stays full-bleed and does not move.
156
+ `describeSurface()` and `read_chat_surface` report `movable: true` and
157
+ `draggable: true` for it on a wide viewport, and `move_chat` no longer
158
+ refuses with "the panel is docked". `side` and `full` under `bar`, and
159
+ `sidebar` and `embedded` under `none`, still own their position; they now
160
+ report `draggable: true` where they reported `false`, so a refused move
161
+ there names the placement rather than the dock or the page. **A host has
162
+ nothing to do**: every host using `bar` or `none` gets the movable panel on
163
+ upgrade. A host that wants the 0.48.0 fixed dock sets
164
+ `data-launcher-drag="false"`, which turns the header drag and `moveTo` off
165
+ here as it does on a corner panel.
166
+
167
+ ### Fixed
168
+
169
+ - **Quiet's header buttons follow `--ag-ui-header-fg`.** The accent recipe
170
+ (0.45.0) put the accent on the header, but Quiet drew the history, new-chat,
171
+ theme and collapse buttons in its third ink at 0.85 opacity, about 1.2:1 on
172
+ a `#3d5afe` header, and hovered them on the panel's near-white or near-black
173
+ shade. Once a host sets `--ag-ui-header-fg`, the buttons draw in it at full
174
+ opacity, and the hover square is the opposite of that text at 18% (darker
175
+ under white text, lighter under dark), so a hovered button reads at least
176
+ what the host's header pair reads: white on `#3d5afe` is 5.1:1 at rest and
177
+ about 7:1 hovered. Browsers without relative colour syntax (Firefox 120
178
+ to 127, Safari 17.5 and 17.6) mix the square from the text itself instead.
179
+ This applies under Quiet and under a host's own theme word, which starts
180
+ from Quiet, in every scheme; Soft, Ledger, Classic and code are unchanged.
181
+ Unset, nothing changes. **No opt-in is needed.** A host that
182
+ wrote `ag-ui-chat::part(header-button) { color: var(--ag-ui-header-fg) }`
183
+ and the `:hover` background beside it may delete both: kept, the colour
184
+ rule is harmless, but the page's 18% tint of the text still beats the new
185
+ square, and on a mid-tone accent it takes the hovered glyph under 4.5:1
186
+ (3.75:1 on `#3d5afe`). Delete the hover background at least; to keep a
187
+ lightening square, set `--ag-ui-header-btn-hover` to it. With the header
188
+ text set, the header buttons no longer read `--ag-ui-hover`; the rest of
189
+ the panel still does. A host that sets neither `--ag-ui-header-fg`
190
+ nor `--ag-ui-tool-fg`, the Django admin among them, sees no change.
191
+ - **`--ag-ui-tool-fg` reaches the calls in a step group.** Under the grouped
192
+ default (0.46.0) every call in a group was drawn in the second ink whatever
193
+ the token said, so the recipe's tool colour reached nothing. A set
194
+ `--ag-ui-tool-fg` now colours grouped calls under every theme; unset, they
195
+ keep the second ink, Classic included. **No opt-in is needed**, but a host
196
+ that set the token and kept the grouped default sees its calls in that
197
+ colour from this release.
198
+
10
199
  ## [0.48.0] — 2026-11-07
11
200
 
12
201
  ### Added
@@ -4979,7 +5168,9 @@ hosts that both arrange the page the way it expects.
4979
5168
  ### Notes
4980
5169
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
4981
5170
 
4982
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.48.0...HEAD
5171
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.50.0...HEAD
5172
+ [0.50.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.49.0...v0.50.0
5173
+ [0.49.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.48.0...v0.49.0
4983
5174
  [0.48.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.47.0...v0.48.0
4984
5175
  [0.47.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.46.0...v0.47.0
4985
5176
  [0.46.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.45.0...v0.46.0
package/README.md CHANGED
@@ -208,7 +208,7 @@ another origin, add `credentials="include"` too; see
208
208
  | `data-side` | — | CSS-only, for `placement="sidebar"`: which edge it docks to — `right` (default) / `left`. |
209
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). |
210
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). |
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
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. |
213
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`. |
214
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. |
@@ -1183,10 +1183,13 @@ matching JS API:
1183
1183
  - `moveTo(corner)` — send the panel to `top-left` / `top-right` / `bottom-left` / `bottom-right`,
1184
1184
  returning whether it went. It takes the axes the same way a user drag does, so the launcher
1185
1185
  travels with it and switching placement hands them back. Returns `false` rather than pretending
1186
- 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.
1187
1188
  - `resetSize()` — drop a size the user dragged, and its stored copy, so the placement's own
1188
1189
  size and your `--ag-ui-width` / `--ag-ui-height` apply again. For a host that enters a sized
1189
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.
1190
1193
  - `setCollapsed(collapsed)` / `toggleCollapsed()` — collapse or expand the widget. The state is
1191
1194
  reflected as the boolean `collapsed` attribute/property and persisted per-tab in
1192
1195
  `sessionStorage`, so it survives a reload.
@@ -1485,14 +1488,35 @@ body {
1485
1488
  your toolbar: its max height is the viewport less the bar's height (or, under `none`, the
1486
1489
  distance to the anchor's top) and one gutter, and its bottom sits on the bar or the anchor.
1487
1490
 
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.
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`.
1492
1515
 
1493
1516
  **Phones.** At 600px or less the open panel stays full-bleed, over the bar, and the header's
1494
1517
  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.
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.
1496
1520
 
1497
1521
  The bar is styled through the parts `bar` (the strip's wrapper, holding the launcher and the
1498
1522
  history button) and `bar-history`, and the launcher's own parts (`launcher`, `rail-label`,
@@ -3302,6 +3326,45 @@ fallback white (2.5:1), as it always was. What they miss is the correction for a
3302
3326
  mid-tone brand accent can fall under 4.5:1 there, under any theme. If you support those browsers
3303
3327
  and set a mid-tone accent, set `--ag-ui-on-accent` yourself. There is no script path.
3304
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. The same recipe holds
3334
+ under every other theme:
3335
+
3336
+ ```css
3337
+ ag-ui-chat {
3338
+ --ag-ui-header-bg: #3d5afe;
3339
+ --ag-ui-header-fg: #ffffff;
3340
+ --ag-ui-launcher-bg: #3d5afe;
3341
+ --ag-ui-launcher-fg: #ffffff;
3342
+ --ag-ui-tool-fg: #3d5afe;
3343
+ }
3344
+ ```
3345
+
3346
+ - **The header buttons follow `--ag-ui-header-fg`, under every theme.** Unset, each theme draws
3347
+ them its own way at 0.85 opacity: Quiet, Soft and Ledger in their third ink, Classic and Code in
3348
+ the header text, each on its own hover square. Once you set the header text they draw in it at
3349
+ full opacity, and the hover square is the opposite of that text at about a fifth (a darker square
3350
+ under white text, a lighter one under dark text), so a hovered button reads at least what your
3351
+ header pair reads. A theme word of your own starts from Quiet, so it holds there too. No
3352
+ `::part(header-button)` rule is needed. The opposite is computed with relative
3353
+ colour syntax; the browsers without it (see the fallback above) mix the square from the text
3354
+ itself, which on a mid-tone header can take the hovered glyph under your pair (white on
3355
+ `#3d5afe`: 3.75:1).
3356
+ - **`--ag-ui-header-btn-hover` sets the hover square outright** under every theme, whether or not
3357
+ the header text is set, and is drawn exactly as given. For a square that lightens under white
3358
+ text instead, at the cost of contrast on a mid-tone header:
3359
+
3360
+ ```css
3361
+ ag-ui-chat { --ag-ui-header-btn-hover: color-mix(in srgb, #ffffff 18%, transparent); }
3362
+ ```
3363
+ - **`--ag-ui-tool-fg` colours every tool call**, in a step group as well as in the card displays.
3364
+ Unset, a grouped call is drawn in the second ink (`--ag-ui-ink-2`) under every theme.
3365
+ - Pick the pair yourself: `--ag-ui-header-fg` is not derived from the header colour the way
3366
+ `--ag-ui-on-accent` is from the accent.
3367
+
3305
3368
  #### Overriding a theme, lightest layer first
3306
3369
 
3307
3370
  Four layers, each documented and each lighter than the next. Reach for the first one that does