@runtypelabs/persona 1.48.0 → 2.1.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 (69) hide show
  1. package/README.md +140 -8
  2. package/dist/index.cjs +90 -39
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1098 -24
  5. package/dist/index.d.ts +1098 -24
  6. package/dist/index.global.js +134 -83
  7. package/dist/index.global.js.map +1 -1
  8. package/dist/index.js +90 -39
  9. package/dist/index.js.map +1 -1
  10. package/dist/install.global.js +1 -1
  11. package/dist/install.global.js.map +1 -1
  12. package/dist/widget.css +849 -513
  13. package/package.json +1 -1
  14. package/src/artifacts-session.test.ts +80 -0
  15. package/src/client.test.ts +20 -21
  16. package/src/client.ts +153 -4
  17. package/src/components/approval-bubble.ts +45 -42
  18. package/src/components/artifact-card.ts +91 -0
  19. package/src/components/artifact-pane.ts +501 -0
  20. package/src/components/composer-builder.ts +32 -27
  21. package/src/components/event-stream-view.ts +40 -40
  22. package/src/components/feedback.ts +36 -36
  23. package/src/components/forms.ts +11 -11
  24. package/src/components/header-builder.test.ts +32 -0
  25. package/src/components/header-builder.ts +55 -36
  26. package/src/components/header-layouts.ts +58 -125
  27. package/src/components/launcher.ts +36 -21
  28. package/src/components/message-bubble.ts +92 -65
  29. package/src/components/messages.ts +2 -2
  30. package/src/components/panel.ts +42 -11
  31. package/src/components/reasoning-bubble.ts +23 -23
  32. package/src/components/registry.ts +4 -0
  33. package/src/components/suggestions.ts +1 -1
  34. package/src/components/tool-bubble.ts +32 -32
  35. package/src/defaults.ts +30 -4
  36. package/src/index.ts +80 -2
  37. package/src/install.ts +22 -0
  38. package/src/plugins/types.ts +23 -0
  39. package/src/postprocessors.ts +2 -2
  40. package/src/runtime/host-layout.ts +174 -0
  41. package/src/runtime/init.test.ts +236 -0
  42. package/src/runtime/init.ts +114 -55
  43. package/src/session.ts +135 -2
  44. package/src/styles/tailwind.css +1 -1
  45. package/src/styles/widget.css +849 -513
  46. package/src/types/theme.ts +376 -0
  47. package/src/types.ts +338 -15
  48. package/src/ui.docked.test.ts +104 -0
  49. package/src/ui.ts +940 -227
  50. package/src/utils/artifact-gate.test.ts +255 -0
  51. package/src/utils/artifact-gate.ts +142 -0
  52. package/src/utils/artifact-resize.test.ts +64 -0
  53. package/src/utils/artifact-resize.ts +67 -0
  54. package/src/utils/attachment-manager.ts +10 -10
  55. package/src/utils/code-generators.test.ts +52 -0
  56. package/src/utils/code-generators.ts +40 -36
  57. package/src/utils/dock.ts +17 -0
  58. package/src/utils/dom-context.test.ts +504 -0
  59. package/src/utils/dom-context.ts +896 -0
  60. package/src/utils/dom.ts +12 -1
  61. package/src/utils/message-fingerprint.test.ts +187 -0
  62. package/src/utils/message-fingerprint.ts +105 -0
  63. package/src/utils/migration.ts +220 -0
  64. package/src/utils/morph.ts +1 -1
  65. package/src/utils/plugins.ts +175 -0
  66. package/src/utils/positioning.ts +4 -4
  67. package/src/utils/theme.test.ts +157 -0
  68. package/src/utils/theme.ts +224 -60
  69. package/src/utils/tokens.ts +701 -0
@@ -0,0 +1,701 @@
1
+ import type {
2
+ PersonaTheme,
3
+ ResolvedToken,
4
+ ThemeValidationResult,
5
+ ThemeValidationError,
6
+ CreateThemeOptions,
7
+ ComponentTokens,
8
+ SemanticTokens,
9
+ } from '../types/theme';
10
+
11
+ export const DEFAULT_PALETTE = {
12
+ colors: {
13
+ primary: {
14
+ 50: '#eff6ff',
15
+ 100: '#dbeafe',
16
+ 200: '#bfdbfe',
17
+ 300: '#93c5fd',
18
+ 400: '#60a5fa',
19
+ 500: '#3b82f6',
20
+ 600: '#2563eb',
21
+ 700: '#1d4ed8',
22
+ 800: '#1e40af',
23
+ 900: '#1e3a8a',
24
+ 950: '#172554',
25
+ },
26
+ secondary: {
27
+ 50: '#f5f3ff',
28
+ 100: '#ede9fe',
29
+ 200: '#ddd6fe',
30
+ 300: '#c4b5fd',
31
+ 400: '#a78bfa',
32
+ 500: '#8b5cf6',
33
+ 600: '#7c3aed',
34
+ 700: '#6d28d9',
35
+ 800: '#5b21b6',
36
+ 900: '#4c1d95',
37
+ 950: '#2e1065',
38
+ },
39
+ accent: {
40
+ 50: '#ecfeff',
41
+ 100: '#cffafe',
42
+ 200: '#a5f3fc',
43
+ 300: '#67e8f9',
44
+ 400: '#22d3ee',
45
+ 500: '#06b6d4',
46
+ 600: '#0891b2',
47
+ 700: '#0e7490',
48
+ 800: '#155e75',
49
+ 900: '#164e63',
50
+ 950: '#083344',
51
+ },
52
+ gray: {
53
+ 50: '#f9fafb',
54
+ 100: '#f3f4f6',
55
+ 200: '#e5e7eb',
56
+ 300: '#d1d5db',
57
+ 400: '#9ca3af',
58
+ 500: '#6b7280',
59
+ 600: '#4b5563',
60
+ 700: '#374151',
61
+ 800: '#1f2937',
62
+ 900: '#111827',
63
+ 950: '#030712',
64
+ },
65
+ success: {
66
+ 50: '#f0fdf4',
67
+ 100: '#dcfce7',
68
+ 200: '#bbf7d0',
69
+ 300: '#86efac',
70
+ 400: '#4ade80',
71
+ 500: '#22c55e',
72
+ 600: '#16a34a',
73
+ 700: '#15803d',
74
+ 800: '#166534',
75
+ 900: '#14532d',
76
+ },
77
+ warning: {
78
+ 50: '#fefce8',
79
+ 100: '#fef9c3',
80
+ 200: '#fef08a',
81
+ 300: '#fde047',
82
+ 400: '#facc15',
83
+ 500: '#eab308',
84
+ 600: '#ca8a04',
85
+ 700: '#a16207',
86
+ 800: '#854d0e',
87
+ 900: '#713f12',
88
+ },
89
+ error: {
90
+ 50: '#fef2f2',
91
+ 100: '#fee2e2',
92
+ 200: '#fecaca',
93
+ 300: '#fca5a5',
94
+ 400: '#f87171',
95
+ 500: '#ef4444',
96
+ 600: '#dc2626',
97
+ 700: '#b91c1c',
98
+ 800: '#991b1b',
99
+ 900: '#7f1d1d',
100
+ },
101
+ },
102
+ spacing: {
103
+ 0: '0px',
104
+ 1: '0.25rem',
105
+ 2: '0.5rem',
106
+ 3: '0.75rem',
107
+ 4: '1rem',
108
+ 5: '1.25rem',
109
+ 6: '1.5rem',
110
+ 8: '2rem',
111
+ 10: '2.5rem',
112
+ 12: '3rem',
113
+ 16: '4rem',
114
+ 20: '5rem',
115
+ 24: '6rem',
116
+ 32: '8rem',
117
+ 40: '10rem',
118
+ 48: '12rem',
119
+ 56: '14rem',
120
+ 64: '16rem',
121
+ },
122
+ typography: {
123
+ fontFamily: {
124
+ sans: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
125
+ serif: 'Georgia, Cambria, "Times New Roman", Times, serif',
126
+ mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
127
+ },
128
+ fontSize: {
129
+ xs: '0.75rem',
130
+ sm: '0.875rem',
131
+ base: '1rem',
132
+ lg: '1.125rem',
133
+ xl: '1.25rem',
134
+ '2xl': '1.5rem',
135
+ '3xl': '1.875rem',
136
+ '4xl': '2.25rem',
137
+ },
138
+ fontWeight: {
139
+ normal: '400',
140
+ medium: '500',
141
+ semibold: '600',
142
+ bold: '700',
143
+ },
144
+ lineHeight: {
145
+ tight: '1.25',
146
+ normal: '1.5',
147
+ relaxed: '1.625',
148
+ },
149
+ },
150
+ shadows: {
151
+ none: 'none',
152
+ sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
153
+ md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
154
+ lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
155
+ xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
156
+ '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
157
+ },
158
+ borders: {
159
+ none: 'none',
160
+ sm: '1px solid',
161
+ md: '2px solid',
162
+ lg: '4px solid',
163
+ },
164
+ radius: {
165
+ none: '0px',
166
+ sm: '0.125rem',
167
+ md: '0.375rem',
168
+ lg: '0.5rem',
169
+ xl: '0.75rem',
170
+ '2xl': '1rem',
171
+ full: '9999px',
172
+ },
173
+ };
174
+
175
+ export const DEFAULT_SEMANTIC: SemanticTokens = {
176
+ colors: {
177
+ primary: 'palette.colors.primary.500',
178
+ secondary: 'palette.colors.gray.500',
179
+ accent: 'palette.colors.primary.600',
180
+ surface: 'palette.colors.gray.50',
181
+ background: 'palette.colors.gray.50',
182
+ container: 'palette.colors.gray.100',
183
+ text: 'palette.colors.gray.900',
184
+ textMuted: 'palette.colors.gray.500',
185
+ textInverse: 'palette.colors.gray.50',
186
+ border: 'palette.colors.gray.200',
187
+ divider: 'palette.colors.gray.200',
188
+ interactive: {
189
+ default: 'palette.colors.primary.500',
190
+ hover: 'palette.colors.primary.600',
191
+ focus: 'palette.colors.primary.700',
192
+ active: 'palette.colors.primary.800',
193
+ disabled: 'palette.colors.gray.300',
194
+ },
195
+ feedback: {
196
+ success: 'palette.colors.success.500',
197
+ warning: 'palette.colors.warning.500',
198
+ error: 'palette.colors.error.500',
199
+ info: 'palette.colors.primary.500',
200
+ },
201
+ },
202
+ spacing: {
203
+ xs: 'palette.spacing.1',
204
+ sm: 'palette.spacing.2',
205
+ md: 'palette.spacing.4',
206
+ lg: 'palette.spacing.6',
207
+ xl: 'palette.spacing.8',
208
+ '2xl': 'palette.spacing.10',
209
+ },
210
+ typography: {
211
+ fontFamily: 'palette.typography.fontFamily.sans',
212
+ fontSize: 'palette.typography.fontSize.base',
213
+ fontWeight: 'palette.typography.fontWeight.normal',
214
+ lineHeight: 'palette.typography.lineHeight.normal',
215
+ },
216
+ };
217
+
218
+ export const DEFAULT_COMPONENTS: ComponentTokens = {
219
+ button: {
220
+ primary: {
221
+ background: 'semantic.colors.primary',
222
+ foreground: 'semantic.colors.textInverse',
223
+ borderRadius: 'palette.radius.lg',
224
+ padding: 'semantic.spacing.md',
225
+ },
226
+ secondary: {
227
+ background: 'semantic.colors.surface',
228
+ foreground: 'semantic.colors.text',
229
+ borderRadius: 'palette.radius.lg',
230
+ padding: 'semantic.spacing.md',
231
+ },
232
+ ghost: {
233
+ background: 'transparent',
234
+ foreground: 'semantic.colors.text',
235
+ borderRadius: 'palette.radius.md',
236
+ padding: 'semantic.spacing.sm',
237
+ },
238
+ },
239
+ input: {
240
+ background: 'semantic.colors.surface',
241
+ placeholder: 'semantic.colors.textMuted',
242
+ borderRadius: 'palette.radius.lg',
243
+ padding: 'semantic.spacing.md',
244
+ focus: {
245
+ border: 'semantic.colors.interactive.focus',
246
+ ring: 'semantic.colors.interactive.focus',
247
+ },
248
+ },
249
+ launcher: {
250
+ size: '60px',
251
+ iconSize: '28px',
252
+ borderRadius: 'palette.radius.full',
253
+ shadow: 'palette.shadows.lg',
254
+ },
255
+ panel: {
256
+ width: 'min(400px, calc(100vw - 24px))',
257
+ maxWidth: '400px',
258
+ height: '600px',
259
+ maxHeight: 'calc(100vh - 80px)',
260
+ borderRadius: 'palette.radius.xl',
261
+ shadow: 'palette.shadows.xl',
262
+ },
263
+ header: {
264
+ background: 'semantic.colors.surface',
265
+ border: 'semantic.colors.border',
266
+ borderRadius: 'palette.radius.xl palette.radius.xl 0 0',
267
+ padding: 'semantic.spacing.md',
268
+ },
269
+ message: {
270
+ user: {
271
+ background: 'semantic.colors.primary',
272
+ text: 'semantic.colors.textInverse',
273
+ borderRadius: 'palette.radius.lg',
274
+ shadow: 'palette.shadows.sm',
275
+ },
276
+ assistant: {
277
+ background: 'semantic.colors.container',
278
+ text: 'semantic.colors.text',
279
+ borderRadius: 'palette.radius.lg',
280
+ border: 'semantic.colors.border',
281
+ shadow: 'palette.shadows.sm',
282
+ },
283
+ },
284
+ toolBubble: {
285
+ shadow: 'palette.shadows.sm',
286
+ },
287
+ reasoningBubble: {
288
+ shadow: 'palette.shadows.sm',
289
+ },
290
+ composer: {
291
+ shadow: 'none',
292
+ },
293
+ markdown: {
294
+ inlineCode: {
295
+ background: 'semantic.colors.container',
296
+ foreground: 'semantic.colors.text',
297
+ },
298
+ link: {
299
+ foreground: 'semantic.colors.accent',
300
+ },
301
+ prose: {
302
+ fontFamily: 'inherit',
303
+ },
304
+ },
305
+ voice: {
306
+ recording: {
307
+ indicator: 'palette.colors.error.500',
308
+ background: 'palette.colors.error.50',
309
+ border: 'palette.colors.error.200',
310
+ },
311
+ processing: {
312
+ icon: 'palette.colors.primary.500',
313
+ background: 'palette.colors.primary.50',
314
+ },
315
+ speaking: {
316
+ icon: 'palette.colors.success.500',
317
+ },
318
+ },
319
+ approval: {
320
+ requested: {
321
+ background: 'palette.colors.warning.50',
322
+ border: 'palette.colors.warning.200',
323
+ text: 'palette.colors.gray.900',
324
+ },
325
+ approve: {
326
+ background: 'palette.colors.success.500',
327
+ foreground: 'palette.colors.gray.50',
328
+ borderRadius: 'palette.radius.md',
329
+ padding: 'semantic.spacing.sm',
330
+ },
331
+ deny: {
332
+ background: 'palette.colors.error.500',
333
+ foreground: 'palette.colors.gray.50',
334
+ borderRadius: 'palette.radius.md',
335
+ padding: 'semantic.spacing.sm',
336
+ },
337
+ },
338
+ attachment: {
339
+ image: {
340
+ background: 'palette.colors.gray.100',
341
+ border: 'palette.colors.gray.200',
342
+ },
343
+ },
344
+ };
345
+
346
+ function resolveTokenValue(theme: PersonaTheme, path: string): string | undefined {
347
+ if (
348
+ !path.startsWith('palette.') &&
349
+ !path.startsWith('semantic.') &&
350
+ !path.startsWith('components.')
351
+ ) {
352
+ return path;
353
+ }
354
+
355
+ const parts = path.split('.');
356
+ let current: any = theme;
357
+
358
+ for (const part of parts) {
359
+ if (current === undefined || current === null) {
360
+ return undefined;
361
+ }
362
+ current = current[part];
363
+ }
364
+
365
+ if (
366
+ typeof current === 'string' &&
367
+ (current.startsWith('palette.') ||
368
+ current.startsWith('semantic.') ||
369
+ current.startsWith('components.'))
370
+ ) {
371
+ return resolveTokenValue(theme, current);
372
+ }
373
+
374
+ return current;
375
+ }
376
+
377
+ export function resolveTokens(theme: PersonaTheme): Record<string, ResolvedToken> {
378
+ const resolved: Record<string, ResolvedToken> = {};
379
+
380
+ function resolveObject(obj: any, prefix: string) {
381
+ for (const [key, value] of Object.entries(obj)) {
382
+ const path = `${prefix}.${key}`;
383
+
384
+ if (typeof value === 'string') {
385
+ const resolvedValue = resolveTokenValue(theme, value);
386
+ if (resolvedValue !== undefined) {
387
+ resolved[path] = {
388
+ path,
389
+ value: resolvedValue,
390
+ type:
391
+ prefix.includes('color')
392
+ ? 'color'
393
+ : prefix.includes('spacing')
394
+ ? 'spacing'
395
+ : prefix.includes('typography')
396
+ ? 'typography'
397
+ : prefix.includes('shadow')
398
+ ? 'shadow'
399
+ : prefix.includes('border')
400
+ ? 'border'
401
+ : 'color',
402
+ };
403
+ }
404
+ } else if (typeof value === 'object' && value !== null) {
405
+ resolveObject(value, path);
406
+ }
407
+ }
408
+ }
409
+
410
+ resolveObject(theme.palette, 'palette');
411
+ resolveObject(theme.semantic, 'semantic');
412
+ resolveObject(theme.components, 'components');
413
+
414
+ return resolved;
415
+ }
416
+
417
+ export function validateTheme(theme: Partial<PersonaTheme>): ThemeValidationResult {
418
+ const errors: ThemeValidationError[] = [];
419
+ const warnings: ThemeValidationError[] = [];
420
+
421
+ if (!theme.palette) {
422
+ errors.push({
423
+ path: 'palette',
424
+ message: 'Theme must include a palette',
425
+ severity: 'error',
426
+ });
427
+ }
428
+
429
+ if (!theme.semantic) {
430
+ warnings.push({
431
+ path: 'semantic',
432
+ message: 'No semantic tokens defined - defaults will be used',
433
+ severity: 'warning',
434
+ });
435
+ }
436
+
437
+ if (!theme.components) {
438
+ warnings.push({
439
+ path: 'components',
440
+ message: 'No component tokens defined - defaults will be used',
441
+ severity: 'warning',
442
+ });
443
+ }
444
+
445
+ return {
446
+ valid: errors.length === 0,
447
+ errors,
448
+ warnings,
449
+ };
450
+ }
451
+
452
+ function mergeRecords(
453
+ base: Record<string, unknown>,
454
+ override: Record<string, unknown>
455
+ ): Record<string, unknown> {
456
+ const result = { ...base };
457
+ for (const [key, value] of Object.entries(override)) {
458
+ const existing = result[key];
459
+ if (existing && typeof existing === 'object' && !Array.isArray(existing) &&
460
+ value && typeof value === 'object' && !Array.isArray(value)) {
461
+ result[key] = mergeRecords(
462
+ existing as Record<string, unknown>,
463
+ value as Record<string, unknown>
464
+ );
465
+ } else {
466
+ result[key] = value;
467
+ }
468
+ }
469
+ return result;
470
+ }
471
+
472
+ function deepMergeComponents(
473
+ base: ComponentTokens,
474
+ override?: Partial<ComponentTokens>
475
+ ): ComponentTokens {
476
+ if (!override) return base;
477
+ return mergeRecords(
478
+ base as unknown as Record<string, unknown>,
479
+ override as unknown as Record<string, unknown>
480
+ ) as unknown as ComponentTokens;
481
+ }
482
+
483
+ export function createTheme(
484
+ userConfig?: Partial<PersonaTheme>,
485
+ options: CreateThemeOptions = {}
486
+ ): PersonaTheme {
487
+ const baseTheme: PersonaTheme = {
488
+ palette: DEFAULT_PALETTE as PersonaTheme['palette'],
489
+ semantic: DEFAULT_SEMANTIC as PersonaTheme['semantic'],
490
+ components: DEFAULT_COMPONENTS as PersonaTheme['components'],
491
+ };
492
+
493
+ let theme: PersonaTheme = {
494
+ palette: {
495
+ ...baseTheme.palette,
496
+ ...userConfig?.palette,
497
+ colors: {
498
+ ...baseTheme.palette.colors,
499
+ ...userConfig?.palette?.colors,
500
+ },
501
+ spacing: {
502
+ ...baseTheme.palette.spacing,
503
+ ...userConfig?.palette?.spacing,
504
+ },
505
+ typography: {
506
+ ...baseTheme.palette.typography,
507
+ ...userConfig?.palette?.typography,
508
+ },
509
+ shadows: {
510
+ ...baseTheme.palette.shadows,
511
+ ...userConfig?.palette?.shadows,
512
+ },
513
+ borders: {
514
+ ...baseTheme.palette.borders,
515
+ ...userConfig?.palette?.borders,
516
+ },
517
+ radius: {
518
+ ...baseTheme.palette.radius,
519
+ ...userConfig?.palette?.radius,
520
+ },
521
+ },
522
+ semantic: {
523
+ ...baseTheme.semantic,
524
+ ...userConfig?.semantic,
525
+ colors: {
526
+ ...baseTheme.semantic.colors,
527
+ ...userConfig?.semantic?.colors,
528
+ interactive: {
529
+ ...baseTheme.semantic.colors.interactive,
530
+ ...userConfig?.semantic?.colors?.interactive,
531
+ },
532
+ feedback: {
533
+ ...baseTheme.semantic.colors.feedback,
534
+ ...userConfig?.semantic?.colors?.feedback,
535
+ },
536
+ },
537
+ spacing: {
538
+ ...baseTheme.semantic.spacing,
539
+ ...userConfig?.semantic?.spacing,
540
+ },
541
+ typography: {
542
+ ...baseTheme.semantic.typography,
543
+ ...userConfig?.semantic?.typography,
544
+ },
545
+ },
546
+ components: deepMergeComponents(baseTheme.components, userConfig?.components),
547
+ };
548
+
549
+ if (options.validate !== false) {
550
+ const validation = validateTheme(theme);
551
+ if (!validation.valid) {
552
+ throw new Error(
553
+ `Theme validation failed: ${validation.errors.map((e) => e.message).join(', ')}`
554
+ );
555
+ }
556
+ }
557
+
558
+ if (options.plugins) {
559
+ for (const plugin of options.plugins) {
560
+ theme = plugin.transform(theme);
561
+ }
562
+ }
563
+
564
+ return theme;
565
+ }
566
+
567
+ export function themeToCssVariables(theme: PersonaTheme): Record<string, string> {
568
+ const resolved = resolveTokens(theme);
569
+ const cssVars: Record<string, string> = {};
570
+
571
+ for (const [path, token] of Object.entries(resolved)) {
572
+ const varName = path.replace(/\./g, '-');
573
+ cssVars[`--persona-${varName}`] = token.value;
574
+ }
575
+
576
+ cssVars['--persona-primary'] = cssVars['--persona-semantic-colors-primary'] ?? cssVars['--persona-palette-colors-primary-500'];
577
+ cssVars['--persona-secondary'] = cssVars['--persona-semantic-colors-secondary'] ?? cssVars['--persona-palette-colors-secondary-500'];
578
+ cssVars['--persona-accent'] = cssVars['--persona-semantic-colors-accent'] ?? cssVars['--persona-palette-colors-accent-500'];
579
+ cssVars['--persona-surface'] = cssVars['--persona-semantic-colors-surface'] ?? cssVars['--persona-palette-colors-gray-50'];
580
+ cssVars['--persona-background'] = cssVars['--persona-semantic-colors-background'] ?? cssVars['--persona-palette-colors-gray-50'];
581
+ cssVars['--persona-container'] = cssVars['--persona-semantic-colors-container'] ?? cssVars['--persona-palette-colors-gray-100'];
582
+ cssVars['--persona-text'] = cssVars['--persona-semantic-colors-text'] ?? cssVars['--persona-palette-colors-gray-900'];
583
+ cssVars['--persona-text-muted'] = cssVars['--persona-semantic-colors-text-muted'] ?? cssVars['--persona-palette-colors-gray-500'];
584
+ cssVars['--persona-text-inverse'] = cssVars['--persona-semantic-colors-text-inverse'] ?? cssVars['--persona-palette-colors-gray-50'];
585
+ cssVars['--persona-border'] = cssVars['--persona-semantic-colors-border'] ?? cssVars['--persona-palette-colors-gray-200'];
586
+ cssVars['--persona-divider'] = cssVars['--persona-semantic-colors-divider'] ?? cssVars['--persona-palette-colors-gray-200'];
587
+ cssVars['--persona-muted'] = cssVars['--persona-text-muted'];
588
+
589
+ cssVars['--persona-voice-recording-indicator'] = cssVars['--persona-components-voice-recording-indicator'] ?? cssVars['--persona-palette-colors-error-500'];
590
+ cssVars['--persona-voice-recording-bg'] = cssVars['--persona-components-voice-recording-background'] ?? cssVars['--persona-palette-colors-error-50'];
591
+ cssVars['--persona-voice-processing-icon'] = cssVars['--persona-components-voice-processing-icon'] ?? cssVars['--persona-palette-colors-primary-500'];
592
+ cssVars['--persona-voice-speaking-icon'] = cssVars['--persona-components-voice-speaking-icon'] ?? cssVars['--persona-palette-colors-success-500'];
593
+
594
+ cssVars['--persona-approval-bg'] = cssVars['--persona-components-approval-requested-background'] ?? cssVars['--persona-palette-colors-warning-50'];
595
+ cssVars['--persona-approval-border'] = cssVars['--persona-components-approval-requested-border'] ?? cssVars['--persona-palette-colors-warning-200'];
596
+ cssVars['--persona-approval-text'] = cssVars['--persona-components-approval-requested-text'] ?? cssVars['--persona-palette-colors-gray-900'];
597
+ cssVars['--persona-approval-approve-bg'] = cssVars['--persona-components-approval-approve-background'] ?? cssVars['--persona-palette-colors-success-500'];
598
+ cssVars['--persona-approval-deny-bg'] = cssVars['--persona-components-approval-deny-background'] ?? cssVars['--persona-palette-colors-error-500'];
599
+
600
+ cssVars['--persona-attachment-image-bg'] = cssVars['--persona-components-attachment-image-background'] ?? cssVars['--persona-palette-colors-gray-100'];
601
+ cssVars['--persona-attachment-image-border'] = cssVars['--persona-components-attachment-image-border'] ?? cssVars['--persona-palette-colors-gray-200'];
602
+
603
+ // Typography shorthand aliases
604
+ cssVars['--persona-font-family'] = cssVars['--persona-semantic-typography-fontFamily'] ?? cssVars['--persona-palette-typography-fontFamily-sans'];
605
+ cssVars['--persona-font-size'] = cssVars['--persona-semantic-typography-fontSize'] ?? cssVars['--persona-palette-typography-fontSize-base'];
606
+ cssVars['--persona-font-weight'] = cssVars['--persona-semantic-typography-fontWeight'] ?? cssVars['--persona-palette-typography-fontWeight-normal'];
607
+ cssVars['--persona-line-height'] = cssVars['--persona-semantic-typography-lineHeight'] ?? cssVars['--persona-palette-typography-lineHeight-normal'];
608
+
609
+ // Radius aliases used throughout the existing widget CSS.
610
+ cssVars['--persona-radius-sm'] = cssVars['--persona-palette-radius-sm'] ?? '0.125rem';
611
+ cssVars['--persona-radius-md'] = cssVars['--persona-palette-radius-md'] ?? '0.375rem';
612
+ cssVars['--persona-radius-lg'] = cssVars['--persona-palette-radius-lg'] ?? '0.5rem';
613
+ cssVars['--persona-radius-xl'] = cssVars['--persona-palette-radius-xl'] ?? '0.75rem';
614
+ cssVars['--persona-launcher-radius'] =
615
+ cssVars['--persona-components-launcher-borderRadius'] ??
616
+ cssVars['--persona-palette-radius-full'] ??
617
+ '9999px';
618
+ cssVars['--persona-button-radius'] =
619
+ cssVars['--persona-components-button-primary-borderRadius'] ??
620
+ cssVars['--persona-palette-radius-full'] ??
621
+ '9999px';
622
+ cssVars['--persona-panel-radius'] =
623
+ cssVars['--persona-components-panel-borderRadius'] ??
624
+ cssVars['--persona-radius-xl'] ??
625
+ '0.75rem';
626
+ cssVars['--persona-input-radius'] =
627
+ cssVars['--persona-components-input-borderRadius'] ??
628
+ cssVars['--persona-radius-lg'] ??
629
+ '0.5rem';
630
+ cssVars['--persona-message-user-radius'] =
631
+ cssVars['--persona-components-message-user-borderRadius'] ??
632
+ cssVars['--persona-radius-lg'] ??
633
+ '0.5rem';
634
+ cssVars['--persona-message-assistant-radius'] =
635
+ cssVars['--persona-components-message-assistant-borderRadius'] ??
636
+ cssVars['--persona-radius-lg'] ??
637
+ '0.5rem';
638
+
639
+ // Component-level color overrides — these map component tokens to
640
+ // dedicated CSS variables that the widget CSS reads for individual elements.
641
+ cssVars['--persona-header-bg'] =
642
+ cssVars['--persona-components-header-background'] ?? cssVars['--persona-surface'];
643
+ cssVars['--persona-header-border'] =
644
+ cssVars['--persona-components-header-border'] ?? cssVars['--persona-divider'];
645
+
646
+ cssVars['--persona-message-user-bg'] =
647
+ cssVars['--persona-components-message-user-background'] ?? cssVars['--persona-accent'];
648
+ cssVars['--persona-message-user-text'] =
649
+ cssVars['--persona-components-message-user-text'] ?? cssVars['--persona-text-inverse'];
650
+ cssVars['--persona-message-user-shadow'] =
651
+ cssVars['--persona-components-message-user-shadow'] ?? '0 5px 15px rgba(15, 23, 42, 0.08)';
652
+ cssVars['--persona-message-assistant-bg'] =
653
+ cssVars['--persona-components-message-assistant-background'] ?? cssVars['--persona-surface'];
654
+ cssVars['--persona-message-assistant-text'] =
655
+ cssVars['--persona-components-message-assistant-text'] ?? cssVars['--persona-text'];
656
+ cssVars['--persona-message-assistant-border'] =
657
+ cssVars['--persona-components-message-assistant-border'] ?? cssVars['--persona-border'];
658
+ cssVars['--persona-message-assistant-shadow'] =
659
+ cssVars['--persona-components-message-assistant-shadow'] ?? '0 1px 2px 0 rgb(0 0 0 / 0.05)';
660
+
661
+ cssVars['--persona-tool-bubble-shadow'] =
662
+ cssVars['--persona-components-toolBubble-shadow'] ?? '0 5px 15px rgba(15, 23, 42, 0.08)';
663
+ cssVars['--persona-reasoning-bubble-shadow'] =
664
+ cssVars['--persona-components-reasoningBubble-shadow'] ?? '0 5px 15px rgba(15, 23, 42, 0.08)';
665
+ cssVars['--persona-composer-shadow'] =
666
+ cssVars['--persona-components-composer-shadow'] ?? 'none';
667
+
668
+ cssVars['--persona-md-inline-code-bg'] =
669
+ cssVars['--persona-components-markdown-inlineCode-background'] ?? cssVars['--persona-container'];
670
+ cssVars['--persona-md-inline-code-color'] =
671
+ cssVars['--persona-components-markdown-inlineCode-foreground'] ?? cssVars['--persona-text'];
672
+
673
+ cssVars['--persona-md-link-color'] =
674
+ cssVars['--persona-components-markdown-link-foreground'] ??
675
+ cssVars['--persona-accent'] ??
676
+ '#3b82f6';
677
+
678
+ const mdH1Size = cssVars['--persona-components-markdown-heading-h1-fontSize'];
679
+ if (mdH1Size) cssVars['--persona-md-h1-size'] = mdH1Size;
680
+ const mdH1Weight = cssVars['--persona-components-markdown-heading-h1-fontWeight'];
681
+ if (mdH1Weight) cssVars['--persona-md-h1-weight'] = mdH1Weight;
682
+ const mdH2Size = cssVars['--persona-components-markdown-heading-h2-fontSize'];
683
+ if (mdH2Size) cssVars['--persona-md-h2-size'] = mdH2Size;
684
+ const mdH2Weight = cssVars['--persona-components-markdown-heading-h2-fontWeight'];
685
+ if (mdH2Weight) cssVars['--persona-md-h2-weight'] = mdH2Weight;
686
+
687
+ const mdProseFont = cssVars['--persona-components-markdown-prose-fontFamily'];
688
+ if (mdProseFont && mdProseFont !== 'inherit') {
689
+ cssVars['--persona-md-prose-font-family'] = mdProseFont;
690
+ }
691
+
692
+ return cssVars;
693
+ }
694
+
695
+ export function applyThemeVariables(element: HTMLElement, theme: PersonaTheme): void {
696
+ const cssVars = themeToCssVariables(theme);
697
+
698
+ for (const [name, value] of Object.entries(cssVars)) {
699
+ element.style.setProperty(name, value);
700
+ }
701
+ }