@runtypelabs/persona 4.7.0 → 4.9.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 (185) hide show
  1. package/README.md +1 -0
  2. package/dist/animations/glyph-cycle.cjs +2 -2
  3. package/dist/animations/glyph-cycle.d.cts +1 -1
  4. package/dist/animations/glyph-cycle.d.ts +1 -1
  5. package/dist/animations/glyph-cycle.js +2 -2
  6. package/dist/animations/{types-CSmiKRVa.d.cts → types-4ROVJ1gA.d.cts} +85 -3
  7. package/dist/animations/{types-CSmiKRVa.d.ts → types-4ROVJ1gA.d.ts} +85 -3
  8. package/dist/animations/wipe.cjs +2 -2
  9. package/dist/animations/wipe.d.cts +1 -1
  10. package/dist/animations/wipe.d.ts +1 -1
  11. package/dist/chunk-5EIIHQLQ.js +1 -0
  12. package/dist/chunk-IO5VVUKP.js +3 -0
  13. package/dist/chunk-IPVK3KOM.js +1 -0
  14. package/dist/chunk-UPO4GUFC.js +1 -0
  15. package/dist/codegen.cjs +13 -13
  16. package/dist/codegen.js +14 -14
  17. package/dist/context-mentions-7S5KVUTG.js +169 -0
  18. package/dist/context-mentions-inline-TTCN7ZM2.js +4 -0
  19. package/dist/context-mentions-inline.cjs +4 -0
  20. package/dist/context-mentions-inline.d.cts +203 -0
  21. package/dist/context-mentions-inline.d.ts +203 -0
  22. package/dist/context-mentions-inline.js +4 -0
  23. package/dist/context-mentions.cjs +295 -0
  24. package/dist/context-mentions.d.cts +7025 -0
  25. package/dist/context-mentions.d.ts +7025 -0
  26. package/dist/context-mentions.js +295 -0
  27. package/dist/index.cjs +99 -68
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +1507 -182
  30. package/dist/index.d.ts +1507 -182
  31. package/dist/index.global.js +88 -56
  32. package/dist/index.global.js.map +1 -1
  33. package/dist/index.js +99 -68
  34. package/dist/index.js.map +1 -1
  35. package/dist/install.global.js +1 -1
  36. package/dist/install.global.js.map +1 -1
  37. package/dist/launcher.global.js +3 -2
  38. package/dist/launcher.global.js.map +1 -1
  39. package/dist/markdown-parsers.js +24 -24
  40. package/dist/plugin-kit.cjs +1 -1
  41. package/dist/plugin-kit.d.cts +17 -0
  42. package/dist/plugin-kit.d.ts +17 -0
  43. package/dist/plugin-kit.js +1 -1
  44. package/dist/runtype-tts-entry-UJAEF7NZ.js +1 -0
  45. package/dist/runtype-tts.js +1 -1
  46. package/dist/session-reconnect-JKIJBHS5.js +1 -0
  47. package/dist/smart-dom-reader.cjs +18 -17
  48. package/dist/smart-dom-reader.d.cts +1260 -16
  49. package/dist/smart-dom-reader.d.ts +1260 -16
  50. package/dist/smart-dom-reader.js +18 -17
  51. package/dist/testing.cjs +3 -3
  52. package/dist/testing.js +3 -3
  53. package/dist/theme-editor-preview.cjs +260 -58
  54. package/dist/theme-editor-preview.d.cts +1234 -15
  55. package/dist/theme-editor-preview.d.ts +1234 -15
  56. package/dist/theme-editor-preview.js +87 -58
  57. package/dist/theme-editor.cjs +10 -10
  58. package/dist/theme-editor.d.cts +1226 -15
  59. package/dist/theme-editor.d.ts +1226 -15
  60. package/dist/theme-editor.js +10 -10
  61. package/dist/theme-reference.cjs +1 -1
  62. package/dist/theme-reference.d.cts +76 -0
  63. package/dist/theme-reference.d.ts +76 -0
  64. package/dist/theme-reference.js +1 -1
  65. package/dist/voice-worklet-player.cjs +2 -2
  66. package/dist/voice-worklet-player.js +2 -2
  67. package/dist/webmcp-polyfill.js +2 -2
  68. package/dist/widget.css +1 -1
  69. package/package.json +16 -5
  70. package/src/artifacts-session.test.ts +178 -0
  71. package/src/client.test.ts +255 -1
  72. package/src/client.ts +157 -60
  73. package/src/components/artifact-card.test.ts +333 -0
  74. package/src/components/artifact-card.ts +75 -28
  75. package/src/components/artifact-inline.test.ts +1328 -0
  76. package/src/components/artifact-inline.ts +920 -0
  77. package/src/components/artifact-pane.test.ts +1042 -0
  78. package/src/components/artifact-pane.ts +440 -131
  79. package/src/components/artifact-preview.test.ts +1155 -0
  80. package/src/components/artifact-preview.ts +994 -0
  81. package/src/components/composer-parts.ts +3 -12
  82. package/src/components/context-mention-button.test.ts +70 -0
  83. package/src/components/context-mention-button.ts +82 -0
  84. package/src/components/context-mention-chip.ts +134 -0
  85. package/src/components/context-mention-menu.test.ts +508 -0
  86. package/src/components/context-mention-menu.ts +0 -0
  87. package/src/components/message-bubble.test.ts +175 -0
  88. package/src/components/message-bubble.ts +177 -19
  89. package/src/components/panel.ts +7 -10
  90. package/src/components/pill-composer-builder.test.ts +6 -2
  91. package/src/components/pill-composer-builder.ts +6 -6
  92. package/src/components/reasoning-bubble.ts +1 -13
  93. package/src/components/registry.ts +38 -3
  94. package/src/components/tool-bubble.ts +1 -13
  95. package/src/context-mentions-bundle.test.ts +163 -0
  96. package/src/context-mentions-entry.ts +185 -0
  97. package/src/context-mentions-inline-entry.test.ts +136 -0
  98. package/src/context-mentions-inline-entry.ts +226 -0
  99. package/src/context-mentions-inline-loader.test.ts +30 -0
  100. package/src/context-mentions-inline-loader.ts +36 -0
  101. package/src/context-mentions-inline.ts +15 -0
  102. package/src/context-mentions-loader.ts +32 -0
  103. package/src/context-mentions.ts +16 -0
  104. package/src/defaults.ts +1 -0
  105. package/src/generated/runtype-openapi-contract.ts +55 -3
  106. package/src/index-core.ts +46 -1
  107. package/src/index-global.ts +51 -0
  108. package/src/index.ts +8 -0
  109. package/src/markdown-parsers-loader.test.ts +158 -0
  110. package/src/markdown-parsers-loader.ts +83 -27
  111. package/src/plugin-kit.test.ts +40 -0
  112. package/src/plugin-kit.ts +39 -5
  113. package/src/runtime/host-layout.test.ts +163 -0
  114. package/src/runtime/host-layout.ts +110 -7
  115. package/src/runtime/init.ts +18 -61
  116. package/src/session.mentions.test.ts +175 -0
  117. package/src/session.test.ts +52 -4
  118. package/src/session.ts +197 -27
  119. package/src/smart-dom-reader.test.ts +129 -2
  120. package/src/smart-dom-reader.ts +127 -1
  121. package/src/styles/context-mention-menu-css.ts +176 -0
  122. package/src/styles/widget.css +1002 -152
  123. package/src/theme-editor/preview.ts +2 -0
  124. package/src/theme-editor/sections.test.ts +26 -1
  125. package/src/theme-editor/sections.ts +13 -5
  126. package/src/theme-reference.ts +2 -2
  127. package/src/tool-call-display-defaults.test.ts +1 -0
  128. package/src/types/theme.ts +79 -0
  129. package/src/types.ts +1041 -17
  130. package/src/ui.artifact-pane-gating.test.ts +636 -0
  131. package/src/ui.component-directive.test.ts +104 -0
  132. package/src/ui.composer-bar.test.ts +60 -2
  133. package/src/ui.detached-panel.test.ts +1049 -0
  134. package/src/ui.mention-submit.test.ts +235 -0
  135. package/src/ui.tool-display.test.ts +51 -0
  136. package/src/ui.ts +998 -159
  137. package/src/utils/artifact-custom-actions.ts +128 -0
  138. package/src/utils/artifact-display.test.ts +42 -0
  139. package/src/utils/artifact-display.ts +84 -0
  140. package/src/utils/artifact-file.test.ts +116 -0
  141. package/src/utils/artifact-file.ts +117 -0
  142. package/src/utils/artifact-gate.test.ts +112 -5
  143. package/src/utils/artifact-gate.ts +39 -14
  144. package/src/utils/artifact-loading-status.ts +55 -0
  145. package/src/utils/artifact-status-label.ts +190 -0
  146. package/src/utils/buttons.ts +7 -1
  147. package/src/utils/chunk-loader.test.ts +97 -0
  148. package/src/utils/chunk-loader.ts +88 -0
  149. package/src/utils/code-highlight.test.ts +186 -0
  150. package/src/utils/code-highlight.ts +400 -0
  151. package/src/utils/composer-contenteditable.test.ts +507 -0
  152. package/src/utils/composer-contenteditable.ts +626 -0
  153. package/src/utils/composer-document.test.ts +280 -0
  154. package/src/utils/composer-document.ts +293 -0
  155. package/src/utils/composer-history.test.ts +35 -7
  156. package/src/utils/composer-history.ts +30 -20
  157. package/src/utils/composer-input.ts +159 -0
  158. package/src/utils/context-mention-controller.test.ts +1215 -0
  159. package/src/utils/context-mention-controller.ts +1186 -0
  160. package/src/utils/context-mention-manager.test.ts +422 -0
  161. package/src/utils/context-mention-manager.ts +410 -0
  162. package/src/utils/context-mention-orchestrator.test.ts +538 -0
  163. package/src/utils/context-mention-orchestrator.ts +348 -0
  164. package/src/utils/icons.ts +2 -0
  165. package/src/utils/live-region.test.ts +108 -0
  166. package/src/utils/live-region.ts +94 -0
  167. package/src/utils/mention-channels.ts +63 -0
  168. package/src/utils/mention-llm-format.test.ts +91 -0
  169. package/src/utils/mention-llm-format.ts +79 -0
  170. package/src/utils/mention-matcher.test.ts +86 -0
  171. package/src/utils/mention-matcher.ts +221 -0
  172. package/src/utils/mention-token.ts +72 -0
  173. package/src/utils/mention-trigger.test.ts +155 -0
  174. package/src/utils/mention-trigger.ts +156 -0
  175. package/src/utils/roving-tablist.test.ts +152 -0
  176. package/src/utils/roving-tablist.ts +111 -0
  177. package/src/utils/spinner.ts +45 -0
  178. package/src/utils/theme.test.ts +76 -4
  179. package/src/utils/theme.ts +7 -0
  180. package/src/utils/tokens.ts +118 -11
  181. package/src/utils/tool-loading-animation.test.ts +32 -0
  182. package/src/utils/tool-loading-animation.ts +24 -0
  183. package/dist/chunk-DFBSCFYN.js +0 -1
  184. package/dist/runtype-tts-entry-HFUV2UF7.js +0 -1
  185. package/dist/session-reconnect-U77QFUR7.js +0 -1
@@ -169,6 +169,31 @@ describe('theme utils', () => {
169
169
  expect(customVars['--persona-header-action-icon-fg']).toBe('#9ca3af');
170
170
  });
171
171
 
172
+ it('maps button.ghost tokens to the composer ghost icon-button CSS variables', () => {
173
+ const cssVars = themeToCssVariables(createTheme());
174
+
175
+ // The composer's transparent icon buttons (.persona-mention-button /
176
+ // .persona-attachment-button) read these instead of hardcoded inline styles.
177
+ expect(cssVars['--persona-button-ghost-bg']).toBe('transparent');
178
+ expect(cssVars['--persona-button-ghost-fg']).toBe('#111827'); // semantic.text → gray.900
179
+ expect(cssVars['--persona-button-ghost-radius']).toBe('0.375rem'); // radius.md
180
+ expect(cssVars['--persona-button-ghost-hover-bg']).toBe('rgba(0, 0, 0, 0.05)');
181
+
182
+ const custom = createTheme({
183
+ components: {
184
+ button: {
185
+ ghost: {
186
+ foreground: 'palette.colors.accent.500',
187
+ hoverBackground: 'palette.colors.gray.100',
188
+ },
189
+ },
190
+ },
191
+ } as any);
192
+ const customVars = themeToCssVariables(custom);
193
+ expect(customVars['--persona-button-ghost-fg']).toBe('#06b6d4'); // accent.500
194
+ expect(customVars['--persona-button-ghost-hover-bg']).toBe('#f3f4f6'); // gray.100
195
+ });
196
+
172
197
  it('defaults artifact pane fill from semantic container and resolves toolbar background token refs', () => {
173
198
  const theme = createTheme();
174
199
  const cssVars = themeToCssVariables(theme);
@@ -192,6 +217,38 @@ describe('theme utils', () => {
192
217
  expect(surfaceVars['--persona-artifact-toolbar-bg']).toBe('#f9fafb');
193
218
  });
194
219
 
220
+ it('maps artifact inline chrome tokens to dedicated CSS variables (with semantic refs)', () => {
221
+ const theme = createTheme({
222
+ components: {
223
+ artifact: {
224
+ inline: {
225
+ background: 'semantic.colors.surface',
226
+ border: '1px solid #e5e7eb',
227
+ borderRadius: '12px',
228
+ chromeBackground: 'semantic.colors.container',
229
+ chromeBorder: '#e5e7eb',
230
+ titleColor: 'palette.colors.gray.900',
231
+ mutedColor: 'palette.colors.gray.500',
232
+ frameHeight: '400px',
233
+ },
234
+ },
235
+ },
236
+ } as any);
237
+
238
+ const cssVars = themeToCssVariables(theme);
239
+
240
+ // semantic.* refs resolve to concrete colors; plain values pass through.
241
+ // surface and container both default to gray.50.
242
+ expect(cssVars['--persona-artifact-inline-bg']).toBe('#f9fafb');
243
+ expect(cssVars['--persona-artifact-inline-border']).toBe('1px solid #e5e7eb');
244
+ expect(cssVars['--persona-artifact-inline-radius']).toBe('12px');
245
+ expect(cssVars['--persona-artifact-inline-chrome-bg']).toBe('#f9fafb');
246
+ expect(cssVars['--persona-artifact-inline-chrome-border']).toBe('#e5e7eb');
247
+ expect(cssVars['--persona-artifact-inline-title-color']).toBe('#111827');
248
+ expect(cssVars['--persona-artifact-inline-muted-color']).toBe('#6b7280');
249
+ expect(cssVars['--persona-artifact-inline-frame-height']).toBe('400px');
250
+ });
251
+
195
252
  it('maps component bubble shadow tokens to consumer CSS variables', () => {
196
253
  const cfg = {
197
254
  colorScheme: 'light' as const,
@@ -280,12 +337,11 @@ describe('theme utils', () => {
280
337
  );
281
338
  });
282
339
 
283
- it('falls back to the legacy intro-card shadow when no token is set', () => {
340
+ it('defaults the intro card to flat (transparent, no shadow) when no token is set', () => {
284
341
  const theme = createTheme({});
285
342
  const cssVars = themeToCssVariables(theme);
286
- expect(cssVars['--persona-intro-card-shadow']).toBe(
287
- '0 5px 15px rgba(15, 23, 42, 0.08)'
288
- );
343
+ expect(cssVars['--persona-intro-card-shadow']).toBe('none');
344
+ expect(cssVars['--persona-intro-card-bg']).toBe('transparent');
289
345
  });
290
346
 
291
347
  it('drives --persona-tool-bubble-shadow from the theme token (config.toolCall.shadow is applied inline on the bubble, not the root var)', () => {
@@ -305,4 +361,20 @@ describe('theme utils', () => {
305
361
  '0 1px 2px rgba(255,0,0,0.5)'
306
362
  );
307
363
  });
364
+
365
+ it('stamps the resolved color scheme on the root so scheme-scoped CSS (syntax palette) follows the widget, not the OS', () => {
366
+ const el = document.createElement('div');
367
+ applyThemeVariables(el, { colorScheme: 'light' });
368
+ expect(el.getAttribute('data-persona-color-scheme')).toBe('light');
369
+ applyThemeVariables(el, { colorScheme: 'dark' });
370
+ expect(el.getAttribute('data-persona-color-scheme')).toBe('dark');
371
+ });
372
+
373
+ it('maps components.code.background to --persona-code-bg', () => {
374
+ const theme = createTheme({
375
+ components: { code: { background: '#fafafa' } },
376
+ });
377
+ const cssVars = themeToCssVariables(theme);
378
+ expect(cssVars['--persona-code-bg']).toBe('#fafafa');
379
+ });
308
380
  });
@@ -194,6 +194,13 @@ export const applyThemeVariables = (
194
194
  for (const [name, value] of Object.entries(cssVars)) {
195
195
  element.style.setProperty(name, value);
196
196
  }
197
+
198
+ // Stamp the resolved scheme so stylesheet rules can key scheme-specific
199
+ // defaults (e.g. the syntax-highlight palette) off the widget's own color
200
+ // scheme instead of the OS prefers-color-scheme, which diverges whenever a
201
+ // host pins colorScheme. Re-applied on every theme application, so live
202
+ // config updates and theme-observer callbacks keep it current.
203
+ element.setAttribute("data-persona-color-scheme", getColorScheme(config));
197
204
  };
198
205
 
199
206
  export const createThemeObserver = (
@@ -13,6 +13,11 @@ import {
13
13
  DEFAULT_FLOATING_LAUNCHER_WIDTH,
14
14
  } from '../defaults';
15
15
 
16
+ // Detached/docked panel defaults, shared by the panel token defaults, the alias
17
+ // fallbacks below, host-layout, the theme editor, and the artifact gate.
18
+ export const DEFAULT_PANEL_INSET = '16px';
19
+ export const DEFAULT_PANEL_CANVAS_BACKGROUND = 'transparent';
20
+
16
21
  export const DEFAULT_PALETTE = {
17
22
  colors: {
18
23
  primary: {
@@ -258,6 +263,9 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
258
263
  foreground: 'semantic.colors.text',
259
264
  borderRadius: 'palette.radius.md',
260
265
  padding: 'semantic.spacing.sm',
266
+ // Subtle neutral tint on hover — the composer's transparent icon buttons
267
+ // (attachment, mention) read this via `--persona-button-ghost-hover-bg`.
268
+ hoverBackground: 'rgba(0, 0, 0, 0.05)',
261
269
  },
262
270
  },
263
271
  input: {
@@ -287,6 +295,8 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
287
295
  maxHeight: 'calc(100vh - 80px)',
288
296
  borderRadius: 'palette.radius.xl',
289
297
  shadow: 'palette.shadows.xl',
298
+ inset: DEFAULT_PANEL_INSET,
299
+ canvasBackground: DEFAULT_PANEL_CANVAS_BACKGROUND,
290
300
  },
291
301
  header: {
292
302
  // Header role: solid primary
@@ -319,12 +329,12 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
319
329
  border: 'semantic.colors.border',
320
330
  },
321
331
  introCard: {
322
- // Defaults preserve the legacy `persona-shadow-sm` look exactly so existing
323
- // pages render unchanged when no token is set.
324
- background: 'semantic.colors.surface',
332
+ // Flat by default: the greeting reads as plain text on the transcript
333
+ // background (the industry norm), not an elevated card.
334
+ background: 'transparent',
325
335
  borderRadius: 'palette.radius.2xl',
326
336
  padding: 'semantic.spacing.lg',
327
- shadow: '0 5px 15px rgba(15, 23, 42, 0.08)',
337
+ shadow: 'palette.shadows.none',
328
338
  },
329
339
  toolBubble: {
330
340
  shadow: 'palette.shadows.sm',
@@ -732,6 +742,21 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
732
742
  cssVars['--persona-components-button-primary-borderRadius'] ??
733
743
  cssVars['--persona-palette-radius-full'] ??
734
744
  '9999px';
745
+ // Ghost variant: transparent, neutral-foreground icon buttons (the composer's
746
+ // attachment + mention affordances). Wired to `components.button.ghost.*`.
747
+ cssVars['--persona-button-ghost-bg'] =
748
+ cssVars['--persona-components-button-ghost-background'] ??
749
+ 'transparent';
750
+ cssVars['--persona-button-ghost-fg'] =
751
+ cssVars['--persona-components-button-ghost-foreground'] ??
752
+ cssVars['--persona-text'];
753
+ cssVars['--persona-button-ghost-radius'] =
754
+ cssVars['--persona-components-button-ghost-borderRadius'] ??
755
+ cssVars['--persona-radius-md'] ??
756
+ '0.375rem';
757
+ cssVars['--persona-button-ghost-hover-bg'] =
758
+ cssVars['--persona-components-button-ghost-hoverBackground'] ??
759
+ 'rgba(0, 0, 0, 0.05)';
735
760
  cssVars['--persona-panel-radius'] =
736
761
  cssVars['--persona-components-panel-borderRadius'] ??
737
762
  cssVars['--persona-radius-xl'] ??
@@ -742,6 +767,10 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
742
767
  cssVars['--persona-components-panel-shadow'] ??
743
768
  cssVars['--persona-palette-shadows-xl'] ??
744
769
  '0 25px 50px -12px rgba(0, 0, 0, 0.25)';
770
+ cssVars['--persona-panel-inset'] =
771
+ cssVars['--persona-components-panel-inset'] ?? DEFAULT_PANEL_INSET;
772
+ cssVars['--persona-panel-canvas-bg'] =
773
+ cssVars['--persona-components-panel-canvasBackground'] ?? DEFAULT_PANEL_CANVAS_BACKGROUND;
745
774
  cssVars['--persona-launcher-shadow'] =
746
775
  cssVars['--persona-components-launcher-shadow'] ??
747
776
  '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)';
@@ -780,19 +809,17 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
780
809
  if (headerTokens?.borderBottom) cssVars['--persona-header-border-bottom'] = headerTokens.borderBottom;
781
810
 
782
811
  // Intro card aliases: short names the panel inline-styles read directly.
783
- // The full-path `--persona-components-introCard-*` variables auto-emit above;
784
- // these mirror them with sensible fallbacks so existing pages keep their look.
785
- const introCardTokens = theme.components?.introCard;
812
+ // The full-path `--persona-components-introCard-*` variables auto-emit above.
813
+ // Default is flat (transparent, no shadow): the greeting renders as plain
814
+ // text on the transcript background; set introCard tokens for a card look.
786
815
  cssVars['--persona-intro-card-bg'] =
787
- cssVars['--persona-components-introCard-background'] ?? cssVars['--persona-surface'];
816
+ cssVars['--persona-components-introCard-background'] ?? 'transparent';
788
817
  cssVars['--persona-intro-card-radius'] =
789
818
  cssVars['--persona-components-introCard-borderRadius'] ?? '1rem';
790
819
  cssVars['--persona-intro-card-padding'] =
791
820
  cssVars['--persona-components-introCard-padding'] ?? '1.5rem';
792
821
  cssVars['--persona-intro-card-shadow'] =
793
- introCardTokens?.shadow
794
- ?? cssVars['--persona-components-introCard-shadow']
795
- ?? '0 5px 15px rgba(15, 23, 42, 0.08)';
822
+ cssVars['--persona-components-introCard-shadow'] ?? 'none';
796
823
 
797
824
  cssVars['--persona-input-background'] =
798
825
  cssVars['--persona-components-input-background'] ?? cssVars['--persona-surface'];
@@ -967,6 +994,10 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
967
994
  if (t.iconBorder) cssVars['--persona-artifact-toolbar-icon-border'] = t.iconBorder;
968
995
  if (t.toggleGroupGap) cssVars['--persona-artifact-toolbar-toggle-group-gap'] = t.toggleGroupGap;
969
996
  if (t.toggleBorderRadius) cssVars['--persona-artifact-toolbar-toggle-radius'] = t.toggleBorderRadius;
997
+ if (t.toggleGroupPadding) cssVars['--persona-artifact-toolbar-toggle-group-padding'] = t.toggleGroupPadding;
998
+ if (t.toggleGroupBorder) cssVars['--persona-artifact-toolbar-toggle-group-border'] = t.toggleGroupBorder;
999
+ if (t.toggleGroupBorderRadius) cssVars['--persona-artifact-toolbar-toggle-group-radius'] = t.toggleGroupBorderRadius;
1000
+ if (t.toggleGroupBackground) cssVars['--persona-artifact-toolbar-toggle-group-bg'] = resolveTokenValue(theme, t.toggleGroupBackground) ?? t.toggleGroupBackground;
970
1001
  if (t.copyBackground) cssVars['--persona-artifact-toolbar-copy-bg'] = t.copyBackground;
971
1002
  if (t.copyBorder) cssVars['--persona-artifact-toolbar-copy-border'] = t.copyBorder;
972
1003
  if (t.copyColor) cssVars['--persona-artifact-toolbar-copy-color'] = t.copyColor;
@@ -1015,6 +1046,80 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
1015
1046
  cssVars['--persona-artifact-toolbar-bg'] = toolbarBg;
1016
1047
  }
1017
1048
  }
1049
+ if (artifact?.card) {
1050
+ const t = artifact.card;
1051
+ if (t.background) cssVars['--persona-artifact-card-bg'] = t.background;
1052
+ if (t.border) cssVars['--persona-artifact-card-border'] = t.border;
1053
+ if (t.borderRadius) cssVars['--persona-artifact-card-radius'] = t.borderRadius;
1054
+ if (t.hoverBackground) cssVars['--persona-artifact-card-hover-bg'] = t.hoverBackground;
1055
+ if (t.hoverBorderColor) cssVars['--persona-artifact-card-hover-border'] = t.hoverBorderColor;
1056
+ }
1057
+ if (artifact?.inline) {
1058
+ const t = artifact.inline;
1059
+ if (t.background) {
1060
+ cssVars['--persona-artifact-inline-bg'] =
1061
+ resolveTokenValue(theme, t.background) ?? t.background;
1062
+ }
1063
+ if (t.border) cssVars['--persona-artifact-inline-border'] = t.border;
1064
+ if (t.borderRadius) cssVars['--persona-artifact-inline-radius'] = t.borderRadius;
1065
+ if (t.chromeBackground) {
1066
+ cssVars['--persona-artifact-inline-chrome-bg'] =
1067
+ resolveTokenValue(theme, t.chromeBackground) ?? t.chromeBackground;
1068
+ }
1069
+ if (t.chromeBorder) {
1070
+ cssVars['--persona-artifact-inline-chrome-border'] =
1071
+ resolveTokenValue(theme, t.chromeBorder) ?? t.chromeBorder;
1072
+ }
1073
+ if (t.titleColor) {
1074
+ cssVars['--persona-artifact-inline-title-color'] =
1075
+ resolveTokenValue(theme, t.titleColor) ?? t.titleColor;
1076
+ }
1077
+ if (t.mutedColor) {
1078
+ cssVars['--persona-artifact-inline-muted-color'] =
1079
+ resolveTokenValue(theme, t.mutedColor) ?? t.mutedColor;
1080
+ }
1081
+ if (t.frameHeight) cssVars['--persona-artifact-inline-frame-height'] = t.frameHeight;
1082
+ }
1083
+
1084
+ // Code (syntax-highlighted artifact source view) tokens.
1085
+ const code = components?.code;
1086
+ if (code) {
1087
+ if (code.keywordColor) cssVars['--persona-code-keyword-color'] = code.keywordColor;
1088
+ if (code.stringColor) cssVars['--persona-code-string-color'] = code.stringColor;
1089
+ if (code.commentColor) cssVars['--persona-code-comment-color'] = code.commentColor;
1090
+ if (code.numberColor) cssVars['--persona-code-number-color'] = code.numberColor;
1091
+ if (code.tagColor) cssVars['--persona-code-tag-color'] = code.tagColor;
1092
+ if (code.attrColor) cssVars['--persona-code-attr-color'] = code.attrColor;
1093
+ if (code.propertyColor) cssVars['--persona-code-property-color'] = code.propertyColor;
1094
+ if (code.lineNumberColor) cssVars['--persona-code-line-number-color'] = code.lineNumberColor;
1095
+ if (code.gutterBorderColor) cssVars['--persona-code-gutter-border-color'] = code.gutterBorderColor;
1096
+ if (code.background)
1097
+ cssVars['--persona-code-bg'] = resolveTokenValue(theme, code.background) ?? code.background;
1098
+ }
1099
+
1100
+ // Interactive-state defaults. The default preset resolves container === surface,
1101
+ // which turns every hover/active rule that falls back to --persona-container
1102
+ // into a visual no-op; anchor those states one gray step down in that case.
1103
+ // Component config emitted above must keep winning, so only fill vars not set.
1104
+ const stateSurface = cssVars['--persona-surface'];
1105
+ const stateContainer = cssVars['--persona-container'];
1106
+ const gray100 = cssVars['--persona-palette-colors-gray-100'] ?? '#f3f4f6';
1107
+ const gray200 = cssVars['--persona-palette-colors-gray-200'] ?? '#e5e7eb';
1108
+ const gray300 = cssVars['--persona-palette-colors-gray-300'] ?? '#d1d5db';
1109
+ const flatTheme = !stateContainer || stateContainer === stateSurface;
1110
+ const hoverBgDefault = flatTheme ? gray100 : stateContainer;
1111
+ const activeBgDefault = flatTheme ? gray200 : stateContainer;
1112
+ cssVars['--persona-icon-btn-hover-bg'] = cssVars['--persona-icon-btn-hover-bg'] ?? hoverBgDefault;
1113
+ cssVars['--persona-icon-btn-active-bg'] = cssVars['--persona-icon-btn-active-bg'] ?? activeBgDefault;
1114
+ if (flatTheme) {
1115
+ cssVars['--persona-icon-btn-active-border'] =
1116
+ cssVars['--persona-icon-btn-active-border'] ?? gray300;
1117
+ }
1118
+ cssVars['--persona-label-btn-hover-bg'] = cssVars['--persona-label-btn-hover-bg'] ?? hoverBgDefault;
1119
+ cssVars['--persona-artifact-tab-hover-bg'] =
1120
+ cssVars['--persona-artifact-tab-hover-bg'] ?? hoverBgDefault;
1121
+ cssVars['--persona-artifact-card-hover-bg'] =
1122
+ cssVars['--persona-artifact-card-hover-bg'] ?? hoverBgDefault;
1018
1123
 
1019
1124
  return cssVars;
1020
1125
  }
@@ -1041,6 +1146,8 @@ export const THEME_ZONES = {
1041
1146
  container: 'Main widget container',
1042
1147
  'artifact-pane': 'Artifact sidebar',
1043
1148
  'artifact-toolbar': 'Artifact toolbar',
1149
+ 'artifact-inline': 'Inline artifact block',
1150
+ 'artifact-inline-chrome': 'Inline artifact title bar',
1044
1151
  } as const;
1045
1152
 
1046
1153
  export type ThemeZone = keyof typeof THEME_ZONES;
@@ -0,0 +1,32 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it } from "vitest";
4
+ import { appendCharSpans } from "./tool-loading-animation";
5
+
6
+ const NBSP = String.fromCharCode(0xa0);
7
+
8
+ describe("appendCharSpans", () => {
9
+ it("wraps each character in a persona-tool-char span with a staggered index", () => {
10
+ const container = document.createElement("div");
11
+ const next = appendCharSpans(container, "ab", 0);
12
+
13
+ const spans = container.querySelectorAll(".persona-tool-char");
14
+ expect(spans.length).toBe(2);
15
+ expect((spans[0] as HTMLElement).style.getPropertyValue("--char-index")).toBe("0");
16
+ expect((spans[1] as HTMLElement).style.getPropertyValue("--char-index")).toBe("1");
17
+ expect(spans[0].textContent).toBe("a");
18
+ expect(spans[1].textContent).toBe("b");
19
+ expect(next).toBe(2);
20
+ });
21
+
22
+ it("renders spaces as non-breaking spaces and honors the start index", () => {
23
+ const container = document.createElement("div");
24
+ const next = appendCharSpans(container, "a b", 5);
25
+
26
+ const spans = container.querySelectorAll(".persona-tool-char");
27
+ expect(spans.length).toBe(3);
28
+ expect((spans[0] as HTMLElement).style.getPropertyValue("--char-index")).toBe("5");
29
+ expect(spans[1].textContent).toBe(NBSP);
30
+ expect(next).toBe(8);
31
+ });
32
+ });
@@ -0,0 +1,24 @@
1
+ import { createElement } from "./dom";
2
+
3
+ /**
4
+ * Append text as individual animated character spans.
5
+ * Each character becomes a `persona-tool-char` span carrying a `--char-index`
6
+ * custom property so the shimmer/rainbow keyframes can stagger their delay.
7
+ * Spaces are rendered as non-breaking spaces so they animate like any other
8
+ * character. Returns the next available character index.
9
+ */
10
+ export const appendCharSpans = (
11
+ container: HTMLElement,
12
+ text: string,
13
+ startIndex: number
14
+ ): number => {
15
+ let idx = startIndex;
16
+ for (const char of text) {
17
+ const span = createElement("span", "persona-tool-char");
18
+ span.style.setProperty("--char-index", String(idx));
19
+ span.textContent = char === " " ? "\u00A0" : char;
20
+ container.appendChild(span);
21
+ idx++;
22
+ }
23
+ return idx;
24
+ };
@@ -1 +0,0 @@
1
- var a=class{constructor(e=24e3,t={}){this.ctx=null;this.nextStartTime=0;this.activeSources=[];this.finishedCallbacks=[];this.startedCallbacks=[];this.playing=!1;this.streamEnded=!1;this.pendingCount=0;this.started=!1;this.userPaused=!1;this.pendingBuffers=[];this.pendingSamples=0;this.remainder=null;var s;this.sampleRate=e;let i=Math.max(0,(s=t.prebufferMs)!=null?s:0);this.waterlineSamples=Math.round(e*i/1e3),this.buffering=this.waterlineSamples>0}ensureContext(){if(!this.ctx){let t=typeof window!="undefined"?window:void 0;if(!t)throw new Error("AudioPlaybackManager requires a browser environment");let i=t.AudioContext||t.webkitAudioContext;this.ctx=new i({sampleRate:this.sampleRate})}let e=this.ctx;return e.state==="suspended"&&!this.userPaused&&e.resume(),e}enqueue(e){if(e.length===0)return;let t=e;if(this.remainder){let s=new Uint8Array(this.remainder.length+e.length);s.set(this.remainder),s.set(e,this.remainder.length),t=s,this.remainder=null}if(t.length%2!==0&&(this.remainder=new Uint8Array([t[t.length-1]]),t=t.subarray(0,t.length-1)),t.length===0)return;let i=this.pcmToFloat32(t);i.length!==0&&(this.buffering?(this.pendingBuffers.push(i),this.pendingSamples+=i.length,this.pendingSamples>=this.waterlineSamples&&this.releaseBuffer()):this.scheduleSamples(i))}markStreamEnd(){this.pendingBuffers.length>0&&this.releaseBuffer(),this.streamEnded=!0,this.checkFinished()}flush(){for(let e of this.activeSources)try{e.stop(),e.disconnect()}catch{}this.activeSources=[],this.pendingCount=0,this.nextStartTime=0,this.playing=!1,this.streamEnded=!1,this.finishedCallbacks=[],this.startedCallbacks=[],this.remainder=null,this.pendingBuffers=[],this.pendingSamples=0,this.buffering=this.waterlineSamples>0,this.started=!1}isPlaying(){return this.playing}onFinished(e){this.finishedCallbacks.push(e)}onStarted(e){this.startedCallbacks.push(e)}pause(){this.userPaused=!0,this.ctx&&this.ctx.state==="running"&&this.ctx.suspend()}resume(){this.userPaused=!1,this.ctx&&this.ctx.state==="suspended"&&this.ctx.resume()}async destroy(){this.flush(),this.ctx&&(await this.ctx.close(),this.ctx=null)}releaseBuffer(){this.buffering=!1;let e=this.pendingBuffers;this.pendingBuffers=[],this.pendingSamples=0;for(let t of e)this.scheduleSamples(t)}scheduleSamples(e){if(e.length===0)return;let t=this.ensureContext(),i=t.createBuffer(1,e.length,this.sampleRate);i.getChannelData(0).set(e);let s=t.createBufferSource();s.buffer=i,s.connect(t.destination);let n=t.currentTime;if(this.nextStartTime===0?this.nextStartTime=n:this.nextStartTime<n&&(this.nextStartTime=n,this.waterlineSamples>0&&(this.buffering=!0)),s.start(this.nextStartTime),this.nextStartTime+=i.duration,this.activeSources.push(s),this.pendingCount++,this.playing=!0,!this.started){this.started=!0;let r=this.startedCallbacks.slice();this.startedCallbacks=[];for(let h of r)h()}s.onended=()=>{let r=this.activeSources.indexOf(s);r!==-1&&this.activeSources.splice(r,1),this.pendingCount--,this.checkFinished()}}checkFinished(){if(this.streamEnded&&this.pendingCount<=0&&this.pendingBuffers.length===0){this.playing=!1,this.streamEnded=!1;let e=this.finishedCallbacks.slice();this.finishedCallbacks=[];for(let t of e)t()}}pcmToFloat32(e){let t=Math.floor(e.length/2),i=new Float32Array(t),s=new DataView(e.buffer,e.byteOffset,e.byteLength);for(let n=0;n<t;n++){let r=s.getInt16(n*2,!0);i[n]=r/32768}return i}};export{a};
@@ -1 +0,0 @@
1
- import{a as c}from"./chunk-DFBSCFYN.js";function g(o){return o.replace(/\/+$/,"")}var m=class{constructor(e){this.opts=e;this.id="runtype-tts";this.supportsPause=!0;this.player=null;this.playerPromise=null;this.generation=0}ensurePlayer(){var e,t;return(t=this.playerPromise)!=null?t:this.playerPromise=Promise.resolve(this.opts.createPlaybackEngine?this.opts.createPlaybackEngine():new c(24e3,{prebufferMs:(e=this.opts.prebufferMs)!=null?e:200})).then(r=>this.player=r)}speak(e,t){let r=++this.generation;this.run(r,e,t)}async run(e,t,r){var i,a,p,h;try{let n=await this.ensurePlayer();if(e!==this.generation)return;n.flush(),n.resume(),n.onStarted(()=>{var s;e===this.generation&&((s=r.onStart)==null||s.call(r))}),n.onFinished(()=>{var s;e===this.generation&&((s=r.onEnd)==null||s.call(r))});let u=`${g(this.opts.host)}/v1/agents/${encodeURIComponent(this.opts.agentId)}/speak`,y=await fetch(u,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.opts.clientToken}`},body:JSON.stringify({text:t.text,voice:(i=t.voice)!=null?i:this.opts.voice,format:"pcm"})});if(e!==this.generation)return;if(!y.ok||!y.body)throw new Error(await S(y));let v=y.body.getReader();for(;;){let{done:s,value:d}=await v.read();if(e!==this.generation){await v.cancel().catch(()=>{});return}if(s)break;d&&d.byteLength>0&&n.enqueue(d)}n.markStreamEnd()}catch(n){if(e!==this.generation)return;let u=n instanceof Error?n:new Error(String(n));(p=(a=this.opts).onError)==null||p.call(a,u),(h=r.onError)==null||h.call(r,u)}}pause(){var e;(e=this.player)==null||e.pause()}resume(){var e;(e=this.player)==null||e.resume()}stop(){var e;this.generation++,(e=this.player)==null||e.flush()}destroy(){var e;this.generation++,(e=this.player)==null||e.destroy(),this.player=null,this.playerPromise=null}};async function S(o){var e,t;try{let r=await o.json();return r.detail?`${(e=r.error)!=null?e:`Runtype TTS ${o.status}`}: ${r.detail}`:(t=r.error)!=null?t:`Runtype TTS request failed (${o.status})`}catch{return`Runtype TTS request failed (${o.status})`}}var f=class{constructor(e,t,r={}){this.primary=e;this.fallback=t;this.options=r;this.id="fallback";this.active=e}get supportsPause(){return this.active.supportsPause}speak(e,t){this.active=this.primary;let r=!1;this.primary.speak(e,{onStart:()=>{var i;r=!0,(i=t.onStart)==null||i.call(t)},onEnd:()=>{var i;return(i=t.onEnd)==null?void 0:i.call(t)},onError:i=>{var a,p,h;if(r){(a=t.onError)==null||a.call(t,i);return}(h=(p=this.options).onFallback)==null||h.call(p,i),this.active=this.fallback,this.fallback.speak(e,t)}})}pause(){this.active.pause()}resume(){this.active.resume()}stop(){this.active.stop()}destroy(){var e,t,r,i;(t=(e=this.primary).destroy)==null||t.call(e),(i=(r=this.fallback).destroy)==null||i.call(r)}};export{f as FallbackSpeechEngine,m as RuntypeSpeechEngine};
@@ -1 +0,0 @@
1
- var k=[1e3,2e3,4e3,8e3,8e3];function T(e){let t=0,r=null,i=null,g=!1,b=()=>{if(i&&(clearTimeout(i),i=null),r){let n=r;r=null,n()}},v=()=>{let n=e.getStatus();n!=="resuming"&&n!=="paused"||b()},p=()=>{typeof document!="undefined"&&document.visibilityState==="hidden"||v()},R=()=>{v()},C=()=>{g||(typeof document!="undefined"&&document.addEventListener("visibilitychange",p),typeof window!="undefined"&&window.addEventListener("online",R),g=!0)},m=()=>{g&&(typeof document!="undefined"&&document.removeEventListener("visibilitychange",p),typeof window!="undefined"&&window.removeEventListener("online",R),g=!1)},M=n=>new Promise(s=>{r=s,i=setTimeout(()=>{i=null,r=null,s()},n)}),E=()=>{var d;let n=e.getResumable();e.clearResumable(),e.setReconnecting(!1),t=0,m(),e.setAbortController(null);let s=!1;for(let c of e.getMessages())c.streaming&&(c.streaming=!1,s=!0);let l=e.buildErrorContent("Connection lost and the response could not be resumed.");l?e.appendMessage({id:`reconnect-failed-${(d=n==null?void 0:n.executionId)!=null?d:e.nextSequence()}`,role:"assistant",content:l,createdAt:new Date().toISOString(),streaming:!1,sequence:e.nextSequence()}):s&&e.notifyMessagesChanged(),e.setStreaming(!1),e.setStatus("idle"),e.onError(new Error("Durable session reconnect failed."))},x=async()=>{var d,c,y,S,w,A;let n=(c=(d=e.config.reconnect)==null?void 0:d.backoffMs)!=null?c:k,s=(S=(y=e.config.reconnect)==null?void 0:y.maxAttempts)!=null?S:n.length,l=e.config.reconnectStream;if(!l){e.setReconnecting(!1);return}for(;e.getResumable()&&t<s;){t+=1,e.setStatus("resuming");let o=e.getResumable(),I=o.lastEventId,u=new AbortController;e.setAbortController(u),e.emitReconnect({phase:"resuming",handle:o,attempt:t});let f=null;try{let a=await l({executionId:o.executionId,after:o.lastEventId,signal:u.signal});a&&a.ok&&a.body&&(f=a.body)}catch{f=null}if(u.signal.aborted)return;if(f){let a=(w=e.getMessages().find(W=>W.id===o.assistantMessageId))==null?void 0:w.content,L=typeof a=="string"?a:"";try{await e.resumeConnect(f,o.assistantMessageId,L)}catch{}if(u.signal.aborted)return;if(!e.getResumable()){e.setReconnecting(!1),t=0,m(),e.emitReconnect({phase:"resumed",handle:o});return}e.getResumable().lastEventId!==I&&(t=0)}if(e.getResumable()&&t<s&&(e.setStatus("paused"),await M((A=n[Math.min(t-1,n.length-1)])!=null?A:1e3),u.signal.aborted))return}e.getResumable()&&E()};return{begin(){t=0,C(),x()},teardown(){i&&(clearTimeout(i),i=null),r=null,t=0,m()},wake:b}}export{T as createReconnectController};