@runtypelabs/persona 1.48.0 → 2.0.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 +1055 -24
  5. package/dist/index.d.ts +1055 -24
  6. package/dist/index.global.js +111 -60
  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 +836 -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 +836 -513
  46. package/src/types/theme.ts +354 -0
  47. package/src/types.ts +314 -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 +179 -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 +125 -0
  68. package/src/utils/theme.ts +216 -60
  69. package/src/utils/tokens.ts +682 -0
@@ -0,0 +1,682 @@
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
+ },
275
+ assistant: {
276
+ background: 'semantic.colors.container',
277
+ text: 'semantic.colors.text',
278
+ borderRadius: 'palette.radius.lg',
279
+ border: 'semantic.colors.border',
280
+ shadow: 'palette.shadows.sm',
281
+ },
282
+ },
283
+ markdown: {
284
+ inlineCode: {
285
+ background: 'semantic.colors.container',
286
+ foreground: 'semantic.colors.text',
287
+ },
288
+ link: {
289
+ foreground: 'semantic.colors.accent',
290
+ },
291
+ prose: {
292
+ fontFamily: 'inherit',
293
+ },
294
+ },
295
+ voice: {
296
+ recording: {
297
+ indicator: 'palette.colors.error.500',
298
+ background: 'palette.colors.error.50',
299
+ border: 'palette.colors.error.200',
300
+ },
301
+ processing: {
302
+ icon: 'palette.colors.primary.500',
303
+ background: 'palette.colors.primary.50',
304
+ },
305
+ speaking: {
306
+ icon: 'palette.colors.success.500',
307
+ },
308
+ },
309
+ approval: {
310
+ requested: {
311
+ background: 'palette.colors.warning.50',
312
+ border: 'palette.colors.warning.200',
313
+ text: 'palette.colors.gray.900',
314
+ },
315
+ approve: {
316
+ background: 'palette.colors.success.500',
317
+ foreground: 'palette.colors.gray.50',
318
+ borderRadius: 'palette.radius.md',
319
+ padding: 'semantic.spacing.sm',
320
+ },
321
+ deny: {
322
+ background: 'palette.colors.error.500',
323
+ foreground: 'palette.colors.gray.50',
324
+ borderRadius: 'palette.radius.md',
325
+ padding: 'semantic.spacing.sm',
326
+ },
327
+ },
328
+ attachment: {
329
+ image: {
330
+ background: 'palette.colors.gray.100',
331
+ border: 'palette.colors.gray.200',
332
+ },
333
+ },
334
+ };
335
+
336
+ function resolveTokenValue(theme: PersonaTheme, path: string): string | undefined {
337
+ if (
338
+ !path.startsWith('palette.') &&
339
+ !path.startsWith('semantic.') &&
340
+ !path.startsWith('components.')
341
+ ) {
342
+ return path;
343
+ }
344
+
345
+ const parts = path.split('.');
346
+ let current: any = theme;
347
+
348
+ for (const part of parts) {
349
+ if (current === undefined || current === null) {
350
+ return undefined;
351
+ }
352
+ current = current[part];
353
+ }
354
+
355
+ if (
356
+ typeof current === 'string' &&
357
+ (current.startsWith('palette.') ||
358
+ current.startsWith('semantic.') ||
359
+ current.startsWith('components.'))
360
+ ) {
361
+ return resolveTokenValue(theme, current);
362
+ }
363
+
364
+ return current;
365
+ }
366
+
367
+ export function resolveTokens(theme: PersonaTheme): Record<string, ResolvedToken> {
368
+ const resolved: Record<string, ResolvedToken> = {};
369
+
370
+ function resolveObject(obj: any, prefix: string) {
371
+ for (const [key, value] of Object.entries(obj)) {
372
+ const path = `${prefix}.${key}`;
373
+
374
+ if (typeof value === 'string') {
375
+ const resolvedValue = resolveTokenValue(theme, value);
376
+ if (resolvedValue !== undefined) {
377
+ resolved[path] = {
378
+ path,
379
+ value: resolvedValue,
380
+ type:
381
+ prefix.includes('color')
382
+ ? 'color'
383
+ : prefix.includes('spacing')
384
+ ? 'spacing'
385
+ : prefix.includes('typography')
386
+ ? 'typography'
387
+ : prefix.includes('shadow')
388
+ ? 'shadow'
389
+ : prefix.includes('border')
390
+ ? 'border'
391
+ : 'color',
392
+ };
393
+ }
394
+ } else if (typeof value === 'object' && value !== null) {
395
+ resolveObject(value, path);
396
+ }
397
+ }
398
+ }
399
+
400
+ resolveObject(theme.palette, 'palette');
401
+ resolveObject(theme.semantic, 'semantic');
402
+ resolveObject(theme.components, 'components');
403
+
404
+ return resolved;
405
+ }
406
+
407
+ export function validateTheme(theme: Partial<PersonaTheme>): ThemeValidationResult {
408
+ const errors: ThemeValidationError[] = [];
409
+ const warnings: ThemeValidationError[] = [];
410
+
411
+ if (!theme.palette) {
412
+ errors.push({
413
+ path: 'palette',
414
+ message: 'Theme must include a palette',
415
+ severity: 'error',
416
+ });
417
+ }
418
+
419
+ if (!theme.semantic) {
420
+ warnings.push({
421
+ path: 'semantic',
422
+ message: 'No semantic tokens defined - defaults will be used',
423
+ severity: 'warning',
424
+ });
425
+ }
426
+
427
+ if (!theme.components) {
428
+ warnings.push({
429
+ path: 'components',
430
+ message: 'No component tokens defined - defaults will be used',
431
+ severity: 'warning',
432
+ });
433
+ }
434
+
435
+ return {
436
+ valid: errors.length === 0,
437
+ errors,
438
+ warnings,
439
+ };
440
+ }
441
+
442
+ function mergeRecords(
443
+ base: Record<string, unknown>,
444
+ override: Record<string, unknown>
445
+ ): Record<string, unknown> {
446
+ const result = { ...base };
447
+ for (const [key, value] of Object.entries(override)) {
448
+ const existing = result[key];
449
+ if (existing && typeof existing === 'object' && !Array.isArray(existing) &&
450
+ value && typeof value === 'object' && !Array.isArray(value)) {
451
+ result[key] = mergeRecords(
452
+ existing as Record<string, unknown>,
453
+ value as Record<string, unknown>
454
+ );
455
+ } else {
456
+ result[key] = value;
457
+ }
458
+ }
459
+ return result;
460
+ }
461
+
462
+ function deepMergeComponents(
463
+ base: ComponentTokens,
464
+ override?: Partial<ComponentTokens>
465
+ ): ComponentTokens {
466
+ if (!override) return base;
467
+ return mergeRecords(
468
+ base as unknown as Record<string, unknown>,
469
+ override as unknown as Record<string, unknown>
470
+ ) as unknown as ComponentTokens;
471
+ }
472
+
473
+ export function createTheme(
474
+ userConfig?: Partial<PersonaTheme>,
475
+ options: CreateThemeOptions = {}
476
+ ): PersonaTheme {
477
+ const baseTheme: PersonaTheme = {
478
+ palette: DEFAULT_PALETTE as PersonaTheme['palette'],
479
+ semantic: DEFAULT_SEMANTIC as PersonaTheme['semantic'],
480
+ components: DEFAULT_COMPONENTS as PersonaTheme['components'],
481
+ };
482
+
483
+ let theme: PersonaTheme = {
484
+ palette: {
485
+ ...baseTheme.palette,
486
+ ...userConfig?.palette,
487
+ colors: {
488
+ ...baseTheme.palette.colors,
489
+ ...userConfig?.palette?.colors,
490
+ },
491
+ spacing: {
492
+ ...baseTheme.palette.spacing,
493
+ ...userConfig?.palette?.spacing,
494
+ },
495
+ typography: {
496
+ ...baseTheme.palette.typography,
497
+ ...userConfig?.palette?.typography,
498
+ },
499
+ shadows: {
500
+ ...baseTheme.palette.shadows,
501
+ ...userConfig?.palette?.shadows,
502
+ },
503
+ borders: {
504
+ ...baseTheme.palette.borders,
505
+ ...userConfig?.palette?.borders,
506
+ },
507
+ radius: {
508
+ ...baseTheme.palette.radius,
509
+ ...userConfig?.palette?.radius,
510
+ },
511
+ },
512
+ semantic: {
513
+ ...baseTheme.semantic,
514
+ ...userConfig?.semantic,
515
+ colors: {
516
+ ...baseTheme.semantic.colors,
517
+ ...userConfig?.semantic?.colors,
518
+ interactive: {
519
+ ...baseTheme.semantic.colors.interactive,
520
+ ...userConfig?.semantic?.colors?.interactive,
521
+ },
522
+ feedback: {
523
+ ...baseTheme.semantic.colors.feedback,
524
+ ...userConfig?.semantic?.colors?.feedback,
525
+ },
526
+ },
527
+ spacing: {
528
+ ...baseTheme.semantic.spacing,
529
+ ...userConfig?.semantic?.spacing,
530
+ },
531
+ typography: {
532
+ ...baseTheme.semantic.typography,
533
+ ...userConfig?.semantic?.typography,
534
+ },
535
+ },
536
+ components: deepMergeComponents(baseTheme.components, userConfig?.components),
537
+ };
538
+
539
+ if (options.validate !== false) {
540
+ const validation = validateTheme(theme);
541
+ if (!validation.valid) {
542
+ throw new Error(
543
+ `Theme validation failed: ${validation.errors.map((e) => e.message).join(', ')}`
544
+ );
545
+ }
546
+ }
547
+
548
+ if (options.plugins) {
549
+ for (const plugin of options.plugins) {
550
+ theme = plugin.transform(theme);
551
+ }
552
+ }
553
+
554
+ return theme;
555
+ }
556
+
557
+ export function themeToCssVariables(theme: PersonaTheme): Record<string, string> {
558
+ const resolved = resolveTokens(theme);
559
+ const cssVars: Record<string, string> = {};
560
+
561
+ for (const [path, token] of Object.entries(resolved)) {
562
+ const varName = path.replace(/\./g, '-');
563
+ cssVars[`--persona-${varName}`] = token.value;
564
+ }
565
+
566
+ cssVars['--persona-primary'] = cssVars['--persona-semantic-colors-primary'] ?? cssVars['--persona-palette-colors-primary-500'];
567
+ cssVars['--persona-secondary'] = cssVars['--persona-semantic-colors-secondary'] ?? cssVars['--persona-palette-colors-secondary-500'];
568
+ cssVars['--persona-accent'] = cssVars['--persona-semantic-colors-accent'] ?? cssVars['--persona-palette-colors-accent-500'];
569
+ cssVars['--persona-surface'] = cssVars['--persona-semantic-colors-surface'] ?? cssVars['--persona-palette-colors-gray-50'];
570
+ cssVars['--persona-background'] = cssVars['--persona-semantic-colors-background'] ?? cssVars['--persona-palette-colors-gray-50'];
571
+ cssVars['--persona-container'] = cssVars['--persona-semantic-colors-container'] ?? cssVars['--persona-palette-colors-gray-100'];
572
+ cssVars['--persona-text'] = cssVars['--persona-semantic-colors-text'] ?? cssVars['--persona-palette-colors-gray-900'];
573
+ cssVars['--persona-text-muted'] = cssVars['--persona-semantic-colors-text-muted'] ?? cssVars['--persona-palette-colors-gray-500'];
574
+ cssVars['--persona-text-inverse'] = cssVars['--persona-semantic-colors-text-inverse'] ?? cssVars['--persona-palette-colors-gray-50'];
575
+ cssVars['--persona-border'] = cssVars['--persona-semantic-colors-border'] ?? cssVars['--persona-palette-colors-gray-200'];
576
+ cssVars['--persona-divider'] = cssVars['--persona-semantic-colors-divider'] ?? cssVars['--persona-palette-colors-gray-200'];
577
+ cssVars['--persona-muted'] = cssVars['--persona-text-muted'];
578
+
579
+ cssVars['--persona-voice-recording-indicator'] = cssVars['--persona-components-voice-recording-indicator'] ?? cssVars['--persona-palette-colors-error-500'];
580
+ cssVars['--persona-voice-recording-bg'] = cssVars['--persona-components-voice-recording-background'] ?? cssVars['--persona-palette-colors-error-50'];
581
+ cssVars['--persona-voice-processing-icon'] = cssVars['--persona-components-voice-processing-icon'] ?? cssVars['--persona-palette-colors-primary-500'];
582
+ cssVars['--persona-voice-speaking-icon'] = cssVars['--persona-components-voice-speaking-icon'] ?? cssVars['--persona-palette-colors-success-500'];
583
+
584
+ cssVars['--persona-approval-bg'] = cssVars['--persona-components-approval-requested-background'] ?? cssVars['--persona-palette-colors-warning-50'];
585
+ cssVars['--persona-approval-border'] = cssVars['--persona-components-approval-requested-border'] ?? cssVars['--persona-palette-colors-warning-200'];
586
+ cssVars['--persona-approval-text'] = cssVars['--persona-components-approval-requested-text'] ?? cssVars['--persona-palette-colors-gray-900'];
587
+ cssVars['--persona-approval-approve-bg'] = cssVars['--persona-components-approval-approve-background'] ?? cssVars['--persona-palette-colors-success-500'];
588
+ cssVars['--persona-approval-deny-bg'] = cssVars['--persona-components-approval-deny-background'] ?? cssVars['--persona-palette-colors-error-500'];
589
+
590
+ cssVars['--persona-attachment-image-bg'] = cssVars['--persona-components-attachment-image-background'] ?? cssVars['--persona-palette-colors-gray-100'];
591
+ cssVars['--persona-attachment-image-border'] = cssVars['--persona-components-attachment-image-border'] ?? cssVars['--persona-palette-colors-gray-200'];
592
+
593
+ // Typography shorthand aliases
594
+ cssVars['--persona-font-family'] = cssVars['--persona-semantic-typography-fontFamily'] ?? cssVars['--persona-palette-typography-fontFamily-sans'];
595
+ cssVars['--persona-font-size'] = cssVars['--persona-semantic-typography-fontSize'] ?? cssVars['--persona-palette-typography-fontSize-base'];
596
+ cssVars['--persona-font-weight'] = cssVars['--persona-semantic-typography-fontWeight'] ?? cssVars['--persona-palette-typography-fontWeight-normal'];
597
+ cssVars['--persona-line-height'] = cssVars['--persona-semantic-typography-lineHeight'] ?? cssVars['--persona-palette-typography-lineHeight-normal'];
598
+
599
+ // Radius aliases used throughout the existing widget CSS.
600
+ cssVars['--persona-radius-sm'] = cssVars['--persona-palette-radius-sm'] ?? '0.125rem';
601
+ cssVars['--persona-radius-md'] = cssVars['--persona-palette-radius-md'] ?? '0.375rem';
602
+ cssVars['--persona-radius-lg'] = cssVars['--persona-palette-radius-lg'] ?? '0.5rem';
603
+ cssVars['--persona-radius-xl'] = cssVars['--persona-palette-radius-xl'] ?? '0.75rem';
604
+ cssVars['--persona-launcher-radius'] =
605
+ cssVars['--persona-components-launcher-borderRadius'] ??
606
+ cssVars['--persona-palette-radius-full'] ??
607
+ '9999px';
608
+ cssVars['--persona-button-radius'] =
609
+ cssVars['--persona-components-button-primary-borderRadius'] ??
610
+ cssVars['--persona-palette-radius-full'] ??
611
+ '9999px';
612
+ cssVars['--persona-panel-radius'] =
613
+ cssVars['--persona-components-panel-borderRadius'] ??
614
+ cssVars['--persona-radius-xl'] ??
615
+ '0.75rem';
616
+ cssVars['--persona-input-radius'] =
617
+ cssVars['--persona-components-input-borderRadius'] ??
618
+ cssVars['--persona-radius-lg'] ??
619
+ '0.5rem';
620
+ cssVars['--persona-message-user-radius'] =
621
+ cssVars['--persona-components-message-user-borderRadius'] ??
622
+ cssVars['--persona-radius-lg'] ??
623
+ '0.5rem';
624
+ cssVars['--persona-message-assistant-radius'] =
625
+ cssVars['--persona-components-message-assistant-borderRadius'] ??
626
+ cssVars['--persona-radius-lg'] ??
627
+ '0.5rem';
628
+
629
+ // Component-level color overrides — these map component tokens to
630
+ // dedicated CSS variables that the widget CSS reads for individual elements.
631
+ cssVars['--persona-header-bg'] =
632
+ cssVars['--persona-components-header-background'] ?? cssVars['--persona-surface'];
633
+ cssVars['--persona-header-border'] =
634
+ cssVars['--persona-components-header-border'] ?? cssVars['--persona-divider'];
635
+
636
+ cssVars['--persona-message-user-bg'] =
637
+ cssVars['--persona-components-message-user-background'] ?? cssVars['--persona-accent'];
638
+ cssVars['--persona-message-user-text'] =
639
+ cssVars['--persona-components-message-user-text'] ?? cssVars['--persona-text-inverse'];
640
+ cssVars['--persona-message-assistant-bg'] =
641
+ cssVars['--persona-components-message-assistant-background'] ?? cssVars['--persona-surface'];
642
+ cssVars['--persona-message-assistant-text'] =
643
+ cssVars['--persona-components-message-assistant-text'] ?? cssVars['--persona-text'];
644
+ cssVars['--persona-message-assistant-border'] =
645
+ cssVars['--persona-components-message-assistant-border'] ?? cssVars['--persona-border'];
646
+ cssVars['--persona-message-assistant-shadow'] =
647
+ cssVars['--persona-components-message-assistant-shadow'] ?? '0 1px 2px 0 rgb(0 0 0 / 0.05)';
648
+
649
+ cssVars['--persona-md-inline-code-bg'] =
650
+ cssVars['--persona-components-markdown-inlineCode-background'] ?? cssVars['--persona-container'];
651
+ cssVars['--persona-md-inline-code-color'] =
652
+ cssVars['--persona-components-markdown-inlineCode-foreground'] ?? cssVars['--persona-text'];
653
+
654
+ cssVars['--persona-md-link-color'] =
655
+ cssVars['--persona-components-markdown-link-foreground'] ??
656
+ cssVars['--persona-accent'] ??
657
+ '#3b82f6';
658
+
659
+ const mdH1Size = cssVars['--persona-components-markdown-heading-h1-fontSize'];
660
+ if (mdH1Size) cssVars['--persona-md-h1-size'] = mdH1Size;
661
+ const mdH1Weight = cssVars['--persona-components-markdown-heading-h1-fontWeight'];
662
+ if (mdH1Weight) cssVars['--persona-md-h1-weight'] = mdH1Weight;
663
+ const mdH2Size = cssVars['--persona-components-markdown-heading-h2-fontSize'];
664
+ if (mdH2Size) cssVars['--persona-md-h2-size'] = mdH2Size;
665
+ const mdH2Weight = cssVars['--persona-components-markdown-heading-h2-fontWeight'];
666
+ if (mdH2Weight) cssVars['--persona-md-h2-weight'] = mdH2Weight;
667
+
668
+ const mdProseFont = cssVars['--persona-components-markdown-prose-fontFamily'];
669
+ if (mdProseFont && mdProseFont !== 'inherit') {
670
+ cssVars['--persona-md-prose-font-family'] = mdProseFont;
671
+ }
672
+
673
+ return cssVars;
674
+ }
675
+
676
+ export function applyThemeVariables(element: HTMLElement, theme: PersonaTheme): void {
677
+ const cssVars = themeToCssVariables(theme);
678
+
679
+ for (const [name, value] of Object.entries(cssVars)) {
680
+ element.style.setProperty(name, value);
681
+ }
682
+ }