@artooi/ag-ui-web-component 0.33.1 → 0.35.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.
- package/CHANGELOG.md +563 -1
- package/README.md +279 -16
- package/dist/ag-ui-web-component.bundle.js +676 -101
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +80 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +37 -2
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/dom/animations.d.ts +14 -0
- package/dist/dom/animations.d.ts.map +1 -1
- package/dist/dom/highlight_overlay.d.ts +47 -0
- package/dist/dom/highlight_overlay.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2288 -388
- package/dist/index.js.map +4 -4
- package/dist/tools/chat_surface_tools.d.ts +96 -0
- package/dist/tools/chat_surface_tools.d.ts.map +1 -0
- package/dist/tools/page_action_tools.d.ts +2 -0
- package/dist/tools/page_action_tools.d.ts.map +1 -1
- package/dist/ui/chart_block.d.ts +18 -0
- package/dist/ui/chart_block.d.ts.map +1 -1
- package/dist/ui/clamp_launcher.d.ts +10 -5
- package/dist/ui/clamp_launcher.d.ts.map +1 -1
- package/dist/ui/clamp_panel.d.ts +13 -0
- package/dist/ui/clamp_panel.d.ts.map +1 -0
- package/dist/ui/launcher_drag.d.ts +2 -2
- package/dist/ui/launcher_drag.d.ts.map +1 -1
- package/dist/ui/launcher_placement.d.ts +14 -1
- package/dist/ui/launcher_placement.d.ts.map +1 -1
- package/dist/ui/panel_drag.d.ts +40 -0
- package/dist/ui/panel_drag.d.ts.map +1 -0
- package/dist/ui/place_widget.d.ts +31 -0
- package/dist/ui/place_widget.d.ts.map +1 -0
- package/dist/ui/run_notice.d.ts +14 -3
- package/dist/ui/run_notice.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/thread_drawer.d.ts +21 -0
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +16 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +87 -0
- package/src/core/ag_ui_chat.ts +1146 -38
- package/src/dom/animations.ts +30 -0
- package/src/dom/highlight_overlay.ts +256 -0
- package/src/index.ts +12 -0
- package/src/tools/chat_surface_tools.ts +207 -0
- package/src/tools/page_action_tools.ts +2 -0
- package/src/ui/chart_block.ts +222 -57
- package/src/ui/clamp_launcher.ts +25 -7
- package/src/ui/clamp_panel.ts +30 -0
- package/src/ui/launcher_drag.ts +11 -2
- package/src/ui/launcher_placement.ts +50 -60
- package/src/ui/panel_drag.ts +138 -0
- package/src/ui/place_widget.ts +64 -0
- package/src/ui/run_notice.ts +32 -3
- package/src/ui/styles.ts +622 -47
- package/src/ui/thread_drawer.ts +138 -8
- package/src/ui/ui_strings.ts +24 -0
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,566 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.35.0] — 2026-09-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Enter during a run queues instead of doing nothing.** A second run cannot
|
|
15
|
+
start while one is in flight -- it would orphan the first, which is
|
|
16
|
+
unabortable, and the second's settle sweep would corrupt the first's still
|
|
17
|
+
pending tool cards -- so that key did nothing at all, silently, and the text
|
|
18
|
+
sat in the box.
|
|
19
|
+
|
|
20
|
+
What is waiting now shows above the composer as chips, each of which takes its
|
|
21
|
+
message back when pressed, and the next is sent when the run settles. Stopping
|
|
22
|
+
the run discards them: sending into a conversation someone has just stopped is
|
|
23
|
+
the opposite of what stopping meant. Not sending it is not the same as
|
|
24
|
+
destroying it, though -- a queued message has already left the composer, so
|
|
25
|
+
it goes to the front of the arrow-key recall history rather than nowhere.
|
|
26
|
+
Text only -- an attachment is settled state the tray already holds, and the
|
|
27
|
+
composer has no second copy of it.
|
|
28
|
+
|
|
29
|
+
- **A very long paste becomes an attachment rather than a wall of text.** At
|
|
30
|
+
5000 characters, and only where uploads are configured -- without somewhere
|
|
31
|
+
for it to go, quietly dropping a paste for being long is far worse than an
|
|
32
|
+
awkward composer. The field is capped at `40vh`, so a paste that size is
|
|
33
|
+
already taller than the box holding it: the reader cannot see what they
|
|
34
|
+
pasted, cannot edit around it, and sends one enormous turn.
|
|
35
|
+
|
|
36
|
+
Nothing is lost by removing the chip, because the text is still on the
|
|
37
|
+
clipboard -- which is why this needs no undo of its own.
|
|
38
|
+
`data-paste-attach` takes `off` or a character count.
|
|
39
|
+
|
|
40
|
+
- **The conversation list filters itself** once there are eight or more
|
|
41
|
+
conversations in it -- below that a search box is a control asking to be used
|
|
42
|
+
on a list you can already read in one glance. It matches the title **and** the
|
|
43
|
+
preview, because the title is often the model's one-line summary and the
|
|
44
|
+
phrase you remember is as likely to be inside the conversation as on it, and
|
|
45
|
+
it filters what the drawer already holds rather than going back to the server
|
|
46
|
+
for a list that is already in memory. A filter that matches nothing says so,
|
|
47
|
+
rather than reusing the sentence for having no conversations at all.
|
|
48
|
+
|
|
49
|
+
- **`data-starters` offers prompts on an empty transcript.** A JSON array of
|
|
50
|
+
strings, drawn as chips that send themselves. Different from the suggestion
|
|
51
|
+
chips a run pushes, which are follow-ups to something already said -- these
|
|
52
|
+
answer the blank page, and they are the host's rather than the model's,
|
|
53
|
+
because only the host knows what its page is for. They are fallback content
|
|
54
|
+
for `slot="empty"`, so slotting your own replaces them entirely, and they
|
|
55
|
+
share the four-prompt and 120-character limits with the pushed chips.
|
|
56
|
+
|
|
57
|
+
- **Up and Down walk back through what you have already sent**, from an empty
|
|
58
|
+
composer with the skills palette closed. The shape every shell and every
|
|
59
|
+
coding agent uses; the conditions are what make it safe, because an arrow
|
|
60
|
+
inside text is how you move the caret. Arrowing forward past the newest turn
|
|
61
|
+
empties the box again, so the way out is the key that got you in, and typing
|
|
62
|
+
hands the composer back so the next walk starts from the newest turn.
|
|
63
|
+
|
|
64
|
+
- **On a full page, the conversation list docks beside the transcript instead of
|
|
65
|
+
covering it.** From 900px of panel width under `placement="page"`: no
|
|
66
|
+
backdrop, no focus trap, and `role="region"` rather than a modal dialog, with
|
|
67
|
+
the transcript shifted over rather than hidden. Covering the conversation to
|
|
68
|
+
show the list of conversations hides the thing you are trying to get back to,
|
|
69
|
+
and a dedicated route is the one surface with width to spare.
|
|
70
|
+
|
|
71
|
+
Narrower than that, or under any other placement, it stays the slide-over it
|
|
72
|
+
was -- a few hundred pixels of panel with a list docked into it leaves a column
|
|
73
|
+
of transcript narrower than the messages in it. Width alone is not the test:
|
|
74
|
+
an app shell can hand `embedded` a page-sized box, and that box is still a
|
|
75
|
+
column of somebody's layout.
|
|
76
|
+
|
|
77
|
+
`--ag-ui-threads-rail-width` sets the docked width; the host carries
|
|
78
|
+
`data-threads-docked` while it is showing. `closeThreads()` joins
|
|
79
|
+
`openThreads()`.
|
|
80
|
+
|
|
81
|
+
- **The agent can move the panel it is speaking from.** Four tools behind a new
|
|
82
|
+
`chat` token on `data-page-actions`: `read_chat_surface`, `move_chat`,
|
|
83
|
+
`minimise_chat` and `restore_chat`, plus the `describeSurface()` and
|
|
84
|
+
`moveTo(corner)` methods behind them.
|
|
85
|
+
|
|
86
|
+
This is the affordance nobody else can offer. Every other assistant's chat is
|
|
87
|
+
a surface of its own, so it has nothing to be in the way *of*; this one is
|
|
88
|
+
mounted in the page the user is working in.
|
|
89
|
+
|
|
90
|
+
**They report what happened, not what was asked.** A panel that fills the
|
|
91
|
+
screen has nowhere to move to, and a placement that places itself owns its
|
|
92
|
+
position, so `move_chat` answers `moved: false` with the reason and what would
|
|
93
|
+
work instead. `read_chat_surface` lets the agent ask before it acts rather
|
|
94
|
+
than learn through a failure. `moveTo` claims the axes the same way a user
|
|
95
|
+
drag does -- the launcher travels with the panel, the corner it opens from is
|
|
96
|
+
re-picked, and switching placement hands everything back.
|
|
97
|
+
|
|
98
|
+
None is stamped `x-destructive`: moving a window destroys nothing, and a
|
|
99
|
+
confirmation card in front of it would be worse than the move. **What it gets
|
|
100
|
+
instead is a notice and an undo.** A panel that rearranges itself
|
|
101
|
+
mid-conversation has to be both visible and reversible, so `move_chat` and
|
|
102
|
+
`minimise_chat` write a run notice with an Undo beside it that puts the panel
|
|
103
|
+
back exactly where it was -- inset, launcher inset and the corner it opens
|
|
104
|
+
from, which are one decision rather than three. A host calling `moveTo` or
|
|
105
|
+
`setCollapsed` itself gets no notice: it is arranging its own page and does
|
|
106
|
+
not need telling what it just did.
|
|
107
|
+
|
|
108
|
+
Run notices may now carry that one control, and only ever an undo. Anything
|
|
109
|
+
the user has to *decide* is a confirmation card; a notice reports something
|
|
110
|
+
already done.
|
|
111
|
+
|
|
112
|
+
- **`showHighlightOverlay(el, options)`** -- ring a host-page element from an
|
|
113
|
+
overlay drawn *outside* it, optionally dimming everything else (`scrim`) or
|
|
114
|
+
flowing a gradient round it (`gradient`). Returns a function that removes it.
|
|
115
|
+
`flash` and `focusWithFlash` accept both and route through it when either is
|
|
116
|
+
asked for; the plain outline is unchanged and still the default.
|
|
117
|
+
|
|
118
|
+
These are one mechanism rather than two features, and the reason is the same
|
|
119
|
+
in both directions. The flat ring is an `outline` on the element deliberately,
|
|
120
|
+
because a `box-shadow` is clipped away by any `overflow: hidden` ancestor
|
|
121
|
+
sharing the target's box while the helper still reports success -- but an
|
|
122
|
+
outline takes a *colour*, there is no `outline-image`, and anything else that
|
|
123
|
+
can carry a gradient is a property of the target and lands back inside
|
|
124
|
+
whatever is clipping it. Dimming everything else needs a surface larger than
|
|
125
|
+
the target, which is the same problem from the other side.
|
|
126
|
+
|
|
127
|
+
**It is inert.** The overlay takes no pointer event at the cut-out or anywhere
|
|
128
|
+
else: a dim that swallows clicks is a modal the user did not open, and
|
|
129
|
+
`highlightThenClick` has to reach the control it just pointed at. It follows
|
|
130
|
+
the target on scroll and resize, and under reduced motion the gradient is
|
|
131
|
+
still drawn but stops travelling. Themed with `--ag-ui-highlight-scrim` and
|
|
132
|
+
`--ag-ui-highlight-gradient`.
|
|
133
|
+
|
|
134
|
+
- **Touch affordances, decided by the pointer rather than the width.** The eight
|
|
135
|
+
resize grips are hidden -- a 6px edge strip is not a control, it is a trap that
|
|
136
|
+
eats a scroll -- and the action, tool and send buttons go to 44px, which is
|
|
137
|
+
what iOS and Android ask for and what 28-30px missed.
|
|
138
|
+
|
|
139
|
+
The composer states at least 16px. iOS Safari zooms the page when a control
|
|
140
|
+
under that takes focus, which drags the whole fixed panel with it and leaves
|
|
141
|
+
the user pinching back out of a chat they only wanted to type into; the
|
|
142
|
+
composer inherits the widget font, which is 14px by default and 13px at
|
|
143
|
+
compact density.
|
|
144
|
+
|
|
145
|
+
Every scroll container also contains its own overscroll, so reaching the end
|
|
146
|
+
of the transcript no longer scrolls the page behind it.
|
|
147
|
+
|
|
148
|
+
- **`data-small-viewport="off"`** keeps the desktop layout at every width. Every
|
|
149
|
+
value the small-viewport override sets is a token a host can re-state; the
|
|
150
|
+
trigger is a media query, which cannot read one -- so without this the
|
|
151
|
+
breakpoint was the only part of the placement model a consumer could not reach.
|
|
152
|
+
|
|
153
|
+
- **A small-viewport layout.** At 600px wide and below, every placement but
|
|
154
|
+
`embedded` becomes one full-bleed shape: edge to edge, no radius, no shadow,
|
|
155
|
+
no resize grips. There was previously no width-based behaviour of any kind, so
|
|
156
|
+
a phone got the desktop's 380x560 floating panel clamped against the screen
|
|
157
|
+
edges -- and that clamp had already produced one shipped defect, because any
|
|
158
|
+
viewport under 428px was born against it.
|
|
159
|
+
|
|
160
|
+
A phone is not an eighth placement, it is an override that collapses the
|
|
161
|
+
others onto one of them. `embedded` is exempt: it sits in a box the host sized
|
|
162
|
+
and placed, and only the host knows whether that column should become the
|
|
163
|
+
whole screen. The corner placements still rest at their launcher, so a
|
|
164
|
+
full-bleed panel is something the user opens rather than something they are
|
|
165
|
+
given.
|
|
166
|
+
|
|
167
|
+
The breakpoint is a width rather than a pointer test. A touch laptop is
|
|
168
|
+
coarse-pointered and wide; a narrow desktop window is fine-pointered and
|
|
169
|
+
small.
|
|
170
|
+
|
|
171
|
+
- **Tell the widget which edges of the viewport your own chrome already
|
|
172
|
+
occupies**, with `--ag-ui-viewport-inset-top` / `-right` / `-bottom` / `-left`.
|
|
173
|
+
A fixed placement covers the viewport it is given and knows nothing about your
|
|
174
|
+
sticky header, so it came down on top of it.
|
|
175
|
+
|
|
176
|
+
Reserving that space was already possible and was the wrong shape of work:
|
|
177
|
+
`--ag-ui-inset` is one four-value shorthand and every placement has a different
|
|
178
|
+
default, so a host restated it per placement family and then kept
|
|
179
|
+
`--ag-ui-height` and `--ag-ui-max-height` in step by hand. Forgetting the
|
|
180
|
+
height half overflowed the panel off the bottom of the screen with nothing to
|
|
181
|
+
say so. Now `page` and `full` inset by all four edges, `sidebar` and `side` by
|
|
182
|
+
three, `floating` and `bottom-left` add them to their own margins, and every
|
|
183
|
+
height follows centrally.
|
|
184
|
+
|
|
185
|
+
They take `env(safe-area-inset-*)` verbatim. `--ag-ui-viewport-height` and
|
|
186
|
+
`--ag-ui-viewport-width` state the usable box outright for the case no
|
|
187
|
+
viewport-percentage length describes -- an on-screen keyboard changes neither
|
|
188
|
+
`vh` nor `dvh` nor `svh`, so a full-bleed panel on a phone has to be told.
|
|
189
|
+
|
|
190
|
+
The playground's own config bar was the first consumer: it replaced
|
|
191
|
+
twenty-five lines with one declaration.
|
|
192
|
+
|
|
193
|
+
### Changed
|
|
194
|
+
|
|
195
|
+
- **Four documented placements instead of seven.** `floating`, `sidebar`, `page`
|
|
196
|
+
and `embedded` are the four shapes that differ structurally: a corner panel, a
|
|
197
|
+
docked rail, a surface that owns the screen, and a thing in your layout.
|
|
198
|
+
|
|
199
|
+
`bottom-left`, `side` and `full` **still parse and still work**, and are no
|
|
200
|
+
longer documented. Each turned out to be a variant of one of the four rather
|
|
201
|
+
than a shape of its own: `full` is `page` with `--ag-ui-content-max-width:
|
|
202
|
+
none`, `bottom-left` is `floating` with a different `--ag-ui-inset`, and `side`
|
|
203
|
+
is `sidebar` collapsing to the floating launcher instead of an edge rail.
|
|
204
|
+
Nothing warns and no markup breaks -- `placement` is a public attribute in
|
|
205
|
+
hand-written HTML with no build step to catch a removed value, so removing one
|
|
206
|
+
would fail silently and visually. There is simply less to choose between.
|
|
207
|
+
|
|
208
|
+
- **The corner placements now rest at their launcher on a first visit.** An
|
|
209
|
+
unconfigured widget mounted open, so a visitor's first page load put a 380x560
|
|
210
|
+
panel over the host page's own bottom-right corner, uninvited. Every corner
|
|
211
|
+
chat in the field rests closed and treats opening as something the user does.
|
|
212
|
+
|
|
213
|
+
A stored choice still wins in both directions, so nobody who opened the panel
|
|
214
|
+
finds it closed. Only `floating` and `bottom-left` are affected -- the
|
|
215
|
+
placements that place themselves are unchanged, because a host that docks a
|
|
216
|
+
sidebar or embeds the widget in its own layout has already decided it belongs
|
|
217
|
+
on screen. `data-start-open` restores the previous behaviour.
|
|
218
|
+
|
|
219
|
+
**This is a visible change for any host that mounts the widget without a
|
|
220
|
+
placement**, which is the default, and it is the reason for the attribute.
|
|
221
|
+
|
|
222
|
+
- **Where the widget sits, how big it is and which theme it wears now outlive
|
|
223
|
+
the tab.** These went to `sessionStorage` beside the transcript and inherited
|
|
224
|
+
its lifetime without earning it: the transcript is per-tab on purpose -- two
|
|
225
|
+
tabs are two conversations -- while a user who dragged the panel clear of
|
|
226
|
+
their own interface did it again in the next tab, and again after every
|
|
227
|
+
restart.
|
|
228
|
+
|
|
229
|
+
They are written to `localStorage` and to the per-tab store, and read from the
|
|
230
|
+
durable one first. The second write is not redundancy: a privacy mode can deny
|
|
231
|
+
`localStorage` while allowing `sessionStorage`, and losing the durable copy
|
|
232
|
+
should degrade to the previous behaviour rather than to no persistence at all.
|
|
233
|
+
An existing per-tab value is still honoured, so nothing resets on upgrade.
|
|
234
|
+
|
|
235
|
+
**Whether the widget is currently open stays per-tab.** That is a statement
|
|
236
|
+
about this tab rather than a preference, and carrying it across would pop the
|
|
237
|
+
panel open in every new tab because it was opened once somewhere else.
|
|
238
|
+
|
|
239
|
+
- **The page placement no longer collapses.** It is a dedicated route rather
|
|
240
|
+
than a panel on someone else's page, so there was no "away" for it to go to:
|
|
241
|
+
collapsing left a strip of application chrome fixed over a route that no
|
|
242
|
+
longer had an owner, under the one placement that also hides the launcher.
|
|
243
|
+
|
|
244
|
+
Removing the control was not enough on its own. The state has three other
|
|
245
|
+
ways in -- the `collapsed` property, the attribute, and a value restored from
|
|
246
|
+
per-tab storage that was written under a different placement -- and the
|
|
247
|
+
storage key is namespaced per instance, not per placement. A tab that
|
|
248
|
+
collapsed a floating panel and later loaded the same instance as a page would
|
|
249
|
+
have restored a state with no control and no launcher to undo it. So the
|
|
250
|
+
property and the restore are gated, switching into the placement releases a
|
|
251
|
+
collapsed panel, and the stylesheet neutralises the state for the one path
|
|
252
|
+
that reaches none of those: an attribute written straight onto the element.
|
|
253
|
+
|
|
254
|
+
A control removed from the interface is not a state removed from the model.
|
|
255
|
+
|
|
256
|
+
The embedded placement is unchanged and still collapses to its header bar,
|
|
257
|
+
which is an ordinary accordion for a panel that sits in a page's own flow.
|
|
258
|
+
|
|
259
|
+
- **`--ag-ui-edge-gutter` and `--ag-ui-keyboard-inset`.** The first is the gap a
|
|
260
|
+
resting floating panel keeps from the box the host left free, and the size cap
|
|
261
|
+
now subtracts the same one rather than restating it. The second overrides the
|
|
262
|
+
lift an on-screen keyboard earns: the widget publishes what it measured, and a
|
|
263
|
+
host that states this outranks it -- the two-token shape the measured viewport
|
|
264
|
+
height already had, and the inset half was missing.
|
|
265
|
+
|
|
266
|
+
- **`createChatSurfaceTools` and its types are exported.** `moveTo` and
|
|
267
|
+
`describeSurface` are public methods, so a TypeScript consumer could call them
|
|
268
|
+
and had nowhere to import `ChatCorner` or `ChatSurfaceReport` from. The report
|
|
269
|
+
also gained `draggable`, and an origin on its `viewport`, so an agent reasoning
|
|
270
|
+
about the room beside the panel is not mixing two coordinate frames.
|
|
271
|
+
|
|
272
|
+
### Fixed
|
|
273
|
+
|
|
274
|
+
- **One screen-edge bound, instead of four answers to one question.** A drag
|
|
275
|
+
stopped the panel at 0, a resize at 0, a restore at the 24px resting gutter,
|
|
276
|
+
and the launcher at 8 -- so a panel dragged flush leapt a whole gutter inward
|
|
277
|
+
the next time anything re-placed it (a resize, an expand, a reload), and a
|
|
278
|
+
bubble held clear of the boundary sat beside a panel welded to it. Every
|
|
279
|
+
gesture now stops on the same line, 8px in, which is the bound the launcher
|
|
280
|
+
already had and it has it for the same reason: a rounded box with a drop
|
|
281
|
+
shadow sitting on the boundary has its shadow cut and its curve running into
|
|
282
|
+
the edge, and on a rounded screen or under a scrollbar it is genuinely
|
|
283
|
+
clipped. The 24px gutter keeps its own job -- where an untouched panel
|
|
284
|
+
*rests*, and what the size cap subtracts -- and is no longer a limit on where
|
|
285
|
+
a person may put one.
|
|
286
|
+
|
|
287
|
+
- **A floating panel grown to its cap sat outside the screen.** The cap was the
|
|
288
|
+
whole usable box while the resting inset had already spent a gutter on the
|
|
289
|
+
anchored corner, so the far edge landed exactly one gutter outside it -- a top
|
|
290
|
+
of -24 on an ordinary window, taking the header and every control in it off
|
|
291
|
+
the screen. With a reserved header it sat inside that instead, which is the
|
|
292
|
+
one thing reserving it is meant to prevent.
|
|
293
|
+
|
|
294
|
+
- **The corner a panel opened from ignored the edges a host had reserved.** The
|
|
295
|
+
probe compared the room on each side using the usable box's extents as though
|
|
296
|
+
it started at the origin, while the launcher's coordinates were the screen's
|
|
297
|
+
-- understating the room one way and overstating it the other by the same
|
|
298
|
+
reserved inset, which is enough to invert the choice rather than shade it. The
|
|
299
|
+
agent's own `moveTo` had the matching bug, and sent the panel under the very
|
|
300
|
+
chrome the reservation exists to keep it out of.
|
|
301
|
+
|
|
302
|
+
- **The reserved edges are read as lengths.** They were parsed out of the custom
|
|
303
|
+
property directly, which returns the token stream rather than a value: a host
|
|
304
|
+
stating `4rem` reserved four pixels, and one stating
|
|
305
|
+
`calc(56px + env(safe-area-inset-top))` -- the natural spelling of what the
|
|
306
|
+
token's own documentation recommends -- reserved `NaN`, which made every clamp
|
|
307
|
+
`NaN` and dropped the panel to its static position.
|
|
308
|
+
|
|
309
|
+
- **A stated position is measured from the layout viewport.** It was written
|
|
310
|
+
against the visual one, which is the box the widget is *clamped* into rather
|
|
311
|
+
than the box CSS resolves a fixed element's inset against. The two come apart
|
|
312
|
+
exactly when it matters: with a keyboard open, a panel the clamp had just held
|
|
313
|
+
inside the visible band was written back out behind the keyboard.
|
|
314
|
+
|
|
315
|
+
- **Undoing an agent's move erased the position it had stored.** The undo put
|
|
316
|
+
the panel back and then wrote the moved position to storage again, because the
|
|
317
|
+
helper it called returns early when there is nothing to store. The panel
|
|
318
|
+
returned and the next resize or reload sent it back to the corner the user had
|
|
319
|
+
just rejected -- and since that store now outlives the tab, so did the move.
|
|
320
|
+
|
|
321
|
+
- **The composer's history is cleared with the conversation.** Starting a new
|
|
322
|
+
chat, switching threads or changing `user-key` wiped the transcript and left
|
|
323
|
+
every turn the previous principal had typed one ArrowUp away in the composer.
|
|
324
|
+
|
|
325
|
+
- **A conversation filter that hides itself stops filtering.** A list that fell
|
|
326
|
+
below the threshold while a query matched nothing showed "no conversations
|
|
327
|
+
match that" over conversations that were right there, with no control left on
|
|
328
|
+
screen to clear -- and reopening the drawer did not help, only a new one would.
|
|
329
|
+
|
|
330
|
+
- **`move_chat` checks the corner it was given.** `required` in a schema is
|
|
331
|
+
advisory; a corner that is not one matched none of the edge tests, sent the
|
|
332
|
+
panel bottom-right, and was reported as a success for the corner asked for.
|
|
333
|
+
|
|
334
|
+
- **The agent's minimise offers no undo.** The notice is written into the
|
|
335
|
+
transcript and the collapse it describes is what hides the transcript, so the
|
|
336
|
+
control could only be reached from a state where it had nothing left to do.
|
|
337
|
+
|
|
338
|
+
- **A docked conversation list re-decides on a resize.** Docking is a width
|
|
339
|
+
decision taken only on the way in, so a window narrowed with the list open
|
|
340
|
+
kept a rail sized for the wide layout, with no focus trap and no backdrop.
|
|
341
|
+
|
|
342
|
+
- **The theme toggle is drawn rather than typed.** It was the moon and sun
|
|
343
|
+
emoji, and an emoji is painted by the platform's own font at its own weight
|
|
344
|
+
and colour, with ink that routinely runs outside the box laid out for it -- so
|
|
345
|
+
it clipped, and it was the one gold glyph in a row of white strokes. It is an
|
|
346
|
+
SVG path like every neighbour now.
|
|
347
|
+
|
|
348
|
+
- **The launcher keeps a little clear of the screen edge.** Flush against the
|
|
349
|
+
boundary, a circle with a drop shadow reads as clipped whether or not a pixel
|
|
350
|
+
is missing -- and on a rounded display, or under a scrollbar, it is. Not the
|
|
351
|
+
panel's 24px gutter, which is deliberately refused here because it would put
|
|
352
|
+
the corners people drag to out of reach; the margin is given up entirely on a
|
|
353
|
+
viewport too small to hold it.
|
|
354
|
+
|
|
355
|
+
- **The header's controls were sized by their contents**, so each came out as
|
|
356
|
+
wide as the glyph inside it -- five buttons of four different widths, four
|
|
357
|
+
pixels apart, with the theme toggle (the one emoji among the line icons) the
|
|
358
|
+
widest and looking like it had run into its neighbour. They are square and
|
|
359
|
+
equal now, from `--ag-ui-header-btn-size`, spaced by `--ag-ui-header-gap`.
|
|
360
|
+
|
|
361
|
+
Both go to 44px on a coarse pointer, which the earlier touch pass missed --
|
|
362
|
+
it raised the message, tool and send controls and left the header at half
|
|
363
|
+
their size, despite it being the only way to reach history, a new chat or the
|
|
364
|
+
collapse.
|
|
365
|
+
|
|
366
|
+
- **A resized panel and a dragged one disagreed about where the screen ends.**
|
|
367
|
+
The size cap subtracted a gutter that the drag's own limit did not, so the two
|
|
368
|
+
named different edges -- and once the size was capped a grip could no longer
|
|
369
|
+
grow the panel, which left the pull to land on the position instead and took
|
|
370
|
+
the whole panel down the screen. Pulling the other way stopped a gutter short
|
|
371
|
+
of an edge a drag could reach, leaving a band you could drag into but not
|
|
372
|
+
resize into, whose height changed with whatever the host had reserved.
|
|
373
|
+
|
|
374
|
+
The gutter belongs in the resting inset, which is what holds an untouched
|
|
375
|
+
panel clear of the edge; the cap is now simply what the host left free, which
|
|
376
|
+
is the limit the drag already used.
|
|
377
|
+
|
|
378
|
+
**It showed on one axis only, and the arithmetic says why**: the default panel
|
|
379
|
+
is 560 tall against a cap of the viewport minus 48 -- on an 800px screen with
|
|
380
|
+
a header reserved, 72px of headroom, so the height hit its cap almost at once.
|
|
381
|
+
The default width is 380 against a cap near 1230, which is 850px of headroom
|
|
382
|
+
nothing ever reaches. Same rule on both axes; only the vertical one was ever
|
|
383
|
+
felt.
|
|
384
|
+
|
|
385
|
+
- **Resizing had none of the restrictions dragging has.** A grip could be pulled
|
|
386
|
+
past the edges the host reserved, and pulling the *anchored* edge -- the
|
|
387
|
+
bottom, under the default placement -- wrote a negative inset and moved the
|
|
388
|
+
whole panel down the screen instead of growing it. Each edge is now bounded to
|
|
389
|
+
what the host left free: the gesture carries on and the panel simply stops.
|
|
390
|
+
|
|
391
|
+
A resize also persisted the size the pointer asked for rather than the one the
|
|
392
|
+
panel got, so the next mount restored a size it never had -- the same
|
|
393
|
+
disagreement between apply and commit that made the header drag jump.
|
|
394
|
+
|
|
395
|
+
- **Dragging the panel or the launcher jumped, and stopped short of every
|
|
396
|
+
edge.** Reported from a phone and reproduced on a desktop: the widget
|
|
397
|
+
followed the pointer until it passed roughly the middle of the screen, then
|
|
398
|
+
leapt by about the height of the host's own header bar -- and it could not be
|
|
399
|
+
dragged flush to any side.
|
|
400
|
+
|
|
401
|
+
Three separate causes, all of them in this release's own new work:
|
|
402
|
+
|
|
403
|
+
A CSS `inset` on a fixed element is measured from the **real** viewport
|
|
404
|
+
edges, and the new `--ag-ui-viewport-inset-*` support had them measured from
|
|
405
|
+
the box the host left free. Halfway across the screen is where the expand
|
|
406
|
+
corner flips -- the same point stops being written as a `top` and starts
|
|
407
|
+
being written as a `bottom` -- so that is where the difference appeared, as a
|
|
408
|
+
leap of exactly the reserved edge.
|
|
409
|
+
|
|
410
|
+
The 24px gutter a placement rests a panel at was being enforced against a
|
|
411
|
+
drag, which is what made it feel stuck short of every side. Staying on screen
|
|
412
|
+
is the part that matters, and that is still enforced.
|
|
413
|
+
|
|
414
|
+
Releasing the drag moved the launcher again, because the commit recomputed
|
|
415
|
+
the same sum the last move had already applied. Both now come from one
|
|
416
|
+
place, so the release changes nothing by construction.
|
|
417
|
+
|
|
418
|
+
Also fixed while in there: a `pointercancel` now ends a drag. On touch that
|
|
419
|
+
is routine rather than exceptional -- the browser takes the pointer back for
|
|
420
|
+
a scroll or a system gesture and never sends `pointerup` -- and without it the
|
|
421
|
+
move listeners stayed attached and the widget kept following a finger that
|
|
422
|
+
had stopped.
|
|
423
|
+
|
|
424
|
+
- **The highlight overlay could not be themed by any host that themes the
|
|
425
|
+
widget.** It is appended to the document body so it can escape the clipping it
|
|
426
|
+
exists to avoid, so a `var()` in its own inline style resolved against the
|
|
427
|
+
*body's* cascade -- a host setting `--ag-ui-accent` or
|
|
428
|
+
`--ag-ui-highlight-scrim` on `ag-ui-chat` or a wrapper, which is how everything
|
|
429
|
+
else here is themed, never reached it. Every token is now read from the
|
|
430
|
+
element being pointed at, which is where the flat ring has always read its
|
|
431
|
+
accent.
|
|
432
|
+
|
|
433
|
+
Its inline styles also beat any rule a host could write, and `::part` does not
|
|
434
|
+
reach the light DOM, so this was the one surface with no way in at all. The
|
|
435
|
+
ring width, the gradient's speed and the stacking order are now tokens too
|
|
436
|
+
(`--ag-ui-highlight-ring-width`, `--ag-ui-highlight-flow-ms`,
|
|
437
|
+
`--ag-ui-highlight-z-index`), with `ringWidth` and `flowMs` options over them.
|
|
438
|
+
|
|
439
|
+
- **A panel could be dragged somewhere it could not be rescued from.** The
|
|
440
|
+
clamps that keep it on screen measured a viewport starting at the top-left of
|
|
441
|
+
the display, so a panel dragged upward settled happily underneath a host's
|
|
442
|
+
sticky header -- and collapsing it, the one thing a user tries, replaced an
|
|
443
|
+
unreachable panel with an unreachable launcher. They now clamp against the
|
|
444
|
+
box the host actually left, reserved edges included.
|
|
445
|
+
|
|
446
|
+
- **The sidebar's edge rail ran under reserved chrome, and looked like a
|
|
447
|
+
stripe.** It asked for `100vh` and pinned its own bottom, so it ignored those
|
|
448
|
+
same reserved edges -- and the icon lives at the top of the rail, which made
|
|
449
|
+
the one control that reopens the panel the first thing to disappear behind a
|
|
450
|
+
header.
|
|
451
|
+
|
|
452
|
+
It has also been redesigned. A screen-high slab of accent carrying one small
|
|
453
|
+
icon is the widest collapsed state the widget has and the one that said least
|
|
454
|
+
about itself; it now reads as the docked edge of a panel -- the surface the
|
|
455
|
+
panel is made of, a border on the side it docks against, the accent kept for
|
|
456
|
+
the icon, and the widget's own title set down the rail as a caption
|
|
457
|
+
(`rail-label` part).
|
|
458
|
+
|
|
459
|
+
- **A panel no longer sizes itself to screen space the on-screen keyboard is
|
|
460
|
+
covering.** No CSS length describes this: a keyboard has no effect on any
|
|
461
|
+
viewport-percentage unit, so `100vh`, `100dvh` and `100svh` are the same
|
|
462
|
+
number with it up as without. A full-bleed panel sized from one of them put
|
|
463
|
+
its own composer behind the keyboard being typed into.
|
|
464
|
+
|
|
465
|
+
The element now tracks `visualViewport` and publishes both the visible height
|
|
466
|
+
and how much is hidden below it. The height shrinks the panel; the hidden gap
|
|
467
|
+
lifts anything anchored to the bottom, which a shorter panel does not do on
|
|
468
|
+
its own -- a floating widget is positioned against the layout viewport, so its
|
|
469
|
+
bottom edge and the launcher at that corner stayed behind the keyboard
|
|
470
|
+
whatever height it had. A host's own `--ag-ui-viewport-height` still outranks
|
|
471
|
+
the measurement, and nothing is written at all while the two viewports agree.
|
|
472
|
+
|
|
473
|
+
The same measurement fixes the clamps, which were computing which corner to
|
|
474
|
+
open into using space that was off the screen.
|
|
475
|
+
|
|
476
|
+
- **The chat-history list could not be dismissed under the embedded
|
|
477
|
+
placement.** The drawer closes when its backdrop is clicked, which is enough
|
|
478
|
+
wherever a strip of backdrop is showing -- but `embedded` widens the panel to
|
|
479
|
+
the full width of the host's box on purpose, so there was none left to hit.
|
|
480
|
+
What remained was Escape, which is invisible, picking a row, and New chat,
|
|
481
|
+
which replaces the conversation you opened the list to get back to.
|
|
482
|
+
|
|
483
|
+
The drawer header now carries a close control, exposed as the `drawer-close`
|
|
484
|
+
part. It is a third control in a row built for two, so it was measured at the
|
|
485
|
+
narrow end: at a 220px panel nothing overflows and every control stays inside
|
|
486
|
+
it. The title takes the slack and truncates rather than pushing them out.
|
|
487
|
+
|
|
488
|
+
- **The documented way to make a sidebar push content instead of overlaying it
|
|
489
|
+
did not work.** Setting `--ag-ui-position: static` and placing the element in
|
|
490
|
+
your own layout is what the README offers, and it put the panel at the
|
|
491
|
+
document origin rather than in the box you gave it -- measured 1631px above
|
|
492
|
+
its own host on a scrolled page, and pinned to the document's left edge rather
|
|
493
|
+
than the host's when docked left.
|
|
494
|
+
|
|
495
|
+
The panel is taken out of flow so the collapse can slide it out at full width,
|
|
496
|
+
which needs the host to be a containing block. The host was one only by
|
|
497
|
+
accident, because it is `position: fixed` by default; a static element
|
|
498
|
+
establishes nothing, so the panel resolved against the initial containing
|
|
499
|
+
block instead. The sidebar host now contains its own layout whatever its
|
|
500
|
+
position, which is a no-op in the default overlay case.
|
|
501
|
+
|
|
502
|
+
It looked correct wherever it was first tried: a full-height column at the top
|
|
503
|
+
of an unscrolled document is exactly where the two answers coincide. The
|
|
504
|
+
stylesheet's own tests could not see it either -- they match strings against
|
|
505
|
+
the source, and every declaration involved was already correct on its own. The
|
|
506
|
+
regression test measures rects in a real browser, with the host offset from
|
|
507
|
+
the viewport and the page scrolled.
|
|
508
|
+
|
|
509
|
+
## [0.34.0] — 2026-09-03
|
|
510
|
+
|
|
511
|
+
### Added
|
|
512
|
+
|
|
513
|
+
- **The open panel moves by its header**, the way a window moves by its title
|
|
514
|
+
bar. The collapsed launcher has been draggable since 0.33.0 and the panel was
|
|
515
|
+
not, so a chat sitting over the thing it was being asked about could only be
|
|
516
|
+
moved by collapsing it first.
|
|
517
|
+
|
|
518
|
+
It is one widget being moved, not two things being placed: **the launcher
|
|
519
|
+
travels the same distance the panel does**, so a panel dragged 100px left
|
|
520
|
+
leaves the bubble it collapses into 100px left of where it was. The distance
|
|
521
|
+
is the panel's own rather than the pointer's -- a panel held against the
|
|
522
|
+
viewport's margin stops, and the bubble stops with it -- and the bubble is
|
|
523
|
+
then held on screen in its own right.
|
|
524
|
+
|
|
525
|
+
A stated position is also kept rather than re-derived, which is the whole
|
|
526
|
+
difference between the two drags. A launcher drag says where the bubble goes
|
|
527
|
+
and leaves the panel to open into whatever room the viewport has, re-decided
|
|
528
|
+
on every expand and every window resize; a header drag states the panel's own
|
|
529
|
+
position, so it survives collapsing, reopening and reloading, and dragging the
|
|
530
|
+
bubble again hands the decision back. Which corner the panel is pinned by is
|
|
531
|
+
re-picked when the drag ends, from where the bubble ended up, so the next
|
|
532
|
+
expand still opens into clear space -- and re-picking it moves nothing, since
|
|
533
|
+
both insets are written from positions that are already decided. Stored per
|
|
534
|
+
tab like the launcher's own position, and off under
|
|
535
|
+
`data-launcher-drag="false"` along with the launcher drag.
|
|
536
|
+
|
|
537
|
+
A press that starts on a control in the header stays that control's, including
|
|
538
|
+
one a host slotted in. There is no keyboard shortcut on the header on purpose:
|
|
539
|
+
a header is not a control, and making it focusable would put a tab stop with
|
|
540
|
+
no role ahead of the controls a keyboard user came for, while arrow keys on
|
|
541
|
+
the collapsed launcher already move the widget, panel included.
|
|
542
|
+
|
|
543
|
+
### Fixed
|
|
544
|
+
|
|
545
|
+
- **A chart no longer grows with the panel.** Widening the panel used to resize
|
|
546
|
+
what was already in it: the renderer drew into a fixed 480x220 viewBox, the
|
|
547
|
+
block stretched to the transcript's full width, and `width: 100%` scaled the
|
|
548
|
+
drawing to fit it. In a 1100px panel the axis labels came out **3.25x** the
|
|
549
|
+
size they are at the default width and the chart stood **489px** tall, pushing
|
|
550
|
+
the answer it belonged to off the screen.
|
|
551
|
+
|
|
552
|
+
Two separate things were wrong, and both are fixed:
|
|
553
|
+
|
|
554
|
+
- **It was magnified rather than sized.** Geometry is now computed for the
|
|
555
|
+
width the block actually has, one SVG unit per CSS pixel, and recomputed
|
|
556
|
+
when that width changes -- so a 10px axis label is 10px at every size,
|
|
557
|
+
including the narrow end, where the old drawing was shrunk to 7px in the
|
|
558
|
+
default 380px panel. Height follows width inside a 160-320px band, and the
|
|
559
|
+
480px width draws exactly the frame it always did. Where the labels no
|
|
560
|
+
longer fit, every second or third is drawn rather than a smear of
|
|
561
|
+
overlapping words -- a new answer to a collision that was there at every
|
|
562
|
+
size before. Below 220px the drawing scales down as it used to, since at
|
|
563
|
+
that width nothing fits either way.
|
|
564
|
+
- **It was stretched rather than sized too.** The block now takes the width it
|
|
565
|
+
needs and stops, capping at the new `--ag-ui-chart-max-width` (**480px**),
|
|
566
|
+
the way a message bubble caps rather than filling the panel. Raise the token
|
|
567
|
+
for a bigger chart and the labels stay 10px: the cap decides how much room
|
|
568
|
+
the drawing gets, never how big its type is.
|
|
569
|
+
|
|
10
570
|
## [0.33.1] — 2026-09-03
|
|
11
571
|
|
|
12
572
|
### Fixed
|
|
@@ -2840,7 +3400,9 @@ hosts that both arrange the page the way it expects.
|
|
|
2840
3400
|
### Notes
|
|
2841
3401
|
- First release — exercising the automated npm OIDC publish pipeline end-to-end.
|
|
2842
3402
|
|
|
2843
|
-
[Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.
|
|
3403
|
+
[Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.35.0...HEAD
|
|
3404
|
+
[0.35.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.34.0...v0.35.0
|
|
3405
|
+
[0.34.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.33.1...v0.34.0
|
|
2844
3406
|
[0.33.1]: https://github.com/Artui/ag-ui-web-component/compare/v0.33.0...v0.33.1
|
|
2845
3407
|
[0.33.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.32.0...v0.33.0
|
|
2846
3408
|
[0.32.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.31.1...v0.32.0
|