@darqlabs/curator-sdk-react 0.4.0 → 0.4.2

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.
@@ -11,6 +11,21 @@
11
11
  */
12
12
  import { type CSSProperties, type ReactNode } from "react";
13
13
  import type { AssistantMessage, Curator, CuratorError, Environment, StreamState, StreamPlanProgress } from "@darqlabs/curator-sdk";
14
+ /**
15
+ * A starter prompt offered in the empty thread.
16
+ *
17
+ * A bare string is both the button text and the message sent. The object form
18
+ * separates them, because the two want different lengths: a button reads best
19
+ * at three or four words, while the message that actually starts the
20
+ * conversation is usually a full sentence carrying the context the agent needs.
21
+ * Collapsing them forces a choice between a cramped button and a thin prompt.
22
+ */
23
+ export type CuratorPrompt = string | {
24
+ /** Button text. */
25
+ label: string;
26
+ /** Message sent on click. Defaults to `label`. */
27
+ prompt?: string;
28
+ };
14
29
  export interface CuratorChatProps {
15
30
  /** SDK instance. Optional when a `<CuratorProvider>` is mounted. */
16
31
  curator?: Curator;
@@ -26,6 +41,18 @@ export interface CuratorChatProps {
26
41
  conversationId?: string;
27
42
  /** Header title. Default: "Chat". */
28
43
  title?: string;
44
+ /**
45
+ * Render without the header.
46
+ *
47
+ * An embedded widget usually sits under a page that has already said what
48
+ * this is; the header then repeats it. CSS can hide it, but hiding a
49
+ * component's own chrome from outside is not a contract — a markup change
50
+ * here would silently un-hide it on someone's page. This is the contract.
51
+ *
52
+ * The close affordance goes with it, so a consumer passing `onClose` while
53
+ * hiding the header owns dismissal themselves.
54
+ */
55
+ hideHeader?: boolean;
29
56
  /** Input placeholder. Default: "Type a message…". */
30
57
  placeholder?: string;
31
58
  /** Shown above the empty message list as a system prompt. */
@@ -107,6 +134,19 @@ export interface CuratorChatProps {
107
134
  * exists.
108
135
  */
109
136
  enableAttachments?: boolean;
137
+ /**
138
+ * Starter prompts shown in the empty thread. Clicking one sends it.
139
+ *
140
+ * The blank-thread problem is that a capable agent looks identical to an
141
+ * unconfigured one — nothing on screen says what it can do. These are the
142
+ * cheapest answer: they demonstrate scope and remove the cost of composing a
143
+ * first message.
144
+ *
145
+ * prompts={["Book a shipment", { label: "Track a load", prompt: "Where is my shipment LOG-4173?" }]}
146
+ *
147
+ * Shown only while the thread is empty; they are an entry point, not a menu.
148
+ */
149
+ prompts?: CuratorPrompt[];
110
150
  /**
111
151
  * Design tokens — colours, typography, spacing, radii — merged over the
112
152
  * palette `theme` selects.
@@ -131,10 +171,12 @@ declare function AssistantMessage(): import("react").JSX.Element;
131
171
  /**
132
172
  * Consumer-facing token names, mapped to the variables they set.
133
173
  *
134
- * A curated subset rather than every variable: this is the surface someone
135
- * reaches for first, and a prop that mirrors forty internals is a worse
136
- * document than one that names the dozen things people actually change.
137
- * Anything outside it is still reachable as raw CSS.
174
+ * Complete for the axes a consumer can be expected to want: the whole type
175
+ * scale, the whole spacing scale, every colour surface, and the radii. An
176
+ * earlier version exposed only `fontSize` and `radius`, which split each scale
177
+ * down the middle — half reachable by prop, half only by stylesheet. A partial
178
+ * scale is worse than none, because it lets someone change a base size and then
179
+ * discover the secondary sizes did not follow.
138
180
  */
139
181
  declare const TOKEN_VARS: Record<string, string>;
140
182
  /** Token values a consumer may set. Numbers are treated as pixels. */
@@ -1 +1 @@
1
- {"version":3,"file":"CuratorChat.d.ts","sourceRoot":"","sources":["../../src/CuratorChat.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAQL,KAAK,aAAa,EAElB,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAWd,OAAO,KAAK,EACV,gBAAgB,EAEhB,OAAO,EACP,YAAY,EACZ,WAAW,EACX,WAAW,EACX,kBAAkB,EACnB,MAAM,uBAAuB,CAAA;AAQ9B,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACxB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,KAAK,CAAC,EAAE,aAAa,CAAA;IAErB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IAEpB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,SAAS,CAAA;IAC5B,oEAAoE;IACpE,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;IAC/C;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACtC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAA;IACvD,8DAA8D;IAC9D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,GAAG,KAAK,KAAK,IAAI,CAAA;IAE/C;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BAkJlD;AA+JD,iBAAS,gBAAgB,gCAsCxB;AAkXD;;;;;;;GAOG;AACH,QAAA,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAsBtC,CAAA;AAED,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,OAAO,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"CuratorChat.d.ts","sourceRoot":"","sources":["../../src/CuratorChat.tsx"],"names":[],"mappings":"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"}
@@ -22,7 +22,7 @@ const Markdown_js_1 = require("./Markdown.js");
22
22
  const attachmentEnvelope_js_1 = require("./attachmentEnvelope.js");
23
23
  const CuratorAttachmentAdapter_js_1 = require("./CuratorAttachmentAdapter.js");
24
24
  function CuratorChat(props) {
25
- const { curator, agent, project, environment, system, conversationId, title = "Chat", placeholder = "Type a message…", welcomeMessage, theme = "light", className, style, onClose, loadingIndicator, onMessage, onState, onPlanProgress, onError, enableRetry = false, enableCopy = false, showTimestamps = true, enableAttachments = false, tokens, acceptedFileTypes, maxFileSize, queueWhileRunning = true, } = props;
25
+ const { curator, agent, project, environment, system, conversationId, title = "Chat", placeholder = "Type a message…", welcomeMessage, theme = "light", className, style, onClose, loadingIndicator, onMessage, onState, onPlanProgress, onError, enableRetry = false, enableCopy = false, showTimestamps = true, enableAttachments = false, hideHeader = false, prompts, tokens, acceptedFileTypes, maxFileSize, queueWhileRunning = true, } = props;
26
26
  const contextClient = (0, CuratorProvider_js_1.useCuratorOrNull)();
27
27
  const client = curator ?? contextClient;
28
28
  if (!client) {
@@ -75,6 +75,12 @@ function CuratorChat(props) {
75
75
  // Order is the contract: non-colour defaults, then the theme's palette, then
76
76
  // the consumer's tokens, then the raw `style` prop — each able to override
77
77
  // everything before it, with `style` still winning as it always has.
78
+ // Layout effect, not effect: the sheet must be in place before the browser
79
+ // paints, or the first frame renders unstyled and visibly settles.
80
+ const hostRef = (0, react_1.useRef)(null);
81
+ useIsomorphicLayoutEffect(() => {
82
+ injectCuratorStyles(hostRef.current);
83
+ }, []);
78
84
  const palette = theme === "dark" ? DARK_VARS : LIGHT_VARS;
79
85
  const wrapperStyle = {
80
86
  ...BASE_TOKENS,
@@ -83,7 +89,7 @@ function CuratorChat(props) {
83
89
  ...containerStyle,
84
90
  ...style,
85
91
  };
86
- return ((0, jsx_runtime_1.jsxs)("div", { className: className, style: wrapperStyle, "data-curator-chat": true, "data-theme": theme, children: [(0, jsx_runtime_1.jsx)(Header, { title: title, onClose: onClose }), (0, jsx_runtime_1.jsx)(LoadingIndicatorContext.Provider, { value: loadingIndicator ?? (0, jsx_runtime_1.jsx)(TypingDots, {}), children: (0, jsx_runtime_1.jsx)(RetryEnabledContext.Provider, { value: enableRetry, children: (0, jsx_runtime_1.jsx)(CopyEnabledContext.Provider, { value: enableCopy, children: (0, jsx_runtime_1.jsx)(TimestampsEnabledContext.Provider, { value: showTimestamps, children: (0, jsx_runtime_1.jsx)(react_2.AssistantRuntimeProvider, { runtime: runtime, children: (0, jsx_runtime_1.jsxs)(react_2.ThreadPrimitive.Root, { "data-curator-part": "thread", style: threadRootStyle, children: [(0, jsx_runtime_1.jsxs)(react_2.ThreadPrimitive.Viewport, { autoScroll: true, "data-curator-part": "viewport", style: viewportStyle, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Empty, { children: welcomeMessage ? (0, jsx_runtime_1.jsx)(Welcome, { text: welcomeMessage }) : null }), (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Messages, { components: {
92
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: hostRef, className: className, style: wrapperStyle, "data-curator-chat": true, "data-theme": theme, children: [!hideHeader && (0, jsx_runtime_1.jsx)(Header, { title: title, onClose: onClose }), (0, jsx_runtime_1.jsx)(LoadingIndicatorContext.Provider, { value: loadingIndicator ?? (0, jsx_runtime_1.jsx)(TypingDots, {}), children: (0, jsx_runtime_1.jsx)(RetryEnabledContext.Provider, { value: enableRetry, children: (0, jsx_runtime_1.jsx)(CopyEnabledContext.Provider, { value: enableCopy, children: (0, jsx_runtime_1.jsx)(TimestampsEnabledContext.Provider, { value: showTimestamps, children: (0, jsx_runtime_1.jsx)(react_2.AssistantRuntimeProvider, { runtime: runtime, children: (0, jsx_runtime_1.jsxs)(react_2.ThreadPrimitive.Root, { "data-curator-part": "thread", style: threadRootStyle, children: [(0, jsx_runtime_1.jsxs)(react_2.ThreadPrimitive.Viewport, { autoScroll: true, "data-curator-part": "viewport", style: viewportStyle, children: [(0, jsx_runtime_1.jsxs)(react_2.ThreadPrimitive.Empty, { children: [welcomeMessage ? (0, jsx_runtime_1.jsx)(Welcome, { text: welcomeMessage }) : null, prompts && prompts.length > 0 && (0, jsx_runtime_1.jsx)(Prompts, { prompts: prompts })] }), (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Messages, { components: {
87
93
  UserMessage,
88
94
  AssistantMessage,
89
95
  } })] }), (0, jsx_runtime_1.jsx)(Composer, { placeholder: placeholder, allowQueue: queueWhileRunning, allowAttachments: !!attachmentAdapter })] }) }) }) }) }) }), hidePoweredBy === false && (0, jsx_runtime_1.jsx)(brand_js_1.PoweredByCurator, {})] }));
@@ -91,6 +97,14 @@ function CuratorChat(props) {
91
97
  // Context for threading the loading indicator into the message-component
92
98
  // callbacks without remounting them on every render. Default is rendered
93
99
  // when no consumer-supplied node is provided.
100
+ /**
101
+ * `useLayoutEffect` in the browser, `useEffect` on the server.
102
+ *
103
+ * The widget is embeddable in SSR frameworks, where a bare `useLayoutEffect`
104
+ * logs a warning on every render. The effect it guards does nothing on the
105
+ * server anyway — there is no DOM to inject into.
106
+ */
107
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react_1.useLayoutEffect : react_1.useEffect;
94
108
  const LoadingIndicatorContext = (0, react_1.createContext)(null);
95
109
  // Threads the `enableRetry` prop into AssistantMessage, which assistant-ui
96
110
  // instantiates itself (no direct prop passing). Default off.
@@ -107,6 +121,24 @@ function Header({ title, onClose }) {
107
121
  function Welcome({ text }) {
108
122
  return (0, jsx_runtime_1.jsx)("div", { "data-curator-part": "welcome", style: welcomeStyle, children: text });
109
123
  }
124
+ /**
125
+ * Starter prompts for an empty thread.
126
+ *
127
+ * Built on `ThreadPrimitive.Suggestion` rather than a plain button so the send
128
+ * goes through the same path as the composer's — including the case where one
129
+ * is clicked while a run is already in flight, which the primitive queues
130
+ * instead of dropping.
131
+ */
132
+ function Prompts({ prompts }) {
133
+ return ((0, jsx_runtime_1.jsx)("div", { "data-curator-part": "prompts", style: promptsStyle, children: prompts.map((p, i) => {
134
+ const label = typeof p === "string" ? p : p.label;
135
+ const prompt = typeof p === "string" ? p : (p.prompt ?? p.label);
136
+ return ((0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Suggestion, { prompt: prompt, send: true, "data-curator-part": "prompt", style: promptStyle,
137
+ // The message differs from the button when the object form is used,
138
+ // so the full text has to be reachable without clicking.
139
+ title: prompt !== label ? prompt : undefined, children: label }, `${label}-${i}`));
140
+ }) }));
141
+ }
110
142
  function UserMessage() {
111
143
  // Attachments as the message carries them structurally. The envelope path in
112
144
  // `UserTextPart` is the fallback for transcripts written before the server
@@ -159,10 +191,10 @@ function AssistantMessage() {
159
191
  const indicator = (0, react_1.useContext)(LoadingIndicatorContext);
160
192
  const retryEnabled = (0, react_1.useContext)(RetryEnabledContext);
161
193
  const copyEnabled = (0, react_1.useContext)(CopyEnabledContext);
162
- return ((0, jsx_runtime_1.jsxs)(react_2.MessagePrimitive.Root, { "data-curator-part": "message message-assistant", style: rowStartStyle, children: [(0, jsx_runtime_1.jsxs)("div", { "data-curator-part": "bubble bubble-assistant", style: { ...bubbleBaseStyle, ...assistantBubbleStyle }, children: [(0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.If, { hasContent: false, children: indicator }), (0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.Parts, { components: { Text: AssistantTextPart } })] }), (0, jsx_runtime_1.jsx)(Timestamp, { align: "start" }), (retryEnabled || copyEnabled) && (
163
- // `autohide="not-last"` shows the bar only on the trailing assistant
164
- // message; `hideWhenRunning` keeps it away while a reply streams.
165
- (0, jsx_runtime_1.jsxs)(react_2.ActionBarPrimitive.Root, { autohide: "not-last", hideWhenRunning: true, "data-curator-part": "action-bar", style: actionBarStyle, children: [copyEnabled && ((0, jsx_runtime_1.jsxs)(react_2.ActionBarPrimitive.Copy, { "aria-label": "Copy", "data-curator-part": "action-button", style: actionButtonStyle, children: [(0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.If, { copied: true, children: (0, jsx_runtime_1.jsx)(CheckGlyph, {}) }), (0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.If, { copied: false, children: (0, jsx_runtime_1.jsx)(CopyGlyph, {}) })] })), retryEnabled && ((0, jsx_runtime_1.jsx)(react_2.ActionBarPrimitive.Reload, { "aria-label": "Retry", "data-curator-part": "action-button", style: actionButtonStyle, children: (0, jsx_runtime_1.jsx)(RetryGlyph, {}) }))] }))] }));
194
+ return ((0, jsx_runtime_1.jsxs)(react_2.MessagePrimitive.Root, { "data-curator-part": "message message-assistant", style: rowStartStyle, children: [(0, jsx_runtime_1.jsxs)("div", { "data-curator-part": "bubble bubble-assistant", style: { ...bubbleBaseStyle, ...assistantBubbleStyle }, children: [(0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.If, { hasContent: false, children: indicator }), (0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.Parts, { components: { Text: AssistantTextPart } })] }), (0, jsx_runtime_1.jsxs)("div", { "data-curator-part": "message-meta", style: messageMetaStyle, children: [(0, jsx_runtime_1.jsx)(Timestamp, { align: "start" }), (retryEnabled || copyEnabled) && (
195
+ // `hideWhenRunning` still applies: actions on a half-written reply
196
+ // would copy a fragment.
197
+ (0, jsx_runtime_1.jsxs)(react_2.ActionBarPrimitive.Root, { hideWhenRunning: true, "data-curator-part": "action-bar", style: actionBarStyle, children: [copyEnabled && ((0, jsx_runtime_1.jsxs)(react_2.ActionBarPrimitive.Copy, { "aria-label": "Copy", "data-curator-part": "action-button", style: actionButtonStyle, children: [(0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.If, { copied: true, children: (0, jsx_runtime_1.jsx)(CheckGlyph, {}) }), (0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.If, { copied: false, children: (0, jsx_runtime_1.jsx)(CopyGlyph, {}) })] })), retryEnabled && ((0, jsx_runtime_1.jsx)(react_2.ActionBarPrimitive.Reload, { "aria-label": "Retry", "data-curator-part": "action-button", style: actionButtonStyle, children: (0, jsx_runtime_1.jsx)(RetryGlyph, {}) }))] }))] })] }));
166
198
  }
167
199
  /**
168
200
  * Waiting state for a reply that has not started streaming.
@@ -349,22 +381,37 @@ const BASE_TOKENS = {
349
381
  ["--curator-space-4"]: "16px",
350
382
  ["--curator-radius-md"]: "10px",
351
383
  ["--curator-radius-sm"]: "8px",
384
+ ["--curator-border-width"]: "1px",
352
385
  };
353
386
  /**
354
387
  * Consumer-facing token names, mapped to the variables they set.
355
388
  *
356
- * A curated subset rather than every variable: this is the surface someone
357
- * reaches for first, and a prop that mirrors forty internals is a worse
358
- * document than one that names the dozen things people actually change.
359
- * Anything outside it is still reachable as raw CSS.
389
+ * Complete for the axes a consumer can be expected to want: the whole type
390
+ * scale, the whole spacing scale, every colour surface, and the radii. An
391
+ * earlier version exposed only `fontSize` and `radius`, which split each scale
392
+ * down the middle — half reachable by prop, half only by stylesheet. A partial
393
+ * scale is worse than none, because it lets someone change a base size and then
394
+ * discover the secondary sizes did not follow.
360
395
  */
361
396
  const TOKEN_VARS = {
362
397
  fontFamily: "--curator-font-family",
363
398
  fontSize: "--curator-font-size",
399
+ // The rest of the type scale. Exposing `fontSize` alone let a host set a base
400
+ // size while the secondary sizes stayed pinned at 12/11px, which is a worse
401
+ // mismatch than not being able to change any of them.
402
+ fontSizeSm: "--curator-font-size-sm",
403
+ fontSizeXs: "--curator-font-size-xs",
404
+ fontWeightStrong: "--curator-font-weight-strong",
364
405
  lineHeight: "--curator-line-height",
406
+ // Density. Every padding and gap in the widget derives from these four.
407
+ space1: "--curator-space-1",
408
+ space2: "--curator-space-2",
409
+ space3: "--curator-space-3",
410
+ space4: "--curator-space-4",
365
411
  radius: "--curator-radius",
366
412
  radiusMd: "--curator-radius-md",
367
413
  radiusSm: "--curator-radius-sm",
414
+ borderWidth: "--curator-border-width",
368
415
  background: "--curator-bg",
369
416
  surface: "--curator-surface",
370
417
  text: "--curator-text",
@@ -400,6 +447,20 @@ function tokensToVars(tokens) {
400
447
  }
401
448
  return out;
402
449
  }
450
+ /**
451
+ * Accent on the USER bubble, neutral on the assistant's.
452
+ *
453
+ * The reverse of what this widget shipped with, and the reverse of what the
454
+ * variable names imply — the assistant bubble used to borrow `--curator-accent`
455
+ * directly. Two problems with that. The assistant writes most of the words, so
456
+ * painting its bubble in the accent colour turns every substantial reply into a
457
+ * slab of saturated colour and makes the machine the loudest thing on screen.
458
+ * And it inverts the convention every messaging UI has taught people to read,
459
+ * where the accent marks *your* messages.
460
+ *
461
+ * Both are now separate variables, so a consumer who preferred the old
462
+ * arrangement sets `assistantBubble` / `userBubble` back.
463
+ */
403
464
  const LIGHT_VARS = {
404
465
  ["--curator-bg"]: "#ffffff",
405
466
  ["--curator-surface"]: "#f8fafc",
@@ -408,13 +469,10 @@ const LIGHT_VARS = {
408
469
  ["--curator-border"]: "rgba(15, 23, 42, 0.08)",
409
470
  ["--curator-accent"]: "#0f172a",
410
471
  ["--curator-accent-text"]: "#ffffff",
411
- ["--curator-user-bubble"]: "#f1f5f9",
412
- ["--curator-user-bubble-text"]: "#0f172a",
413
- // Was `--curator-accent`. Sharing one variable meant a brand accent could not
414
- // be changed without repainting every assistant bubble; they default to the
415
- // same value so nothing shifts, but they are now separable.
416
- ["--curator-assistant-bubble"]: "#0f172a",
417
- ["--curator-assistant-bubble-text"]: "#ffffff",
472
+ ["--curator-user-bubble"]: "#0f172a",
473
+ ["--curator-user-bubble-text"]: "#ffffff",
474
+ ["--curator-assistant-bubble"]: "#f1f5f9",
475
+ ["--curator-assistant-bubble-text"]: "#0f172a",
418
476
  ["--curator-header-bg"]: "transparent",
419
477
  ["--curator-composer-bg"]: "transparent",
420
478
  ["--curator-focus-ring"]: "rgba(15, 23, 42, 0.16)",
@@ -429,10 +487,12 @@ const DARK_VARS = {
429
487
  ["--curator-border"]: "rgba(226, 232, 240, 0.1)",
430
488
  ["--curator-accent"]: "#e2e8f0",
431
489
  ["--curator-accent-text"]: "#0b1220",
432
- ["--curator-user-bubble"]: "#1f2937",
433
- ["--curator-user-bubble-text"]: "#e2e8f0",
434
- ["--curator-assistant-bubble"]: "#e2e8f0",
435
- ["--curator-assistant-bubble-text"]: "#0b1220",
490
+ // Dark mode keeps the same relationship: the user's bubble is the lighter,
491
+ // more saturated one; the assistant sits on a raised neutral surface.
492
+ ["--curator-user-bubble"]: "#e2e8f0",
493
+ ["--curator-user-bubble-text"]: "#0b1220",
494
+ ["--curator-assistant-bubble"]: "#1f2937",
495
+ ["--curator-assistant-bubble-text"]: "#e2e8f0",
436
496
  ["--curator-header-bg"]: "transparent",
437
497
  ["--curator-composer-bg"]: "transparent",
438
498
  ["--curator-focus-ring"]: "rgba(226, 232, 240, 0.2)",
@@ -447,8 +507,8 @@ const containerStyle = {
447
507
  minHeight: 360,
448
508
  background: "var(--curator-bg)",
449
509
  color: "var(--curator-text)",
450
- border: "1px solid var(--curator-border)",
451
- borderRadius: "var(--curator-radius)",
510
+ border: "var(--curator-border-width, 1px) solid var(--curator-border)",
511
+ borderRadius: RADIUS_LG,
452
512
  overflow: "hidden",
453
513
  fontFamily: "var(--curator-font-family)",
454
514
  fontSize: FONT_BASE,
@@ -482,7 +542,7 @@ const headerActionStyle = {
482
542
  alignItems: "center",
483
543
  justifyContent: "center",
484
544
  border: "none",
485
- borderRadius: 8,
545
+ borderRadius: RADIUS_SM,
486
546
  background: "transparent",
487
547
  color: "var(--curator-muted)",
488
548
  cursor: "pointer",
@@ -505,15 +565,39 @@ const viewportStyle = {
505
565
  // clips rather than growing a horizontal scrollbar across the whole thread.
506
566
  overflowX: "hidden",
507
567
  minWidth: 0,
508
- padding: `${SPACE_4}px ${SPACE_4}px ${SPACE_3}px`,
568
+ padding: `${SPACE_4} ${SPACE_4} ${SPACE_3}`,
509
569
  display: "flex",
510
570
  flexDirection: "column",
511
- gap: SPACE_2,
571
+ gap: SPACE_3,
512
572
  };
513
573
  const welcomeStyle = {
514
574
  color: "var(--curator-muted)",
515
- fontSize: 13,
516
- padding: `${SPACE_2}px 0`,
575
+ fontSize: FONT_SM,
576
+ padding: `${SPACE_2} 0`,
577
+ };
578
+ const promptsStyle = {
579
+ display: "flex",
580
+ flexWrap: "wrap",
581
+ gap: 6,
582
+ // Sits under the welcome line rather than beside it: the welcome says what
583
+ // this is, the prompts say what to do, and that reads as a sequence.
584
+ paddingTop: 2,
585
+ };
586
+ const promptStyle = {
587
+ // Outlined, not filled. A filled chip competes with the send button for
588
+ // "primary action", and there are several of these — a row of solid accent
589
+ // blocks in an otherwise empty panel is the loudest thing on the screen.
590
+ border: "1px solid var(--curator-border)",
591
+ borderRadius: RADIUS_SM,
592
+ background: "transparent",
593
+ color: "var(--curator-text)",
594
+ fontFamily: "inherit",
595
+ fontSize: FONT_SM,
596
+ lineHeight: 1.3,
597
+ padding: `6px ${SPACE_2}`,
598
+ cursor: "pointer",
599
+ textAlign: "left",
600
+ maxWidth: "100%",
517
601
  };
518
602
  const rowStartStyle = {
519
603
  // Column so the retry action bar can stack beneath the bubble, both
@@ -525,26 +609,44 @@ const rowStartStyle = {
525
609
  minWidth: 0,
526
610
  };
527
611
  const rowEndStyle = {
612
+ // Column, mirroring the assistant row. It was a plain row, which made the
613
+ // bubble and the timestamp siblings on one line — so a user message rendered
614
+ // as "hello7:19 AM", the stamp jammed against the bubble with no gap, while
615
+ // the assistant's sat neatly underneath. The two sides should differ in
616
+ // alignment, not in structure.
528
617
  display: "flex",
618
+ flexDirection: "column",
619
+ alignItems: "flex-end",
529
620
  width: "100%",
530
621
  // Same `min-width: auto` trap as the bubble: without this the row itself
531
622
  // refuses to shrink and the cap on the bubble inside it never binds.
532
623
  minWidth: 0,
533
- justifyContent: "flex-end",
624
+ };
625
+ /**
626
+ * The strip under an assistant message: time on the left, actions beside it.
627
+ *
628
+ * `minHeight` is the whole point — it holds the row open at the height the
629
+ * action buttons need, from first render, so neither hovering nor the end of a
630
+ * stream changes the column's height.
631
+ */
632
+ const messageMetaStyle = {
633
+ display: "flex",
634
+ alignItems: "center",
635
+ gap: 6,
636
+ minHeight: 22,
637
+ marginTop: 2,
534
638
  };
535
639
  const actionBarStyle = {
536
640
  display: "flex",
537
641
  alignItems: "center",
538
- gap: 4,
539
- marginTop: 4,
540
- paddingLeft: 2,
642
+ gap: 2,
541
643
  };
542
644
  const actionButtonStyle = {
543
645
  display: "inline-flex",
544
646
  alignItems: "center",
545
647
  justifyContent: "center",
546
- width: 26,
547
- height: 26,
648
+ width: 22,
649
+ height: 22,
548
650
  padding: 0,
549
651
  border: "none",
550
652
  borderRadius: RADIUS_SM,
@@ -564,7 +666,7 @@ const bubbleBaseStyle = {
564
666
  // and the whole thread scrolls sideways. This is the fix for that; the
565
667
  // wrapping rules below only get a chance to apply once the box can shrink.
566
668
  minWidth: 0,
567
- padding: `${SPACE_2 + 2}px ${SPACE_3}px`,
669
+ padding: `calc(${SPACE_2} + 2px) ${SPACE_3}`,
568
670
  borderRadius: RADIUS_MD,
569
671
  whiteSpace: "pre-wrap",
570
672
  // `overflow-wrap: anywhere` is the standard property and it breaks inside an
@@ -592,7 +694,7 @@ const attachmentChipStyle = {
592
694
  // Tinted from the bubble's own text colour so the chip reads as part of the
593
695
  // message in either theme, without needing its own palette token.
594
696
  background: "color-mix(in srgb, currentColor 10%, transparent)",
595
- fontSize: 12,
697
+ fontSize: FONT_SM,
596
698
  lineHeight: 1.4,
597
699
  verticalAlign: "middle",
598
700
  };
@@ -633,7 +735,7 @@ const queuedRowStyle = {
633
735
  display: "flex",
634
736
  alignItems: "center",
635
737
  gap: SPACE_2,
636
- padding: `${SPACE_2}px ${SPACE_3}px 0`,
738
+ padding: `${SPACE_2} ${SPACE_3} 0`,
637
739
  fontSize: FONT_SM,
638
740
  color: "var(--curator-muted)",
639
741
  minWidth: 0,
@@ -666,7 +768,6 @@ const timestampStyle = {
666
768
  fontSize: FONT_XS,
667
769
  color: "var(--curator-muted)",
668
770
  opacity: 0.7,
669
- marginTop: 2,
670
771
  padding: "0 2px",
671
772
  userSelect: "none",
672
773
  };
@@ -714,7 +815,7 @@ const composerRootStyle = {
714
815
  display: "flex",
715
816
  gap: SPACE_2,
716
817
  alignItems: "flex-end",
717
- padding: `${SPACE_3}px ${SPACE_3}px ${SPACE_2}px`,
818
+ padding: `${SPACE_3} ${SPACE_3} ${SPACE_2}`,
718
819
  flexShrink: 0,
719
820
  };
720
821
  const textareaStyle = {
@@ -722,7 +823,7 @@ const textareaStyle = {
722
823
  resize: "none",
723
824
  border: "1px solid var(--curator-border)",
724
825
  borderRadius: RADIUS_SM,
725
- padding: `${SPACE_2}px ${SPACE_3}px`,
826
+ padding: `${SPACE_2} ${SPACE_3}`,
726
827
  background: "var(--curator-bg)",
727
828
  color: "var(--curator-text)",
728
829
  fontFamily: "inherit",
@@ -749,16 +850,29 @@ const sendButtonStyle = {
749
850
  flexShrink: 0,
750
851
  cursor: "pointer",
751
852
  };
752
- // One-shot stylesheet injection — scoped markdown + focus rules.
753
- // Idempotent. Skipped in SSR. STYLE_ID is bumped when the rules change
754
- // so existing pages re-inject the new sheet on next mount instead of
755
- // reusing the stale element.
756
- if (typeof document !== "undefined") {
757
- const STYLE_ID = "curator-chat-styles-v5";
758
- if (!document.getElementById(STYLE_ID)) {
759
- const el = document.createElement("style");
760
- el.id = STYLE_ID;
761
- el.textContent = `
853
+ /**
854
+ * Stylesheet injection, scoped to the tree the widget is actually in.
855
+ *
856
+ * This used to run at module load and append to `document.head`, which assumes
857
+ * the widget owns the document. It does not when it is embedded in a shadow
858
+ * root: styles in the host document do not cross a shadow boundary, so every
859
+ * rule below was silently lost — the typing dots stopped animating, the prompt
860
+ * hover and action-bar reveal never fired, the composer focus ring vanished,
861
+ * and all markdown formatting went with them. Nothing errors; the widget just
862
+ * quietly renders unstyled in one embedding mode.
863
+ *
864
+ * Injecting into the host element's own `getRootNode()` fixes both cases at
865
+ * once: that resolves to the document normally and to the shadow root when
866
+ * embedded, so a consumer does not have to know which they are in or mirror the
867
+ * sheet themselves.
868
+ *
869
+ * Reported by a consumer who had worked around it downstream by copying the
870
+ * sheet into their shadow root — a workaround nobody should need.
871
+ */
872
+ const STYLE_PREFIX = "curator-chat-styles";
873
+ /** Bump when the rules change; superseded sheets are removed on inject. */
874
+ const STYLE_ID = `${STYLE_PREFIX}-v6`;
875
+ const STYLE_TEXT = `
762
876
  @keyframes curator-wave {
763
877
  0%, 60%, 100% { transform: translateY(0); opacity: 0.35; }
764
878
  30% { transform: translateY(-4px); opacity: 0.95; }
@@ -775,6 +889,26 @@ if (typeof document !== "undefined") {
775
889
  transform: none;
776
890
  }
777
891
  }
892
+ /* Hidden by opacity rather than by unmounting, so the row keeps its height.
893
+ pointer-events follows visibility so an invisible button is not clickable,
894
+ and focus-within keeps it reachable by keyboard, where there is no hover. */
895
+ [data-curator-chat] [data-curator-part~="action-bar"] {
896
+ opacity: 0;
897
+ pointer-events: none;
898
+ transition: opacity 120ms ease;
899
+ }
900
+ [data-curator-chat] [data-curator-part~="message"]:hover [data-curator-part~="action-bar"],
901
+ [data-curator-chat] [data-curator-part~="action-bar"]:focus-within {
902
+ opacity: 1;
903
+ pointer-events: auto;
904
+ }
905
+ @media (prefers-reduced-motion: reduce) {
906
+ [data-curator-chat] [data-curator-part~="action-bar"] { transition: none; }
907
+ }
908
+ [data-curator-chat] [data-curator-part~="prompt"]:hover {
909
+ background-color: var(--curator-user-bubble);
910
+ border-color: var(--curator-muted);
911
+ }
778
912
  [data-curator-chat] button:disabled { opacity: 0.4; cursor: not-allowed; }
779
913
  [data-curator-chat] textarea {
780
914
  transition: border-color 120ms ease, box-shadow 120ms ease;
@@ -870,7 +1004,37 @@ if (typeof document !== "undefined") {
870
1004
  [data-curator-md] th, [data-curator-md] td { border: 1px solid currentColor; padding: 4px 8px; }
871
1005
  [data-curator-md] th { font-weight: 600; }
872
1006
  `;
873
- document.head.appendChild(el);
1007
+ /**
1008
+ * Put the sheet in `host`'s root, once per root.
1009
+ *
1010
+ * Runs on mount rather than at import: the root is only knowable from a mounted
1011
+ * element, and a lazily-loaded route chunk can import this module long before —
1012
+ * or well after — the tree it will live in exists.
1013
+ *
1014
+ * Not removed on unmount. Two widgets can share a root, and reference-counting
1015
+ * a `<style>` element costs more than the element does; a shadow root that is
1016
+ * torn down takes its copy with it.
1017
+ */
1018
+ function injectCuratorStyles(host) {
1019
+ if (!host || typeof document === "undefined")
1020
+ return;
1021
+ const root = host.getRootNode();
1022
+ // A Document takes the sheet in `head`; a ShadowRoot has no head and takes it
1023
+ // directly. Both accept an element created by the owning document.
1024
+ const container = root.head ?? root;
1025
+ if (!container || typeof container.querySelector !== "function")
1026
+ return;
1027
+ if (container.querySelector(`#${STYLE_ID}`))
1028
+ return;
1029
+ // Drop superseded versions in THIS root. Without it an upgraded page would
1030
+ // apply two generations of rules at once, with the older one winning wherever
1031
+ // it happened to be more specific.
1032
+ for (const stale of Array.from(container.querySelectorAll(`[id^="${STYLE_PREFIX}-"]`))) {
1033
+ stale.remove();
874
1034
  }
1035
+ const el = document.createElement("style");
1036
+ el.id = STYLE_ID;
1037
+ el.textContent = STYLE_TEXT;
1038
+ container.appendChild(el);
875
1039
  }
876
1040
  //# sourceMappingURL=CuratorChat.js.map