@runtypelabs/persona 4.23.1 → 4.24.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 (158) hide show
  1. package/dist/animations/glyph-cycle.d.cts +1 -1
  2. package/dist/animations/glyph-cycle.d.ts +1 -1
  3. package/dist/animations/{types-9Vxm-PPz.d.cts → types-Db61aI9J.d.cts} +25 -2
  4. package/dist/animations/{types-9Vxm-PPz.d.ts → types-Db61aI9J.d.ts} +25 -2
  5. package/dist/animations/wipe.d.cts +1 -1
  6. package/dist/animations/wipe.d.ts +1 -1
  7. package/dist/animations-extra.d.cts +25 -2
  8. package/dist/animations-extra.d.ts +25 -2
  9. package/dist/approval-ui-3NMYNXBD.js +1 -0
  10. package/dist/approval-ui.cjs +2 -2
  11. package/dist/approval-ui.d.cts +261 -40
  12. package/dist/approval-ui.d.ts +261 -40
  13. package/dist/approval-ui.js +2 -2
  14. package/dist/{artifacts-ui-F4N6VMKF.js → artifacts-ui-IGB5YKLO.js} +4 -4
  15. package/dist/artifacts-ui.d.cts +256 -28
  16. package/dist/artifacts-ui.d.ts +256 -28
  17. package/dist/{chunk-NIKYKSNZ.js → chunk-7CO4T5KB.js} +1 -1
  18. package/dist/codegen.cjs +11 -11
  19. package/dist/codegen.js +13 -13
  20. package/dist/context-mentions.d.cts +256 -28
  21. package/dist/context-mentions.d.ts +256 -28
  22. package/dist/event-stream-view.d.cts +256 -28
  23. package/dist/event-stream-view.d.ts +256 -28
  24. package/dist/forms-ui.d.cts +291 -30
  25. package/dist/forms-ui.d.ts +291 -30
  26. package/dist/history-view.d.cts +25 -2
  27. package/dist/history-view.d.ts +25 -2
  28. package/dist/index.cjs +70 -69
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +337 -51
  31. package/dist/index.d.ts +337 -51
  32. package/dist/index.global.js +57 -54
  33. package/dist/index.global.js.map +1 -1
  34. package/dist/index.js +70 -69
  35. package/dist/index.js.map +1 -1
  36. package/dist/install.global.js +1 -1
  37. package/dist/install.global.js.map +1 -1
  38. package/dist/launcher.global.js +2 -2
  39. package/dist/launcher.global.js.map +1 -1
  40. package/dist/session-reconnect.d.cts +256 -28
  41. package/dist/session-reconnect.d.ts +256 -28
  42. package/dist/smart-dom-reader.d.cts +256 -28
  43. package/dist/smart-dom-reader.d.ts +256 -28
  44. package/dist/theme-editor-preview.cjs +74 -73
  45. package/dist/theme-editor-preview.d.cts +263 -28
  46. package/dist/theme-editor-preview.d.ts +263 -28
  47. package/dist/theme-editor-preview.js +51 -50
  48. package/dist/theme-editor.cjs +10 -10
  49. package/dist/theme-editor.d.cts +276 -30
  50. package/dist/theme-editor.d.ts +276 -30
  51. package/dist/theme-editor.js +10 -10
  52. package/dist/theme-reference.cjs +1 -1
  53. package/dist/theme-reference.d.cts +1522 -11
  54. package/dist/theme-reference.d.ts +1522 -11
  55. package/dist/theme-reference.js +1 -1
  56. package/dist/widget.css +1 -1
  57. package/package.json +3 -2
  58. package/src/__fixtures__/defaults-parity.v4.json +776 -0
  59. package/src/approval-ui-entry.ts +0 -1
  60. package/src/approval-ui-loader.ts +5 -4
  61. package/src/approval-ui.ts +0 -1
  62. package/src/client-identity-proof.test.ts +193 -0
  63. package/src/client-session-warm.test.ts +216 -0
  64. package/src/client-visitor-history.test.ts +6 -0
  65. package/src/client.tool-pair-replay.test.ts +325 -0
  66. package/src/client.ts +154 -61
  67. package/src/codegen.test.ts +24 -0
  68. package/src/components/activity-row.test.ts +100 -0
  69. package/src/components/activity-row.ts +136 -0
  70. package/src/components/approval-actions.test.ts +8 -5
  71. package/src/components/approval-actions.ts +19 -6
  72. package/src/components/approval-bubble.test.ts +56 -9
  73. package/src/components/approval-bubble.ts +36 -41
  74. package/src/components/composer-builder.ts +5 -2
  75. package/src/components/composer-parts.ts +10 -2
  76. package/src/components/header-builder.test.ts +32 -0
  77. package/src/components/header-builder.ts +7 -4
  78. package/src/components/header-layouts.test.ts +12 -0
  79. package/src/components/header-layouts.ts +3 -2
  80. package/src/components/launcher.ts +11 -2
  81. package/src/components/message-bubble.test.ts +22 -0
  82. package/src/components/message-bubble.ts +7 -3
  83. package/src/components/panel.test.ts +52 -1
  84. package/src/components/panel.ts +47 -8
  85. package/src/components/pill-composer-builder.test.ts +2 -0
  86. package/src/components/pill-composer-builder.ts +0 -1
  87. package/src/components/reasoning-bubble.ts +29 -17
  88. package/src/components/suggestions.test.ts +1 -0
  89. package/src/components/suggestions.ts +0 -16
  90. package/src/components/tool-bubble.ts +131 -114
  91. package/src/components/tool-details.test.ts +87 -0
  92. package/src/components/tool-details.ts +102 -0
  93. package/src/components/welcome.test.ts +8 -0
  94. package/src/components/welcome.ts +4 -2
  95. package/src/defaults-parity.test.ts +158 -0
  96. package/src/defaults.test.ts +69 -0
  97. package/src/defaults.ts +128 -49
  98. package/src/generated/runtype-openapi-contract.ts +28 -13
  99. package/src/index-core.ts +2 -0
  100. package/src/install.test.ts +24 -0
  101. package/src/install.ts +3 -1
  102. package/src/launcher-global.ts +2 -1
  103. package/src/launcher-variants.test.ts +52 -0
  104. package/src/runtime/init-update-reset.test.ts +21 -0
  105. package/src/session.ts +61 -9
  106. package/src/styles/widget-styles.test.ts +19 -0
  107. package/src/styles/widget.css +295 -23
  108. package/src/theme-editor/index.ts +2 -0
  109. package/src/theme-editor/preview-utils.test.ts +8 -0
  110. package/src/theme-editor/preview-utils.ts +7 -3
  111. package/src/theme-editor/sections.test.ts +31 -0
  112. package/src/theme-editor/sections.ts +55 -1
  113. package/src/theme-editor/state-preferences.test.ts +49 -0
  114. package/src/theme-editor/state.ts +73 -15
  115. package/src/theme-reference.test.ts +20 -0
  116. package/src/theme-reference.ts +11 -5
  117. package/src/tool-call-display-defaults.test.ts +33 -1
  118. package/src/types/theme.ts +80 -3
  119. package/src/types.ts +179 -23
  120. package/src/ui.activity-row.test.ts +91 -0
  121. package/src/ui.approval-plugin.test.ts +63 -0
  122. package/src/ui.composer-compact.test.ts +24 -2
  123. package/src/ui.composer-placement.test.ts +23 -0
  124. package/src/ui.detached-panel.test.ts +33 -6
  125. package/src/ui.docked.test.ts +19 -0
  126. package/src/ui.header-update-stability.test.ts +25 -0
  127. package/src/ui.identity-proof.test.ts +58 -0
  128. package/src/ui.launcher-teaser.test.ts +25 -0
  129. package/src/ui.message-width.test.ts +17 -2
  130. package/src/ui.reasoning-display.test.ts +92 -0
  131. package/src/ui.rebuild-composer.test.ts +20 -3
  132. package/src/ui.scroll.test.ts +32 -0
  133. package/src/ui.session-init.test.ts +312 -0
  134. package/src/ui.status-indicator.test.ts +82 -0
  135. package/src/ui.suggest-replies.test.ts +11 -0
  136. package/src/ui.ts +363 -74
  137. package/src/ui.v5-defaults.test.ts +144 -0
  138. package/src/ui.welcome.test.ts +92 -3
  139. package/src/utils/__snapshots__/tokens-parity.test.ts.snap +811 -140
  140. package/src/utils/code-generators.ts +14 -0
  141. package/src/utils/composer-compact.test.ts +0 -2
  142. package/src/utils/composer-compact.ts +2 -4
  143. package/src/utils/composer-placement.ts +2 -3
  144. package/src/utils/config-merge.test.ts +81 -5
  145. package/src/utils/config-merge.ts +13 -3
  146. package/src/utils/defaults-provenance.ts +67 -0
  147. package/src/utils/dock.ts +7 -1
  148. package/src/utils/launcher-variant.ts +16 -0
  149. package/src/utils/panel-config.ts +97 -0
  150. package/src/utils/status-indicator.ts +39 -0
  151. package/src/utils/theme.test.ts +182 -0
  152. package/src/utils/theme.ts +131 -18
  153. package/src/utils/tokens.test.ts +122 -1
  154. package/src/utils/tokens.ts +177 -17
  155. package/src/utils/tool-pair-replay.ts +100 -0
  156. package/src/welcome.test.ts +23 -0
  157. package/src/welcome.ts +16 -9
  158. package/dist/approval-ui-K7R5Q5OC.js +0 -1
@@ -1,9 +1,10 @@
1
1
  /** Headless state management for the theme editor (no DOM, no localStorage, no side effects) */
2
2
 
3
3
  import type { AgentWidgetConfig } from '../types';
4
- import type { PersonaTheme } from '../types/theme';
4
+ import type { DeepPartial, PersonaTheme } from '../types/theme';
5
5
  import { createTheme } from '../utils/theme';
6
- import { DEFAULT_WIDGET_CONFIG } from '../defaults';
6
+ import { resolveDefaults, resolveDefaultsVersion } from '../defaults';
7
+ import { deepMerge } from '../utils/deep-merge';
7
8
  import type { ConfiguratorSnapshot, ConfigChangeListener } from './types';
8
9
 
9
10
  // ─── Dot-path utilities ─────────────────────────────────────────
@@ -65,18 +66,29 @@ export class ThemeEditorState {
65
66
  private history: ConfiguratorSnapshot[] = [];
66
67
  private historyIndex = -1;
67
68
  private suppressHistory = false;
69
+ private authoredConfig: Partial<AgentWidgetConfig> = {};
70
+ private authoredTheme: DeepPartial<PersonaTheme> = {};
71
+ private mergeDefaults = true;
72
+ private snapshotIntent = new WeakMap<ConfiguratorSnapshot, { config: Partial<AgentWidgetConfig>; theme: DeepPartial<PersonaTheme> }>();
68
73
 
69
74
  constructor(
70
- initialTheme?: Partial<PersonaTheme>,
75
+ initialTheme?: DeepPartial<PersonaTheme>,
71
76
  initialConfig?: Partial<AgentWidgetConfig>,
72
77
  options?: { mergeDefaults?: boolean }
73
78
  ) {
74
79
  const mergeDefaults = options?.mergeDefaults ?? true;
80
+ this.mergeDefaults = mergeDefaults;
81
+ this.authoredConfig = { ...initialConfig };
82
+ this.authoredTheme = initialTheme ?? {};
83
+ const defaults = resolveDefaults(initialConfig);
75
84
  this.config = (mergeDefaults
76
- ? { ...DEFAULT_WIDGET_CONFIG, ...initialConfig }
77
- : (initialConfig ?? DEFAULT_WIDGET_CONFIG)
85
+ ? { ...defaults, ...initialConfig }
86
+ : (initialConfig ?? defaults)
78
87
  ) as AgentWidgetConfig;
79
- this.theme = createTheme(initialTheme, { validate: false });
88
+ this.theme = createTheme(initialTheme, {
89
+ validate: false,
90
+ future: this.config.future,
91
+ });
80
92
  this.syncThemeIntoConfig();
81
93
  this.pushHistorySnapshot(this.exportSnapshot(), true);
82
94
  }
@@ -116,13 +128,15 @@ export class ThemeEditorState {
116
128
  * - everything else → writes into AgentWidgetConfig
117
129
  */
118
130
  set(path: string, value: unknown): void {
131
+ const previousVersion = resolveDefaultsVersion(this.config);
132
+ this.recordIntent(path, value);
119
133
  if (path.startsWith('theme.')) {
120
134
  const themePath = path.replace('theme.', '');
121
135
  this.theme = setByPath(this.theme, themePath, value) as PersonaTheme;
122
136
  this.syncThemeIntoConfig();
123
137
  } else if (path.startsWith('darkTheme.')) {
124
138
  const themePath = path.replace('darkTheme.', '');
125
- const dark = this.config.darkTheme ?? createTheme();
139
+ const dark = this.config.darkTheme ?? createTheme(undefined, { future: this.config.future });
126
140
  this.config = {
127
141
  ...this.config,
128
142
  darkTheme: setByPath(dark, themePath, value) as AgentWidgetConfig['darkTheme'],
@@ -131,12 +145,15 @@ export class ThemeEditorState {
131
145
  this.config = setByPath(this.config, path, value) as AgentWidgetConfig;
132
146
  }
133
147
 
148
+ this.rebaseDefaults(previousVersion);
134
149
  this.recordHistory();
135
150
  this.notifyListeners();
136
151
  }
137
152
 
138
153
  /** Delete a dot-path and prune empty parents so sparse preferences inherit. */
139
154
  unset(path: string): void {
155
+ const previousVersion = resolveDefaultsVersion(this.config);
156
+ this.recordIntent(path, undefined, true);
140
157
  if (path.startsWith('theme.')) {
141
158
  const themePath = path.replace('theme.', '');
142
159
  this.theme = unsetByPath(this.theme, themePath) as PersonaTheme;
@@ -153,24 +170,27 @@ export class ThemeEditorState {
153
170
  } else {
154
171
  this.config = unsetByPath(this.config, path) as AgentWidgetConfig;
155
172
  }
173
+ this.rebaseDefaults(previousVersion);
156
174
  this.recordHistory();
157
175
  this.notifyListeners();
158
176
  }
159
177
 
160
178
  /** Batch-set multiple paths at once */
161
179
  setBatch(updates: Record<string, unknown>): void {
180
+ const previousVersion = resolveDefaultsVersion(this.config);
162
181
  let themeChanged = false;
163
182
  let darkThemeChanged = false;
164
183
  let configChanged = false;
165
184
 
166
185
  for (const [path, value] of Object.entries(updates)) {
186
+ this.recordIntent(path, value);
167
187
  if (path.startsWith('theme.')) {
168
188
  const themePath = path.replace('theme.', '');
169
189
  this.theme = setByPath(this.theme, themePath, value) as PersonaTheme;
170
190
  themeChanged = true;
171
191
  } else if (path.startsWith('darkTheme.')) {
172
192
  const themePath = path.replace('darkTheme.', '');
173
- const dark = this.config.darkTheme ?? createTheme();
193
+ const dark = this.config.darkTheme ?? createTheme(undefined, { future: this.config.future });
174
194
  this.config = {
175
195
  ...this.config,
176
196
  darkTheme: setByPath(dark, themePath, value) as AgentWidgetConfig['darkTheme'],
@@ -185,6 +205,7 @@ export class ThemeEditorState {
185
205
  if (themeChanged) {
186
206
  this.syncThemeIntoConfig();
187
207
  }
208
+ this.rebaseDefaults(previousVersion);
188
209
  if (themeChanged || darkThemeChanged || configChanged) {
189
210
  this.recordHistory();
190
211
  this.notifyListeners();
@@ -193,6 +214,7 @@ export class ThemeEditorState {
193
214
 
194
215
  /** Replace the entire theme */
195
216
  setTheme(theme: PersonaTheme): void {
217
+ this.authoredTheme = theme;
196
218
  this.theme = theme;
197
219
  this.syncThemeIntoConfig();
198
220
  this.recordHistory();
@@ -201,6 +223,8 @@ export class ThemeEditorState {
201
223
 
202
224
  /** Replace the entire config (for preset loading) */
203
225
  setFullConfig(config: AgentWidgetConfig, theme?: PersonaTheme): void {
226
+ this.authoredConfig = { ...config };
227
+ if (theme) this.authoredTheme = theme;
204
228
  this.config = { ...config };
205
229
  if (theme) {
206
230
  this.theme = theme;
@@ -221,21 +245,27 @@ export class ThemeEditorState {
221
245
  if ('config' in parsed || 'theme' in parsed || parsed.version === 2) {
222
246
  const config = (parsed.config ?? this.config) as AgentWidgetConfig;
223
247
  const theme = createTheme(
224
- (parsed.theme ?? this.theme) as Partial<PersonaTheme>,
225
- { validate: false }
248
+ (parsed.theme ?? this.theme) as DeepPartial<PersonaTheme>,
249
+ { validate: false, future: config.future }
226
250
  );
227
251
  this.setFullConfig(config, theme);
228
252
  return;
229
253
  }
230
254
 
231
- const theme = createTheme(parsed as Partial<PersonaTheme>, { validate: false });
255
+ const theme = createTheme(parsed as DeepPartial<PersonaTheme>, { validate: false, future: this.config.future });
232
256
  this.setTheme(theme);
233
257
  }
234
258
 
235
259
  /** Reset to defaults */
236
260
  resetToDefaults(): void {
237
- this.config = { ...DEFAULT_WIDGET_CONFIG } as AgentWidgetConfig;
238
- this.theme = createTheme();
261
+ const future = this.config.future;
262
+ this.authoredConfig = future ? { future } : {};
263
+ this.authoredTheme = {};
264
+ this.config = {
265
+ ...resolveDefaults({ future }),
266
+ ...(future ? { future } : {}),
267
+ } as AgentWidgetConfig;
268
+ this.theme = createTheme(undefined, { future: this.config.future });
239
269
  this.syncThemeIntoConfig();
240
270
  this.history = [];
241
271
  this.historyIndex = -1;
@@ -295,6 +325,27 @@ export class ThemeEditorState {
295
325
 
296
326
  // ─── Private ────────────────────────────────────────────────
297
327
 
328
+ private recordIntent(path: string, value: unknown, remove = false): void {
329
+ const write = (target: object, key: string) => remove || value === undefined
330
+ ? unsetByPath(target, key) : setByPath(target, key, value);
331
+ if (path.startsWith('theme.')) {
332
+ this.authoredTheme = write(this.authoredTheme, path.slice(6)) as DeepPartial<PersonaTheme>;
333
+ } else if (path === 'theme') {
334
+ this.authoredTheme = (value ?? {}) as DeepPartial<PersonaTheme>;
335
+ } else {
336
+ this.authoredConfig = write(this.authoredConfig, path) as Partial<AgentWidgetConfig>;
337
+ }
338
+ }
339
+
340
+ private rebaseDefaults(previousVersion: string): void {
341
+ if (previousVersion === resolveDefaultsVersion(this.config)) return;
342
+ this.config = (this.mergeDefaults
343
+ ? deepMerge(resolveDefaults(this.authoredConfig), this.authoredConfig)
344
+ : { ...this.authoredConfig }) as AgentWidgetConfig;
345
+ this.theme = createTheme(this.authoredTheme, { validate: false, future: this.config.future });
346
+ this.syncThemeIntoConfig();
347
+ }
348
+
298
349
  private syncThemeIntoConfig(): void {
299
350
  this.config = {
300
351
  ...this.config,
@@ -314,6 +365,7 @@ export class ThemeEditorState {
314
365
 
315
366
  private pushHistorySnapshot(snapshot: ConfiguratorSnapshot, replaceCurrent = false): void {
316
367
  if (this.suppressHistory) return;
368
+ this.snapshotIntent.set(snapshot, { config: this.authoredConfig, theme: this.authoredTheme });
317
369
 
318
370
  const serialized = JSON.stringify(snapshot);
319
371
  const currentSerialized =
@@ -326,7 +378,10 @@ export class ThemeEditorState {
326
378
  return;
327
379
  }
328
380
 
329
- if (serialized === currentSerialized) return;
381
+ const currentIntent = this.historyIndex >= 0
382
+ ? this.snapshotIntent.get(this.history[this.historyIndex]) : undefined;
383
+ if (serialized === currentSerialized &&
384
+ JSON.stringify(this.snapshotIntent.get(snapshot)) === JSON.stringify(currentIntent)) return;
330
385
 
331
386
  this.history = this.history.slice(0, this.historyIndex + 1);
332
387
  this.history.push(snapshot);
@@ -335,8 +390,11 @@ export class ThemeEditorState {
335
390
 
336
391
  private restoreSnapshot(snapshot: ConfiguratorSnapshot): void {
337
392
  this.suppressHistory = true;
393
+ const intent = this.snapshotIntent.get(snapshot);
394
+ this.authoredConfig = intent?.config ?? snapshot.config as Partial<AgentWidgetConfig>;
395
+ this.authoredTheme = intent?.theme ?? snapshot.theme;
338
396
  this.config = snapshot.config as unknown as AgentWidgetConfig;
339
- this.theme = createTheme(snapshot.theme, { validate: false });
397
+ this.theme = createTheme(snapshot.theme, { validate: false, future: this.config.future });
340
398
  this.syncThemeIntoConfig();
341
399
  this.suppressHistory = false;
342
400
  this.notifyListeners();
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { getThemeReference } from "./theme-reference";
4
+ import { resolveDefaults } from "./defaults";
5
+ import { resolveThemeDefaults } from "./utils/tokens";
6
+
7
+ describe.each([false, true])("theme reference defaults versioning (v5=%s)", (v5Defaults) => {
8
+ it("reads its documented defaults from the shared future-aware resolvers", () => {
9
+ const reference = getThemeReference({ v5Defaults });
10
+ const future = { v5Defaults };
11
+
12
+ expect(reference.defaultsVersion).toBe(v5Defaults ? "v5" : "v4");
13
+ expect(reference.themeDefaultsVersion).toBe(v5Defaults ? "v5" : "v4");
14
+ expect(reference.defaultColorPalette).toBe(
15
+ resolveThemeDefaults(future).palette.colors
16
+ );
17
+ expect(reference.defaultRadius).toBe(resolveThemeDefaults(future).palette.radius);
18
+ expect(reference.defaultLauncher).toEqual(resolveDefaults({ future }).launcher);
19
+ });
20
+ });
@@ -5,7 +5,8 @@
5
5
  * out of the IIFE widget bundle. Intended for AI/MCP tool consumption.
6
6
  */
7
7
 
8
- import { DEFAULT_PALETTE } from './utils/tokens'
8
+ import { resolveThemeDefaults, resolveThemeDefaultsVersion } from './utils/tokens'
9
+ import { resolveDefaults, resolveDefaultsVersion } from './defaults'
9
10
  import type { DeepPartial, PersonaTheme } from './types/theme'
10
11
 
11
12
  // ---------------------------------------------------------------------------
@@ -173,7 +174,7 @@ export const THEME_TOKEN_DOCS = {
173
174
  'Shared scrollbar appearance for every scroller: thumb (default semantic.colors.border), track (default transparent).',
174
175
  reasoningBubble: 'shadow: reasoning/thinking row box-shadow.',
175
176
  composer:
176
- 'Message input form: shadow (box-shadow), borderColor, padding (form inset), gap (textarea row to actions row), fontSize and lineHeight (textarea type), controlSize (edge of every icon control in the action row: attachment, mention, mic, action buttons, overflow trigger, icon-mode send; default 40px) and controlIconSize (glyph inside them, default 24px). Per-control config keys (sendButton.size, voiceRecognition.iconSize) override controlSize; coarse-pointer devices floor the hit area at 40px. overlayBand paints the band behind an overlaid footer (composer.placement: "overlay"). segmented styles a segmented mode-group track (trackBackground, trackBorderRadius, padding, activeBackground, activeForeground, activeShadow, inactiveForeground, itemPadding); every key is optional and the stylesheet owns the defaults. modelPicker styles the model picker (background, hoverBackground, borderColor, borderRadius and labelColor on the closed control, which the native select and the popover trigger share; menuBackground, menuBorderRadius, rowHoverBackground, descriptionColor and suffixColor on the popover only), also all optional. overflowMenu styles the composer overflow "+" menu panel (background, borderColor, borderRadius, foreground, shadow), also all optional.',
177
+ 'Message input form: sendIconSize (send/stop glyph; explicit sendButton.iconSize wins), sendButtonRadius (icon-mode submit radius), footerBorder (footer top border shorthand; none hides it), shadow (box-shadow), borderColor, focusBorderColor (focused form border; V4 keeps the resting border, V5 uses a stronger neutral mix), focusRing (CSS outline shorthand, 1px offset; V4 none, V5 a thin neutral ring; preserves shadow and causes no layout shift), padding (form inset), gap (textarea row to actions row), fontSize and lineHeight (textarea type), controlSize (edge of every icon control in the action row: attachment, mention, mic, action buttons, overflow trigger, icon-mode send; default 40px) and controlIconSize (glyph inside them, default 24px). Per-control config keys (sendButton.size, voiceRecognition.iconSize) override controlSize; coarse-pointer devices floor the hit area at 40px. overlayBand paints the band behind an overlaid footer (composer.placement: "overlay"). segmented styles a segmented mode-group track (trackBackground, trackBorderRadius, padding, activeBackground, activeForeground, activeShadow, inactiveForeground, itemPadding); every key is optional and the stylesheet owns the defaults. modelPicker styles the model picker (background, hoverBackground, borderColor, borderRadius and labelColor on the closed control, which the native select and the popover trigger share; menuBackground, menuBorderRadius, rowHoverBackground, descriptionColor and suffixColor on the popover only), also all optional. overflowMenu styles the composer overflow "+" menu panel (background, borderColor, borderRadius, foreground, shadow), also all optional.',
177
178
  artifact:
178
179
  'toolbar (icon styling, copy menu), tab (background, active states), pane (background, toolbarBackground).',
179
180
  },
@@ -466,11 +467,16 @@ export const THEME_EXAMPLES: Record<string, ThemeExample> = {
466
467
  * Returns token system docs, the default color palette and radius scale,
467
468
  * example themes, and a list of SDK-bundled presets.
468
469
  */
469
- export function getThemeReference() {
470
+ export function getThemeReference(future?: { v5Defaults?: boolean }) {
471
+ const themeDefaults = resolveThemeDefaults(future)
472
+ const defaults = resolveDefaults({ future })
470
473
  return {
471
474
  tokenDocs: THEME_TOKEN_DOCS,
472
- defaultColorPalette: DEFAULT_PALETTE.colors,
473
- defaultRadius: DEFAULT_PALETTE.radius,
475
+ defaultsVersion: resolveDefaultsVersion({ future }),
476
+ themeDefaultsVersion: resolveThemeDefaultsVersion(future),
477
+ defaultColorPalette: themeDefaults.palette.colors,
478
+ defaultRadius: themeDefaults.palette.radius,
479
+ defaultLauncher: defaults.launcher,
474
480
  examples: THEME_EXAMPLES,
475
481
  sdkPresets: ['shop', 'minimal', 'fullscreen'],
476
482
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
 
3
- import { DEFAULT_WIDGET_CONFIG } from "./defaults";
3
+ import { DEFAULT_WIDGET_CONFIG, mergeWithDefaults } from "./defaults";
4
4
 
5
5
  describe("tool call display defaults", () => {
6
6
  it("keeps advanced tool call transcript modes disabled by default", () => {
@@ -23,4 +23,36 @@ describe("tool call display defaults", () => {
23
23
  loadingAnimation: "none",
24
24
  });
25
25
  });
26
+
27
+ it("fills omitted tool display options when one option is overridden", () => {
28
+ const override = { collapsedMode: "tool-name" as const, expandable: false };
29
+ expect(mergeWithDefaults({ features: { toolCallDisplay: override } }).features?.toolCallDisplay)
30
+ .toEqual({ ...DEFAULT_WIDGET_CONFIG.features?.toolCallDisplay, ...override });
31
+ expect(DEFAULT_WIDGET_CONFIG.features?.toolCallDisplay?.collapsedMode).toBe("tool-call");
32
+ });
33
+
34
+ it("fills omitted reasoning display options without losing explicit false or zero", () => {
35
+ const override = { activePreview: false, previewMaxLines: 0, expandable: false };
36
+ expect(mergeWithDefaults({ features: { reasoningDisplay: override } }).features?.reasoningDisplay)
37
+ .toEqual({ ...DEFAULT_WIDGET_CONFIG.features?.reasoningDisplay, ...override });
38
+ expect(DEFAULT_WIDGET_CONFIG.features?.reasoningDisplay?.previewMaxLines).toBe(3);
39
+ });
40
+
41
+ it("retains optional display settings and leaves unrelated features intact", () => {
42
+ const result = mergeWithDefaults({
43
+ features: {
44
+ showToolCalls: false,
45
+ toolCallDisplay: { grouped: true, activeMinHeight: "100px", completedVisibility: "removed" },
46
+ reasoningDisplay: { loadingAnimation: "shimmer" },
47
+ },
48
+ });
49
+ expect(result.features?.showToolCalls).toBe(false);
50
+ expect(result.features?.toolCallDisplay?.activeMinHeight).toBe("100px");
51
+ expect(result.features?.toolCallDisplay?.completedVisibility).toBe("removed");
52
+ expect(result.features?.toolCallDisplay?.grouped).toBe(true);
53
+ expect(result.features?.reasoningDisplay).toEqual({
54
+ ...DEFAULT_WIDGET_CONFIG.features?.reasoningDisplay,
55
+ loadingAnimation: "shimmer",
56
+ });
57
+ });
26
58
  });
@@ -212,6 +212,8 @@ export interface InputTokens extends ComponentTokenSet {
212
212
  }
213
213
 
214
214
  export interface LauncherTokens extends ComponentTokenSet {
215
+ /** Distance from the viewport edges. Circle default: 20px; pill: 24px. */
216
+ offset?: string;
215
217
  size: string;
216
218
  iconSize: string;
217
219
  shadow: TokenReference<'shadow'>;
@@ -235,6 +237,15 @@ export interface PanelTokens extends ComponentTokenSet {
235
237
  inset?: string;
236
238
  /** Background of the region revealed behind a detached panel. */
237
239
  canvasBackground?: string;
240
+ /** Chrome defaults by rendered panel mode. */
241
+ modes?: Partial<Record<'floating' | 'inline' | 'sidebar' | 'docked' | 'mobile', {
242
+ /** Full border shorthand (for example, `"1px solid var(--persona-border)"`). */
243
+ border?: string;
244
+ /** Box-shadow token reference or raw CSS shadow. */
245
+ shadow?: string;
246
+ /** Corner radius token reference or raw CSS radius. */
247
+ borderRadius?: TokenReference<'radius'>;
248
+ }>>;
238
249
  }
239
250
 
240
251
  /** Per-element text styling shared by themable text surfaces. */
@@ -263,10 +274,16 @@ export interface HeaderTokens extends ComponentTokenSet {
263
274
  */
264
275
  border?: TokenReference<'color'>;
265
276
  borderRadius: TokenReference<'radius'>;
277
+ /** Standard-header padding. @default "20px 24px" */
278
+ padding?: string;
279
+ /** Minimal-header padding. Explicitly wins over `padding`. @default "16px 24px" */
280
+ minimalPadding?: string;
266
281
  /** Background of the rounded avatar tile next to the title (Lucide / emoji / image). */
267
282
  iconBackground: TokenReference<'color'>;
268
283
  /** Foreground (glyph stroke or emoji text) on the header avatar tile. */
269
284
  iconForeground: TokenReference<'color'>;
285
+ /** Header identity glyph size as a fraction of its icon box. @default "0.6" */
286
+ iconScale?: string;
270
287
  /**
271
288
  * Legacy alias of `title.color`; `title.color` wins when both are set.
272
289
  * Unset, the title takes `foreground`, then the primary color.
@@ -305,6 +322,14 @@ export interface HeaderTokens extends ComponentTokenSet {
305
322
  * sibling. @default "1.5"
306
323
  */
307
324
  controlStrokeWidth?: string;
325
+ /** Header control corner radius. Per-control radius overrides win. */
326
+ controlBorderRadius?: string;
327
+ /** Default control hover/focus fill. Explicit button backgrounds win. */
328
+ controlHoverBackground?: TokenReference<'color'>;
329
+ /** Default control hover/focus icon color. Explicit button colors win. */
330
+ controlHoverForeground?: TokenReference<'color'>;
331
+ /** Keyboard focus outline; unset preserves the browser default. */
332
+ controlFocusOutline?: string;
308
333
  /** Box-shadow on the header (e.g., a fade shadow to replace the default border). */
309
334
  shadow?: string;
310
335
  /** Override the header bottom border (e.g., `none`). */
@@ -484,6 +509,12 @@ export interface MessageGeometryTokens {
484
509
  }
485
510
 
486
511
  export interface MessageTokens {
512
+ /** Height of the optional transcript top fade. Default 18px. */
513
+ topFadeHeight?: string;
514
+ /** Vertical gap between transcript turns. @default "12px" */
515
+ gap?: string;
516
+ /** Gap between turns in fullscreen mode; falls back to `gap`. */
517
+ fullscreenGap?: string;
487
518
  user: MessageGeometryTokens & {
488
519
  background: TokenReference<'color'>;
489
520
  text: TokenReference<'color'>;
@@ -497,6 +528,8 @@ export interface MessageTokens {
497
528
  borderRadius: TokenReference<'radius'>;
498
529
  /** Assistant bubble border color (CSS color). */
499
530
  border?: TokenReference<'color'>;
531
+ /** Border width; set to 0px for a flat assistant response. @default "1px" */
532
+ borderWidth?: string;
500
533
  /** Assistant bubble box-shadow (token ref or raw CSS, e.g. `none`). */
501
534
  shadow?: string;
502
535
  };
@@ -622,13 +655,42 @@ export interface AttachmentTokens {
622
655
  }
623
656
 
624
657
  /** Tool-call row chrome (collapsible tool bubbles). */
625
- export interface ToolBubbleTokens {
658
+ export interface ActivityTokens {
659
+ /** Minimum activity row height. */
660
+ rowHeight?: string;
661
+ /** Group child indentation; falls back to indent when unset. V5: 0px. */
662
+ groupIndent?: string;
663
+ /** Extra vertical gap between grouped rows. V5: 0px. */
664
+ groupGap?: string;
665
+ /** Padding around expanded group children. V5: 0px. */
666
+ groupPadding?: string;
667
+ /** Gap between consecutive activity entries. V5: 0px. */
668
+ transcriptGap?: string;
669
+ /** Gap from activity to an assistant response. V5: 12px. */
670
+ responseGap?: string;
671
+ /** Expanded body padding. V5: 4px 0. */
672
+ bodyPadding?: string;
673
+ /** Expanded reasoning text size. V5: 13px. */
674
+ bodySize?: string;
675
+ /** Expanded reasoning line height. V5: 1.5. */
676
+ bodyLineHeight?: string;
677
+ /** Expanded reasoning font weight. V5: 400. */
678
+ bodyFontWeight?: string;
679
+ /** Gap between expanded detail sections. V5: 8px. */
680
+ sectionGap?: string;
681
+ labelSize?: string;
682
+ iconSize?: string;
683
+ indent?: string;
684
+ bodySurface?: TokenReference<'color'>;
685
+ }
686
+
687
+ export interface ToolBubbleTokens extends ActivityTokens {
626
688
  /** Box-shadow for tool bubbles (token ref or raw CSS, e.g. `none`). */
627
689
  shadow: string;
628
690
  }
629
691
 
630
692
  /** Reasoning / “thinking” row chrome. */
631
- export interface ReasoningBubbleTokens {
693
+ export interface ReasoningBubbleTokens extends ActivityTokens {
632
694
  shadow: string;
633
695
  }
634
696
 
@@ -769,6 +831,10 @@ export interface ComposerChromeTokens {
769
831
  shadow: string;
770
832
  /** Border color of the composer form. @default semantic.colors.border */
771
833
  borderColor?: TokenReference<'color'>;
834
+ /** Focused form border color; falls back to borderColor. V5: a stronger neutral border. */
835
+ focusBorderColor?: TokenReference<'color'>;
836
+ /** Outer focus ring (CSS outline shorthand). V4: none; V5: a thin neutral ring. */
837
+ focusRing?: string;
772
838
  /** Inner padding of the composer form (raw CSS shorthand). @default "0.75rem 1rem" */
773
839
  padding?: string;
774
840
  /** Gap between the textarea row and the actions row. @default "0.5rem" */
@@ -787,12 +853,18 @@ export interface ComposerChromeTokens {
787
853
  controlSize?: string;
788
854
  /** Glyph box inside those controls. @default "24px" */
789
855
  controlIconSize?: string;
856
+ /** Send/stop glyph size. Explicit sendButton.iconSize wins. V5: 18px. */
857
+ sendIconSize?: string;
858
+ /** Icon-mode submit button radius. V4 inherits the primary button; V5: 9999px. */
859
+ sendButtonRadius?: string;
860
+ /** Footer top border (CSS shorthand). V4: 1px solid divider; V5: none. */
861
+ footerBorder?: string;
790
862
  /**
791
863
  * Background painted behind an overlaid composer footer
792
864
  * (`composer.placement: "overlay"`). Any CSS `background` value, gradients
793
865
  * included — same convention as the shadow tokens. Inert under
794
866
  * `placement: "block"`.
795
- * @default "transparent"
867
+ * @default "transparent" (V4); a theme-aware 24px fade (V5)
796
868
  */
797
869
  overlayBand?: string;
798
870
  /** Segmented mode-group track. */
@@ -979,6 +1051,8 @@ export interface ScrollToBottomTokens extends ComponentTokenSet {
979
1051
 
980
1052
  /** Visual tokens shared by one suggestion presentation variant. */
981
1053
  export interface SuggestionVariantTokens extends ComponentTokenSet {
1054
+ /** Label weight. V4: 500; V5: 400. */
1055
+ fontWeight?: string;
982
1056
  /** Space inside one item, between its icon and its copy. */
983
1057
  gap?: string;
984
1058
  /** Space between suggestion items in the container. */
@@ -1030,6 +1104,7 @@ export interface ComponentTokens {
1030
1104
  voice: VoiceTokens;
1031
1105
  approval: ApprovalTokens;
1032
1106
  attachment: AttachmentTokens;
1107
+ activity?: ActivityTokens;
1033
1108
  toolBubble: ToolBubbleTokens;
1034
1109
  reasoningBubble: ReasoningBubbleTokens;
1035
1110
  /** Event-stream inspector badge chips. */
@@ -1124,4 +1199,6 @@ export interface CreateThemeOptions {
1124
1199
  plugins?: PersonaThemePlugin[];
1125
1200
  validate?: boolean;
1126
1201
  extend?: PersonaTheme;
1202
+ /** Selects the defaults overlay; explicit theme tokens always take precedence. */
1203
+ future?: { v5Defaults?: boolean };
1127
1204
  }