@runtypelabs/persona 4.13.0 → 4.15.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 (106) hide show
  1. package/README.md +4 -4
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/{types-DveIaNx6.d.cts → types-BpWGm7_6.d.cts} +17 -0
  5. package/dist/animations/{types-DveIaNx6.d.ts → types-BpWGm7_6.d.ts} +17 -0
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/chunk-QJ4LV2KW.js +1 -0
  9. package/dist/codegen.cjs +12 -11
  10. package/dist/codegen.js +14 -13
  11. package/dist/{context-mentions-ONG7A7M6.js → context-mentions-BTSTIBFC.js} +1 -1
  12. package/dist/context-mentions.cjs +11 -8
  13. package/dist/context-mentions.d.cts +339 -22
  14. package/dist/context-mentions.d.ts +339 -22
  15. package/dist/context-mentions.js +11 -8
  16. package/dist/index.cjs +68 -67
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +483 -48
  19. package/dist/index.d.ts +483 -48
  20. package/dist/index.global.js +61 -58
  21. package/dist/index.global.js.map +1 -1
  22. package/dist/index.js +70 -69
  23. package/dist/index.js.map +1 -1
  24. package/dist/launcher.global.js +5 -2
  25. package/dist/launcher.global.js.map +1 -1
  26. package/dist/smart-dom-reader.d.cts +339 -22
  27. package/dist/smart-dom-reader.d.ts +339 -22
  28. package/dist/theme-editor-preview.cjs +67 -67
  29. package/dist/theme-editor-preview.d.cts +355 -23
  30. package/dist/theme-editor-preview.d.ts +355 -23
  31. package/dist/theme-editor-preview.js +75 -75
  32. package/dist/theme-editor.cjs +10 -10
  33. package/dist/theme-editor.d.cts +376 -24
  34. package/dist/theme-editor.d.ts +376 -24
  35. package/dist/theme-editor.js +10 -10
  36. package/dist/theme-reference.cjs +1 -1
  37. package/dist/theme-reference.d.cts +38 -0
  38. package/dist/theme-reference.d.ts +38 -0
  39. package/dist/theme-reference.js +1 -1
  40. package/dist/widget.css +1 -1
  41. package/package.json +1 -1
  42. package/src/artifacts-session.test.ts +106 -0
  43. package/src/ask-user-question-tool.ts +10 -7
  44. package/src/client.test.ts +2 -2
  45. package/src/client.ts +4 -1
  46. package/src/components/artifact-card.ts +13 -2
  47. package/src/components/artifact-inline.ts +11 -2
  48. package/src/components/artifact-preview.ts +1 -1
  49. package/src/components/composer-builder.test.ts +2 -0
  50. package/src/components/composer-builder.ts +1 -1
  51. package/src/components/composer-parts.test.ts +105 -4
  52. package/src/components/composer-parts.ts +54 -16
  53. package/src/components/message-bubble.test.ts +7 -1
  54. package/src/components/message-bubble.ts +11 -8
  55. package/src/components/messages.ts +5 -2
  56. package/src/components/panel.ts +31 -5
  57. package/src/components/pill-composer-builder.test.ts +18 -0
  58. package/src/components/suggestions.test.ts +233 -0
  59. package/src/components/suggestions.ts +463 -81
  60. package/src/components/widget-view.ts +4 -0
  61. package/src/defaults.ts +5 -3
  62. package/src/generated/runtype-openapi-contract.ts +17 -0
  63. package/src/index-core.ts +52 -2
  64. package/src/launcher-global.ts +1 -1
  65. package/src/plugins/types.ts +71 -3
  66. package/src/presets.ts +3 -3
  67. package/src/session.ts +55 -12
  68. package/src/styles/widget-styles.test.ts +162 -0
  69. package/src/styles/widget.css +440 -1
  70. package/src/suggest-replies-tool.test.ts +233 -2
  71. package/src/suggest-replies-tool.ts +115 -39
  72. package/src/theme-editor/index.ts +5 -0
  73. package/src/theme-editor/presets.test.ts +43 -0
  74. package/src/theme-editor/presets.ts +101 -87
  75. package/src/theme-editor/preview-utils.test.ts +34 -0
  76. package/src/theme-editor/preview-utils.ts +77 -2
  77. package/src/theme-editor/sections.test.ts +72 -7
  78. package/src/theme-editor/sections.ts +113 -13
  79. package/src/theme-editor/state-preferences.test.ts +15 -0
  80. package/src/theme-editor/state.ts +46 -0
  81. package/src/theme-editor/types.ts +15 -1
  82. package/src/theme-editor/webmcp/tools.test.ts +13 -0
  83. package/src/theme-editor/webmcp/tools.ts +5 -0
  84. package/src/theme-reference.ts +9 -2
  85. package/src/types/theme.ts +32 -0
  86. package/src/types.ts +291 -25
  87. package/src/ui.artifact-pane-gating.test.ts +5 -5
  88. package/src/ui.config-preferences.test.ts +166 -0
  89. package/src/ui.detached-panel.test.ts +1 -1
  90. package/src/ui.suggest-replies.test.ts +1066 -1
  91. package/src/ui.ts +311 -45
  92. package/src/ui.typing-indicator.test.ts +135 -0
  93. package/src/utils/artifact-display.test.ts +180 -16
  94. package/src/utils/artifact-display.ts +155 -14
  95. package/src/utils/artifact-file.test.ts +1 -1
  96. package/src/utils/code-generators.test.ts +40 -0
  97. package/src/utils/code-generators.ts +22 -0
  98. package/src/utils/config-merge.test.ts +84 -0
  99. package/src/utils/config-merge.ts +6 -1
  100. package/src/utils/feature-preferences.test.ts +326 -0
  101. package/src/utils/feature-preferences.ts +498 -0
  102. package/src/utils/icons.test.ts +26 -0
  103. package/src/utils/icons.ts +8 -0
  104. package/src/utils/theme.test.ts +171 -0
  105. package/src/utils/tokens.ts +71 -7
  106. package/dist/chunk-MMUPR2JW.js +0 -1
@@ -2,100 +2,114 @@
2
2
 
3
3
  import type { ThemeEditorPreset } from './types';
4
4
 
5
- export const BUILT_IN_PRESETS: ThemeEditorPreset[] = [
6
- {
7
- id: 'default-light',
8
- name: 'Default Light',
9
- description: 'Clean monochrome light theme',
10
- theme: {
11
- components: {
12
- artifact: {
13
- pane: {
14
- background: 'semantic.colors.container',
15
- toolbarBackground: 'semantic.colors.container',
16
- },
5
+ const DEFAULT_LIGHT_PRESET: ThemeEditorPreset = {
6
+ id: 'default-light',
7
+ name: 'Default Light',
8
+ description: 'Clean monochrome light theme',
9
+ theme: {
10
+ components: {
11
+ artifact: {
12
+ pane: {
13
+ background: 'semantic.colors.container',
14
+ toolbarBackground: 'semantic.colors.container',
17
15
  },
18
16
  },
19
17
  },
20
- preview: { primary: '#171717', surface: '#ffffff', accent: '#0f0f0f' },
21
- tags: ['light'],
22
18
  },
23
- {
24
- id: 'default-dark',
25
- name: 'Default Dark',
26
- description: 'Monochrome dark theme',
27
- theme: {
28
- palette: {
29
- colors: {
30
- primary: {
31
- 50: '#ffffff', 100: '#f5f5f5', 200: '#d4d4d4', 300: '#a3a3a3',
32
- 400: '#737373', 500: '#171717', 600: '#0f0f0f', 700: '#0a0a0a',
33
- 800: '#050505', 900: '#030303', 950: '#000000',
34
- },
35
- gray: {
36
- 50: '#f9fafb', 100: '#f3f4f6', 200: '#e5e7eb', 300: '#d1d5db',
37
- 400: '#9ca3af', 500: '#6b7280', 600: '#4b5563', 700: '#374151',
38
- 800: '#1f2937', 900: '#111827', 950: '#030712',
39
- },
40
- secondary: {
41
- 50: '#f5f3ff', 100: '#ede9fe', 200: '#ddd6fe', 300: '#c4b5fd',
42
- 400: '#a78bfa', 500: '#8b5cf6', 600: '#7c3aed', 700: '#6d28d9',
43
- 800: '#5b21b6', 900: '#4c1d95', 950: '#2e1065',
44
- },
45
- accent: {
46
- 50: '#ecfeff', 100: '#cffafe', 200: '#a5f3fc', 300: '#67e8f9',
47
- 400: '#22d3ee', 500: '#06b6d4', 600: '#0891b2', 700: '#0e7490',
48
- 800: '#155e75', 900: '#164e63', 950: '#083344',
49
- },
50
- success: {
51
- 50: '#f0fdf4', 100: '#dcfce7', 200: '#bbf7d0', 300: '#86efac',
52
- 400: '#4ade80', 500: '#22c55e', 600: '#16a34a', 700: '#15803d',
53
- 800: '#166534', 900: '#14532d',
54
- },
55
- warning: {
56
- 50: '#fefce8', 100: '#fef9c3', 200: '#fef08a', 300: '#fde047',
57
- 400: '#facc15', 500: '#eab308', 600: '#ca8a04', 700: '#a16207',
58
- 800: '#854d0e', 900: '#713f12',
59
- },
60
- error: {
61
- 50: '#fef2f2', 100: '#fee2e2', 200: '#fecaca', 300: '#fca5a5',
62
- 400: '#f87171', 500: '#ef4444', 600: '#dc2626', 700: '#b91c1c',
63
- 800: '#991b1b', 900: '#7f1d1d',
64
- },
19
+ preview: { primary: '#171717', surface: '#ffffff', accent: '#0f0f0f' },
20
+ tags: ['light'],
21
+ };
22
+
23
+ const DEFAULT_DARK_PRESET: ThemeEditorPreset = {
24
+ id: 'default-dark',
25
+ name: 'Default Dark',
26
+ description: 'Monochrome dark theme',
27
+ theme: {
28
+ palette: {
29
+ colors: {
30
+ primary: {
31
+ 50: '#ffffff', 100: '#f5f5f5', 200: '#d4d4d4', 300: '#a3a3a3',
32
+ 400: '#737373', 500: '#171717', 600: '#0f0f0f', 700: '#0a0a0a',
33
+ 800: '#050505', 900: '#030303', 950: '#000000',
65
34
  },
66
- } as any,
67
- semantic: {
68
- colors: {
69
- primary: 'palette.colors.primary.400',
70
- secondary: 'palette.colors.gray.400',
71
- accent: 'palette.colors.primary.500',
72
- surface: 'palette.colors.gray.800',
73
- background: 'palette.colors.gray.900',
74
- container: 'palette.colors.gray.800',
75
- text: 'palette.colors.gray.100',
76
- textMuted: 'palette.colors.gray.400',
77
- textInverse: 'palette.colors.gray.900',
78
- border: 'palette.colors.gray.700',
79
- divider: 'palette.colors.gray.700',
80
- interactive: {
81
- default: 'palette.colors.primary.400',
82
- hover: 'palette.colors.primary.300',
83
- focus: 'palette.colors.primary.500',
84
- active: 'palette.colors.primary.600',
85
- disabled: 'palette.colors.gray.600',
86
- },
87
- feedback: {
88
- success: 'palette.colors.success.400',
89
- warning: 'palette.colors.warning.400',
90
- error: 'palette.colors.error.400',
91
- info: 'palette.colors.primary.400',
92
- },
35
+ gray: {
36
+ 50: '#f9fafb', 100: '#f3f4f6', 200: '#e5e7eb', 300: '#d1d5db',
37
+ 400: '#9ca3af', 500: '#6b7280', 600: '#4b5563', 700: '#374151',
38
+ 800: '#1f2937', 900: '#111827', 950: '#030712',
93
39
  },
94
- } as any,
95
- },
96
- preview: { primary: '#171717', surface: '#1f2937', accent: '#0f0f0f' },
97
- tags: ['dark'],
40
+ secondary: {
41
+ 50: '#f5f3ff', 100: '#ede9fe', 200: '#ddd6fe', 300: '#c4b5fd',
42
+ 400: '#a78bfa', 500: '#8b5cf6', 600: '#7c3aed', 700: '#6d28d9',
43
+ 800: '#5b21b6', 900: '#4c1d95', 950: '#2e1065',
44
+ },
45
+ accent: {
46
+ 50: '#ecfeff', 100: '#cffafe', 200: '#a5f3fc', 300: '#67e8f9',
47
+ 400: '#22d3ee', 500: '#06b6d4', 600: '#0891b2', 700: '#0e7490',
48
+ 800: '#155e75', 900: '#164e63', 950: '#083344',
49
+ },
50
+ success: {
51
+ 50: '#f0fdf4', 100: '#dcfce7', 200: '#bbf7d0', 300: '#86efac',
52
+ 400: '#4ade80', 500: '#22c55e', 600: '#16a34a', 700: '#15803d',
53
+ 800: '#166534', 900: '#14532d',
54
+ },
55
+ warning: {
56
+ 50: '#fefce8', 100: '#fef9c3', 200: '#fef08a', 300: '#fde047',
57
+ 400: '#facc15', 500: '#eab308', 600: '#ca8a04', 700: '#a16207',
58
+ 800: '#854d0e', 900: '#713f12',
59
+ },
60
+ error: {
61
+ 50: '#fef2f2', 100: '#fee2e2', 200: '#fecaca', 300: '#fca5a5',
62
+ 400: '#f87171', 500: '#ef4444', 600: '#dc2626', 700: '#b91c1c',
63
+ 800: '#991b1b', 900: '#7f1d1d',
64
+ },
65
+ },
66
+ } as any,
67
+ semantic: {
68
+ colors: {
69
+ primary: 'palette.colors.primary.400',
70
+ secondary: 'palette.colors.gray.400',
71
+ accent: 'palette.colors.primary.500',
72
+ surface: 'palette.colors.gray.800',
73
+ background: 'palette.colors.gray.900',
74
+ container: 'palette.colors.gray.800',
75
+ text: 'palette.colors.gray.100',
76
+ textMuted: 'palette.colors.gray.400',
77
+ textInverse: 'palette.colors.gray.900',
78
+ border: 'palette.colors.gray.700',
79
+ divider: 'palette.colors.gray.700',
80
+ interactive: {
81
+ default: 'palette.colors.primary.400',
82
+ hover: 'palette.colors.primary.300',
83
+ focus: 'palette.colors.primary.500',
84
+ active: 'palette.colors.primary.600',
85
+ disabled: 'palette.colors.gray.600',
86
+ },
87
+ feedback: {
88
+ success: 'palette.colors.success.400',
89
+ warning: 'palette.colors.warning.400',
90
+ error: 'palette.colors.error.400',
91
+ info: 'palette.colors.primary.400',
92
+ },
93
+ },
94
+ } as any,
95
+ },
96
+ preview: { primary: '#171717', surface: '#1f2937', accent: '#0f0f0f' },
97
+ tags: ['dark'],
98
+ };
99
+
100
+ export const BUILT_IN_PRESETS: ThemeEditorPreset[] = [
101
+ {
102
+ id: 'persona-default',
103
+ name: 'Persona Default',
104
+ description: 'Adaptive Persona theme with light and dark modes',
105
+ theme: DEFAULT_LIGHT_PRESET.theme,
106
+ darkTheme: DEFAULT_DARK_PRESET.theme,
107
+ preview: DEFAULT_LIGHT_PRESET.preview,
108
+ darkPreview: DEFAULT_DARK_PRESET.preview,
109
+ tags: ['light', 'dark', 'adaptive'],
98
110
  },
111
+ DEFAULT_LIGHT_PRESET,
112
+ DEFAULT_DARK_PRESET,
99
113
  {
100
114
  id: 'high-contrast',
101
115
  name: 'High Contrast',
@@ -9,6 +9,40 @@ import {
9
9
  } from "./preview-utils";
10
10
 
11
11
  describe("theme editor preview demo data", () => {
12
+ it("keeps the home scene in a true pre-conversation state", () => {
13
+ const config = buildPreviewConfig({ scene: "home" });
14
+
15
+ expect(config.initialMessages).toEqual([]);
16
+ expect(config.suggestionChips?.length).toBeGreaterThan(0);
17
+ });
18
+
19
+ it("seeds rich agent suggestions in the follow-ups scene", () => {
20
+ const config = buildPreviewConfig({ scene: "follow-ups" });
21
+ const messages = config.initialMessages ?? [];
22
+ const suggestionMessage = messages.find(
23
+ (message) =>
24
+ message.variant === "tool" &&
25
+ message.toolCall?.name === "suggest_replies",
26
+ );
27
+ const suggestions = (
28
+ suggestionMessage?.toolCall?.args as
29
+ | { suggestions?: Array<Record<string, unknown>> }
30
+ | undefined
31
+ )?.suggestions;
32
+
33
+ expect(messages.some((message) => message.role === "user")).toBe(true);
34
+ expect(suggestionMessage?.toolCall?.status).toBe("complete");
35
+ expect(suggestions).toHaveLength(4);
36
+ expect(suggestions?.[0]).toEqual(
37
+ expect.objectContaining({
38
+ label: "Show me an implementation",
39
+ description: expect.any(String),
40
+ icon: "code-2",
41
+ emphasis: "primary",
42
+ }),
43
+ );
44
+ });
45
+
12
46
  it("seeds tool call preview messages when advanced tool display modes are enabled", () => {
13
47
  const config = buildPreviewConfig({
14
48
  scene: "conversation",
@@ -195,7 +195,12 @@ export function buildSrcdoc(
195
195
 
196
196
  // ─── Preview Messages ───────────────────────────────────────────
197
197
 
198
- export type PreviewScene = 'home' | 'conversation' | 'minimized' | 'artifact';
198
+ export type PreviewScene =
199
+ | 'home'
200
+ | 'conversation'
201
+ | 'follow-ups'
202
+ | 'minimized'
203
+ | 'artifact';
199
204
 
200
205
  export type PreviewTranscriptEntryPreset =
201
206
  | 'user-message'
@@ -543,8 +548,78 @@ export function createPreviewMessages(
543
548
  config?: Partial<AgentWidgetConfig>,
544
549
  appendedMessages: AgentWidgetMessage[] = []
545
550
  ): AgentWidgetMessage[] {
551
+ if (scene === 'follow-ups') {
552
+ const now = Date.now();
553
+ return [
554
+ {
555
+ id: 'preview-follow-up-user',
556
+ role: 'user',
557
+ content: 'What can Persona help me build?',
558
+ createdAt: new Date(now - 120000).toISOString(),
559
+ },
560
+ {
561
+ id: 'preview-follow-up-assistant',
562
+ role: 'assistant',
563
+ content:
564
+ 'Persona gives you a themeable streaming chat interface with flexible integrations and interaction patterns. Where would you like to go next?',
565
+ createdAt: new Date(now - 60000).toISOString(),
566
+ },
567
+ {
568
+ id: 'preview-follow-up-suggestions',
569
+ role: 'assistant',
570
+ content: '',
571
+ createdAt: new Date(now - 59000).toISOString(),
572
+ streaming: false,
573
+ variant: 'tool',
574
+ toolCall: {
575
+ id: 'preview-follow-up-suggestions',
576
+ name: 'suggest_replies',
577
+ status: 'complete',
578
+ args: {
579
+ suggestions: [
580
+ {
581
+ id: 'preview-implementation',
582
+ label: 'Show me an implementation',
583
+ prompt: 'Show me a practical implementation',
584
+ description: 'See a complete integration example',
585
+ icon: 'code-2',
586
+ emphasis: 'primary',
587
+ },
588
+ {
589
+ id: 'preview-theming',
590
+ label: 'Explore theming',
591
+ prompt: 'Tell me how theming works',
592
+ description: 'Customize colors, type, and component styles',
593
+ icon: 'palette',
594
+ },
595
+ {
596
+ id: 'preview-security',
597
+ label: 'Review security',
598
+ prompt: 'Explain the security model',
599
+ description: 'Learn about isolation and safe rendering',
600
+ icon: 'shield-check',
601
+ },
602
+ {
603
+ id: 'preview-support',
604
+ label: 'Talk to support',
605
+ prompt: 'Help me contact support',
606
+ description: 'Get help choosing the right approach',
607
+ icon: 'messages-square',
608
+ },
609
+ ],
610
+ },
611
+ chunks: [],
612
+ },
613
+ },
614
+ ...appendedMessages,
615
+ ];
616
+ }
546
617
  if (scene === 'home') {
547
- return [{ id: 'preview-home-1', role: 'assistant', content: 'Hi there! How can we help today?', createdAt: new Date().toISOString() }];
618
+ // Home is the pre-conversation state: welcome copy + starter suggestions
619
+ // already provide the greeting and entry points. Seeding an assistant
620
+ // bubble here creates a duplicate greeting that the default runtime never
621
+ // enters unless a host explicitly configures its own initialMessages.
622
+ return [];
548
623
  }
549
624
  if (scene === 'minimized') {
550
625
  return [{ id: 'preview-min-1', role: 'assistant', content: 'We are here whenever you are ready.', createdAt: new Date().toISOString() }];
@@ -60,6 +60,39 @@ describe("theme editor scroll-to-bottom controls", () => {
60
60
  expect(fieldPaths).toContain("theme.components.scrollToBottom.iconSize");
61
61
  });
62
62
 
63
+ it("exposes suggestion behavior and component token controls", () => {
64
+ const suggestions = CONFIGURE_SECTIONS.find(
65
+ (section) => section.id === "suggestions"
66
+ );
67
+ const configPaths =
68
+ suggestions?.fields.map((field) => field.path) ?? [];
69
+ expect(configPaths).toEqual(
70
+ expect.arrayContaining([
71
+ "suggestions.starters.variant",
72
+ "suggestions.starters.placement",
73
+ "suggestions.starters.behavior",
74
+ "suggestions.followUps.variant",
75
+ "suggestions.followUps.placement",
76
+ "suggestions.followUps.behavior",
77
+ "suggestions.followUps.overflow",
78
+ ])
79
+ );
80
+
81
+ const componentPaths = COMPONENTS_SECTIONS.flatMap((section) =>
82
+ section.fields.map((field) => field.path)
83
+ );
84
+ expect(componentPaths).toEqual(
85
+ expect.arrayContaining([
86
+ "theme.components.suggestion.chip.background",
87
+ "theme.components.suggestion.chip.hoverBackground",
88
+ "theme.components.suggestion.chip.focusRing",
89
+ "theme.components.suggestion.card.background",
90
+ "theme.components.suggestion.card.shadow",
91
+ "theme.components.suggestion.list.background",
92
+ ])
93
+ );
94
+ });
95
+
63
96
  it("exposes a shadow control for every themeable component", () => {
64
97
  const fieldPaths = COMPONENTS_SECTIONS.flatMap((section) => section.fields.map((field) => field.path));
65
98
 
@@ -99,16 +132,45 @@ describe("theme editor scroll-to-bottom controls", () => {
99
132
  expect(INTERFACE_ROLES_SECTION.fields.some((field) => field.id === "role-scroll-to-bottom")).toBe(true);
100
133
  });
101
134
 
102
- it("exposes grouped and collapsed tool call preview controls", () => {
135
+ it("moves persistable stream controls out of developer debug without duplicates", () => {
103
136
  const debugSection = CONFIGURE_SECTIONS.find((section) => section.id === "debug-inspection");
137
+ const preferenceSection = CONFIGURE_SECTIONS.find(
138
+ (section) => section.id === "stream-display-preferences"
139
+ );
104
140
 
105
- expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.collapsedMode")).toBe(true);
141
+ expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.collapsedMode")).toBe(false);
106
142
  expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.activePreview")).toBe(true);
107
143
  expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.previewMaxLines")).toBe(true);
108
144
  expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.activeMinHeight")).toBe(true);
109
- expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.expandable")).toBe(true);
110
- expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.grouped")).toBe(true);
111
- expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.groupedMode")).toBe(true);
145
+ expect(preferenceSection?.fields.some((field) => field.path === "features.toolCallDisplay.collapsedMode")).toBe(true);
146
+ expect(preferenceSection?.fields.some((field) => field.path === "features.toolCallDisplay.expandable")).toBe(true);
147
+ expect(preferenceSection?.fields.some((field) => field.path === "features.toolCallDisplay.grouped")).toBe(true);
148
+ expect(preferenceSection?.fields.some((field) => field.path === "features.toolCallDisplay.groupedMode")).toBe(true);
149
+
150
+ const paths = CONFIGURE_SECTIONS.flatMap((section) =>
151
+ section.fields.map((field) => field.path)
152
+ );
153
+ expect(paths.filter((path) => path === "features.toolCallDisplay.grouped")).toHaveLength(1);
154
+ });
155
+
156
+ it("exposes inheritable capability-tagged artifact preference fields", () => {
157
+ const section = CONFIGURE_SECTIONS.find(
158
+ (entry) => entry.id === "artifact-display-preferences"
159
+ );
160
+ const paths = section?.fields.map((field) => field.path) ?? [];
161
+ expect(paths).toEqual(
162
+ expect.arrayContaining([
163
+ "features.artifacts.display.files",
164
+ "features.artifacts.display.byKind.component",
165
+ "features.artifacts.display.default",
166
+ "features.artifacts.filePreview.enabled",
167
+ ])
168
+ );
169
+ expect(paths).not.toContain("features.artifacts.display.byKind.markdown");
170
+ expect(paths).not.toContain("features.artifacts.display.byCategory.document");
171
+ expect(paths).not.toContain("features.artifacts.display.byCategory.application");
172
+ expect(section?.requiresCapability).toBe("artifacts");
173
+ expect(section?.fields.every((field) => field.unsettable)).toBe(true);
112
174
  });
113
175
 
114
176
  it("exposes product-facing artifact toolbar copy", () => {
@@ -129,9 +191,12 @@ describe("theme editor scroll-to-bottom controls", () => {
129
191
 
130
192
  it("exposes collapsed reasoning preview controls", () => {
131
193
  const debugSection = CONFIGURE_SECTIONS.find((section) => section.id === "debug-inspection");
194
+ const preferenceSection = CONFIGURE_SECTIONS.find(
195
+ (section) => section.id === "stream-display-preferences"
196
+ );
132
197
 
133
- expect(debugSection?.fields.some((field) => field.path === "features.reasoningDisplay.expandable")).toBe(true);
134
- expect(debugSection?.fields.some((field) => field.path === "features.reasoningDisplay.activePreview")).toBe(true);
198
+ expect(preferenceSection?.fields.some((field) => field.path === "features.reasoningDisplay.expandable")).toBe(true);
199
+ expect(preferenceSection?.fields.some((field) => field.path === "features.reasoningDisplay.activePreview")).toBe(true);
135
200
  expect(debugSection?.fields.some((field) => field.path === "features.reasoningDisplay.previewMaxLines")).toBe(true);
136
201
  expect(debugSection?.fields.some((field) => field.path === "features.reasoningDisplay.activeMinHeight")).toBe(true);
137
202
  });
@@ -369,6 +369,18 @@ const inputShapeSectionDef: SectionDef = {
369
369
  ],
370
370
  };
371
371
 
372
+ const composerSpacingSectionDef: SectionDef = {
373
+ id: 'comp-composer-spacing',
374
+ title: 'Composer Spacing',
375
+ collapsed: true,
376
+ fields: [
377
+ { id: 'composer-padding', label: 'Padding', type: 'text', path: 'theme.components.composer.padding', defaultValue: '0.75rem 1rem' },
378
+ { id: 'composer-gap', label: 'Row Gap', description: 'Space between the textarea row and the actions row', type: 'text', path: 'theme.components.composer.gap', defaultValue: '0.5rem' },
379
+ { id: 'composer-font-size', label: 'Font Size', type: 'text', path: 'theme.components.composer.fontSize', defaultValue: '0.875rem' },
380
+ { id: 'composer-line-height', label: 'Line Height', type: 'text', path: 'theme.components.composer.lineHeight', defaultValue: '1.25rem' },
381
+ ],
382
+ };
383
+
372
384
  const buttonShapeSectionDef: SectionDef = {
373
385
  id: 'comp-button-shape',
374
386
  title: 'Button Shape',
@@ -466,6 +478,32 @@ const scrollToBottomSectionDef: SectionDef = {
466
478
  ],
467
479
  };
468
480
 
481
+ const suggestionStyleSectionDef: SectionDef = {
482
+ id: 'comp-suggestions',
483
+ title: 'Suggestions',
484
+ description: 'Style starter cards, follow-up chips, and list rows.',
485
+ collapsed: true,
486
+ fields: [
487
+ { id: 'suggestion-chip-bg', label: 'Chip Background', type: 'token-ref', path: 'theme.components.suggestion.chip.background', defaultValue: 'semantic.colors.surface', tokenRef: { tokenType: 'color' } },
488
+ { id: 'suggestion-chip-fg', label: 'Chip Text', type: 'token-ref', path: 'theme.components.suggestion.chip.foreground', defaultValue: 'semantic.colors.text', tokenRef: { tokenType: 'color' } },
489
+ { id: 'suggestion-chip-border', label: 'Chip Border', type: 'token-ref', path: 'theme.components.suggestion.chip.border', defaultValue: 'semantic.colors.border', tokenRef: { tokenType: 'color' } },
490
+ { id: 'suggestion-chip-hover', label: 'Chip Hover', type: 'token-ref', path: 'theme.components.suggestion.chip.hoverBackground', defaultValue: 'palette.colors.gray.100', tokenRef: { tokenType: 'color' } },
491
+ { id: 'suggestion-chip-focus', label: 'Chip Focus Ring', type: 'token-ref', path: 'theme.components.suggestion.chip.focusRing', defaultValue: 'semantic.colors.interactive.focus', tokenRef: { tokenType: 'color' } },
492
+ { id: 'suggestion-card-bg', label: 'Card Background', type: 'token-ref', path: 'theme.components.suggestion.card.background', defaultValue: 'semantic.colors.surface', tokenRef: { tokenType: 'color' } },
493
+ { id: 'suggestion-card-fg', label: 'Card Text', type: 'token-ref', path: 'theme.components.suggestion.card.foreground', defaultValue: 'semantic.colors.text', tokenRef: { tokenType: 'color' } },
494
+ { id: 'suggestion-card-border', label: 'Card Border', type: 'token-ref', path: 'theme.components.suggestion.card.border', defaultValue: 'semantic.colors.border', tokenRef: { tokenType: 'color' } },
495
+ { id: 'suggestion-card-hover', label: 'Card Hover', type: 'token-ref', path: 'theme.components.suggestion.card.hoverBackground', defaultValue: 'palette.colors.gray.100', tokenRef: { tokenType: 'color' } },
496
+ { id: 'suggestion-card-shadow', label: 'Card Shadow', type: 'select', path: 'theme.components.suggestion.card.shadow', defaultValue: 'palette.shadows.sm', options: [
497
+ { value: 'palette.shadows.none', label: 'None' },
498
+ { value: 'palette.shadows.sm', label: 'Small' },
499
+ { value: 'palette.shadows.md', label: 'Medium' },
500
+ { value: 'palette.shadows.lg', label: 'Large' },
501
+ ] },
502
+ { id: 'suggestion-list-bg', label: 'List Background', type: 'color', path: 'theme.components.suggestion.list.background', defaultValue: 'transparent' },
503
+ { id: 'suggestion-list-border', label: 'List Border', type: 'token-ref', path: 'theme.components.suggestion.list.border', defaultValue: 'semantic.colors.border', tokenRef: { tokenType: 'color' } },
504
+ ],
505
+ };
506
+
469
507
  // Reusable shadow-scale options matching the launcher / panel / scroll-to-bottom selects.
470
508
  const SHADOW_SCALE_OPTIONS: { value: string; label: string }[] = [
471
509
  { value: 'palette.shadows.none', label: 'None' },
@@ -502,6 +540,7 @@ export const COMPONENT_SHAPE_SECTIONS: SectionDef[] = [
502
540
  launcherLayoutSectionDef,
503
541
  messageShapeSectionDef,
504
542
  inputShapeSectionDef,
543
+ composerSpacingSectionDef,
505
544
  buttonShapeSectionDef,
506
545
  componentShadowsSectionDef,
507
546
  ];
@@ -513,6 +552,7 @@ export const COMPONENT_COLOR_SECTIONS: SectionDef[] = [
513
552
  inputColorsSectionDef,
514
553
  buttonColorsSectionDef,
515
554
  scrollToBottomSectionDef,
555
+ suggestionStyleSectionDef,
516
556
  ];
517
557
 
518
558
  export const COMPONENTS_SECTIONS: SectionDef[] = [
@@ -526,9 +566,9 @@ export const COMPONENTS_SECTIONS: SectionDef[] = [
526
566
 
527
567
  const MB = 1024 * 1024;
528
568
  const DEFAULT_SUGGESTION_CHIPS = [
529
- 'What can you help me with?',
530
- 'Tell me about your features',
531
- 'How does this work?',
569
+ 'Show me what you can help with',
570
+ 'Walk me through getting started',
571
+ 'Answer a question about this page',
532
572
  ];
533
573
 
534
574
  const ATTACHMENT_TYPE_PRESETS: Record<string, string[]> = {
@@ -576,9 +616,17 @@ const copySectionDef: SectionDef = {
576
616
  };
577
617
 
578
618
  const suggestionsSectionDef: SectionDef = {
579
- id: 'suggestions', title: 'Suggestion Chips', description: 'Configure chip content and styling.', collapsed: true,
619
+ id: 'suggestions', title: 'Suggestions', description: 'Configure starter prompts and agent follow-ups.', collapsed: true,
580
620
  fields: [
581
- { id: 'suggestions-list', label: 'Suggestions', description: 'Add, edit, and remove chips directly.', type: 'chip-list', path: 'suggestionChips', defaultValue: DEFAULT_SUGGESTION_CHIPS },
621
+ { id: 'suggestions-list', label: 'Starter Prompts', description: 'Add, edit, and remove starter prompts directly.', type: 'chip-list', path: 'suggestionChips', defaultValue: DEFAULT_SUGGESTION_CHIPS },
622
+ { id: 'suggestions-starter-variant', label: 'Starter Style', type: 'select', path: 'suggestions.starters.variant', defaultValue: 'card', options: [{ value: 'card', label: 'Cards' }, { value: 'chip', label: 'Chips' }, { value: 'list', label: 'List' }] },
623
+ { id: 'suggestions-starter-placement', label: 'Starter Placement', type: 'select', path: 'suggestions.starters.placement', defaultValue: 'auto', options: [{ value: 'auto', label: 'Automatic' }, { value: 'welcome', label: 'Welcome Surface' }, { value: 'composer', label: 'Above Composer' }] },
624
+ { id: 'suggestions-starter-behavior', label: 'Starter Click', type: 'select', path: 'suggestions.starters.behavior', defaultValue: 'send', options: [{ value: 'send', label: 'Send Immediately' }, { value: 'fill', label: 'Fill Composer' }] },
625
+ { id: 'suggestions-starter-overflow', label: 'Starter Overflow', type: 'select', path: 'suggestions.starters.overflow', defaultValue: 'wrap', options: [{ value: 'wrap', label: 'Wrap Rows' }, { value: 'scroll', label: 'Horizontal Scroll' }] },
626
+ { id: 'suggestions-followup-variant', label: 'Follow-up Style', type: 'select', path: 'suggestions.followUps.variant', defaultValue: 'chip', options: [{ value: 'chip', label: 'Chips' }, { value: 'card', label: 'Cards' }, { value: 'list', label: 'List' }] },
627
+ { id: 'suggestions-followup-placement', label: 'Follow-up Placement', type: 'select', path: 'suggestions.followUps.placement', defaultValue: 'auto', options: [{ value: 'auto', label: 'Automatic' }, { value: 'after-message', label: 'After Messages' }, { value: 'composer', label: 'Above Composer' }] },
628
+ { id: 'suggestions-followup-behavior', label: 'Follow-up Click', type: 'select', path: 'suggestions.followUps.behavior', defaultValue: 'send', options: [{ value: 'send', label: 'Send Immediately' }, { value: 'fill', label: 'Fill Composer' }] },
629
+ { id: 'suggestions-followup-overflow', label: 'Follow-up Overflow', type: 'select', path: 'suggestions.followUps.overflow', defaultValue: 'scroll', options: [{ value: 'scroll', label: 'Horizontal Scroll' }, { value: 'wrap', label: 'Wrap Rows' }] },
582
630
  ],
583
631
  };
584
632
 
@@ -869,20 +917,71 @@ const apiIntegrationSectionDef: SectionDef = {
869
917
  ],
870
918
  };
871
919
 
920
+ const DISPLAY_MODE_OPTIONS = [
921
+ { value: 'collapsed', label: 'Collapsed card' },
922
+ { value: 'panel', label: 'Side panel' },
923
+ { value: 'inline', label: 'In conversation' },
924
+ ];
925
+
926
+ const INHERITABLE_DISPLAY_FIELD = {
927
+ type: 'select' as const,
928
+ defaultValue: 'panel',
929
+ options: DISPLAY_MODE_OPTIONS,
930
+ unsettable: true,
931
+ unsetLabel: 'Use default',
932
+ requiresCapability: 'artifacts' as const,
933
+ };
934
+
935
+ export const ARTIFACT_DISPLAY_PREFERENCE_SECTION: SectionDef = {
936
+ id: 'artifact-display-preferences',
937
+ title: 'Artifact display',
938
+ description: 'Choose where recognizable artifact groups appear.',
939
+ requiresCapability: 'artifacts',
940
+ fields: [
941
+ { ...INHERITABLE_DISPLAY_FIELD, id: 'artifact-display-files', label: 'Generated files', description: 'Default for HTML, CSV, documents, and code files.', path: 'features.artifacts.display.files' },
942
+ { ...INHERITABLE_DISPLAY_FIELD, id: 'artifact-display-components', label: 'Registered UI components', description: 'Charts, forms, cards, and custom UI rendered by the host.', path: 'features.artifacts.display.byKind.component' },
943
+ { ...INHERITABLE_DISPLAY_FIELD, id: 'artifact-display-default', label: 'Other artifacts', description: 'Plain documents and anything without a narrower rule.', path: 'features.artifacts.display.default' },
944
+ {
945
+ id: 'artifact-file-preview',
946
+ label: 'Render supported file previews',
947
+ type: 'toggle',
948
+ path: 'features.artifacts.filePreview.enabled',
949
+ defaultValue: true,
950
+ unsettable: true,
951
+ unsetLabel: 'Use default',
952
+ requiresCapability: 'artifacts',
953
+ },
954
+ ],
955
+ };
956
+
957
+ const streamDisplayPreferencesSectionDef: SectionDef = {
958
+ id: 'stream-display-preferences',
959
+ title: 'Tool calls and reasoning',
960
+ description: 'Control how agent activity appears in the conversation.',
961
+ fields: [
962
+ { id: 'pref-show-tools', label: 'Show tool calls', type: 'toggle', path: 'features.showToolCalls', defaultValue: true, unsettable: true, requiresCapability: 'tools' },
963
+ { id: 'pref-tool-collapsed-mode', label: 'Tool call summary', type: 'select', path: 'features.toolCallDisplay.collapsedMode', defaultValue: 'tool-call', options: [{ value: 'tool-call', label: 'Tool call' }, { value: 'tool-name', label: 'Tool name' }, { value: 'tool-preview', label: 'Tool preview' }], unsettable: true, requiresCapability: 'tools' },
964
+ { id: 'pref-tool-expandable', label: 'Tool calls expandable', type: 'toggle', path: 'features.toolCallDisplay.expandable', defaultValue: true, unsettable: true, requiresCapability: 'tools' },
965
+ { id: 'pref-tool-grouped', label: 'Group sequential tool calls', type: 'toggle', path: 'features.toolCallDisplay.grouped', defaultValue: false, unsettable: true, requiresCapability: 'tools' },
966
+ { id: 'pref-tool-grouped-mode', label: 'Grouped tool detail', type: 'select', path: 'features.toolCallDisplay.groupedMode', defaultValue: 'stack', options: [{ value: 'stack', label: 'Show steps' }, { value: 'summary', label: 'Summary only' }], unsettable: true, requiresCapability: 'tools' },
967
+ { id: 'pref-tool-loading-animation', label: 'Tool loading animation', type: 'select', path: 'features.toolCallDisplay.loadingAnimation', defaultValue: 'none', options: [{ value: 'none', label: 'None' }, { value: 'pulse', label: 'Pulse' }, { value: 'shimmer', label: 'Shimmer' }, { value: 'shimmer-color', label: 'Color shimmer' }, { value: 'rainbow', label: 'Rainbow' }], unsettable: true, requiresCapability: 'tools' },
968
+ { id: 'pref-show-reasoning', label: 'Show reasoning', type: 'toggle', path: 'features.showReasoning', defaultValue: true, unsettable: true, requiresCapability: 'reasoning' },
969
+ { id: 'pref-reasoning-expandable', label: 'Reasoning expandable', type: 'toggle', path: 'features.reasoningDisplay.expandable', defaultValue: true, unsettable: true, requiresCapability: 'reasoning' },
970
+ { id: 'pref-reasoning-active-preview', label: 'Reasoning preview while active', type: 'toggle', path: 'features.reasoningDisplay.activePreview', defaultValue: false, unsettable: true, requiresCapability: 'reasoning' },
971
+ ],
972
+ };
973
+
974
+ export const STREAM_AND_ARTIFACTS_SUB_GROUP: SubGroupDef = {
975
+ label: 'Stream and artifacts',
976
+ sections: [streamDisplayPreferencesSectionDef, ARTIFACT_DISPLAY_PREFERENCE_SECTION],
977
+ };
978
+
872
979
  const debugSectionDef: SectionDef = {
873
980
  id: 'debug-inspection', title: 'Debug & Inspection', collapsed: true,
874
981
  fields: [
875
- { id: 'dev-reasoning', label: 'Show Reasoning', description: 'Display AI reasoning steps', type: 'toggle', path: 'features.showReasoning', defaultValue: false },
876
- { id: 'dev-tool-calls', label: 'Show Tool Calls', description: 'Display tool call details', type: 'toggle', path: 'features.showToolCalls', defaultValue: false },
877
- { id: 'dev-tool-collapsed-mode', label: 'Tool Call Summary', description: 'Choose what collapsed tool rows show by default', type: 'select', path: 'features.toolCallDisplay.collapsedMode', defaultValue: 'tool-call', options: [{ value: 'tool-call', label: 'Tool Call' }, { value: 'tool-name', label: 'Tool Name' }, { value: 'tool-preview', label: 'Tool Preview' }] },
878
982
  { id: 'dev-tool-active-preview', label: 'Tool Preview While Active', description: 'Show a lightweight preview in collapsed active tool rows', type: 'toggle', path: 'features.toolCallDisplay.activePreview', defaultValue: false },
879
983
  { id: 'dev-tool-preview-lines', label: 'Tool Preview Lines', type: 'select', path: 'features.toolCallDisplay.previewMaxLines', defaultValue: 3, options: [{ value: '1', label: '1' }, { value: '2', label: '2' }, { value: '3', label: '3' }, { value: '4', label: '4' }, { value: '5', label: '5' }], formatValue: (v: unknown) => String(v ?? 3), parseValue: (v: unknown) => Number(v) },
880
984
  { id: 'dev-tool-active-min-height', label: 'Tool Active Min Height', description: 'CSS min-height for collapsed active tool rows (e.g. 5rem)', type: 'text', path: 'features.toolCallDisplay.activeMinHeight', defaultValue: '' },
881
- { id: 'dev-tool-expandable', label: 'Tool Calls Expandable', description: 'Allow expanding tool call rows to see full details', type: 'toggle', path: 'features.toolCallDisplay.expandable', defaultValue: true },
882
- { id: 'dev-tool-grouped', label: 'Group Sequential Tool Calls', description: 'Render consecutive tool rows inside a grouped container', type: 'toggle', path: 'features.toolCallDisplay.grouped', defaultValue: false },
883
- { id: 'dev-tool-grouped-mode', label: 'Grouped Tool Detail', description: 'Show each tool step or one consolidated summary', type: 'select', path: 'features.toolCallDisplay.groupedMode', defaultValue: 'stack', options: [{ value: 'stack', label: 'Show Steps' }, { value: 'summary', label: 'Summary Only' }] },
884
- { id: 'dev-reasoning-expandable', label: 'Reasoning Expandable', description: 'Allow expanding reasoning rows to see full details', type: 'toggle', path: 'features.reasoningDisplay.expandable', defaultValue: true },
885
- { id: 'dev-reasoning-active-preview', label: 'Reasoning Preview While Active', description: 'Show a lightweight preview in collapsed active reasoning rows', type: 'toggle', path: 'features.reasoningDisplay.activePreview', defaultValue: false },
886
985
  { id: 'dev-reasoning-preview-lines', label: 'Reasoning Preview Lines', type: 'select', path: 'features.reasoningDisplay.previewMaxLines', defaultValue: 3, options: [{ value: '1', label: '1' }, { value: '2', label: '2' }, { value: '3', label: '3' }, { value: '4', label: '4' }, { value: '5', label: '5' }], formatValue: (v: unknown) => String(v ?? 3), parseValue: (v: unknown) => Number(v) },
887
986
  { id: 'dev-reasoning-active-min-height', label: 'Reasoning Active Min Height', description: 'CSS min-height for collapsed active reasoning rows (e.g. 5rem)', type: 'text', path: 'features.reasoningDisplay.activeMinHeight', defaultValue: '' },
888
987
  { id: 'dev-debug', label: 'Debug Mode', description: 'Show debug information', type: 'toggle', path: 'debug', defaultValue: false },
@@ -906,6 +1005,7 @@ export const CONFIGURE_SUB_GROUPS: SubGroupDef[] = [
906
1005
  { label: 'Layout', sections: [generalLayoutSectionDef, headerLayoutSectionDef, messagesLayoutSectionDef, messageActionsSectionDef] },
907
1006
  { label: 'Widget', sections: [launcherBasicsSectionDef, launcherAdvancedSectionDef, sendButtonSectionDef, closeButtonSectionDef, clearChatSectionDef, statusIndicatorSectionDef] },
908
1007
  { label: 'Features', sections: [featuresSectionDef, streamAnimationSectionDef, attachmentsSectionDef, artifactsSectionDef, artifactCustomizationSectionDef] },
1008
+ STREAM_AND_ARTIFACTS_SUB_GROUP,
909
1009
  { label: 'Developer', collapsedByDefault: true, sections: [apiIntegrationSectionDef, debugSectionDef, markdownSectionDef] },
910
1010
  ];
911
1011