@artooi/ag-ui-web-component 0.34.0 → 0.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +517 -1
  2. package/README.md +243 -13
  3. package/dist/ag-ui-web-component.bundle.js +614 -96
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +76 -3
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +37 -2
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/dom/animations.d.ts +14 -0
  10. package/dist/dom/animations.d.ts.map +1 -1
  11. package/dist/dom/highlight_overlay.d.ts +47 -0
  12. package/dist/dom/highlight_overlay.d.ts.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1886 -320
  16. package/dist/index.js.map +4 -4
  17. package/dist/tools/chat_surface_tools.d.ts +96 -0
  18. package/dist/tools/chat_surface_tools.d.ts.map +1 -0
  19. package/dist/tools/page_action_tools.d.ts +2 -0
  20. package/dist/tools/page_action_tools.d.ts.map +1 -1
  21. package/dist/ui/clamp_launcher.d.ts +10 -5
  22. package/dist/ui/clamp_launcher.d.ts.map +1 -1
  23. package/dist/ui/clamp_panel.d.ts +2 -2
  24. package/dist/ui/clamp_panel.d.ts.map +1 -1
  25. package/dist/ui/launcher_drag.d.ts +2 -2
  26. package/dist/ui/launcher_drag.d.ts.map +1 -1
  27. package/dist/ui/launcher_placement.d.ts +14 -1
  28. package/dist/ui/launcher_placement.d.ts.map +1 -1
  29. package/dist/ui/panel_drag.d.ts.map +1 -1
  30. package/dist/ui/place_widget.d.ts +9 -1
  31. package/dist/ui/place_widget.d.ts.map +1 -1
  32. package/dist/ui/run_notice.d.ts +14 -3
  33. package/dist/ui/run_notice.d.ts.map +1 -1
  34. package/dist/ui/styles.d.ts +1 -1
  35. package/dist/ui/styles.d.ts.map +1 -1
  36. package/dist/ui/thread_drawer.d.ts +21 -0
  37. package/dist/ui/thread_drawer.d.ts.map +1 -1
  38. package/dist/ui/ui_strings.d.ts +16 -0
  39. package/dist/ui/ui_strings.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/constants.ts +82 -3
  42. package/src/core/ag_ui_chat.ts +965 -55
  43. package/src/dom/animations.ts +30 -0
  44. package/src/dom/highlight_overlay.ts +256 -0
  45. package/src/index.ts +12 -0
  46. package/src/tools/chat_surface_tools.ts +207 -0
  47. package/src/tools/page_action_tools.ts +2 -0
  48. package/src/ui/clamp_launcher.ts +25 -7
  49. package/src/ui/clamp_panel.ts +10 -4
  50. package/src/ui/launcher_drag.ts +11 -2
  51. package/src/ui/launcher_placement.ts +34 -8
  52. package/src/ui/panel_drag.ts +4 -0
  53. package/src/ui/place_widget.ts +11 -3
  54. package/src/ui/run_notice.ts +32 -3
  55. package/src/ui/styles.ts +563 -45
  56. package/src/ui/thread_drawer.ts +138 -8
  57. package/src/ui/ui_strings.ts +24 -0
  58. package/src/version.ts +1 -1
package/README.md CHANGED
@@ -149,6 +149,17 @@ Drop the element into your page and register the tools the agent may call:
149
149
  <ag-ui-chat endpoint="/agent/" title-text="Assistant"></ag-ui-chat>
150
150
  ```
151
151
 
152
+ !!! note "If nothing appears, check `defineAgUiChat()` ran"
153
+ Forgetting it looks like nothing. `<ag-ui-chat>` is then an *unknown
154
+ element*, which every browser renders as an inline 0x0 box, in flow, with no
155
+ console warning and no error — so the page looks finished and the chat is
156
+ simply absent. A bare `import "@artooi/ag-ui-web-component"` does not
157
+ register anything, deliberately: registration is an explicit call so the
158
+ package stays tree-shakeable.
159
+
160
+ `customElements.get("ag-ui-chat")` in a console answers it in one line —
161
+ `undefined` means the call did not run.
162
+
152
163
  That's the whole integration: an `endpoint` attribute pointing at your AG-UI server, optional
153
164
  `headers`, and the tools you want the agent to be able to invoke in the browser. If your API is on
154
165
  another origin, add `credentials="include"` too; see
@@ -187,13 +198,19 @@ another origin, add `credentials="include"` too; see
187
198
  | `data-quote-selection` | — | **On by default.** `="false"` stops the transcript offering to quote a selection. `quote()` keeps working either way. See [Quoting a selection](#quoting-a-selection). |
188
199
  | `data-message-actions` | — | **All on by default.** A comma list of the actions a finished answer keeps: `copy` / `retry` / `feedback` (e.g. `"copy,retry"`). `="false"` removes the row entirely. See [Message actions](#message-actions-copy-retry-feedback). |
189
200
  | `data-max-tool-rounds` | — | Upper bound on frontend tool-call → re-run rounds within one send (default 10; a value below 1 is ignored). Raise it for a page-driving agent whose turn takes many small steps. See [The run loop](#the-run-loop-and-the-ag-ui-client). |
190
- | `data-page-actions` | — | Opt-in built-in page-action tools: a comma list of `scroll` / `drag` (e.g. `"scroll,drag"`). See [Page-action tools](#page-action-tools). |
201
+ | `data-page-actions` | — | Opt-in built-in page-action tools: a comma list of `scroll` / `drag` / `chat` (e.g. `"scroll,drag"`). See [Page-action tools](#page-action-tools). |
191
202
  | `data-side` | — | CSS-only, for `placement="sidebar"`: which edge it docks to — `right` (default) / `left`. |
192
203
  | `data-answer-well` | — | CSS-only boolean: box each assistant turn (its text, tool cards, and thinking) in one bordered "well". Off by default. See [The answer well](#the-answer-well). |
193
- | `collapsed` | `collapsed` | Reflected boolean; collapses the widget to its [launcher](#collapsing-to-the-launcher) (a rail under `placement="sidebar"`, the header bar under `embedded` / `page`). Persisted per-tab in `sessionStorage`. |
204
+ | `collapsed` | `collapsed` | Reflected boolean; collapses the widget to its [launcher](#collapsing-to-the-launcher) (a rail under `placement="sidebar"`, the header bar under `embedded`). Persisted per tab. `placement="page"` has no collapsed state and ignores it. |
205
+ | `data-dragging` | — | **Written by the element, not by you.** Stamped on whichever handle a gesture is currently using, so the styles can react and so the element knows not to re-place the widget under a drag in progress. Cleared on `pointerup` and on `pointercancel`. |
206
+ | `data-expand-corner` | — | **Written by the element, not by you.** It stamps the corner a dragged or agent-moved panel opens from, so the collapse animation starts where the panel actually is. Listed because the element reads its own stamp back; setting it yourself is overwritten on the next move. |
207
+ | `data-small-viewport` | — | CSS-only: `off` keeps the desktop layout at every width, opting out of the [small-viewport override](#small-viewports). Everything that override sets is a token you can re-state; its trigger is a media query, which is the one thing you cannot. |
208
+ | `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. |
209
+ | `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. |
210
+ | `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. |
194
211
  | `theme` | — | CSS-only: `light` (default) / `dark` / `auto` / `code`. |
195
212
  | `density` | — | CSS-only: `comfortable` (default) / `compact`. |
196
- | `placement` | — | CSS-only: `floating` (default) / `bottom-left` / `side` / `sidebar` / `full` / `page` / `embedded`. |
213
+ | `placement` | — | CSS-only: `floating` (default) / `sidebar` / `page` / `embedded`. |
197
214
 
198
215
  Each header control also takes its own icon slot — `icon-history`, `icon-checkpoints`,
199
216
  `icon-new`, `icon-collapse` — with the built-in glyph as the fallback, so a host can project a
@@ -268,7 +285,8 @@ the `copyCode` / `copied` / `copyFailed` strings.
268
285
 
269
286
  **Methods**: `registerTool`, `registerPageState`, `registerActivityRenderer`, `setSkills`,
270
287
  `sendMessage`, `attachFile`, `appendMessage`, `retryLastTurn`, `quote`, `offerQuoteInPage`,
271
- `enableCharts`, `newChat`, `setCollapsed`, `toggleCollapsed`, `toggleTheme`, `openThreads`,
288
+ `enableCharts`, `newChat`, `setCollapsed`, `toggleCollapsed`, `describeSurface`, `moveTo`,
289
+ `toggleTheme`, `openThreads`, `closeThreads`,
272
290
  `openCheckpoints`, `closeCheckpoints`, `toggleCheckpoints`, `reload`, and the deprecated
273
291
  `registerStateHook` (renamed to `registerPageState`).
274
292
 
@@ -812,6 +830,44 @@ operations a tool handler typically wants:
812
830
  flash defaults to `flashMs: 0` here: the field is about to be typed into, which is its own
813
831
  highlight. Pass `flashMs` (and optionally `color`) to ring it first.
814
832
  - `clickElement(el, options)` / `pressButton(el, options)` — scroll to, highlight/press, and click.
833
+ - `showHighlightOverlay(el, options)` — ring an element from an **overlay drawn outside it**, and
834
+ optionally dim everything else (`scrim`) or flow a gradient round it (`gradient`). Returns a
835
+ function that removes it. `flash` and `focusWithFlash` take `scrim` and `gradient` too and route
836
+ through this when either is asked for.
837
+
838
+ **Why a second mechanism.** The plain ring is an `outline` on the element, which is deliberate:
839
+ a `box-shadow` paints outside the border box, so an `overflow: hidden` ancestor sharing the
840
+ target's box clips the whole ring away while the helper still reports success. But an outline
841
+ takes a *colour* — there is no `outline-image` — so a gradient cannot be one, and anything else
842
+ that can be is a property of the target and lands back inside whatever is clipping it. Dimming
843
+ everything else needs a surface larger than the target, which is the same problem from the other
844
+ side. So they are one overlay rather than two features.
845
+
846
+ **It is inert.** The overlay never takes a pointer event, at the cut-out or anywhere else — a dim
847
+ that swallows clicks is a modal the user did not open, and `highlightThenClick` has to reach the
848
+ control it just finished pointing at. It follows the target on scroll and resize, and under
849
+ reduced motion the gradient is drawn but does not travel.
850
+
851
+ **Themed from the element you point at, not from the widget.** The overlay is appended to the
852
+ document body so it can escape the clipping it exists to avoid, which means a `var()` in its own
853
+ style would resolve against the body — so every token is read from the *target's* computed style
854
+ instead, the same place the flat ring reads `--ag-ui-accent`. Set them wherever they inherit to
855
+ the elements the agent touches, usually `:root`:
856
+
857
+ ```css
858
+ :root {
859
+ --ag-ui-highlight-scrim: rgba(15, 15, 25, 0.45);
860
+ --ag-ui-highlight-gradient: linear-gradient(115deg, transparent 20%, #4f46e5 50%, transparent 80%);
861
+ --ag-ui-highlight-ring-width: 3; /* unitless; px */
862
+ --ag-ui-highlight-flow-ms: 2400; /* one pass of the gradient */
863
+ --ag-ui-highlight-z-index: 2147483001;
864
+ }
865
+ ```
866
+
867
+ `ringWidth` and `flowMs` options override the tokens per call; `color`, `padding` and `radius`
868
+ have no token because they are per-target rather than per-theme. The overlay's own styles are
869
+ inline and it lives in the light DOM, so neither a stylesheet rule nor `::part` can reach it —
870
+ these tokens and options are the whole surface, which is why they cover every value it draws.
815
871
  - `selectControl(el, value)` / `toggleCheckbox(el, checked)` — animate a `<select>` / checkbox.
816
872
  - `setControlValue(el, value)` — set a `<select>` or checkbox without animation, dispatching
817
873
  `input`/`change`.
@@ -861,6 +917,22 @@ want — so you control the agent's interaction surface:
861
917
  - **`drag_and_drop`** — drag the `from` element onto the `to` element (selectors / page-map ids),
862
918
  firing the standard HTML5 drag sequence (`dragstart` → `dragenter`/`dragover`/`drop` → `dragend`)
863
919
  so the page's own drop handler reacts. Useful for reordering sortable lists.
920
+ - **`chat`** — four tools that let the agent move the panel it is speaking from:
921
+ `read_chat_surface`, `move_chat`, `minimise_chat`, `restore_chat`.
922
+
923
+ This is the one nobody else can offer. Every other assistant's chat is a surface of its own, so
924
+ there is nothing for it to be in the way *of*; ours is mounted in the page the user is working
925
+ in, which makes "let me move this aside so you can see the table" something the agent can act on
926
+ rather than apologise for.
927
+
928
+ **They report what happened, not what was asked.** A panel that fills the screen has nowhere to
929
+ move to, and a placement that places itself owns its position — so `move_chat` answers
930
+ `moved: false` with the reason and what would work instead, rather than reporting success on a
931
+ panel that did not budge. `read_chat_surface` is there so the agent can ask before it acts
932
+ instead of learning through a failure; it is read-only.
933
+
934
+ None of them is stamped `x-destructive`. Moving a window destroys nothing, and a confirmation
935
+ card in front of it would be worse than the move.
864
936
 
865
937
  **Your drag surface must listen to drag events, and many "modern" ones do not.** `drag_and_drop`
866
938
  dispatches the native HTML5 sequence with one shared `DataTransfer`. A surface built on a
@@ -907,6 +979,13 @@ matching JS API:
907
979
  server-backed store it stays on the server. Deleting one is the drawer row's own action. A chat
908
980
  nothing was ever sent in is the exception — it was never listed, so it is dropped rather than
909
981
  left behind.
982
+ - `describeSurface()` — where the panel is and what can be done to it: placement, collapsed,
983
+ whether it can be moved, whether it fills the screen, its box and the viewport. `movable` folds
984
+ the two reasons a move can fail into the one answer a caller needs.
985
+ - `moveTo(corner)` — send the panel to `top-left` / `top-right` / `bottom-left` / `bottom-right`,
986
+ returning whether it went. It takes the axes the same way a user drag does, so the launcher
987
+ travels with it and switching placement hands them back. Returns `false` rather than pretending
988
+ when the placement owns its position or the panel fills the screen.
910
989
  - `setCollapsed(collapsed)` / `toggleCollapsed()` — collapse or expand the widget. The state is
911
990
  reflected as the boolean `collapsed` attribute/property and persisted per-tab in
912
991
  `sessionStorage`, so it survives a reload.
@@ -921,6 +1000,25 @@ chat.toggleCollapsed();
921
1000
  chat.addEventListener("ag-ui-toggle", (e) => console.log(e.detail.collapsed));
922
1001
  ```
923
1002
 
1003
+ ### The composer's own keys
1004
+
1005
+ **Enter during a run queues.** A second run cannot start while one is in flight —
1006
+ it would orphan the first — so that key used to do nothing at all, silently. What
1007
+ is waiting shows above the composer as chips, each of which takes its message
1008
+ back when pressed, and the next one is sent when the run settles. Stopping the
1009
+ run discards them: sending into a conversation someone has just stopped is the
1010
+ opposite of what stopping meant. It is not thrown away, though — a queued
1011
+ message has already left the composer, so it goes to the front of the recall
1012
+ history below rather than nowhere.
1013
+
1014
+ The composer also walks back through what you have already sent, on **Up** and
1015
+ **Down** — the shape every shell and every coding agent uses. Only from an empty
1016
+ composer and only with the skills palette closed: an arrow inside text is how you
1017
+ move the caret, and taking it unconditionally would break editing to add a
1018
+ shortcut. Arrowing forward past the newest turn empties the box again, so the way
1019
+ out is the key that got you in. The history is this conversation's: starting a
1020
+ new chat, switching threads or changing `user-key` clears it with the transcript.
1021
+
924
1022
  ### Collapsing to the launcher
925
1023
 
926
1024
  A collapsed widget shrinks to a round **floating launcher** in the corner it already occupies: the
@@ -1364,9 +1462,9 @@ server's text.
1364
1462
  The panel carries a grip on **every edge and every corner**, so it can be
1365
1463
  dragged from whichever side you are already near.
1366
1464
 
1367
- - `placement="full"` / `placement="page"` get **no grips** — a full-bleed layout
1465
+ - `placement="page"` gets **no grips** — a full-bleed layout
1368
1466
  is `100vw`/`100vh` by definition, so there is nothing to drag.
1369
- - `placement="sidebar"` / `placement="side"` keep only the two vertical edges;
1467
+ - `placement="sidebar"` keeps only the two vertical edges;
1370
1468
  the placement owns the height, so a horizontal edge or a corner would
1371
1469
  advertise a drag that does nothing.
1372
1470
  - Everything else gets all eight.
@@ -2313,6 +2411,12 @@ re-export point. Internal modules import from leaf paths.
2313
2411
  | `isNavigates(parameters)` | function | Read the `x-navigates` flag. |
2314
2412
  | `createPageActionTools(enabled, resolveTarget)` | function | Build the opt-in `scroll_to` / `drag_and_drop` tools. |
2315
2413
  | `PAGE_ACTIONS` | const | The page-action opt-in tokens (`scroll` / `drag`). |
2414
+ | `createChatSurfaceTools(surface)` | function | Build the opt-in `read_chat_surface` / `move_chat` / `minimise_chat` / `restore_chat` tools, which let the agent move the panel it is speaking from. |
2415
+ | `ChatSurface` | type | The narrow port those tools drive — `describeSurface` / `moveTo` / `setCollapsed`. The element satisfies it. |
2416
+ | `ChatSurfaceReport` | type | What `describeSurface()` answers: placement, collapsed, collapsible, movable, draggable, fullBleed, and the panel's box against the viewport it sits in. |
2417
+ | `ChatCorner` | type | `"top-left"` / `"top-right"` / `"bottom-left"` / `"bottom-right"` — the argument to `moveTo`. |
2418
+ | `CHAT_CORNERS` | const | Those four, as a list. |
2419
+ | `isChatCorner(value)` | function | Whether a string names one of them. |
2316
2420
  | `ResolvePageTarget` | type | `(target) => HTMLElement \| null` — the page-target resolver. |
2317
2421
  | `X_DESTRUCTIVE_KEY` / `X_NAVIGATES_KEY` | const | The JSON-Schema extension keys. |
2318
2422
  | `parseToolCatalog(data)` | function | Parse a fetched `data-tools-url` catalog into a `Record<string, ToolCatalogEntry>` — whole entries, not bare summaries, so a caller can reach `description` too. Malformed input yields an empty map rather than throwing. |
@@ -2413,6 +2517,8 @@ re-export point. Internal modules import from leaf paths.
2413
2517
  | `asQuote(text)` | function | Shape text as a markdown blockquote with a blank line after it. |
2414
2518
  | `MAX_QUOTE_CHARS` | const | The cap a quotation is truncated to (500). |
2415
2519
  | `typeInto` / `highlightThenClick` / `pressThenClick` / `selectOption` / `toggleControl` / `scrollIntoCenterView` / `flash` / `focusWithFlash` / `prefersReducedMotion` | function | Animation primitives. |
2520
+ | `showHighlightOverlay` | function | Ring a host-page element from an overlay drawn outside it, optionally dimming everything else or flowing a gradient round it. Returns a function that removes it. |
2521
+ | `HighlightOverlayOptions` | type | Options for `showHighlightOverlay`. |
2416
2522
  | `fillField` / `clickElement` / `pressButton` / `selectControl` / `setControlValue` / `toggleCheckbox` | function | DOM-driver primitives. |
2417
2523
  | `setNativeValue` / `setNativeChecked` | function | Set a control via its native prototype setter (React-controlled inputs). |
2418
2524
  | `TypeOptions` / `HighlightClickOptions` / `PressOptions` / `SelectOptions` / `ToggleOptions` / `FlashOptions` / `ScrollOptions` / `FillFieldOptions` / `TextLikeElement` | type | Primitive option shapes. |
@@ -2478,6 +2584,8 @@ ag-ui-chat {
2478
2584
  --ag-ui-bg: #ffffff;
2479
2585
  --ag-ui-fg: #1a1a2e;
2480
2586
  --ag-ui-radius: 12px;
2587
+ --ag-ui-header-btn-size: 30px; /* the header's controls; 44px on touch */
2588
+ --ag-ui-header-gap: 4px;
2481
2589
 
2482
2590
  /* What is drawn on top of the accent and danger fills. Change these with
2483
2591
  the fills: a pale accent leaves white-on-pale everywhere they are used. */
@@ -2492,6 +2600,117 @@ ag-ui-chat {
2492
2600
  }
2493
2601
  ```
2494
2602
 
2603
+ ### Small viewports
2604
+
2605
+ At **600px wide and below** every placement but `embedded` becomes one full-bleed
2606
+ shape: edge to edge, no radius, no shadow, no resize grips. A phone is not an
2607
+ eighth placement — it is an override that collapses the others onto one of them.
2608
+ The host picked a placement for the desktop it was designing, and a 380x560
2609
+ panel with a 24px margin is not a smaller version of that decision, it is most
2610
+ of the screen with a frame drawn round it.
2611
+
2612
+ `embedded` is left alone deliberately: it sits in a box you sized and placed, and
2613
+ only you know whether that column should become the whole screen.
2614
+
2615
+ The corner placements still rest at their launcher, so a full-bleed panel is
2616
+ something the user opens rather than something they are given.
2617
+
2618
+ ### The conversation list on a full page
2619
+
2620
+ On `placement="page"`, once the panel is at least **900px** wide, the chat-history
2621
+ list **docks beside the transcript** instead of covering it — no backdrop, no
2622
+ focus trap, and `role="region"` rather than a modal dialog. Covering the
2623
+ conversation to show the list of conversations hides the thing you are trying to
2624
+ get back to, and a dedicated route is the one surface with width to spare.
2625
+
2626
+ Narrower than that, or under any other placement, it stays the slide-over it was:
2627
+ a few hundred pixels of panel with a list docked into it leaves a column of
2628
+ transcript narrower than the messages in it. Width alone is not the test — an app
2629
+ shell can hand `embedded` a page-sized box, and that box is still a column of
2630
+ somebody's layout.
2631
+
2632
+ `--ag-ui-threads-rail-width` sets the docked width (default 280px). While it is
2633
+ docked the host carries `data-threads-docked`, so your own CSS can react.
2634
+
2635
+ The list also grows a filter once there are eight or more conversations in it
2636
+ — above that a search box is worth having, below it it is a control asking to be
2637
+ used on a list you can already read in one glance. It matches the title **and**
2638
+ the preview, because the title is often the model's one-line summary and the
2639
+ phrase you remember is as likely to be inside the conversation as on it, and it
2640
+ filters what the drawer already holds rather than going back to the server for a
2641
+ list that is already in memory.
2642
+
2643
+ **To keep your desktop layout at every width**, set `data-small-viewport="off"`.
2644
+ That exists because the *trigger* is the one part of this you cannot reach: every
2645
+ value the override sets is a `--ag-ui-*` token you can re-state, but a media
2646
+ query cannot read a custom property, so the breakpoint itself is a literal.
2647
+
2648
+ The breakpoint is a width rather than a pointer test, and that is on purpose: a
2649
+ touch laptop is coarse-pointered and wide, a narrow desktop window is
2650
+ fine-pointered and small. Width decides the layout; the pointer decides which
2651
+ controls make sense.
2652
+
2653
+ ### Reserving the space your own chrome occupies
2654
+
2655
+ A fixed placement covers the viewport it is given, and it does not know about
2656
+ your sticky header. Tell it which edges are already spent and every placement
2657
+ does its own arithmetic:
2658
+
2659
+ ```css
2660
+ ag-ui-chat {
2661
+ --ag-ui-viewport-inset-top: 64px; /* your nav bar */
2662
+ }
2663
+ ```
2664
+
2665
+ `page` and `full` inset by all four edges; `sidebar` and `side` by three, leaving
2666
+ the docked edge free; `floating` adds them to its own margins.
2667
+ **The heights follow on their own** — that is the point of these rather than
2668
+ restating `--ag-ui-inset` per placement, which leaves you to keep
2669
+ `--ag-ui-height` and `--ag-ui-max-height` in step by hand and overflows the panel
2670
+ off the bottom of the screen the one time you forget.
2671
+
2672
+ Four longhands rather than one shorthand because a custom property is a token
2673
+ stream and CSS cannot index one; the height arithmetic needs the vertical pair on
2674
+ its own. Any CSS length works — `px`, `rem`, `env(safe-area-inset-*)`, or a
2675
+ `calc()` combining them; the widget reads the resolved value rather than the text
2676
+ you wrote, so the number it clamps against is the one the stylesheet uses:
2677
+
2678
+ ```css
2679
+ ag-ui-chat {
2680
+ --ag-ui-viewport-inset-top: env(safe-area-inset-top);
2681
+ --ag-ui-viewport-inset-bottom: env(safe-area-inset-bottom);
2682
+ }
2683
+ ```
2684
+
2685
+ If your chrome changes height — a bar that wraps at narrow widths — measure it
2686
+ and publish the value, since no CSS length tracks it:
2687
+
2688
+ ```js
2689
+ new ResizeObserver(() => {
2690
+ document.documentElement.style.setProperty("--bar-h", `${bar.offsetHeight}px`);
2691
+ }).observe(bar);
2692
+ ```
2693
+
2694
+ ```css
2695
+ ag-ui-chat { --ag-ui-viewport-inset-top: var(--bar-h, 0px); }
2696
+ ```
2697
+
2698
+ `--ag-ui-edge-gutter` (default `24px`) is the gap a resting `floating` panel
2699
+ keeps between itself and that box. The size cap subtracts the same one, so a
2700
+ panel grown to its limit reaches the far edge of the usable box and no further —
2701
+ set it to `0` for a panel flush against the corner.
2702
+
2703
+ `--ag-ui-keyboard-inset` overrides the lift an on-screen keyboard earns. The
2704
+ widget measures the hidden band and publishes it as
2705
+ `--ag-ui-visual-viewport-inset-bottom`; state this one instead to outrank that
2706
+ measurement, or set it to `0px` to opt out of the lift entirely.
2707
+
2708
+ `--ag-ui-viewport-height` and `--ag-ui-viewport-width` state the usable box
2709
+ outright, for the case where no viewport-percentage length describes it. An
2710
+ on-screen keyboard is the one that matters: it changes neither `vh` nor `dvh` nor
2711
+ `svh` on any current mobile browser, so a full-bleed panel has to be told the
2712
+ visual viewport's height rather than deriving it.
2713
+
2495
2714
  Marks are variables too, so one vocabulary covers a re-theme rather than
2496
2715
  leaving half the transcript in the built-in set: `--ag-ui-tool-icon-done` /
2497
2716
  `-error` / `-declined` for tool status, and `--ag-ui-disclosure-collapsed` /
@@ -2548,9 +2767,17 @@ have to hand-tune the variables:
2548
2767
 
2549
2768
  - `theme` — `light` (default) / `dark` / `auto` (follow the OS) / `code`.
2550
2769
  - `density` — `comfortable` (default) / `compact`.
2551
- - `placement` — `floating` (default) / `bottom-left` / `side` / `sidebar` / `full` / `page` /
2552
- `embedded`. `embedded` drops the fixed positioning and z-index so the widget sits in normal
2553
- document flow; `page` is a full-screen [centred reading column](#page-placement).
2770
+ - `placement` — `floating` (default) / `sidebar` / `page` / `embedded`. `embedded` drops the
2771
+ fixed positioning and z-index so the widget sits in normal document flow; `page` is a
2772
+ full-screen [centred reading column](#page-placement) for a route of its own.
2773
+
2774
+ Four, because those are the four shapes that differ structurally: a corner panel, a docked
2775
+ rail, a surface that owns the screen, and a thing in your layout. Three older values --
2776
+ `bottom-left`, `side` and `full` -- still parse and still work, and are no longer documented:
2777
+ each is a variant of one of the four rather than a shape of its own. `full` is `page` with
2778
+ `--ag-ui-content-max-width: none`, `bottom-left` is `floating` with a different
2779
+ `--ag-ui-inset`, and `side` is `sidebar` that collapses to the floating launcher instead of
2780
+ an edge rail. Nothing warns and nothing breaks; there is simply less to choose between.
2554
2781
 
2555
2782
  **`embedded` fills the box your page gives it, so give it one.** It is the placement app-shell
2556
2783
  layouts reach for, and a grid or flex item defaults to `min-height: auto` — which lets a growing
@@ -2562,7 +2789,7 @@ fix belongs to the containing element, not to the widget:
2562
2789
  ```
2563
2790
 
2564
2791
  ```html
2565
- <ag-ui-chat endpoint="/agent/" theme="dark" density="compact" placement="side"></ag-ui-chat>
2792
+ <ag-ui-chat endpoint="/agent/" theme="dark" density="compact" placement="sidebar"></ag-ui-chat>
2566
2793
  ```
2567
2794
 
2568
2795
  See [`src/ui/styles.ts`](src/ui/styles.ts) for the full variable + preset list. The
@@ -2570,6 +2797,8 @@ See [`src/ui/styles.ts`](src/ui/styles.ts) for the full variable + preset list.
2570
2797
  text-animation, tool-display, and the answer well live from a single page, and demos the
2571
2798
  streamed thoughts region, the mic, and the header theme toggle.
2572
2799
 
2800
+ It binds every interface and prints the addresses this machine can be reached on, so you can open the playground on a phone — which is the only way to see the small-viewport layout with a real on-screen keyboard rather than a resized desktop window. `HOST=127.0.0.1` keeps it to this machine.
2801
+
2573
2802
  ### Parts and slots
2574
2803
 
2575
2804
  For styling beyond the `--ag-ui-*` variables, every structural element exposes a `part` so you can
@@ -2592,12 +2821,13 @@ component sets, so a new one cannot ship undocumented.
2592
2821
  | --- | --- |
2593
2822
  | Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `pending`, `stopped`, `jump-latest`, and one per resize grip: `resize-handle` plus `resize-handle-top`, `resize-handle-bottom`, `resize-handle-left`, `resize-handle-right`, `resize-handle-top-left`, `resize-handle-top-right`, `resize-handle-bottom-left`, `resize-handle-bottom-right` |
2594
2823
  | Header buttons | `header-button` on each, plus `history-button`, `checkpoints-button`, `new-button`, `collapse-button`, `theme-toggle` |
2595
- | Collapsed widget | `launcher`, `launcher-icon`, `launcher-badge` |
2824
+ | Collapsed widget | `launcher`, `launcher-icon`, `launcher-badge`, `rail-label` |
2596
2825
  | Answers | `answer` (the per-turn group), `message` (plus `message-user`, `message-assistant`), `code-copy` |
2597
2826
  | Reasoning | `thoughts`, `thoughts-toggle`, `thoughts-body`, `thoughts-label` |
2598
2827
  | Follow-up suggestions | `suggestions`, `suggestion-chip` |
2599
2828
  | 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`) |
2600
- | 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-icon`, `run-notice-text` |
2829
+ | Queued messages | `queued`, `queued-chip` |
2830
+ | 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-icon`, `run-notice-text`, `run-notice-undo` |
2601
2831
  | 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` |
2602
2832
  | Delegated sub-agents | `subagent`, `subagent-row`, `subagent-icon`, `subagent-status`, `subagent-steps`, `subagent-step`, `subagent-step-icon`, `subagent-step-name` |
2603
2833
  | Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`, `confirm-always`) |
@@ -2606,7 +2836,7 @@ component sets, so a new one cannot ship undocumented.
2606
2836
  | Composer | `composer`, `composer-surface`, `composer-tools`, `input`, `send`, `attach-button`, `voice-button` |
2607
2837
  | Attachments | `attachment-tray`, `attachment-chips` (the read-only chips on sent bubbles), and the shared chip parts `attachment-chip`, `attachment-chip-icon`, `attachment-chip-name`, `attachment-chip-size`, `attachment-chip-bar`, `attachment-chip-bar-fill`, `attachment-chip-retry`, `attachment-chip-remove` |
2608
2838
  | Skills | `skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`, `skill-item-desc`, `skill-item-token`, `skill-hint` (the missing-placeholder hint) |
2609
- | Thread drawer | `drawer`, `drawer-backdrop`, `drawer-panel`, `drawer-header`, `drawer-title`, `drawer-new`, `drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`, `drawer-row-title`, `drawer-row-time`, `drawer-row-preview`, `drawer-row-actions`, `drawer-row-rename`, `drawer-row-delete`, `drawer-rename-input`, `drawer-confirm`, `drawer-confirm-label`, `drawer-confirm-yes`, `drawer-confirm-no` |
2839
+ | Thread drawer | `drawer`, `drawer-backdrop`, `drawer-panel`, `drawer-header`, `drawer-title`, `drawer-new`, `drawer-close`, `drawer-filter`, `drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`, `drawer-row-title`, `drawer-row-time`, `drawer-row-preview`, `drawer-row-actions`, `drawer-row-rename`, `drawer-row-delete`, `drawer-rename-input`, `drawer-confirm`, `drawer-confirm-label`, `drawer-confirm-yes`, `drawer-confirm-no` |
2610
2840
  | Charts | `chart-block`, `chart-title`, `chart-legend` |
2611
2841
  | Checkpoints panel | `checkpoints`, `checkpoints-header`, `checkpoints-title`, `checkpoints-list`, `checkpoints-empty`, `checkpoint-row`, `checkpoint-label`, `checkpoint-time`, `checkpoint-id`, `checkpoint-branch`, `checkpoint-action` (plus `checkpoint-resume`, `checkpoint-fork`) |
2612
2842