@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
@@ -52,7 +52,7 @@ const createMessageImagePreviews = (
52
52
  try {
53
53
  const container = createElement(
54
54
  "div",
55
- "tvw-flex tvw-flex-col tvw-gap-2"
55
+ "persona-flex persona-flex-col persona-gap-2"
56
56
  );
57
57
  container.setAttribute("data-message-attachments", "images");
58
58
  if (hasVisibleText) {
@@ -82,8 +82,8 @@ const createMessageImagePreviews = (
82
82
  imageElement.style.height = "auto";
83
83
  imageElement.style.objectFit = "contain";
84
84
  imageElement.style.borderRadius = "10px";
85
- imageElement.style.backgroundColor = "var(--cw-container, #f3f4f6)";
86
- imageElement.style.border = "1px solid var(--cw-border, #e5e7eb)";
85
+ imageElement.style.backgroundColor = "var(--persona-attachment-image-bg, var(--persona-container, #f3f4f6))";
86
+ imageElement.style.border = "1px solid var(--persona-attachment-image-border, var(--persona-border, #e5e7eb))";
87
87
 
88
88
  let settled = false;
89
89
  visiblePreviewCount += 1;
@@ -119,22 +119,22 @@ const createMessageImagePreviews = (
119
119
  // Create typing indicator element
120
120
  export const createTypingIndicator = (): HTMLElement => {
121
121
  const container = document.createElement("div");
122
- container.className = "tvw-flex tvw-items-center tvw-space-x-1 tvw-h-5 tvw-mt-2";
122
+ container.className = "persona-flex persona-items-center persona-space-x-1 persona-h-5 persona-mt-2";
123
123
 
124
124
  const dot1 = document.createElement("div");
125
- dot1.className = "tvw-bg-cw-primary tvw-animate-typing tvw-rounded-full tvw-h-1.5 tvw-w-1.5";
125
+ dot1.className = "persona-bg-persona-primary persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5";
126
126
  dot1.style.animationDelay = "0ms";
127
127
 
128
128
  const dot2 = document.createElement("div");
129
- dot2.className = "tvw-bg-cw-primary tvw-animate-typing tvw-rounded-full tvw-h-1.5 tvw-w-1.5";
129
+ dot2.className = "persona-bg-persona-primary persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5";
130
130
  dot2.style.animationDelay = "250ms";
131
131
 
132
132
  const dot3 = document.createElement("div");
133
- dot3.className = "tvw-bg-cw-primary tvw-animate-typing tvw-rounded-full tvw-h-1.5 tvw-w-1.5";
133
+ dot3.className = "persona-bg-persona-primary persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5";
134
134
  dot3.style.animationDelay = "500ms";
135
135
 
136
136
  const srOnly = document.createElement("span");
137
- srOnly.className = "tvw-sr-only";
137
+ srOnly.className = "persona-sr-only";
138
138
  srOnly.textContent = "Loading";
139
139
 
140
140
  container.appendChild(dot1);
@@ -183,7 +183,7 @@ const createAvatar = (
183
183
  ): HTMLElement => {
184
184
  const avatar = createElement(
185
185
  "div",
186
- "tvw-flex-shrink-0 tvw-w-8 tvw-h-8 tvw-rounded-full tvw-flex tvw-items-center tvw-justify-center tvw-text-sm"
186
+ "persona-flex-shrink-0 persona-w-8 persona-h-8 persona-rounded-full persona-flex persona-items-center persona-justify-center persona-text-sm"
187
187
  );
188
188
 
189
189
  const avatarContent = role === "user"
@@ -196,22 +196,22 @@ const createAvatar = (
196
196
  const img = createElement("img") as HTMLImageElement;
197
197
  img.src = avatarContent;
198
198
  img.alt = role === "user" ? "User" : "Assistant";
199
- img.className = "tvw-w-full tvw-h-full tvw-rounded-full tvw-object-cover";
199
+ img.className = "persona-w-full persona-h-full persona-rounded-full persona-object-cover";
200
200
  avatar.appendChild(img);
201
201
  } else {
202
202
  // Emoji or text
203
203
  avatar.textContent = avatarContent;
204
204
  avatar.classList.add(
205
- role === "user" ? "tvw-bg-cw-accent" : "tvw-bg-cw-primary",
206
- "tvw-text-white"
205
+ role === "user" ? "persona-bg-persona-accent" : "persona-bg-persona-primary",
206
+ "persona-text-white"
207
207
  );
208
208
  }
209
209
  } else {
210
210
  // Default avatar
211
211
  avatar.textContent = role === "user" ? "U" : "A";
212
212
  avatar.classList.add(
213
- role === "user" ? "tvw-bg-cw-accent" : "tvw-bg-cw-primary",
214
- "tvw-text-white"
213
+ role === "user" ? "persona-bg-persona-accent" : "persona-bg-persona-primary",
214
+ "persona-text-white"
215
215
  );
216
216
  }
217
217
 
@@ -227,7 +227,7 @@ const createTimestamp = (
227
227
  ): HTMLElement => {
228
228
  const timestamp = createElement(
229
229
  "div",
230
- "tvw-text-xs tvw-text-cw-muted"
230
+ "persona-text-xs persona-text-persona-muted"
231
231
  );
232
232
 
233
233
  const date = new Date(message.createdAt);
@@ -252,7 +252,7 @@ const getBubbleClasses = (
252
252
  role: "user" | "assistant" | "system",
253
253
  layout: AgentWidgetMessageLayoutConfig["layout"] = "bubble"
254
254
  ): string[] => {
255
- const baseClasses = ["vanilla-message-bubble", "tvw-max-w-[85%]"];
255
+ const baseClasses = ["vanilla-message-bubble", "persona-max-w-[85%]"];
256
256
 
257
257
  switch (layout) {
258
258
  case "flat":
@@ -260,15 +260,15 @@ const getBubbleClasses = (
260
260
  if (role === "user") {
261
261
  baseClasses.push(
262
262
  "vanilla-message-user-bubble",
263
- "tvw-ml-auto",
264
- "tvw-text-cw-primary",
265
- "tvw-py-2"
263
+ "persona-ml-auto",
264
+ "persona-text-persona-primary",
265
+ "persona-py-2"
266
266
  );
267
267
  } else {
268
268
  baseClasses.push(
269
269
  "vanilla-message-assistant-bubble",
270
- "tvw-text-cw-primary",
271
- "tvw-py-2"
270
+ "persona-text-persona-primary",
271
+ "persona-py-2"
272
272
  );
273
273
  }
274
274
  break;
@@ -276,27 +276,27 @@ const getBubbleClasses = (
276
276
  case "minimal":
277
277
  // Minimal layout: reduced padding and styling
278
278
  baseClasses.push(
279
- "tvw-text-sm",
280
- "tvw-leading-relaxed"
279
+ "persona-text-sm",
280
+ "persona-leading-relaxed"
281
281
  );
282
282
  if (role === "user") {
283
283
  baseClasses.push(
284
284
  "vanilla-message-user-bubble",
285
- "tvw-ml-auto",
286
- "tvw-bg-cw-accent",
287
- "tvw-text-white",
288
- "tvw-px-3",
289
- "tvw-py-2",
290
- "tvw-rounded-lg"
285
+ "persona-ml-auto",
286
+ "persona-bg-persona-accent",
287
+ "persona-text-white",
288
+ "persona-px-3",
289
+ "persona-py-2",
290
+ "persona-rounded-lg"
291
291
  );
292
292
  } else {
293
293
  baseClasses.push(
294
294
  "vanilla-message-assistant-bubble",
295
- "tvw-bg-cw-surface",
296
- "tvw-text-cw-primary",
297
- "tvw-px-3",
298
- "tvw-py-2",
299
- "tvw-rounded-lg"
295
+ "persona-bg-persona-surface",
296
+ "persona-text-persona-primary",
297
+ "persona-px-3",
298
+ "persona-py-2",
299
+ "persona-rounded-lg"
300
300
  );
301
301
  }
302
302
  break;
@@ -305,29 +305,29 @@ const getBubbleClasses = (
305
305
  default:
306
306
  // Default bubble layout
307
307
  baseClasses.push(
308
- "tvw-rounded-2xl",
309
- "tvw-text-sm",
310
- "tvw-leading-relaxed",
311
- "tvw-shadow-sm"
308
+ "persona-rounded-2xl",
309
+ "persona-text-sm",
310
+ "persona-leading-relaxed",
311
+ "persona-shadow-sm"
312
312
  );
313
313
  if (role === "user") {
314
314
  baseClasses.push(
315
315
  "vanilla-message-user-bubble",
316
- "tvw-ml-auto",
317
- "tvw-bg-cw-accent",
318
- "tvw-text-white",
319
- "tvw-px-5",
320
- "tvw-py-3"
316
+ "persona-ml-auto",
317
+ "persona-bg-persona-accent",
318
+ "persona-text-white",
319
+ "persona-px-5",
320
+ "persona-py-3"
321
321
  );
322
322
  } else {
323
323
  baseClasses.push(
324
324
  "vanilla-message-assistant-bubble",
325
- "tvw-bg-cw-surface",
326
- "tvw-border",
327
- "tvw-border-cw-message-border",
328
- "tvw-text-cw-primary",
329
- "tvw-px-5",
330
- "tvw-py-3"
325
+ "persona-bg-persona-surface",
326
+ "persona-border",
327
+ "persona-border-persona-message-border",
328
+ "persona-text-persona-primary",
329
+ "persona-px-5",
330
+ "persona-py-3"
331
331
  );
332
332
  }
333
333
  break;
@@ -338,36 +338,53 @@ const getBubbleClasses = (
338
338
 
339
339
  /**
340
340
  * Create message action buttons (copy, upvote, downvote)
341
+ *
342
+ * This is a pure rendering function. It creates button elements with the
343
+ * correct `data-action` attributes, icons, and CSS classes. All click
344
+ * handling, vote state management, clipboard logic, and callback dispatch
345
+ * is handled via event delegation in `ui.ts` so that handlers survive
346
+ * idiomorph DOM morphing.
341
347
  */
342
348
  export const createMessageActions = (
343
349
  message: AgentWidgetMessage,
344
350
  actionsConfig: AgentWidgetMessageActionsConfig,
351
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
345
352
  _callbacks?: MessageActionCallbacks
346
353
  ): HTMLElement => {
347
354
  const showCopy = actionsConfig.showCopy ?? true;
348
355
  const showUpvote = actionsConfig.showUpvote ?? true;
349
356
  const showDownvote = actionsConfig.showDownvote ?? true;
357
+
358
+ // Don't render the container at all when no actions are visible
359
+ if (!showCopy && !showUpvote && !showDownvote) {
360
+ const empty = createElement("div");
361
+ empty.style.display = "none";
362
+ empty.id = `actions-${message.id}`;
363
+ empty.setAttribute("data-actions-for", message.id);
364
+ return empty;
365
+ }
366
+
350
367
  const visibility = actionsConfig.visibility ?? "hover";
351
368
  const align = actionsConfig.align ?? "right";
352
369
  const layout = actionsConfig.layout ?? "pill-inside";
353
370
 
354
371
  // Map alignment to CSS class
355
372
  const alignClass = {
356
- left: "tvw-message-actions-left",
357
- center: "tvw-message-actions-center",
358
- right: "tvw-message-actions-right",
373
+ left: "persona-message-actions-left",
374
+ center: "persona-message-actions-center",
375
+ right: "persona-message-actions-right",
359
376
  }[align];
360
377
 
361
378
  // Map layout to CSS class
362
379
  const layoutClass = {
363
- "pill-inside": "tvw-message-actions-pill",
364
- "row-inside": "tvw-message-actions-row",
380
+ "pill-inside": "persona-message-actions-pill",
381
+ "row-inside": "persona-message-actions-row",
365
382
  }[layout];
366
383
 
367
384
  const container = createElement(
368
385
  "div",
369
- `tvw-message-actions tvw-flex tvw-items-center tvw-gap-1 tvw-mt-2 ${alignClass} ${layoutClass} ${
370
- visibility === "hover" ? "tvw-message-actions-hover" : ""
386
+ `persona-message-actions persona-flex persona-items-center persona-gap-1 persona-mt-2 ${alignClass} ${layoutClass} ${
387
+ visibility === "hover" ? "persona-message-actions-hover" : ""
371
388
  }`
372
389
  );
373
390
  // Set id for idiomorph matching (prevents recreation on morph)
@@ -380,7 +397,7 @@ export const createMessageActions = (
380
397
  dataAction: string
381
398
  ): HTMLButtonElement => {
382
399
  const button = document.createElement("button");
383
- button.className = "tvw-message-action-btn";
400
+ button.className = "persona-message-action-btn";
384
401
  button.setAttribute("aria-label", label);
385
402
  button.setAttribute("title", label);
386
403
  button.setAttribute("data-action", dataAction);
@@ -393,17 +410,17 @@ export const createMessageActions = (
393
410
  return button;
394
411
  };
395
412
 
396
- // Copy button - click handled via event delegation in ui.ts
413
+ // Copy button
397
414
  if (showCopy) {
398
415
  container.appendChild(createActionButton("copy", "Copy message", "copy"));
399
416
  }
400
417
 
401
- // Upvote button - click handled via event delegation in ui.ts
418
+ // Upvote button
402
419
  if (showUpvote) {
403
420
  container.appendChild(createActionButton("thumbs-up", "Upvote", "upvote"));
404
421
  }
405
422
 
406
- // Downvote button - click handled via event delegation in ui.ts
423
+ // Downvote button
407
424
  if (showDownvote) {
408
425
  container.appendChild(createActionButton("thumbs-down", "Downvote", "downvote"));
409
426
  }
@@ -453,6 +470,15 @@ export const createStandardBubble = (
453
470
  bubble.id = `bubble-${message.id}`;
454
471
  bubble.setAttribute("data-message-id", message.id);
455
472
 
473
+ // Apply component-level color overrides via CSS variables
474
+ if (message.role === "user") {
475
+ bubble.style.backgroundColor = 'var(--persona-message-user-bg, var(--persona-accent))';
476
+ bubble.style.color = 'var(--persona-message-user-text, white)';
477
+ } else if (message.role === "assistant") {
478
+ bubble.style.backgroundColor = 'var(--persona-message-assistant-bg, var(--persona-surface))';
479
+ bubble.style.color = 'var(--persona-message-assistant-text, var(--persona-text))';
480
+ }
481
+
456
482
  const imageParts = getMessageImageParts(message);
457
483
  const messageContentText = message.content?.trim() ?? "";
458
484
  const isImageOnlyFallbackMessage =
@@ -461,6 +487,7 @@ export const createStandardBubble = (
461
487
 
462
488
  // Add message content
463
489
  const contentDiv = document.createElement("div");
490
+ contentDiv.classList.add("persona-message-content");
464
491
  const transformedContent = transform({
465
492
  text: message.content,
466
493
  message,
@@ -481,7 +508,7 @@ export const createStandardBubble = (
481
508
  // Add inline timestamp if configured
482
509
  if (showTimestamp && timestampPosition === "inline" && message.createdAt) {
483
510
  const timestamp = createTimestamp(message, timestampConfig!);
484
- timestamp.classList.add("tvw-ml-2", "tvw-inline");
511
+ timestamp.classList.add("persona-ml-2", "persona-inline");
485
512
  contentDiv.appendChild(timestamp);
486
513
  }
487
514
 
@@ -508,7 +535,7 @@ export const createStandardBubble = (
508
535
  // Add timestamp below if configured
509
536
  if (showTimestamp && timestampPosition === "below" && message.createdAt) {
510
537
  const timestamp = createTimestamp(message, timestampConfig!);
511
- timestamp.classList.add("tvw-mt-1");
538
+ timestamp.classList.add("persona-mt-1");
512
539
  bubble.appendChild(timestamp);
513
540
  }
514
541
 
@@ -548,7 +575,7 @@ export const createStandardBubble = (
548
575
  // Create wrapper with avatar
549
576
  const wrapper = createElement(
550
577
  "div",
551
- `tvw-flex tvw-gap-2 ${message.role === "user" ? "tvw-flex-row-reverse" : ""}`
578
+ `persona-flex persona-gap-2 ${message.role === "user" ? "persona-flex-row-reverse" : ""}`
552
579
  );
553
580
 
554
581
  const avatar = createAvatar(avatarConfig!, message.role);
@@ -560,8 +587,8 @@ export const createStandardBubble = (
560
587
  }
561
588
 
562
589
  // Adjust bubble max-width when avatar is present
563
- bubble.classList.remove("tvw-max-w-[85%]");
564
- bubble.classList.add("tvw-max-w-[calc(85%-2.5rem)]");
590
+ bubble.classList.remove("persona-max-w-[85%]");
591
+ bubble.classList.add("persona-max-w-[calc(85%-2.5rem)]");
565
592
 
566
593
  return wrapper;
567
594
  };
@@ -35,9 +35,9 @@ export const renderMessages = (
35
35
  );
36
36
  }
37
37
 
38
- const wrapper = createElement("div", "tvw-flex");
38
+ const wrapper = createElement("div", "persona-flex");
39
39
  if (message.role === "user") {
40
- wrapper.classList.add("tvw-justify-end");
40
+ wrapper.classList.add("persona-justify-end");
41
41
  }
42
42
  wrapper.appendChild(bubble);
43
43
  fragment.appendChild(wrapper);
@@ -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 } from "../utils/dock";
4
5
  import { buildHeader, attachHeaderToContainer, HeaderElements } from "./header-builder";
5
6
  import { buildHeaderWithLayout } from "./header-layouts";
6
7
  import { buildComposer, ComposerElements } from "./composer-builder";
@@ -12,17 +13,32 @@ export interface PanelWrapper {
12
13
 
13
14
  export const createWrapper = (config?: AgentWidgetConfig): PanelWrapper => {
14
15
  const launcherEnabled = config?.launcher?.enabled ?? true;
16
+ const dockedMode = isDockedMountMode(config);
17
+
18
+ if (dockedMode) {
19
+ const wrapper = createElement(
20
+ "div",
21
+ "persona-relative persona-h-full persona-w-full persona-flex persona-flex-1 persona-min-h-0 persona-flex-col"
22
+ );
23
+ const panel = createElement(
24
+ "div",
25
+ "persona-relative persona-h-full persona-w-full persona-flex persona-flex-1 persona-min-h-0 persona-flex-col"
26
+ );
27
+
28
+ wrapper.appendChild(panel);
29
+ return { wrapper, panel };
30
+ }
15
31
 
16
32
  if (!launcherEnabled) {
17
33
  // For inline embed mode, use flex layout to ensure the widget fills its container
18
34
  // and only the chat messages area scrolls
19
35
  const wrapper = createElement(
20
36
  "div",
21
- "tvw-relative tvw-h-full tvw-flex tvw-flex-col tvw-flex-1 tvw-min-h-0"
37
+ "persona-relative persona-h-full persona-flex persona-flex-col persona-flex-1 persona-min-h-0"
22
38
  );
23
39
  const panel = createElement(
24
40
  "div",
25
- "tvw-relative tvw-flex-1 tvw-flex tvw-flex-col tvw-min-h-0"
41
+ "persona-relative persona-flex-1 persona-flex persona-flex-col persona-min-h-0"
26
42
  );
27
43
 
28
44
  // Apply width from config, defaulting to 100% for inline embed mode
@@ -42,12 +58,12 @@ export const createWrapper = (config?: AgentWidgetConfig): PanelWrapper => {
42
58
 
43
59
  const wrapper = createElement(
44
60
  "div",
45
- `tvw-widget-wrapper tvw-fixed ${position} tvw-z-50 tvw-transition`
61
+ `persona-widget-wrapper persona-fixed ${position} persona-z-50 persona-transition`
46
62
  );
47
63
 
48
64
  const panel = createElement(
49
65
  "div",
50
- "tvw-widget-panel tvw-relative tvw-min-h-[320px]"
66
+ "persona-widget-panel persona-relative persona-min-h-[320px]"
51
67
  );
52
68
  const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;
53
69
  const width = launcherWidth ?? "min(400px, calc(100vw - 24px))";
@@ -98,7 +114,7 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
98
114
  // the body (chat messages area) to scroll while header/footer stay fixed
99
115
  const container = createElement(
100
116
  "div",
101
- "tvw-widget-container tvw-flex tvw-h-full tvw-w-full tvw-flex-1 tvw-min-h-0 tvw-flex-col tvw-bg-cw-surface tvw-text-cw-primary tvw-rounded-2xl tvw-overflow-hidden tvw-border tvw-border-cw-border"
117
+ "persona-widget-container persona-flex persona-h-full persona-w-full persona-flex-1 persona-min-h-0 persona-flex-col persona-bg-persona-surface persona-text-persona-primary persona-rounded-2xl persona-overflow-hidden persona-border persona-border-persona-border"
102
118
  );
103
119
 
104
120
  // Build header using layout config if available, otherwise use standard builder
@@ -111,22 +127,22 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
111
127
  // Build body with intro card and messages wrapper
112
128
  const body = createElement(
113
129
  "div",
114
- "tvw-widget-body tvw-flex tvw-flex-1 tvw-min-h-0 tvw-flex-col tvw-gap-6 tvw-overflow-y-auto tvw-bg-cw-container tvw-px-6 tvw-py-6"
130
+ "persona-widget-body persona-flex persona-flex-1 persona-min-h-0 persona-flex-col persona-gap-6 persona-overflow-y-auto persona-bg-persona-container persona-px-6 persona-py-6"
115
131
  );
116
132
  body.id = "persona-scroll-container";
117
133
 
118
134
  const introCard = createElement(
119
135
  "div",
120
- "tvw-rounded-2xl tvw-bg-cw-surface tvw-p-6 tvw-shadow-sm"
136
+ "persona-rounded-2xl persona-bg-persona-surface persona-p-6 persona-shadow-sm"
121
137
  );
122
138
  const introTitle = createElement(
123
139
  "h2",
124
- "tvw-text-lg tvw-font-semibold tvw-text-cw-primary"
140
+ "persona-text-lg persona-font-semibold persona-text-persona-primary"
125
141
  );
126
142
  introTitle.textContent = config?.copy?.welcomeTitle ?? "Hello 👋";
127
143
  const introSubtitle = createElement(
128
144
  "p",
129
- "tvw-mt-2 tvw-text-sm tvw-text-cw-muted"
145
+ "persona-mt-2 persona-text-sm persona-text-persona-muted"
130
146
  );
131
147
  introSubtitle.textContent =
132
148
  config?.copy?.welcomeSubtitle ??
@@ -135,10 +151,25 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
135
151
 
136
152
  const messagesWrapper = createElement(
137
153
  "div",
138
- "tvw-flex tvw-flex-col tvw-gap-3"
154
+ "persona-flex persona-flex-col persona-gap-3"
139
155
  );
140
156
 
141
- body.append(introCard, messagesWrapper);
157
+ const contentMaxWidth = config?.layout?.contentMaxWidth;
158
+ if (contentMaxWidth) {
159
+ messagesWrapper.style.maxWidth = contentMaxWidth;
160
+ messagesWrapper.style.marginLeft = "auto";
161
+ messagesWrapper.style.marginRight = "auto";
162
+ messagesWrapper.style.width = "100%";
163
+ }
164
+
165
+ const showWelcomeCard = config?.copy?.showWelcomeCard !== false;
166
+ if (!showWelcomeCard) {
167
+ body.classList.remove("persona-gap-6");
168
+ body.classList.add("persona-gap-3");
169
+ body.append(messagesWrapper);
170
+ } else {
171
+ body.append(introCard, messagesWrapper);
172
+ }
142
173
 
143
174
  // Build composer/footer using extracted builder
144
175
  const composerElements: ComposerElements = buildComposer({ config });
@@ -10,15 +10,15 @@ export const reasoningExpansionState = new Set<string>();
10
10
  export const updateReasoningBubbleUI = (messageId: string, bubble: HTMLElement): void => {
11
11
  const expanded = reasoningExpansionState.has(messageId);
12
12
  const header = bubble.querySelector('button[data-expand-header="true"]') as HTMLElement;
13
- const content = bubble.querySelector('.tvw-border-t') as HTMLElement;
13
+ const content = bubble.querySelector('.persona-border-t') as HTMLElement;
14
14
 
15
15
  if (!header || !content) return;
16
16
 
17
17
  header.setAttribute("aria-expanded", expanded ? "true" : "false");
18
18
 
19
- // Find toggle icon container - it's the direct child div of headerMeta (which has tvw-ml-auto)
20
- const headerMeta = header.querySelector('.tvw-ml-auto') as HTMLElement;
21
- const toggleIcon = headerMeta?.querySelector(':scope > .tvw-flex.tvw-items-center') as HTMLElement;
19
+ // Find toggle icon container - it's the direct child div of headerMeta (which has persona-ml-auto)
20
+ const headerMeta = header.querySelector('.persona-ml-auto') as HTMLElement;
21
+ const toggleIcon = headerMeta?.querySelector(':scope > .persona-flex.persona-items-center') as HTMLElement;
22
22
  if (toggleIcon) {
23
23
  toggleIcon.innerHTML = "";
24
24
  const iconColor = "currentColor";
@@ -40,17 +40,17 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
40
40
  [
41
41
  "vanilla-message-bubble",
42
42
  "vanilla-reasoning-bubble",
43
- "tvw-w-full",
44
- "tvw-max-w-[85%]",
45
- "tvw-rounded-2xl",
46
- "tvw-bg-cw-surface",
47
- "tvw-border",
48
- "tvw-border-cw-message-border",
49
- "tvw-text-cw-primary",
50
- "tvw-shadow-sm",
51
- "tvw-overflow-hidden",
52
- "tvw-px-0",
53
- "tvw-py-0"
43
+ "persona-w-full",
44
+ "persona-max-w-[85%]",
45
+ "persona-rounded-2xl",
46
+ "persona-bg-persona-surface",
47
+ "persona-border",
48
+ "persona-border-persona-message-border",
49
+ "persona-text-persona-primary",
50
+ "persona-shadow-sm",
51
+ "persona-overflow-hidden",
52
+ "persona-px-0",
53
+ "persona-py-0"
54
54
  ].join(" ")
55
55
  );
56
56
  // Set id for idiomorph matching
@@ -64,19 +64,19 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
64
64
  let expanded = reasoningExpansionState.has(message.id);
65
65
  const header = createElement(
66
66
  "button",
67
- "tvw-flex tvw-w-full tvw-items-center tvw-justify-between tvw-gap-3 tvw-bg-transparent tvw-px-4 tvw-py-3 tvw-text-left tvw-cursor-pointer tvw-border-none"
67
+ "persona-flex persona-w-full persona-items-center persona-justify-between persona-gap-3 persona-bg-transparent persona-px-4 persona-py-3 persona-text-left persona-cursor-pointer persona-border-none"
68
68
  ) as HTMLButtonElement;
69
69
  header.type = "button";
70
70
  header.setAttribute("aria-expanded", expanded ? "true" : "false");
71
71
  header.setAttribute("data-expand-header", "true");
72
72
  header.setAttribute("data-bubble-type", "reasoning");
73
73
 
74
- const headerContent = createElement("div", "tvw-flex tvw-flex-col tvw-text-left");
75
- const title = createElement("span", "tvw-text-xs tvw-text-cw-primary");
74
+ const headerContent = createElement("div", "persona-flex persona-flex-col persona-text-left");
75
+ const title = createElement("span", "persona-text-xs persona-text-persona-primary");
76
76
  title.textContent = "Thinking...";
77
77
  headerContent.appendChild(title);
78
78
 
79
- const status = createElement("span", "tvw-text-xs tvw-text-cw-primary");
79
+ const status = createElement("span", "persona-text-xs persona-text-persona-primary");
80
80
  status.textContent = describeReasonStatus(reasoning);
81
81
  headerContent.appendChild(status);
82
82
 
@@ -86,7 +86,7 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
86
86
  title.style.display = "";
87
87
  }
88
88
 
89
- const toggleIcon = createElement("div", "tvw-flex tvw-items-center");
89
+ const toggleIcon = createElement("div", "persona-flex persona-items-center");
90
90
  const iconColor = "currentColor";
91
91
  const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
92
92
  if (chevronIcon) {
@@ -96,21 +96,21 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
96
96
  toggleIcon.textContent = expanded ? "Hide" : "Show";
97
97
  }
98
98
 
99
- const headerMeta = createElement("div", "tvw-flex tvw-items-center tvw-ml-auto");
99
+ const headerMeta = createElement("div", "persona-flex persona-items-center persona-ml-auto");
100
100
  headerMeta.append(toggleIcon);
101
101
 
102
102
  header.append(headerContent, headerMeta);
103
103
 
104
104
  const content = createElement(
105
105
  "div",
106
- "tvw-border-t tvw-px-4 tvw-py-3"
106
+ "persona-border-t persona-border-gray-200 persona-bg-gray-50 persona-px-4 persona-py-3"
107
107
  );
108
108
  content.style.display = expanded ? "" : "none";
109
109
 
110
110
  const text = reasoning.chunks.join("");
111
111
  const body = createElement(
112
112
  "div",
113
- "tvw-whitespace-pre-wrap tvw-text-xs tvw-leading-snug tvw-text-cw-muted"
113
+ "persona-whitespace-pre-wrap persona-text-xs persona-leading-snug persona-text-persona-muted"
114
114
  );
115
115
  body.textContent =
116
116
  text ||
@@ -1,4 +1,5 @@
1
1
  import { AgentWidgetConfig, AgentWidgetMessage } from "../types";
2
+ import { PersonaArtifactCard } from "./artifact-card";
2
3
 
3
4
  /**
4
5
  * Context provided to component renderers
@@ -85,3 +86,6 @@ class ComponentRegistry {
85
86
  * Global component registry instance
86
87
  */
87
88
  export const componentRegistry = new ComponentRegistry();
89
+
90
+ // Register built-in components
91
+ componentRegistry.register("PersonaArtifactCard", PersonaArtifactCard);
@@ -52,7 +52,7 @@ export const createSuggestions = (container: HTMLElement): SuggestionButtons =>
52
52
  chips.forEach((chip) => {
53
53
  const btn = createElement(
54
54
  "button",
55
- "tvw-rounded-button tvw-bg-cw-surface tvw-px-3 tvw-py-1.5 tvw-text-xs tvw-font-medium tvw-text-cw-muted hover:tvw-opacity-90 tvw-cursor-pointer tvw-border tvw-border-gray-200"
55
+ "persona-rounded-button persona-bg-persona-surface persona-px-3 persona-py-1.5 persona-text-xs persona-font-medium persona-text-persona-muted hover:persona-opacity-90 persona-cursor-pointer persona-border persona-border-gray-200"
56
56
  ) as HTMLButtonElement;
57
57
  btn.type = "button";
58
58
  btn.textContent = chip;