@artooi/ag-ui-web-component 0.1.0 → 0.2.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 (109) hide show
  1. package/CHANGELOG.md +102 -2
  2. package/README.md +235 -47
  3. package/dist/ag-ui-web-component.bundle.js +451 -46
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +34 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/{ag_ui_chat.d.ts → core/ag_ui_chat.d.ts} +63 -6
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -0
  9. package/dist/core/agui_client.d.ts.map +1 -0
  10. package/dist/core/conversation_store.d.ts.map +1 -0
  11. package/dist/core/create_http_agent.d.ts.map +1 -0
  12. package/dist/core/define_ag_ui_chat.d.ts.map +1 -0
  13. package/dist/dom/animations.d.ts +71 -0
  14. package/dist/dom/animations.d.ts.map +1 -0
  15. package/dist/{dom_driver.d.ts → dom/dom_driver.d.ts} +7 -1
  16. package/dist/dom/dom_driver.d.ts.map +1 -0
  17. package/dist/dom/native_setter.d.ts +5 -0
  18. package/dist/dom/native_setter.d.ts.map +1 -0
  19. package/dist/index.d.ts +19 -16
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3886 -388
  22. package/dist/index.js.map +4 -4
  23. package/dist/skills/fill_template.d.ts +17 -0
  24. package/dist/skills/fill_template.d.ts.map +1 -0
  25. package/dist/skills/parse_skills.d.ts +8 -0
  26. package/dist/skills/parse_skills.d.ts.map +1 -0
  27. package/dist/skills/skill.d.ts +24 -0
  28. package/dist/skills/skill.d.ts.map +1 -0
  29. package/dist/{client_tool_registry.d.ts → tools/client_tool_registry.d.ts} +6 -1
  30. package/dist/tools/client_tool_registry.d.ts.map +1 -0
  31. package/dist/tools/is_destructive.d.ts.map +1 -0
  32. package/dist/tools/is_navigates.d.ts.map +1 -0
  33. package/dist/tools/page_map.d.ts.map +1 -0
  34. package/dist/tools/route_map.d.ts +40 -0
  35. package/dist/tools/route_map.d.ts.map +1 -0
  36. package/dist/tools/state_hook.d.ts.map +1 -0
  37. package/dist/ui/confirmation_card.d.ts +22 -0
  38. package/dist/ui/confirmation_card.d.ts.map +1 -0
  39. package/dist/ui/render_markdown.d.ts +13 -0
  40. package/dist/ui/render_markdown.d.ts.map +1 -0
  41. package/dist/ui/reveal_words.d.ts +8 -0
  42. package/dist/ui/reveal_words.d.ts.map +1 -0
  43. package/dist/ui/skills_menu.d.ts +35 -0
  44. package/dist/ui/skills_menu.d.ts.map +1 -0
  45. package/dist/ui/styles.d.ts +2 -0
  46. package/dist/ui/styles.d.ts.map +1 -0
  47. package/dist/ui/tool_call_card.d.ts +35 -0
  48. package/dist/ui/tool_call_card.d.ts.map +1 -0
  49. package/package.json +5 -3
  50. package/src/constants.ts +38 -0
  51. package/src/core/ag_ui_chat.ts +786 -0
  52. package/src/{agui_client.ts → core/agui_client.ts} +1 -1
  53. package/src/{define_ag_ui_chat.ts → core/define_ag_ui_chat.ts} +1 -1
  54. package/src/dom/animations.ts +201 -0
  55. package/src/{dom_driver.ts → dom/dom_driver.ts} +35 -2
  56. package/src/dom/native_setter.ts +42 -0
  57. package/src/index.ts +61 -32
  58. package/src/skills/fill_template.ts +32 -0
  59. package/src/skills/parse_skills.ts +26 -0
  60. package/src/skills/skill.ts +23 -0
  61. package/src/{client_tool_registry.ts → tools/client_tool_registry.ts} +6 -4
  62. package/src/{is_destructive.ts → tools/is_destructive.ts} +1 -1
  63. package/src/{is_navigates.ts → tools/is_navigates.ts} +1 -1
  64. package/src/tools/route_map.ts +140 -0
  65. package/src/{state_hook.ts → tools/state_hook.ts} +1 -1
  66. package/src/ui/confirmation_card.ts +71 -0
  67. package/src/ui/render_markdown.ts +68 -0
  68. package/src/ui/reveal_words.ts +41 -0
  69. package/src/ui/skills_menu.ts +171 -0
  70. package/src/ui/styles.ts +631 -0
  71. package/src/{tool_call_card.ts → ui/tool_call_card.ts} +50 -19
  72. package/src/version.ts +1 -1
  73. package/dist/ag_ui_chat.d.ts.map +0 -1
  74. package/dist/agui_client.d.ts.map +0 -1
  75. package/dist/animations.d.ts +0 -33
  76. package/dist/animations.d.ts.map +0 -1
  77. package/dist/client_tool_registry.d.ts.map +0 -1
  78. package/dist/confirmation_modal.d.ts +0 -14
  79. package/dist/confirmation_modal.d.ts.map +0 -1
  80. package/dist/conversation_store.d.ts.map +0 -1
  81. package/dist/create_http_agent.d.ts.map +0 -1
  82. package/dist/define_ag_ui_chat.d.ts.map +0 -1
  83. package/dist/dom_driver.d.ts.map +0 -1
  84. package/dist/is_destructive.d.ts.map +0 -1
  85. package/dist/is_navigates.d.ts.map +0 -1
  86. package/dist/page_map.d.ts.map +0 -1
  87. package/dist/route_map.d.ts +0 -27
  88. package/dist/route_map.d.ts.map +0 -1
  89. package/dist/state_hook.d.ts.map +0 -1
  90. package/dist/styles.d.ts +0 -2
  91. package/dist/styles.d.ts.map +0 -1
  92. package/dist/tool_call_card.d.ts +0 -29
  93. package/dist/tool_call_card.d.ts.map +0 -1
  94. package/src/ag_ui_chat.ts +0 -411
  95. package/src/animations.ts +0 -86
  96. package/src/confirmation_modal.ts +0 -69
  97. package/src/route_map.ts +0 -83
  98. package/src/styles.ts +0 -296
  99. /package/dist/{agui_client.d.ts → core/agui_client.d.ts} +0 -0
  100. /package/dist/{conversation_store.d.ts → core/conversation_store.d.ts} +0 -0
  101. /package/dist/{create_http_agent.d.ts → core/create_http_agent.d.ts} +0 -0
  102. /package/dist/{define_ag_ui_chat.d.ts → core/define_ag_ui_chat.d.ts} +0 -0
  103. /package/dist/{is_destructive.d.ts → tools/is_destructive.d.ts} +0 -0
  104. /package/dist/{is_navigates.d.ts → tools/is_navigates.d.ts} +0 -0
  105. /package/dist/{page_map.d.ts → tools/page_map.d.ts} +0 -0
  106. /package/dist/{state_hook.d.ts → tools/state_hook.d.ts} +0 -0
  107. /package/src/{conversation_store.ts → core/conversation_store.ts} +0 -0
  108. /package/src/{create_http_agent.ts → core/create_http_agent.ts} +0 -0
  109. /package/src/{page_map.ts → tools/page_map.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -7,7 +7,107 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] — 2026-06-02
11
+
12
+ ### Added
13
+ - **Markdown + HTML rendering** in assistant message bubbles (`renderMarkdown`),
14
+ sanitised with DOMPurify (scripts, event handlers, and `javascript:` URLs
15
+ stripped; links hardened with `target`/`rel`). User messages stay literal.
16
+ - **Pending indicator** — an animated "thinking" indicator shown while the agent
17
+ is awaited (before the first token and between tool rounds), honouring
18
+ `prefers-reduced-motion`.
19
+ - **New-chat button** in the header — clears the transcript, the persisted
20
+ conversation, and the in-memory run state, and mints a fresh thread.
21
+ - **Collapse seam** — a reflected `collapsed` attribute, a built-in header
22
+ toggle, a persisted (per-tab) collapsed state, and a `TOGGLE_EVENT`
23
+ (`ag-ui-toggle`) so a host can drive its own chrome.
24
+ - **Tool-call display modes** (`TOOL_DISPLAY`, `data-tool-display`): `minimal`
25
+ (name + status), `compact` (args + result behind one "Details" toggle), and
26
+ `full` (the default; original behaviour).
27
+ - **Richer action animations**: `pressThenClick`, `selectOption`,
28
+ `toggleControl` (+ `pressButton` / `selectControl` / `toggleCheckbox` driver
29
+ wrappers), all honouring `prefers-reduced-motion`.
30
+ - **Dynamic route syntax**: `Route.path` supports `:name` placeholders;
31
+ `navigate_to_route` substitutes path params (leftover params → query string)
32
+ and `list_routes` advertises each route's `pathParams`.
33
+ - `X_CONFIRM_KEY` (`x-confirm`) tool metadata for a human-readable confirmation
34
+ prompt.
35
+ - `setNativeValue` / `setNativeChecked` utilities (also used internally).
36
+ - **Theming** — a `theme` attribute (`light` / `dark` / `auto` / `code`); `auto`
37
+ follows `prefers-color-scheme`, `code` is a monospace terminal palette. Plus a
38
+ wider set of themeable `--ag-ui-*` variables.
39
+ - **Density + placement presets** — `density` (`comfortable` / `compact`) and
40
+ `placement` (`bottom-left` / `side` / `full` / `embedded`); `embedded` drops
41
+ the floating chrome and high z-index so the widget lives in the host layout.
42
+ - **Incoming-text animations** — `data-text-animation` (`none` / `fade` /
43
+ `word`), the last revealing assistant text word-by-word; honours
44
+ `prefers-reduced-motion`.
45
+ - **`confirmPredicate`** — a per-call `(toolName, args) => boolean | Promise`
46
+ hook deciding confirmation dynamically (authoritative over `x-destructive`).
47
+ - **Built-in `read_page` tool** — present when a `getPageMap` provider is set, so
48
+ the agent can re-read the page mid-turn after acting.
49
+ - **`x-summary`** tool metadata (`X_SUMMARY_KEY`) — a friendly label shown on the
50
+ tool-call card instead of the raw tool name.
51
+ - `observedAttributes` / `attributeChangedCallback` so a late `title-text`
52
+ change updates the header.
53
+ - Accessibility: `role="log"`/`aria-live` on the transcript, `role="status"` on
54
+ the pending indicator, `role="group"` on the confirmation card, input label.
55
+ - **Skills** — pre-defined prompts surfaced as **chips** (`data-prompt-chips`)
56
+ and/or a **`/`-command palette** (`data-slash-commands`), both opt-in over one
57
+ catalog. Catalog from the `skills` setter (`setSkills`), a `data-skills` JSON
58
+ embed, and/or a fetched `data-skills-url` (merged backend → embed → client).
59
+ Picking pre-fills the input (or auto-sends with `sendImmediately`); prompts
60
+ support `{placeholder}`s filled from `skillContext`, with a missing value
61
+ blocking the send and showing a hint. Exports the `Skill` type.
62
+
63
+ ### Changed
64
+ - The destructive-action confirmation is now an **inline card in the transcript**
65
+ (Confirm / Cancel, with the `x-confirm` message) instead of a focus-stealing
66
+ modal overlay. `requestConfirmation` now renders inline; the
67
+ `confirmation_modal` module was removed.
68
+ - **Framework-controlled inputs now work.** `fillField` / `typeInto` /
69
+ `selectOption` / `toggleControl` / `setControlValue` set `value` / `checked`
70
+ through the **native prototype setter** before dispatching `input`, so
71
+ React/Vue/Svelte value-tracking sees the change (previously the field looked
72
+ filled but host state stayed empty).
73
+ - **Framework interop:** reflecting **property setters** for `endpoint`,
74
+ `toolDisplay`, and `collapsed` (React 19 assigns matching props as element
75
+ properties — getter-only props previously threw).
76
+ - `registerTool` is now **idempotent** — re-registering a tool name replaces it
77
+ instead of throwing (re-fired refs / React StrictMode).
78
+
79
+ ### Fixed
80
+ - The Markdown/HTML allowlist now permits sanitised **`<img>`** (safe-scheme
81
+ `src`, no event handlers); `javascript:` srcs and disallowed tags (e.g.
82
+ `iframe`) are still stripped.
83
+
84
+ ## [0.1.1] — 2026-06-01
85
+
86
+ ### Changed
87
+ - CI: resolve the pnpm version from `package.json`'s `packageManager` only
88
+ (dropped the conflicting `version` input in the release/test workflows).
89
+
90
+ ### Notes
91
+ - First fully-automated release via the npm OIDC publish pipeline — 0.1.0 was
92
+ the manual bootstrap publish that created the package.
93
+
94
+ ## [0.1.0] — 2026-06-01
95
+
10
96
  ### Added
11
- - Initial scaffold.
97
+ - `<ag-ui-chat>` Web Component over AG-UI: Shadow-DOM chat UI, a pluggable tool
98
+ registry (`registerTool`), the `x-destructive` confirmation modal, and the
99
+ DOM-driver + animation primitives.
100
+ - Durable conversation + resumable run loop that survives the full page reloads
101
+ of a multi-page app: `SessionStorageStore`, a persisted thread id, and
102
+ `x-navigates` checkpoints completed via `navigationResult` on the next mount.
103
+ - Host seams: `routeMap` (+ `list_routes` / `navigate_to_route`), an
104
+ auto-injected `getPageMap` context, `registerStateHook`, and an optional
105
+ client-side `navigate()` callback (SPA vs MPA).
106
+
107
+ ### Notes
108
+ - First release — exercising the automated npm OIDC publish pipeline end-to-end.
12
109
 
13
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.1.0...HEAD
110
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.2.0...HEAD
111
+ [0.2.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.1.1...v0.2.0
112
+ [0.1.1]: https://github.com/Artui/ag-ui-web-component/compare/v0.1.0...v0.1.1
113
+ [0.1.0]: https://github.com/Artui/ag-ui-web-component/releases/tag/v0.1.0
package/README.md CHANGED
@@ -17,8 +17,13 @@ It wraps [`@ag-ui/client`](https://www.npmjs.com/package/@ag-ui/client)'s `HttpA
17
17
  - Generic **DOM-driver primitives** (`fillField`, `clickElement`, `setControlValue`) and
18
18
  **animation primitives** (`typeInto`, `highlightThenClick`, …) so the agent can drive the page
19
19
  at human-readable speed.
20
- - A **confirmation modal** that intercepts destructive tool calls (those whose JSON Schema
21
- carries `x-destructive: true`) before the handler runs.
20
+ - An **inline confirmation card** that intercepts tool calls needing confirmation (those whose
21
+ JSON Schema carries `x-destructive: true`, or a per-call `confirmPredicate`) before the handler
22
+ runs — rendered right in the transcript, never a modal overlay.
23
+ - **Markdown + HTML rendering** of assistant replies (sanitized `marked` + DOMPurify), with
24
+ themes, density/placement presets, incoming-text animations, tool-call display modes, an
25
+ animated thinking indicator, and an opt-in **skills** palette (prompt chips + `/`-commands).
26
+ - A **new-chat** button and a **collapse** toggle built into the header.
22
27
  - An **MPA durability story**: a durable conversation store, a stable thread id, and a resumable
23
28
  run loop that survives full page reloads (`x-navigates` + `navigationResult`).
24
29
  - **Host seams** for SPAs: a route map, an auto-injected page map, state hooks, and an optional
@@ -36,12 +41,16 @@ No framework, no Django, no admin specifics live here. Downstream consumers (e.g
36
41
  - [Core concepts](#core-concepts)
37
42
  - [The run loop and the AG-UI client](#the-run-loop-and-the-ag-ui-client)
38
43
  - [Registering tools](#registering-tools)
39
- - [The confirmation modal (`x-destructive`)](#the-confirmation-modal-x-destructive)
44
+ - [Inline confirmation (`x-destructive` / `x-confirm` / `confirmPredicate`)](#inline-confirmation-x-destructive--x-confirm--confirmpredicate)
40
45
  - [DOM-driver and animation primitives](#dom-driver-and-animation-primitives)
46
+ - [New chat and collapse](#new-chat-and-collapse)
47
+ - [Tool-call display modes](#tool-call-display-modes)
48
+ - [Markdown rendering](#markdown-rendering)
49
+ - [Skills: prompt chips and slash palette](#skills-prompt-chips-and-slash-palette)
41
50
  - [MPA durability: surviving full page reloads](#mpa-durability-surviving-full-page-reloads)
42
51
  - [Host seams: the SPA story](#host-seams-the-spa-story)
43
52
  - [Public API surface](#public-api-surface)
44
- - [Theming](#theming)
53
+ - [Theming, density, and placement](#theming-density-and-placement)
45
54
  - [Building the bundle](#building-the-bundle)
46
55
  - [Compatibility](#compatibility)
47
56
 
@@ -110,7 +119,7 @@ Drop the element into your page and register the tools the agent may call:
110
119
  });
111
120
 
112
121
  // A destructive tool: x-destructive at the JSON-Schema root gates it behind
113
- // the confirmation modal before the handler runs.
122
+ // the inline confirmation card before the handler runs.
114
123
  chat.registerTool({
115
124
  name: "save_article",
116
125
  description: "Save the article. Destructive — asks for confirmation.",
@@ -130,14 +139,32 @@ That's the whole integration: an `endpoint` attribute pointing at your AG-UI ser
130
139
 
131
140
  ### Attributes and properties
132
141
 
142
+ **Attributes** (set in HTML; the CSS-only ones are styling presets with no JS API):
143
+
133
144
  | Attribute | Property | Notes |
134
145
  | --- | --- | --- |
135
- | `endpoint` | `endpoint` (getter) | The AG-UI endpoint URL. Required to send. |
136
- | `title-text` | — | Header label; defaults to `"Assistant"`. |
137
- | | `headers` | `Record<string, string>` of extra HTTP headers. |
138
- | | `autoConfirm` | When `true`, destructive tools run without the modal. |
139
-
140
- A self-contained working example lives in [`demo/`](demo/) run `make demo` to serve it against a
146
+ | `endpoint` | `endpoint` | The AG-UI endpoint URL. Required to send. Reflecting getter + setter. |
147
+ | `title-text` | — | Header label; defaults to `"Assistant"`. The only **observed** attribute (live-updates the header). |
148
+ | `data-tool-display` | `toolDisplay` | Tool-call card detail: `minimal` / `compact` / `full` (default `full`). |
149
+ | `data-text-animation` | | Incoming-text reveal: `none` (default) / `fade` / `word`. |
150
+ | `data-prompt-chips` | — | `"true"` to surface skills as chips. |
151
+ | `data-slash-commands` || `"true"` to enable the `/`-command palette. |
152
+ | `data-skills` | — | Inline JSON skill catalog. |
153
+ | `data-skills-url` | — | URL of a JSON skill catalog (fetched with `headers`). |
154
+ | `collapsed` | `collapsed` | Reflected boolean; collapses the widget. Persisted per-tab in `sessionStorage`. |
155
+ | `theme` | — | CSS-only: `light` (default) / `dark` / `auto` / `code`. |
156
+ | `density` | — | CSS-only: `comfortable` (default) / `compact`. |
157
+ | `placement` | — | CSS-only: `floating` (default) / `bottom-left` / `side` / `full` / `embedded`. |
158
+
159
+ **Properties** (JS only, not attributes): `headers`, `autoConfirm`, `confirmPredicate`,
160
+ `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`, `getPageMap`,
161
+ `autoInjectPageMap`, `conversationStore`, `navigationResult`, `skillContext`, plus the mirrors
162
+ `endpoint` / `toolDisplay` / `collapsed`.
163
+
164
+ **Methods**: `registerTool`, `registerStateHook`, `setSkills`, `appendMessage`, `newChat`,
165
+ `setCollapsed`, `toggleCollapsed`.
166
+
167
+ A self-contained live playground lives in [`demo/`](demo/) — run `make demo` to serve it against a
141
168
  mock AG-UI server.
142
169
 
143
170
  ---
@@ -146,9 +173,9 @@ mock AG-UI server.
146
173
 
147
174
  ### The run loop and the AG-UI client
148
175
 
149
- `<ag-ui-chat>` is the view; [`AgUiClient`](src/agui_client.ts) is the orchestration layer over an
150
- AG-UI `AbstractAgent`. On the first send the element builds a client (via the overridable
151
- `agentFactory`, which defaults to [`createHttpAgent`](src/create_http_agent.ts)). Each turn:
176
+ `<ag-ui-chat>` is the view; [`AgUiClient`](src/core/agui_client.ts) is the orchestration layer over
177
+ an AG-UI `AbstractAgent`. On the first send the element builds a client (via the overridable
178
+ `agentFactory`, which defaults to [`createHttpAgent`](src/core/create_http_agent.ts)). Each turn:
152
179
 
153
180
  1. The user message is appended and the agent runs once.
154
181
  2. AG-UI subscriber events are translated into the element's handlers — streaming text deltas
@@ -184,48 +211,181 @@ Names must be unique (registering a duplicate throws). Each `<ag-ui-chat>` eleme
184
211
  registry, AG-UI client, and Shadow DOM, so **multiple instances on one page never interfere** —
185
212
  there is no module-level shared state anywhere in the package.
186
213
 
187
- ### The confirmation modal (`x-destructive`)
214
+ ### Inline confirmation (`x-destructive` / `x-confirm` / `confirmPredicate`)
215
+
216
+ When a tool call needs confirmation, the element appends an **inline confirmation card** (a
217
+ `<div class="confirm">`) to the transcript via
218
+ [`requestConfirmation`](src/ui/confirmation_card.ts) — it is not a modal overlay. The card reads
219
+ naturally after the assistant's explanation, never steals focus from the page, and stays in the
220
+ transcript as a resolved record after the decision:
221
+
222
+ - **Confirm** → the handler runs and the result is posted back.
223
+ - **Cancel** → a `"User declined the action."` result is posted; the agent acknowledges on its
224
+ next turn.
225
+
226
+ Whether a call is gated is decided in this order:
227
+
228
+ 1. If `chat.autoConfirm === true`, the call **never** prompts (an "autopilot" toggle).
229
+ 2. Else if `chat.confirmPredicate` is set, its boolean return is authoritative — given the tool
230
+ name + parsed args it decides per-call (so one tool can be instant for some args and confirmed
231
+ for others, which a static flag can't express).
232
+ 3. Else the element falls back to [`isDestructive(parameters)`](src/tools/is_destructive.ts),
233
+ which reads the `x-destructive` JSON-Schema flag.
188
234
 
189
235
  AG-UI has no built-in risk flag, so destructiveness is carried as a JSON-Schema extension at the
190
236
  **schema root**: `parameters["x-destructive"] = true` (use the exported `X_DESTRUCTIVE_KEY`
191
- constant). There is no parallel metadata channel the flag lives on the schema, the registry
192
- forwards it verbatim to `RunAgentInput.tools`, and [`isDestructive`](src/is_destructive.ts) reads
193
- it back.
237
+ constant). There is no parallel metadata channel and no name heuristic destructiveness is exactly
238
+ the `x-destructive` flag (or `confirmPredicate`). The registry forwards the flag verbatim to
239
+ `RunAgentInput.tools`.
194
240
 
195
- When the agent calls a destructive tool, the element shows the
196
- [confirmation modal](src/confirmation_modal.ts) (rendered inside its own Shadow DOM) **before**
197
- dispatching to the handler:
241
+ If the schema carries an `x-confirm` string (use `X_CONFIRM_KEY`), the card shows it as the prompt;
242
+ otherwise it falls back to a generic `Run "<tool>"?`.
198
243
 
199
- - **Confirm** → the handler runs and the result is posted back.
200
- - **Cancel / dismiss** → a `"User declined the action."` result is posted; the agent acknowledges
201
- on its next turn.
244
+ ```js
245
+ // Per-call: confirm a delete only when it would remove more than one row.
246
+ chat.confirmPredicate = (name, args) =>
247
+ name === "delete_rows" && Array.isArray(args.ids) && args.ids.length > 1;
202
248
 
203
- Set `chat.autoConfirm = true` to bypass the modal (an "autopilot" toggle).
249
+ // Prompt text via x-confirm:
250
+ chat.registerTool({
251
+ name: "activate_project",
252
+ description: "Activate the current project.",
253
+ parameters: { type: "object", properties: {}, [X_DESTRUCTIVE_KEY]: true, [X_CONFIRM_KEY]: "Activate this project?" },
254
+ handler: async () => await api.activate(),
255
+ });
256
+ ```
204
257
 
205
258
  ### DOM-driver and animation primitives
206
259
 
207
260
  So the agent can visibly drive the host page, the package ships generic, framework-free
208
- primitives. The **animation** primitives ([`animations.ts`](src/animations.ts)) operate at
261
+ primitives. The **animation** primitives ([`animations.ts`](src/dom/animations.ts)) operate at
209
262
  human-readable speed (configurable; pass small/zero durations in tests):
210
263
 
211
264
  - `typeInto(el, value, { charDelayMs })` — clears and types a value character by character,
212
265
  firing `input`/`change` events as a real user would.
213
- - `highlightThenClick(el, { highlightMs })` outlines an element, pauses, then clicks.
266
+ - `highlightThenClick(el, { highlightMs })` / `pressThenClick(el, options)` outline/press an
267
+ element, pause, then click.
268
+ - `selectOption(el, value)` / `toggleControl(el, checked)` — animate a `<select>` / checkbox.
214
269
  - `scrollIntoCenterView(el)` / `focusWithFlash(el, { flashMs })`.
270
+ - `prefersReducedMotion()` — honoured throughout so animations collapse to instant when the user
271
+ asks for reduced motion.
215
272
 
216
- The **DOM-driver** primitives ([`dom_driver.ts`](src/dom_driver.ts)) compose those into the
273
+ The **DOM-driver** primitives ([`dom_driver.ts`](src/dom/dom_driver.ts)) compose those into the
217
274
  operations a tool handler typically wants:
218
275
 
219
276
  - `fillField(el, value, options)` — scroll to, focus-flash, and type into a text field.
220
- - `clickElement(el, options)` — scroll to, highlight, and click.
277
+ - `clickElement(el, options)` / `pressButton(el, options)` — scroll to, highlight/press, and click.
278
+ - `selectControl(el, value)` / `toggleCheckbox(el, checked)` — animate a `<select>` / checkbox.
221
279
  - `setControlValue(el, value)` — set a `<select>` or checkbox without animation, dispatching
222
280
  `input`/`change`.
223
281
 
282
+ The native-setter helpers ([`native_setter.ts`](src/dom/native_setter.ts)) — `setNativeValue` /
283
+ `setNativeChecked` — set a control through its native prototype setter so React-controlled inputs
284
+ register the change.
285
+
224
286
  Each takes an element the caller has already located; host packages wrap them with
225
287
  environment-aware lookups (e.g. "find `#id_<name>`, then `fillField`").
226
288
 
227
289
  ---
228
290
 
291
+ ## New chat and collapse
292
+
293
+ The header carries two built-in buttons: a new-chat (✚) button and a collapse (—) toggle. The
294
+ matching JS API:
295
+
296
+ - `newChat()` — clears the transcript and the persisted history, drops the in-memory run state,
297
+ and mints a new thread id.
298
+ - `setCollapsed(collapsed)` / `toggleCollapsed()` — collapse or expand the widget. The state is
299
+ reflected as the boolean `collapsed` attribute/property and persisted per-tab in
300
+ `sessionStorage`, so it survives a reload.
301
+
302
+ Each change emits an `ag-ui-toggle` event (the `TOGGLE_EVENT` constant) with
303
+ `detail: { collapsed: boolean }` (typed `ToggleDetail`), so a host can mirror the state in its own
304
+ chrome — or hide the built-in toggle and drive the `collapsed` attribute itself.
305
+
306
+ ```js
307
+ chat.newChat();
308
+ chat.toggleCollapsed();
309
+ chat.addEventListener("ag-ui-toggle", (e) => console.log(e.detail.collapsed));
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Tool-call display modes
315
+
316
+ How much a tool-call card shows is set via the `data-tool-display` attribute (or `toolDisplay`
317
+ property), one of `minimal` / `compact` / `full` (default `full`):
318
+
319
+ - `minimal` — tool name + status pill only.
320
+ - `compact` — name + status, with args *and* result behind a single collapsed "Details" toggle.
321
+ - `full` — args inline, result behind its own toggle (the original behaviour).
322
+
323
+ If a tool's schema carries an `x-summary` string (use `X_SUMMARY_KEY`), the card shows it on the
324
+ label instead of the raw tool name.
325
+
326
+ ```html
327
+ <ag-ui-chat endpoint="/agent/" data-tool-display="compact"></ag-ui-chat>
328
+ ```
329
+
330
+ ---
331
+
332
+ ## Markdown rendering
333
+
334
+ Assistant bubbles render sanitized markdown/HTML via [`marked`](https://www.npmjs.com/package/marked)
335
+ (GitHub-flavoured, single-newline line breaks) piped through
336
+ [DOMPurify](https://www.npmjs.com/package/dompurify). User messages stay literal text. The
337
+ allowlist permits emphasis, code, lists, quotes, headings, links, tables, and images (`img`); links
338
+ are hardened with `target="_blank" rel="noopener noreferrer"`; `iframe`/`style`/scripting are
339
+ excluded. The exported helper `renderMarkdown(text)` does this standalone. `marked` and `dompurify`
340
+ are runtime dependencies.
341
+
342
+ An animated 3-dot "thinking" indicator (`role="status"`, with an aria-label) appears before the
343
+ first token and between tool rounds, honouring `prefers-reduced-motion`. It has no public API.
344
+
345
+ ### Incoming-text animations
346
+
347
+ The `data-text-animation` attribute controls how a fully-received assistant message reveals:
348
+ `none` (default) / `fade` (a CSS fade) / `word` (JS word-by-word via the internal `wrapWords`
349
+ reveal). It honours `prefers-reduced-motion` (collapsing to instant).
350
+
351
+ ```html
352
+ <ag-ui-chat endpoint="/agent/" data-text-animation="word"></ag-ui-chat>
353
+ ```
354
+
355
+ ---
356
+
357
+ ## Skills: prompt chips and slash palette
358
+
359
+ Skills are pre-defined prompts the user can launch from a chip or the `/`-command palette. They are
360
+ opt-in via two attributes:
361
+
362
+ ```html
363
+ <ag-ui-chat endpoint="/agent/" data-prompt-chips="true" data-slash-commands="true"></ag-ui-chat>
364
+ ```
365
+
366
+ A `Skill` is `{ name, title, description?, prompt, sendImmediately?, chip? }`. Skills are merged
367
+ from three sources — **backend → embed → client** (later wins by `name`):
368
+
369
+ - `data-skills-url` — a JSON endpoint, fetched with the element's `headers`.
370
+ - `data-skills` — an inline JSON catalog.
371
+ - `setSkills(skills)` — set the client catalog from JS.
372
+
373
+ ```js
374
+ chat.setSkills([
375
+ { name: "summarize", title: "Summarize page", prompt: "Summarize {title}.", chip: true },
376
+ ]);
377
+ ```
378
+
379
+ A skill `prompt` may contain `{placeholder}` tokens; the `skillContext` property
380
+ (`() => Record<string, unknown>`) supplies the values, filled in before send. A missing placeholder
381
+ blocks the send and shows a hint instead.
382
+
383
+ ```js
384
+ chat.skillContext = () => ({ title: document.title });
385
+ ```
386
+
387
+ ---
388
+
229
389
  ## MPA durability: surviving full page reloads
230
390
 
231
391
  In a multi-page app, a tool that navigates reloads the whole page and destroys the in-memory run
@@ -234,10 +394,10 @@ mechanisms.
234
394
 
235
395
  **1. Thread identity.** AG-UI's `thread_id` is the conversation key. It is generated once and
236
396
  persisted (so the element reattaches after a reload) by the
237
- [`ClientConversationStore`](src/conversation_store.ts).
397
+ [`ClientConversationStore`](src/core/conversation_store.ts).
238
398
 
239
399
  **2. Durable conversation.** A pluggable `ClientConversationStore` holds the message list. The
240
- default [`SessionStorageStore`](src/conversation_store.ts) keeps everything per-tab in
400
+ default [`SessionStorageStore`](src/core/conversation_store.ts) keeps everything per-tab in
241
401
  `sessionStorage`, so the chat survives full page reloads and clears on tab close. `loadMessages`
242
402
  is async-friendly, so a host can inject a server-backed store (e.g. one that rehydrates from a
243
403
  history endpoint) for cross-tab/device durability:
@@ -249,7 +409,7 @@ chat.conversationStore = new MyServerBackedStore();
249
409
  On mount the element rehydrates the transcript from the store, so the chat looks continuous.
250
410
 
251
411
  **3. Resumable loop (`x-navigates` + `navigationResult`).** A tool whose schema carries
252
- `x-navigates: true` (use `X_NAVIGATES_KEY`; read back by [`isNavigates`](src/is_navigates.ts))
412
+ `x-navigates: true` (use `X_NAVIGATES_KEY`; read back by [`isNavigates`](src/tools/is_navigates.ts))
253
413
  triggers a full reload. Before the handler navigates, the element writes a checkpoint
254
414
  (`{ toolCallId }`) to the store. On the next page mount it:
255
415
 
@@ -276,10 +436,16 @@ rather than by exploring:
276
436
  - `list_routes` — read-only; lists the routes.
277
437
  - `navigate_to_route(route_id, params?)` — resolves the id to a path and navigates.
278
438
 
439
+ A route `path` may contain `:param` segments — e.g. `/projects/:id/users/:userId/`.
440
+ `navigate_to_route` substitutes the path params (URL-encoded) and sends any leftover params as a
441
+ query string; a missing or empty required path param throws. The resolved shape is the exported
442
+ `RouteWithParams` type.
443
+
279
444
  ```js
280
445
  chat.routeMap = [
281
446
  { id: "users", path: "/users", title: "Users", description: "Manage user accounts" },
282
447
  { id: "billing", path: "/billing", title: "Billing" },
448
+ { id: "user-detail", path: "/projects/:id/users/:userId/", title: "User detail" },
283
449
  ];
284
450
  ```
285
451
 
@@ -333,6 +499,7 @@ re-export point. Internal modules import from leaf paths.
333
499
  | `defineAgUiChat()` | function | Idempotently register the element. |
334
500
  | `MessageRole` | type | Role of a rendered chat message. |
335
501
  | `SubmitDetail` | type | `detail` shape of the submit event. |
502
+ | `ToggleDetail` | type | `detail` shape of the `ag-ui-toggle` event (`{ collapsed }`). |
336
503
 
337
504
  ### AG-UI client & agent
338
505
 
@@ -360,10 +527,12 @@ re-export point. Internal modules import from leaf paths.
360
527
  | --- | --- | --- |
361
528
  | `createRouteTools(...)` | function | Build the built-in `route.*` tools. |
362
529
  | `Route` / `RouteMap` | type | Navigable-route shapes. |
530
+ | `RouteWithParams` | type | A route resolved with `:param` path segments + leftover query params. |
363
531
  | `createPageMapContext(...)` | function | Build the per-run `page_map` context entry. |
364
532
  | `PageMap` | type | The compact page-surface shape. |
365
533
  | `createStateHookTools(hook)` | function | Build `read_<name>` / `set_<name>` tools. |
366
534
  | `StateHook` | type | A state-binding declaration. |
535
+ | `Skill` | type | A launchable prompt (chip / `/`-command). |
367
536
 
368
537
  ### Durability
369
538
 
@@ -378,12 +547,14 @@ re-export point. Internal modules import from leaf paths.
378
547
  | Export | Kind | Summary |
379
548
  | --- | --- | --- |
380
549
  | `ToolCallCard` | class | A live tool-call card for the transcript. |
381
- | `ToolCallStatus` / `SettledStatus` | type | Card lifecycle states. |
382
- | `requestConfirmation(host, request)` | function | Render the confirmation modal. |
383
- | `ConfirmationRequest` | type | What the modal displays. |
384
- | `typeInto` / `highlightThenClick` / `scrollIntoCenterView` / `focusWithFlash` | function | Animation primitives. |
385
- | `fillField` / `clickElement` / `setControlValue` | function | DOM-driver primitives. |
386
- | `TypeOptions` / `HighlightClickOptions` / `FlashOptions` / `FillFieldOptions` / `TextLikeElement` | type | Primitive option shapes. |
550
+ | `ToolCallStatus` / `SettledStatus` / `ToolDisplayMode` | type | Card lifecycle states + display mode. |
551
+ | `requestConfirmation(host, request)` | function | Append the inline confirmation card to the transcript. |
552
+ | `ConfirmationRequest` | type | What the card displays. |
553
+ | `renderMarkdown(text)` | function | Render sanitized markdown/HTML (marked + DOMPurify). |
554
+ | `typeInto` / `highlightThenClick` / `pressThenClick` / `selectOption` / `toggleControl` / `scrollIntoCenterView` / `focusWithFlash` / `prefersReducedMotion` | function | Animation primitives. |
555
+ | `fillField` / `clickElement` / `pressButton` / `selectControl` / `setControlValue` / `toggleCheckbox` | function | DOM-driver primitives. |
556
+ | `setNativeValue` / `setNativeChecked` | function | Set a control via its native prototype setter (React-controlled inputs). |
557
+ | `TypeOptions` / `HighlightClickOptions` / `PressOptions` / `SelectOptions` / `ToggleOptions` / `FlashOptions` / `FillFieldOptions` / `TextLikeElement` | type | Primitive option shapes. |
387
558
 
388
559
  ### Constants
389
560
 
@@ -391,17 +562,22 @@ re-export point. Internal modules import from leaf paths.
391
562
  | --- | --- |
392
563
  | `ELEMENT_TAG` | The registered tag name (`ag-ui-chat`). |
393
564
  | `SUBMIT_EVENT` | The submit CustomEvent name. |
565
+ | `TOGGLE_EVENT` | The collapse-toggle CustomEvent name (`ag-ui-toggle`). |
394
566
  | `MESSAGE_ROLE` | Message role constants. |
395
567
  | `TOOL_CALL_STATUS` | Tool-call card status constants. |
568
+ | `TOOL_DISPLAY` | Tool-call display-mode constants (`minimal` / `compact` / `full`). |
569
+ | `X_CONFIRM_KEY` | JSON-Schema key carrying a confirmation prompt. |
570
+ | `X_SUMMARY_KEY` | JSON-Schema key carrying a short tool-card label. |
396
571
  | `MAX_TOOL_ROUNDS` | Upper bound on tool-call → re-run rounds per send. |
397
572
  | `VERSION` | The package version string. |
398
573
 
399
574
  ---
400
575
 
401
- ## Theming
576
+ ## Theming, density, and placement
402
577
 
403
- The chat shell is styled inside its Shadow DOM and exposes CSS custom properties on `:host`, so you
404
- theme it from outside without piercing the shadow boundary. A few of the knobs:
578
+ The chat shell is styled inside its Shadow DOM and exposes a large set of `--ag-ui-*` CSS custom
579
+ properties on `:host` (colors, status, surface, spacing, layout), so you theme it from outside
580
+ without piercing the shadow boundary. A few of the knobs:
405
581
 
406
582
  ```css
407
583
  ag-ui-chat {
@@ -410,8 +586,7 @@ ag-ui-chat {
410
586
  --ag-ui-fg: #1a1a2e;
411
587
  --ag-ui-radius: 12px;
412
588
 
413
- /* Layout — float (default) or embed in your own flow with --ag-ui-position: static */
414
- --ag-ui-position: fixed;
589
+ /* Layout */
415
590
  --ag-ui-width: 380px;
416
591
  --ag-ui-height: 560px;
417
592
  --ag-ui-inset: auto 24px 24px auto;
@@ -419,8 +594,21 @@ ag-ui-chat {
419
594
  }
420
595
  ```
421
596
 
422
- See [`src/styles.ts`](src/styles.ts) for the full list, and [`demo/themes/`](demo/themes/) for
423
- worked examples (default, dark, embedded, and a "claude" theme).
597
+ For the common cases there are three CSS-reactive **preset attributes** (no JS API), so you don't
598
+ have to hand-tune the variables:
599
+
600
+ - `theme` — `light` (default) / `dark` / `auto` (follow the OS) / `code`.
601
+ - `density` — `comfortable` (default) / `compact`.
602
+ - `placement` — `floating` (default) / `bottom-left` / `side` / `full` / `embedded`. `embedded`
603
+ drops the fixed positioning and z-index so the widget sits in normal document flow.
604
+
605
+ ```html
606
+ <ag-ui-chat endpoint="/agent/" theme="dark" density="compact" placement="side"></ag-ui-chat>
607
+ ```
608
+
609
+ See [`src/ui/styles.ts`](src/ui/styles.ts) for the full variable + preset list. The
610
+ [`demo/`](demo/) live playground (`node demo/mock-server.mjs`) flips theme, density, placement,
611
+ text-animation, and tool-display live from a single page.
424
612
 
425
613
  ---
426
614
 
@@ -449,7 +637,7 @@ Other workflow targets (all identical in name to the sibling Python packages):
449
637
  | `make test` | Vitest with a 100% line + branch + function + statement coverage gate. |
450
638
  | `make lint` | `biome check .` + `tsc --noEmit`. |
451
639
  | `make format` | `biome format --write .`. |
452
- | `make demo` | Build, then serve `demo/` against a mock AG-UI server. |
640
+ | `make demo` | Build, then serve the live playground (`demo/themes/index.html`) on port 5173 via `demo/mock-server.mjs`. |
453
641
 
454
642
  ---
455
643
 
@@ -457,7 +645,7 @@ Other workflow targets (all identical in name to the sibling Python packages):
457
645
 
458
646
  | Component | Floor | Tested |
459
647
  | --- | --- | --- |
460
- | Node (tooling/tests only) | 20 | 20, 22, 24 |
648
+ | Node (tooling/tests only) | 22 | 22, 24 |
461
649
  | Browsers (runtime target) | ES2022 / evergreen | Chrome / Firefox / Safari 17+ |
462
650
  | `@ag-ui/client` | latest 0.x | — |
463
651