@candor-ui/react 1.5.0 → 1.8.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 (72) 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 +51 -34
  4. package/dist/adapters/claude-agent-sdk.js +46 -2
  5. package/dist/adapters/claude-agent-server.d.ts +34 -34
  6. package/dist/adapters/use-ag-ui.d.ts +6 -6
  7. package/dist/adapters/use-ag-ui.js +14 -2
  8. package/dist/adapters/use-candor-chat.d.ts +1 -1
  9. package/dist/adapters/use-candor-chat.js +1 -1
  10. package/dist/adapters/use-claude-agent.d.ts +10 -10
  11. package/dist/adapters/use-claude-agent.js +1 -1
  12. package/dist/blocks/copy-field.d.ts +1 -1
  13. package/dist/blocks/copy-field.js +1 -1
  14. package/dist/blocks/degraded-panel.d.ts +5 -5
  15. package/dist/blocks/locale.d.ts +6 -6
  16. package/dist/blocks/locked.d.ts +8 -8
  17. package/dist/blocks/relative-time.d.ts +6 -6
  18. package/dist/blocks/status-dot.d.ts +3 -3
  19. package/dist/blocks/step-list.d.ts +3 -3
  20. package/dist/blocks/streaming-text.d.ts +3 -3
  21. package/dist/chat/agent-avatar.d.ts +4 -4
  22. package/dist/chat/ai-disclosure.d.ts +5 -5
  23. package/dist/chat/ai-disclosure.js +1 -1
  24. package/dist/chat/attachment-preview.d.ts +6 -6
  25. package/dist/chat/attachment-preview.js +1 -1
  26. package/dist/chat/card-registry.js +1 -1
  27. package/dist/chat/citation.d.ts +3 -3
  28. package/dist/chat/citation.js +3 -3
  29. package/dist/chat/composer.d.ts +37 -20
  30. package/dist/chat/composer.js +13 -6
  31. package/dist/chat/context-meter.d.ts +1 -1
  32. package/dist/chat/context-meter.js +1 -1
  33. package/dist/chat/entity-chip.d.ts +3 -3
  34. package/dist/chat/entity-chip.js +1 -1
  35. package/dist/chat/folded-turns.d.ts +11 -6
  36. package/dist/chat/folded-turns.js +2 -2
  37. package/dist/chat/message-actions.d.ts +6 -6
  38. package/dist/chat/message-actions.js +1 -1
  39. package/dist/chat/message-content.d.ts +13 -7
  40. package/dist/chat/message-content.js +10 -5
  41. package/dist/chat/message.d.ts +64 -37
  42. package/dist/chat/message.js +17 -11
  43. package/dist/chat/payload.d.ts +4 -4
  44. package/dist/chat/payload.js +2 -2
  45. package/dist/chat/permission-prompt.d.ts +23 -23
  46. package/dist/chat/prompt-suggestions.d.ts +4 -4
  47. package/dist/chat/prompt-suggestions.js +1 -1
  48. package/dist/chat/reasoning.d.ts +3 -3
  49. package/dist/chat/reasoning.js +1 -1
  50. package/dist/chat/thread-divider.d.ts +3 -3
  51. package/dist/chat/thread-event.d.ts +10 -5
  52. package/dist/chat/thread-event.js +3 -2
  53. package/dist/chat/thread.d.ts +23 -7
  54. package/dist/chat/thread.js +28 -5
  55. package/dist/chat/tool-call.d.ts +2 -2
  56. package/dist/chat/tool-call.js +2 -2
  57. package/dist/index.d.ts +1 -0
  58. package/dist/index.js +1 -0
  59. package/dist/lib/chat.d.ts +42 -42
  60. package/dist/lib/know.d.ts +14 -12
  61. package/dist/lib/mcp-input.d.ts +316 -0
  62. package/dist/lib/mcp-input.js +369 -0
  63. package/dist/lib/render-prop.d.ts +4 -4
  64. package/dist/lib/safe-markdown.d.ts +1 -1
  65. package/dist/lib/status.d.ts +21 -0
  66. package/dist/lib/status.js +17 -0
  67. package/dist/lib/use-shortcut.d.ts +1 -1
  68. package/dist/lib/use-virtual-list.d.ts +6 -6
  69. package/dist/styles.css +4 -1
  70. package/dist/ui/button.d.ts +1 -1
  71. package/dist/ui/button.js +1 -1
  72. package/package.json +1 -1
@@ -8,30 +8,30 @@ export type UseClaudeAgentOptions = Omit<FromClaudeAgentOptions, "running"> & {
8
8
  * Your route that records an answer (answerClaudeApproval). It receives `{ id, approved, reason, updatedInput,
9
9
  * answers, alwaysAllow }` and returns `{ ok }`. Not needed with `onRespond`.
10
10
  */
11
- approveApi?: string;
11
+ approveApi?: string | undefined;
12
12
  /**
13
13
  * Answer through your own route instead, e.g. one that signals the workflow running the agent. Resolve true once
14
14
  * the answer is recorded, false if it wasn't (the request had ended).
15
15
  */
16
- onRespond?: (approvalId: string, answer: ClaudeApprovalAnswer) => Promise<boolean>;
16
+ onRespond?: ((approvalId: string, answer: ClaudeApprovalAnswer) => Promise<boolean>) | undefined;
17
17
  /** Events you kept from earlier (onEvent, a run store): the conversation is folded from them, as it was. */
18
- initialEvents?: AgentEvent[];
18
+ initialEvents?: AgentEvent[] | undefined;
19
19
  /**
20
20
  * Where to re-attach to a run after a reload: `(runId, from) => \`/api/agent/${runId}?from=${from}\``, a route that
21
21
  * returns attachClaudeAgentRun. With `runId`, the hook re-attaches when it mounts.
22
22
  */
23
- attachUrl?: (runId: string, from: number) => string;
23
+ attachUrl?: ((runId: string, from: number) => string) | undefined;
24
24
  /** A run to re-attach to when the hook mounts, e.g. the one you saved from `runId` before the reload. */
25
- runId?: string;
25
+ runId?: string | undefined;
26
26
  /** Your route that calls stopClaudeAgentRun with `{ runId }`. Needed to stop a run that keeps going without the browser (`detach`). */
27
- stopApi?: string;
27
+ stopApi?: string | undefined;
28
28
  /** Your route that calls setClaudeAgentPermissionMode with `{ runId, mode }`. Enables `setPermissionMode`. */
29
- modeApi?: string;
29
+ modeApi?: string | undefined;
30
30
  /** Extra fields for every request, e.g. which project the agent works on. */
31
- body?: Record<string, unknown>;
32
- headers?: Record<string, string>;
31
+ body?: Record<string, unknown> | undefined;
32
+ headers?: Record<string, string> | undefined;
33
33
  /** Replaces the global fetch, e.g. to add auth or to play a script in tests and demos. */
34
- fetch?: typeof fetch;
34
+ fetch?: typeof fetch | undefined;
35
35
  };
36
36
  /**
37
37
  * Wires a Claude Agent SDK server route to Candor: messages for <Thread>/<Message>,
@@ -97,7 +97,7 @@ export function useClaudeAgent(options) {
97
97
  if (!attachUrl)
98
98
  return Promise.resolve();
99
99
  setRunId(id);
100
- return stream((signal) => (fetcher ?? fetch)(attachUrl(id, from), { headers, signal }), undefined, from === 0);
100
+ return stream((signal) => (fetcher ?? fetch)(attachUrl(id, from), { ...(headers && { headers }), signal }), undefined, from === 0);
101
101
  }, [attachUrl, fetcher, headers, stream]);
102
102
  // Re-attach to the run named in options once, when the hook mounts.
103
103
  const attachOnMount = React.useRef(options.runId);
@@ -14,6 +14,6 @@ declare function CopyField({ label, value, className, }: {
14
14
  label: string;
15
15
  /** The text that is shown and copied. */
16
16
  value: string;
17
- className?: string;
17
+ className?: string | undefined;
18
18
  }): import("react").JSX.Element;
19
19
  export { CopyField };
@@ -18,6 +18,6 @@ export const copyFieldStrings = {
18
18
  function CopyField({ label, value, className, }) {
19
19
  const { state, copy } = useCopy();
20
20
  const t = useStrings("copyField", copyFieldStrings);
21
- return (_jsxs("div", { "data-slot": "copy-field", className: cn("flex flex-col gap-1", className), children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }), _jsxs("button", { type: "button", onClick: () => copy(value), className: "group/copy flex w-full items-center gap-2 rounded-lg border bg-sunken px-3 py-2 text-start outline-none transition-colors duration-150 hover:border-border-strong focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: value }), _jsx("span", { "aria-live": "polite", className: "inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground group-hover/copy:text-foreground", children: state === "copied" ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "size-3 text-success-text" }), " ", t("copied")] })) : state === "failed" ? (_jsxs(_Fragment, { children: [_jsx(X, { className: "size-3 text-destructive-text" }), " ", t("failed")] })) : (_jsxs(_Fragment, { children: [_jsx(Copy, { className: "size-3" }), " ", t("copy")] })) })] })] }));
21
+ return (_jsxs("div", { "data-slot": "copy-field", className: cn("flex flex-col gap-1", className), children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }), _jsxs("button", { type: "button", onClick: () => copy(value), className: "group/copy flex w-full items-center gap-2 rounded-lg border bg-sunken px-3 py-2 text-start outline-none transition-colors duration-150 hover:border-border-strong focus-visible:ring-[3px] focus-visible:ring-ring/80", children: [_jsx("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: value }), _jsx("span", { "aria-live": "polite", className: "inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground group-hover/copy:text-foreground", children: state === "copied" ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "size-3 text-success-text" }), " ", t("copied")] })) : state === "failed" ? (_jsxs(_Fragment, { children: [_jsx(X, { className: "size-3 text-destructive-text" }), " ", t("failed")] })) : (_jsxs(_Fragment, { children: [_jsx(Copy, { className: "size-3" }), " ", t("copy")] })) })] })] }));
22
22
  }
23
23
  export { CopyField };
@@ -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 };
@@ -13,6 +13,7 @@ export declare const composerStrings: {
13
13
  hintSend: string;
14
14
  hintNewLine: string;
15
15
  hintMention: string;
16
+ hintCommand: string;
16
17
  stop: string;
17
18
  send: string;
18
19
  dropFiles: string;
@@ -22,7 +23,12 @@ export type ComposerState = "ready" | "sending" | "streaming" | {
22
23
  reason: string;
23
24
  } | {
24
25
  kind: "offline";
25
- reason?: string;
26
+ reason?: string | undefined;
27
+ }
28
+ /** Typing stays open, but sending waits, and the box says why: "Your partner is still thinking". */
29
+ | {
30
+ kind: "held";
31
+ reason: string;
26
32
  };
27
33
  /**
28
34
  * Where a person asks. Mentions (@) attach things in the user's system as chips the agent
@@ -33,9 +39,11 @@ export type ComposerState = "ready" | "sending" | "streaming" | {
33
39
  * Enter sends, Shift+Enter breaks a line. While an agent replies the send button
34
40
  * becomes Stop. A locked composer says why; an offline one says so. With
35
41
  * `onAttach`, files can also be pasted or dropped onto it. `tools` adds your
36
- * own controls to the toolbar, such as a dictation button.
42
+ * own controls to the toolbar, such as a dictation button. A held composer
43
+ * (`{ kind: "held", reason }`) keeps typing open and says why sending waits.
44
+ * `textareaRef` focuses the box from outside, e.g. after a new project starts.
37
45
  */
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, }: {
46
+ 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, hints, className, }: {
39
47
  /** The text in the box. */
40
48
  value: string;
41
49
  /** Called with the new text as the person types. */
@@ -43,38 +51,47 @@ declare function Composer({ value, onChange, onSubmit, onStop, state, scope, onS
43
51
  /** Called on send with the text and the entities mentioned. */
44
52
  onSubmit: (value: string, references: EntityRef[]) => void;
45
53
  /** Shown as Stop while a reply is streaming. */
46
- onStop?: () => void;
47
- /** ready, sending, streaming, locked (with a reason) or offline. */
48
- state?: ComposerState;
54
+ onStop?: (() => void) | undefined;
55
+ /** ready, sending, streaming, locked (with a reason), held (typing open, sending waits, with a reason) or offline. */
56
+ state?: ComposerState | undefined;
49
57
  /** Where the next message's work lands; production turns the pill to a warning. */
50
- scope?: ComposerScope;
58
+ scope?: ComposerScope | undefined;
51
59
  /** Makes the scope pill a button, e.g. to change environment. */
52
- onScopeClick?: () => void;
60
+ onScopeClick?: (() => void) | undefined;
53
61
  /** What @ can insert, grouped under their headings. */
54
- mentions?: MentionOption[];
62
+ mentions?: MentionOption[] | undefined;
55
63
  /** What / can run, only at the start of a message. */
56
- commands?: SlashCommand[];
64
+ commands?: SlashCommand[] | undefined;
57
65
  /** Entities already attached, shown as chips. */
58
- references?: EntityRef[];
66
+ references?: EntityRef[] | undefined;
59
67
  /** Called when a chip is added or removed. */
60
- onReferencesChange?: (references: EntityRef[]) => void;
68
+ onReferencesChange?: ((references: EntityRef[]) => void) | undefined;
61
69
  /** Files attached, with upload progress or an error. */
62
- attachments?: Attachment[];
70
+ attachments?: Attachment[] | undefined;
63
71
  /** Shows an attach button; called with the chosen files. */
64
- onAttach?: (files: File[]) => void;
72
+ onAttach?: ((files: File[]) => void) | undefined;
65
73
  /**
66
74
  * Which files the attach button's picker offers, as for `<input accept>` (e.g. "image/*,.pdf"). Files pasted or
67
75
  * dropped arrive as they are, so check types where you upload.
68
76
  */
69
- accept?: string;
77
+ accept?: string | undefined;
70
78
  /** Called when a file is removed. */
71
- onRemoveAttachment?: (id: string) => void;
79
+ onRemoveAttachment?: ((id: string) => void) | undefined;
72
80
  /** Shown in the empty box; "Ask a question, or describe a change" by default. */
73
- placeholder?: string;
81
+ placeholder?: string | undefined;
74
82
  /** The accessible name of the text box; "Message" by default. */
75
- label?: string;
83
+ label?: string | undefined;
76
84
  /** Your own controls in the toolbar, after the attach button: a dictation button, a model picker. */
77
- tools?: React.ReactNode;
78
- className?: string;
85
+ tools?: React.ReactNode | undefined;
86
+ /**
87
+ * The keyboard hints shown at wide widths ("↵ send · ⇧↵ new line"), naming @ and / only when `mentions` or
88
+ * `commands` are given. Defaults to true; false hides them.
89
+ */
90
+ hints?: boolean | undefined;
91
+ /** The text box, to focus it or read its selection from outside. */
92
+ textareaRef?: React.Ref<HTMLTextAreaElement> | undefined;
93
+ /** The send button's shape: "square" (the default, rounded corners) or "round". */
94
+ sendShape?: "square" | "round" | undefined;
95
+ className?: string | undefined;
79
96
  }): React.JSX.Element;
80
97
  export { Composer };
@@ -20,6 +20,7 @@ export const composerStrings = {
20
20
  hintSend: "send",
21
21
  hintNewLine: "new line",
22
22
  hintMention: "mention",
23
+ hintCommand: "command",
23
24
  stop: "Stop",
24
25
  send: "Send",
25
26
  dropFiles: "Drop files to attach",
@@ -48,24 +49,28 @@ const hasFiles = (e) => Array.from(e.dataTransfer?.types ?? []).includes("Files"
48
49
  * Enter sends, Shift+Enter breaks a line. While an agent replies the send button
49
50
  * becomes Stop. A locked composer says why; an offline one says so. With
50
51
  * `onAttach`, files can also be pasted or dropped onto it. `tools` adds your
51
- * own controls to the toolbar, such as a dictation button.
52
+ * own controls to the toolbar, such as a dictation button. A held composer
53
+ * (`{ kind: "held", reason }`) keeps typing open and says why sending waits.
54
+ * `textareaRef` focuses the box from outside, e.g. after a new project starts.
52
55
  */
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, }) {
56
+ 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", hints = true, className, }) {
54
57
  const kindOf = useEntityKinds();
55
58
  const t = useStrings("composer", composerStrings);
56
59
  const placeholder = placeholderProp ?? t("placeholder");
57
60
  const label = labelProp ?? t("label");
58
61
  const id = React.useId();
59
62
  const textarea = React.useRef(null);
63
+ React.useImperativeHandle(textareaRef, () => textarea.current, []);
60
64
  // Files dragged over the composer: a count of enter/leave pairs, since child elements fire their own.
61
65
  const [dragDepth, setDragDepth] = React.useState(0);
62
66
  const fileInput = React.useRef(null);
63
67
  const [trigger, setTrigger] = React.useState(null);
64
68
  const [active, setActive] = React.useState(0);
65
- const locked = typeof state === "object" ? state : null;
69
+ const held = typeof state === "object" && state.kind === "held" ? state : null;
70
+ const locked = typeof state === "object" && state.kind !== "held" ? state : null;
66
71
  const canAttach = Boolean(onAttach) && !locked;
67
72
  const busy = state === "sending" || state === "streaming";
68
- const canSend = !locked && !busy && (value.trim().length > 0 || references.length > 0);
73
+ const canSend = !locked && !held && !busy && (value.trim().length > 0 || references.length > 0);
69
74
  const items = React.useMemo(() => {
70
75
  if (!trigger)
71
76
  return [];
@@ -207,11 +212,13 @@ function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, o
207
212
  onAttach(files);
208
213
  if (!e.clipboardData.getData("text/plain"))
209
214
  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) => {
215
+ }, "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
216
  const files = Array.from(e.target.files ?? []);
212
217
  if (files.length)
213
218
  onAttach(files);
214
219
  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 }) }))] })] })] }));
220
+ } }), _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"))] })] })), hints && (
221
+ // Only what this composer can do: @ and / are named only when there's something for them to offer.
222
+ _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"), mentions.length > 0 && (_jsxs(_Fragment, { children: [" · ", _jsx("kbd", { className: "font-sans", children: "@" }), " ", t("hintMention")] })), commands.length > 0 && (_jsxs(_Fragment, { children: [" · ", _jsx("kbd", { className: "font-sans", children: "/" }), " ", t("hintCommand")] }))] })), 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", hints && "@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", hints && "@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
223
  }
217
224
  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 };