@candor-ui/react 1.5.0 → 1.7.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 (67) hide show
  1. package/dist/adapters/ag-ui.d.ts +24 -24
  2. package/dist/adapters/ai-sdk.d.ts +6 -6
  3. package/dist/adapters/claude-agent-sdk.d.ts +34 -34
  4. package/dist/adapters/claude-agent-server.d.ts +34 -34
  5. package/dist/adapters/use-ag-ui.d.ts +6 -6
  6. package/dist/adapters/use-ag-ui.js +14 -2
  7. package/dist/adapters/use-candor-chat.d.ts +1 -1
  8. package/dist/adapters/use-candor-chat.js +1 -1
  9. package/dist/adapters/use-claude-agent.d.ts +10 -10
  10. package/dist/adapters/use-claude-agent.js +1 -1
  11. package/dist/blocks/copy-field.d.ts +1 -1
  12. package/dist/blocks/copy-field.js +1 -1
  13. package/dist/blocks/degraded-panel.d.ts +5 -5
  14. package/dist/blocks/locale.d.ts +6 -6
  15. package/dist/blocks/locked.d.ts +8 -8
  16. package/dist/blocks/relative-time.d.ts +6 -6
  17. package/dist/blocks/status-dot.d.ts +3 -3
  18. package/dist/blocks/step-list.d.ts +3 -3
  19. package/dist/blocks/streaming-text.d.ts +3 -3
  20. package/dist/chat/agent-avatar.d.ts +4 -4
  21. package/dist/chat/ai-disclosure.d.ts +5 -5
  22. package/dist/chat/ai-disclosure.js +1 -1
  23. package/dist/chat/attachment-preview.d.ts +6 -6
  24. package/dist/chat/attachment-preview.js +1 -1
  25. package/dist/chat/card-registry.js +1 -1
  26. package/dist/chat/citation.d.ts +3 -3
  27. package/dist/chat/citation.js +3 -3
  28. package/dist/chat/composer.d.ts +31 -20
  29. package/dist/chat/composer.js +10 -6
  30. package/dist/chat/context-meter.d.ts +1 -1
  31. package/dist/chat/context-meter.js +1 -1
  32. package/dist/chat/entity-chip.d.ts +3 -3
  33. package/dist/chat/entity-chip.js +1 -1
  34. package/dist/chat/folded-turns.d.ts +11 -6
  35. package/dist/chat/folded-turns.js +2 -2
  36. package/dist/chat/message-actions.d.ts +6 -6
  37. package/dist/chat/message-actions.js +1 -1
  38. package/dist/chat/message-content.d.ts +13 -7
  39. package/dist/chat/message-content.js +10 -5
  40. package/dist/chat/message.d.ts +64 -37
  41. package/dist/chat/message.js +17 -11
  42. package/dist/chat/payload.d.ts +4 -4
  43. package/dist/chat/payload.js +2 -2
  44. package/dist/chat/permission-prompt.d.ts +23 -23
  45. package/dist/chat/prompt-suggestions.d.ts +4 -4
  46. package/dist/chat/prompt-suggestions.js +1 -1
  47. package/dist/chat/reasoning.d.ts +3 -3
  48. package/dist/chat/reasoning.js +1 -1
  49. package/dist/chat/thread-divider.d.ts +3 -3
  50. package/dist/chat/thread-event.d.ts +10 -5
  51. package/dist/chat/thread-event.js +3 -2
  52. package/dist/chat/thread.d.ts +23 -7
  53. package/dist/chat/thread.js +28 -5
  54. package/dist/chat/tool-call.d.ts +2 -2
  55. package/dist/chat/tool-call.js +2 -2
  56. package/dist/lib/chat.d.ts +42 -42
  57. package/dist/lib/know.d.ts +14 -12
  58. package/dist/lib/render-prop.d.ts +4 -4
  59. package/dist/lib/safe-markdown.d.ts +1 -1
  60. package/dist/lib/status.d.ts +21 -0
  61. package/dist/lib/status.js +17 -0
  62. package/dist/lib/use-shortcut.d.ts +1 -1
  63. package/dist/lib/use-virtual-list.d.ts +6 -6
  64. package/dist/styles.css +4 -1
  65. package/dist/ui/button.d.ts +1 -1
  66. package/dist/ui/button.js +1 -1
  67. package/package.json +1 -1
@@ -13,13 +13,13 @@ declare function DegradedPanel({ reason, missing, onRetry, retrying, variant, cl
13
13
  /** What went wrong, in a sentence. */
14
14
  reason: React.ReactNode;
15
15
  /** What the person won't see because of it. */
16
- missing?: React.ReactNode;
16
+ missing?: React.ReactNode | undefined;
17
17
  /** Shows a Try again button that calls this. */
18
- onRetry?: () => void;
18
+ onRetry?: (() => void) | undefined;
19
19
  /** Shows the retry as in progress. */
20
- retrying?: boolean;
20
+ retrying?: boolean | undefined;
21
21
  /** A boxed panel, or one line inside other content. */
22
- variant?: "block" | "inline";
23
- className?: string;
22
+ variant?: "block" | "inline" | undefined;
23
+ className?: string | undefined;
24
24
  }): import("react").JSX.Element;
25
25
  export { DegradedPanel };
@@ -27,19 +27,19 @@ export type TextDirection = "ltr" | "rtl";
27
27
  export declare function directionOf(locale: string | undefined): TextDirection;
28
28
  declare function LocaleProvider({ locale, dir, timeZone, strings, children, }: {
29
29
  /** A BCP 47 tag such as "en-GB", "fr" or "ar-EG", for dates, numbers and plural forms. Left out, the browser's own is used once the page has hydrated. */
30
- locale?: string;
30
+ locale?: string | undefined;
31
31
  /** Text direction. Left out, it follows the locale: "rtl" for Arabic, Hebrew, Persian, Urdu and the like. Set `dir` on `<html>` too, so the page's own layout flips with Candor's. */
32
- dir?: TextDirection;
32
+ dir?: TextDirection | undefined;
33
33
  /** An IANA time zone such as "Europe/Paris". With `locale`, server and browser format dates the same way, so absolute times render on the server too. */
34
- timeZone?: string;
34
+ timeZone?: string | undefined;
35
35
  /** Overrides by component name, e.g. `{ thread: { jumpToLatest: "…" } }`; any left out stay English. Nested providers merge. */
36
- strings?: StringOverrides;
36
+ strings?: StringOverrides | undefined;
37
37
  children: React.ReactNode;
38
38
  }): React.JSX.Element;
39
39
  /** The locale, time zone and text direction in effect. */
40
40
  declare function useLocale(): {
41
- locale?: string;
42
- timeZone?: string;
41
+ locale?: string | undefined;
42
+ timeZone?: string | undefined;
43
43
  dir: TextDirection;
44
44
  };
45
45
  /**
@@ -1,10 +1,10 @@
1
1
  import * as React from "react";
2
2
  type Lockable = React.ReactElement<{
3
- "aria-disabled"?: boolean | "true" | "false";
4
- "aria-describedby"?: string;
5
- onClick?: React.MouseEventHandler;
6
- onKeyDown?: React.KeyboardEventHandler;
7
- "data-locked"?: string;
3
+ "aria-disabled"?: boolean | "true" | "false" | undefined;
4
+ "aria-describedby"?: string | undefined;
5
+ onClick?: React.MouseEventHandler | undefined;
6
+ onKeyDown?: React.KeyboardEventHandler | undefined;
7
+ "data-locked"?: string | undefined;
8
8
  }>;
9
9
  /**
10
10
  * The honest-state rule: a control that cannot be used says why.
@@ -17,10 +17,10 @@ type Lockable = React.ReactElement<{
17
17
  */
18
18
  declare function Locked({ reason, inline, children, className, }: {
19
19
  /** Why the control can't be used. Without one, the control renders as normal. */
20
- reason?: string | null;
20
+ reason?: string | null | undefined;
21
21
  /** Print the reason under the control instead of in a tooltip. */
22
- inline?: boolean;
22
+ inline?: boolean | undefined;
23
23
  children: Lockable;
24
- className?: string;
24
+ className?: string | undefined;
25
25
  }): React.JSX.Element;
26
26
  export { Locked };
@@ -9,9 +9,9 @@ export declare const relativeTimeStrings: {
9
9
  * spaces: "5 minutes".
10
10
  */
11
11
  export declare function formatRelative(date: Date, now?: Date, { locale, justNow, bare }?: {
12
- locale?: string;
13
- justNow?: string;
14
- bare?: boolean;
12
+ locale?: string | undefined;
13
+ justNow?: string | undefined;
14
+ bare?: boolean | undefined;
15
15
  }): string;
16
16
  /** The current time in milliseconds, updated every 30 seconds; null while rendering on the server. */
17
17
  export declare function useNow(): number | null;
@@ -23,9 +23,9 @@ declare function RelativeTime({ date, bare, format, className, }: {
23
23
  /** When it happened. Shown as "5 minutes ago", with the exact time on hover. */
24
24
  date: Date | string;
25
25
  /** Drop "ago" and "in", for tight spaces: "5 minutes". */
26
- bare?: boolean;
26
+ bare?: boolean | undefined;
27
27
  /** "relative" (the default), or an absolute "date", "datetime" or "time" in the locale. */
28
- format?: "relative" | DateStyle;
29
- className?: string;
28
+ format?: "relative" | DateStyle | undefined;
29
+ className?: string | undefined;
30
30
  }): React.JSX.Element;
31
31
  export { RelativeTime };
@@ -8,9 +8,9 @@ declare function StatusDot({ status, showLabel, label, className, }: {
8
8
  /** Which status to show; running and waiting pulse. */
9
9
  status: RunStatus;
10
10
  /** Print the status name next to the dot. */
11
- showLabel?: boolean;
11
+ showLabel?: boolean | undefined;
12
12
  /** Words to use instead of the status name. */
13
- label?: string;
14
- className?: string;
13
+ label?: string | undefined;
14
+ className?: string | undefined;
15
15
  }): import("react").JSX.Element;
16
16
  export { StatusDot };
@@ -4,8 +4,8 @@ export type Step = {
4
4
  id: string;
5
5
  title: React.ReactNode;
6
6
  status: RunStatus;
7
- detail?: React.ReactNode;
8
- meta?: React.ReactNode;
7
+ detail?: React.ReactNode | undefined;
8
+ meta?: React.ReactNode | undefined;
9
9
  };
10
10
  declare function StepMark({ status, }: {
11
11
  /** The step's status, which picks its mark. */
@@ -18,6 +18,6 @@ declare function StepMark({ status, }: {
18
18
  declare function StepList({ steps, className, }: {
19
19
  /** The steps in order, each with a status, and optional detail and meta. */
20
20
  steps: Step[];
21
- className?: string;
21
+ className?: string | undefined;
22
22
  }): React.JSX.Element;
23
23
  export { StepList, StepMark };
@@ -6,12 +6,12 @@ declare function StreamingText({ text, streaming, className, }: {
6
6
  /** The text so far. */
7
7
  text: string;
8
8
  /** Show a caret while more is arriving; announced once when it stops. */
9
- streaming?: boolean;
10
- className?: string;
9
+ streaming?: boolean | undefined;
10
+ className?: string | undefined;
11
11
  }): import("react").JSX.Element;
12
12
  /** A label that shimmers while an agent is working ("Thinking", "Reading the docs"). */
13
13
  declare function ShimmerLabel({ children, className, }: {
14
14
  children: React.ReactNode;
15
- className?: string;
15
+ className?: string | undefined;
16
16
  }): import("react").JSX.Element;
17
17
  export { StreamingText, ShimmerLabel };
@@ -34,15 +34,15 @@ declare function AgentAvatar({ agent, size, className, }: {
34
34
  /** Who the agent is: id, display name and role. */
35
35
  agent: AgentIdentity;
36
36
  /** md for message headers, sm for lists and chips. */
37
- size?: "sm" | "md";
38
- className?: string;
37
+ size?: "sm" | "md" | undefined;
38
+ className?: string | undefined;
39
39
  }): React.JSX.Element;
40
40
  /** Avatar plus name, with the role spelled out for screen readers. */
41
41
  declare function AgentBadge({ agent, size, className, }: {
42
42
  /** Who the agent is: id, display name and role. */
43
43
  agent: AgentIdentity;
44
44
  /** md for message headers, sm for lists and chips. */
45
- size?: "sm" | "md";
46
- className?: string;
45
+ size?: "sm" | "md" | undefined;
46
+ className?: string | undefined;
47
47
  }): React.JSX.Element;
48
48
  export { AgentAvatar, AgentBadge, AgentRolesProvider, useAgentRoles };
@@ -25,13 +25,13 @@ export declare const aiDisclosureStrings: {
25
25
  */
26
26
  declare function AiDisclosure({ agent, variant, details, onLearnMore, className, }: {
27
27
  /** The agent, named by the app: an AgentIdentity or a plain name. Left out, the notice says "an AI system". Never model text. */
28
- agent?: AgentIdentity | string;
28
+ agent?: AgentIdentity | string | undefined;
29
29
  /** "line" (the default) for a first line in the thread; "badge" for a compact mark in a header, e.g. ThreadHeader's. Place each where it fits. */
30
- variant?: "line" | "badge";
30
+ variant?: "line" | "badge" | undefined;
31
31
  /** The app's own plain text about the system: who operates it, what data it uses, how to reach a person. Shown under the line, or in the badge's explanation. */
32
- details?: string;
32
+ details?: string | undefined;
33
33
  /** Opens the app's own page or dialog about its AI use. A callback, never a URL from the model. Shows a "Learn more" button. */
34
- onLearnMore?: () => void;
35
- className?: string;
34
+ onLearnMore?: (() => void) | undefined;
35
+ className?: string | undefined;
36
36
  }): import("react").JSX.Element;
37
37
  export { AiDisclosure };
@@ -41,7 +41,7 @@ function AiDisclosure({ agent, variant = "line", details, onLearnMore, className
41
41
  // Nothing more to say: a static mark with a name, not a button that opens nothing.
42
42
  if (!details && !onLearnMore)
43
43
  return (_jsx("span", { "data-slot": "ai-disclosure", "data-variant": "badge", role: "img", "aria-label": label, className: cn(badgeClass, className), children: mark }));
44
- return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { "data-slot": "ai-disclosure", "data-variant": "badge", "aria-label": label, className: cn(badgeClass, "outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", className), children: mark }), _jsxs(PopoverContent, { align: "start", className: "flex w-72 flex-col gap-1.5 p-3 text-xs", children: [_jsx("p", { className: "text-sm font-medium", children: name ? t("about", { name }) : t("aboutUnnamed") }), _jsx("p", { children: sentence }), details && _jsx("p", { className: "text-muted-foreground", children: details }), learnMore && _jsx("div", { children: learnMore })] })] }));
44
+ return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { "data-slot": "ai-disclosure", "data-variant": "badge", "aria-label": label, className: cn(badgeClass, "outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/80", className), children: mark }), _jsxs(PopoverContent, { align: "start", className: "flex w-72 flex-col gap-1.5 p-3 text-xs", children: [_jsx("p", { className: "text-sm font-medium", children: name ? t("about", { name }) : t("aboutUnnamed") }), _jsx("p", { children: sentence }), details && _jsx("p", { className: "text-muted-foreground", children: details }), learnMore && _jsx("div", { children: learnMore })] })] }));
45
45
  }
46
46
  return (_jsxs("div", { "data-slot": "ai-disclosure", "data-variant": "line", role: "note", className: cn("flex items-start justify-center gap-1.5 py-1 text-center text-xs text-muted-foreground", className), children: [_jsx(Bot, { "aria-hidden": true, className: "mt-0.5 size-3 shrink-0" }), _jsxs("p", { className: "flex flex-wrap items-center justify-center gap-x-1.5", children: [_jsx("span", { children: sentence }), details && _jsx("span", { children: details }), learnMore] })] }));
47
47
  }
@@ -27,8 +27,8 @@ declare function AttachmentChip({ attachment: a, onRemove, className, }: {
27
27
  /** The file, with progress (0–1) while uploading or an error if it failed. */
28
28
  attachment: Attachment;
29
29
  /** Shows a remove button. */
30
- onRemove?: (id: string) => void;
31
- className?: string;
30
+ onRemove?: ((id: string) => void) | undefined;
31
+ className?: string | undefined;
32
32
  }): React.JSX.Element;
33
33
  /**
34
34
  * A sent file, as a tile in a message: an image preview when the app supplied one, else an icon for its type, with
@@ -39,15 +39,15 @@ declare function AttachmentPreview({ attachment: a, onOpen, className, }: {
39
39
  /** The file. `previewUrl` must be one your app serves (a thumbnail, a blob: URL); it's shown only for images. */
40
40
  attachment: Attachment;
41
41
  /** Makes the tile a button that opens the file. */
42
- onOpen?: (attachment: Attachment) => void;
43
- className?: string;
42
+ onOpen?: ((attachment: Attachment) => void) | undefined;
43
+ className?: string | undefined;
44
44
  }): React.JSX.Element;
45
45
  /** Several sent files, as a wrapping row of tiles, announced as a list. */
46
46
  declare function AttachmentList({ attachments, onOpen, className, }: {
47
47
  /** The files. */
48
48
  attachments: Attachment[];
49
49
  /** Makes each tile a button that opens its file. */
50
- onOpen?: (attachment: Attachment) => void;
51
- className?: string;
50
+ onOpen?: ((attachment: Attachment) => void) | undefined;
51
+ className?: string | undefined;
52
52
  }): React.JSX.Element;
53
53
  export { AttachmentChip, AttachmentPreview, AttachmentList };
@@ -72,7 +72,7 @@ function AttachmentPreview({ attachment: a, onOpen, className, }) {
72
72
  const size = formatFileSize(a.size, fmt.number, t);
73
73
  const body = (_jsxs(_Fragment, { children: [thumb ? (_jsx("img", { src: thumb, alt: "", className: "h-24 w-full rounded-md bg-muted object-cover" })) : (_jsx("span", { "aria-hidden": true, className: "flex size-8 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", children: _jsx(FileGlyph, { attachment: a }) })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-sm", children: a.name }), (size || a.error) && _jsx("span", { className: cn("block text-xs tabular-nums", a.error ? "text-destructive-text" : "text-faint-foreground"), children: a.error ?? size })] })] }));
74
74
  const frame = cn("flex w-56 max-w-full gap-2.5 rounded-lg border bg-card p-2 text-start", thumb ? "flex-col" : "items-center", className);
75
- return onOpen ? (_jsx("button", { type: "button", "data-slot": "attachment-preview", "aria-label": t("open", { name: a.name }), onClick: () => onOpen(a), className: cn(frame, "outline-none transition-colors duration-150 hover:border-border-strong focus-visible:ring-[3px] focus-visible:ring-ring/40"), children: body })) : (_jsx("div", { "data-slot": "attachment-preview", className: frame, children: body }));
75
+ return onOpen ? (_jsx("button", { type: "button", "data-slot": "attachment-preview", "aria-label": t("open", { name: a.name }), onClick: () => onOpen(a), className: cn(frame, "outline-none transition-colors duration-150 hover:border-border-strong focus-visible:ring-[3px] focus-visible:ring-ring/80"), children: body })) : (_jsx("div", { "data-slot": "attachment-preview", className: frame, children: body }));
76
76
  }
77
77
  /** Several sent files, as a wrapping row of tiles, announced as a list. */
78
78
  function AttachmentList({ attachments, onOpen, className, }) {
@@ -32,7 +32,7 @@ class CardBoundary extends React.Component {
32
32
  }
33
33
  function Summary({ text, }) {
34
34
  const t = useStrings("cardRegistry", cardRegistryStrings);
35
- return (_jsxs(Collapsible, { className: "group/summary", children: [_jsxs(CollapsibleTrigger, { className: "relative after:absolute after:inset-x-0 after:-inset-y-1 inline-flex items-center gap-1 text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40 rounded-sm", children: [_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3 transition-transform group-data-[state=open]/summary:[transform:rotate(90deg)]" }), " ", t("showAsText")] }), _jsx(CollapsibleContent, { children: _jsx("p", { className: "mt-1.5 rounded-md bg-sunken px-3 py-2 text-sm whitespace-pre-wrap", children: text }) })] }));
35
+ return (_jsxs(Collapsible, { className: "group/summary", children: [_jsxs(CollapsibleTrigger, { className: "relative after:absolute after:inset-x-0 after:-inset-y-1 inline-flex items-center gap-1 text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80 rounded-sm", children: [_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3 transition-transform group-data-[state=open]/summary:[transform:rotate(90deg)]" }), " ", t("showAsText")] }), _jsx(CollapsibleContent, { children: _jsx("p", { className: "mt-1.5 rounded-md bg-sunken px-3 py-2 text-sm whitespace-pre-wrap", children: text }) })] }));
36
36
  }
37
37
  /**
38
38
  * Renders one card from the registry. Three honest fallbacks, each with the
@@ -31,14 +31,14 @@ declare function SourceDetail({ source, }: {
31
31
  */
32
32
  declare function Citation({ source, marker, }: {
33
33
  /** The source this marker points to; without it the marker says it's unknown. */
34
- source?: MessageSource;
34
+ source?: MessageSource | undefined;
35
35
  /** The number to show when there's no source. */
36
- marker?: number;
36
+ marker?: number | undefined;
37
37
  }): import("react").JSX.Element;
38
38
  /** The sources under a reply, as chips that open the same detail. */
39
39
  declare function SourceList({ sources, className, }: {
40
40
  /** Every source the reply used, in marker order. */
41
41
  sources: MessageSource[];
42
- className?: string;
42
+ className?: string | undefined;
43
43
  }): import("react").JSX.Element | null;
44
44
  export { Citation, SourceList, SourceDetail };
@@ -42,7 +42,7 @@ function SourceDetail({ source, }) {
42
42
  const { Icon, noun } = kindOf(source.kind, t);
43
43
  return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-sm bg-muted text-muted-foreground", children: _jsx(Icon, { className: "size-3" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium leading-snug", children: source.label }), _jsx("p", { className: "text-xs text-muted-foreground", children: source.origin ?? noun })] })] }), source.snippet && (
44
44
  // oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex
45
- _jsx("blockquote", { tabIndex: 0, className: "max-h-40 overflow-auto rounded-md bg-sunken outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40 focus-visible:ring-inset px-2.5 py-2 text-xs leading-relaxed whitespace-pre-wrap text-muted-foreground", children: source.snippet })), source.provisional && (_jsx("p", { className: "text-xs text-provisional", children: t("notVerified") }))] }));
45
+ _jsx("blockquote", { tabIndex: 0, className: "max-h-40 overflow-auto rounded-md bg-sunken outline-none focus-visible:ring-[3px] focus-visible:ring-ring/80 focus-visible:ring-inset px-2.5 py-2 text-xs leading-relaxed whitespace-pre-wrap text-muted-foreground", children: source.snippet })), source.provisional && (_jsx("p", { className: "text-xs text-provisional", children: t("notVerified") }))] }));
46
46
  }
47
47
  /**
48
48
  * An inline [n]. Opens the source in a popover on click or Enter, so evidence is
@@ -54,7 +54,7 @@ function Citation({ source, marker, }) {
54
54
  if (!source) {
55
55
  return _jsxs("sup", { className: "text-faint-foreground", children: ["[", marker ?? "?", "]"] });
56
56
  }
57
- return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("sourceLabel", { marker: source.marker, label: source.label }), className: cn("mx-0.5 inline-flex h-4 min-w-4 -translate-y-px items-center justify-center rounded-sm px-1 align-middle text-2xs font-semibold tabular-nums transition-colors duration-150 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40", source.provisional
57
+ return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("sourceLabel", { marker: source.marker, label: source.label }), className: cn("mx-0.5 inline-flex h-4 min-w-4 -translate-y-px items-center justify-center rounded-sm px-1 align-middle text-2xs font-semibold tabular-nums transition-colors duration-150 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/80", source.provisional
58
58
  ? "border border-dashed border-provisional/60 text-provisional hover:bg-muted"
59
59
  : "bg-accent-weak text-accent-text hover:bg-primary hover:text-primary-foreground"), children: source.marker }) }), _jsx(PopoverContent, { align: "start", className: "w-80 max-w-[calc(100vw-2rem)] p-3", children: _jsx(SourceDetail, { source: source }) })] }));
60
60
  }
@@ -67,7 +67,7 @@ function SourceList({ sources, className, }) {
67
67
  .sort((a, b) => a.marker - b.marker)
68
68
  .map((s) => {
69
69
  const { Icon } = kindOf(s.kind, t);
70
- return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: cn("relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex max-w-56 items-center gap-1.5 rounded-md border bg-card py-0.5 pe-2 ps-1 text-xs text-muted-foreground transition-colors duration-150 outline-none hover:border-border-strong hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", s.provisional && "border-dashed"), children: [_jsx("span", { className: "flex h-4 min-w-4 items-center justify-center rounded-sm bg-muted px-1 text-2xs font-semibold tabular-nums", children: s.marker }), _jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0" }), _jsx("span", { className: "truncate", children: s.label })] }) }), _jsx(PopoverContent, { align: "start", className: "w-80 max-w-[calc(100vw-2rem)] p-3", children: _jsx(SourceDetail, { source: s }) })] }, s.id));
70
+ return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: cn("relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex max-w-56 items-center gap-1.5 rounded-md border bg-card py-0.5 pe-2 ps-1 text-xs text-muted-foreground transition-colors duration-150 outline-none hover:border-border-strong hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80", s.provisional && "border-dashed"), children: [_jsx("span", { className: "flex h-4 min-w-4 items-center justify-center rounded-sm bg-muted px-1 text-2xs font-semibold tabular-nums", children: s.marker }), _jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0" }), _jsx("span", { className: "truncate", children: s.label })] }) }), _jsx(PopoverContent, { align: "start", className: "w-80 max-w-[calc(100vw-2rem)] p-3", children: _jsx(SourceDetail, { source: s }) })] }, s.id));
71
71
  })] }));
72
72
  }
73
73
  export { Citation, SourceList, SourceDetail };
@@ -22,7 +22,12 @@ export type ComposerState = "ready" | "sending" | "streaming" | {
22
22
  reason: string;
23
23
  } | {
24
24
  kind: "offline";
25
- reason?: string;
25
+ reason?: string | undefined;
26
+ }
27
+ /** Typing stays open, but sending waits, and the box says why: "Your partner is still thinking". */
28
+ | {
29
+ kind: "held";
30
+ reason: string;
26
31
  };
27
32
  /**
28
33
  * Where a person asks. Mentions (@) attach things in the user's system as chips the agent
@@ -33,9 +38,11 @@ export type ComposerState = "ready" | "sending" | "streaming" | {
33
38
  * Enter sends, Shift+Enter breaks a line. While an agent replies the send button
34
39
  * becomes Stop. A locked composer says why; an offline one says so. With
35
40
  * `onAttach`, files can also be pasted or dropped onto it. `tools` adds your
36
- * own controls to the toolbar, such as a dictation button.
41
+ * own controls to the toolbar, such as a dictation button. A held composer
42
+ * (`{ kind: "held", reason }`) keeps typing open and says why sending waits.
43
+ * `textareaRef` focuses the box from outside, e.g. after a new project starts.
37
44
  */
38
- declare function Composer({ value, onChange, onSubmit, onStop, state, scope, onScopeClick, mentions, commands, references, onReferencesChange, attachments, onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, tools, className, }: {
45
+ declare function Composer({ value, onChange, onSubmit, onStop, state, scope, onScopeClick, mentions, commands, references, onReferencesChange, attachments, onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, tools, textareaRef, sendShape, className, }: {
39
46
  /** The text in the box. */
40
47
  value: string;
41
48
  /** Called with the new text as the person types. */
@@ -43,38 +50,42 @@ declare function Composer({ value, onChange, onSubmit, onStop, state, scope, onS
43
50
  /** Called on send with the text and the entities mentioned. */
44
51
  onSubmit: (value: string, references: EntityRef[]) => void;
45
52
  /** Shown as Stop while a reply is streaming. */
46
- onStop?: () => void;
47
- /** ready, sending, streaming, locked (with a reason) or offline. */
48
- state?: ComposerState;
53
+ onStop?: (() => void) | undefined;
54
+ /** ready, sending, streaming, locked (with a reason), held (typing open, sending waits, with a reason) or offline. */
55
+ state?: ComposerState | undefined;
49
56
  /** Where the next message's work lands; production turns the pill to a warning. */
50
- scope?: ComposerScope;
57
+ scope?: ComposerScope | undefined;
51
58
  /** Makes the scope pill a button, e.g. to change environment. */
52
- onScopeClick?: () => void;
59
+ onScopeClick?: (() => void) | undefined;
53
60
  /** What @ can insert, grouped under their headings. */
54
- mentions?: MentionOption[];
61
+ mentions?: MentionOption[] | undefined;
55
62
  /** What / can run, only at the start of a message. */
56
- commands?: SlashCommand[];
63
+ commands?: SlashCommand[] | undefined;
57
64
  /** Entities already attached, shown as chips. */
58
- references?: EntityRef[];
65
+ references?: EntityRef[] | undefined;
59
66
  /** Called when a chip is added or removed. */
60
- onReferencesChange?: (references: EntityRef[]) => void;
67
+ onReferencesChange?: ((references: EntityRef[]) => void) | undefined;
61
68
  /** Files attached, with upload progress or an error. */
62
- attachments?: Attachment[];
69
+ attachments?: Attachment[] | undefined;
63
70
  /** Shows an attach button; called with the chosen files. */
64
- onAttach?: (files: File[]) => void;
71
+ onAttach?: ((files: File[]) => void) | undefined;
65
72
  /**
66
73
  * Which files the attach button's picker offers, as for `<input accept>` (e.g. "image/*,.pdf"). Files pasted or
67
74
  * dropped arrive as they are, so check types where you upload.
68
75
  */
69
- accept?: string;
76
+ accept?: string | undefined;
70
77
  /** Called when a file is removed. */
71
- onRemoveAttachment?: (id: string) => void;
78
+ onRemoveAttachment?: ((id: string) => void) | undefined;
72
79
  /** Shown in the empty box; "Ask a question, or describe a change" by default. */
73
- placeholder?: string;
80
+ placeholder?: string | undefined;
74
81
  /** The accessible name of the text box; "Message" by default. */
75
- label?: string;
82
+ label?: string | undefined;
76
83
  /** Your own controls in the toolbar, after the attach button: a dictation button, a model picker. */
77
- tools?: React.ReactNode;
78
- className?: string;
84
+ tools?: React.ReactNode | undefined;
85
+ /** The text box, to focus it or read its selection from outside. */
86
+ textareaRef?: React.Ref<HTMLTextAreaElement> | undefined;
87
+ /** The send button's shape: "square" (the default, rounded corners) or "round". */
88
+ sendShape?: "square" | "round" | undefined;
89
+ className?: string | undefined;
79
90
  }): React.JSX.Element;
80
91
  export { Composer };
@@ -48,24 +48,28 @@ const hasFiles = (e) => Array.from(e.dataTransfer?.types ?? []).includes("Files"
48
48
  * Enter sends, Shift+Enter breaks a line. While an agent replies the send button
49
49
  * becomes Stop. A locked composer says why; an offline one says so. With
50
50
  * `onAttach`, files can also be pasted or dropped onto it. `tools` adds your
51
- * own controls to the toolbar, such as a dictation button.
51
+ * own controls to the toolbar, such as a dictation button. A held composer
52
+ * (`{ kind: "held", reason }`) keeps typing open and says why sending waits.
53
+ * `textareaRef` focuses the box from outside, e.g. after a new project starts.
52
54
  */
53
- function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, onScopeClick, mentions = [], commands = [], references = [], onReferencesChange, attachments = [], onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, tools, className, }) {
55
+ function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, onScopeClick, mentions = [], commands = [], references = [], onReferencesChange, attachments = [], onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, tools, textareaRef, sendShape = "square", className, }) {
54
56
  const kindOf = useEntityKinds();
55
57
  const t = useStrings("composer", composerStrings);
56
58
  const placeholder = placeholderProp ?? t("placeholder");
57
59
  const label = labelProp ?? t("label");
58
60
  const id = React.useId();
59
61
  const textarea = React.useRef(null);
62
+ React.useImperativeHandle(textareaRef, () => textarea.current, []);
60
63
  // Files dragged over the composer: a count of enter/leave pairs, since child elements fire their own.
61
64
  const [dragDepth, setDragDepth] = React.useState(0);
62
65
  const fileInput = React.useRef(null);
63
66
  const [trigger, setTrigger] = React.useState(null);
64
67
  const [active, setActive] = React.useState(0);
65
- const locked = typeof state === "object" ? state : null;
68
+ const held = typeof state === "object" && state.kind === "held" ? state : null;
69
+ const locked = typeof state === "object" && state.kind !== "held" ? state : null;
66
70
  const canAttach = Boolean(onAttach) && !locked;
67
71
  const busy = state === "sending" || state === "streaming";
68
- const canSend = !locked && !busy && (value.trim().length > 0 || references.length > 0);
72
+ const canSend = !locked && !held && !busy && (value.trim().length > 0 || references.length > 0);
69
73
  const items = React.useMemo(() => {
70
74
  if (!trigger)
71
75
  return [];
@@ -207,11 +211,11 @@ function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, o
207
211
  onAttach(files);
208
212
  if (!e.clipboardData.getData("text/plain"))
209
213
  e.preventDefault();
210
- }, "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen ? optionId(active) : undefined, "aria-autocomplete": "list", "aria-describedby": locked ? `${id}-locked` : undefined, className: cn("block max-h-60 min-h-12 w-full resize-none bg-transparent px-4 pt-3 pb-1 text-prose leading-relaxed outline-none [field-sizing:content] placeholder:text-faint-foreground disabled:cursor-not-allowed", locked && !value && "min-h-0 h-3 pt-3 pb-0") }), locked && (_jsx("p", { id: `${id}-locked`, className: "flex items-center gap-1.5 px-4 pb-1 text-sm text-muted-foreground", children: locked.kind === "offline" ? locked.reason ?? t("offline") : locked.reason })), _jsxs("div", { className: "flex items-center gap-1.5 px-2 pt-1 pb-2", children: [onAttach && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInput, type: "file", multiple: true, accept: accept, tabIndex: -1, "aria-label": t("attachFiles"), className: "hidden", onChange: (e) => {
214
+ }, "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen ? optionId(active) : undefined, "aria-autocomplete": "list", "aria-describedby": locked ? `${id}-locked` : held ? `${id}-held` : undefined, className: cn("block max-h-60 min-h-12 w-full resize-none bg-transparent px-4 pt-3 pb-1 text-prose leading-relaxed outline-none [field-sizing:content] placeholder:text-faint-foreground disabled:cursor-not-allowed", locked && !value && "min-h-0 h-3 pt-3 pb-0") }), locked && (_jsx("p", { id: `${id}-locked`, className: "flex items-center gap-1.5 px-4 pb-1 text-sm text-muted-foreground", children: locked.kind === "offline" ? locked.reason ?? t("offline") : locked.reason })), held && (_jsx("p", { id: `${id}-held`, "data-slot": "composer-held", className: "px-4 pb-1 text-sm text-muted-foreground", children: held.reason })), _jsxs("div", { className: "flex items-center gap-1.5 px-2 pt-1 pb-2", children: [onAttach && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInput, type: "file", multiple: true, accept: accept, tabIndex: -1, "aria-label": t("attachFiles"), className: "hidden", onChange: (e) => {
211
215
  const files = Array.from(e.target.files ?? []);
212
216
  if (files.length)
213
217
  onAttach(files);
214
218
  e.target.value = "";
215
- } }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("attachFiles"), disabled: Boolean(locked), onClick: () => fileInput.current?.click(), className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:opacity-40", children: _jsx(Paperclip, { className: "size-4" }) }) }), _jsx(TooltipContent, { children: t("attachFiles") })] })] })), tools, scope && (_jsxs("button", { type: "button", onClick: onScopeClick, disabled: !onScopeClick, className: cn("inline-flex min-w-0 items-center gap-1.5 rounded-lg px-2 py-1 text-xs transition-colors duration-150 enabled:hover:bg-accent", production ? "bg-warning-weak text-warning-text enabled:hover:bg-warning-weak" : "text-muted-foreground"), children: [production ? _jsx(TriangleAlert, { "aria-hidden": true, className: "size-3 shrink-0" }) : _jsx("span", { "aria-hidden": true, className: "size-1.5 shrink-0 rounded-full bg-success" }), _jsxs("span", { className: "truncate", children: [scope.environment, " \u00B7 ", production ? t("production") : (scope.detail ?? t("nonProduction"))] })] })), _jsxs("span", { className: "ms-auto hidden text-2xs text-faint-foreground @lg:inline", children: [_jsx("kbd", { className: "font-sans", children: "\u21B5" }), " ", t("hintSend"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "\u21E7\u21B5" }), " ", t("hintNewLine"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "@" }), " ", t("hintMention")] }), state === "streaming" && onStop ? (_jsx("button", { type: "button", "aria-label": t("stop"), onClick: onStop, className: "ms-auto flex size-8 items-center justify-center rounded-lg bg-foreground text-background transition-transform duration-150 active:scale-95 @lg:ms-1", children: _jsx(Square, { className: "size-3 fill-current" }) })) : (_jsx("button", { type: "button", "aria-label": t("send"), onClick: submit, disabled: !canSend, "aria-busy": state === "sending", className: "ms-auto flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground transition-[background-color,opacity,transform] duration-150 enabled:hover:bg-primary-hover active:scale-95 disabled:opacity-30 @lg:ms-1", children: state === "sending" ? _jsx(LoaderCircle, { className: "size-4 animate-spin" }) : _jsx(ArrowUp, { className: "size-4", strokeWidth: 2.5 }) }))] })] })] }));
219
+ } }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("attachFiles"), disabled: Boolean(locked), onClick: () => fileInput.current?.click(), className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:opacity-40", children: _jsx(Paperclip, { className: "size-4" }) }) }), _jsx(TooltipContent, { children: t("attachFiles") })] })] })), tools, scope && (_jsxs("button", { type: "button", onClick: onScopeClick, disabled: !onScopeClick, className: cn("inline-flex min-w-0 items-center gap-1.5 rounded-lg px-2 py-1 text-xs transition-colors duration-150 enabled:hover:bg-accent", production ? "bg-warning-weak text-warning-text enabled:hover:bg-warning-weak" : "text-muted-foreground"), children: [production ? _jsx(TriangleAlert, { "aria-hidden": true, className: "size-3 shrink-0" }) : _jsx("span", { "aria-hidden": true, className: "size-1.5 shrink-0 rounded-full bg-success" }), _jsxs("span", { className: "truncate", children: [scope.environment, " \u00B7 ", production ? t("production") : (scope.detail ?? t("nonProduction"))] })] })), _jsxs("span", { className: "ms-auto hidden text-2xs text-faint-foreground @lg:inline", children: [_jsx("kbd", { className: "font-sans", children: "\u21B5" }), " ", t("hintSend"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "\u21E7\u21B5" }), " ", t("hintNewLine"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "@" }), " ", t("hintMention")] }), state === "streaming" && onStop ? (_jsx("button", { type: "button", "aria-label": t("stop"), onClick: onStop, className: cn("ms-auto flex size-8 items-center justify-center bg-foreground text-background transition-transform duration-150 active:scale-95 @lg:ms-1", sendShape === "round" ? "rounded-full" : "rounded-lg"), children: _jsx(Square, { className: "size-3 fill-current" }) })) : (_jsx("button", { type: "button", "aria-label": t("send"), onClick: submit, disabled: !canSend, "aria-busy": state === "sending", className: cn("ms-auto flex size-8 items-center justify-center bg-primary text-primary-foreground transition-[background-color,opacity,transform] duration-150 enabled:hover:bg-primary-hover active:scale-95 disabled:opacity-30 @lg:ms-1", sendShape === "round" ? "rounded-full" : "rounded-lg"), children: state === "sending" ? _jsx(LoaderCircle, { className: "size-4 animate-spin" }) : _jsx(ArrowUp, { className: "size-4", strokeWidth: 2.5 }) }))] })] })] }));
216
220
  }
217
221
  export { Composer };
@@ -18,6 +18,6 @@ declare function ContextMeter({ segments, limit, className, }: {
18
18
  segments: ContextSegment[];
19
19
  /** The model's context window, in tokens. */
20
20
  limit: number;
21
- className?: string;
21
+ className?: string | undefined;
22
22
  }): import("react").JSX.Element;
23
23
  export { ContextMeter };
@@ -40,7 +40,7 @@ function ContextMeter({ segments, limit, className, }) {
40
40
  const warn = pct > 0.8;
41
41
  const r = 7;
42
42
  const c = 2 * Math.PI * r;
43
- return (_jsxs(Popover, { children: [_jsxs(PopoverTrigger, { "aria-label": t("label", { used: tokens(used), limit: tokens(limit) }), className: cn("inline-flex items-center gap-1.5 rounded-md px-1.5 py-1 text-2xs tabular-nums outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", warn ? "text-warning-text" : "text-muted-foreground", className), children: [_jsxs("svg", { viewBox: "0 0 18 18", className: "size-4 -rotate-90", "aria-hidden": true, children: [_jsx("circle", { cx: "9", cy: "9", r: r, fill: "none", stroke: "var(--muted)", strokeWidth: "2.5" }), _jsx("circle", { cx: "9", cy: "9", r: r, fill: "none", stroke: warn ? "var(--warning)" : "var(--primary)", strokeWidth: "2.5", strokeDasharray: `${pct * c} ${c}`, strokeLinecap: "round" })] }), fmt.number(Math.round(pct * 100) / 100, { style: "percent" })] }), _jsxs(PopoverContent, { align: "start", className: "w-72 p-3", children: [_jsxs("p", { className: "flex items-baseline justify-between text-sm font-medium", children: [t("context"), " ", _jsx("span", { className: "text-xs font-normal text-muted-foreground tabular-nums", children: t("usage", { used: tokens(used), limit: tokens(limit) }) })] }), _jsx("div", { "aria-hidden": true, className: "mt-2 flex h-2 overflow-hidden rounded-full bg-muted", children: segments.map((s) => (_jsx("span", { style: { width: `${(s.tokens / limit) * 100}%`, background: kindColor[s.kind] } }, s.label))) }), _jsx("ul", { className: "mt-3 flex flex-col gap-1.5", children: [...segments]
43
+ return (_jsxs(Popover, { children: [_jsxs(PopoverTrigger, { "aria-label": t("label", { used: tokens(used), limit: tokens(limit) }), className: cn("inline-flex items-center gap-1.5 rounded-md px-1.5 py-1 text-2xs tabular-nums outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/80", warn ? "text-warning-text" : "text-muted-foreground", className), children: [_jsxs("svg", { viewBox: "0 0 18 18", className: "size-4 -rotate-90", "aria-hidden": true, children: [_jsx("circle", { cx: "9", cy: "9", r: r, fill: "none", stroke: "var(--muted)", strokeWidth: "2.5" }), _jsx("circle", { cx: "9", cy: "9", r: r, fill: "none", stroke: warn ? "var(--warning)" : "var(--primary)", strokeWidth: "2.5", strokeDasharray: `${pct * c} ${c}`, strokeLinecap: "round" })] }), fmt.number(Math.round(pct * 100) / 100, { style: "percent" })] }), _jsxs(PopoverContent, { align: "start", className: "w-72 p-3", children: [_jsxs("p", { className: "flex items-baseline justify-between text-sm font-medium", children: [t("context"), " ", _jsx("span", { className: "text-xs font-normal text-muted-foreground tabular-nums", children: t("usage", { used: tokens(used), limit: tokens(limit) }) })] }), _jsx("div", { "aria-hidden": true, className: "mt-2 flex h-2 overflow-hidden rounded-full bg-muted", children: segments.map((s) => (_jsx("span", { style: { width: `${(s.tokens / limit) * 100}%`, background: kindColor[s.kind] } }, s.label))) }), _jsx("ul", { className: "mt-3 flex flex-col gap-1.5", children: [...segments]
44
44
  .sort((a, b) => b.tokens - a.tokens)
45
45
  .map((s) => (_jsxs("li", { className: "flex items-center gap-2 text-xs", children: [_jsx("span", { "aria-hidden": true, className: "size-2 rounded-xs", style: { background: kindColor[s.kind] } }), _jsx("span", { className: "flex-1", children: s.label }), _jsx("span", { className: "text-muted-foreground tabular-nums", children: tokens(s.tokens) })] }, s.label))) }), warn && _jsx("p", { className: "mt-3 text-xs text-warning-text", children: t("nearlyFull") })] })] }));
46
46
  }
@@ -48,9 +48,9 @@ declare function EntityChip({ entity, onOpen, onRemove, className, }: {
48
48
  /** The thing to show: its kind, identifier and optional label. */
49
49
  entity: EntityRef;
50
50
  /** Makes the chip a button that opens the thing. */
51
- onOpen?: (entity: EntityRef) => void;
51
+ onOpen?: ((entity: EntityRef) => void) | undefined;
52
52
  /** Adds a remove button, e.g. in the composer. */
53
- onRemove?: (entity: EntityRef) => void;
54
- className?: string;
53
+ onRemove?: ((entity: EntityRef) => void) | undefined;
54
+ className?: string | undefined;
55
55
  }): React.JSX.Element;
56
56
  export { EntityChip, EntityKindsProvider, useEntityKinds };
@@ -81,6 +81,6 @@ function EntityChip({ entity, onOpen, onRemove, className, }) {
81
81
  : t("title", { noun, name: entity.name });
82
82
  const body = (_jsxs(_Fragment, { children: [_jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate", children: shown }), _jsxs("span", { className: "sr-only", children: [" (", title, ")"] })] }));
83
83
  const base = "inline-flex max-w-full items-center gap-1 rounded-sm border bg-card px-1.5 py-px align-baseline font-mono text-[0.92em] leading-[1.35] text-foreground";
84
- return (_jsxs("span", { "data-slot": "entity-chip", "data-kind": entity.kind, title: title, className: cn("inline-flex max-w-full min-w-0 items-center sm:max-w-64", className), children: [onOpen ? (_jsx("button", { type: "button", onClick: () => onOpen(entity), className: cn(base, "cursor-pointer transition-colors duration-150 hover:border-border-strong hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40 outline-none"), children: body })) : (_jsx("span", { className: base, children: body })), onRemove && (_jsx("button", { type: "button", "aria-label": t("remove", { name: shown }), onClick: () => onRemove(entity), className: "relative after:absolute after:-inset-1 -ms-px rounded-e-sm border border-s-0 bg-card px-1 py-px text-faint-foreground hover:text-foreground", children: "\u00D7" }))] }));
84
+ return (_jsxs("span", { "data-slot": "entity-chip", "data-kind": entity.kind, title: title, className: cn("inline-flex max-w-full min-w-0 items-center sm:max-w-64", className), children: [onOpen ? (_jsx("button", { type: "button", onClick: () => onOpen(entity), className: cn(base, "cursor-pointer transition-colors duration-150 hover:border-border-strong hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/80 outline-none"), children: body })) : (_jsx("span", { className: base, children: body })), onRemove && (_jsx("button", { type: "button", "aria-label": t("remove", { name: shown }), onClick: () => onRemove(entity), className: "relative after:absolute after:-inset-1 -ms-px rounded-e-sm border border-s-0 bg-card px-1 py-px text-faint-foreground hover:text-foreground", children: "\u00D7" }))] }));
85
85
  }
86
86
  export { EntityChip, EntityKindsProvider, useEntityKinds };
@@ -15,19 +15,24 @@ export declare const foldedTurnsStrings: {
15
15
  * versions and compared them". The line says how many turns are inside and opens them in place; `summary` is yours
16
16
  * (a sentence, a count of tool calls, a cost). Folding never hides that the turns happened.
17
17
  */
18
- declare function FoldedTurns({ count, summary, defaultOpen, open, onOpenChange, children, className, }: {
18
+ declare function FoldedTurns({ count, summary, defaultOpen, open, onOpenChange, header, children, className, }: {
19
19
  /** How many turns are inside, for the button ("Show 12 turns"). */
20
20
  count: number;
21
21
  /** What happened in them, shown on the folded line: text or your own small summary. */
22
- summary?: React.ReactNode;
22
+ summary?: React.ReactNode | undefined;
23
23
  /** Start open. */
24
- defaultOpen?: boolean;
24
+ defaultOpen?: boolean | undefined;
25
25
  /** Controlled open state. */
26
- open?: boolean;
26
+ open?: boolean | undefined;
27
27
  /** Called when a person opens or closes it. */
28
- onOpenChange?: (open: boolean) => void;
28
+ onOpenChange?: ((open: boolean) => void) | undefined;
29
+ /**
30
+ * Your own folded line instead of the dashed one: a card with a title, how it ended, its cost, actions. The
31
+ * Show/Hide button follows it, so the turns can still be opened.
32
+ */
33
+ header?: React.ReactNode | undefined;
29
34
  /** The turns, usually Messages. */
30
35
  children: React.ReactNode;
31
- className?: string;
36
+ className?: string | undefined;
32
37
  }): React.JSX.Element;
33
38
  export { FoldedTurns };
@@ -15,7 +15,7 @@ export const foldedTurnsStrings = {
15
15
  * versions and compared them". The line says how many turns are inside and opens them in place; `summary` is yours
16
16
  * (a sentence, a count of tool calls, a cost). Folding never hides that the turns happened.
17
17
  */
18
- function FoldedTurns({ count, summary, defaultOpen = false, open, onOpenChange, children, className, }) {
18
+ function FoldedTurns({ count, summary, defaultOpen = false, open, onOpenChange, header, children, className, }) {
19
19
  const t = useStrings("foldedTurns", foldedTurnsStrings);
20
20
  const [own, setOwn] = React.useState(defaultOpen);
21
21
  const isOpen = open ?? own;
@@ -24,6 +24,6 @@ function FoldedTurns({ count, summary, defaultOpen = false, open, onOpenChange,
24
24
  setOwn(next);
25
25
  onOpenChange?.(next);
26
26
  };
27
- return (_jsxs(Collapsible, { open: isOpen, onOpenChange: set, "data-slot": "folded-turns", className: cn("group/folded flex flex-col gap-7", className), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 rounded-lg border border-dashed px-3 py-2 text-sm", children: [summary && _jsx("div", { className: "min-w-0 flex-1 text-muted-foreground", children: summary }), _jsxs(CollapsibleTrigger, { className: "relative after:absolute after:inset-x-0 after:-inset-y-0.5 ms-auto inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium text-accent-text outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 transition-transform duration-200 group-data-[state=open]/folded:[transform:rotate(90deg)]" }), t(isOpen ? "hide" : "show", { count })] })] }), _jsx(CollapsibleContent, { className: "flex flex-col gap-7", children: children })] }));
27
+ return (_jsxs(Collapsible, { open: isOpen, onOpenChange: set, "data-slot": "folded-turns", className: cn("group/folded flex flex-col gap-7", className), children: [_jsxs("div", { "data-slot": "folded-turns-line", className: cn("flex flex-wrap items-center gap-x-3 gap-y-1 text-sm", header ? "flex-col items-stretch" : "rounded-lg border border-dashed px-3 py-2"), children: [header ?? (summary && _jsx("div", { className: "min-w-0 flex-1 text-muted-foreground", children: summary })), _jsxs(CollapsibleTrigger, { className: cn("relative after:absolute after:inset-x-0 after:-inset-y-0.5 ms-auto inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium text-accent-text outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/80", header && "self-end"), children: [_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 transition-transform duration-200 group-data-[state=open]/folded:[transform:rotate(90deg)]" }), t(isOpen ? "hide" : "show", { count })] })] }), _jsx(CollapsibleContent, { className: "flex flex-col gap-7", children: children })] }));
28
28
  }
29
29
  export { FoldedTurns };
@@ -16,15 +16,15 @@ export type Rating = "up" | "down" | null;
16
16
  */
17
17
  declare function MessageActions({ text, onRetry, rating, onRate, meta, className, }: {
18
18
  /** What Copy copies; without it there's no Copy. */
19
- text?: string;
19
+ text?: string | undefined;
20
20
  /** Shows Retry. */
21
- onRetry?: () => void;
21
+ onRetry?: (() => void) | undefined;
22
22
  /** The current rating: up, down or none. */
23
- rating?: Rating;
23
+ rating?: Rating | undefined;
24
24
  /** Shows thumbs up and down. */
25
- onRate?: (rating: Rating) => void;
25
+ onRate?: ((rating: Rating) => void) | undefined;
26
26
  /** Facts after the actions, e.g. the model and cost. */
27
- meta?: React.ReactNode;
28
- className?: string;
27
+ meta?: React.ReactNode | undefined;
28
+ className?: string | undefined;
29
29
  }): React.JSX.Element;
30
30
  export { MessageActions };