@artooi/ag-ui-web-component 0.39.0 → 0.40.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 (46) hide show
  1. package/CHANGELOG.md +155 -1
  2. package/README.md +70 -12
  3. package/dist/ag-ui-web-component.bundle.js +168 -45
  4. package/dist/ag-ui-web-component.bundle.js.map +3 -3
  5. package/dist/core/ag_ui_chat.d.ts +7 -3
  6. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  7. package/dist/core/agui_client.d.ts +12 -0
  8. package/dist/core/agui_client.d.ts.map +1 -1
  9. package/dist/core/client_seed.d.ts +14 -2
  10. package/dist/core/client_seed.d.ts.map +1 -1
  11. package/dist/index.js +636 -231
  12. package/dist/index.js.map +2 -2
  13. package/dist/tools/tool_catalog.d.ts.map +1 -1
  14. package/dist/ui/composer/composer_attachments.d.ts +6 -1
  15. package/dist/ui/composer/composer_attachments.d.ts.map +1 -1
  16. package/dist/ui/composer/composer_voice.d.ts +3 -0
  17. package/dist/ui/composer/composer_voice.d.ts.map +1 -1
  18. package/dist/ui/excerpts/transcript_quote_offer.d.ts +7 -2
  19. package/dist/ui/excerpts/transcript_quote_offer.d.ts.map +1 -1
  20. package/dist/ui/history/conversation_history.d.ts +29 -3
  21. package/dist/ui/history/conversation_history.d.ts.map +1 -1
  22. package/dist/ui/placement/launcher_drag.d.ts +6 -0
  23. package/dist/ui/placement/launcher_drag.d.ts.map +1 -1
  24. package/dist/ui/placement/panel_placement.d.ts +1 -1
  25. package/dist/ui/placement/panel_placement.d.ts.map +1 -1
  26. package/dist/ui/styles.d.ts +1 -1
  27. package/dist/ui/styles.d.ts.map +1 -1
  28. package/dist/ui/transcript/transcript.d.ts +3 -2
  29. package/dist/ui/transcript/transcript.d.ts.map +1 -1
  30. package/dist/ui/ui_strings.d.ts +2 -0
  31. package/dist/ui/ui_strings.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/core/ag_ui_chat.ts +217 -64
  34. package/src/core/agui_client.ts +26 -14
  35. package/src/core/client_seed.ts +14 -2
  36. package/src/tools/tool_catalog.ts +63 -12
  37. package/src/ui/composer/composer_attachments.ts +64 -41
  38. package/src/ui/composer/composer_voice.ts +5 -0
  39. package/src/ui/excerpts/transcript_quote_offer.ts +26 -13
  40. package/src/ui/history/conversation_history.ts +125 -17
  41. package/src/ui/placement/launcher_drag.ts +104 -89
  42. package/src/ui/placement/panel_placement.ts +27 -3
  43. package/src/ui/styles.ts +137 -14
  44. package/src/ui/transcript/transcript.ts +10 -5
  45. package/src/ui/ui_strings.ts +3 -0
  46. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,159 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.40.0] — 2026-09-17
11
+
12
+ ### Changed
13
+
14
+ - **An `askUserRenderer` that throws now hands the question to the built-in
15
+ question card.** A throw, or a rejected promise, used to escape the `ask_user`
16
+ tool: its card settled as an error quoting the host's message, that message
17
+ went on to the agent as the tool result although it was never written for the
18
+ model, and the pending wait was never closed, so the next Stop aborted the
19
+ signal of a question that had already ended.
20
+
21
+ The renderer decides how the question looks, not whether it is asked -- the
22
+ same rule `approvalRenderer` follows -- so it now falls back to the built-in
23
+ card, with a `console.warn` naming the tool call, and the run carries on as if
24
+ no renderer had been set. The exception is a rejection after the signal has
25
+ fired, which is how a renderer is expected to honour Stop: that wait resolves
26
+ with an empty answer, as the built-in card does on the same signal, with no
27
+ card drawn and no warning.
28
+
29
+ - **On a small viewport the composer stays at the foot of the panel, with the
30
+ greeting over the space above it.** Until a conversation had something in it,
31
+ `page` -- and an `embedded` panel that opts into a greeting -- centred the
32
+ composer between the greeting and the foot at every width. Centring is a shape
33
+ for a screen with room to spare: on a phone the on-screen keyboard takes the
34
+ room, and the composer was left halfway up what remained with an empty band
35
+ under it, which is the part of the screen a thumb is already on. At 600px wide
36
+ and below it now stays at the foot, directly over the keyboard, and the
37
+ greeting takes the space above it. The prompts in `data-starters`, or
38
+ whatever a host slots as `empty`, go to the foot with it and sit against the
39
+ composer: a prompt chip is a way into the conversation, and next to the field
40
+ it starts it reads as one. The greeting takes the middle of what they leave,
41
+ and the middle of the transcript where there are none.
42
+ `data-small-viewport="off"` keeps the centred shape at every width, as it
43
+ keeps the rest of the desktop layout.
44
+
45
+ ### Fixed
46
+
47
+ - **A tool card called with no arguments stops drawing an empty ARGUMENTS
48
+ heading.** The card hides the region rather than framing an empty object, but
49
+ the region declares its own `display`, which beats the user-agent rule for
50
+ the hidden attribute, so 42px of card was drawn holding a heading over
51
+ nothing -- on every call an agent made with no arguments, in the display mode
52
+ that shows arguments by default, and on a pending card's result region too.
53
+
54
+ - **A checkpoint continuation is let go of when its first save fails.** The
55
+ save runs through `conversationStore` synchronously inside the send, and a
56
+ store is the host's to replace: the built-in one swallows a write the browser
57
+ refused, a server-backed one need not. A throw there left the element holding
58
+ a client that would never run, so every later Resume or Fork was refused for
59
+ it with no way to clear it -- the composer's button is Send until a run
60
+ reports a start, so Stop was never offered -- and `sharedState` went on
61
+ writing into the dead client, which the next run then sent stale. The failure
62
+ is now reported with a `console.warn` and the panel takes another pick.
63
+
64
+ - **A turn typed between picking a checkpoint and its run starting is parked
65
+ rather than raced.** The composer learns a run is going from its first event,
66
+ a request round trip behind the pick, so its button was still Send: a turn
67
+ sent in that window started a second run against the conversation the
68
+ continuation had already frozen to save against, and whichever saved last
69
+ dropped the other's turn from the store while both answers streamed into one
70
+ transcript. The composer now queues it, as it does during a run, and sends it
71
+ when the continuation settles. `sendMessage` no-ops in the same window, as it
72
+ already did during a run.
73
+
74
+ - **An empty row of skill chips stops taking space above the composer.** The
75
+ row is hidden whenever the host offers no skills, which is most elements most
76
+ of the time, but it declares its own `display`, and an author display beats
77
+ the user-agent rule for the hidden attribute. So it kept its 20px of padding
78
+ under every placement: a band of panel between the transcript and the
79
+ composer, reading as the gap under whatever the transcript ended with. The
80
+ palette and the queued row beside it were fixed for this reason already, and
81
+ a tool card's payload regions turned out to be a fourth case, below.
82
+
83
+ - **A checkpoint continuation waits for the run in flight.** Picking Resume or
84
+ Fork while an answer was streaming started a second run beside it, and an
85
+ earlier continuation kept streaming where Stop no longer reached it. The pick
86
+ is now refused with a composer hint, as Retry is; the run in flight is left
87
+ alone and the typed turn stays in the composer. `continueWhileRunning` is new
88
+ in the string table.
89
+
90
+ - **A continued exchange is kept with its conversation.** It was never saved,
91
+ so a reload lost it, and the next message went out without it and with the
92
+ shared state from before the continuation. Each save now writes the
93
+ conversation with the exchange after it, through the existing `saveMessages`
94
+ -- `ClientConversationStore` is unchanged -- and the next message carries
95
+ both. After continuing from an earlier run, what is saved is what the
96
+ transcript shows, not only the turns up to that run. `sharedState` reads a
97
+ running continuation, and `AgUiClient.annotatedMessages` is new: the history
98
+ in the form a save writes it, with how each tool call ended.
99
+
100
+ - **A full-screen panel stays on the visible screen when a keyboard opens under
101
+ the field.** To show the composer, iOS Safari pans the visible area down the
102
+ page, and a panel anchored at the top of the screen stayed where it was: sized
103
+ to the visible height, it showed only its lower part, with the header and the
104
+ greeting off the top and an empty band above the keyboard. That covered
105
+ `page`, `full`, `side` and `sidebar`, and at phone width the corner placements
106
+ too, because they become the whole screen there, and their keyboard lift never
107
+ moved them: a box with its top, height and bottom all set ignores the bottom.
108
+ The element now also publishes how far the visible area is panned, as
109
+ `--ag-ui-visual-viewport-inset-top`, and every panel anchored at the top moves
110
+ down by as much of it as goes past the host's `--ag-ui-viewport-inset-top`.
111
+ `--ag-ui-keyboard-inset-top` outranks that, as `--ag-ui-keyboard-inset` does
112
+ at the bottom; `0px` opts out.
113
+
114
+ The same panel with a bar reserved at the top also ran past the bottom of the
115
+ visible area by the bar's height whenever a keyboard was up, pan or no pan:
116
+ the measured height replaced the host's box rather than being cut from it, so
117
+ the bar stayed in the position and left the height. The height is now the
118
+ host's box cut to the visible area.
119
+
120
+ At the deepest pan, the one that shows a composer docked at the foot of the
121
+ screen, the panel still went back to the top of the page at full height. The
122
+ element read the page's height from `innerHeight`, which iOS Safari reports
123
+ less the pan for as long as the pan lasts; at that depth it equals the visible
124
+ height, so the element took the two viewports to agree and withdrew its
125
+ measurements. The band below the visible area came out as nothing at any pan
126
+ too, leaving a bottom-anchored panel behind the keyboard. The page's height
127
+ is now read from the root element's `clientHeight`, which an iPhone reported
128
+ unchanged through the pan.
129
+
130
+ - **A full-screen panel no longer runs under Safari's bars with no keyboard
131
+ up.** Where nothing has been measured, the panel's height fell back to
132
+ `100vh`, which iOS Safari resolves to the screen with its bars collapsed, so
133
+ on a page that never scrolls them away the foot of the panel sat under the
134
+ address bar: 40px on the iPhone measured, with a docked composer in it. The
135
+ fallback is now `100dvh`, the screen with the bars as they are.
136
+
137
+ - **An element removed from the document and inserted again is one element.**
138
+ Every insertion built the chrome again into the same shadow root, so a
139
+ re-inserted `<ag-ui-chat>` had two headers, two composers, a second set of
140
+ resize grips, and the conversation drawn a second time beneath the first. The
141
+ listeners on its long-lived parts were added again too:
142
+ - one click on Stop cancelled the run three times;
143
+ - one press of the built-in theme toggle flipped it twice, so it appeared to
144
+ do nothing;
145
+ - a dropped or pasted file was added twice;
146
+ - one arrow press moved the launcher two steps.
147
+
148
+ With `data-threads-url`, a second remote store wrapped the first, so every
149
+ rename and delete reached the server twice. Frameworks do this without being
150
+ asked (a portal, a keyed list, a move between containers), and removing and
151
+ re-inserting is the documented way to apply a connect-time attribute written
152
+ late.
153
+
154
+ Connecting now builds the chrome in place of the last connection's, and ties
155
+ every listener on a long-lived part to the connection, so leaving the document
156
+ removes them. The upload tray and the mic are mounted afresh. The thread store
157
+ is wrapped once, and a `data-threads-url` removed while detached takes its
158
+ remote store away. The transcript is rebuilt from history, as `reload()`
159
+ rebuilds it. The composer's recall history is kept. Re-inserting still applies
160
+ the attributes as they stand when the element comes back. A host that called
161
+ `offerQuoteInPage()` calls it again after re-inserting, as before.
162
+
10
163
  ## [0.39.0] — 2026-09-17
11
164
 
12
165
  ### Changed
@@ -3973,7 +4126,8 @@ hosts that both arrange the page the way it expects.
3973
4126
  ### Notes
3974
4127
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
3975
4128
 
3976
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.39.0...HEAD
4129
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.40.0...HEAD
4130
+ [0.40.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.39.0...v0.40.0
3977
4131
  [0.39.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.38.0...v0.39.0
3978
4132
  [0.38.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.37.0...v0.38.0
3979
4133
  [0.37.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.36.0...v0.37.0
package/README.md CHANGED
@@ -253,6 +253,11 @@ The list: `data-attachments-url`, `data-attachment-accept`, `data-attachment-max
253
253
  `data-skills-url`, `data-skills`, `data-prompt-chips`, `data-slash-commands`, `data-theme-toggle`,
254
254
  `data-strings`, `data-icon-url`.
255
255
 
256
+ What re-inserting costs: the chrome is built again from the attributes as they stand, a run in
257
+ progress is cancelled, and the conversation is redrawn from its stored history — the same rebuild
258
+ `reload()` performs, and the reason that method exists for a host that can only configure the
259
+ element after the fact.
260
+
256
261
  **Properties** (JS only, not attributes): `headers`, `getHeaders`, `trustedOrigins`, `allowImages`,
257
262
  `autoConfirm`, `confirmPredicate`, `askUser`, `askUserRenderer`, `approvalRenderer`,
258
263
  `approveWithEdits`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`, `getPageMap`,
@@ -300,10 +305,13 @@ the `copyCode` / `copied` / `copyFailed` strings.
300
305
 
301
306
  `sendMessage(content, attachments?)` sends as if the user had typed it — user bubble,
302
307
  `ag-ui-submit` event, run started. Use it for an "Ask about this order" button, a command
303
- palette, or a composer of your own replacing the built-in one. It no-ops while a run is in
304
- flight and for an entirely empty message, and unlike the built-in Send it does **not** consult
305
- the attachment tray: what you pass is what is sent, so your composer stays in charge of its
306
- own state.
308
+ palette, or a composer of your own replacing the built-in one. It no-ops for an entirely
309
+ empty message, and while a run or a checkpoint continuation picked from the panel is in
310
+ flight; a continuation counts from the pick, not from its first event, so the gap where two
311
+ runs could start against one conversation is closed. Unlike the built-in Send it does **not**
312
+ queue, so your composer keeps what it tried to send, and it does **not** consult the
313
+ attachment tray: what you pass is what is sent, so your composer stays in charge of its own
314
+ state.
307
315
 
308
316
  `attachFile(file)` queues a file into the tray exactly as the picker and drag-and-drop do, with
309
317
  the same validation and progress chip. It returns `false` when uploads are not configured
@@ -869,6 +877,13 @@ chat.askUserRenderer = (request, { signal }) =>
869
877
  myModal.ask(request.question, request.options, { allowCustom: request.allowCustom, signal });
870
878
  ```
871
879
 
880
+ **A renderer that throws or rejects falls back to the built-in card** for that call, with a
881
+ `console.warn` naming the tool call. As with `approvalRenderer`, the renderer decides how the
882
+ question looks, not whether it is asked, so the run carries on as if no renderer were set. The one
883
+ exception is a rejection after the signal fired, which is how a renderer is expected to honour Stop:
884
+ that wait resolves with an empty answer, as the built-in card does on the same signal, with no card
885
+ and no warning.
886
+
872
887
  ### DOM-driver and animation primitives
873
888
 
874
889
  So the agent can visibly drive the host page, the package ships generic, framework-free
@@ -1082,7 +1097,10 @@ chat.addEventListener("ag-ui-toggle", (e) => console.log(e.detail.collapsed));
1082
1097
  ### The composer's own keys
1083
1098
 
1084
1099
  **Enter during a run queues.** A second run cannot start while one is in flight —
1085
- it would orphan the first — so that key used to do nothing at all, silently. What
1100
+ it would orphan the first — so that key used to do nothing at all, silently. A
1101
+ checkpoint continuation picked from the panel counts as in flight from the pick
1102
+ rather than from its first event, which is a request later, so a turn typed in
1103
+ that gap is parked too rather than racing it. What
1086
1104
  is waiting shows above the composer as chips, each of which takes its message
1087
1105
  back when pressed, and the next one is sent when the run settles. Stopping the
1088
1106
  run discards them: sending into a conversation someone has just stopped is the
@@ -2384,6 +2402,12 @@ Picking a row with an empty composer says so above the input and puts the caret
2384
2402
  there, rather than closing the panel over nothing: a continuation sends **only**
2385
2403
  the new turn, so with nothing typed there is nothing to send.
2386
2404
 
2405
+ Picking a row while an answer is still streaming — the conversation's own, or
2406
+ another continuation's — is refused the same way: the hint asks you to wait or
2407
+ stop it, and what you typed stays in the composer for the pick after. The run in
2408
+ flight is not cancelled for you, since a continuation starting over it would
2409
+ leave two answers streaming into one transcript with Stop reaching only one.
2410
+
2387
2411
  ### One URL, three endpoints
2388
2412
 
2389
2413
  `data-runs-url` is the only thing to configure. `resume/<id>/` and `fork/<id>/`
@@ -2401,7 +2425,7 @@ The component satisfies that structurally rather than by remembering a rule. A
2401
2425
  continuation runs on its own short-lived agent, built pointing at the resume
2402
2426
  endpoint and seeded with **no** history — so "only the new turn" is the only
2403
2427
  thing it *can* send, and the fresh run id comes free because a new agent mints
2404
- one. Your main agent's history is never touched.
2428
+ one. Nothing of the conversation's history goes out on its request.
2405
2429
 
2406
2430
  A resumed run is a normal run in every other respect: frontend tools execute,
2407
2431
  approval interrupts render their card, `headers` are re-read per request so
@@ -2411,9 +2435,14 @@ a rotated CSRF token or JWT still reaches the endpoint, it carries and updates
2411
2435
  construction as the conversation's own, differing only in the endpoint and the
2412
2436
  empty seed.
2413
2437
 
2414
- It does not write the conversation store. Its agent holds only the new turn and
2415
- the answer, and a store keeps one message list per thread, so saving that would
2416
- replace the stored conversation with its last exchange.
2438
+ The continued exchange joins the conversation. Its agent holds only the new turn
2439
+ and the answer, and a store keeps one message list per thread, so each save
2440
+ writes the conversation as it stood when you picked the row with the exchange
2441
+ after it — through the same `saveMessages` as any other run, and as far as it
2442
+ got if it was stopped. A reload then replays the exchange, and the next message
2443
+ you send, now to `endpoint` again, carries it together with the shared state the
2444
+ continuation left. What is saved is what the transcript shows: a fork's exchange
2445
+ follows the conversation it was picked from, in that same thread.
2417
2446
 
2418
2447
  If the index can't be reached, the panel shows its empty state rather than an
2419
2448
  error — a history affordance that fails is empty, not broken.
@@ -2745,6 +2774,12 @@ of the screen with a frame drawn round it.
2745
2774
  `embedded` is left alone deliberately: it sits in a box you sized and placed, and
2746
2775
  only you know whether that column should become the whole screen.
2747
2776
 
2777
+ The [greeting layout](#the-greeting-on-an-empty-page) changes shape here too, and
2778
+ it is the one part of this that `embedded` gets as well, because it moves nothing
2779
+ about the box you placed: an empty conversation keeps its composer at the foot of
2780
+ the panel, with the starters against it and the greeting over the space above
2781
+ them, instead of centring all three together.
2782
+
2748
2783
  The corner placements still rest at their launcher, so a full-bleed panel is
2749
2784
  something the user opens rather than something they are given.
2750
2785
 
@@ -2831,11 +2866,25 @@ widget measures the hidden band and publishes it as
2831
2866
  `--ag-ui-visual-viewport-inset-bottom`; state this one instead to outrank that
2832
2867
  measurement, or set it to `0px` to opt out of the lift entirely.
2833
2868
 
2869
+ `--ag-ui-keyboard-inset-top` does the same at the top. To show a field a
2870
+ keyboard would cover, a mobile browser pans the visible area down the page, and
2871
+ a panel anchored to the screen (`page`, `full`, `side`, `sidebar`, and at phone
2872
+ width the corner placements too) moves down with it. An `embedded` panel renders
2873
+ in your own box rather than against the screen, so it does not move at any width.
2874
+ The widget publishes the pan as `--ag-ui-visual-viewport-inset-top`, and the
2875
+ panel moves by as much of it as goes past your `--ag-ui-viewport-inset-top`,
2876
+ since the pan scrolls a reserved bar away with the page. State
2877
+ `--ag-ui-keyboard-inset-top` to outrank that distance, or `0px` to keep the
2878
+ panel below your reserved top.
2879
+
2834
2880
  `--ag-ui-viewport-height` and `--ag-ui-viewport-width` state the usable box
2835
2881
  outright, for the case where no viewport-percentage length describes it. An
2836
2882
  on-screen keyboard is the one that matters: it changes neither `vh` nor `dvh` nor
2837
2883
  `svh` on any current mobile browser, so a full-bleed panel has to be told the
2838
- visual viewport's height rather than deriving it.
2884
+ visual viewport's height rather than deriving it. The widget does that itself;
2885
+ where it has nothing to report, the panel falls back to `100dvh` rather than
2886
+ `100vh`, because iOS Safari resolves `vh` to the screen with its bars collapsed
2887
+ and a panel sized from it runs under them.
2839
2888
 
2840
2889
  Marks are variables too, so one vocabulary covers a re-theme rather than
2841
2890
  leaving half the transcript in the built-in set: `--ag-ui-tool-icon-done` /
@@ -2961,7 +3010,7 @@ component sets, so a new one cannot ship undocumented.
2961
3010
  | Typed question | `question`, `question-body`, `question-options`, `question-choice`, `question-choice-text`, `question-radio`, `question-input`, `question-actions`, `question-button` |
2962
3011
  | Composer | `composer`, `composer-surface`, `composer-tools`, `input`, `send`, `attach-button`, `voice-button` |
2963
3012
  | 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` |
2964
- | Skills | `skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`, `skill-item-desc`, `skill-item-token`, `skill-hint` (the composer hint: a skill’s missing placeholders, and a run continuation with nothing typed) |
3013
+ | Skills | `skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`, `skill-item-desc`, `skill-item-token`, `skill-hint` (the composer hint: a skill’s missing placeholders, and a run continuation picked with nothing typed or while a run is in flight) |
2965
3014
  | 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` |
2966
3015
  | Charts | `chart-block`, `chart-title`, `chart-legend` |
2967
3016
  | 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`) |
@@ -3050,7 +3099,8 @@ naturally with the [answer well](#the-answer-well).
3050
3099
  #### The greeting on an empty page
3051
3100
 
3052
3101
  Until the first message is sent, a page shows a greeting with the composer centred beneath it,
3053
- and the composer moves to the foot of the page once the conversation has something in it.
3102
+ and the composer moves to the foot of the page once the conversation has something in it. On a
3103
+ phone it starts at the foot, for the reason below.
3054
3104
 
3055
3105
  ```html
3056
3106
  <ag-ui-chat endpoint="/agent/" placement="page" user-name="Ada"></ag-ui-chat>
@@ -3071,6 +3121,14 @@ and the composer moves to the foot of the page once the conversation has somethi
3071
3121
  restores the plain layout there. `embedded` opts in with `data-greeting` (any value but `off`),
3072
3122
  for an app shell that gives the panel a page-sized box. The corner placements and the sidebar
3073
3123
  never show it: a panel opened from a launcher is already mid-task.
3124
+ - **On a phone** the composer stays at the foot, the starters (or your own `empty` content) sit
3125
+ against it, and the greeting takes the middle of what they leave — the middle of the
3126
+ transcript when there are none. Centring is a shape for a screen with room to spare; at
3127
+ [600px wide and below](#small-viewports) the room is what the on-screen keyboard takes, and a
3128
+ composer centred over an empty band sits halfway up what is left of the screen, with the
3129
+ prompts that start a conversation nowhere near the field that sends one.
3130
+ `data-small-viewport="off"` keeps the centred shape at every width, as it keeps the rest of
3131
+ the desktop layout.
3074
3132
 
3075
3133
  The composer's rows stay centred while you type, while a draft grows and while attachments are
3076
3134
  added, because the centring is two equal flexible halves either side of them and growth splits