@artooi/ag-ui-web-component 0.44.0 → 0.46.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 (187) hide show
  1. package/CHANGELOG.md +182 -1
  2. package/README.md +245 -32
  3. package/dist/ag-ui-web-component.bundle.js +3696 -2798
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +4 -1
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +28 -8
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/read_flag.d.ts +11 -0
  10. package/dist/core/read_flag.d.ts.map +1 -0
  11. package/dist/core/read_json_attribute.d.ts +15 -0
  12. package/dist/core/read_json_attribute.d.ts.map +1 -0
  13. package/dist/index.js +5084 -3481
  14. package/dist/index.js.map +4 -4
  15. package/dist/tools/tool_catalog.d.ts +5 -0
  16. package/dist/tools/tool_catalog.d.ts.map +1 -1
  17. package/dist/tools/tool_dispatch.d.ts.map +1 -1
  18. package/dist/ui/charts/chart_styles.d.ts +2 -0
  19. package/dist/ui/charts/chart_styles.d.ts.map +1 -0
  20. package/dist/ui/composer/attachment_styles.d.ts +2 -0
  21. package/dist/ui/composer/attachment_styles.d.ts.map +1 -0
  22. package/dist/ui/composer/build_composer.d.ts +30 -0
  23. package/dist/ui/composer/build_composer.d.ts.map +1 -0
  24. package/dist/ui/composer/composer_styles.d.ts +2 -0
  25. package/dist/ui/composer/composer_styles.d.ts.map +1 -0
  26. package/dist/ui/composer/skill_styles.d.ts +2 -0
  27. package/dist/ui/composer/skill_styles.d.ts.map +1 -0
  28. package/dist/ui/history/history_styles.d.ts +2 -0
  29. package/dist/ui/history/history_styles.d.ts.map +1 -0
  30. package/dist/ui/interrupts/approval_styles.d.ts +2 -0
  31. package/dist/ui/interrupts/approval_styles.d.ts.map +1 -0
  32. package/dist/ui/interrupts/confirmation_styles.d.ts +2 -0
  33. package/dist/ui/interrupts/confirmation_styles.d.ts.map +1 -0
  34. package/dist/ui/interrupts/question_styles.d.ts +2 -0
  35. package/dist/ui/interrupts/question_styles.d.ts.map +1 -0
  36. package/dist/ui/placement/collapse_styles.d.ts +2 -0
  37. package/dist/ui/placement/collapse_styles.d.ts.map +1 -0
  38. package/dist/ui/placement/embedded_styles.d.ts +2 -0
  39. package/dist/ui/placement/embedded_styles.d.ts.map +1 -0
  40. package/dist/ui/placement/launcher_styles.d.ts +2 -0
  41. package/dist/ui/placement/launcher_styles.d.ts.map +1 -0
  42. package/dist/ui/placement/page_styles.d.ts +2 -0
  43. package/dist/ui/placement/page_styles.d.ts.map +1 -0
  44. package/dist/ui/placement/panel_placement.d.ts.map +1 -1
  45. package/dist/ui/placement/preset_styles.d.ts +2 -0
  46. package/dist/ui/placement/preset_styles.d.ts.map +1 -0
  47. package/dist/ui/placement/resize_styles.d.ts +2 -0
  48. package/dist/ui/placement/resize_styles.d.ts.map +1 -0
  49. package/dist/ui/placement/sidebar_styles.d.ts +2 -0
  50. package/dist/ui/placement/sidebar_styles.d.ts.map +1 -0
  51. package/dist/ui/placement/small_viewport_styles.d.ts +2 -0
  52. package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -0
  53. package/dist/ui/placement/surface_for.d.ts +74 -0
  54. package/dist/ui/placement/surface_for.d.ts.map +1 -0
  55. package/dist/ui/progress/run_notice_styles.d.ts +2 -0
  56. package/dist/ui/progress/run_notice_styles.d.ts.map +1 -0
  57. package/dist/ui/progress/thoughts_styles.d.ts +2 -0
  58. package/dist/ui/progress/thoughts_styles.d.ts.map +1 -0
  59. package/dist/ui/progress/tool_call_card.d.ts +8 -0
  60. package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
  61. package/dist/ui/progress/tool_group_styles.d.ts +2 -0
  62. package/dist/ui/progress/tool_group_styles.d.ts.map +1 -0
  63. package/dist/ui/progress/tool_step_group.d.ts +47 -0
  64. package/dist/ui/progress/tool_step_group.d.ts.map +1 -0
  65. package/dist/ui/progress/tool_step_styles.d.ts +2 -0
  66. package/dist/ui/progress/tool_step_styles.d.ts.map +1 -0
  67. package/dist/ui/shell/adopt_styles.d.ts +9 -4
  68. package/dist/ui/shell/adopt_styles.d.ts.map +1 -1
  69. package/dist/ui/shell/build_header.d.ts +40 -0
  70. package/dist/ui/shell/build_header.d.ts.map +1 -0
  71. package/dist/ui/shell/build_launcher.d.ts +20 -0
  72. package/dist/ui/shell/build_launcher.d.ts.map +1 -0
  73. package/dist/ui/shell/frame_styles.d.ts +2 -0
  74. package/dist/ui/shell/frame_styles.d.ts.map +1 -0
  75. package/dist/ui/shell/theme_styles.d.ts +2 -0
  76. package/dist/ui/shell/theme_styles.d.ts.map +1 -0
  77. package/dist/ui/shell/token_styles.d.ts +2 -0
  78. package/dist/ui/shell/token_styles.d.ts.map +1 -0
  79. package/dist/ui/styles.d.ts +1 -1
  80. package/dist/ui/styles.d.ts.map +1 -1
  81. package/dist/ui/themes/classic_styles.d.ts +2 -0
  82. package/dist/ui/themes/classic_styles.d.ts.map +1 -0
  83. package/dist/ui/themes/classic_tokens.d.ts +2 -0
  84. package/dist/ui/themes/classic_tokens.d.ts.map +1 -0
  85. package/dist/ui/themes/code_tokens.d.ts +2 -0
  86. package/dist/ui/themes/code_tokens.d.ts.map +1 -0
  87. package/dist/ui/themes/ledger_styles.d.ts +2 -0
  88. package/dist/ui/themes/ledger_styles.d.ts.map +1 -0
  89. package/dist/ui/themes/ledger_tokens.d.ts +2 -0
  90. package/dist/ui/themes/ledger_tokens.d.ts.map +1 -0
  91. package/dist/ui/themes/modern_styles.d.ts +2 -0
  92. package/dist/ui/themes/modern_styles.d.ts.map +1 -0
  93. package/dist/ui/themes/quiet_styles.d.ts +2 -0
  94. package/dist/ui/themes/quiet_styles.d.ts.map +1 -0
  95. package/dist/ui/themes/scheme_styles.d.ts +2 -0
  96. package/dist/ui/themes/scheme_styles.d.ts.map +1 -0
  97. package/dist/ui/themes/soft_styles.d.ts +2 -0
  98. package/dist/ui/themes/soft_styles.d.ts.map +1 -0
  99. package/dist/ui/themes/soft_tokens.d.ts +2 -0
  100. package/dist/ui/themes/soft_tokens.d.ts.map +1 -0
  101. package/dist/ui/themes/theme_hosts.d.ts +15 -0
  102. package/dist/ui/themes/theme_hosts.d.ts.map +1 -0
  103. package/dist/ui/transcript/attachment_chip_styles.d.ts +2 -0
  104. package/dist/ui/transcript/attachment_chip_styles.d.ts.map +1 -0
  105. package/dist/ui/transcript/build_transcript.d.ts +21 -0
  106. package/dist/ui/transcript/build_transcript.d.ts.map +1 -0
  107. package/dist/ui/transcript/greeting_styles.d.ts +2 -0
  108. package/dist/ui/transcript/greeting_styles.d.ts.map +1 -0
  109. package/dist/ui/transcript/groups_tool_steps.d.ts +13 -0
  110. package/dist/ui/transcript/groups_tool_steps.d.ts.map +1 -0
  111. package/dist/ui/transcript/message_action_styles.d.ts +2 -0
  112. package/dist/ui/transcript/message_action_styles.d.ts.map +1 -0
  113. package/dist/ui/transcript/message_styles.d.ts +2 -0
  114. package/dist/ui/transcript/message_styles.d.ts.map +1 -0
  115. package/dist/ui/transcript/transcript.d.ts +16 -1
  116. package/dist/ui/transcript/transcript.d.ts.map +1 -1
  117. package/dist/ui/transcript/transcript_styles.d.ts +2 -0
  118. package/dist/ui/transcript/transcript_styles.d.ts.map +1 -0
  119. package/dist/ui/ui_strings.d.ts +18 -0
  120. package/dist/ui/ui_strings.d.ts.map +1 -1
  121. package/package.json +2 -2
  122. package/src/constants.ts +4 -1
  123. package/src/core/ag_ui_chat.ts +164 -299
  124. package/src/core/read_flag.ts +13 -0
  125. package/src/core/read_json_attribute.ts +30 -0
  126. package/src/tools/tool_catalog.ts +23 -10
  127. package/src/tools/tool_dispatch.ts +26 -11
  128. package/src/ui/charts/chart_block.ts +9 -9
  129. package/src/ui/charts/chart_styles.ts +63 -0
  130. package/src/ui/composer/attachment_styles.ts +71 -0
  131. package/src/ui/composer/build_composer.ts +112 -0
  132. package/src/ui/composer/composer_styles.ts +174 -0
  133. package/src/ui/composer/skill_styles.ts +120 -0
  134. package/src/ui/history/history_styles.ts +484 -0
  135. package/src/ui/interrupts/approval_styles.ts +52 -0
  136. package/src/ui/interrupts/confirmation_styles.ts +114 -0
  137. package/src/ui/interrupts/question_styles.ts +73 -0
  138. package/src/ui/placement/collapse_styles.ts +137 -0
  139. package/src/ui/placement/embedded_styles.ts +14 -0
  140. package/src/ui/placement/launcher_styles.ts +189 -0
  141. package/src/ui/placement/page_styles.ts +47 -0
  142. package/src/ui/placement/panel_placement.ts +3 -12
  143. package/src/ui/placement/preset_styles.ts +33 -0
  144. package/src/ui/placement/resize_styles.ts +186 -0
  145. package/src/ui/placement/sidebar_styles.ts +88 -0
  146. package/src/ui/placement/small_viewport_styles.ts +56 -0
  147. package/src/ui/placement/surface_for.ts +135 -0
  148. package/src/ui/progress/run_notice_styles.ts +52 -0
  149. package/src/ui/progress/thoughts_styles.ts +65 -0
  150. package/src/ui/progress/tool_call_card.ts +11 -0
  151. package/src/ui/progress/tool_group_styles.ts +232 -0
  152. package/src/ui/progress/tool_step_group.ts +269 -0
  153. package/src/ui/progress/tool_step_styles.ts +472 -0
  154. package/src/ui/shell/adopt_styles.ts +11 -5
  155. package/src/ui/shell/build_header.ts +108 -0
  156. package/src/ui/shell/build_launcher.ts +50 -0
  157. package/src/ui/shell/frame_styles.ts +119 -0
  158. package/src/ui/shell/theme_styles.ts +11 -0
  159. package/src/ui/shell/token_styles.ts +406 -0
  160. package/src/ui/styles.ts +104 -3774
  161. package/src/ui/themes/classic_styles.ts +32 -0
  162. package/src/ui/themes/classic_tokens.ts +50 -0
  163. package/src/ui/themes/code_tokens.ts +21 -0
  164. package/src/ui/themes/ledger_styles.ts +168 -0
  165. package/src/ui/themes/ledger_tokens.ts +32 -0
  166. package/src/ui/themes/modern_styles.ts +80 -0
  167. package/src/ui/themes/quiet_styles.ts +115 -0
  168. package/src/ui/themes/scheme_styles.ts +17 -0
  169. package/src/ui/themes/soft_styles.ts +148 -0
  170. package/src/ui/themes/soft_tokens.ts +28 -0
  171. package/src/ui/themes/theme_hosts.ts +14 -0
  172. package/src/ui/transcript/attachment_chip_styles.ts +116 -0
  173. package/src/ui/transcript/build_transcript.ts +67 -0
  174. package/src/ui/transcript/greeting_styles.ts +171 -0
  175. package/src/ui/transcript/groups_tool_steps.ts +16 -0
  176. package/src/ui/transcript/message_action_styles.ts +155 -0
  177. package/src/ui/transcript/message_styles.ts +262 -0
  178. package/src/ui/transcript/transcript.ts +109 -3
  179. package/src/ui/transcript/transcript_styles.ts +140 -0
  180. package/src/ui/ui_strings.ts +31 -0
  181. package/src/version.ts +1 -1
  182. package/dist/ui/placement/is_collapsible_placement.d.ts +0 -18
  183. package/dist/ui/placement/is_collapsible_placement.d.ts.map +0 -1
  184. package/dist/ui/placement/is_draggable_placement.d.ts +0 -9
  185. package/dist/ui/placement/is_draggable_placement.d.ts.map +0 -1
  186. package/src/ui/placement/is_collapsible_placement.ts +0 -19
  187. package/src/ui/placement/is_draggable_placement.ts +0 -23
@@ -0,0 +1,472 @@
1
+ // Tool steps: the per-call card in each display mode, its status glyphs, and
2
+ // the sub-agent progress a delegating card holds. The skill palette's token
3
+ // mark sits among them only because no rule moved in the split.
4
+ export const TOOL_STEP_STYLES: string = `
5
+ .tool-call {
6
+ align-self: flex-start;
7
+ max-width: 80%;
8
+ box-sizing: border-box;
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 6px;
12
+ font-size: 12px;
13
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
14
+ padding: 8px 10px;
15
+ border-radius: 8px;
16
+ background: var(--_tool-bg);
17
+ border: 1px solid var(--_border);
18
+ color: var(--_tool-fg);
19
+ }
20
+
21
+ /* Wraps, because the name is the only flexible child and every badge the row
22
+ gains is taken out of it. An approved call adds a third fixed badge, which in
23
+ a sidebar-width panel left the name 37px and broke it mid-word. Badges drop to
24
+ their own row instead. */
25
+ .tool-call-head {
26
+ display: flex;
27
+ flex-wrap: wrap;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ gap: 8px;
31
+ }
32
+
33
+ .tool-call-name {
34
+ /* An auto basis, and a min-width floor rather than zero: the name may shrink,
35
+ but not below something readable, so wrapping moves a badge instead of
36
+ shredding a word. Breaking anywhere still applies to a name that cannot fit
37
+ on a line of its own, which is what keeps a long unbroken tool name inside
38
+ the card. */
39
+ flex: 1 1 auto;
40
+ min-width: 6ch;
41
+ font-weight: 600;
42
+ overflow-wrap: anywhere;
43
+ }
44
+
45
+ /* Leading status icon. Empty in the DOM — the glyph/spinner is drawn
46
+ here from the card's data-status, so it stays themeable. */
47
+ .tool-call-icon {
48
+ flex: none;
49
+ box-sizing: border-box;
50
+ display: inline-flex;
51
+ align-items: center;
52
+ justify-content: center;
53
+ width: 14px;
54
+ height: 14px;
55
+ font-size: 12px;
56
+ line-height: 1;
57
+ }
58
+
59
+ /* Pending: a real spinning ring. Speed is tunable; reduced motion stops it. */
60
+ .tool-call[data-status="pending"] .tool-call-icon {
61
+ border: 2px solid var(--_muted);
62
+ border-top-color: transparent;
63
+ border-radius: 50%;
64
+ animation: ag-ui-tool-spin var(--_tool-spin-duration) linear infinite;
65
+ }
66
+
67
+ @keyframes ag-ui-tool-spin {
68
+ to { transform: rotate(360deg); }
69
+ }
70
+
71
+ /* Deferred: no spinner, because nothing is spinning. A steady accent dot, since
72
+ the state is waiting-on-you rather than an outcome. */
73
+ .tool-call[data-status="deferred"] .tool-call-icon {
74
+ border-radius: 50%;
75
+ background: var(--_accent);
76
+ }
77
+
78
+ /* Settled: a themeable glyph coloured by outcome. */
79
+ .tool-call[data-status="done"] .tool-call-icon::before {
80
+ content: var(--_tool-icon-done);
81
+ color: var(--_success);
82
+ }
83
+
84
+ .tool-call[data-status="error"] .tool-call-icon::before {
85
+ content: var(--_tool-icon-error);
86
+ color: var(--_danger);
87
+ }
88
+
89
+ .tool-call[data-status="declined"] .tool-call-icon::before {
90
+ content: var(--_tool-icon-declined);
91
+ color: var(--_muted);
92
+ }
93
+
94
+ /* Not finished: muted like declined, since nothing failed, but its own glyph,
95
+ since nobody refused it either. */
96
+ .tool-call[data-status="interrupted"] .tool-call-icon::before {
97
+ content: var(--_tool-icon-interrupted);
98
+ color: var(--_muted);
99
+ }
100
+
101
+ @media (prefers-reduced-motion: reduce) {
102
+ .tool-call[data-status="pending"] .tool-call-icon {
103
+ animation: none;
104
+ }
105
+ }
106
+
107
+ /* Inline display mode: the lightest card. Drop the box chrome so the status row
108
+ reads as one line of the answer; the result toggle still expands below it. */
109
+ :host([data-tool-display="inline"]) .tool-call {
110
+ max-width: 100%;
111
+ background: transparent;
112
+ border: none;
113
+ padding: 2px 0;
114
+ gap: 2px;
115
+ }
116
+
117
+ .tool-call-status {
118
+ flex: none;
119
+ padding: 1px 8px;
120
+ border-radius: 999px;
121
+ font-size: 11px;
122
+ font-weight: 600;
123
+ background: rgba(127, 127, 127, 0.16);
124
+ color: var(--_muted);
125
+ }
126
+
127
+ .tool-call[data-status="deferred"] .tool-call-status {
128
+ color: var(--_accent);
129
+ }
130
+
131
+ .tool-call[data-status="done"] .tool-call-status {
132
+ color: var(--_success);
133
+ }
134
+
135
+ .tool-call[data-status="error"] .tool-call-status {
136
+ color: var(--_danger);
137
+ }
138
+
139
+ .tool-call[data-status="declined"] .tool-call-status,
140
+ .tool-call[data-status="interrupted"] .tool-call-status {
141
+ color: var(--_muted);
142
+ }
143
+
144
+ .tool-call-body {
145
+ display: flex;
146
+ flex-direction: column;
147
+ gap: 6px;
148
+ }
149
+
150
+ .tool-call-section {
151
+ display: flex;
152
+ flex-direction: column;
153
+ gap: 3px;
154
+ }
155
+
156
+ /* A call with no arguments drops the region rather than framing an empty
157
+ object, and a settled card drops the result region until it has one -- both
158
+ by setting the hidden property, and an author display beats the user-agent
159
+ rule for that attribute. Without this the region kept laying out: 42px of
160
+ card holding the ARGUMENTS heading over nothing, on every call the agent
161
+ made with no arguments, in the display mode that shows arguments by
162
+ default. */
163
+ .tool-call-section[hidden] {
164
+ display: none;
165
+ }
166
+
167
+ /* The heading that tells the two payloads apart. Without it the arguments and
168
+ the result were one run of text and a reader had to guess the boundary. */
169
+ .tool-call-section-label {
170
+ font-size: 10px;
171
+ font-weight: 700;
172
+ letter-spacing: 0.06em;
173
+ text-transform: uppercase;
174
+ color: var(--_muted);
175
+ }
176
+
177
+ .skill-item-token {
178
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
179
+ font-size: 0.92em;
180
+ color: var(--_accent);
181
+ margin-right: 6px;
182
+ }
183
+
184
+ /* The lasting record of a human decision on a gated call — without it an
185
+ approved call looks exactly like one that was never gated. */
186
+ .tool-call-decision {
187
+ flex: none;
188
+ font-size: 11px;
189
+ font-style: italic;
190
+ color: var(--_muted);
191
+ }
192
+
193
+ .tool-call-args,
194
+ .tool-call-result {
195
+ margin: 0;
196
+ padding: 6px 8px;
197
+ max-height: 160px;
198
+ overflow: auto;
199
+ background: var(--_bg);
200
+ border: 1px solid var(--_border);
201
+ border-radius: 6px;
202
+ white-space: pre-wrap;
203
+ word-break: break-word;
204
+ color: var(--_fg);
205
+ }
206
+
207
+ /* A region a host formatter took over, marked by the card. Preformatted
208
+ whitespace is what makes the built-in block read as written, and it is the one
209
+ thing a host cannot want: a table inherits it as mangled cell spacing, and a
210
+ sentence as line breaks nobody typed.
211
+
212
+ Whitespace only. The card's own face, frame, padding and scroll cap stay,
213
+ because the card is one visual object -- the head row and the status pill are
214
+ monospaced too -- and a region that dropped the family would be the only part
215
+ of it wearing a different one. A host that wants that restyles the
216
+ tool-card-result part, which does not need the formatter at all. */
217
+ .tool-call-args[data-formatted],
218
+ .tool-call-result[data-formatted] {
219
+ white-space: normal;
220
+ }
221
+
222
+ /* Display modes are pure visibility over one DOM shape, selected from the host
223
+ attribute rather than a value stamped on the card at build time, so flipping
224
+ data-tool-display re-styles cards already on screen. See ToolCallCard.
225
+
226
+ Default (no attribute) is the full mode: arguments always visible, result
227
+ behind the toggle. */
228
+ .tool-call[data-expanded="false"] .tool-call-section--result {
229
+ display: none;
230
+ }
231
+
232
+ /* Compact: one toggle over both regions, so a settled card is a single line
233
+ until asked. */
234
+ :host([data-tool-display="compact"]) .tool-call[data-expanded="false"] .tool-call-section {
235
+ display: none;
236
+ }
237
+
238
+ /* Inline: the result only; the call's arguments are noise at this density. */
239
+ :host([data-tool-display="inline"]) .tool-call .tool-call-section--args {
240
+ display: none;
241
+ }
242
+
243
+ /* Minimal: the status row and nothing else, so there is no toggle to press. */
244
+ :host([data-tool-display="minimal"]) .tool-call .tool-call-toggle,
245
+ :host([data-tool-display="minimal"]) .tool-call .tool-call-body {
246
+ display: none;
247
+ }
248
+
249
+ /* A pending card has no result yet, and in the modes where the arguments are
250
+ hidden too there is nothing behind the toggle. Hide the control rather than
251
+ offer one that expands onto nothing. A deferred card is the same, and its
252
+ arguments are shown unconditionally by the rules below. */
253
+ .tool-call[data-status="pending"] .tool-call-toggle,
254
+ .tool-call[data-status="deferred"] .tool-call-toggle,
255
+ :host([data-tool-display="inline"]) .tool-call[data-status="pending"] .tool-call-toggle {
256
+ display: none;
257
+ }
258
+
259
+ /* The approval prompt for a gated call, rendered inside that call's own card.
260
+ Empty on every card nobody is being asked about, so it collapses instead of
261
+ adding a gap to each one. */
262
+ .tool-call-approval:empty {
263
+ display: none;
264
+ }
265
+
266
+ .tool-call-approval {
267
+ margin-top: 8px;
268
+ }
269
+
270
+ /* A card that is asking a question shows what it is asking about, in every
271
+ display mode. Three gated calls of one tool ask the same words, so the
272
+ arguments are the only thing telling them apart, and a density setting must
273
+ not be able to hide the answer to "which one is this". */
274
+ :host([data-tool-display="minimal"]) .tool-call[data-status="deferred"] .tool-call-body {
275
+ display: flex;
276
+ }
277
+
278
+ /* The arguments region only, never every section: the result region carries the
279
+ hidden attribute until a result exists, and a display value here overrides it,
280
+ framing an empty RESULT heading under the question. */
281
+ :host([data-tool-display="compact"]) .tool-call[data-status="deferred"] .tool-call-section--args,
282
+ :host([data-tool-display="inline"]) .tool-call[data-status="deferred"] .tool-call-section--args {
283
+ display: flex;
284
+ }
285
+
286
+ /* A delegated sub-agent's progress, inside the card that delegated. Empty on
287
+ every card that delegated nothing, so it collapses rather than adding a gap
288
+ to each one -- the same shape the approval slot uses. */
289
+ .tool-call-subagent:empty {
290
+ display: none;
291
+ }
292
+
293
+ .tool-call-subagent {
294
+ display: flex;
295
+ flex-direction: column;
296
+ gap: 4px;
297
+ }
298
+
299
+ .subagent {
300
+ display: flex;
301
+ flex-direction: column;
302
+ gap: 4px;
303
+ min-width: 0;
304
+ }
305
+
306
+ /* The collapsed row is the status and the expander at once, which is what keeps
307
+ a ten-step child one row until somebody opens it. Full width and left-aligned,
308
+ because it is a line of the card rather than a button on it. */
309
+ .subagent-row {
310
+ display: flex;
311
+ align-items: center;
312
+ gap: 6px;
313
+ width: 100%;
314
+ box-sizing: border-box;
315
+ padding: 2px 0;
316
+ border: none;
317
+ background: none;
318
+ font: inherit;
319
+ text-align: left;
320
+ color: var(--_muted);
321
+ cursor: pointer;
322
+ }
323
+
324
+ /* Nothing behind the row yet -- a delegation that failed before calling
325
+ anything. Drop the affordances rather than offer a control that expands onto
326
+ an empty region, which is the refusal the card's own toggle already makes. */
327
+ .subagent-row:disabled {
328
+ cursor: default;
329
+ }
330
+
331
+ .subagent-row::after {
332
+ content: var(--_disclosure-collapsed);
333
+ flex: none;
334
+ margin-left: auto;
335
+ color: var(--_accent);
336
+ }
337
+
338
+ .subagent-row[aria-expanded="true"]::after {
339
+ content: var(--_disclosure-expanded);
340
+ }
341
+
342
+ .subagent-row:disabled::after {
343
+ display: none;
344
+ }
345
+
346
+ /* Empty in the DOM; the glyph is drawn here from the panel's data-phase, so a
347
+ host re-themes it through the same tool-icon custom properties the card uses. */
348
+ .subagent-icon {
349
+ flex: none;
350
+ box-sizing: border-box;
351
+ display: inline-flex;
352
+ align-items: center;
353
+ justify-content: center;
354
+ width: 10px;
355
+ height: 10px;
356
+ font-size: 10px;
357
+ line-height: 1;
358
+ }
359
+
360
+ /* Anything that is not a terminal phase is the child still working. Selected by
361
+ what it is not, so a phase this client has not heard of still spins rather
362
+ than rendering as a blank. */
363
+ .subagent[data-phase]:not([data-phase="finished"]):not([data-phase="failed"]) .subagent-icon {
364
+ border: 2px solid var(--_muted);
365
+ border-top-color: transparent;
366
+ border-radius: 50%;
367
+ animation: ag-ui-tool-spin var(--_tool-spin-duration) linear infinite;
368
+ }
369
+
370
+ .subagent[data-phase="finished"] .subagent-icon::before {
371
+ content: var(--_tool-icon-done);
372
+ color: var(--_success);
373
+ }
374
+
375
+ .subagent[data-phase="failed"] .subagent-icon::before {
376
+ content: var(--_tool-icon-error);
377
+ color: var(--_danger);
378
+ }
379
+
380
+ @media (prefers-reduced-motion: reduce) {
381
+ .subagent .subagent-icon {
382
+ animation: none;
383
+ }
384
+ }
385
+
386
+ /* The server's own pre-rendered line. Shrinks and wraps rather than pushing the
387
+ chevron out of the card, which is what a fixed-width sibling in a flex row
388
+ does to a panel at sidebar width. */
389
+ .subagent-status {
390
+ flex: 1 1 auto;
391
+ min-width: 0;
392
+ overflow-wrap: anywhere;
393
+ }
394
+
395
+ /* The child's own calls. Indented and ruled, so the nesting is visible without
396
+ a second card frame around it. */
397
+ .subagent-steps {
398
+ display: flex;
399
+ flex-direction: column;
400
+ gap: 2px;
401
+ margin-left: 4px;
402
+ padding-left: 10px;
403
+ border-left: 1px solid var(--_border);
404
+ }
405
+
406
+ .subagent-steps[hidden] {
407
+ display: none;
408
+ }
409
+
410
+ .subagent-step {
411
+ display: flex;
412
+ align-items: center;
413
+ gap: 6px;
414
+ min-width: 0;
415
+ color: var(--_muted);
416
+ }
417
+
418
+ .subagent-step-icon {
419
+ flex: none;
420
+ box-sizing: border-box;
421
+ display: inline-flex;
422
+ align-items: center;
423
+ justify-content: center;
424
+ width: 8px;
425
+ height: 8px;
426
+ font-size: 9px;
427
+ line-height: 1;
428
+ }
429
+
430
+ /* No outcome yet: the wire says null while the call is in flight, and the
431
+ absence of the attribute is how that arrives here. A hollow ring, not a
432
+ spinner -- several can be on screen at once and the row above already spins. */
433
+ .subagent-step:not([data-ok]) .subagent-step-icon {
434
+ border: 1px solid var(--_muted);
435
+ border-radius: 50%;
436
+ }
437
+
438
+ .subagent-step[data-ok="true"] .subagent-step-icon::before {
439
+ content: var(--_tool-icon-done);
440
+ color: var(--_success);
441
+ }
442
+
443
+ .subagent-step[data-ok="false"] .subagent-step-icon::before {
444
+ content: var(--_tool-icon-error);
445
+ color: var(--_danger);
446
+ }
447
+
448
+ .subagent-step-name {
449
+ flex: 1 1 auto;
450
+ min-width: 0;
451
+ overflow-wrap: anywhere;
452
+ }
453
+
454
+ .tool-call-toggle {
455
+ align-self: flex-start;
456
+ border: none;
457
+ padding: 0;
458
+ background: none;
459
+ font: inherit;
460
+ font-weight: 600;
461
+ color: var(--_accent);
462
+ cursor: pointer;
463
+ }
464
+
465
+ .tool-call-toggle::before {
466
+ content: var(--_disclosure-collapsed) " ";
467
+ }
468
+
469
+ .tool-call-toggle[aria-expanded="true"]::before {
470
+ content: var(--_disclosure-expanded) " ";
471
+ }
472
+ `;
@@ -16,13 +16,19 @@ import { STYLES } from "../styles.js";
16
16
  * Per instance is no worse than the `<style>` element it replaces, which also
17
17
  * parsed once per mount.
18
18
  *
19
+ * `extra` is the element's `extraStyles`, adopted after the built-in sheet so
20
+ * a host's rules win at equal specificity. They are constructed sheets too, so
21
+ * they inherit the same CSP immunity.
22
+ *
19
23
  * No fallback: constructible `CSSStyleSheet` is Chrome 73, Firefox 101 and
20
- * Safari 16.4, all below this package's declared Safari 17 runtime target. A
21
- * guard here would be code no supported browser can reach, and the only way
22
- * to keep it would be to exempt it from the coverage gate.
24
+ * Safari 16.4, all below this package's browser floor (Chrome 123, Firefox
25
+ * 120, Safari 17.5). A guard here would be code no supported browser can
26
+ * reach, and the only way to keep it would be to exempt it from the coverage
27
+ * gate.
23
28
  */
24
- export function adoptStyles(root: ShadowRoot): void {
29
+ export function adoptStyles(root: ShadowRoot, extra: readonly CSSStyleSheet[] = []): CSSStyleSheet {
25
30
  const sheet = new CSSStyleSheet();
26
31
  sheet.replaceSync(STYLES);
27
- root.adoptedStyleSheets = [sheet];
32
+ root.adoptedStyleSheets = [sheet, ...extra];
33
+ return sheet;
28
34
  }
@@ -0,0 +1,108 @@
1
+ import type { UiStrings } from "../ui_strings.js";
2
+ import { headerButton } from "./header_button.js";
3
+ import { iconElement } from "./icon_element.js";
4
+
5
+ /** What {@link buildHeader} needs from the element that owns the header. */
6
+ export interface HeaderOptions {
7
+ /** The element itself, asked whether a host slotted an icon. */
8
+ readonly host: HTMLElement;
9
+ /** The title node, kept by the element so a later `title-text` can update it. */
10
+ readonly title: HTMLElement;
11
+ readonly titleText: string;
12
+ readonly iconUrl: string | null;
13
+ readonly strings: UiStrings;
14
+ /** Whether the server indexes runs, so there is anything to continue. */
15
+ readonly withCheckpoints: boolean;
16
+ /** The opt-in theme toggle, or `null` when the host did not ask for one. */
17
+ readonly themeToggle: HTMLButtonElement | null;
18
+ readonly signal: AbortSignal;
19
+ readonly onHistory: () => void;
20
+ readonly onCheckpoints: () => void;
21
+ readonly onNewChat: () => void;
22
+ readonly onCollapse: () => void;
23
+ readonly onToggleTheme: () => void;
24
+ }
25
+
26
+ /** The header, and the one control the element still needs a handle on. */
27
+ export interface BuiltHeader {
28
+ readonly header: HTMLElement;
29
+ /**
30
+ * The checkpoints button, built whether or not it is shown: the click-away
31
+ * handler for the checkpoints popover has to exclude it either way.
32
+ */
33
+ readonly checkpoints: HTMLButtonElement;
34
+ }
35
+
36
+ /**
37
+ * Build the panel's title bar: the optional icon, the title, the host's
38
+ * header-actions slot, and the controls.
39
+ *
40
+ * Every control calls back into the element's public methods, so a host
41
+ * chrome driving them imperatively takes exactly the path the built-in button
42
+ * takes.
43
+ */
44
+ export function buildHeader(options: HeaderOptions): BuiltHeader {
45
+ const { strings } = options;
46
+ const header = document.createElement("div");
47
+ header.className = "header";
48
+ header.setAttribute("part", "header");
49
+
50
+ const title = options.title;
51
+ title.className = "header-title";
52
+ title.setAttribute("part", "title");
53
+ title.textContent = options.titleText;
54
+
55
+ // Optional header icon: a slot (any markup) with a `data-icon-url` <img>
56
+ // fallback. Rendered only when one of the two is provided, so the header has
57
+ // no phantom gap otherwise.
58
+ if (options.host.querySelector('[slot="icon"]') !== null || options.iconUrl !== null) {
59
+ header.append(iconElement("icon", "icon", null, options.iconUrl));
60
+ }
61
+
62
+ // A coarse slot for host-provided header actions, between title and controls.
63
+ const headerActions = document.createElement("slot");
64
+ headerActions.name = "header-actions";
65
+
66
+ const controls = document.createElement("div");
67
+ controls.className = "header-controls";
68
+ controls.setAttribute("part", "header-controls");
69
+
70
+ const history = headerButton("history", strings.chatHistory, "☰");
71
+ history.addEventListener("click", options.onHistory);
72
+
73
+ // ↺ rather than ⭯: the same idea in a glyph that has a font behind it in
74
+ // every browser. The obscure one rendered as an unreadable mark at 14px, and a
75
+ // header control nobody can name is one nobody presses.
76
+ const checkpoints = headerButton("checkpoints", strings.checkpoints, "↺");
77
+ checkpoints.addEventListener("click", options.onCheckpoints);
78
+
79
+ const newChat = headerButton("new", strings.newChat, "✚");
80
+ newChat.addEventListener("click", options.onNewChat);
81
+
82
+ const collapse = headerButton("collapse", strings.collapse, "—");
83
+ collapse.addEventListener("click", options.onCollapse);
84
+
85
+ // Only offered when the server actually indexes runs — without
86
+ // `data-runs-url` there is nothing to continue and the button would open
87
+ // a permanently empty panel.
88
+ if (options.withCheckpoints) {
89
+ controls.append(history, checkpoints, newChat);
90
+ } else {
91
+ controls.append(history, newChat);
92
+ }
93
+ // Optional built-in theme toggle: off unless the host opts in, so
94
+ // it never competes with a host-supplied switch in `slot="header-actions"`.
95
+ const toggle = options.themeToggle;
96
+ if (toggle !== null) {
97
+ toggle.type = "button";
98
+ toggle.className = "header-btn header-btn--theme";
99
+ toggle.setAttribute("part", "header-button theme-toggle");
100
+ toggle.title = strings.toggleTheme;
101
+ toggle.setAttribute("aria-label", strings.toggleTheme);
102
+ toggle.addEventListener("click", options.onToggleTheme, { signal: options.signal });
103
+ controls.append(toggle);
104
+ }
105
+ controls.append(collapse);
106
+ header.append(title, headerActions, controls);
107
+ return { header, checkpoints };
108
+ }
@@ -0,0 +1,50 @@
1
+ import { ICON_LAUNCHER } from "../../constants.js";
2
+ import type { UiStrings } from "../ui_strings.js";
3
+ import { iconElement } from "./icon_element.js";
4
+
5
+ /** The launcher's parts, owned by the element, and what fills them. */
6
+ export interface LauncherOptions {
7
+ readonly launcher: HTMLButtonElement;
8
+ readonly badge: HTMLElement;
9
+ readonly railLabel: HTMLElement;
10
+ readonly titleText: string;
11
+ readonly iconUrl: string | null;
12
+ readonly strings: UiStrings;
13
+ readonly signal: AbortSignal;
14
+ readonly onOpen: () => void;
15
+ }
16
+
17
+ /**
18
+ * Build what a collapsed widget shrinks to: a round floating button, or the
19
+ * slim edge rail under `placement="sidebar"` -- one element, shaped by the
20
+ * stylesheet. The element mounts it as a sibling of the panel, so it survives
21
+ * the panel being hidden.
22
+ */
23
+ export function buildLauncher(options: LauncherOptions): void {
24
+ const { launcher, badge, railLabel } = options;
25
+ launcher.className = "launcher";
26
+ launcher.type = "button";
27
+ launcher.setAttribute("part", "launcher");
28
+ launcher.setAttribute("aria-label", options.strings.expand);
29
+ badge.className = "launcher-badge";
30
+ badge.setAttribute("part", "launcher-badge");
31
+ // The count is announced through the launcher's own label, so the badge is
32
+ // decoration to a screen reader rather than a second, context-free number.
33
+ badge.setAttribute("aria-hidden", "true");
34
+ badge.hidden = true;
35
+ // Only the edge rail shows this. A full-height column carrying one small
36
+ // icon reads as a coloured stripe rather than a way back into a
37
+ // conversation -- it is the widest collapsed state there is and the one
38
+ // that says least about itself. Written here and hidden in CSS everywhere
39
+ // else, because the launcher is one element shaped by placement.
40
+ railLabel.className = "rail-label";
41
+ railLabel.setAttribute("part", "rail-label");
42
+ railLabel.setAttribute("aria-hidden", "true");
43
+ railLabel.textContent = options.titleText;
44
+ launcher.replaceChildren(
45
+ iconElement("launcher", "launcher-icon", ICON_LAUNCHER, options.iconUrl),
46
+ railLabel,
47
+ badge,
48
+ );
49
+ launcher.addEventListener("click", options.onOpen, { signal: options.signal });
50
+ }