@artooi/ag-ui-web-component 0.22.0 → 0.23.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 (122) hide show
  1. package/CHANGELOG.md +134 -36
  2. package/README.md +7 -7
  3. package/dist/ag-ui-web-component.bundle.js +145 -118
  4. package/dist/ag-ui-web-component.bundle.js.map +3 -3
  5. package/dist/constants.d.ts +65 -78
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +93 -126
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/agui_client.d.ts +24 -30
  10. package/dist/core/agui_client.d.ts.map +1 -1
  11. package/dist/core/attachment.d.ts +9 -14
  12. package/dist/core/attachment.d.ts.map +1 -1
  13. package/dist/core/conversation_store.d.ts +20 -27
  14. package/dist/core/conversation_store.d.ts.map +1 -1
  15. package/dist/core/create_http_agent.d.ts +13 -15
  16. package/dist/core/create_http_agent.d.ts.map +1 -1
  17. package/dist/core/remote_conversation_store.d.ts +8 -9
  18. package/dist/core/remote_conversation_store.d.ts.map +1 -1
  19. package/dist/core/run_index.d.ts +11 -20
  20. package/dist/core/run_index.d.ts.map +1 -1
  21. package/dist/core/transcribe_audio.d.ts +8 -8
  22. package/dist/core/transcribe_audio.d.ts.map +1 -1
  23. package/dist/core/upload_attachment.d.ts +15 -18
  24. package/dist/core/upload_attachment.d.ts.map +1 -1
  25. package/dist/core/utils.d.ts +4 -6
  26. package/dist/core/utils.d.ts.map +1 -1
  27. package/dist/dom/animations.d.ts +22 -30
  28. package/dist/dom/animations.d.ts.map +1 -1
  29. package/dist/dom/dom_driver.d.ts +7 -7
  30. package/dist/dom/native_setter.d.ts +2 -2
  31. package/dist/dom/native_setter.d.ts.map +1 -1
  32. package/dist/index.js +347 -401
  33. package/dist/index.js.map +2 -2
  34. package/dist/skills/fill_template.d.ts +4 -5
  35. package/dist/skills/fill_template.d.ts.map +1 -1
  36. package/dist/skills/parse_skills.d.ts.map +1 -1
  37. package/dist/skills/skill.d.ts +7 -8
  38. package/dist/skills/skill.d.ts.map +1 -1
  39. package/dist/tools/client_tool_registry.d.ts +2 -2
  40. package/dist/tools/page_action_tools.d.ts +7 -10
  41. package/dist/tools/page_action_tools.d.ts.map +1 -1
  42. package/dist/tools/page_state.d.ts +5 -8
  43. package/dist/tools/page_state.d.ts.map +1 -1
  44. package/dist/tools/route_map.d.ts +7 -10
  45. package/dist/tools/route_map.d.ts.map +1 -1
  46. package/dist/ui/approval_card.d.ts +15 -20
  47. package/dist/ui/approval_card.d.ts.map +1 -1
  48. package/dist/ui/attach_copy_buttons.d.ts +4 -10
  49. package/dist/ui/attach_copy_buttons.d.ts.map +1 -1
  50. package/dist/ui/attachment_chips.d.ts +13 -5
  51. package/dist/ui/attachment_chips.d.ts.map +1 -1
  52. package/dist/ui/attachment_tray.d.ts +6 -6
  53. package/dist/ui/attachment_tray.d.ts.map +1 -1
  54. package/dist/ui/checkpoint_menu.d.ts +7 -8
  55. package/dist/ui/checkpoint_menu.d.ts.map +1 -1
  56. package/dist/ui/confirmation_card.d.ts +10 -15
  57. package/dist/ui/confirmation_card.d.ts.map +1 -1
  58. package/dist/ui/question_card.d.ts +12 -15
  59. package/dist/ui/question_card.d.ts.map +1 -1
  60. package/dist/ui/relative_time.d.ts +5 -7
  61. package/dist/ui/relative_time.d.ts.map +1 -1
  62. package/dist/ui/render_markdown.d.ts +8 -8
  63. package/dist/ui/render_markdown.d.ts.map +1 -1
  64. package/dist/ui/resize_handle.d.ts +21 -34
  65. package/dist/ui/resize_handle.d.ts.map +1 -1
  66. package/dist/ui/run_notice.d.ts +5 -7
  67. package/dist/ui/run_notice.d.ts.map +1 -1
  68. package/dist/ui/skills_menu.d.ts +4 -5
  69. package/dist/ui/skills_menu.d.ts.map +1 -1
  70. package/dist/ui/styles.d.ts +1 -1
  71. package/dist/ui/styles.d.ts.map +1 -1
  72. package/dist/ui/thoughts_block.d.ts +9 -11
  73. package/dist/ui/thoughts_block.d.ts.map +1 -1
  74. package/dist/ui/thread_drawer.d.ts +6 -5
  75. package/dist/ui/thread_drawer.d.ts.map +1 -1
  76. package/dist/ui/tool_call_card.d.ts +17 -25
  77. package/dist/ui/tool_call_card.d.ts.map +1 -1
  78. package/dist/ui/ui_strings.d.ts +6 -12
  79. package/dist/ui/ui_strings.d.ts.map +1 -1
  80. package/dist/ui/voice_input.d.ts +10 -11
  81. package/dist/ui/voice_input.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/constants.ts +71 -80
  84. package/src/core/ag_ui_chat.ts +242 -292
  85. package/src/core/agui_client.ts +60 -71
  86. package/src/core/attachment.ts +9 -14
  87. package/src/core/conversation_store.ts +25 -33
  88. package/src/core/create_http_agent.ts +18 -22
  89. package/src/core/remote_conversation_store.ts +14 -15
  90. package/src/core/run_index.ts +14 -23
  91. package/src/core/transcribe_audio.ts +9 -10
  92. package/src/core/upload_attachment.ts +18 -21
  93. package/src/core/utils.ts +4 -6
  94. package/src/dom/animations.ts +33 -43
  95. package/src/dom/dom_driver.ts +7 -7
  96. package/src/dom/native_setter.ts +11 -12
  97. package/src/skills/fill_template.ts +4 -5
  98. package/src/skills/parse_skills.ts +3 -4
  99. package/src/skills/skill.ts +7 -8
  100. package/src/tools/client_tool_registry.ts +2 -2
  101. package/src/tools/page_action_tools.ts +12 -15
  102. package/src/tools/page_state.ts +5 -8
  103. package/src/tools/route_map.ts +15 -19
  104. package/src/ui/approval_card.ts +15 -20
  105. package/src/ui/attach_copy_buttons.ts +9 -18
  106. package/src/ui/attachment_chips.ts +19 -10
  107. package/src/ui/attachment_tray.ts +8 -7
  108. package/src/ui/checkpoint_menu.ts +7 -8
  109. package/src/ui/confirmation_card.ts +10 -15
  110. package/src/ui/question_card.ts +12 -15
  111. package/src/ui/relative_time.ts +5 -7
  112. package/src/ui/render_markdown.ts +25 -51
  113. package/src/ui/resize_handle.ts +25 -38
  114. package/src/ui/run_notice.ts +9 -12
  115. package/src/ui/skills_menu.ts +4 -5
  116. package/src/ui/styles.ts +122 -93
  117. package/src/ui/thoughts_block.ts +11 -13
  118. package/src/ui/thread_drawer.ts +6 -5
  119. package/src/ui/tool_call_card.ts +22 -32
  120. package/src/ui/ui_strings.ts +6 -12
  121. package/src/ui/voice_input.ts +10 -11
  122. package/src/version.ts +1 -1
package/src/ui/styles.ts CHANGED
@@ -8,12 +8,11 @@ export const STYLES = `
8
8
  only the alias is used by the rules below.
9
9
 
10
10
  The indirection is what makes ancestor theming work. Declaring the public
11
- name on :host would set it *on the host element*, and a value on an element
12
- always beats one inherited from an ancestor so a page that put the tokens
13
- on a wrapper would see no effect at all, which is the one thing the API is
14
- documented to support. Reading the public name with the default as a var()
15
- fallback leaves it undeclared on the element, so an ancestor's value is
16
- inherited normally while a value aimed at the element still wins over it.
11
+ name on :host would set it on the host element, and a value on an element
12
+ always beats one inherited from an ancestor, so tokens put on a wrapper
13
+ would have no effect. Reading the public name with the default as a var()
14
+ fallback leaves it undeclared on the element, so an ancestor's value
15
+ inherits normally while one aimed at the element still wins.
17
16
 
18
17
  Two invariants hold this together:
19
18
  1. No rule outside this file's :host blocks may reference a public name
@@ -40,11 +39,9 @@ export const STYLES = `
40
39
  --_border: var(--ag-ui-border, #e2e2ec);
41
40
  --_radius: var(--ag-ui-radius, 12px);
42
41
 
43
- /* Body text and raised chrome. Both are referenced by the code-block copy
44
- button; neither had a default before, so the declarations reading them
45
- were dropped and the button fell back to the inherited colour over a
46
- transparent box. The defaults below restate exactly that, so this is a
47
- rename with no repaint — see the note on .code-copy. */
42
+ /* Body text and raised chrome, read only by the code-block copy button.
43
+ The defaults restate what it inherits, so a host that sets neither sees
44
+ no repaint see the note on .code-copy. */
48
45
  --_text: var(--ag-ui-text, var(--_fg));
49
46
  --_surface: var(--ag-ui-surface, transparent);
50
47
 
@@ -97,9 +94,9 @@ export const STYLES = `
97
94
  --_launcher-inset: var(--ag-ui-launcher-inset, auto 0 0 auto);
98
95
 
99
96
  /* Motion. One duration and two curves drive every collapse, expand and
100
- slide-over, so the whole widget accelerates and settles as one thing.
101
- The default curve is a long-tailed ease-out (a fast start that decelerates
102
- into place); the pop curve overshoots slightly, for something arriving. */
97
+ slide-over, so the widget accelerates and settles as one thing. The
98
+ default curve decelerates into place; the pop curve overshoots slightly,
99
+ for something arriving. */
103
100
  --_motion: var(--ag-ui-motion, 0.28s);
104
101
  --_ease: var(--ag-ui-ease, cubic-bezier(0.32, 0.72, 0, 1));
105
102
  --_ease-pop: var(--ag-ui-ease-pop, cubic-bezier(0.34, 1.36, 0.64, 1));
@@ -123,7 +120,7 @@ export const STYLES = `
123
120
  /* Reading-column width for placement="page" (full-bleed, centred content). */
124
121
  --_content-max-width: var(--ag-ui-content-max-width, 820px);
125
122
  /* Slim rail the sidebar placement collapses to. Only that placement reads
126
- it, but it is declared here so invariant 2 holds for every alias. */
123
+ it, but it is declared here so every alias has a default in one place. */
127
124
  --_rail-width: var(--ag-ui-rail-width, 52px);
128
125
 
129
126
  position: var(--_position);
@@ -215,11 +212,11 @@ export const STYLES = `
215
212
  --_radius: var(--ag-ui-radius, 0);
216
213
  }
217
214
 
218
- /* Page: full-bleed background with a centred reading column. Unlike
219
- "full" (edge-to-edge, left-aligned messages) the content sits in a column
220
- capped at --ag-ui-content-max-width. The column is produced by symmetric auto
221
- padding on the scroll area + composer (no per-row wrapper), so user pills
222
- still right-align and the assistant well spans the column. */
215
+ /* Page: full-bleed background with a centred reading column capped at
216
+ --ag-ui-content-max-width, where "full" is edge-to-edge and left-aligned.
217
+ The column comes from symmetric auto padding on the scroll area and
218
+ composer rather than a per-row wrapper, so user pills still right-align and
219
+ the assistant well spans the column. */
223
220
  :host([placement="page"]) {
224
221
  --_inset: var(--ag-ui-inset, 0);
225
222
  --_width: var(--ag-ui-width, 100vw);
@@ -237,9 +234,8 @@ export const STYLES = `
237
234
  padding-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));
238
235
  }
239
236
 
240
- /* The rows between the message list and the composer (skill chips, the
241
- /-command palette, the missing-placeholder hint, the upload tray) line up
242
- with the column too — chips are padding-based, the palette/hint/tray are
237
+ /* The rows between the message list and the composer line up with the column
238
+ too. Chips and tray are padding-based while palette and hint are
243
239
  margin-based, so each gets its own inline axis nudged by the same gutter. */
244
240
  :host([placement="page"]) .skill-chips,
245
241
  :host([placement="page"]) .attachment-tray {
@@ -348,13 +344,12 @@ export const STYLES = `
348
344
  visibility var(--_motion) var(--_ease);
349
345
  }
350
346
 
351
- /* The launcher grows out of the corner the panel shrank into.
352
- It is laid out at rest rather than display:none, which is what lets it
353
- animate in *and* out: an element that was not rendered has no before-change
354
- style to transition from, and one whose display flips to none cannot
355
- transition at all. visibility keeps it unpaintable, untabbable and
356
- unclickable in between -- the expanded panel's own controls sit under it and
357
- must stay reachable. */
347
+ /* The launcher grows out of the corner the panel shrank into. It stays laid
348
+ out at rest rather than display:none, which is what lets it animate both in
349
+ and out: an unrendered element has no before-change style to transition
350
+ from, and one flipping display to none cannot transition at all. visibility
351
+ keeps it unpaintable, untabbable and unclickable in between, so the expanded
352
+ panel's own controls underneath stay reachable. */
358
353
  :host([collapsed]) .launcher {
359
354
  opacity: 1;
360
355
  transform: none;
@@ -515,20 +510,18 @@ export const STYLES = `
515
510
 
516
511
  /* ── Collapse ───────────────────────────────────────────────────────────────
517
512
  Collapsing shrinks the widget to the round floating launcher: the panel
518
- scales down toward the launcher's corner and fades out, the launcher pops in
519
- from that same point. Both halves are transform and opacity only, so the
520
- morph runs on the compositor and never reflows the host page.
521
-
522
- The host box keeps its expanded size. Animating it would animate layout, and
523
- a dragged --ag-ui-width would then fight the launcher's own size. Nothing
524
- paints there once the panel is gone, so the box only has to stop swallowing
525
- clicks: pointer events go to none, and the launcher takes them back.
526
-
527
- Two placements collapse to something else and restore what they need:
528
- "sidebar" slides to its rail (below), while "embedded" and "page" keep the
529
- original header bar embedded is laid out by the host page, where a
530
- floating circle would escape the layout, and page is a full-screen route
531
- with no corner to float in. */
513
+ scales toward the launcher's corner and fades, the launcher pops in from the
514
+ same point. Both halves are transform and opacity only, so the morph runs on
515
+ the compositor and never reflows the host page.
516
+
517
+ The host box keeps its expanded size, since animating it would animate
518
+ layout and a dragged --ag-ui-width would fight the launcher's own size.
519
+ Nothing paints there once the panel is gone, so the box only has to stop
520
+ swallowing clicks: pointer events go to none and the launcher takes them.
521
+
522
+ Two placements collapse differently: "sidebar" slides to its rail (below),
523
+ while "embedded" and "page" keep the header bar, having no corner for a
524
+ floating circle that would escape the host's layout. */
532
525
  :host([collapsed]) {
533
526
  pointer-events: none;
534
527
  }
@@ -539,11 +532,11 @@ export const STYLES = `
539
532
  visibility: hidden;
540
533
  }
541
534
 
542
- /* visibility is what keeps the panel out of the tab order and the a11y tree at
543
- rest without display:none killing the transition. It interpolates so that any
544
- progress below 1 still counts as visible: the panel stays on screen for the
545
- whole collapse and flips hidden exactly at the end, and on expand it is
546
- visible from the first frame. */
535
+ /* visibility keeps the panel out of the tab order and the a11y tree at rest
536
+ without display:none killing the transition. It interpolates so any progress
537
+ below 1 still counts as visible: the panel stays on screen for the whole
538
+ collapse, flips hidden exactly at the end, and on expand is visible from the
539
+ first frame. */
547
540
  .chat {
548
541
  transform-origin: bottom right;
549
542
  transition:
@@ -570,10 +563,9 @@ export const STYLES = `
570
563
  visibility: visible;
571
564
  }
572
565
 
573
- /* These two keep the header bar, so the launcher must stay out of the way
574
- an embedded host is position: static, which would otherwise leave an
575
- absolutely-positioned circle to escape the layout entirely and land against
576
- whatever the page happens to position. */
566
+ /* These two keep the header bar, so the launcher must stay out of the way: an
567
+ embedded host is position: static, which would let an absolutely-positioned
568
+ circle escape the layout and land against whatever the page positions. */
577
569
  :host([collapsed]:is([placement="embedded"], [placement="page"])) .launcher {
578
570
  visibility: hidden;
579
571
  opacity: 0;
@@ -613,7 +605,7 @@ export const STYLES = `
613
605
  and the pending indicator so a whole answer reads (and can be boxed) as one
614
606
  unit. A flex column on the message-list gap, stretched to the list width so
615
607
  its children keep their own left/right alignment. data-answer-well opts into
616
- the bordered "well"; without it the layout is today's flat stack. */
608
+ the bordered "well"; without it the turn renders as a flat stack. */
617
609
  .answer {
618
610
  display: flex;
619
611
  flex-direction: column;
@@ -739,13 +731,9 @@ export const STYLES = `
739
731
  position: relative;
740
732
  }
741
733
 
742
- /* This button is the only reader of --ag-ui-surface and --ag-ui-text, and
743
- neither had a default until the alias layer gave them one. Both references
744
- used to resolve to nothing, dropping the declaration: the background fell
745
- back to the initial transparent, and the hover/copied colour to the
746
- inherited body colour. The defaults chosen upstream (transparent, and the
747
- body foreground) reproduce that, so the rename repaints nothing. A raised
748
- surface behind the button is a separate design question. */
734
+ /* The only reader of --ag-ui-surface and --ag-ui-text. Their defaults are
735
+ transparent and the body foreground, which is what this button rendered as
736
+ before either token existed; changing them repaints only this control. */
749
737
  .code-copy {
750
738
  position: absolute;
751
739
  top: 4px;
@@ -791,12 +779,10 @@ export const STYLES = `
791
779
  color: var(--_muted);
792
780
  }
793
781
 
794
- /* Markdown tables. table/thead/tbody/tr/th/td are all in
795
- the sanitizer's ALLOWED_TAGS, so an agent emitting one renders it and until
796
- now rendered it entirely unstyled, overflowing the bubble. A wide table
797
- scrolls inside its own box rather than stretching the message: the bubble is
798
- width-constrained, so without this the columns either crush or push the
799
- layout sideways. */
782
+ /* Markdown tables. table/thead/tbody/tr/th/td are all in the sanitizer's
783
+ ALLOWED_TAGS, so an agent can emit one. A wide table must scroll inside its
784
+ own box rather than stretch the message: the bubble is width-constrained, so
785
+ without this the columns either crush or push the layout sideways. */
800
786
  .message--assistant table {
801
787
  display: block;
802
788
  width: fit-content;
@@ -1053,8 +1039,6 @@ export const STYLES = `
1053
1039
  color: var(--_muted);
1054
1040
  }
1055
1041
 
1056
- /* The record of a human decision on a gated call. An approved call used to
1057
- look exactly like one that was never gated. */
1058
1042
  .skill-item-token {
1059
1043
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
1060
1044
  font-size: 0.92em;
@@ -1062,6 +1046,8 @@ export const STYLES = `
1062
1046
  margin-right: 6px;
1063
1047
  }
1064
1048
 
1049
+ /* The lasting record of a human decision on a gated call — without it an
1050
+ approved call looks exactly like one that was never gated. */
1065
1051
  .tool-call-decision {
1066
1052
  flex: none;
1067
1053
  font-size: 11px;
@@ -1084,10 +1070,8 @@ export const STYLES = `
1084
1070
  }
1085
1071
 
1086
1072
  /* Display modes are pure visibility over one DOM shape, selected from the host
1087
- attribute rather than a value stamped on the card when it was built. That is
1088
- what lets a host flip data-tool-display and have every card already on screen
1089
- re-read it, the way data-answer-well behaves. Baking the structure per mode
1090
- meant the setting only reached cards created afterwards.
1073
+ attribute rather than a value stamped on the card at build time, so flipping
1074
+ data-tool-display re-styles cards already on screen. See ToolCallCard.
1091
1075
 
1092
1076
  Default (no attribute) is the full mode: arguments always visible, result
1093
1077
  behind the toggle. */
@@ -1184,20 +1168,19 @@ export const STYLES = `
1184
1168
  }
1185
1169
 
1186
1170
  /* Then the measurement corrects it. Which edges are held still belongs to the
1187
- host's layout rather than to placement -- a floating panel a host right-aligns
1188
- is anchored bottom-left, not bottom-right -- so the element measures the edges
1189
- that stay put and stamps them here as "<y>-<x>". Equal specificity to the
1190
- rules above, so source order is what lets the measured value win.
1191
-
1192
- These must not be written as [data-resize-anchor~="left"]: the stamped value
1193
- is one hyphenated token, and ~= matches whitespace-separated words, so such a
1194
- selector cannot ever match. It did not, which left the grip drawn at the
1195
- corner that moves while the cursor pointed along the right diagonal.
1196
-
1197
- Each rule also sets both sides of its axis rather than only the side it
1198
- moves. One that flipped a single way could not undo a placement guess that
1199
- had flipped the other, which put the grip on the anchored corner for an
1200
- embedded panel its host right-aligns. */
1171
+ host's layout, not to placement -- a floating panel a host right-aligns is
1172
+ anchored bottom-left -- so the element measures them and stamps a single
1173
+ hyphenated "<y>-<x>" token here. Equal specificity to the rules above, so
1174
+ source order is what lets the measured value win.
1175
+
1176
+ Two traps, both of which silently draw the grip on the corner that moves:
1177
+
1178
+ Never write these as [data-resize-anchor~="left"]. The stamped value is one
1179
+ hyphenated token and ~= matches whitespace-separated words, so it can never
1180
+ match.
1181
+
1182
+ Each rule must set both sides of its axis, not only the side it moves, or it
1183
+ cannot undo a placement guess that flipped the other way. */
1201
1184
  :host([data-resize-anchor$="-left"]) .resize-handle {
1202
1185
  left: auto;
1203
1186
  right: 0;
@@ -1258,10 +1241,9 @@ export const STYLES = `
1258
1241
  }
1259
1242
 
1260
1243
  /* ── Composer ───────────────────────────────────────────────────────────────
1261
- One surface owns the border, the background and the focus ring; the field and
1262
- its tool row sit inside it. The row used to be four siblings stretched to the
1263
- textarea's height, which gave a paperclip the same visual weight as the field
1264
- and turned Send into a full-height slab. */
1244
+ One surface owns the border, the background and the focus ring; the field
1245
+ and its tool row sit inside it, rather than being siblings stretched to the
1246
+ textarea's height. */
1265
1247
  .input-row {
1266
1248
  display: flex;
1267
1249
  padding: 12px;
@@ -1454,11 +1436,25 @@ export const STYLES = `
1454
1436
  display: contents;
1455
1437
  }
1456
1438
 
1439
+ /* The padding is the only separation the tray gets: its slot is display:
1440
+ contents, so the tray is a direct child of the .chat column, and that column
1441
+ sets no gap. The bottom value keeps a chip off the composer's top edge.
1442
+ The inline value here is only the default: the page placement overrides
1443
+ padding-inline further down to compute its reading-column gutter, and wins
1444
+ on specificity whichever way this declaration is written. Read the two
1445
+ together before changing either. */
1457
1446
  .attachment-tray {
1458
1447
  display: flex;
1459
1448
  flex-wrap: wrap;
1460
1449
  gap: 6px;
1461
- padding: 8px 12px 0;
1450
+ padding: 8px 12px;
1451
+ }
1452
+
1453
+ /* The tray sets the hidden property while empty, and an author display beats
1454
+ the UA stylesheet's rule for it, so without this the empty tray keeps
1455
+ laying out and its padding is permanent dead space above the composer. */
1456
+ .attachment-tray[hidden] {
1457
+ display: none;
1462
1458
  }
1463
1459
 
1464
1460
  .attachment-chips {
@@ -1494,15 +1490,28 @@ export const STYLES = `
1494
1490
  overflow-wrap: anywhere;
1495
1491
  }
1496
1492
 
1493
+ /* A chip carries its own text colour because it carries its own background.
1494
+ The same chip renders in two places with opposite inherited colours: the
1495
+ composer tray gives it the panel's, a sent user bubble gives it the user
1496
+ foreground, which is white on the stock light theme and near-invisible
1497
+ against the chip. Pairing the colour with the background it belongs to makes
1498
+ both placements read alike. Overridden below for an errored chip, which
1499
+ keeps its red. */
1497
1500
  .attachment-chip {
1498
1501
  display: inline-flex;
1499
1502
  align-items: center;
1500
1503
  gap: 6px;
1504
+ /* Without this the 100% below caps the content box, so the chip still
1505
+ overflows its container by its own padding and border. Invisible while a
1506
+ character cap kept names short; routine once the container is what bounds
1507
+ the name. */
1508
+ box-sizing: border-box;
1501
1509
  max-width: 100%;
1502
1510
  padding: 4px 8px;
1503
1511
  border: 1px solid var(--_border);
1504
1512
  border-radius: 999px;
1505
1513
  background: var(--_assistant-bg);
1514
+ color: var(--_text);
1506
1515
  font-size: 0.85em;
1507
1516
  position: relative;
1508
1517
  }
@@ -1512,11 +1521,31 @@ export const STYLES = `
1512
1521
  color: var(--_danger);
1513
1522
  }
1514
1523
 
1524
+ /* The type mark. Painted from currentColor, so it carries the chip's state
1525
+ with it and turns red along with an errored one; muted by opacity rather
1526
+ than a colour, which is what keeps that true. Sized from the chip's own text
1527
+ rather than --ag-ui-glyph-size, the way an icon holder's glyph takes the
1528
+ holder's size: the composer's 18px buttons would make a chip button-height. */
1529
+ .attachment-chip-icon {
1530
+ display: inline-flex;
1531
+ flex: none;
1532
+ opacity: 0.75;
1533
+ }
1534
+
1535
+ .attachment-chip-icon .glyph {
1536
+ width: 1.25em;
1537
+ height: 1.25em;
1538
+ }
1539
+
1540
+ /* No character cap: the chip is already max-width 100%, so its container is
1541
+ what bounds the name, and a fixed cap only truncated names the chip had room
1542
+ for. min-width: 0 is what lets the flex item shrink past its content, so the
1543
+ ellipsis appears at the container edge instead of the chip overflowing. */
1515
1544
  .attachment-chip-name {
1516
1545
  overflow: hidden;
1517
1546
  text-overflow: ellipsis;
1518
1547
  white-space: nowrap;
1519
- max-width: 14ch;
1548
+ min-width: 0;
1520
1549
  }
1521
1550
 
1522
1551
  .attachment-chip-size {
@@ -4,15 +4,13 @@ import { DEFAULT_UI_STRINGS, type UiStrings } from "./ui_strings.js";
4
4
  * A muted, collapsible "thinking" region for a reasoning model's streamed
5
5
  * chain-of-thought.
6
6
  *
7
- * Lives at the top of the current answer group (the turn container): it
8
- * opens expanded while the model reasons {@link stream} replaces its body with
9
- * the running reasoning buffer — and {@link collapse} folds it away once the
10
- * answer's first text token arrives, so the thoughts don't crowd the answer.
11
- * The header toggle lets the reader reopen it.
7
+ * Sits at the top of the current answer group. It opens expanded while the
8
+ * model reasons, {@link stream} replacing its body with the running buffer, and
9
+ * {@link collapse} folds it away once the answer's first text token arrives.
10
+ * The header toggle reopens it.
12
11
  *
13
- * Pure DOM (no framework); the host inserts {@link element} and themes it via
14
- * the `--ag-ui-*` custom properties or the `thoughts*` `part`s. All visible
15
- * chrome text is sourced from {@link UiStrings}.
12
+ * Pure DOM. The host inserts {@link element}; all visible chrome text comes
13
+ * from {@link UiStrings}.
16
14
  */
17
15
  export class ThoughtsBlock {
18
16
  /** The block's root element; insert this at the top of the answer group. */
@@ -30,8 +28,8 @@ export class ThoughtsBlock {
30
28
  this.element = document.createElement("div");
31
29
  this.element.className = "thoughts";
32
30
  this.element.setAttribute("part", "thoughts");
33
- // `data-streaming` lets CSS animate the header (e.g. a pulse) while the
34
- // model is still reasoning; dropped on collapse.
31
+ // Lets CSS animate the header while the model is still reasoning; dropped
32
+ // on collapse.
35
33
  this.element.setAttribute("data-streaming", "");
36
34
 
37
35
  this.#toggle = document.createElement("button");
@@ -63,9 +61,9 @@ export class ThoughtsBlock {
63
61
  }
64
62
 
65
63
  /**
66
- * Fold the region away — called when the answer's first text token arrives.
67
- * Idempotent (the per-token text handler calls it repeatedly) and flips the
68
- * header label from "thinking…" to the settled "Thoughts" affordance.
64
+ * Fold the region away when the answer's first text token arrives, flipping
65
+ * the header label to its settled form. Idempotent, since the per-token text
66
+ * handler calls it repeatedly.
69
67
  */
70
68
  collapse(): void {
71
69
  if (this.#collapsed) {
@@ -17,13 +17,14 @@ export interface ThreadDrawerCallbacks {
17
17
  /**
18
18
  * The chat-history drawer: a slide-over listing the user's threads (title,
19
19
  * relative time, preview), with select / new / rename / delete actions and an
20
- * empty state. Pure DOM in the spirit of {@link SkillsMenu} the host appends
20
+ * empty state. Pure DOM, like {@link SkillsMenu}: the host appends
21
21
  * {@link element}, toggles it, feeds rows via {@link setThreads}, and acts on
22
- * the callbacks. The drawer is a *view*: it does not mutate the store; after a
23
- * callback the host updates the store and calls {@link setThreads} to refresh.
22
+ * the callbacks.
24
23
  *
25
- * All visible text comes from {@link UiStrings}; {@link setStrings} re-localizes
26
- * a drawer the host built before its strings resolved.
24
+ * A view only it never mutates the store. After a callback the host updates
25
+ * the store and calls {@link setThreads} to refresh. All visible text comes
26
+ * from {@link UiStrings}; {@link setStrings} re-localizes a drawer built before
27
+ * its strings resolved.
27
28
  */
28
29
  export class ThreadDrawer {
29
30
  /** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
@@ -41,29 +41,24 @@ function formatPayload(text: string): string {
41
41
  /**
42
42
  * A live tool-call card for the chat transcript.
43
43
  *
44
- * Construction renders a status icon, the tool name, a `running…` pill, and the
45
- * card's body: an **arguments** region and a **result** region, each with its own
46
- * heading and its own `part`. {@link settle} fills in the result and flips the
47
- * pill. Both payloads are pretty-printed, and the two are never concatenated —
48
- * the previous compact layout ran `args: {...}` and the result together in one
49
- * `<pre>`, leaving no way to see where the call ended and the answer began.
44
+ * Construction renders a status icon, the tool name, a status pill, and a body
45
+ * of two separately-headed regions — arguments and result each with its own
46
+ * `part`. {@link settle} fills in the result and flips the pill. Both payloads
47
+ * are pretty-printed and never concatenated into one block.
50
48
  *
51
- * **The card renders one DOM shape in every display mode, and CSS decides what
52
- * shows.** That is what makes `data-tool-display` behave like `data-answer-well`
53
- * flip it on the host and every card already on screen re-reads it. Building a
54
- * different structure per mode meant only cards created *after* the change
55
- * picked it up, so the setting appeared not to work until the next conversation.
56
- * Visibility is selected from the host attribute rather than a value copied onto
57
- * the card at construction, for the same reason.
49
+ * The card renders one DOM shape in every display mode and lets CSS decide what
50
+ * shows, selecting visibility from the host attribute rather than a value
51
+ * copied onto the card at construction. That is what lets `data-tool-display`
52
+ * be flipped on the host and re-read by every card already on screen; building
53
+ * a different structure per mode would leave existing cards stale.
58
54
  *
59
- * The leading icon carries no text of its own: its glyph/spinner is drawn by
60
- * the shadow CSS keyed off the card's `data-status`, so a host themes it via
61
- * the `--ag-ui-tool-icon-*` custom properties (or the `tool-card-icon` part)
62
- * without the card reaching into the host stylesheet.
55
+ * The leading icon carries no text: the shadow CSS draws its glyph or spinner
56
+ * from the card's `data-status`, so a host themes it through the
57
+ * `--ag-ui-tool-icon-*` custom properties or the `tool-card-icon` part without
58
+ * the card reaching into the host stylesheet.
63
59
  *
64
- * Pure DOM (no framework); the host appends {@link element} into its shadow
65
- * root and themes it via the `--ag-ui-*` custom properties or the exposed
66
- * `tool-card*` `part`s. All visible text is sourced from {@link UiStrings}.
60
+ * Pure DOM. The host appends {@link element} into its shadow root; all visible
61
+ * text comes from {@link UiStrings}.
67
62
  */
68
63
  export class ToolCallCard {
69
64
  /** The card's root element; append this into the message list. */
@@ -97,9 +92,8 @@ export class ToolCallCard {
97
92
  head.className = "tool-call-head";
98
93
  head.setAttribute("part", "tool-card-head");
99
94
 
100
- // The leading status icon a spinner while pending, a check/cross/slash on
101
- // settle. Empty in the DOM: the shadow CSS draws it from `data-status`, so
102
- // the glyphs stay themeable (`--ag-ui-tool-icon-*`) and the spin is real.
95
+ // Left empty in the DOM on purpose: the shadow CSS draws the spinner or
96
+ // settled mark from `data-status`, keeping the glyphs themeable.
103
97
  const icon = document.createElement("span");
104
98
  icon.className = "tool-call-icon";
105
99
  icon.setAttribute("part", "tool-card-icon");
@@ -125,16 +119,15 @@ export class ToolCallCard {
125
119
 
126
120
  const argsSection = this.#section("args", strings.argumentsLabel);
127
121
  argsSection.body.textContent = JSON.stringify(args, null, 2);
128
- // A call with no arguments renders an empty object in a box of its own,
129
- // which is a frame around nothing. Drop the region instead.
122
+ // Drop the region rather than frame an empty object.
130
123
  argsSection.root.hidden = Object.keys(args).length === 0;
131
124
 
132
125
  const resultSection = this.#section("result", strings.resultLabel);
133
126
  this.#resultSection = resultSection.root;
134
127
  this.#resultLabel = resultSection.label;
135
128
  this.#resultBody = resultSection.body;
136
- // Nothing to show until `settle` supplies it; a pending card would
137
- // otherwise expand onto an empty region.
129
+ // Nothing to show until `settle` supplies it, or a pending card expands
130
+ // onto an empty region.
138
131
  resultSection.root.hidden = true;
139
132
 
140
133
  this.#toggle = document.createElement("button");
@@ -155,11 +148,8 @@ export class ToolCallCard {
155
148
 
156
149
  /**
157
150
  * Record that a human approved or declined this call, as a line in the card.
158
- *
159
- * Approval used to leave no trace at all: a declined call became a tool
160
- * result saying so, while an approved one simply ran, making the transcript
161
- * of a gated call byte-identical to one that was never gated. The prompt is
162
- * gone once answered, so this is where the decision lives.
151
+ * The prompt disappears once answered, so this is the only lasting trace that
152
+ * the call was gated at all.
163
153
  */
164
154
  recordDecision(kind: "approved" | "declined"): void {
165
155
  this.element.setAttribute("data-decision", kind);
@@ -5,14 +5,9 @@
5
5
  * or its `data-strings` JSON attribute) and the rest fall back to the English
6
6
  * defaults.
7
7
  *
8
- * A handful of values are **templates** carrying `{token}` placeholders the call
9
- * site fills in (e.g. `minutesAgo` → `"{n}m ago"`); the token names are noted on
10
- * each key. Translators keep the token verbatim.
11
- *
12
- * This module is one cohesive unit — the `UiStrings` shape, its `DEFAULT_UI_STRINGS`
13
- * constant-like backing, and the {@link mergeUiStrings} merge over those defaults —
14
- * so it bends the one-symbol-per-file rule the way `tool_call_card.ts` (class +
15
- * its types) and `route_map.ts` (factory + its types) already do.
8
+ * Some values are templates carrying `{token}` placeholders the call site fills
9
+ * in; the token names are noted on each key, and a translation must keep them
10
+ * verbatim.
16
11
  */
17
12
  export interface UiStrings {
18
13
  // ── Header ────────────────────────────────────────────────────────────────
@@ -27,10 +22,9 @@ export interface UiStrings {
27
22
  /** Expand affordance (the launcher, and the sidebar rail). */
28
23
  expand: string;
29
24
  /**
30
- * The launcher's label while unread answers are waiting behind it, with
31
- * `{count}` replaced by how many. It replaces {@link expand} rather than
32
- * appending to it, so a translation can order the two parts as its language
33
- * needs.
25
+ * The launcher's label while unread answers wait behind it. Token: `{count}`.
26
+ * Replaces {@link expand} rather than appending to it, so a translation can
27
+ * order the two parts as its language needs.
34
28
  */
35
29
  expandUnread: string;
36
30
  /** Built-in header theme toggle (light ⇄ dark). */
@@ -20,13 +20,13 @@ export interface VoiceInputOptions {
20
20
  * `MediaRecorder`, then POSTs the clip through a {@link TranscribeHandler} and
21
21
  * hands the transcript back via `onText`.
22
22
  *
23
- * Click to start recording (browser mic permission prompt), click again to stop
24
- * the clip is transcribed and dropped into the composer. The button reflects
25
- * its `idle` / `recording` / `transcribing` state on `data-state` for theming
26
- * and is exposed as `part="voice-button"`. A capture or transcription failure
27
- * returns the button to idle and surfaces the message on its tooltip.
23
+ * Click to start recording, which prompts for mic permission, and again to stop
24
+ * and transcribe. The button reflects its `idle` / `recording` / `transcribing`
25
+ * state on `data-state` for theming and is exposed as `part="voice-button"`; a
26
+ * capture or transcription failure returns it to idle and surfaces the message
27
+ * on its tooltip.
28
28
  *
29
- * Pure DOM (no framework); the host mounts {@link element} in the input row.
29
+ * Pure DOM. The host mounts {@link element} in the input row.
30
30
  */
31
31
  export class VoiceInput {
32
32
  /** The mic button; mount this in the composer. */
@@ -102,11 +102,10 @@ export class VoiceInput {
102
102
  }
103
103
 
104
104
  /**
105
- * Tear the control down the teardown path when the host element is removed
106
- * mid-recording. Stops any live `MediaRecorder`, releases the mic tracks (so
107
- * the browser's recording indicator clears), and suppresses the pending
108
- * transcription: a disconnected control must not fire `onText` back into a
109
- * detached element.
105
+ * Tear the control down, for a host element removed mid-recording. Stops any
106
+ * live `MediaRecorder`, releases the mic tracks so the browser's recording
107
+ * indicator clears, and suppresses the pending transcription — a
108
+ * disconnected control must not fire `onText` into a detached element.
110
109
  */
111
110
  dispose(): void {
112
111
  this.#disposed = true;
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION: string = "0.22.0";
1
+ export const VERSION: string = "0.23.1";