@shapesos/clay 0.18.0 → 0.19.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 (60) hide show
  1. package/dist/artifacts.cjs +151 -130
  2. package/dist/artifacts.cjs.map +1 -1
  3. package/dist/artifacts.js +3 -2
  4. package/dist/badge.cjs +70 -0
  5. package/dist/badge.cjs.map +1 -0
  6. package/dist/badge.d.cts +28 -0
  7. package/dist/badge.d.ts +28 -0
  8. package/dist/badge.js +9 -0
  9. package/dist/badge.js.map +1 -0
  10. package/dist/blocks.cjs +434 -176
  11. package/dist/blocks.cjs.map +1 -1
  12. package/dist/blocks.css +1 -1
  13. package/dist/blocks.d.cts +14 -3
  14. package/dist/blocks.d.ts +14 -3
  15. package/dist/blocks.js +17 -5
  16. package/dist/chat.cjs +439 -231
  17. package/dist/chat.cjs.map +1 -1
  18. package/dist/chat.d.cts +1 -1
  19. package/dist/chat.d.ts +1 -1
  20. package/dist/chat.js +10 -5
  21. package/dist/{chunk-JGMN6W72.js → chunk-2X4KUXYP.js} +1 -1
  22. package/dist/chunk-CSTCTO3B.js +161 -0
  23. package/dist/chunk-CSTCTO3B.js.map +1 -0
  24. package/dist/{chunk-QR4VBGK4.js → chunk-DC5KYT2G.js} +65 -198
  25. package/dist/chunk-DC5KYT2G.js.map +1 -0
  26. package/dist/chunk-EIAWZXE4.js +69 -0
  27. package/dist/chunk-EIAWZXE4.js.map +1 -0
  28. package/dist/chunk-EXRNGIQ2.js +41 -0
  29. package/dist/chunk-EXRNGIQ2.js.map +1 -0
  30. package/dist/{chunk-CATIRDZP.js → chunk-FCSYVF4Z.js} +10 -44
  31. package/dist/chunk-FCSYVF4Z.js.map +1 -0
  32. package/dist/{chunk-4MZZH3WX.js → chunk-HU5W7CGE.js} +37 -2
  33. package/dist/chunk-HU5W7CGE.js.map +1 -0
  34. package/dist/chunk-JMHE3Q5K.js +406 -0
  35. package/dist/chunk-JMHE3Q5K.js.map +1 -0
  36. package/dist/copy-button.cjs +439 -0
  37. package/dist/copy-button.cjs.map +1 -0
  38. package/dist/copy-button.d.cts +20 -0
  39. package/dist/copy-button.d.ts +20 -0
  40. package/dist/copy-button.js +12 -0
  41. package/dist/copy-button.js.map +1 -0
  42. package/dist/index.cjs +515 -289
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +4 -2
  45. package/dist/index.d.ts +4 -2
  46. package/dist/index.js +26 -9
  47. package/dist/table.cjs +40 -0
  48. package/dist/table.cjs.map +1 -1
  49. package/dist/table.d.cts +37 -2
  50. package/dist/table.d.ts +37 -2
  51. package/dist/table.js +11 -1
  52. package/dist/{types-BDYPVP2N.d.cts → types-Bhg0_ES8.d.cts} +74 -2
  53. package/dist/{types-BMwa4bPW.d.ts → types-orOQcI2z.d.ts} +74 -2
  54. package/package.json +11 -1
  55. package/dist/chunk-4MZZH3WX.js.map +0 -1
  56. package/dist/chunk-7MY4X4YL.js +0 -250
  57. package/dist/chunk-7MY4X4YL.js.map +0 -1
  58. package/dist/chunk-CATIRDZP.js.map +0 -1
  59. package/dist/chunk-QR4VBGK4.js.map +0 -1
  60. /package/dist/{chunk-JGMN6W72.js.map → chunk-2X4KUXYP.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/blocks/types.ts","../src/components/blocks/artifact-ref-block/artifact-ref-block.tsx","../src/components/blocks/block-services/artifact-ref-block-service.ts","../src/components/blocks/notification-draft-block/types.ts","../src/components/blocks/notification-draft-block/notification-draft-block.tsx","../src/components/blocks/markdown/markdown.tsx","../src/components/blocks/scrollable-table/scrollable-table-styles.ts","../src/components/blocks/scrollable-table/scrollable-table.tsx","../src/components/blocks/text-block/sanitize.ts","../src/components/blocks/text-block/text-block-styles.ts","../src/components/blocks/notification-draft-block/to-text.ts","../src/components/blocks/block-services/notification-draft-block-service.ts","../src/components/blocks/text-block/text-block.tsx","../src/components/blocks/block-services/text-block-service.ts","../src/components/blocks/block-services/index.ts","../src/components/blocks/block/block.tsx"],"sourcesContent":["import type { ComponentType } from \"react\";\n\nimport type { ArtifactCallbacksOf, ArtifactLabelsOf, ArtifactRecord } from \"@/components/artifacts/types\";\nimport type { NotificationDraftBlockRecord } from \"./notification-draft-block/types\";\n\n/** Block-type discriminator constants. Mirrors the wire shape from io-server / shapes-agent. */\nexport const blockTypes = {\n TEXT: \"TEXT\",\n ARTIFACT_REF: \"ARTIFACT_REF\",\n NOTIFICATION_DRAFT: \"NOTIFICATION_DRAFT\",\n} as const;\n\n/** Discriminator for content blocks. */\nexport type BlockType = (typeof blockTypes)[keyof typeof blockTypes];\n\n/** Plain-text content block — rendered as Markdown. */\nexport interface TextBlockRecord {\n /** Block-type discriminator. */\n type: typeof blockTypes.TEXT;\n /** Type-specific data; kept as `payload` to match the wire shape 1:1. */\n payload: {\n /** The Markdown source for this text segment. */\n text: string;\n };\n}\n\n/**\n * Reference-to-an-artifact content block — dispatched to the per-type artifact component.\n *\n * The artifact-domain types (`ArtifactRecord`, `TableArtifactRecord`, etc.) live in\n * `@/components/artifacts/types` so the artifact layer can be consumed independently of the\n * block layer (e.g. by a future surface that renders an artifact outside chat). The block layer\n * imports them here to wrap an artifact in an `ARTIFACT_REF` block.\n *\n * `ArtifactRefBlockRecord` is a *distributed* union over `ArtifactRecord` branches — TS narrows\n * `labels` and `callbacks` to the slice matching `artifact.type` via `ArtifactLabelsOf<T>` /\n * `ArtifactCallbacksOf<T>`. Without this distribution, `Partial<ChartLabels | TableLabels>`\n * collapses to `Partial<ChartLabels> | Partial<TableLabels>` and TS accepts chart-only keys on\n * a TABLE block's payload (the discriminator becomes load-bearing only at runtime, not in the\n * type system).\n */\nexport type ArtifactRefBlockRecord = ArtifactRecord extends infer TArtifact\n ? TArtifact extends ArtifactRecord\n ? {\n /** Block-type discriminator. */\n type: typeof blockTypes.ARTIFACT_REF;\n /** Type-specific data; kept as `payload` to match the wire shape 1:1. */\n payload: {\n /** The artifact's stable identifier, used for keying / future cross-message reuse. */\n artifactId: string;\n /** Inlined artifact metadata — clients fetch the underlying data via `artifact.protectedAsset.presignedUrl`. */\n artifact: TArtifact;\n /**\n * Optional consumer-supplied labels for the rendered artifact's action buttons + status\n * messages — a flat slice narrowed by `artifact.type`. NOT a wire field; the consumer\n * constructs this from their i18n layer when materialising the block-record from the\n * server payload, then passes the augmented block to `<Block>`. The dispatcher forwards\n * the slice directly to the matching renderer — no per-type key wrapping (the block\n * already carries the discriminator).\n *\n * TS catches cross-type assignments here: `block.payload.labels = { downloadCsv: 'X' }`\n * on a `TableArtifactRecord`-typed block is a type error because `downloadCsv` isn't a\n * `TableArtifactLabels` key.\n */\n labels?: Partial<ArtifactLabelsOf<TArtifact>>;\n /**\n * Optional side-effect callbacks for actions on the rendered artifact (e.g. `onDownload`).\n * Flat slice narrowed by `artifact.type` via `ArtifactCallbacksOf<T>`. NOT a wire field\n * — the consumer wires this once to bridge artifact actions to their analytics /\n * observability layer.\n */\n callbacks?: Partial<ArtifactCallbacksOf<TArtifact>>;\n };\n }\n : never\n : never;\n\n/** Discriminated union of all supported content blocks. Additive — new types extend this union. */\nexport type BlockRecord = TextBlockRecord | ArtifactRefBlockRecord | NotificationDraftBlockRecord;\n\n/** Props every concrete block component receives from the dispatcher. */\nexport interface BlockComponentProps<TBlock extends BlockRecord = BlockRecord> {\n /** The block to render. */\n block: TBlock;\n}\n\n/** Per-block-type service — registry entry that pairs a discriminator with its concrete component. */\nexport interface BlockService<TBlock extends BlockRecord = BlockRecord> {\n /** Discriminator value this service handles. */\n type: BlockType;\n /** React component that renders a block of this type. */\n Component: ComponentType<BlockComponentProps<TBlock>>;\n /**\n * Returns a plain-text or markdown string representation of this block suitable for clipboard\n * copy. Called by `copyMessageText` when the user clicks \"copy message\".\n *\n * - `TextBlockService`: returns `block.payload.text` verbatim (markdown preserved).\n * - `ArtifactRefBlockService`: delegates to `ArtifactServices[artifact.type].toClipboardText`.\n * Falls back to `[Artifact: <title>]` when the artifact type has no registered component.\n *\n * Optional so external `BlockService` implementors don't break on upgrade — when omitted, the\n * aggregator treats the block as contributing the empty string (silently skipped). Implementations\n * must be pure, synchronous functions — no async, no hooks, no side effects.\n */\n toClipboardText?: (block: TBlock) => string;\n}\n","import type { ComponentType, ReactNode } from \"react\";\n\nimport { ArtifactServices } from \"@/components/artifacts/artifact-services\";\nimport type { ArtifactRefBlockRecord, BlockComponentProps } from \"../types\";\n\n/**\n * Renders an `ARTIFACT_REF` block by dispatching the inlined artifact to its per-type component.\n *\n * The cast at the seam is necessary because the registry-based dispatch can't statically prove\n * type-T narrowing — but it's not hiding a bug. `ArtifactRefBlockRecord` is a distributed union\n * over `ArtifactRecord` branches (see `blocks/types.ts`), so TS catches wrong-shape labels at the\n * consumer's construction site: a chart-labels bag assigned to a TABLE block's payload is a type\n * error there, not silently forwarded to the wrong renderer here.\n *\n * Unknown artifact types render as `null` — the map only contains types we know how to render,\n * so an older clay client receiving a future artifact type degrades silently rather than\n * crashing the chat.\n */\nexport function ArtifactRefBlock({ block }: BlockComponentProps<ArtifactRefBlockRecord>): ReactNode {\n const { artifact, labels, callbacks } = block.payload;\n const entry = ArtifactServices[artifact.type];\n if (!entry) return null;\n // The registry stores `ArtifactService<ArtifactRecord>` (the union), so `entry.Component` is\n // `ComponentType<ArtifactServiceProps<ArtifactRecord>>`. The labels/callbacks props in the\n // narrower per-type `ArtifactServiceProps<TArtifact>` are `Partial<ArtifactLabelsOf<TArtifact>>`\n // which the union doesn't satisfy positionally. Cast forwards the values verbatim — the\n // runtime invariant is provably correct because the distributed `ArtifactRefBlockRecord` type\n // enforces it at construction.\n const Component = entry.Component as ComponentType<{\n artifact: typeof artifact;\n labels?: typeof labels;\n callbacks?: typeof callbacks;\n }>;\n return <Component artifact={artifact} labels={labels} callbacks={callbacks} />;\n}\n","import { artifactToClipboardText } from \"@/components/artifacts/artifact-services\";\nimport { ArtifactRefBlock } from \"../artifact-ref-block/artifact-ref-block\";\nimport { blockTypes } from \"../types\";\nimport type { ArtifactRefBlockRecord, BlockService } from \"../types\";\n\nexport const ArtifactRefBlockService: BlockService<ArtifactRefBlockRecord> = {\n type: blockTypes.ARTIFACT_REF,\n Component: ArtifactRefBlock,\n /**\n * Delegates to the registered artifact component's `toClipboardText`. Falls back to\n * `[Artifact: <title>]` via `artifactToClipboardText`'s own fallback when the artifact type\n * has no registered component, so copy never silently produces an empty string.\n */\n toClipboardText: (block) => artifactToClipboardText(block.payload.artifact),\n};\n","import { blockTypes } from \"../types\";\n\n/** Delivery channel the drafted notification targets. */\nexport const notificationChannels = {\n /** Branded email — renders a From/subject header. */\n EMAIL: \"email\",\n /** Slack DM — renders as a chat bubble, no subject. */\n SLACK: \"slack\",\n /** Microsoft Teams DM — renders as a chat bubble, no subject. */\n TEAMS: \"teams\",\n} as const;\n\n/** One of the supported notification channels. */\nexport type NotificationChannel = (typeof notificationChannels)[keyof typeof notificationChannels];\n\n/** Which recipient address the email is sent to (email channel only). */\nexport const notificationEmailTypes = {\n /** Recipient's work email. */\n WORK: \"work\",\n /** Recipient's personal email. */\n PERSONAL: \"personal\",\n} as const;\n\n/** One of the supported email address types. */\nexport type NotificationEmailType = (typeof notificationEmailTypes)[keyof typeof notificationEmailTypes];\n\n/**\n * A single resolved recipient of the drafted notification — the block's one source of recipient\n * truth. The summary chips and the detail table are both projected from this (count, chip labels,\n * and fallback count are all derived, never transmitted separately).\n */\nexport interface NotificationRecipient {\n /** Stable employee identifier — used as the React key. */\n employeeId: string;\n /** Display name; falls back to `email` for the chip label when absent. */\n name?: string | null;\n /** Resolved delivery address (work or personal, already resolved server-side). */\n email?: string | null;\n /**\n * Human-readable label for the channel this recipient is reached on (e.g. \"Email\", \"Slack\").\n * Client-provided so clay isn't limited to a fixed channel set and holds no channel copy of its\n * own — the consumer passes whatever label fits its use case in the payload.\n */\n channelLabel: string;\n /** Whether this recipient is unreachable on the chosen channel and falls back to email. */\n fallback: boolean;\n}\n\n/**\n * Wire payload for a drafted notification preview — populated by the agent from the\n * `validate_send_notification` dry-run. Presentation only: this block previews what will be\n * sent, it is NOT the approval/confirmation control.\n */\nexport interface NotificationDraftPayload {\n /** Delivery channel; drives whether a subject title is shown (email only). */\n channel: NotificationChannel;\n /** Which address the email is sent to. Email channel only; ignored otherwise. */\n emailType?: NotificationEmailType;\n /** Email subject line. Email channel only; absent for slack/teams. */\n subject?: string;\n /** Message body (markdown source). Rendered as markdown, identical to chat assistant text. */\n body: string;\n /** Resolved From display name, e.g. \"Acme HR via Shapes\". Email channel only. */\n fromDisplay?: string;\n /**\n * Every resolved recipient. Clay derives the count, the summary chips (`name ?? email`, with a\n * \"+N\" overflow), and the fallback count from this single array.\n */\n recipients: NotificationRecipient[];\n}\n\n/** A NOTIFICATION_DRAFT content block — wraps a {@link NotificationDraftPayload}. */\nexport interface NotificationDraftBlockRecord {\n /** Block-type discriminator. */\n type: typeof blockTypes.NOTIFICATION_DRAFT;\n /** Type-specific data; kept as `payload` to match the wire shape 1:1. */\n payload: NotificationDraftPayload;\n}\n","import type { ReactNode } from \"react\";\nimport { useState } from \"react\";\nimport { IconChevronDown, IconFlask2Filled } from \"@tabler/icons-react\";\n\nimport { Badge } from \"@/components/badge\";\nimport { CopyButton } from \"@/components/copy-button\";\nimport { Icon } from \"@/components/icon\";\nimport {\n DataTable,\n DATA_TABLE_CELL_CLASS,\n DATA_TABLE_HEAD_CLASS,\n DATA_TABLE_ROW_CLASS,\n TableCell,\n TableHead,\n TableRow,\n} from \"@/components/table\";\nimport { Markdown } from \"../markdown/markdown\";\nimport type { BlockComponentProps } from \"../types\";\nimport { notificationDraftToText } from \"./to-text\";\nimport { type NotificationDraftBlockRecord, type NotificationRecipient, notificationChannels } from \"./types\";\n\n/** Number of recipient chips shown before collapsing the rest into a \"+N\" overflow chip. */\nconst CHIP_LIMIT = 3;\n\n/** Max recipient-table height (px) before it scrolls, engaging the sticky header + scroll shadow. */\nconst RECIPIENTS_MAX_HEIGHT = 320;\n\n/** Chip label for a recipient — name, falling back to email, then the id. */\nfunction chipLabel(recipient: NotificationRecipient): string {\n return recipient.name ?? recipient.email ?? recipient.employeeId;\n}\n\n/** Channel cell text — the client-provided channel label, marked when it's a fallback. */\nfunction channelCellText(recipient: NotificationRecipient): string {\n return recipient.fallback ? `${recipient.channelLabel} (fallback)` : recipient.channelLabel;\n}\n\n/** Small muted form-field caption sitting tightly above the value it labels. */\nfunction FieldLabel({ children }: { children: string }): ReactNode {\n return <span className=\"text-geist-label-caption-medium tracking-wide text-brown-60\">{children}</span>;\n}\n\n/** Collapsed summary: the first few recipients as chips with a \"+N\" overflow. */\nfunction RecipientChips({ recipients }: { recipients: NotificationRecipient[] }): ReactNode {\n const shown = recipients.slice(0, CHIP_LIMIT);\n const overflow = recipients.length - shown.length;\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {shown.map((recipient) => (\n <Badge key={recipient.employeeId} variant=\"outline\">\n {chipLabel(recipient)}\n </Badge>\n ))}\n {overflow > 0 && <Badge variant=\"subtle\">{`+${overflow}`}</Badge>}\n </div>\n );\n}\n\n/** Expanded detail: one row per recipient with name, email, and resolved channel (fallback tagged). */\nfunction RecipientsTable({ recipients }: { recipients: NotificationRecipient[] }): ReactNode {\n return (\n <DataTable\n maxBodyHeight={RECIPIENTS_MAX_HEIGHT}\n header={\n <TableRow className=\"hover:bg-transparent\">\n <TableHead scope=\"col\" className={DATA_TABLE_HEAD_CLASS}>\n Name\n </TableHead>\n <TableHead scope=\"col\" className={DATA_TABLE_HEAD_CLASS}>\n Email\n </TableHead>\n <TableHead scope=\"col\" className={DATA_TABLE_HEAD_CLASS}>\n Channel\n </TableHead>\n </TableRow>\n }\n >\n {recipients.map((recipient) => (\n <TableRow key={recipient.employeeId} className={DATA_TABLE_ROW_CLASS}>\n <TableCell className={DATA_TABLE_CELL_CLASS}>{recipient.name ?? \"—\"}</TableCell>\n <TableCell className={DATA_TABLE_CELL_CLASS}>{recipient.email ?? \"—\"}</TableCell>\n <TableCell className={DATA_TABLE_CELL_CLASS}>\n <Badge tone={recipient.fallback ? \"warning\" : \"neutral\"} variant=\"subtle\">\n {channelCellText(recipient)}\n </Badge>\n </TableCell>\n </TableRow>\n ))}\n </DataTable>\n );\n}\n\n/**\n * Preview of a drafted notification — one uniform card for every channel. Header carries a \"Labs\"\n * badge (experimental) and a copy-to-clipboard button; the body renders as markdown. Recipients are\n * a single source of truth shown two ways, never both at once: collapsed as summary chips, or\n * expanded (via the \"Details\" toggle) as a Name / Email / Channel table matching the table artifact.\n * The only cross-channel difference is a bold subject title, shown for email. Presentation only.\n */\nexport function NotificationDraftBlock({ block }: BlockComponentProps<NotificationDraftBlockRecord>): ReactNode {\n const { channel, subject, body, recipients } = block.payload;\n const [detailsOpen, setDetailsOpen] = useState(false);\n // Subject is the sole cross-channel difference — email only (§16); slack/teams have no subject line.\n const showSubject = channel === notificationChannels.EMAIL && subject;\n\n return (\n <div className=\"overflow-hidden rounded-2xl border border-brown-30 bg-white\">\n <div className=\"flex flex-col gap-3 border-b border-brown-30 px-4 py-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-geist-body-s-semi-bold text-brown-100\">Notification draft</span>\n <Badge tone=\"violet\" variant=\"subtle\" size=\"sm\" className=\"gap-1 py-1\">\n <Icon icon={IconFlask2Filled} size={12} />\n Labs\n </Badge>\n </div>\n <CopyButton text={notificationDraftToText(block.payload)} copyLabel=\"Copy draft\" copiedLabel=\"Copied\" />\n </div>\n\n {showSubject && (\n <div className=\"flex flex-col gap-0.5\">\n <FieldLabel>Title</FieldLabel>\n <p className=\"text-geist-body-s-medium text-brown-100\">{subject}</p>\n </div>\n )}\n\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <FieldLabel>{`Recipients (${recipients.length})`}</FieldLabel>\n <button\n type=\"button\"\n aria-expanded={detailsOpen}\n onClick={() => setDetailsOpen((open) => !open)}\n className=\"inline-flex items-center gap-1 text-geist-label-caption-medium text-brown-70 transition-colors hover:text-brown-100\"\n >\n Details\n <Icon icon={IconChevronDown} size={14} className={detailsOpen ? \"rotate-180\" : undefined} />\n </button>\n </div>\n {detailsOpen ? <RecipientsTable recipients={recipients} /> : <RecipientChips recipients={recipients} />}\n </div>\n </div>\n\n <div className=\"px-4 py-3\">\n <Markdown>{body}</Markdown>\n </div>\n </div>\n );\n}\n","import type { ReactNode } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport remarkBreaks from \"remark-breaks\";\nimport remarkGfm from \"remark-gfm\";\n\nimport { ScrollableTable } from \"../scrollable-table/scrollable-table\";\nimport { sanitize } from \"../text-block/sanitize\";\nimport { TextBlockWrapper } from \"../text-block/text-block-styles\";\n\nconst markdownComponents = {\n table: ((tableProps: { node?: unknown; children?: ReactNode }) => (\n <ScrollableTable {...tableProps} />\n )) as React.ComponentType<unknown>,\n};\n\n/**\n * Shared markdown renderer for chat content blocks — GFM (tables, strikethrough) + single-newline\n * line breaks, tables wrapped in a scrollable container, prose typography from `TextBlockWrapper`.\n * The single source of the markdown setup so every block (TextBlock, NotificationDraftBlock) renders\n * assistant text identically.\n */\nexport function Markdown({ children }: { children: string }): ReactNode {\n return (\n <TextBlockWrapper>\n <ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]} components={markdownComponents}>\n {sanitize(children)}\n </ReactMarkdown>\n </TextBlockWrapper>\n );\n}\n","import styled from \"styled-components\";\n\nexport const TableContainer = styled.div`\n position: relative;\n margin-block: 8px 16px;\n`;\n\nexport const TableScroll = styled.div`\n width: 100%;\n overflow-x: auto;\n overflow-y: hidden;\n`;\n","import { type ReactNode } from \"react\";\n\nimport { TableContainer, TableScroll } from \"./scrollable-table-styles\";\n\n/** Props for the ScrollableTable component. */\ninterface ScrollableTableProps {\n /** Unused node prop from react-markdown. */\n node?: unknown;\n /** Table children elements. */\n children?: ReactNode;\n}\n\n/** Renders a markdown table inside a horizontally scrollable wrapper. Read-only — actions live on artifact-tables. */\nexport function ScrollableTable({ node: _node, ...props }: ScrollableTableProps): ReactNode {\n return (\n <TableContainer>\n <TableScroll>\n <table {...props} />\n </TableScroll>\n </TableContainer>\n );\n}\n","// Strips io-server's `<!-- table-title: ... -->` HTML-comment metadata before rendering.\n// The marker is emitted by the server's CSV-export pipeline so the export button can label\n// the file; it has no place in the visual output. If the server-side format changes\n// (prefix, spacing, closing-tag style), this filter silently stops matching and the\n// raw HTML comment leaks into rendered Markdown — keep this contract aligned with io-server.\nexport function sanitize(text: string): string {\n return text\n .split(\"\\n\")\n .filter((line) => {\n const t = line.trim();\n return !(t.startsWith(\"<!-- table-title:\") && t.endsWith(\"-->\"));\n })\n .join(\"\\n\");\n}\n","import styled from \"styled-components\";\n\nimport { colors } from \"@/tokens/colors\";\nimport { typographyMixin, typographyTypes } from \"@/tokens/typography\";\n\nexport const TextBlockWrapper = styled.div`\n ${typographyMixin(typographyTypes.GEIST_BODY_S_REGULAR)};\n color: ${colors[\"brown-100\"]};\n word-break: break-word;\n\n & h1 {\n ${typographyMixin(typographyTypes.GEIST_HEADING_S_BOLD)};\n margin-block: 20px;\n }\n\n & h2 {\n ${typographyMixin(typographyTypes.GEIST_BODY_L_SEMI_BOLD)};\n margin-block: 12px;\n }\n\n & h3,\n & h4,\n & h5,\n & h6 {\n ${typographyMixin(typographyTypes.GEIST_BODY_M_SEMI_BOLD)};\n margin-block: 8px;\n }\n\n & strong {\n ${typographyMixin(typographyTypes.GEIST_BODY_S_SEMI_BOLD)};\n }\n\n & p {\n margin-block: 0px;\n }\n\n & p + p {\n margin-block: 8px 0px;\n }\n\n & ul,\n & ol {\n margin-block: 16px;\n padding-left: 20px;\n }\n\n & li + li {\n margin-block: 4px;\n }\n\n & ul {\n list-style-type: disc;\n }\n\n & ul ul {\n list-style-type: circle;\n }\n\n & ul ul ul {\n list-style-type: square;\n }\n\n & ol {\n list-style-type: decimal;\n }\n\n & ol ol {\n list-style-type: lower-alpha;\n }\n\n & ol ol ol {\n list-style-type: lower-roman;\n }\n\n & a {\n color: inherit;\n text-decoration-line: underline;\n text-decoration-style: dotted;\n text-decoration-thickness: auto;\n text-underline-offset: auto;\n text-underline-position: from-font;\n transition: color 75ms ease-in-out;\n\n &:hover {\n color: ${colors[\"blue-600\"]};\n }\n }\n\n & hr {\n border: none;\n border-top: 1px solid ${colors[\"brown-40\"]};\n margin-block: 20px;\n }\n\n & table {\n margin: 0;\n width: 100%;\n border-collapse: collapse;\n }\n\n & th,\n & td {\n border: none;\n border-bottom: 1px solid ${colors[\"brown-40\"]};\n text-align: left;\n }\n\n & th {\n ${typographyMixin(typographyTypes.GEIST_LABEL_CAPTION_MEDIUM)};\n padding: 8px;\n }\n\n & td {\n ${typographyMixin(typographyTypes.GEIST_BODY_XS_MEDIUM)};\n padding: 16px 8px;\n min-width: 150px;\n width: 1%;\n }\n\n & td strong {\n font-weight: inherit;\n font-size: inherit;\n line-height: inherit;\n }\n\n & blockquote {\n margin-block: 4px;\n margin-inline: 0px;\n padding-left: 16px;\n border-left: 4px solid ${colors[\"brown-40\"]};\n color: ${colors[\"brown-80\"]};\n }\n`;\n","import { markdownToPlainText } from \"@/utils/markdown-to-plain-text\";\nimport { notificationChannels } from \"./types\";\nimport type { NotificationDraftPayload } from \"./types\";\n\n/**\n * Plain-text projection of a drafted notification: subject + body for email (which has a subject),\n * body alone for slack/teams. The body is markdown (§16), so it's run through `markdownToPlainText`\n * — matching `TextBlockService.toClipboardText` — so a copied draft never carries `**`, list\n * markers, etc. into plain-text paste targets. Shared by the block-service's `toClipboardText` and\n * the in-block copy button so both copy the same text.\n */\nexport function notificationDraftToText(payload: NotificationDraftPayload): string {\n const { channel, subject, body } = payload;\n const text = markdownToPlainText(body);\n return channel === notificationChannels.EMAIL && subject ? `${subject}\\n\\n${text}` : text;\n}\n","import { NotificationDraftBlock } from \"../notification-draft-block/notification-draft-block\";\nimport { notificationDraftToText } from \"../notification-draft-block/to-text\";\nimport type { NotificationDraftBlockRecord } from \"../notification-draft-block/types\";\nimport { blockTypes } from \"../types\";\nimport type { BlockService } from \"../types\";\n\nexport const NotificationDraftBlockService: BlockService<NotificationDraftBlockRecord> = {\n type: blockTypes.NOTIFICATION_DRAFT,\n Component: NotificationDraftBlock,\n /**\n * Plain-text clipboard form of the drafted notification — shared with the in-block copy button\n * via {@link notificationDraftToText} so both copy the same text.\n */\n toClipboardText: (block) => notificationDraftToText(block.payload),\n};\n","import type { ReactNode } from \"react\";\n\nimport { Markdown } from \"../markdown/markdown\";\nimport type { BlockComponentProps, TextBlockRecord } from \"../types\";\n\n/** Renders a TEXT block as Markdown with GFM (tables, strikethrough) and line breaks. */\nexport function TextBlock({ block }: BlockComponentProps<TextBlockRecord>): ReactNode {\n return <Markdown>{block.payload.text}</Markdown>;\n}\n","import { markdownToPlainText } from \"@/utils/markdown-to-plain-text\";\nimport { TextBlock } from \"../text-block/text-block\";\nimport { blockTypes } from \"../types\";\nimport type { BlockService, TextBlockRecord } from \"../types\";\n\nexport const TextBlockService: BlockService<TextBlockRecord> = {\n type: blockTypes.TEXT,\n Component: TextBlock,\n /**\n * Converts the block's markdown source to plain text so the final clipboard value is clean\n * when pasted into tools that don't render markdown. Each block type owns its own clipboard\n * conversion so `useCopyToClipboard` can write text verbatim without a second pass.\n */\n toClipboardText: (block) => markdownToPlainText(block.payload.text),\n};\n","import { blockTypes, type BlockService, type BlockType } from \"../types\";\nimport { ArtifactRefBlockService } from \"./artifact-ref-block-service\";\nimport { NotificationDraftBlockService } from \"./notification-draft-block-service\";\nimport { TextBlockService } from \"./text-block-service\";\n\n// `BlockService<TBlock>` is invariant on `TBlock` via React's `ComponentType` (props are\n// contravariant). The concrete services overlap structurally with the base `BlockService` type\n// enough for a direct `as BlockService` cast. The registry dispatches by runtime discriminator —\n// `BlockServices[block.type]` always resolves to the matching concrete service — so the cast is sound.\nexport const BlockServices: Record<BlockType, BlockService> = {\n [blockTypes.TEXT]: TextBlockService as BlockService,\n [blockTypes.ARTIFACT_REF]: ArtifactRefBlockService as BlockService,\n [blockTypes.NOTIFICATION_DRAFT]: NotificationDraftBlockService as BlockService,\n};\n","import type { ReactNode } from \"react\";\n\nimport { BlockServices } from \"../block-services\";\nimport type { BlockRecord } from \"../types\";\n\n/** Props for the Block dispatcher. */\ninterface BlockProps {\n /** The block to render. Dispatched to the registered concrete component for its `type`. */\n block: BlockRecord;\n}\n\n/**\n * Dispatches a single block to its registered concrete component. Unknown types render as a no-op.\n *\n * Generic by design: per-block-type extras (e.g. localized artifact labels for `ARTIFACT_REF`)\n * live on the block's `payload`, not as sibling props here. That keeps the dispatcher's contract\n * shallow — third-party `BlockService` implementors only need to handle `{ block }` — and lets\n * the consumer materialise a fully-decorated block once (from the server payload + their i18n\n * layer) instead of threading sibling props through every dispatch site.\n */\nexport function Block({ block }: BlockProps): ReactNode {\n const service = BlockServices[block.type];\n if (!service) return null;\n const { Component } = service;\n return <Component block={block} />;\n}\n"],"mappings":"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","names":["jsx","styled","styled","jsx","jsx","jsx","jsx"]}
@@ -0,0 +1,439 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/components/copy-button/index.ts
31
+ var copy_button_exports = {};
32
+ __export(copy_button_exports, {
33
+ CopyButton: () => CopyButton
34
+ });
35
+ module.exports = __toCommonJS(copy_button_exports);
36
+
37
+ // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
38
+ var import_react = require("react");
39
+
40
+ // node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs
41
+ var defaultAttributes = {
42
+ outline: {
43
+ xmlns: "http://www.w3.org/2000/svg",
44
+ width: 24,
45
+ height: 24,
46
+ viewBox: "0 0 24 24",
47
+ fill: "none",
48
+ stroke: "currentColor",
49
+ strokeWidth: 2,
50
+ strokeLinecap: "round",
51
+ strokeLinejoin: "round"
52
+ },
53
+ filled: {
54
+ xmlns: "http://www.w3.org/2000/svg",
55
+ width: 24,
56
+ height: 24,
57
+ viewBox: "0 0 24 24",
58
+ fill: "currentColor",
59
+ stroke: "none"
60
+ }
61
+ };
62
+
63
+ // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
64
+ var createReactComponent = (type, iconName, iconNamePascal, iconNode) => {
65
+ const Component = (0, import_react.forwardRef)(
66
+ ({ color = "currentColor", size = 24, stroke = 2, title, className, children, ...rest }, ref) => (0, import_react.createElement)(
67
+ "svg",
68
+ {
69
+ ref,
70
+ ...defaultAttributes[type],
71
+ width: size,
72
+ height: size,
73
+ className: [`tabler-icon`, `tabler-icon-${iconName}`, className].join(" "),
74
+ ...type === "filled" ? {
75
+ fill: color
76
+ } : {
77
+ strokeWidth: stroke,
78
+ stroke: color
79
+ },
80
+ ...rest
81
+ },
82
+ [
83
+ title && (0, import_react.createElement)("title", { key: "svg-title" }, title),
84
+ ...iconNode.map(([tag, attrs]) => (0, import_react.createElement)(tag, attrs)),
85
+ ...Array.isArray(children) ? children : [children]
86
+ ]
87
+ )
88
+ );
89
+ Component.displayName = `${iconNamePascal}`;
90
+ return Component;
91
+ };
92
+
93
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs
94
+ var __iconNode = [["path", { "d": "M5 12l5 5l10 -10", "key": "svg-0" }]];
95
+ var IconCheck = createReactComponent("outline", "check", "Check", __iconNode);
96
+
97
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconCopy.mjs
98
+ var __iconNode2 = [["path", { "d": "M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666", "key": "svg-0" }], ["path", { "d": "M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1", "key": "svg-1" }]];
99
+ var IconCopy = createReactComponent("outline", "copy", "Copy", __iconNode2);
100
+
101
+ // src/components/chat/hooks/use-copy-to-clipboard.ts
102
+ var import_react2 = require("react");
103
+
104
+ // src/utils/clipboard.ts
105
+ function copyToClipboard(text, options) {
106
+ navigator.clipboard.writeText(text).then(
107
+ () => options?.onSuccess?.(),
108
+ (error) => options?.onFailure?.(error)
109
+ );
110
+ }
111
+
112
+ // src/components/chat/hooks/use-copy-to-clipboard.ts
113
+ var RESET_DELAY_MS = 2e3;
114
+ function useCopyToClipboard() {
115
+ const [isCopied, setIsCopied] = (0, import_react2.useState)(false);
116
+ const timeoutRef = (0, import_react2.useRef)(null);
117
+ (0, import_react2.useEffect)(() => {
118
+ return () => {
119
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
120
+ };
121
+ }, []);
122
+ const copy = (0, import_react2.useCallback)((text) => {
123
+ copyToClipboard(text, {
124
+ onSuccess: () => {
125
+ setIsCopied(true);
126
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
127
+ timeoutRef.current = setTimeout(() => {
128
+ setIsCopied(false);
129
+ timeoutRef.current = null;
130
+ }, RESET_DELAY_MS);
131
+ }
132
+ });
133
+ }, []);
134
+ return { isCopied, copy };
135
+ }
136
+
137
+ // src/components/icon-button/icon-button.tsx
138
+ var import_react4 = require("react");
139
+
140
+ // src/components/icon/icon.tsx
141
+ var import_react3 = require("react");
142
+
143
+ // src/components/icon/icon-styles.ts
144
+ var import_styled_components = __toESM(require("styled-components"), 1);
145
+ var STROKE_WIDTH_BY_SIZE = {
146
+ 12: 1.4,
147
+ 14: 1.4,
148
+ 16: 1.8,
149
+ 18: 1.8,
150
+ 20: 1.8,
151
+ 24: 1.8
152
+ };
153
+ var DEFAULT_STROKE_WIDTH = 1.8;
154
+ function getStrokeWidth(size) {
155
+ return STROKE_WIDTH_BY_SIZE[size] ?? DEFAULT_STROKE_WIDTH;
156
+ }
157
+ var IconWrapper = import_styled_components.default.span`
158
+ display: inline-flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ color: ${({ $color }) => $color ?? "currentColor"};
162
+ flex-shrink: 0;
163
+ `;
164
+
165
+ // src/components/icon/icon.tsx
166
+ var import_jsx_runtime = require("react/jsx-runtime");
167
+ var DEFAULT_SIZE = 16;
168
+ var Icon = (0, import_react3.forwardRef)(function Icon2({ icon: IconComponent, size = DEFAULT_SIZE, color, className, "aria-label": ariaLabel }, ref) {
169
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
170
+ IconWrapper,
171
+ {
172
+ ref,
173
+ className,
174
+ $color: color,
175
+ "aria-label": ariaLabel,
176
+ role: ariaLabel ? "img" : void 0,
177
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconComponent, { width: size, height: size, strokeWidth: getStrokeWidth(size) })
178
+ }
179
+ );
180
+ });
181
+
182
+ // src/components/icon-button/icon-button-styles.ts
183
+ var import_styled_components2 = __toESM(require("styled-components"), 1);
184
+
185
+ // src/tokens/colors.ts
186
+ var colors = {
187
+ // White
188
+ white: "#FFFFFF",
189
+ "white-alpha-85": "rgba(255, 255, 255, 0.85)",
190
+ "white-alpha-40": "rgba(255, 255, 255, 0.40)",
191
+ // Brown
192
+ "brown-10": "#FAF9F8",
193
+ "brown-20": "#F5F3F0",
194
+ "brown-30": "#EEEBE5",
195
+ "brown-40": "#E6E2DA",
196
+ "brown-50": "#CFCBC4",
197
+ "brown-60": "#A19E99",
198
+ "brown-70": "#73716D",
199
+ "brown-80": "#5C5A57",
200
+ "brown-90": "#2E2D2C",
201
+ "brown-100": "#171716",
202
+ "brown-alpha-12": "rgba(23, 23, 22, 0.12)",
203
+ "brown-alpha-20": "rgba(23, 23, 22, 0.20)",
204
+ "brown-alpha-55": "rgba(23, 23, 22, 0.55)",
205
+ "brown-alpha-70": "rgba(23, 23, 22, 0.70)",
206
+ "brown-alpha-80": "rgba(23, 23, 22, 0.80)",
207
+ "brown-alpha-90": "rgba(23, 23, 22, 0.90)",
208
+ // Fuchsia
209
+ "fuchsia-50": "#FAF5FF",
210
+ "fuchsia-300": "#F0ABFC",
211
+ "fuchsia-500": "#D946EF",
212
+ "fuchsia-600": "#C026D3",
213
+ "fuchsia-800": "#86198F",
214
+ // Pink
215
+ "pink-50": "#FDF2F8",
216
+ "pink-400": "#F472B6",
217
+ "pink-600": "#DB2777",
218
+ "pink-800": "#9D174D",
219
+ // Violet
220
+ "violet-50": "#F5F3FF",
221
+ "violet-400": "#A78BFA",
222
+ "violet-600": "#7C3AED",
223
+ "violet-800": "#5B21B6",
224
+ // Indigo
225
+ "indigo-50": "#EEF2FF",
226
+ "indigo-400": "#818CF8",
227
+ "indigo-500": "#6366F1",
228
+ "indigo-600": "#4F46E5",
229
+ "indigo-800": "#3730A3",
230
+ // Cyan
231
+ "cyan-50": "#E7FEFF",
232
+ "cyan-300": "#67E8F9",
233
+ "cyan-600": "#0891B2",
234
+ "cyan-900": "#164E63",
235
+ // Teal
236
+ "teal-50": "#EBFDF9",
237
+ "teal-300": "#5EEAD4",
238
+ "teal-600": "#0D9488",
239
+ // Rose
240
+ "rose-400": "#FB7185",
241
+ // Purple
242
+ "purple-400": "#C084FC",
243
+ // Blue
244
+ "blue-50": "#EFF6FF",
245
+ "blue-100": "#DBEAFE",
246
+ "blue-300": "#93C5FD",
247
+ "blue-400": "#60A5FA",
248
+ "blue-600": "#2563EB",
249
+ "blue-800": "#1E40AF",
250
+ // Sky
251
+ "sky-300": "#7DD3FC",
252
+ // Green
253
+ "green-50": "#ECF9F0",
254
+ "green-100": "#D4F1D9",
255
+ "green-400": "#3DC269",
256
+ "green-700": "#277C43",
257
+ "green-800": "#1C5930",
258
+ // Orange
259
+ "orange-50": "#FFF7ED",
260
+ "orange-100": "#FFEDD5",
261
+ "orange-200": "#FED7AA",
262
+ "orange-500": "#F97316",
263
+ "orange-600": "#EA580C",
264
+ // Red
265
+ "red-50": "#FEF2F2",
266
+ "red-100": "#FEE2E2",
267
+ "red-200": "#FECACA",
268
+ "red-400": "#F7776C",
269
+ "red-500": "#EF4444",
270
+ "red-600": "#DC2626",
271
+ // Deck
272
+ "deck-canvas": "#faf8f5"
273
+ };
274
+
275
+ // src/components/icon-button/icon-button-styles.ts
276
+ var PADDING = {
277
+ short: {
278
+ small: "4px",
279
+ medium: "6px"
280
+ },
281
+ long: {
282
+ small: "4px 6px",
283
+ medium: "6px 10px"
284
+ }
285
+ };
286
+ var BORDER_RADIUS = {
287
+ small: 6,
288
+ medium: 10
289
+ };
290
+ var intentStyles = {
291
+ secondary: ({ $isSelected }) => import_styled_components2.css`
292
+ background: ${$isSelected ? colors["brown-40"] : "transparent"};
293
+ color: ${colors["brown-100"]};
294
+
295
+ &:hover:not(:disabled) {
296
+ background: ${$isSelected ? colors["brown-40"] : colors["brown-20"]};
297
+ }
298
+
299
+ &:active:not(:disabled) {
300
+ background: ${colors["brown-40"]};
301
+ }
302
+
303
+ &:disabled {
304
+ color: ${colors["brown-50"]};
305
+ background: transparent;
306
+ }
307
+ `,
308
+ primary: () => import_styled_components2.css`
309
+ background: ${colors["brown-100"]};
310
+ color: ${colors.white};
311
+
312
+ &:hover:not(:disabled) {
313
+ background: ${colors["brown-90"]};
314
+ }
315
+
316
+ &:active:not(:disabled) {
317
+ background: ${colors["brown-90"]};
318
+ }
319
+
320
+ &:disabled {
321
+ color: ${colors["brown-50"]};
322
+ background: ${colors["brown-20"]};
323
+ }
324
+ `,
325
+ error: () => import_styled_components2.css`
326
+ background: ${colors["red-500"]};
327
+ color: ${colors.white};
328
+
329
+ &:hover:not(:disabled) {
330
+ background: ${colors["red-600"]};
331
+ }
332
+
333
+ &:active:not(:disabled) {
334
+ background: ${colors["red-600"]};
335
+ }
336
+
337
+ &:disabled {
338
+ color: ${colors["red-200"]};
339
+ background: ${colors["red-100"]};
340
+ }
341
+ `
342
+ };
343
+ var Button = import_styled_components2.default.button`
344
+ display: inline-flex;
345
+ align-items: center;
346
+ justify-content: center;
347
+ padding: ${({ $size, $variant }) => PADDING[$variant][$size]};
348
+ border: none;
349
+ border-radius: ${({ $size }) => BORDER_RADIUS[$size]}px;
350
+ cursor: pointer;
351
+ transition: background-color 100ms ease;
352
+
353
+ ${({ $intent, $isSelected }) => intentStyles[$intent]({ $isSelected })}
354
+
355
+ &:disabled {
356
+ cursor: not-allowed;
357
+ }
358
+ `;
359
+
360
+ // src/components/icon-button/icon-button.tsx
361
+ var import_jsx_runtime2 = require("react/jsx-runtime");
362
+ var ICON_SIZE_BY_BUTTON_SIZE = {
363
+ small: 16,
364
+ medium: 20
365
+ };
366
+ var IconButton = (0, import_react4.forwardRef)(function IconButton2({
367
+ icon,
368
+ size = "small",
369
+ variant = "short",
370
+ intent = "secondary",
371
+ isSelected = false,
372
+ disabled = false,
373
+ onClick,
374
+ className,
375
+ id,
376
+ as,
377
+ "aria-label": ariaLabel,
378
+ ...rest
379
+ }, ref) {
380
+ const isNativeButton = as === void 0 || as === "button";
381
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
382
+ Button,
383
+ {
384
+ ref,
385
+ className,
386
+ id,
387
+ as,
388
+ $size: size,
389
+ $variant: variant,
390
+ $intent: intent,
391
+ $isSelected: isSelected,
392
+ disabled,
393
+ onClick: disabled ? void 0 : onClick,
394
+ "aria-label": ariaLabel,
395
+ ...isNativeButton ? { type: "button" } : {},
396
+ ...rest,
397
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { icon, size: ICON_SIZE_BY_BUTTON_SIZE[size] })
398
+ }
399
+ );
400
+ });
401
+
402
+ // src/components/copy-button/copy-button.tsx
403
+ var import_jsx_runtime3 = require("react/jsx-runtime");
404
+ function CopyButton({
405
+ text,
406
+ copyLabel = "Copy",
407
+ copiedLabel = "Copied",
408
+ className
409
+ }) {
410
+ const { isCopied, copy } = useCopyToClipboard();
411
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
412
+ IconButton,
413
+ {
414
+ icon: isCopied ? IconCheck : IconCopy,
415
+ onClick: () => copy(text),
416
+ "aria-label": isCopied ? copiedLabel : copyLabel,
417
+ className
418
+ }
419
+ );
420
+ }
421
+ // Annotate the CommonJS export names for ESM import in node:
422
+ 0 && (module.exports = {
423
+ CopyButton
424
+ });
425
+ /*! Bundled license information:
426
+
427
+ @tabler/icons-react/dist/esm/defaultAttributes.mjs:
428
+ @tabler/icons-react/dist/esm/createReactComponent.mjs:
429
+ @tabler/icons-react/dist/esm/icons/IconCheck.mjs:
430
+ @tabler/icons-react/dist/esm/icons/IconCopy.mjs:
431
+ @tabler/icons-react/dist/esm/tabler-icons-react.mjs:
432
+ (**
433
+ * @license @tabler/icons-react v3.37.1 - MIT
434
+ *
435
+ * This source code is licensed under the MIT license.
436
+ * See the LICENSE file in the root directory of this source tree.
437
+ *)
438
+ */
439
+ //# sourceMappingURL=copy-button.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/copy-button/index.ts","../node_modules/@tabler/icons-react/src/defaultAttributes.ts","../node_modules/@tabler/icons-react/src/createReactComponent.ts","../node_modules/@tabler/icons-react/src/icons/IconCheck.ts","../node_modules/@tabler/icons-react/src/icons/IconCopy.ts","../src/components/chat/hooks/use-copy-to-clipboard.ts","../src/utils/clipboard.ts","../src/components/icon-button/icon-button.tsx","../src/components/icon/icon.tsx","../src/components/icon/icon-styles.ts","../src/components/icon-button/icon-button-styles.ts","../src/tokens/colors.ts","../src/components/copy-button/copy-button.tsx"],"sourcesContent":["export { CopyButton } from \"./copy-button\";\nexport type { CopyButtonProps } from \"./copy-button\";\n","export default {\n outline: {\n xmlns: 'http://www.w3.org/2000/svg',\n width: 24,\n height: 24,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n },\n filled: {\n xmlns: 'http://www.w3.org/2000/svg',\n width: 24,\n height: 24,\n viewBox: '0 0 24 24',\n fill: 'currentColor',\n stroke: 'none',\n },\n};\n","import { forwardRef, createElement } from 'react';\nimport defaultAttributes from './defaultAttributes';\nimport type { IconNode, IconProps, Icon } from './types';\n\nconst createReactComponent = (\n type: 'outline' | 'filled',\n iconName: string,\n iconNamePascal: string,\n iconNode: IconNode,\n) => {\n const Component = forwardRef<SVGSVGElement, IconProps>(\n (\n { color = 'currentColor', size = 24, stroke = 2, title, className, children, ...rest }: IconProps,\n ref,\n ) =>\n createElement(\n 'svg',\n {\n ref,\n ...defaultAttributes[type],\n width: size,\n height: size,\n className: [`tabler-icon`, `tabler-icon-${iconName}`, className].join(' '),\n ...(type === 'filled'\n ? {\n fill: color,\n }\n : {\n strokeWidth: stroke,\n stroke: color,\n }),\n ...rest,\n },\n [\n title && createElement('title', { key: 'svg-title' }, title),\n ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),\n ...(Array.isArray(children) ? children : [children]),\n ],\n ),\n );\n\n Component.displayName = `${iconNamePascal}`;\n\n return Component;\n};\n\nexport default createReactComponent;\n","import createReactComponent from '../createReactComponent';\nimport { IconNode } from '../types';\n\nexport const __iconNode: IconNode = [[\"path\",{\"d\":\"M5 12l5 5l10 -10\",\"key\":\"svg-0\"}]]\n\nconst IconCheck = createReactComponent('outline', 'check', 'Check', __iconNode);\n\nexport default IconCheck;","import createReactComponent from '../createReactComponent';\nimport { IconNode } from '../types';\n\nexport const __iconNode: IconNode = [[\"path\",{\"d\":\"M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666\",\"key\":\"svg-0\"}],[\"path\",{\"d\":\"M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1\",\"key\":\"svg-1\"}]]\n\nconst IconCopy = createReactComponent('outline', 'copy', 'Copy', __iconNode);\n\nexport default IconCopy;","import { useState, useCallback, useRef, useEffect } from \"react\";\n\nimport { copyToClipboard } from \"@/utils/clipboard\";\n\nconst RESET_DELAY_MS = 2000;\n\ninterface UseCopyToClipboardResult {\n isCopied: boolean;\n copy: (text: string) => void;\n}\n\nexport function useCopyToClipboard(): UseCopyToClipboardResult {\n const [isCopied, setIsCopied] = useState(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n };\n }, []);\n\n // Write text verbatim — callers (copyMessageText + each BlockService.toClipboardText) are\n // responsible for converting their own format to clipboard-ready plain text.\n const copy = useCallback((text: string) => {\n copyToClipboard(text, {\n onSuccess: () => {\n setIsCopied(true);\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n timeoutRef.current = setTimeout(() => {\n setIsCopied(false);\n timeoutRef.current = null;\n }, RESET_DELAY_MS);\n },\n });\n }, []);\n\n return { isCopied, copy };\n}\n","/** Options for the {@link copyToClipboard} function. */\ninterface CopyToClipboardOptions {\n /** Called after the text has been successfully written to the clipboard. */\n onSuccess?: () => void;\n /** Called if the clipboard write fails (e.g., permission denied). */\n onFailure?: (error: unknown) => void;\n}\n\n/**\n * Copies text to the system clipboard using the Clipboard API.\n * @param text - The string to copy.\n * @param options - Optional success/failure callbacks.\n */\nexport function copyToClipboard(text: string, options?: CopyToClipboardOptions): void {\n navigator.clipboard.writeText(text).then(\n () => options?.onSuccess?.(),\n (error) => options?.onFailure?.(error)\n );\n}\n","import { forwardRef } from \"react\";\n\nimport { Icon } from \"../icon/icon\";\nimport { Button } from \"./icon-button-styles\";\nimport type { IconButtonProps, IconButtonSize } from \"./types\";\n\nconst ICON_SIZE_BY_BUTTON_SIZE: Record<IconButtonSize, number> = {\n small: 16,\n medium: 20,\n};\n\n/** Icon-only button. `forwardRef` + the rest-props spread so Radix `Slot`-based wrappers\n * (Tooltip, Popover, DropdownMenu) can attach refs and event handlers via `asChild`. */\nexport const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(\n {\n icon,\n size = \"small\",\n variant = \"short\",\n intent = \"secondary\",\n isSelected = false,\n disabled = false,\n onClick,\n className,\n id,\n as,\n \"aria-label\": ariaLabel,\n ...rest\n },\n ref\n) {\n // Only set `type=\"button\"` when rendering as a native button. Anchors (`as=\"a\"`) treat the\n // `type` attribute as a content-type hint for the linked resource — `type=\"button\"` on an\n // anchor is semantically wrong and shows up as an a11y / HTML-validation noise warning.\n const isNativeButton = as === undefined || as === \"button\";\n return (\n <Button\n ref={ref}\n className={className}\n id={id}\n as={as}\n $size={size}\n $variant={variant}\n $intent={intent}\n $isSelected={isSelected}\n disabled={disabled}\n onClick={disabled ? undefined : onClick}\n aria-label={ariaLabel}\n {...(isNativeButton ? { type: \"button\" as const } : {})}\n {...rest}\n >\n <Icon icon={icon} size={ICON_SIZE_BY_BUTTON_SIZE[size]} />\n </Button>\n );\n});\n","import { forwardRef } from \"react\";\n\nimport { IconWrapper, getStrokeWidth } from \"./icon-styles\";\nimport type { IconProps } from \"./types\";\n\nconst DEFAULT_SIZE = 16;\n\nexport const Icon = forwardRef<HTMLSpanElement, IconProps>(function Icon(\n { icon: IconComponent, size = DEFAULT_SIZE, color, className, \"aria-label\": ariaLabel },\n ref\n) {\n return (\n <IconWrapper\n ref={ref}\n className={className}\n $color={color}\n aria-label={ariaLabel}\n role={ariaLabel ? \"img\" : undefined}\n >\n <IconComponent width={size} height={size} strokeWidth={getStrokeWidth(size)} />\n </IconWrapper>\n );\n});\n","import styled from \"styled-components\";\n\nexport const STROKE_WIDTH_BY_SIZE: Record<number, number> = {\n 12: 1.4,\n 14: 1.4,\n 16: 1.8,\n 18: 1.8,\n 20: 1.8,\n 24: 1.8,\n};\n\nexport const DEFAULT_STROKE_WIDTH = 1.8;\n\nexport function getStrokeWidth(size: number): number {\n return STROKE_WIDTH_BY_SIZE[size] ?? DEFAULT_STROKE_WIDTH;\n}\n\nexport const IconWrapper = styled.span<{ $color?: string }>`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: ${({ $color }) => $color ?? \"currentColor\"};\n flex-shrink: 0;\n`;\n","import styled, { css } from \"styled-components\";\n\nimport { colors } from \"../../tokens/colors\";\nimport type { IconButtonIntent, IconButtonSize, IconButtonVariant } from \"./types\";\n\nconst PADDING: Record<IconButtonVariant, Record<IconButtonSize, string>> = {\n short: {\n small: \"4px\",\n medium: \"6px\",\n },\n long: {\n small: \"4px 6px\",\n medium: \"6px 10px\",\n },\n};\n\nconst BORDER_RADIUS: Record<IconButtonSize, number> = {\n small: 6,\n medium: 10,\n};\n\n// Color treatment per intent. \"secondary\" is the original transparent ghost (default — preserves\n// every existing consumer). \"primary\" / \"error\" are filled, mirroring `Button`'s solid intents.\nconst intentStyles: Record<IconButtonIntent, (args: { $isSelected: boolean }) => ReturnType<typeof css>> = {\n secondary: ({ $isSelected }) => css`\n background: ${$isSelected ? colors[\"brown-40\"] : \"transparent\"};\n color: ${colors[\"brown-100\"]};\n\n &:hover:not(:disabled) {\n background: ${$isSelected ? colors[\"brown-40\"] : colors[\"brown-20\"]};\n }\n\n &:active:not(:disabled) {\n background: ${colors[\"brown-40\"]};\n }\n\n &:disabled {\n color: ${colors[\"brown-50\"]};\n background: transparent;\n }\n `,\n primary: () => css`\n background: ${colors[\"brown-100\"]};\n color: ${colors.white};\n\n &:hover:not(:disabled) {\n background: ${colors[\"brown-90\"]};\n }\n\n &:active:not(:disabled) {\n background: ${colors[\"brown-90\"]};\n }\n\n &:disabled {\n color: ${colors[\"brown-50\"]};\n background: ${colors[\"brown-20\"]};\n }\n `,\n error: () => css`\n background: ${colors[\"red-500\"]};\n color: ${colors.white};\n\n &:hover:not(:disabled) {\n background: ${colors[\"red-600\"]};\n }\n\n &:active:not(:disabled) {\n background: ${colors[\"red-600\"]};\n }\n\n &:disabled {\n color: ${colors[\"red-200\"]};\n background: ${colors[\"red-100\"]};\n }\n `,\n};\n\nexport const Button = styled.button<{\n $size: IconButtonSize;\n $variant: IconButtonVariant;\n $intent: IconButtonIntent;\n $isSelected: boolean;\n}>`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: ${({ $size, $variant }) => PADDING[$variant][$size]};\n border: none;\n border-radius: ${({ $size }) => BORDER_RADIUS[$size]}px;\n cursor: pointer;\n transition: background-color 100ms ease;\n\n ${({ $intent, $isSelected }) => intentStyles[$intent]({ $isSelected })}\n\n &:disabled {\n cursor: not-allowed;\n }\n`;\n","export const colors = {\n // White\n white: \"#FFFFFF\",\n \"white-alpha-85\": \"rgba(255, 255, 255, 0.85)\",\n \"white-alpha-40\": \"rgba(255, 255, 255, 0.40)\",\n\n // Brown\n \"brown-10\": \"#FAF9F8\",\n \"brown-20\": \"#F5F3F0\",\n \"brown-30\": \"#EEEBE5\",\n \"brown-40\": \"#E6E2DA\",\n \"brown-50\": \"#CFCBC4\",\n \"brown-60\": \"#A19E99\",\n \"brown-70\": \"#73716D\",\n \"brown-80\": \"#5C5A57\",\n \"brown-90\": \"#2E2D2C\",\n \"brown-100\": \"#171716\",\n \"brown-alpha-12\": \"rgba(23, 23, 22, 0.12)\",\n \"brown-alpha-20\": \"rgba(23, 23, 22, 0.20)\",\n \"brown-alpha-55\": \"rgba(23, 23, 22, 0.55)\",\n \"brown-alpha-70\": \"rgba(23, 23, 22, 0.70)\",\n \"brown-alpha-80\": \"rgba(23, 23, 22, 0.80)\",\n \"brown-alpha-90\": \"rgba(23, 23, 22, 0.90)\",\n\n // Fuchsia\n \"fuchsia-50\": \"#FAF5FF\",\n \"fuchsia-300\": \"#F0ABFC\",\n \"fuchsia-500\": \"#D946EF\",\n \"fuchsia-600\": \"#C026D3\",\n \"fuchsia-800\": \"#86198F\",\n\n // Pink\n \"pink-50\": \"#FDF2F8\",\n \"pink-400\": \"#F472B6\",\n \"pink-600\": \"#DB2777\",\n \"pink-800\": \"#9D174D\",\n\n // Violet\n \"violet-50\": \"#F5F3FF\",\n \"violet-400\": \"#A78BFA\",\n \"violet-600\": \"#7C3AED\",\n \"violet-800\": \"#5B21B6\",\n\n // Indigo\n \"indigo-50\": \"#EEF2FF\",\n \"indigo-400\": \"#818CF8\",\n \"indigo-500\": \"#6366F1\",\n \"indigo-600\": \"#4F46E5\",\n \"indigo-800\": \"#3730A3\",\n\n // Cyan\n \"cyan-50\": \"#E7FEFF\",\n \"cyan-300\": \"#67E8F9\",\n \"cyan-600\": \"#0891B2\",\n \"cyan-900\": \"#164E63\",\n\n // Teal\n \"teal-50\": \"#EBFDF9\",\n \"teal-300\": \"#5EEAD4\",\n \"teal-600\": \"#0D9488\",\n\n // Rose\n \"rose-400\": \"#FB7185\",\n\n // Purple\n \"purple-400\": \"#C084FC\",\n\n // Blue\n \"blue-50\": \"#EFF6FF\",\n \"blue-100\": \"#DBEAFE\",\n \"blue-300\": \"#93C5FD\",\n \"blue-400\": \"#60A5FA\",\n \"blue-600\": \"#2563EB\",\n \"blue-800\": \"#1E40AF\",\n\n // Sky\n \"sky-300\": \"#7DD3FC\",\n\n // Green\n \"green-50\": \"#ECF9F0\",\n \"green-100\": \"#D4F1D9\",\n \"green-400\": \"#3DC269\",\n \"green-700\": \"#277C43\",\n \"green-800\": \"#1C5930\",\n\n // Orange\n \"orange-50\": \"#FFF7ED\",\n \"orange-100\": \"#FFEDD5\",\n \"orange-200\": \"#FED7AA\",\n \"orange-500\": \"#F97316\",\n \"orange-600\": \"#EA580C\",\n\n // Red\n \"red-50\": \"#FEF2F2\",\n \"red-100\": \"#FEE2E2\",\n \"red-200\": \"#FECACA\",\n \"red-400\": \"#F7776C\",\n \"red-500\": \"#EF4444\",\n \"red-600\": \"#DC2626\",\n\n // Deck\n \"deck-canvas\": \"#faf8f5\",\n} as const;\n\nexport type ColorToken = keyof typeof colors;\n","import type { ReactNode } from \"react\";\nimport { IconCheck, IconCopy } from \"@tabler/icons-react\";\n\nimport { useCopyToClipboard } from \"../chat/hooks/use-copy-to-clipboard\";\nimport { IconButton } from \"../icon-button/icon-button\";\n\nexport interface CopyButtonProps {\n /** Text written to the clipboard on click. */\n text: string;\n /** Accessible label + tooltip in the idle state. Default \"Copy\". */\n copyLabel?: string;\n /** Accessible label while the success flash is showing. Default \"Copied\". */\n copiedLabel?: string;\n /** Extra classes forwarded to the underlying `IconButton`. */\n className?: string;\n}\n\n/**\n * Icon button that copies `text` to the clipboard and flashes a check for ~2s (`IconCopy` →\n * `IconCheck`). Wraps `IconButton` + `useCopyToClipboard`; for copying plain text (not images —\n * for image capture use `ArtifactCopyAsImageButton`).\n */\nexport function CopyButton({\n text,\n copyLabel = \"Copy\",\n copiedLabel = \"Copied\",\n className,\n}: CopyButtonProps): ReactNode {\n const { isCopied, copy } = useCopyToClipboard();\n return (\n <IconButton\n icon={isCopied ? IconCheck : IconCopy}\n onClick={() => copy(text)}\n aria-label={isCopied ? copiedLabel : copyLabel}\n className={className}\n />\n );\n}\n"],"mappings":"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","names":["__iconNode","import_react","import_react","import_react","styled","Icon","import_styled_components","styled","import_jsx_runtime","IconButton","import_jsx_runtime"]}
@@ -0,0 +1,20 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ interface CopyButtonProps {
4
+ /** Text written to the clipboard on click. */
5
+ text: string;
6
+ /** Accessible label + tooltip in the idle state. Default "Copy". */
7
+ copyLabel?: string;
8
+ /** Accessible label while the success flash is showing. Default "Copied". */
9
+ copiedLabel?: string;
10
+ /** Extra classes forwarded to the underlying `IconButton`. */
11
+ className?: string;
12
+ }
13
+ /**
14
+ * Icon button that copies `text` to the clipboard and flashes a check for ~2s (`IconCopy` →
15
+ * `IconCheck`). Wraps `IconButton` + `useCopyToClipboard`; for copying plain text (not images —
16
+ * for image capture use `ArtifactCopyAsImageButton`).
17
+ */
18
+ declare function CopyButton({ text, copyLabel, copiedLabel, className, }: CopyButtonProps): ReactNode;
19
+
20
+ export { CopyButton, type CopyButtonProps };
@@ -0,0 +1,20 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ interface CopyButtonProps {
4
+ /** Text written to the clipboard on click. */
5
+ text: string;
6
+ /** Accessible label + tooltip in the idle state. Default "Copy". */
7
+ copyLabel?: string;
8
+ /** Accessible label while the success flash is showing. Default "Copied". */
9
+ copiedLabel?: string;
10
+ /** Extra classes forwarded to the underlying `IconButton`. */
11
+ className?: string;
12
+ }
13
+ /**
14
+ * Icon button that copies `text` to the clipboard and flashes a check for ~2s (`IconCopy` →
15
+ * `IconCheck`). Wraps `IconButton` + `useCopyToClipboard`; for copying plain text (not images —
16
+ * for image capture use `ArtifactCopyAsImageButton`).
17
+ */
18
+ declare function CopyButton({ text, copyLabel, copiedLabel, className, }: CopyButtonProps): ReactNode;
19
+
20
+ export { CopyButton, type CopyButtonProps };
@@ -0,0 +1,12 @@
1
+ import {
2
+ CopyButton
3
+ } from "./chunk-EIAWZXE4.js";
4
+ import "./chunk-CSTCTO3B.js";
5
+ import "./chunk-E36VI2HA.js";
6
+ import "./chunk-OLJIJYB5.js";
7
+ import "./chunk-SN3ORQSJ.js";
8
+ import "./chunk-5WRI5ZAA.js";
9
+ export {
10
+ CopyButton
11
+ };
12
+ //# sourceMappingURL=copy-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}