@runtypelabs/persona 4.14.0 → 4.16.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 (119) 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 +520 -21
  14. package/dist/context-mentions.d.ts +520 -21
  15. package/dist/context-mentions.js +11 -8
  16. package/dist/index.cjs +69 -68
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +595 -46
  19. package/dist/index.d.ts +595 -46
  20. package/dist/index.global.js +60 -57
  21. package/dist/index.global.js.map +1 -1
  22. package/dist/index.js +69 -68
  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 +520 -21
  27. package/dist/smart-dom-reader.d.ts +520 -21
  28. package/dist/testing.cjs +3 -3
  29. package/dist/testing.d.cts +7 -0
  30. package/dist/testing.d.ts +7 -0
  31. package/dist/testing.js +3 -3
  32. package/dist/theme-editor-preview.cjs +67 -67
  33. package/dist/theme-editor-preview.d.cts +534 -22
  34. package/dist/theme-editor-preview.d.ts +534 -22
  35. package/dist/theme-editor-preview.js +57 -57
  36. package/dist/theme-editor.cjs +10 -10
  37. package/dist/theme-editor.d.cts +521 -23
  38. package/dist/theme-editor.d.ts +521 -23
  39. package/dist/theme-editor.js +10 -10
  40. package/dist/theme-reference.cjs +1 -1
  41. package/dist/theme-reference.d.cts +104 -3
  42. package/dist/theme-reference.d.ts +104 -3
  43. package/dist/theme-reference.js +1 -1
  44. package/dist/widget.css +1 -1
  45. package/package.json +1 -1
  46. package/src/ask-user-question-tool.ts +10 -7
  47. package/src/components/artifact-pane.test.ts +27 -0
  48. package/src/components/artifact-pane.ts +7 -3
  49. package/src/components/artifact-preview.ts +1 -1
  50. package/src/components/composer-parts.test.ts +103 -4
  51. package/src/components/composer-parts.ts +53 -15
  52. package/src/components/header-builder.ts +27 -2
  53. package/src/components/header-layouts.test.ts +56 -0
  54. package/src/components/header-layouts.ts +29 -9
  55. package/src/components/launcher-surface.test.ts +403 -0
  56. package/src/components/launcher.ts +297 -2
  57. package/src/components/messages.ts +5 -2
  58. package/src/components/panel.ts +92 -71
  59. package/src/components/pill-composer-builder.test.ts +18 -0
  60. package/src/components/suggestions.test.ts +273 -0
  61. package/src/components/suggestions.ts +528 -86
  62. package/src/components/welcome.test.ts +121 -0
  63. package/src/components/welcome.ts +305 -0
  64. package/src/components/widget-view.test.ts +5 -3
  65. package/src/components/widget-view.ts +18 -7
  66. package/src/defaults.ts +7 -5
  67. package/src/generated/runtype-openapi-contract.ts +17 -0
  68. package/src/index-core.ts +29 -2
  69. package/src/launcher-global.ts +11 -8
  70. package/src/plugins/types.ts +136 -3
  71. package/src/presets.ts +3 -3
  72. package/src/session.ts +10 -8
  73. package/src/styles/widget-styles.test.ts +382 -0
  74. package/src/styles/widget.css +778 -4
  75. package/src/suggest-replies-tool.test.ts +233 -2
  76. package/src/suggest-replies-tool.ts +115 -39
  77. package/src/testing/mock-stream.test.ts +19 -0
  78. package/src/testing/mock-stream.ts +20 -0
  79. package/src/theme-editor/preview-utils.test.ts +34 -0
  80. package/src/theme-editor/preview-utils.ts +77 -2
  81. package/src/theme-editor/sections.test.ts +33 -0
  82. package/src/theme-editor/sections.ts +83 -6
  83. package/src/theme-reference.ts +34 -5
  84. package/src/types/theme.ts +72 -3
  85. package/src/types.ts +312 -18
  86. package/src/ui.body-top-slot.test.ts +95 -0
  87. package/src/ui.content-width.test.ts +91 -0
  88. package/src/ui.detached-panel.test.ts +1 -1
  89. package/src/ui.docked.test.ts +7 -4
  90. package/src/ui.launcher-teaser.test.ts +162 -0
  91. package/src/ui.rebuild-composer.test.ts +491 -0
  92. package/src/ui.render-welcome.test.ts +505 -0
  93. package/src/ui.scroll-additive.test.ts +91 -21
  94. package/src/ui.scroll.test.ts +302 -14
  95. package/src/ui.suggest-replies.test.ts +1112 -1
  96. package/src/ui.ts +1008 -149
  97. package/src/ui.welcome.test.ts +504 -0
  98. package/src/utils/artifact-file.test.ts +1 -1
  99. package/src/utils/artifact-gate.test.ts +27 -0
  100. package/src/utils/artifact-gate.ts +9 -0
  101. package/src/utils/attachment-manager.test.ts +37 -0
  102. package/src/utils/attachment-manager.ts +15 -0
  103. package/src/utils/auto-follow.test.ts +23 -0
  104. package/src/utils/auto-follow.ts +11 -1
  105. package/src/utils/code-generators.test.ts +40 -0
  106. package/src/utils/code-generators.ts +22 -0
  107. package/src/utils/config-merge.test.ts +24 -0
  108. package/src/utils/config-merge.ts +2 -0
  109. package/src/utils/content-width.test.ts +37 -0
  110. package/src/utils/content-width.ts +17 -0
  111. package/src/utils/icons.test.ts +26 -0
  112. package/src/utils/icons.ts +8 -0
  113. package/src/utils/plugin-storage.test.ts +94 -0
  114. package/src/utils/plugin-storage.ts +98 -0
  115. package/src/utils/theme.test.ts +216 -0
  116. package/src/utils/tokens.ts +113 -3
  117. package/src/welcome.test.ts +213 -0
  118. package/src/welcome.ts +130 -0
  119. package/dist/chunk-MMUPR2JW.js +0 -1
package/src/types.ts CHANGED
@@ -1251,6 +1251,13 @@ export type AgentWidgetArtifactsLayoutConfig = {
1251
1251
  * @default 520
1252
1252
  */
1253
1253
  narrowHostMaxWidth?: number;
1254
+ /**
1255
+ * Width of the slide-over artifact drawer (the narrow-host and mobile
1256
+ * presentations; `paneWidth`/`paneMaxWidth` only size the desktop split).
1257
+ * CSS length; `"100%"` makes the drawer cover the whole panel.
1258
+ * @default min(calc(100% - 1rem), 22rem)
1259
+ */
1260
+ drawerWidth?: string;
1254
1261
  /**
1255
1262
  * When true (default), widen the launcher panel while artifacts are visible and not user-dismissed.
1256
1263
  * No-op for inline embed (`launcher.enabled === false`).
@@ -1894,6 +1901,19 @@ export type AgentWidgetScrollMode = "follow" | "anchor-top" | "none";
1894
1901
  */
1895
1902
  export type AgentWidgetScrollRestorePosition = "bottom" | "last-user-turn";
1896
1903
 
1904
+ /**
1905
+ * Scrollbar visibility policy for every scroll surface in the widget.
1906
+ *
1907
+ * - `"on-scroll"` hides bars at rest and reveals them on genuine reader
1908
+ * input, keeping them visible while the reader is away from the resting
1909
+ * position: overlay-scrollbar semantics on every platform.
1910
+ * - `"auto"` keeps the themed bar under native visibility semantics: overlay
1911
+ * platforms still auto-hide, classic-scrollbar platforms (Windows,
1912
+ * always-show macOS) show it persistently.
1913
+ * - `"hidden"` never shows an indicator. Scrolling itself is unaffected.
1914
+ */
1915
+ export type AgentWidgetScrollbarPolicy = "on-scroll" | "auto" | "hidden";
1916
+
1897
1917
  export type AgentWidgetScrollBehaviorFeature = {
1898
1918
  /** Scroll behavior during streamed responses. @default "anchor-top" */
1899
1919
  mode?: AgentWidgetScrollMode;
@@ -1938,6 +1958,13 @@ export type AgentWidgetScrollBehaviorFeature = {
1938
1958
  * @default false
1939
1959
  */
1940
1960
  announce?: boolean;
1961
+ /**
1962
+ * Scrollbar visibility policy, applied to the transcript and every inner
1963
+ * scroller through one root attribute. Themable via
1964
+ * `--persona-scrollbar-thumb` and `--persona-scrollbar-track`.
1965
+ * @default "on-scroll"
1966
+ */
1967
+ scrollbar?: AgentWidgetScrollbarPolicy;
1941
1968
  };
1942
1969
 
1943
1970
  export type AgentWidgetScrollToBottomFeature = {
@@ -2257,10 +2284,13 @@ export type AgentWidgetFeatureFlags = {
2257
2284
  */
2258
2285
  askUserQuestion?: AgentWidgetAskUserQuestionFeature;
2259
2286
  /**
2260
- * Built-in `suggest_replies` quick-reply chips. When the assistant invokes
2261
- * the tool, the widget shows the suggestions as tappable chips above the
2262
- * composer (reusing the suggestion-chips surface) and immediately resumes
2263
- * the execution: fire-and-forget, no user input awaited.
2287
+ * Built-in `suggest_replies` follow-ups. When the assistant invokes the
2288
+ * tool, the widget shows the suggestions using `suggestions.followUps` and
2289
+ * immediately resumes the execution: fire-and-forget, no user input awaited.
2290
+ *
2291
+ * @deprecated Both keys moved to `suggestions.followUps`: use
2292
+ * `suggestions.followUps.enabled` and `suggestions.followUps.expose`. This
2293
+ * alias keeps working per key, with `suggestions.followUps` winning.
2264
2294
  */
2265
2295
  suggestReplies?: AgentWidgetSuggestRepliesFeature;
2266
2296
  };
@@ -2327,11 +2357,12 @@ export type WidgetPreferenceSlice = {
2327
2357
  };
2328
2358
 
2329
2359
  /**
2330
- * Feature config for the built-in `suggest_replies` quick-reply chips.
2331
- * Chips render in the existing suggestions slot above the composer and are
2332
- * styled by the widget-level `suggestionChipsConfig`. A tapped chip is sent
2333
- * verbatim as the user's next message; chips clear once any user message
2334
- * follows them.
2360
+ * Feature config for the built-in `suggest_replies` follow-ups.
2361
+ *
2362
+ * @deprecated Both keys moved to `suggestions.followUps`
2363
+ * (`suggestions.followUps.enabled` / `suggestions.followUps.expose`), which
2364
+ * also owns presentation, placement, and send/fill behavior. Resolution is
2365
+ * per key with `suggestions.followUps` winning; removal waits for 5.0.
2335
2366
  */
2336
2367
  export type AgentWidgetSuggestRepliesFeature = {
2337
2368
  /**
@@ -2339,14 +2370,18 @@ export type AgentWidgetSuggestRepliesFeature = {
2339
2370
  * renders as a regular tool bubble and is NOT auto-resumed: only set this
2340
2371
  * with no server-side `suggest_replies` declaration, or the execution
2341
2372
  * parks awaiting a resume that never comes.
2373
+ * @deprecated Use `suggestions.followUps.enabled`.
2342
2374
  */
2343
2375
  enabled?: boolean;
2344
2376
  /**
2345
2377
  * Advertise the built-in `suggest_replies` tool to the agent on every
2346
2378
  * dispatch via `clientTools[]`: no server-side `runtimeTools` declaration
2347
- * needed. Defaults to `false`: flows that already declare the tool via
2348
- * `runtimeTools` would otherwise present it to the model twice. Ignored
2349
- * when `enabled` is `false`.
2379
+ * needed. Defaults to `false`. Declare the tool in one place: the API
2380
+ * dedupes a double declaration by name with `clientTools[]` winning, so the
2381
+ * model still sees exactly one tool, but the widget's description and schema
2382
+ * win and the effective tool type becomes `local` (the execution pauses and
2383
+ * the widget auto-resumes it). Ignored when `enabled` is `false`.
2384
+ * @deprecated Use `suggestions.followUps.expose`.
2350
2385
  */
2351
2386
  expose?: boolean;
2352
2387
  };
@@ -2421,10 +2456,13 @@ export type AgentWidgetAskUserQuestionFeature = {
2421
2456
  * matching {@link AskUserQuestionPayload}; when the model calls it, the
2422
2457
  * existing answer-pill sheet renders and the answer resumes the execution.
2423
2458
  *
2424
- * Defaults to `false`: flows that already declare `ask_user_question` via
2425
- * `runtimeTools` would otherwise present the tool to the model twice.
2426
- * Ignored when `enabled` is `false`: never offer the agent a question
2427
- * tool the widget can't render an answer UI for.
2459
+ * Defaults to `false`. Declare the tool in one place: the API dedupes a
2460
+ * double declaration by name with `clientTools[]` winning, so the model
2461
+ * still sees exactly one tool, but the widget's description and schema win
2462
+ * and the effective tool type becomes `local` (the execution pauses and the
2463
+ * widget resumes it with the user's answer). Ignored when `enabled` is
2464
+ * `false`: never offer the agent a question tool the widget can't render an
2465
+ * answer UI for.
2428
2466
  */
2429
2467
  expose?: boolean;
2430
2468
  /** Slide-in animation duration in ms. Defaults to 180. */
@@ -2904,6 +2942,58 @@ export type AgentWidgetLauncherConfig = {
2904
2942
  * @example "min(380px, calc(100vw - 48px))"
2905
2943
  */
2906
2944
  collapsedMaxWidth?: string;
2945
+ /**
2946
+ * Proactive nudge bubble anchored above the collapsed floating launcher.
2947
+ * Omit it (the default) and no teaser renders.
2948
+ */
2949
+ teaser?: AgentWidgetLauncherTeaserConfig;
2950
+ };
2951
+
2952
+ /**
2953
+ * Launcher teaser: a small bubble above the collapsed launcher. Clicking it
2954
+ * opens the panel and consumes the teaser exactly like an explicit dismissal.
2955
+ * Within a single page load the teaser appears at most once under either
2956
+ * frequency.
2957
+ *
2958
+ * @example
2959
+ * ```typescript
2960
+ * launcher: {
2961
+ * teaser: { text: "Need a hand picking a plan?", delayMs: 3000 }
2962
+ * }
2963
+ * ```
2964
+ */
2965
+ export type AgentWidgetLauncherTeaserConfig = {
2966
+ /** Bubble copy. An empty string renders no teaser. */
2967
+ text: string;
2968
+ /**
2969
+ * Delay before the bubble appears, in milliseconds.
2970
+ * @default 0
2971
+ */
2972
+ delayMs?: number;
2973
+ /**
2974
+ * How often the teaser may return.
2975
+ *
2976
+ * `"once"`: a dismissal or a click-through is remembered in `localStorage`
2977
+ * under `` `${persistState.keyPrefix ?? "persona-"}teaser-dismissed` `` and
2978
+ * the teaser never returns on that browser. `"always"`: shows on every page
2979
+ * load, tracked in memory only. `persistState: false` downgrades `"once"` to
2980
+ * in-memory too, so the teaser reappears each load.
2981
+ *
2982
+ * Custom `storageAdapter` implementations do not participate in teaser state.
2983
+ *
2984
+ * @default "once"
2985
+ */
2986
+ frequency?: "once" | "always";
2987
+ /**
2988
+ * Render an x affordance that dismisses the teaser without opening the panel.
2989
+ * @default true
2990
+ */
2991
+ dismissible?: boolean;
2992
+ /**
2993
+ * Accessible label for the dismiss affordance.
2994
+ * @default "Dismiss message"
2995
+ */
2996
+ dismissLabel?: string;
2907
2997
  };
2908
2998
 
2909
2999
  export type AgentWidgetSendButtonConfig = {
@@ -2913,6 +3003,10 @@ export type AgentWidgetSendButtonConfig = {
2913
3003
  paddingY?: string;
2914
3004
  iconText?: string;
2915
3005
  iconName?: string;
3006
+ /** Icon glyph box in icon mode, e.g. "20px". Defaults to half of `size`. */
3007
+ iconSize?: string;
3008
+ /** Stroke width for the icon glyph, in viewBox units. Default 2. */
3009
+ iconStrokeWidth?: number;
2916
3010
  useIcon?: boolean;
2917
3011
  tooltipText?: string;
2918
3012
  showTooltip?: boolean;
@@ -3728,6 +3822,187 @@ export type AgentWidgetSuggestionChipsConfig = {
3728
3822
  paddingY?: string;
3729
3823
  };
3730
3824
 
3825
+ /** A suggested next action shown to the user. Strings remain supported as shorthand. */
3826
+ export type AgentWidgetSuggestion =
3827
+ | string
3828
+ | {
3829
+ /** Stable analytics / DOM identity. Falls back to the prompt or label. */
3830
+ id?: string;
3831
+ /** Concise visible action label. */
3832
+ label: string;
3833
+ /** Text placed in the composer or sent. Defaults to `label`. */
3834
+ prompt?: string;
3835
+ /**
3836
+ * Optional supporting copy, most useful with the `card` variant. The
3837
+ * `chip` variant ignores it and renders the label alone.
3838
+ */
3839
+ description?: string;
3840
+ /** Optional icon from Persona's bundled Lucide registry. */
3841
+ icon?: IconName;
3842
+ /**
3843
+ * CSS color for this item's glyph. Tints the icon only: label, border,
3844
+ * and background stay neutral. Ignored without an `icon`.
3845
+ */
3846
+ iconColor?: string;
3847
+ /** Override the surface's click behavior for this item. */
3848
+ behavior?: AgentWidgetSuggestionBehavior;
3849
+ /** Visually promote one high-confidence action. */
3850
+ emphasis?: "default" | "primary";
3851
+ };
3852
+
3853
+ export type AgentWidgetSuggestionBehavior = "send" | "fill";
3854
+ export type AgentWidgetSuggestionVariant = "chip" | "card" | "list";
3855
+ export type AgentWidgetSuggestionSurface = "starter" | "followUp";
3856
+ /**
3857
+ * Who produced the rendered suggestions: static config, the agent's
3858
+ * `suggest_replies` tool, or `controller.setFollowUpSuggestions`.
3859
+ */
3860
+ export type AgentWidgetSuggestionSource = "config" | "agent" | "host";
3861
+
3862
+ /**
3863
+ * A suggestion after Persona has normalized shorthand strings and resolved the
3864
+ * effective click behavior. Plugin hooks receive this stable shape.
3865
+ */
3866
+ export type AgentWidgetResolvedSuggestion = {
3867
+ id: string;
3868
+ label: string;
3869
+ prompt: string;
3870
+ description?: string;
3871
+ icon?: IconName;
3872
+ /** CSS color for the glyph only; never the label, border, or background. */
3873
+ iconColor?: string;
3874
+ behavior: AgentWidgetSuggestionBehavior;
3875
+ emphasis: "default" | "primary";
3876
+ };
3877
+
3878
+ /** Presentation keys shared by every suggestion surface. */
3879
+ export type AgentWidgetSuggestionSurfaceConfig = {
3880
+ /** Item density. Defaults to `card` for starters and `chip` for follow-ups. */
3881
+ variant?: AgentWidgetSuggestionVariant;
3882
+ /** Default click behavior, overridable per item. Defaults to `send`. */
3883
+ behavior?: AgentWidgetSuggestionBehavior;
3884
+ /**
3885
+ * Layout for items past the surface width. Defaults to `wrap` on every
3886
+ * surface; `scroll` opts into a horizontal strip for large sets. Applies
3887
+ * to the `chip` variant only; the `card` and `list` layouts manage their
3888
+ * own stacking.
3889
+ */
3890
+ overflow?: "scroll" | "wrap";
3891
+ /** Maximum visible items. Defaults to 4. */
3892
+ maxItems?: number;
3893
+ };
3894
+
3895
+ export type AgentWidgetStarterSuggestionsConfig =
3896
+ AgentWidgetSuggestionSurfaceConfig & {
3897
+ /** Suggestions shown before the first user message. */
3898
+ items?: AgentWidgetSuggestion[];
3899
+ /**
3900
+ * `auto` uses the welcome surface when the welcome card renders and the
3901
+ * composer otherwise. Explicit values are literal: `welcome` renders
3902
+ * nothing when the welcome card is hidden. Defaults to `auto`.
3903
+ */
3904
+ placement?: "auto" | "welcome" | "composer";
3905
+ };
3906
+
3907
+ export type AgentWidgetFollowUpSuggestionsConfig =
3908
+ AgentWidgetSuggestionSurfaceConfig & {
3909
+ /**
3910
+ * Enable the built-in `suggest_replies` follow-ups. Defaults to true. When
3911
+ * false, `suggest_replies` renders as a regular tool bubble and is NOT
3912
+ * auto-resumed: only set this with no server-side `suggest_replies`
3913
+ * declaration, or the execution parks awaiting a resume that never comes.
3914
+ */
3915
+ enabled?: boolean;
3916
+ /**
3917
+ * Advertise the built-in `suggest_replies` tool to the agent on every
3918
+ * dispatch via `clientTools[]`: no server-side `runtimeTools` declaration
3919
+ * needed. Defaults to `false`. Declare the tool in one place: the API
3920
+ * dedupes a double declaration by name with `clientTools[]` winning, so
3921
+ * the model still sees exactly one tool, but the widget's description and
3922
+ * schema win and the effective tool type becomes `local` (the execution
3923
+ * pauses and the widget auto-resumes it). Ignored when `enabled` is
3924
+ * `false`.
3925
+ */
3926
+ expose?: boolean;
3927
+ /**
3928
+ * `auto` places follow-ups after the transcript in regular panels and above
3929
+ * the composer in composer-bar mode. Defaults to `auto`.
3930
+ */
3931
+ placement?: "auto" | "after-message" | "composer";
3932
+ // No `items`: follow-up items are contextual and come from the agent's
3933
+ // `suggest_replies` tool or `controller.setFollowUpSuggestions`, not static
3934
+ // config. Static lists are `starters`.
3935
+ };
3936
+
3937
+ /**
3938
+ * Structured suggestions configuration. The legacy `suggestionChips` and
3939
+ * `suggestionChipsConfig` fields remain supported for backwards compatibility.
3940
+ */
3941
+ export type AgentWidgetSuggestionsConfig = {
3942
+ starters?: AgentWidgetStarterSuggestionsConfig;
3943
+ followUps?: AgentWidgetFollowUpSuggestionsConfig;
3944
+ };
3945
+
3946
+ /**
3947
+ * Which welcome surface renders.
3948
+ * `"card"` is the top-of-scroll intro card (default, back-compatible),
3949
+ * `"hero"` centers it in the empty conversation, `"none"` renders no surface.
3950
+ */
3951
+ export type AgentWidgetWelcomeVariant = "card" | "hero" | "none";
3952
+
3953
+ /**
3954
+ * When the welcome surface goes away. `"never"` is the default for `"card"`;
3955
+ * `"on-first-message"` is the default for `"hero"` and cannot be overridden
3956
+ * there.
3957
+ */
3958
+ export type AgentWidgetWelcomeDismiss = "never" | "on-first-message";
3959
+
3960
+ /**
3961
+ * Avatar or logo shown above the welcome title. Discriminated union; the
3962
+ * function form is the escape hatch for custom markup. There is deliberately
3963
+ * no raw HTML string variant (sanitization surface plus accessibility hole).
3964
+ */
3965
+ export type AgentWidgetWelcomeIcon =
3966
+ | { type: "lucide"; name: IconName }
3967
+ /** `alt` is required, and may be `""` for a decorative logo. */
3968
+ | { type: "image"; url: string; alt: string }
3969
+ /** Emoji or short text glyph. */
3970
+ | { type: "text"; text: string }
3971
+ | (() => HTMLElement | SVGElement);
3972
+
3973
+ /**
3974
+ * First-open welcome surface. Consolidates the deprecated
3975
+ * `copy.welcomeTitle` / `copy.welcomeSubtitle` / `copy.showWelcomeCard`
3976
+ * options; a field present here wins over its legacy alias.
3977
+ *
3978
+ * All defaults live in the resolver (`resolveWelcomeConfig`), never in
3979
+ * `DEFAULT_WIDGET_CONFIG`: presence on this object is what distinguishes a
3980
+ * host-set value from a filled-in default.
3981
+ */
3982
+ export interface AgentWidgetWelcomeConfig {
3983
+ /** Card or hero title. Defaults to "Hello 👋". */
3984
+ title?: string;
3985
+ /** Scope statement in the assistant's voice. Empty string omits it. */
3986
+ subtitle?: string;
3987
+ /** Avatar or logo shown above the title. */
3988
+ icon?: AgentWidgetWelcomeIcon;
3989
+ /** @default "card" */
3990
+ variant?: AgentWidgetWelcomeVariant;
3991
+ /** @default "never" for `card`, always `"on-first-message"` for `hero`. */
3992
+ dismiss?: AgentWidgetWelcomeDismiss;
3993
+ /**
3994
+ * Display-only greeting bubble pinned at transcript position zero. It is UI
3995
+ * chrome derived from config, never a session message: it does not appear in
3996
+ * `getMessages()`, is never persisted or sent to the model, `onMessage` does
3997
+ * not fire for it, and it survives `clearChat()`. Use an assistant
3998
+ * `initialMessages` entry instead when the model should see the greeting.
3999
+ *
4000
+ * Ignored (with a debug-mode warning) when `variant` is `"hero"`: the hero
4001
+ * is the greeting.
4002
+ */
4003
+ message?: string;
4004
+ }
4005
+
3731
4006
  /**
3732
4007
  * Interface for pluggable stream parsers that extract text from streaming responses.
3733
4008
  * Parsers handle incremental parsing to extract text values from structured formats (JSON, XML, etc.).
@@ -4020,7 +4295,9 @@ export type AgentWidgetHeaderLayoutConfig = {
4020
4295
  */
4021
4296
  render?: (context: HeaderRenderContext) => HTMLElement;
4022
4297
  /**
4023
- * Shown after the title in `minimal` header layout (e.g. chevron menu affordance).
4298
+ * Icon action buttons at the trailing edge of the `minimal` header layout,
4299
+ * clustered with the close button and sharing its chrome. For a menu
4300
+ * affordance next to the title, use `titleMenu` instead.
4024
4301
  */
4025
4302
  trailingActions?: AgentWidgetHeaderTrailingAction[];
4026
4303
  /** Called when a `trailingActions` button is clicked. */
@@ -4216,7 +4493,11 @@ export type AgentWidgetLayoutConfig = {
4216
4493
  /**
4217
4494
  * Max width for the content area (messages + composer).
4218
4495
  * Applied with `margin: 0 auto` for centering.
4219
- * Accepts any CSS width value (e.g. "90ch", "720px", "80%").
4496
+ * Accepts any CSS width value (e.g. "90ch", "720px", "80%"), or "none"
4497
+ * for a full-width column. Only engages on panels wider than the cap,
4498
+ * so launcher-width panels are unaffected.
4499
+ * @default "768px" ("720px" in composer-bar mode via
4500
+ * `launcher.composerBar.contentMaxWidth`)
4220
4501
  */
4221
4502
  contentMaxWidth?: string;
4222
4503
  };
@@ -4976,7 +5257,9 @@ export type AgentWidgetConfig = {
4976
5257
  */
4977
5258
  getHeaders?: AgentWidgetHeadersFunction;
4978
5259
  copy?: {
5260
+ /** @deprecated Use `welcome.title`. */
4979
5261
  welcomeTitle?: string;
5262
+ /** @deprecated Use `welcome.subtitle`. */
4980
5263
  welcomeSubtitle?: string;
4981
5264
  inputPlaceholder?: string;
4982
5265
  sendButtonLabel?: string;
@@ -4984,6 +5267,7 @@ export type AgentWidgetConfig = {
4984
5267
  stopButtonLabel?: string;
4985
5268
  /**
4986
5269
  * When false, the welcome / intro card is not shown above the message list.
5270
+ * @deprecated Use `welcome.variant: "none"`.
4987
5271
  * @default true
4988
5272
  */
4989
5273
  showWelcomeCard?: boolean;
@@ -5048,7 +5332,17 @@ export type AgentWidgetConfig = {
5048
5332
  * with `initialArtifacts`.
5049
5333
  */
5050
5334
  initialSelectedArtifactId?: string | null;
5335
+ /** Rich starter and follow-up suggestion surfaces. */
5336
+ suggestions?: AgentWidgetSuggestionsConfig;
5337
+ /**
5338
+ * First-open welcome surface (card, hero, or none) plus the optional
5339
+ * display-only greeting bubble. Supersedes `copy.welcomeTitle`,
5340
+ * `copy.welcomeSubtitle`, and `copy.showWelcomeCard`.
5341
+ */
5342
+ welcome?: AgentWidgetWelcomeConfig;
5343
+ /** @deprecated Prefer `suggestions.starters.items`. */
5051
5344
  suggestionChips?: string[];
5345
+ /** @deprecated Prefer semantic tokens under `theme.components.suggestion`. */
5052
5346
  suggestionChipsConfig?: AgentWidgetSuggestionChipsConfig;
5053
5347
  debug?: boolean;
5054
5348
  formEndpoint?: string;
@@ -0,0 +1,95 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+
5
+ import { createAgentExperience } from "./ui";
6
+
7
+ const createMount = () => {
8
+ const mount = document.createElement("div");
9
+ document.body.appendChild(mount);
10
+ return mount;
11
+ };
12
+
13
+ describe("layout.slots['body-top']", () => {
14
+ afterEach(() => {
15
+ document.body.innerHTML = "";
16
+ vi.restoreAllMocks();
17
+ });
18
+
19
+ it("resolves defaultContent() to the intro card (regression: queried stale utility classes)", () => {
20
+ window.scrollTo = vi.fn();
21
+ const mount = createMount();
22
+ let resolved: HTMLElement | null | undefined;
23
+
24
+ createAgentExperience(mount, {
25
+ apiUrl: "https://api.example.com/chat",
26
+ launcher: { enabled: false },
27
+ layout: {
28
+ slots: {
29
+ "body-top": ({ defaultContent }) => {
30
+ resolved = defaultContent();
31
+ return null;
32
+ },
33
+ },
34
+ },
35
+ });
36
+
37
+ expect(resolved).not.toBeNull();
38
+ expect(resolved!.hasAttribute("data-persona-intro-card")).toBe(true);
39
+ });
40
+
41
+ it("replaces the intro card with the slot element instead of prepending beside it", () => {
42
+ window.scrollTo = vi.fn();
43
+ const mount = createMount();
44
+
45
+ createAgentExperience(mount, {
46
+ apiUrl: "https://api.example.com/chat",
47
+ launcher: { enabled: false },
48
+ layout: {
49
+ slots: {
50
+ "body-top": () => {
51
+ const custom = document.createElement("div");
52
+ custom.setAttribute("data-test-slot", "body-top");
53
+ return custom;
54
+ },
55
+ },
56
+ },
57
+ });
58
+
59
+ const body = mount.querySelector("#persona-scroll-container")!;
60
+ expect(body.querySelector("[data-test-slot='body-top']")).not.toBeNull();
61
+ expect(body.querySelector("[data-persona-intro-card]")).toBeNull();
62
+ expect(
63
+ (body.firstElementChild as HTMLElement).getAttribute("data-test-slot")
64
+ ).toBe("body-top");
65
+ });
66
+
67
+ it("supports composing around the default intro card", () => {
68
+ window.scrollTo = vi.fn();
69
+ const mount = createMount();
70
+
71
+ createAgentExperience(mount, {
72
+ apiUrl: "https://api.example.com/chat",
73
+ launcher: { enabled: false },
74
+ layout: {
75
+ slots: {
76
+ "body-top": ({ defaultContent }) => {
77
+ const wrapper = document.createElement("div");
78
+ wrapper.setAttribute("data-test-slot", "wrapper");
79
+ const banner = document.createElement("p");
80
+ banner.textContent = "Welcome back";
81
+ wrapper.appendChild(banner);
82
+ const card = defaultContent();
83
+ if (card) wrapper.appendChild(card);
84
+ return wrapper;
85
+ },
86
+ },
87
+ },
88
+ });
89
+
90
+ const body = mount.querySelector("#persona-scroll-container")!;
91
+ const wrapper = body.querySelector("[data-test-slot='wrapper']");
92
+ expect(wrapper).not.toBeNull();
93
+ expect(wrapper!.querySelector("[data-persona-intro-card]")).not.toBeNull();
94
+ });
95
+ });
@@ -0,0 +1,91 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+
5
+ import { createAgentExperience } from "./ui";
6
+
7
+ const mounts: HTMLElement[] = [];
8
+ const controllers: ReturnType<typeof createAgentExperience>[] = [];
9
+
10
+ const makeController = (config: Record<string, unknown> = {}) => {
11
+ const mount = document.createElement("div");
12
+ document.body.appendChild(mount);
13
+ mounts.push(mount);
14
+ const controller = createAgentExperience(mount, {
15
+ apiUrl: "https://api.example.com/chat",
16
+ launcher: { enabled: false },
17
+ persistState: false,
18
+ suggestionChips: [],
19
+ ...config,
20
+ } as unknown as Parameters<typeof createAgentExperience>[1]);
21
+ controllers.push(controller);
22
+ return { mount, controller };
23
+ };
24
+
25
+ const wrapper = (mount: HTMLElement) =>
26
+ mount.querySelector<HTMLElement>("#persona-scroll-container > .persona-widget-messages")!;
27
+
28
+ describe("default content column", () => {
29
+ afterEach(() => {
30
+ controllers.splice(0).forEach((controller) => controller.destroy());
31
+ mounts.splice(0).forEach((mount) => mount.remove());
32
+ document.body.innerHTML = "";
33
+ });
34
+
35
+ it("keeps the transcript wrapper :empty pre-conversation so CSS drops it from the flex flow", () => {
36
+ // The stylesheet's .persona-widget-messages:empty { display: none } only
37
+ // works if nothing seeds the wrapper with placeholder nodes.
38
+ const { mount, controller } = makeController();
39
+ const messagesWrapper = wrapper(mount);
40
+ expect(messagesWrapper.matches(":empty")).toBe(true);
41
+
42
+ controller.injectUserMessage({ content: "hi" });
43
+ expect(messagesWrapper.matches(":empty")).toBe(false);
44
+ });
45
+
46
+ it("caps and centers the transcript at 768px by default", () => {
47
+ const { mount } = makeController();
48
+ const messagesWrapper = wrapper(mount);
49
+ expect(messagesWrapper.style.maxWidth).toBe("768px");
50
+ expect(messagesWrapper.style.marginLeft).toBe("auto");
51
+ expect(messagesWrapper.style.marginRight).toBe("auto");
52
+ expect(messagesWrapper.style.width).toBe("100%");
53
+ });
54
+
55
+ it("honors an explicit layout.contentMaxWidth", () => {
56
+ const { mount } = makeController({ layout: { contentMaxWidth: "90ch" } });
57
+ expect(wrapper(mount).style.maxWidth).toBe("90ch");
58
+ });
59
+
60
+ it("opts out with \"none\" for a full-width column", () => {
61
+ const { mount } = makeController({ layout: { contentMaxWidth: "none" } });
62
+ expect(wrapper(mount).style.maxWidth).toBe("none");
63
+ });
64
+
65
+ it("caps the composer status text to the same column as the composer form", () => {
66
+ const { mount, controller } = makeController();
67
+ const statusText = mount.querySelector<HTMLElement>("[data-persona-composer-status]")!;
68
+ expect(statusText.style.maxWidth).toBe("768px");
69
+ expect(statusText.style.marginLeft).toBe("auto");
70
+ expect(statusText.style.marginRight).toBe("auto");
71
+
72
+ controller.update({ layout: { contentMaxWidth: "90ch" } });
73
+ expect(statusText.style.maxWidth).toBe("90ch");
74
+
75
+ controller.update({ layout: { contentMaxWidth: "" } });
76
+ expect(statusText.style.maxWidth).toBe("");
77
+ expect(statusText.style.marginLeft).toBe("");
78
+ });
79
+
80
+ it("publishes the resolved column as a CSS var for plugin content", () => {
81
+ const { mount, controller } = makeController();
82
+ expect(
83
+ mount.style.getPropertyValue("--persona-content-max-width")
84
+ ).toBe("768px");
85
+
86
+ controller.update({ layout: { contentMaxWidth: "90ch" } });
87
+ expect(
88
+ mount.style.getPropertyValue("--persona-content-max-width")
89
+ ).toBe("90ch");
90
+ });
91
+ });
@@ -907,7 +907,7 @@ describe("createAgentExperience welded artifact split chrome", () => {
907
907
 
908
908
  controller.hideArtifacts();
909
909
 
910
- // Byte-identical to pane-closed: container carries the border, panel does not.
910
+ // Same as pane-closed: container carries the border, panel does not.
911
911
  expect(mount.classList.contains("persona-artifact-welded-split")).toBe(false);
912
912
  expect(container?.style.border).toContain("--persona-panel-border");
913
913
  expect(panel?.style.border).toBe("");
@@ -27,24 +27,27 @@ describe("createAgentExperience docked mode", () => {
27
27
  });
28
28
 
29
29
  const wrapper = mount.firstElementChild as HTMLElement | null;
30
- const launcherButton = mount.lastElementChild as HTMLButtonElement | null;
30
+ // The launcher mounts inside its surface wrapper; visibility is driven on
31
+ // the wrapper so a teaser bubble hides with the button.
32
+ const launcherSurface = mount.lastElementChild as HTMLElement | null;
33
+ const launcherButton = launcherSurface?.querySelector("button") ?? null;
31
34
 
32
35
  expect(wrapper).not.toBeNull();
33
36
  expect(wrapper?.style.display).toBe("flex");
34
37
  expect(launcherButton).not.toBeNull();
35
38
  expect(launcherButton?.tagName).toBe("BUTTON");
36
- expect(launcherButton?.style.display).toBe("none");
39
+ expect(launcherSurface?.style.display).toBe("none");
37
40
  expect(launcherButton?.className).toContain("persona-mt-4");
38
41
 
39
42
  controller.close();
40
43
 
41
44
  expect(wrapper?.style.display).toBe("none");
42
- expect(launcherButton?.style.display).toBe("none");
45
+ expect(launcherSurface?.style.display).toBe("none");
43
46
 
44
47
  controller.open();
45
48
 
46
49
  expect(wrapper?.style.display).toBe("flex");
47
- expect(launcherButton?.style.display).toBe("none");
50
+ expect(launcherSurface?.style.display).toBe("none");
48
51
 
49
52
  controller.destroy();
50
53
  });