@artooi/ag-ui-web-component 0.44.0 → 0.46.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 (187) hide show
  1. package/CHANGELOG.md +182 -1
  2. package/README.md +245 -32
  3. package/dist/ag-ui-web-component.bundle.js +3696 -2798
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +4 -1
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +28 -8
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/read_flag.d.ts +11 -0
  10. package/dist/core/read_flag.d.ts.map +1 -0
  11. package/dist/core/read_json_attribute.d.ts +15 -0
  12. package/dist/core/read_json_attribute.d.ts.map +1 -0
  13. package/dist/index.js +5084 -3481
  14. package/dist/index.js.map +4 -4
  15. package/dist/tools/tool_catalog.d.ts +5 -0
  16. package/dist/tools/tool_catalog.d.ts.map +1 -1
  17. package/dist/tools/tool_dispatch.d.ts.map +1 -1
  18. package/dist/ui/charts/chart_styles.d.ts +2 -0
  19. package/dist/ui/charts/chart_styles.d.ts.map +1 -0
  20. package/dist/ui/composer/attachment_styles.d.ts +2 -0
  21. package/dist/ui/composer/attachment_styles.d.ts.map +1 -0
  22. package/dist/ui/composer/build_composer.d.ts +30 -0
  23. package/dist/ui/composer/build_composer.d.ts.map +1 -0
  24. package/dist/ui/composer/composer_styles.d.ts +2 -0
  25. package/dist/ui/composer/composer_styles.d.ts.map +1 -0
  26. package/dist/ui/composer/skill_styles.d.ts +2 -0
  27. package/dist/ui/composer/skill_styles.d.ts.map +1 -0
  28. package/dist/ui/history/history_styles.d.ts +2 -0
  29. package/dist/ui/history/history_styles.d.ts.map +1 -0
  30. package/dist/ui/interrupts/approval_styles.d.ts +2 -0
  31. package/dist/ui/interrupts/approval_styles.d.ts.map +1 -0
  32. package/dist/ui/interrupts/confirmation_styles.d.ts +2 -0
  33. package/dist/ui/interrupts/confirmation_styles.d.ts.map +1 -0
  34. package/dist/ui/interrupts/question_styles.d.ts +2 -0
  35. package/dist/ui/interrupts/question_styles.d.ts.map +1 -0
  36. package/dist/ui/placement/collapse_styles.d.ts +2 -0
  37. package/dist/ui/placement/collapse_styles.d.ts.map +1 -0
  38. package/dist/ui/placement/embedded_styles.d.ts +2 -0
  39. package/dist/ui/placement/embedded_styles.d.ts.map +1 -0
  40. package/dist/ui/placement/launcher_styles.d.ts +2 -0
  41. package/dist/ui/placement/launcher_styles.d.ts.map +1 -0
  42. package/dist/ui/placement/page_styles.d.ts +2 -0
  43. package/dist/ui/placement/page_styles.d.ts.map +1 -0
  44. package/dist/ui/placement/panel_placement.d.ts.map +1 -1
  45. package/dist/ui/placement/preset_styles.d.ts +2 -0
  46. package/dist/ui/placement/preset_styles.d.ts.map +1 -0
  47. package/dist/ui/placement/resize_styles.d.ts +2 -0
  48. package/dist/ui/placement/resize_styles.d.ts.map +1 -0
  49. package/dist/ui/placement/sidebar_styles.d.ts +2 -0
  50. package/dist/ui/placement/sidebar_styles.d.ts.map +1 -0
  51. package/dist/ui/placement/small_viewport_styles.d.ts +2 -0
  52. package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -0
  53. package/dist/ui/placement/surface_for.d.ts +74 -0
  54. package/dist/ui/placement/surface_for.d.ts.map +1 -0
  55. package/dist/ui/progress/run_notice_styles.d.ts +2 -0
  56. package/dist/ui/progress/run_notice_styles.d.ts.map +1 -0
  57. package/dist/ui/progress/thoughts_styles.d.ts +2 -0
  58. package/dist/ui/progress/thoughts_styles.d.ts.map +1 -0
  59. package/dist/ui/progress/tool_call_card.d.ts +8 -0
  60. package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
  61. package/dist/ui/progress/tool_group_styles.d.ts +2 -0
  62. package/dist/ui/progress/tool_group_styles.d.ts.map +1 -0
  63. package/dist/ui/progress/tool_step_group.d.ts +47 -0
  64. package/dist/ui/progress/tool_step_group.d.ts.map +1 -0
  65. package/dist/ui/progress/tool_step_styles.d.ts +2 -0
  66. package/dist/ui/progress/tool_step_styles.d.ts.map +1 -0
  67. package/dist/ui/shell/adopt_styles.d.ts +9 -4
  68. package/dist/ui/shell/adopt_styles.d.ts.map +1 -1
  69. package/dist/ui/shell/build_header.d.ts +40 -0
  70. package/dist/ui/shell/build_header.d.ts.map +1 -0
  71. package/dist/ui/shell/build_launcher.d.ts +20 -0
  72. package/dist/ui/shell/build_launcher.d.ts.map +1 -0
  73. package/dist/ui/shell/frame_styles.d.ts +2 -0
  74. package/dist/ui/shell/frame_styles.d.ts.map +1 -0
  75. package/dist/ui/shell/theme_styles.d.ts +2 -0
  76. package/dist/ui/shell/theme_styles.d.ts.map +1 -0
  77. package/dist/ui/shell/token_styles.d.ts +2 -0
  78. package/dist/ui/shell/token_styles.d.ts.map +1 -0
  79. package/dist/ui/styles.d.ts +1 -1
  80. package/dist/ui/styles.d.ts.map +1 -1
  81. package/dist/ui/themes/classic_styles.d.ts +2 -0
  82. package/dist/ui/themes/classic_styles.d.ts.map +1 -0
  83. package/dist/ui/themes/classic_tokens.d.ts +2 -0
  84. package/dist/ui/themes/classic_tokens.d.ts.map +1 -0
  85. package/dist/ui/themes/code_tokens.d.ts +2 -0
  86. package/dist/ui/themes/code_tokens.d.ts.map +1 -0
  87. package/dist/ui/themes/ledger_styles.d.ts +2 -0
  88. package/dist/ui/themes/ledger_styles.d.ts.map +1 -0
  89. package/dist/ui/themes/ledger_tokens.d.ts +2 -0
  90. package/dist/ui/themes/ledger_tokens.d.ts.map +1 -0
  91. package/dist/ui/themes/modern_styles.d.ts +2 -0
  92. package/dist/ui/themes/modern_styles.d.ts.map +1 -0
  93. package/dist/ui/themes/quiet_styles.d.ts +2 -0
  94. package/dist/ui/themes/quiet_styles.d.ts.map +1 -0
  95. package/dist/ui/themes/scheme_styles.d.ts +2 -0
  96. package/dist/ui/themes/scheme_styles.d.ts.map +1 -0
  97. package/dist/ui/themes/soft_styles.d.ts +2 -0
  98. package/dist/ui/themes/soft_styles.d.ts.map +1 -0
  99. package/dist/ui/themes/soft_tokens.d.ts +2 -0
  100. package/dist/ui/themes/soft_tokens.d.ts.map +1 -0
  101. package/dist/ui/themes/theme_hosts.d.ts +15 -0
  102. package/dist/ui/themes/theme_hosts.d.ts.map +1 -0
  103. package/dist/ui/transcript/attachment_chip_styles.d.ts +2 -0
  104. package/dist/ui/transcript/attachment_chip_styles.d.ts.map +1 -0
  105. package/dist/ui/transcript/build_transcript.d.ts +21 -0
  106. package/dist/ui/transcript/build_transcript.d.ts.map +1 -0
  107. package/dist/ui/transcript/greeting_styles.d.ts +2 -0
  108. package/dist/ui/transcript/greeting_styles.d.ts.map +1 -0
  109. package/dist/ui/transcript/groups_tool_steps.d.ts +13 -0
  110. package/dist/ui/transcript/groups_tool_steps.d.ts.map +1 -0
  111. package/dist/ui/transcript/message_action_styles.d.ts +2 -0
  112. package/dist/ui/transcript/message_action_styles.d.ts.map +1 -0
  113. package/dist/ui/transcript/message_styles.d.ts +2 -0
  114. package/dist/ui/transcript/message_styles.d.ts.map +1 -0
  115. package/dist/ui/transcript/transcript.d.ts +16 -1
  116. package/dist/ui/transcript/transcript.d.ts.map +1 -1
  117. package/dist/ui/transcript/transcript_styles.d.ts +2 -0
  118. package/dist/ui/transcript/transcript_styles.d.ts.map +1 -0
  119. package/dist/ui/ui_strings.d.ts +18 -0
  120. package/dist/ui/ui_strings.d.ts.map +1 -1
  121. package/package.json +2 -2
  122. package/src/constants.ts +4 -1
  123. package/src/core/ag_ui_chat.ts +164 -299
  124. package/src/core/read_flag.ts +13 -0
  125. package/src/core/read_json_attribute.ts +30 -0
  126. package/src/tools/tool_catalog.ts +23 -10
  127. package/src/tools/tool_dispatch.ts +26 -11
  128. package/src/ui/charts/chart_block.ts +9 -9
  129. package/src/ui/charts/chart_styles.ts +63 -0
  130. package/src/ui/composer/attachment_styles.ts +71 -0
  131. package/src/ui/composer/build_composer.ts +112 -0
  132. package/src/ui/composer/composer_styles.ts +174 -0
  133. package/src/ui/composer/skill_styles.ts +120 -0
  134. package/src/ui/history/history_styles.ts +484 -0
  135. package/src/ui/interrupts/approval_styles.ts +52 -0
  136. package/src/ui/interrupts/confirmation_styles.ts +114 -0
  137. package/src/ui/interrupts/question_styles.ts +73 -0
  138. package/src/ui/placement/collapse_styles.ts +137 -0
  139. package/src/ui/placement/embedded_styles.ts +14 -0
  140. package/src/ui/placement/launcher_styles.ts +189 -0
  141. package/src/ui/placement/page_styles.ts +47 -0
  142. package/src/ui/placement/panel_placement.ts +3 -12
  143. package/src/ui/placement/preset_styles.ts +33 -0
  144. package/src/ui/placement/resize_styles.ts +186 -0
  145. package/src/ui/placement/sidebar_styles.ts +88 -0
  146. package/src/ui/placement/small_viewport_styles.ts +56 -0
  147. package/src/ui/placement/surface_for.ts +135 -0
  148. package/src/ui/progress/run_notice_styles.ts +52 -0
  149. package/src/ui/progress/thoughts_styles.ts +65 -0
  150. package/src/ui/progress/tool_call_card.ts +11 -0
  151. package/src/ui/progress/tool_group_styles.ts +232 -0
  152. package/src/ui/progress/tool_step_group.ts +269 -0
  153. package/src/ui/progress/tool_step_styles.ts +472 -0
  154. package/src/ui/shell/adopt_styles.ts +11 -5
  155. package/src/ui/shell/build_header.ts +108 -0
  156. package/src/ui/shell/build_launcher.ts +50 -0
  157. package/src/ui/shell/frame_styles.ts +119 -0
  158. package/src/ui/shell/theme_styles.ts +11 -0
  159. package/src/ui/shell/token_styles.ts +406 -0
  160. package/src/ui/styles.ts +104 -3774
  161. package/src/ui/themes/classic_styles.ts +32 -0
  162. package/src/ui/themes/classic_tokens.ts +50 -0
  163. package/src/ui/themes/code_tokens.ts +21 -0
  164. package/src/ui/themes/ledger_styles.ts +168 -0
  165. package/src/ui/themes/ledger_tokens.ts +32 -0
  166. package/src/ui/themes/modern_styles.ts +80 -0
  167. package/src/ui/themes/quiet_styles.ts +115 -0
  168. package/src/ui/themes/scheme_styles.ts +17 -0
  169. package/src/ui/themes/soft_styles.ts +148 -0
  170. package/src/ui/themes/soft_tokens.ts +28 -0
  171. package/src/ui/themes/theme_hosts.ts +14 -0
  172. package/src/ui/transcript/attachment_chip_styles.ts +116 -0
  173. package/src/ui/transcript/build_transcript.ts +67 -0
  174. package/src/ui/transcript/greeting_styles.ts +171 -0
  175. package/src/ui/transcript/groups_tool_steps.ts +16 -0
  176. package/src/ui/transcript/message_action_styles.ts +155 -0
  177. package/src/ui/transcript/message_styles.ts +262 -0
  178. package/src/ui/transcript/transcript.ts +109 -3
  179. package/src/ui/transcript/transcript_styles.ts +140 -0
  180. package/src/ui/ui_strings.ts +31 -0
  181. package/src/version.ts +1 -1
  182. package/dist/ui/placement/is_collapsible_placement.d.ts +0 -18
  183. package/dist/ui/placement/is_collapsible_placement.d.ts.map +0 -1
  184. package/dist/ui/placement/is_draggable_placement.d.ts +0 -9
  185. package/dist/ui/placement/is_draggable_placement.d.ts.map +0 -1
  186. package/src/ui/placement/is_collapsible_placement.ts +0 -19
  187. package/src/ui/placement/is_draggable_placement.ts +0 -23
package/CHANGELOG.md CHANGED
@@ -7,6 +7,185 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.46.0] — 2026-11-05
11
+
12
+ ### Changed
13
+
14
+ - **Tool calls fold into a step group by default.** `data-tool-display`
15
+ gains `grouped`, and it is the new default (also for an absent or unknown
16
+ value). A run's consecutive tool calls fold into one quiet row, "Worked
17
+ through 5 steps", with a note such as "1 failed", and the per-call cards one
18
+ click down as `full` cards. Anything else in the turn ends the group, except
19
+ a call's own rendered output. **No
20
+ opt-in is needed, and a host that wants the old per-call cards opts out with
21
+ `data-tool-display="full"`**; `inline`, `minimal`, `compact` and `full`
22
+ render exactly as in 0.45.0. Grouping is the same under every theme; Soft
23
+ draws the row as a tinted pill and Ledger as a monospace heading.
24
+ - The group opens itself while a step waits on the user (a server-side
25
+ approval, which renders inside its card), once per wait; the user can
26
+ fold it again. It opens before the approval is drawn, so the Approve
27
+ button takes focus and scrolls into view, as it does under `full`. A
28
+ client-side confirmation or an `ask_user` question renders beside the
29
+ group and is in view without it opening, and the row reads "waiting for
30
+ you" for as long as one is open rather than "running".
31
+ - A running sub-agent does not open it: the folded row keeps its spinner
32
+ and names the delegation ("Delegated to researcher").
33
+ - Repeated labels are told apart inside a group, "Fill field" then "Fill
34
+ field (2)", in an element of its own; `tool-card-name` is never rewritten.
35
+ - A tool's rendered output (a chart) is placed after the group, in call
36
+ order, so it is never folded away, and a later call still joins the
37
+ group above it.
38
+ - Grouping is decided as each card is built. Switching to `grouped` folds
39
+ only calls drawn afterwards; switching to a card mode flattens existing
40
+ groups by CSS into the cards that mode builds.
41
+ - A restored conversation folds the same way as the live one.
42
+
43
+ ### Added
44
+
45
+ - Parts `tool-group`, `tool-group-head`, `tool-group-icon`,
46
+ `tool-group-label`, `tool-group-meta`, `tool-group-list`, and
47
+ `tool-card-repeat` on a repeated step.
48
+ - The `--ag-ui-ring` token (the group head's focus outline) and the
49
+ `--ag-ui-ico-chevron` mask image (its disclosure chevron).
50
+ - Strings `toolGroupSteps`, `toolGroupWorking`, `toolGroupWorked`,
51
+ `toolGroupWaiting`, `toolGroupFailed`, `toolGroupDeclined` (`{count}`) and
52
+ `toolStepRepeat` (`{n}`).
53
+ - `TOOL_DISPLAY.GROUPED`.
54
+ - A `data-asking` attribute on a tool-call card while a question or a
55
+ confirmation is open on it. It is internal, read by the step group; the
56
+ card's `data-status` is unchanged.
57
+
58
+ ### Fixed
59
+
60
+ - **The wheel scrolls the transcript when the pointer rests on a tool call's
61
+ arguments or result, a code block, the reasoning or a confirmation's
62
+ arguments.** Those blocks swallowed the wheel even when they had nothing
63
+ left to scroll, so the transcript sat still until the pointer moved to plain
64
+ text. They now hand the wheel on to the transcript, including a wheel that
65
+ carries a little sideways motion, as a real mouse or trackpad nearly always
66
+ does. This is long-standing and needs no opt-in.
67
+
68
+ ## [0.45.0] — 2026-11-05
69
+
70
+ ### Added
71
+
72
+ - **A theme set.** `theme` is now up to two words in any order, a theme name
73
+ and a scheme, matched as words: `soft`, `soft dark`, `ledger auto`. The
74
+ names are `quiet` (the new default), `soft` (rounded, tinted from the
75
+ accent), `ledger` (dense, serif and monospace) and `classic` (the look
76
+ before this release). The schemes are `light` (the default), `dark` and
77
+ `auto`, which follows `prefers-color-scheme`. A word the component does not
78
+ ship adds no rules, so `theme="acme"` is Quiet and a hook for the host's own
79
+ `[theme~="acme"]` CSS, and `soft acme` starts from Soft.
80
+ - **Three seeds theme everything.** Each theme declares `--ag-ui-bg`,
81
+ `--ag-ui-fg` and `--ag-ui-accent` with `light-dark()`, and every other colour
82
+ is mixed from them in oklch while still reading its own public token first.
83
+ New tokens: `--ag-ui-ink-2`, `--ag-ui-ink-3`, `--ag-ui-line`,
84
+ `--ag-ui-line-strong`, `--ag-ui-wash`, `--ag-ui-accent-ink`, `--ag-ui-tint`,
85
+ `--ag-ui-tint-2`, the type scale `--ag-ui-text-xs` to `--ag-ui-text-lg`,
86
+ `--ag-ui-leading`, `--ag-ui-serif`, `--ag-ui-frame-radius`,
87
+ `--ag-ui-frame-shadow`, and the status masks `--ag-ui-ico-check`,
88
+ `--ag-ui-ico-error` and `--ag-ui-ico-declined`.
89
+ - **`extraStyles` (unstable).** A per-element `CSSStyleSheet[]` adopted after
90
+ the built-in sheet, for structure the tokens and parts cannot reach. It
91
+ works under a strict `style-src` CSP. It reaches class names inside the
92
+ shadow root, which are not a stable API and may change in any minor release.
93
+ - **Text on the accent follows the accent.** Unless `--ag-ui-on-accent` is
94
+ set, it is white or black by the accent's luminance, whichever contrasts
95
+ more, where relative colour syntax is supported (Chrome 119, Firefox 128,
96
+ Safari 18). Firefox 120 to 127 and Safari 17.5 and 17.6 cannot compute it
97
+ and keep a fixed colour: white under `classic` and `code`, as before, and
98
+ the scheme's colour (white light, `#111111` dark) under `quiet`, `soft`
99
+ and `ledger`. Every built-in accent but `code`'s gets the same side either
100
+ way; a host's mid-tone accent there needs `--ag-ui-on-accent` set by hand.
101
+
102
+ ### Changed
103
+
104
+ - **The default look changes for every host that sets no `theme`.** The
105
+ default is now Quiet: a neutral panel with the header on the panel itself,
106
+ no bubble for the agent's turn, status as an icon rather than a pill, its
107
+ own font stack instead of the page's, and a 48px launcher. `theme="dark"`
108
+ is Quiet in the dark scheme. **A host that wants the old look must opt in
109
+ with `theme="classic"`** (or `theme="classic dark"`); explicitly set tokens
110
+ keep working under every theme. Set `--ag-ui-font: inherit` to keep the
111
+ page's font under any theme.
112
+ - **A host that sets only `--ag-ui-accent` loses the accent header,
113
+ launcher and tool names.** Quiet draws them in the panel's own colours,
114
+ and its user bubble is the accent at 12% rather than the fixed indigo it
115
+ was. To get them back, opt in with `theme="classic"`, or keep Quiet and
116
+ set `--ag-ui-header-bg`, `--ag-ui-launcher-bg` and `--ag-ui-tool-fg` to
117
+ the accent and `--ag-ui-header-fg` and `--ag-ui-launcher-fg` to the
118
+ colour on it.
119
+ - **One chip row and one action row per answer**, under every theme but
120
+ `classic` and `code`. The skill chips show on the empty state only and,
121
+ once a message is sent, live in the slash palette alone. In an answer
122
+ that spans several bubbles (text, a tool call, more text), only the last
123
+ bubble keeps its copy, retry and feedback row.
124
+ - **`ledger` replaces `code`.** `theme="code"` keeps working but is no
125
+ longer documented; Ledger is the monospace look now. `code` changes in
126
+ two ways: it sets `color-scheme: dark`, so native controls, scrollbars
127
+ and the placeholder draw dark, and text on its green accent (the send
128
+ icon) is black where relative colour syntax is supported, 8.3:1 where
129
+ white was 2.5:1.
130
+ - **The browser floor rises to Chrome 123, Firefox 120 and Safari 17.5**,
131
+ from Chrome 120 and Safari 17. Every theme declares its colours with
132
+ `light-dark()`, which those versions are the first to support, and it is
133
+ used without a fallback: below the floor the colours do not resolve and
134
+ the widget draws without them. There is nothing to opt in to; a host
135
+ that must support an older browser stays on the previous release.
136
+ - **`classic` meets AA contrast.** The status pill's text in both schemes,
137
+ the indigo tool names in the dark scheme (2.38:1) and the dark composer
138
+ placeholder (3.93:1) now clear 4.5:1, and the done mark in the dark scheme
139
+ clears 3:1. Otherwise classic is the old look.
140
+ - **The theme toggle flips only the scheme word**, so `soft` becomes
141
+ `soft dark` and `soft dark` becomes `soft light`. Before, any value but
142
+ `dark` became `dark`, which dropped a theme name. It saves only the scheme
143
+ word, and a load puts the saved scheme beside the markup's own theme name,
144
+ so a scheme chosen on one page never carries that page's theme to
145
+ another. A value saved by an earlier release (`light` or `dark`) restores
146
+ as before.
147
+ - **Chart series read the theme's palette.** `--ag-ui-chart-1` to
148
+ `--ag-ui-chart-6` still win; without them each theme supplies its own first
149
+ two series.
150
+
151
+ - **The shadow stylesheet is split by concern.** `src/ui/styles.ts`, one
152
+ 3,774-line string, is now composed from 27 modules, each in the directory of
153
+ the concern it styles: the token defaults and themes under `ui/shell/`, one
154
+ module per placement under `ui/placement/`, and the transcript, composer,
155
+ tool steps, interrupts, history and charts in theirs. The composed sheet is
156
+ the old one with every rule in the same order; only two comments changed
157
+ wording. Nothing a host sees changes, and **no host has to opt in**: tokens,
158
+ parts, attributes and the public type declarations are as they were.
159
+ - **One record per placement.** What each `placement` does on the script side
160
+ -- what it collapses to, whether its launcher can be dragged, which axes it
161
+ resizes on, whether a first mount starts collapsed -- is now one descriptor
162
+ per placement in `src/ui/placement/surface_for.ts`, replacing three separate
163
+ tables that each listed placements. Every placement behaves as before, an
164
+ unknown value included, and **no host has to opt in**.
165
+ - **The element builds its DOM through free functions.** The header, the
166
+ transcript and empty state, the composer and the launcher are each built by
167
+ a function in the directory of their concern (`ui/shell/build_header.ts`,
168
+ `ui/transcript/build_transcript.ts`, `ui/composer/build_composer.ts`,
169
+ `ui/shell/build_launcher.ts`), and the flag and JSON attribute readers are
170
+ free functions under `core/`. Internal restructuring: the DOM, its parts,
171
+ slots and listeners are the same and mount in the same order, the public
172
+ type declarations are unchanged, and **no host has to opt in**.
173
+ - **The sub-agent test fixture is the wire django-ag-ui 0.72.0 writes.** The
174
+ recorded run was a copy from before pydantic-ai 2.63, which django-ag-ui now
175
+ floors. The regenerated one carries `role` on every `TEXT_MESSAGE_START`, a
176
+ `protocolVersion` on `RUN_STARTED`, an `entityId` on the reasoning event
177
+ that records a tool retry (a `REASONING_ENCRYPTED_VALUE`, new to the run),
178
+ and a retry result that no longer ends in pydantic-ai's "Fix the errors and
179
+ try again." prompt. The component reads none of the new keys: it takes message
180
+ ids from the events and builds the card from the `SUBAGENT_*` and
181
+ `ag_ui.subagent` events, and the unit and Chromium sub-agent tests, the
182
+ latter through the element's real `HttpAgent`, pass on the new file unchanged.
183
+ The demo's mock server sends the same retry-free result. A Chromium test now
184
+ asserts the run's closing answer renders past the new event; it fails on a
185
+ fixture whose event is malformed, where every delegation assertion settled
186
+ before it and passed. Test and demo only, nothing shipped changes,
187
+ and **a host owes nothing**.
188
+
10
189
  ## [0.44.0] — 2026-10-20
11
190
 
12
191
  ### Fixed
@@ -4582,7 +4761,9 @@ hosts that both arrange the page the way it expects.
4582
4761
  ### Notes
4583
4762
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
4584
4763
 
4585
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.44.0...HEAD
4764
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.46.0...HEAD
4765
+ [0.46.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.45.0...v0.46.0
4766
+ [0.45.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.44.0...v0.45.0
4586
4767
  [0.44.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.43.0...v0.44.0
4587
4768
  [0.43.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.42.0...v0.43.0
4588
4769
  [0.42.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.41.3...v0.42.0
package/README.md CHANGED
@@ -64,6 +64,7 @@ No framework, no Django, no admin specifics live here. Downstream consumers (e.g
64
64
  - [Host seams: the SPA story](#host-seams-the-spa-story)
65
65
  - [Public API surface](#public-api-surface)
66
66
  - [Theming, density, and placement](#theming-density-and-placement)
67
+ - [Themes](#themes)
67
68
  - [Internationalization (i18n)](#internationalization-i18n)
68
69
  - [Building the bundle](#building-the-bundle)
69
70
  - [Compatibility](#compatibility)
@@ -176,7 +177,7 @@ another origin, add `credentials="include"` too; see
176
177
  | `endpoint` | `endpoint` | The AG-UI endpoint URL. Required to send: without it a send is refused with a `console.error` naming the missing attribute and a `not-connected` run notice in the transcript. Reflecting getter + setter. |
177
178
  | `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). |
178
179
  | `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). |
179
- | `data-tool-display` | `toolDisplay` | Tool-call card detail: `inline` / `minimal` / `compact` / `full` (default `full`). |
180
+ | `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). |
180
181
  | `data-text-animation` | — | Incoming-text reveal: `none` (default) / `fade` / `word`. |
181
182
  | `data-prompt-chips` | — | Present (bare, or any value but `"false"`) to surface skills as chips. |
182
183
  | `data-slash-commands` | — | Present (bare, or any value but `"false"`) to enable the `/`-command palette. |
@@ -213,7 +214,7 @@ another origin, add `credentials="include"` too; see
213
214
  | `data-paste-attach` | — | When to turn a long text paste into an attachment instead of composer text: absent for the 5000-character default, `off` to never, or a positive number of characters. Only acts where `data-attachments-url` (or a custom `uploadHandler`) gives it somewhere to go. |
214
215
  | `data-starters` | — | JSON array of prompts offered on an empty transcript, e.g. `'["Summarise this page"]'`. Fallback content for `slot="empty"`, so slotting your own replaces them. Shares the four-prompt and 120-character limits with the suggestion chips a run pushes. Read once at connect. |
215
216
  | `data-start-open` | — | Mount the panel open on a first visit. The corner placements otherwise rest at their launcher, the way every corner chat does; a stored choice wins over both. The placements that place themselves are unaffected. |
216
- | `theme` | — | CSS-only: `light` (default) / `dark` / `auto` / `code`. |
217
+ | `theme` | — | CSS-only: an optional theme name (`quiet` (default) / `soft` / `ledger` / `classic`, or a word of your own) and an optional scheme (`light` (default) / `dark` / `auto`), in any order, e.g. `soft dark`. See [Themes](#themes). |
217
218
  | `density` | — | CSS-only: `comfortable` (default) / `compact`. |
218
219
  | `placement` | — | CSS-only: `floating` (default) / `sidebar` / `page` / `embedded`. |
219
220
 
@@ -263,7 +264,7 @@ element after the fact.
263
264
  `approveWithEdits`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`, `getPageMap`,
264
265
  `autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`, `navigationResult`,
265
266
  `skillContext`, `toolSummaries`, `formatToolPayload`, `formatRelativeTime`, `strings`,
266
- `resolvePageTarget`, `sharedState`, plus the read-only `unread` and `unhandledActivityTypes`, and
267
+ `resolvePageTarget`, `sharedState`, `extraStyles` ([unstable](#d-extrastyles-the-last-mile)), plus the read-only `unread` and `unhandledActivityTypes`, and
267
268
  the attribute mirrors `endpoint` / `userKey` / `userName` / `toolDisplay` / `collapsed` /
268
269
  `credentials`.
269
270
 
@@ -1182,7 +1183,7 @@ or changing `user-key` clears it with the transcript.
1182
1183
 
1183
1184
  ### Collapsing to the launcher
1184
1185
 
1185
- A collapsed widget shrinks to a round **floating launcher** in the corner it already occupies: the
1186
+ A collapsed widget shrinks to a **floating launcher** in the corner it already occupies: the
1186
1187
  panel scales down into it and fades, the launcher grows out of the same point, and clicking the
1187
1188
  launcher reverses it. Only `transform` and `opacity` animate, so the motion is compositor-only and
1188
1189
  never reflows your page. Two placements collapse to something else instead — `sidebar` slides out
@@ -1204,15 +1205,19 @@ speech bubble.
1204
1205
 
1205
1206
  ```css
1206
1207
  ag-ui-chat {
1207
- --ag-ui-launcher-size: 56px;
1208
- --ag-ui-launcher-radius: 50%; /* 12px for a squircle */
1209
- --ag-ui-launcher-bg: #14532d; /* defaults to the header background */
1210
- --ag-ui-launcher-fg: #ffffff;
1211
- --ag-ui-launcher-icon-size: 26px;
1208
+ --ag-ui-launcher-size: 48px;
1209
+ --ag-ui-launcher-radius: 14px; /* 50% for a circle */
1210
+ --ag-ui-launcher-bg: #14532d; /* defaults to the panel background */
1211
+ --ag-ui-launcher-fg: #ffffff; /* defaults to the text colour */
1212
+ --ag-ui-launcher-icon-size: 22px;
1212
1213
  --ag-ui-launcher-inset: auto 0 0 auto; /* which corner of the widget's box */
1213
1214
  }
1214
1215
  ```
1215
1216
 
1217
+ The sizes and defaults above are Quiet's. The other themes bring their own launcher: `classic`
1218
+ the 56px circle in the header's colours, `soft` a 56px accent tile, and `ledger` a 44px tile in
1219
+ the text colour.
1220
+
1216
1221
  > **The collapsed host keeps its box.** Animating the element's own width and height would animate
1217
1222
  > layout; instead the box stays put with `pointer-events: none`, and the launcher takes the clicks.
1218
1223
  > A host measuring `getBoundingClientRect()` on a collapsed widget still sees the panel's
@@ -1470,15 +1475,22 @@ is the honest answer to "I pushed a chart and nothing happened".
1470
1475
 
1471
1476
  ## Tool-call display modes
1472
1477
 
1473
- How much a tool-call card shows is set via the `data-tool-display` attribute (or `toolDisplay`
1474
- property), one of `inline` / `minimal` / `compact` / `full` (default `full`):
1478
+ How tool calls are drawn is set via the `data-tool-display` attribute (or `toolDisplay`
1479
+ property), one of `grouped` / `inline` / `minimal` / `compact` / `full` (default `grouped`; an
1480
+ absent or unknown value reads as `grouped`):
1475
1481
 
1482
+ - `grouped` — the default. A run's consecutive tool calls fold into one quiet row, **"Worked
1483
+ through 5 steps"**, with a note such as **"1 failed"** beside it, and the calls one click down
1484
+ as `full` cards. Anything else in the turn (text, a confirmation, a question) ends the group, and
1485
+ 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
1486
+ [The step group](#the-step-group).
1476
1487
  - `inline` — the lightest mode: a single status row (icon + summary, no card chrome) with the
1477
1488
  result behind its own toggle. Reads as one line of the answer — pairs with [the answer
1478
1489
  well](#the-answer-well).
1479
1490
  - `minimal` — tool name + status pill only.
1480
1491
  - `compact` — name + status, with arguments *and* result behind a single collapsed toggle.
1481
- - `full` — arguments visible, result behind the toggle (the default).
1492
+ - `full` — arguments visible, result behind the toggle. The default before `grouped`: set
1493
+ `data-tool-display="full"` to keep per-call cards.
1482
1494
 
1483
1495
  Whichever mode is on, a settled card's body holds **two labelled regions** — `Arguments` and
1484
1496
  `Result` (or `Error` / `Declined`) — each with its own part and each pretty-printed. They are
@@ -1486,9 +1498,55 @@ never run together into one block, so where the call ends and the answer begins
1486
1498
  visible. Style them via the `tool-card-args` / `tool-card-result` parts, their headings via
1487
1499
  `tool-card-section-label`, and the whole body via `tool-card-body`.
1488
1500
 
1489
- **The attribute is live.** Changing `data-tool-display` restyles every card already in the
1490
- transcript, the way `data-answer-well` does — the modes are pure visibility over one DOM shape,
1491
- selected by the shadow CSS from the host attribute.
1501
+ **The attribute is live.** Changing `data-tool-display` between the four card modes restyles every
1502
+ card already in the transcript, the way `data-answer-well` does — the modes are pure visibility
1503
+ over one DOM shape, selected by the shadow CSS from the host attribute. Grouping is decided as
1504
+ each card is built: switching **to** `grouped` folds only the calls drawn afterwards, and
1505
+ switching **away** from it flattens the groups already drawn, by CSS, into exactly the cards the
1506
+ new mode builds (the group stops generating a box and its row is hidden).
1507
+
1508
+ ### The step group
1509
+
1510
+ Under `grouped` the row reads, by state:
1511
+
1512
+ | State (`data-state` on the `tool-group` part) | Label | Mark |
1513
+ | --- | --- | --- |
1514
+ | `running` | "Working through 3 steps", and the running step's label as a note | spinner |
1515
+ | `waiting` | "3 steps", with "1 waiting for you" | accent dot |
1516
+ | `done` | "Worked through 3 steps" | check |
1517
+ | `error` | "Worked through 3 steps", with "1 failed" in the danger colour | error mark |
1518
+
1519
+ "N declined" is added as a note whenever a step was declined. `data-count` carries the number of
1520
+ steps and `data-open` whether the cards are showing; the head is a button with `aria-expanded`.
1521
+
1522
+ - **It opens itself while a step waits on you.** A server-side approval renders inside the gated
1523
+ call's own card, so a folded group would hide the question it is asking: the group opens when a
1524
+ step goes `deferred`, once per wait, and stays folded if you fold it again. It opens before the
1525
+ approval is drawn, so the Approve button takes focus and scrolls into view. A client-side
1526
+ confirmation and an `ask_user` question render in the answer beside the group rather than inside
1527
+ a card, so they are in view without it opening; the row still reads `waiting` ("1 waiting for
1528
+ you") while one is open, as the card carries a `data-asking` flag for the group to read, and its
1529
+ `data-status` stays `pending`.
1530
+ - **A running sub-agent does not open it.** The row keeps its spinner and names the delegation
1531
+ instead, "Delegated to researcher" (the `subAgentDelegatedTo` string).
1532
+ - **Repeated labels are told apart.** Two "Fill field" steps read "Fill field" and "Fill field
1533
+ (2)": the ordinal is an element of its own (part `tool-card-repeat`) beside the card's name, so
1534
+ `tool-card-name` is never rewritten, and a card mode hides the ordinal.
1535
+ - **Rendered output stays in view.** A tool's `render` output is placed after the group, in call
1536
+ order, rather than inside it, so a chart is never one click down. It does not end the group: a
1537
+ later call joins the group above it, which is also how a restored conversation draws the run.
1538
+
1539
+ Parts: `tool-group`, `tool-group-head` (the button), `tool-group-icon`, `tool-group-label`,
1540
+ `tool-group-meta` (holding one `.tool-group-note` per note), `tool-group-list` (the cards), and
1541
+ `tool-card-repeat` on a repeated step. The focus ring is `--ag-ui-ring` and the disclosure chevron
1542
+ `--ag-ui-ico-chevron`. Strings (`strings` / `data-strings`): `toolGroupSteps`,
1543
+ `toolGroupWorking`, `toolGroupWorked` (each with `{count}`, always 2 or more), `toolGroupWaiting`,
1544
+ `toolGroupFailed`, `toolGroupDeclined` (each with `{count}`) and `toolStepRepeat` (with `{n}`).
1545
+
1546
+ ```html
1547
+ <!-- Keep per-call cards, as before grouped was the default -->
1548
+ <ag-ui-chat endpoint="/agent/" data-tool-display="full"></ag-ui-chat>
1549
+ ```
1492
1550
 
1493
1551
  ### Drawing a card's body yourself
1494
1552
 
@@ -2873,19 +2931,20 @@ ag-ui-chat { --ag-ui-accent: #b91c1c; } /* one widget */
2873
2931
  > form did anything and the wrapper form silently did nothing. The defaults now sit behind an
2874
2932
  > internal alias, so all three forms work.
2875
2933
 
2876
- A few of the knobs:
2934
+ A few of the knobs, at Quiet's light values:
2877
2935
 
2878
2936
  ```css
2879
2937
  ag-ui-chat {
2880
- --ag-ui-accent: #4f46e5;
2881
- --ag-ui-bg: #ffffff;
2882
- --ag-ui-fg: #1a1a2e;
2938
+ --ag-ui-accent: #232326; /* #f0f0f2 in the dark scheme */
2939
+ --ag-ui-bg: #ffffff; /* #161618 */
2940
+ --ag-ui-fg: #1b1b1d; /* #ececee */
2883
2941
  --ag-ui-radius: 12px;
2884
2942
  --ag-ui-header-btn-size: 30px; /* the header's controls; 44px on touch */
2885
2943
  --ag-ui-header-gap: 4px;
2886
2944
 
2887
- /* What is drawn on top of the accent and danger fills. Change these with
2888
- the fills: a pale accent leaves white-on-pale everywhere they are used. */
2945
+ /* What is drawn on top of the accent and danger fills. Unset, the one on
2946
+ the accent is white or black by the accent's luminance (see Text on the
2947
+ accent below); set it to pin a colour of your own. */
2889
2948
  --ag-ui-on-accent: #ffffff;
2890
2949
  --ag-ui-on-danger: #ffffff;
2891
2950
 
@@ -2893,7 +2952,7 @@ ag-ui-chat {
2893
2952
  --ag-ui-width: 380px;
2894
2953
  --ag-ui-height: 560px;
2895
2954
  --ag-ui-inset: auto 24px 24px auto;
2896
- --ag-ui-shadow: 0 12px 32px rgba(20, 20, 50, 0.18);
2955
+ --ag-ui-shadow: 0 1px 2px rgb(0 0 0 / 0.05), 0 12px 32px -16px rgb(0 0 0 / 0.22);
2897
2956
  }
2898
2957
  ```
2899
2958
 
@@ -2902,7 +2961,153 @@ the panel that have no radius of their own: the answer well, the conversation li
2902
2961
  button and filter field, and the *Undo* on a run notice. The frame is the exception where it meets
2903
2962
  the edge of the window. `page` and `sidebar` draw it square, and so does every placement but
2904
2963
  `embedded` on a small viewport, while the corners inside keep the theme's radius, since none of them
2905
- meets that edge. `--ag-ui-panel-radius` sets the frame on its own, in any placement.
2964
+ meets that edge. `--ag-ui-panel-radius` sets the frame on its own, in any placement. Each theme
2965
+ gives the floating frame a radius of its own (`--ag-ui-frame-radius`: 14px in Quiet against 12px
2966
+ inside), and a host's `--ag-ui-radius` still sets both.
2967
+
2968
+ ### Themes
2969
+
2970
+ `theme` is up to two words, in any order, matched as words (`~=`):
2971
+
2972
+ ```
2973
+ theme = [name] [scheme]
2974
+ name = quiet | soft | ledger | classic | <a word of your own>
2975
+ scheme = light | dark | auto default: light
2976
+ ```
2977
+
2978
+ | Value | Result |
2979
+ | --- | --- |
2980
+ | *(absent)* | Quiet, light |
2981
+ | `dark` | Quiet, dark |
2982
+ | `auto` | Quiet, following the system's `prefers-color-scheme` |
2983
+ | `soft`, `soft dark`, `ledger auto` | that theme in that scheme |
2984
+ | `classic`, `classic dark` | the look from before themes, for a host that pins it |
2985
+ | `acme` | a word the component does not ship adds no rules, so this is Quiet, ready for your `[theme~="acme"]` CSS |
2986
+ | `soft acme` | starts from Soft; your `[theme~="acme"]` rules layer on top |
2987
+
2988
+ The built-in themes:
2989
+
2990
+ | Theme | Look |
2991
+ | --- | --- |
2992
+ | `quiet` | The default. Neutral graphite on white, no bubble for the agent's turn, hairlines, status as an icon. Set one brand accent and the user's turn, the send button and the links follow it. |
2993
+ | `soft` | Rounded and tinted: surfaces mixed from the accent, pill chips, a deep frame shadow. Violet by default. |
2994
+ | `ledger` | Dense and editorial: paper and ink, square corners, serif for what the agent says and monospace for the chrome. |
2995
+ | `classic` | The look before themes: the indigo header, the accent user bubble, the status pill and the round launcher. Pin it to keep that look. |
2996
+
2997
+ **The default scheme is `light`, not `auto`**, because the widget sits on your page and only you
2998
+ know that page's scheme. A light admin screen on a machine in dark mode would otherwise get a dark
2999
+ panel on a white page. If your page follows the system, say `auto`.
3000
+
3001
+ Each theme is three seeds, `--ag-ui-bg`, `--ag-ui-fg` and `--ag-ui-accent`, each declared with
3002
+ `light-dark()` so the scheme word picks the half. Every other colour is mixed from them with
3003
+ `color-mix(in oklch, ...)` -- hover, borders, the secondary text inks, washes, the user's turn (the
3004
+ accent at 12% over the background) -- and every derived value still reads its own public token
3005
+ first, so each stays an override point. The tokens the themes add:
3006
+
3007
+ - **Inks and lines:** `--ag-ui-ink-2`, `--ag-ui-ink-3`, `--ag-ui-line`, `--ag-ui-line-strong`.
3008
+ - **Surfaces:** `--ag-ui-wash`, `--ag-ui-accent-ink`, `--ag-ui-tint`, `--ag-ui-tint-2`.
3009
+ - **Type:** `--ag-ui-text-xs`, `--ag-ui-text-sm`, `--ag-ui-text-md`, `--ag-ui-text-lg`,
3010
+ `--ag-ui-leading`, `--ag-ui-serif`.
3011
+ - **Frame:** `--ag-ui-frame-radius`, `--ag-ui-frame-shadow`. These are what a theme sets for the
3012
+ frame; `--ag-ui-panel-radius` and `--ag-ui-shadow` stay the final say, and are what a docked
3013
+ placement squares or flattens.
3014
+ - **Charts:** `--ag-ui-chart-1` to `--ag-ui-chart-6`. A host's value beats the theme's.
3015
+ - **Status marks:** `--ag-ui-ico-check`, `--ag-ui-ico-error`, `--ag-ui-ico-declined`, mask images
3016
+ drawn in the state's colour, and `--ag-ui-ico-chevron`, the step group's disclosure chevron.
3017
+ - **Focus:** `--ag-ui-ring`, the step group head's focus outline (the accent at 55%).
3018
+
3019
+ The font is part of the theme: Quiet, Soft and Ledger set their own stack (Inter, then the system
3020
+ UI font), and Classic inherits the page's font as the component always did. Set
3021
+ `--ag-ui-font: inherit` to take the page's font under any theme.
3022
+
3023
+ #### Text on the accent
3024
+
3025
+ `--ag-ui-on-accent`, unless you set it, is picked from the accent's own luminance: white on an
3026
+ accent darker than WCAG relative luminance 0.179, black on a lighter one, which is the better of
3027
+ the two on every colour (never under 4.58:1). It is computed with relative colour syntax with
3028
+ channel arithmetic, behind `@supports` for that exact expression: Chrome 119, Firefox 128, Safari
3029
+ 18.
3030
+
3031
+ **The fallback.** Two browsers at or above the floor cannot compute it: Firefox 120 to 127, which
3032
+ lack relative colour syntax, and Safari 17.5 and 17.6, which shipped an earlier draft that rejects
3033
+ this expression. They keep a fixed colour per theme instead:
3034
+
3035
+ | Theme | Without relative colour syntax |
3036
+ | --- | --- |
3037
+ | `quiet`, `soft`, `ledger` | the scheme's: white in the light scheme, near-black (`#111111`) in the dark one |
3038
+ | `classic`, `code` | white in both schemes, the colour they always drew |
3039
+
3040
+ On every built-in accent but code's the fixed colour is on the same side as the computed one, so
3041
+ those browsers see the same contrast. On code's green the computed pick is black (8.3:1) and the
3042
+ fallback white (2.5:1), as it always was. What they miss is the correction for a host's accent: a
3043
+ mid-tone brand accent can fall under 4.5:1 there, under any theme. If you support those browsers
3044
+ and set a mid-tone accent, set `--ag-ui-on-accent` yourself. There is no script path.
3045
+
3046
+ #### Overriding a theme, lightest layer first
3047
+
3048
+ Four layers, each documented and each lighter than the next. Reach for the first one that does
3049
+ the job.
3050
+
3051
+ ##### (a) Tokens
3052
+
3053
+ Any `--ag-ui-*` token, on the element or any ancestor, as above. Per theme, from your own CSS:
3054
+
3055
+ ```css
3056
+ body { --ag-ui-accent: #0f766e; } /* accent only: the rest follows */
3057
+ ag-ui-chat[theme~="soft"] { --ag-ui-accent: #c2410c; --ag-ui-frame-radius: 12px; }
3058
+ ```
3059
+
3060
+ A page rule always beats the theme's own value, because the theme only ever sets the private
3061
+ alias behind the public token.
3062
+
3063
+ ##### (b) Parts
3064
+
3065
+ `::part()` reaches the structural elements, as listed in [Parts and slots](#parts-and-slots):
3066
+
3067
+ ```css
3068
+ ag-ui-chat::part(header) { background: linear-gradient(#f2f7f6, transparent); }
3069
+ ```
3070
+
3071
+ ##### (c) A theme of your own
3072
+
3073
+ Name it, and style the name. An unknown word adds no built-in rules, so it starts from Quiet (or
3074
+ from the built-in theme beside it):
3075
+
3076
+ ```html
3077
+ <ag-ui-chat theme="acme dark"></ag-ui-chat>
3078
+ ```
3079
+
3080
+ ```css
3081
+ ag-ui-chat[theme~="acme"] {
3082
+ --ag-ui-bg: light-dark(#fbfaf7, #10161c);
3083
+ --ag-ui-fg: light-dark(#14212b, #e4ebf0);
3084
+ --ag-ui-accent: light-dark(#0b5cad, #6cb2ff);
3085
+ --ag-ui-frame-radius: 4px;
3086
+ }
3087
+ ```
3088
+
3089
+ Use `light-dark()` in the seeds and the scheme word, and the theme toggle, work for your theme
3090
+ too.
3091
+
3092
+ ##### (d) `extraStyles`: the last mile
3093
+
3094
+ > **Unstable.** `extraStyles` reaches the class names inside the shadow root, and those are not a
3095
+ > public API: they may change in any minor release. Layers (a) to (c) are the supported ones; use
3096
+ > this for what they cannot reach, and expect to revisit it on upgrade.
3097
+
3098
+ ```ts
3099
+ const sheet = new CSSStyleSheet();
3100
+ sheet.replaceSync(`.message--assistant { padding: 10px 12px; border-radius: 10px; }`);
3101
+ chat.extraStyles = [sheet];
3102
+ ```
3103
+
3104
+ - `extraStyles: readonly CSSStyleSheet[]`, per element, default `[]`. Two elements can carry
3105
+ different sheets.
3106
+ - Adopted after the built-in sheet, so a rule of equal specificity wins.
3107
+ - Setting it re-adopts at once. The array is copied, so changing the one you passed does nothing
3108
+ until you set it again; changing a sheet's rules does apply, since the sheet itself is shared.
3109
+ - Constructed sheets are not subject to `style-src`, so this works under the same strict CSP as
3110
+ the built-in sheet. There is no string form and no `<style>` injection.
2906
3111
 
2907
3112
  ### Small viewports
2908
3113
 
@@ -3082,7 +3287,7 @@ highlights too. Without it they fall back to the package indigo.
3082
3287
  For the common cases there are three CSS-reactive **preset attributes** (no JS API), so you don't
3083
3288
  have to hand-tune the variables:
3084
3289
 
3085
- - `theme` — `light` (default) / `dark` / `auto` (follow the OS) / `code`.
3290
+ - `theme` — a theme name and a scheme; see [Themes](#themes).
3086
3291
  - `density` — `comfortable` (default) / `compact`.
3087
3292
  - `placement` — `floating` (default) / `sidebar` / `page` / `embedded`. `embedded` drops the
3088
3293
  fixed positioning and z-index so the widget sits in normal document flow; `page` is a
@@ -3109,7 +3314,8 @@ fix belongs to the containing element, not to the widget:
3109
3314
  <ag-ui-chat endpoint="/agent/" theme="dark" density="compact" placement="sidebar"></ag-ui-chat>
3110
3315
  ```
3111
3316
 
3112
- See [`src/ui/styles.ts`](src/ui/styles.ts) for the full variable + preset list. The
3317
+ See [`src/ui/shell/token_styles.ts`](src/ui/shell/token_styles.ts) for the full variable list and
3318
+ [`src/ui/placement/`](src/ui/placement/) for the placement presets. The
3113
3319
  [`demo/`](demo/) live playground (`node demo/mock-server.mjs`) flips theme, density, placement,
3114
3320
  text-animation, tool-display, and the answer well live from a single page, and demos the
3115
3321
  streamed thoughts region, the mic, and the header theme toggle.
@@ -3145,7 +3351,8 @@ component sets, so a new one cannot ship undocumented.
3145
3351
  | Message actions | `message-actions`, `message-action` (plus `message-action-retry`, `message-action-copy`, `message-action-up`, `message-action-down`), and the icon holder inside each: `message-action-icon` (plus `message-action-icon-retry`, `message-action-icon-copy`, `message-action-icon-up`, `message-action-icon-down`) |
3146
3352
  | Queued messages | `queued`, `queued-chip` |
3147
3353
  | Run notices | `run-notice` (plus `run-notice-interrupted`, `run-notice-attachment-pending`, `run-notice-compaction`, `run-notice-skill`, `run-notice-history-replaced`, `run-notice-chart-undrawable`, `run-notice-surface`, `run-notice-not-connected`), `run-notice-icon`, `run-notice-text`, `run-notice-undo` |
3148
- | Tool cards | `tool-card`, `tool-card-head`, `tool-card-icon`, `tool-card-name`, `tool-card-status`, `tool-card-decision`, `tool-card-toggle`, `tool-card-body`, `tool-card-section` (plus `tool-card-args-section`, `tool-card-result-section`), `tool-card-section-label` (plus `tool-card-args-label`, `tool-card-result-label`), `tool-card-args`, `tool-card-result`, `tool-card-approval`, `tool-card-subagent` |
3354
+ | Tool cards | `tool-card`, `tool-card-head`, `tool-card-icon`, `tool-card-name`, `tool-card-status`, `tool-card-decision`, `tool-card-toggle`, `tool-card-body`, `tool-card-section` (plus `tool-card-args-section`, `tool-card-result-section`), `tool-card-section-label` (plus `tool-card-args-label`, `tool-card-result-label`), `tool-card-args`, `tool-card-result`, `tool-card-approval`, `tool-card-subagent`, `tool-card-repeat` |
3355
+ | Tool-step group | `tool-group`, `tool-group-head`, `tool-group-icon`, `tool-group-label`, `tool-group-meta`, `tool-group-list` |
3149
3356
  | Delegated sub-agents | `subagent`, `subagent-row`, `subagent-icon`, `subagent-status`, `subagent-steps`, `subagent-step`, `subagent-step-icon`, `subagent-step-name` |
3150
3357
  | Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`, `confirm-always`) |
3151
3358
  | Server-side approval | `approval`, `approval-body`, `approval-actions`, `approval-button` (plus `approval-approve`, `approval-deny`), `approval-edit`, `approval-args`, `approval-error` |
@@ -3367,8 +3574,12 @@ mic appears even with no `data-transcribe-url`.
3367
3574
 
3368
3575
  `theme` is a plain attribute you can set yourself, and a host can always drop its own switch into
3369
3576
  `slot="header-actions"`. For convenience, the boolean `data-theme-toggle` attribute adds a built-in
3370
- light⇄dark toggle to the header (part `theme-toggle`) that flips `theme` and persists the choice
3371
- per tab. Off by default so it never competes with a host-supplied control.
3577
+ light⇄dark toggle to the header (part `theme-toggle`) that flips the scheme word of `theme` and
3578
+ persists the choice per tab. Only the scheme changes: `soft` becomes `soft dark`, `soft dark`
3579
+ becomes `soft light`, and `auto` becomes `dark`, an explicit choice. Only the scheme is saved, too:
3580
+ on the next load it is put beside whatever theme name the markup carries, so `dark` chosen on a
3581
+ `soft` page restores as `classic dark` on a `classic` one. Off by default so it never competes with
3582
+ a host-supplied control.
3372
3583
 
3373
3584
  ```html
3374
3585
  <ag-ui-chat endpoint="/agent/" data-theme-toggle></ag-ui-chat>
@@ -3418,7 +3629,7 @@ This produces, into `dist/`:
3418
3629
  - `index.d.ts` (+ source maps) — type declarations; emitted `.js` import specifiers are preserved
3419
3630
  so consumers resolve types without extra flags.
3420
3631
 
3421
- There is **no CSS file to load**. The styles are a template literal injected into
3632
+ There is **no CSS file to load**. The styles are string constants adopted into
3422
3633
  the shadow root at construction, so they ship inside the JS and cannot leak into
3423
3634
  the host page. Restyle through the [CSS custom properties](#theming-density-and-placement) and the
3424
3635
  `part` attributes, not a stylesheet override.
@@ -3439,12 +3650,14 @@ Other workflow targets (all identical in name to the sibling Python packages):
3439
3650
  | Component | Floor | Tested |
3440
3651
  | --- | --- | --- |
3441
3652
  | Node (tooling/tests only) | 22 | 22, 24 |
3442
- | Browsers (runtime target) | ES2022 / evergreen | Chrome / Firefox / Safari 17+ |
3653
+ | Browsers (runtime target) | Chrome 123, Firefox 120, Safari 17.5 | Chromium |
3443
3654
  | `@ag-ui/client`, `@ag-ui/core` | 1.0 | 1.0 |
3444
3655
  | django-ag-ui (server) | 0.63.0 | — |
3445
3656
 
3446
- The shipped artefact targets evergreen browsers (Shadow DOM, Custom Elements v1, ES2022). Node is
3447
- only the build/test runtime, not a runtime target.
3657
+ The shipped artefact targets evergreen browsers (Shadow DOM, Custom Elements v1, ES2022). The
3658
+ browser floor is set by `light-dark()`, which every theme declares its colours with: below Chrome
3659
+ 123, Firefox 120 or Safari 17.5 those colours do not resolve. Node is only the build/test runtime,
3660
+ not a runtime target.
3448
3661
 
3449
3662
  The server floor is a wire contract rather than a dependency. A tool call's outcome and a user
3450
3663
  message's attachment refs travel in `metadata`, since `@ag-ui/client` 1.0 strips every key its