@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
@@ -1,6 +1,10 @@
1
1
  import { createElement } from "../utils/dom";
2
2
  import { renderLucideIcon } from "../utils/icons";
3
- import { AgentWidgetConfig, AgentWidgetHeaderLayoutConfig } from "../types";
3
+ import {
4
+ AgentWidgetConfig,
5
+ AgentWidgetHeaderLayoutConfig,
6
+ AgentWidgetHeaderTrailingAction
7
+ } from "../types";
4
8
  import { buildHeader, HeaderElements, attachHeaderToContainer as _attachHeaderToContainer } from "./header-builder";
5
9
 
6
10
  export interface HeaderLayoutContext {
@@ -8,6 +12,9 @@ export interface HeaderLayoutContext {
8
12
  showClose?: boolean;
9
13
  onClose?: () => void;
10
14
  onClearChat?: () => void;
15
+ /** Passed from `buildHeaderWithLayout` for minimal/default chrome extensions */
16
+ layoutHeaderConfig?: AgentWidgetHeaderLayoutConfig;
17
+ onHeaderAction?: (actionId: string) => void;
11
18
  }
12
19
 
13
20
  export type HeaderLayoutRenderer = (context: HeaderLayoutContext) => HeaderElements;
@@ -29,20 +36,56 @@ export const buildDefaultHeader: HeaderLayoutRenderer = (context) => {
29
36
  * Build minimal header layout
30
37
  * Simplified layout with just title and close button
31
38
  */
39
+ function appendTrailingHeaderActions(
40
+ container: HTMLElement,
41
+ actions: AgentWidgetHeaderTrailingAction[] | undefined,
42
+ onAction?: (id: string) => void
43
+ ): void {
44
+ if (!actions?.length) return;
45
+ for (const a of actions) {
46
+ const btn = createElement(
47
+ "button",
48
+ "persona-inline-flex persona-items-center persona-justify-center persona-rounded-md persona-border-none persona-bg-transparent persona-p-0 persona-text-persona-muted hover:persona-opacity-80"
49
+ ) as HTMLButtonElement;
50
+ btn.type = "button";
51
+ btn.setAttribute("aria-label", a.ariaLabel ?? a.label ?? a.id);
52
+ if (a.icon) {
53
+ const ic = renderLucideIcon(a.icon, 14, "currentColor", 2);
54
+ if (ic) btn.appendChild(ic);
55
+ } else if (a.label) {
56
+ btn.textContent = a.label;
57
+ }
58
+ btn.addEventListener("click", () => onAction?.(a.id));
59
+ container.appendChild(btn);
60
+ }
61
+ }
62
+
32
63
  export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
33
- const { config, showClose = true, onClose } = context;
64
+ const { config, showClose = true, onClose, layoutHeaderConfig, onHeaderAction } = context;
34
65
  const launcher = config?.launcher ?? {};
35
66
 
36
67
  const header = createElement(
37
68
  "div",
38
- "tvw-flex tvw-items-center tvw-justify-between tvw-bg-cw-surface tvw-px-6 tvw-py-4 tvw-border-b-cw-divider"
69
+ "persona-flex persona-items-center persona-justify-between persona-bg-persona-surface persona-px-6 persona-py-4 persona-border-b-persona-divider"
70
+ );
71
+
72
+ const titleRow = createElement(
73
+ "div",
74
+ "persona-flex persona-min-w-0 persona-flex-1 persona-items-center persona-gap-1"
39
75
  );
40
76
 
41
77
  // Title only (no icon, no subtitle)
42
- const title = createElement("span", "tvw-text-base tvw-font-semibold");
78
+ const title = createElement("span", "persona-text-base persona-font-semibold persona-truncate");
43
79
  title.textContent = launcher.title ?? "Chat Assistant";
44
80
 
45
- header.appendChild(title);
81
+ titleRow.appendChild(title);
82
+ appendTrailingHeaderActions(
83
+ titleRow,
84
+ layoutHeaderConfig?.trailingActions,
85
+ layoutHeaderConfig?.onAction ?? onHeaderAction
86
+ );
87
+
88
+ header.appendChild(titleRow);
46
89
 
47
90
  // Close button
48
91
  const closeButtonSize = launcher.closeButtonSize ?? "32px";
@@ -50,7 +93,7 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
50
93
 
51
94
  const closeButton = createElement(
52
95
  "button",
53
- "tvw-inline-flex tvw-items-center tvw-justify-center tvw-rounded-full tvw-text-cw-muted hover:tvw-bg-gray-100 tvw-cursor-pointer tvw-border-none"
96
+ "persona-inline-flex persona-items-center persona-justify-center persona-rounded-full persona-text-persona-muted hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none"
54
97
  ) as HTMLButtonElement;
55
98
  closeButton.style.height = closeButtonSize;
56
99
  closeButton.style.width = closeButtonSize;
@@ -78,6 +121,8 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
78
121
  closeButtonWrapper.appendChild(closeButton);
79
122
  header.appendChild(closeButtonWrapper);
80
123
 
124
+ // title was moved into titleRow; keep headerTitle ref pointing at title for updateController
125
+
81
126
  // Create placeholder elements for compatibility
82
127
  const iconHolder = createElement("div");
83
128
  iconHolder.style.display = "none";
@@ -96,129 +141,13 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
96
141
  };
97
142
  };
98
143
 
99
- /**
100
- * Build expanded header layout
101
- * Full branding area with additional space for custom content
102
- */
103
- export const buildExpandedHeader: HeaderLayoutRenderer = (context) => {
104
- const { config, showClose = true, onClose, onClearChat: _onClearChat } = context;
105
- const launcher = config?.launcher ?? {};
106
-
107
- const header = createElement(
108
- "div",
109
- "tvw-flex tvw-flex-col tvw-bg-cw-surface tvw-px-6 tvw-py-5 tvw-border-b-cw-divider"
110
- );
111
-
112
- // Top row: icon + text + buttons
113
- const topRow = createElement(
114
- "div",
115
- "tvw-flex tvw-items-center tvw-gap-3"
116
- );
117
-
118
- // Icon
119
- const headerIconSize = launcher.headerIconSize ?? "56px";
120
- const iconHolder = createElement(
121
- "div",
122
- "tvw-flex tvw-items-center tvw-justify-center tvw-rounded-xl tvw-bg-cw-primary tvw-text-white tvw-text-2xl"
123
- );
124
- iconHolder.style.height = headerIconSize;
125
- iconHolder.style.width = headerIconSize;
126
-
127
- const headerIconName = launcher.headerIconName;
128
- if (headerIconName) {
129
- const iconSize = parseFloat(headerIconSize) || 24;
130
- const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.5, "#ffffff", 2);
131
- if (iconSvg) {
132
- iconHolder.replaceChildren(iconSvg);
133
- } else {
134
- iconHolder.textContent = launcher.agentIconText ?? "💬";
135
- }
136
- } else if (launcher.iconUrl) {
137
- const img = createElement("img") as HTMLImageElement;
138
- img.src = launcher.iconUrl;
139
- img.alt = "";
140
- img.className = "tvw-rounded-xl tvw-object-cover";
141
- img.style.height = headerIconSize;
142
- img.style.width = headerIconSize;
143
- iconHolder.replaceChildren(img);
144
- } else {
145
- iconHolder.textContent = launcher.agentIconText ?? "💬";
146
- }
147
-
148
- // Title and subtitle
149
- const headerCopy = createElement("div", "tvw-flex tvw-flex-col tvw-flex-1");
150
- const title = createElement("span", "tvw-text-lg tvw-font-semibold");
151
- title.textContent = launcher.title ?? "Chat Assistant";
152
- const subtitle = createElement("span", "tvw-text-sm tvw-text-cw-muted");
153
- subtitle.textContent = launcher.subtitle ?? "Here to help you get answers fast";
154
- headerCopy.append(title, subtitle);
155
-
156
- topRow.append(iconHolder, headerCopy);
157
-
158
- // Close button
159
- const closeButtonSize = launcher.closeButtonSize ?? "32px";
160
- const closeButtonWrapper = createElement("div", "");
161
-
162
- const closeButton = createElement(
163
- "button",
164
- "tvw-inline-flex tvw-items-center tvw-justify-center tvw-rounded-full tvw-text-cw-muted hover:tvw-bg-gray-100 tvw-cursor-pointer tvw-border-none"
165
- ) as HTMLButtonElement;
166
- closeButton.style.height = closeButtonSize;
167
- closeButton.style.width = closeButtonSize;
168
- closeButton.type = "button";
169
- closeButton.setAttribute("aria-label", "Close chat");
170
- closeButton.style.display = showClose ? "" : "none";
171
-
172
- const closeButtonIconName = launcher.closeButtonIconName ?? "x";
173
- const closeIconSvg = renderLucideIcon(
174
- closeButtonIconName,
175
- "20px",
176
- launcher.closeButtonColor || "",
177
- 2
178
- );
179
- if (closeIconSvg) {
180
- closeButton.appendChild(closeIconSvg);
181
- } else {
182
- closeButton.textContent = "×";
183
- }
184
-
185
- if (onClose) {
186
- closeButton.addEventListener("click", onClose);
187
- }
188
-
189
- closeButtonWrapper.appendChild(closeButton);
190
- topRow.appendChild(closeButtonWrapper);
191
-
192
- header.appendChild(topRow);
193
-
194
- // Bottom row: additional space for status or branding
195
- const bottomRow = createElement(
196
- "div",
197
- "tvw-mt-3 tvw-pt-3 tvw-border-t tvw-border-gray-100 tvw-text-xs tvw-text-cw-muted"
198
- );
199
- bottomRow.textContent = "Online and ready to help";
200
- header.appendChild(bottomRow);
201
-
202
- return {
203
- header,
204
- iconHolder,
205
- headerTitle: title,
206
- headerSubtitle: subtitle,
207
- closeButton,
208
- closeButtonWrapper,
209
- clearChatButton: null,
210
- clearChatButtonWrapper: null
211
- };
212
- };
213
-
214
144
  /**
215
145
  * Header layout registry
216
146
  * Maps layout names to their renderer functions
217
147
  */
218
148
  export const headerLayouts: Record<string, HeaderLayoutRenderer> = {
219
149
  default: buildDefaultHeader,
220
- minimal: buildMinimalHeader,
221
- expanded: buildExpandedHeader
150
+ minimal: buildMinimalHeader
222
151
  };
223
152
 
224
153
  /**
@@ -242,7 +171,9 @@ export const buildHeaderWithLayout = (
242
171
  const customHeader = layoutConfig.render({
243
172
  config,
244
173
  onClose: context?.onClose,
245
- onClearChat: context?.onClearChat
174
+ onClearChat: context?.onClearChat,
175
+ trailingActions: layoutConfig.trailingActions,
176
+ onAction: layoutConfig.onAction
246
177
  });
247
178
 
248
179
  // Wrap in HeaderElements structure
@@ -276,7 +207,9 @@ export const buildHeaderWithLayout = (
276
207
  config,
277
208
  showClose: layoutConfig?.showCloseButton ?? context?.showClose ?? true,
278
209
  onClose: context?.onClose,
279
- onClearChat: context?.onClearChat
210
+ onClearChat: context?.onClearChat,
211
+ layoutHeaderConfig: layoutConfig,
212
+ onHeaderAction: layoutConfig?.onAction
280
213
  });
281
214
 
282
215
  // Apply visibility settings from layout config
@@ -1,6 +1,7 @@
1
1
  import { createElement } from "../utils/dom";
2
2
  import { AgentWidgetConfig } from "../types";
3
3
  import { positionMap } from "../utils/positioning";
4
+ import { isDockedMountMode, resolveDockConfig } from "../utils/dock";
4
5
  import { renderLucideIcon } from "../utils/icons";
5
6
 
6
7
  export interface LauncherButton {
@@ -16,18 +17,19 @@ export const createLauncherButton = (
16
17
  const button = createElement("button") as HTMLButtonElement;
17
18
  button.type = "button";
18
19
  button.innerHTML = `
19
- <span class="tvw-inline-flex tvw-items-center tvw-justify-center tvw-rounded-full tvw-bg-cw-primary tvw-text-white" data-role="launcher-icon">💬</span>
20
- <img data-role="launcher-image" class="tvw-rounded-full tvw-object-cover" alt="" style="display:none" />
21
- <span class="tvw-flex tvw-flex-col tvw-items-start tvw-text-left">
22
- <span class="tvw-text-sm tvw-font-semibold tvw-text-cw-primary" data-role="launcher-title"></span>
23
- <span class="tvw-text-xs tvw-text-cw-muted" data-role="launcher-subtitle"></span>
20
+ <span class="persona-inline-flex persona-items-center persona-justify-center persona-rounded-full persona-bg-persona-primary persona-text-white" data-role="launcher-icon">💬</span>
21
+ <img data-role="launcher-image" class="persona-rounded-full persona-object-cover" alt="" style="display:none" />
22
+ <span class="persona-flex persona-flex-col persona-items-start persona-text-left">
23
+ <span class="persona-text-sm persona-font-semibold persona-text-persona-primary" data-role="launcher-title"></span>
24
+ <span class="persona-text-xs persona-text-persona-muted" data-role="launcher-subtitle"></span>
24
25
  </span>
25
- <span class="tvw-ml-2 tvw-grid tvw-place-items-center tvw-rounded-full tvw-bg-cw-primary tvw-text-cw-call-to-action" data-role="launcher-call-to-action-icon">↗</span>
26
+ <span class="persona-ml-2 persona-grid persona-place-items-center persona-rounded-full persona-bg-persona-primary persona-text-persona-call-to-action" data-role="launcher-call-to-action-icon">↗</span>
26
27
  `;
27
28
  button.addEventListener("click", onToggle);
28
29
 
29
30
  const update = (newConfig: AgentWidgetConfig) => {
30
31
  const launcher = newConfig.launcher ?? {};
32
+ const dockedMode = isDockedMountMode(newConfig);
31
33
 
32
34
  const titleEl = button.querySelector("[data-role='launcher-title']");
33
35
  if (titleEl) {
@@ -40,9 +42,9 @@ export const createLauncherButton = (
40
42
  }
41
43
 
42
44
  // Hide/show text container
43
- const textContainer = button.querySelector(".tvw-flex-col");
45
+ const textContainer = button.querySelector(".persona-flex-col");
44
46
  if (textContainer) {
45
- if (launcher.textHidden) {
47
+ if (launcher.textHidden || dockedMode) {
46
48
  (textContainer as HTMLElement).style.display = "none";
47
49
  } else {
48
50
  (textContainer as HTMLElement).style.display = "";
@@ -65,7 +67,7 @@ export const createLauncherButton = (
65
67
  if (launcher.agentIconName) {
66
68
  // Use Lucide icon
67
69
  const iconSizeNum = parseFloat(iconSize) || 24;
68
- const iconSvg = renderLucideIcon(launcher.agentIconName, iconSizeNum * 0.6, "#ffffff", 2);
70
+ const iconSvg = renderLucideIcon(launcher.agentIconName, iconSizeNum * 0.6, "var(--persona-text-inverse, #ffffff)", 2);
69
71
  if (iconSvg) {
70
72
  icon.appendChild(iconSvg);
71
73
  icon.style.display = "";
@@ -108,10 +110,10 @@ export const createLauncherButton = (
108
110
  // Apply background color if configured
109
111
  if (launcher.callToActionIconBackgroundColor) {
110
112
  callToActionIconEl.style.backgroundColor = launcher.callToActionIconBackgroundColor;
111
- callToActionIconEl.classList.remove("tvw-bg-cw-primary");
113
+ callToActionIconEl.classList.remove("persona-bg-persona-primary");
112
114
  } else {
113
115
  callToActionIconEl.style.backgroundColor = "";
114
- callToActionIconEl.classList.add("tvw-bg-cw-primary");
116
+ callToActionIconEl.classList.add("persona-bg-persona-primary");
115
117
  }
116
118
 
117
119
  // Calculate padding to adjust icon size
@@ -130,7 +132,7 @@ export const createLauncherButton = (
130
132
  if (launcher.callToActionIconHidden) {
131
133
  callToActionIconEl.style.display = "none";
132
134
  } else {
133
- callToActionIconEl.style.display = "";
135
+ callToActionIconEl.style.display = dockedMode ? "none" : "";
134
136
 
135
137
  // Clear existing content
136
138
  callToActionIconEl.innerHTML = "";
@@ -158,19 +160,34 @@ export const createLauncherButton = (
158
160
  ? positionMap[launcher.position]
159
161
  : positionMap["bottom-right"];
160
162
 
161
- // Removed hardcoded border/shadow classes (tvw-shadow-lg, tvw-border, tvw-border-gray-200)
162
- // These are now applied via inline styles from config
163
- const base =
164
- "tvw-fixed tvw-flex tvw-items-center tvw-gap-3 tvw-rounded-launcher tvw-bg-cw-surface tvw-py-2.5 tvw-pl-3 tvw-pr-3 tvw-transition hover:tvw-translate-y-[-2px] tvw-cursor-pointer tvw-z-50";
163
+ const floatingBase =
164
+ "persona-fixed persona-flex persona-items-center persona-gap-3 persona-rounded-launcher persona-bg-persona-surface persona-py-2.5 persona-pl-3 persona-pr-3 persona-transition hover:persona-translate-y-[-2px] persona-cursor-pointer persona-z-50";
165
+ const dockedBase =
166
+ "persona-relative persona-mt-4 persona-mb-4 persona-mx-auto persona-flex persona-items-center persona-justify-center persona-rounded-launcher persona-bg-persona-surface persona-transition hover:persona-translate-y-[-2px] persona-cursor-pointer";
165
167
 
166
- button.className = `${base} ${positionClass}`;
168
+ button.className = dockedMode ? dockedBase : `${floatingBase} ${positionClass}`;
167
169
 
168
170
  // Apply launcher border and shadow from config (with defaults matching previous Tailwind classes)
169
- const defaultBorder = "1px solid #e5e7eb";
170
- const defaultShadow = "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)";
171
+ const defaultBorder = "1px solid var(--persona-border, #e5e7eb)";
172
+ const defaultShadow = "var(--persona-shadow, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1))";
171
173
 
172
174
  button.style.border = launcher.border ?? defaultBorder;
173
175
  button.style.boxShadow = launcher.shadow ?? defaultShadow;
176
+
177
+ if (dockedMode) {
178
+ const dock = resolveDockConfig(newConfig);
179
+ button.style.width = `calc(${dock.collapsedWidth} - 16px)`;
180
+ button.style.minWidth = "40px";
181
+ button.style.maxWidth = `calc(${dock.collapsedWidth} - 16px)`;
182
+ button.style.justifyContent = "center";
183
+ button.style.padding = "12px 0";
184
+ } else {
185
+ button.style.width = "";
186
+ button.style.minWidth = "";
187
+ button.style.maxWidth = "";
188
+ button.style.justifyContent = "";
189
+ button.style.padding = "";
190
+ }
174
191
  };
175
192
 
176
193
  const destroy = () => {
@@ -189,5 +206,3 @@ export const createLauncherButton = (
189
206
  destroy
190
207
  };
191
208
  };
192
-
193
-