@shapesos/clay 0.10.0 → 0.11.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/artifacts.cjs +741 -0
  2. package/dist/artifacts.cjs.map +1 -0
  3. package/dist/artifacts.d.cts +56 -0
  4. package/dist/artifacts.d.ts +56 -0
  5. package/dist/artifacts.js +23 -0
  6. package/dist/blocks.cjs +685 -53
  7. package/dist/blocks.cjs.map +1 -1
  8. package/dist/blocks.css +2 -0
  9. package/dist/blocks.d.cts +22 -10
  10. package/dist/blocks.d.ts +22 -10
  11. package/dist/blocks.js +15 -5
  12. package/dist/button.d.cts +2 -2
  13. package/dist/button.d.ts +2 -2
  14. package/dist/chat.cjs +728 -296
  15. package/dist/chat.cjs.map +1 -1
  16. package/dist/chat.d.cts +27 -10
  17. package/dist/chat.d.ts +27 -10
  18. package/dist/chat.js +7 -3
  19. package/dist/chunk-BX5TCEPR.js +436 -0
  20. package/dist/chunk-BX5TCEPR.js.map +1 -0
  21. package/dist/chunk-JGMN6W72.js +12 -0
  22. package/dist/chunk-JGMN6W72.js.map +1 -0
  23. package/dist/{chunk-27GJUWVN.js → chunk-JJUIBBBU.js} +14 -8
  24. package/dist/chunk-JJUIBBBU.js.map +1 -0
  25. package/dist/chunk-L35M3OD5.js +99 -0
  26. package/dist/chunk-L35M3OD5.js.map +1 -0
  27. package/dist/chunk-MEJESPTZ.js +1 -0
  28. package/dist/chunk-MEJESPTZ.js.map +1 -0
  29. package/dist/{chunk-R3BGPOAM.js → chunk-MXOPG747.js} +53 -45
  30. package/dist/chunk-MXOPG747.js.map +1 -0
  31. package/dist/{chunk-MLCRDVQ2.js → chunk-OBOXCBDL.js} +13 -5
  32. package/dist/chunk-OBOXCBDL.js.map +1 -0
  33. package/dist/{chunk-WS4IPADR.js → chunk-OUW6PUEB.js} +42 -110
  34. package/dist/chunk-OUW6PUEB.js.map +1 -0
  35. package/dist/icon.cjs +12 -4
  36. package/dist/icon.cjs.map +1 -1
  37. package/dist/icon.d.cts +19 -6
  38. package/dist/icon.d.ts +19 -6
  39. package/dist/icon.js +1 -1
  40. package/dist/index.cjs +794 -332
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +6 -4
  43. package/dist/index.d.ts +6 -4
  44. package/dist/index.js +50 -25
  45. package/dist/lottie.d.cts +2 -2
  46. package/dist/lottie.d.ts +2 -2
  47. package/dist/table.cjs +142 -0
  48. package/dist/table.cjs.map +1 -0
  49. package/dist/table.d.cts +44 -0
  50. package/dist/table.d.ts +44 -0
  51. package/dist/table.js +24 -0
  52. package/dist/table.js.map +1 -0
  53. package/dist/text-area.d.cts +2 -2
  54. package/dist/text-area.d.ts +2 -2
  55. package/dist/types-3Gzk7cRt.d.cts +121 -0
  56. package/dist/types-3Gzk7cRt.d.ts +121 -0
  57. package/dist/types-C9XX-Uhk.d.ts +68 -0
  58. package/dist/types-DuuRI4ll.d.cts +68 -0
  59. package/dist/utils.cjs +14 -7
  60. package/dist/utils.cjs.map +1 -1
  61. package/dist/utils.d.cts +18 -1
  62. package/dist/utils.d.ts +18 -1
  63. package/dist/utils.js +6 -4
  64. package/package.json +29 -4
  65. package/dist/chunk-27GJUWVN.js.map +0 -1
  66. package/dist/chunk-MLCRDVQ2.js.map +0 -1
  67. package/dist/chunk-OKPNST44.js +0 -1
  68. package/dist/chunk-R3BGPOAM.js.map +0 -1
  69. package/dist/chunk-WS4IPADR.js.map +0 -1
  70. package/dist/types-Q9aqd9nq.d.cts +0 -34
  71. package/dist/types-Q9aqd9nq.d.ts +0 -34
  72. /package/dist/{chunk-OKPNST44.js.map → artifacts.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/parse-markdown-table.ts","../src/utils/escape-csv-field.ts","../src/utils/convert-table-to-csv.ts"],"sourcesContent":["import { stripInlineMarks } from \"./markdown-to-plain-text\";\n\n/** Structured representation of a parsed markdown table. */\nexport interface MarkdownTable {\n /** Column header labels. */\n columns: string[];\n /** Data rows, each an array of cell values matching the column order. */\n rows: string[][];\n /** Optional title extracted from a `<!-- table-title: ... -->` comment above the table. */\n title?: string;\n}\n\n/**\n * Parses cells from a markdown table row, trimming whitespace and stripping leading/trailing pipes.\n * @param line - A single line of a markdown table.\n * @returns An array of trimmed cell values.\n */\nfunction parseCells(line: string): string[] {\n const trimmed = line.trim();\n const stripped = trimmed.startsWith(\"|\") ? trimmed.slice(1) : trimmed;\n const withoutTrailing = stripped.endsWith(\"|\") ? stripped.slice(0, -1) : stripped;\n return withoutTrailing.split(\"|\").map((cell) => cell.trim());\n}\n\n/**\n * Tests whether a line is a GFM table separator row (e.g. `| --- | --- |`).\n * @param line - A single line to test.\n * @returns `true` if the line matches the separator pattern.\n */\nfunction isSeparatorRow(line: string): boolean {\n const cells = parseCells(line);\n return cells.length > 0 && cells.every((cell) => /^:?-{1,}:?$/.test(cell));\n}\n\n/**\n * Tests whether a line looks like a markdown table row (contains at least one pipe).\n * @param line - A single line to test.\n * @returns `true` if the line contains a pipe character.\n */\nfunction isTableRow(line: string): boolean {\n return line.includes(\"|\");\n}\n\nconst TABLE_TITLE_PREFIX = \"<!-- table-title:\";\nconst TABLE_TITLE_SUFFIX = \"-->\";\n\n/**\n * Extracts a table title from a `<!-- table-title: ... -->` HTML comment.\n * Uses string methods instead of regex to avoid ReDoS on untrusted input.\n * @param line - A single line to test.\n * @returns The title string, or `undefined` if the line is not a title comment.\n */\nfunction extractTableTitle(line: string): string | undefined {\n const trimmed = line.trim();\n if (!trimmed.startsWith(TABLE_TITLE_PREFIX) || !trimmed.endsWith(TABLE_TITLE_SUFFIX)) {\n return undefined;\n }\n return trimmed.slice(TABLE_TITLE_PREFIX.length, -TABLE_TITLE_SUFFIX.length).trim() || undefined;\n}\n\n/**\n * Extracts the Nth GFM markdown table from a string.\n * @param markdown - The markdown content to parse.\n * @param tableIndex - Zero-based index of the table to extract.\n * @returns The parsed table, or `null` if no table exists at the given index.\n */\nexport function parseMarkdownTable(markdown: string, tableIndex = 0): MarkdownTable | null {\n const lines = markdown.split(\"\\n\");\n let tableCount = 0;\n\n for (let i = 0; i < lines.length - 1; i++) {\n const headerLine = lines[i];\n const separatorLine = lines[i + 1];\n\n if (!isTableRow(headerLine) || !isSeparatorRow(separatorLine)) {\n continue;\n }\n\n if (tableCount === tableIndex) {\n const columns = parseCells(headerLine).map(stripInlineMarks);\n const rows: string[][] = [];\n const title = i > 0 ? extractTableTitle(lines[i - 1]) : undefined;\n\n for (let j = i + 2; j < lines.length; j++) {\n if (!isTableRow(lines[j]) || isSeparatorRow(lines[j])) {\n break;\n }\n const trimmedLine = lines[j].trim();\n if (trimmedLine === \"\") {\n break;\n }\n rows.push(parseCells(lines[j]).map(stripInlineMarks));\n }\n\n return { columns, rows, ...(title ? { title } : {}) };\n }\n\n tableCount++;\n }\n\n return null;\n}\n","/**\n * Characters that trigger spreadsheet-formula evaluation when present at the start of a CSV cell.\n * Excel, Google Sheets, and Numbers all treat a field beginning with one of these as a formula\n * (e.g. `=HYPERLINK(...)`, `+1+1`, `@SUM(...)`), so a field exfiltrated from user-controlled\n * data can run arbitrary expressions in the spreadsheet on open.\n *\n * Note: `-` is intentionally excluded. While `+` and `-` are listed in some CSV injection docs,\n * `-42` is a valid negative number and Excel/Sheets do NOT evaluate it as a formula. Including `-`\n * would corrupt all negative-number cells. The actual risk from `-` is extremely low in practice.\n */\nconst FORMULA_TRIGGERS = new Set([\"=\", \"+\", \"@\", \"\\t\", \"\\r\"]);\n\n/**\n * Escapes and optionally quotes a CSV field per RFC 4180, with OWASP-recommended formula-injection\n * neutralisation for fields beginning with a trigger character.\n *\n * - Fields starting with `=`, `+`, `@`, `\\t`, or `\\r` are prefixed with a tab so the spreadsheet\n * renders them as literal text instead of evaluating them as a formula. The tab itself disappears\n * in the rendered cell.\n * - Fields containing commas, double quotes, newlines, or a leading tab (from formula neutralisation)\n * are wrapped in double quotes per RFC 4180. The double-quote wrapping ensures the tab prefix is\n * preserved in parsers that would otherwise strip leading whitespace.\n * - Existing double quotes are escaped by doubling them.\n *\n * @param field - The raw field value.\n * @returns The escaped/quoted field string.\n */\nexport function escapeCsvField(field: string): string {\n const needsNeutralise = field.length > 0 && FORMULA_TRIGGERS.has(field[0]);\n const safeField = needsNeutralise ? `\\t${field}` : field;\n if (\n needsNeutralise ||\n safeField.includes(\",\") ||\n safeField.includes('\"') ||\n safeField.includes(\"\\n\") ||\n safeField.includes(\"\\r\")\n ) {\n return `\"${safeField.replace(/\"/g, '\"\"')}\"`;\n }\n return safeField;\n}\n","import { escapeCsvField } from \"./escape-csv-field\";\nimport type { MarkdownTable } from \"./parse-markdown-table\";\n\n/**\n * Converts a parsed markdown table to a CSV string (RFC 4180).\n * Fields containing commas, double quotes, or newlines are quoted.\n * @param table - The structured table data to convert.\n * @returns A CSV-formatted string with CRLF line endings.\n */\nexport function convertTableToCSV(table: MarkdownTable): string {\n const headerRow = table.columns.map(escapeCsvField).join(\",\");\n const dataRows = table.rows.map((row) => row.map(escapeCsvField).join(\",\"));\n return [headerRow, ...dataRows].join(\"\\r\\n\");\n}\n"],"mappings":";;;;;AAiBA,SAAS,WAAW,MAAwB;AAC1C,QAAM,UAAU,KAAK,KAAK;AAC1B,QAAM,WAAW,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;AAC9D,QAAM,kBAAkB,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI;AACzE,SAAO,gBAAgB,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;AAC7D;AAOA,SAAS,eAAe,MAAuB;AAC7C,QAAM,QAAQ,WAAW,IAAI;AAC7B,SAAO,MAAM,SAAS,KAAK,MAAM,MAAM,CAAC,SAAS,cAAc,KAAK,IAAI,CAAC;AAC3E;AAOA,SAAS,WAAW,MAAuB;AACzC,SAAO,KAAK,SAAS,GAAG;AAC1B;AAEA,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAQ3B,SAAS,kBAAkB,MAAkC;AAC3D,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,QAAQ,WAAW,kBAAkB,KAAK,CAAC,QAAQ,SAAS,kBAAkB,GAAG;AACpF,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,MAAM,mBAAmB,QAAQ,CAAC,mBAAmB,MAAM,EAAE,KAAK,KAAK;AACxF;AAQO,SAAS,mBAAmB,UAAkB,aAAa,GAAyB;AACzF,QAAM,QAAQ,SAAS,MAAM,IAAI;AACjC,MAAI,aAAa;AAEjB,WAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AACzC,UAAM,aAAa,MAAM,CAAC;AAC1B,UAAM,gBAAgB,MAAM,IAAI,CAAC;AAEjC,QAAI,CAAC,WAAW,UAAU,KAAK,CAAC,eAAe,aAAa,GAAG;AAC7D;AAAA,IACF;AAEA,QAAI,eAAe,YAAY;AAC7B,YAAM,UAAU,WAAW,UAAU,EAAE,IAAI,gBAAgB;AAC3D,YAAM,OAAmB,CAAC;AAC1B,YAAM,QAAQ,IAAI,IAAI,kBAAkB,MAAM,IAAI,CAAC,CAAC,IAAI;AAExD,eAAS,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACzC,YAAI,CAAC,WAAW,MAAM,CAAC,CAAC,KAAK,eAAe,MAAM,CAAC,CAAC,GAAG;AACrD;AAAA,QACF;AACA,cAAM,cAAc,MAAM,CAAC,EAAE,KAAK;AAClC,YAAI,gBAAgB,IAAI;AACtB;AAAA,QACF;AACA,aAAK,KAAK,WAAW,MAAM,CAAC,CAAC,EAAE,IAAI,gBAAgB,CAAC;AAAA,MACtD;AAEA,aAAO,EAAE,SAAS,MAAM,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,EAAG;AAAA,IACtD;AAEA;AAAA,EACF;AAEA,SAAO;AACT;;;AC3FA,IAAM,mBAAmB,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAM,IAAI,CAAC;AAiBrD,SAAS,eAAe,OAAuB;AACpD,QAAM,kBAAkB,MAAM,SAAS,KAAK,iBAAiB,IAAI,MAAM,CAAC,CAAC;AACzE,QAAM,YAAY,kBAAkB,IAAK,KAAK,KAAK;AACnD,MACE,mBACA,UAAU,SAAS,GAAG,KACtB,UAAU,SAAS,GAAG,KACtB,UAAU,SAAS,IAAI,KACvB,UAAU,SAAS,IAAI,GACvB;AACA,WAAO,IAAI,UAAU,QAAQ,MAAM,IAAI,CAAC;AAAA,EAC1C;AACA,SAAO;AACT;;;AC/BO,SAAS,kBAAkB,OAA8B;AAC9D,QAAM,YAAY,MAAM,QAAQ,IAAI,cAAc,EAAE,KAAK,GAAG;AAC5D,QAAM,WAAW,MAAM,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,cAAc,EAAE,KAAK,GAAG,CAAC;AAC1E,SAAO,CAAC,WAAW,GAAG,QAAQ,EAAE,KAAK,MAAM;AAC7C;","names":[]}
@@ -0,0 +1,99 @@
1
+ // src/components/ui/table.tsx
2
+ import * as React from "react";
3
+
4
+ // src/lib/utils.ts
5
+ import { clsx } from "clsx";
6
+ import { twMerge } from "tailwind-merge";
7
+ function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+
11
+ // src/components/ui/table.tsx
12
+ import { jsx } from "react/jsx-runtime";
13
+ var Table = React.forwardRef(({ className, containerClassName, ...props }, ref) => /* @__PURE__ */ jsx("div", { className: cn("relative w-full overflow-auto", containerClassName), children: /* @__PURE__ */ jsx("table", { ref, className: cn("w-full caption-bottom text-sm", className), ...props }) }));
14
+ Table.displayName = "Table";
15
+ var TableHeader = React.forwardRef(
16
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx("thead", { ref, className: cn("[&_tr]:border-b", className), ...props })
17
+ );
18
+ TableHeader.displayName = "TableHeader";
19
+ var TableBody = React.forwardRef(
20
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx("tbody", { ref, className: cn("[&_tr:last-child]:border-0", className), ...props })
21
+ );
22
+ TableBody.displayName = "TableBody";
23
+ var TableFooter = React.forwardRef(
24
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx("tfoot", { ref, className: cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className), ...props })
25
+ );
26
+ TableFooter.displayName = "TableFooter";
27
+ var TableRow = React.forwardRef(
28
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
29
+ "tr",
30
+ {
31
+ ref,
32
+ className: cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className),
33
+ ...props
34
+ }
35
+ )
36
+ );
37
+ TableRow.displayName = "TableRow";
38
+ var TableHead = React.forwardRef(
39
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
40
+ "th",
41
+ {
42
+ ref,
43
+ className: cn(
44
+ "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
45
+ className
46
+ ),
47
+ ...props
48
+ }
49
+ )
50
+ );
51
+ TableHead.displayName = "TableHead";
52
+ var TableCell = React.forwardRef(
53
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx("td", { ref, className: cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className), ...props })
54
+ );
55
+ TableCell.displayName = "TableCell";
56
+ var TableCaption = React.forwardRef(
57
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx("caption", { ref, className: cn("mt-4 text-sm text-muted-foreground", className), ...props })
58
+ );
59
+ TableCaption.displayName = "TableCaption";
60
+
61
+ // src/components/table/use-scroll-shadow.ts
62
+ import { useEffect, useRef, useState } from "react";
63
+ function useScrollShadow() {
64
+ const targetRef = useRef(null);
65
+ const [isScrolled, setIsScrolled] = useState(false);
66
+ useEffect(() => {
67
+ const target = targetRef.current;
68
+ if (!target) return;
69
+ let scrollContainer = target;
70
+ while (scrollContainer) {
71
+ const overflowY = getComputedStyle(scrollContainer).overflowY;
72
+ if (overflowY === "auto" || overflowY === "scroll") break;
73
+ scrollContainer = scrollContainer.parentElement;
74
+ }
75
+ if (!scrollContainer) return;
76
+ setIsScrolled(scrollContainer.scrollTop > 0);
77
+ const handleScroll = () => {
78
+ if (!scrollContainer) return;
79
+ setIsScrolled(scrollContainer.scrollTop > 0);
80
+ };
81
+ scrollContainer.addEventListener("scroll", handleScroll, { passive: true });
82
+ return () => scrollContainer.removeEventListener("scroll", handleScroll);
83
+ }, []);
84
+ return { targetRef, isScrolled };
85
+ }
86
+
87
+ export {
88
+ cn,
89
+ Table,
90
+ TableHeader,
91
+ TableBody,
92
+ TableFooter,
93
+ TableRow,
94
+ TableHead,
95
+ TableCell,
96
+ TableCaption,
97
+ useScrollShadow
98
+ };
99
+ //# sourceMappingURL=chunk-L35M3OD5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/ui/table.tsx","../src/lib/utils.ts","../src/components/table/use-scroll-shadow.ts"],"sourcesContent":["import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/** Extension of shadcn's `Table` registry component: exposes `containerClassName` so callers can\n * override the wrapping `<div>`'s default `overflow-auto`. We need this for sticky-header support\n * (sticky uses the nearest overflow ancestor as its containing block — when the actual scroll\n * container is one level out, the inner overflow-auto needs to be neutralised with\n * `overflow-visible` so sticky aligns with the real scrolling parent). */\nconst Table = React.forwardRef<\n HTMLTableElement,\n React.HTMLAttributes<HTMLTableElement> & { containerClassName?: string }\n>(({ className, containerClassName, ...props }, ref) => (\n <div className={cn(\"relative w-full overflow-auto\", containerClassName)}>\n <table ref={ref} className={cn(\"w-full caption-bottom text-sm\", className)} {...props} />\n </div>\n));\nTable.displayName = \"Table\";\n\nconst TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n ({ className, ...props }, ref) => <thead ref={ref} className={cn(\"[&_tr]:border-b\", className)} {...props} />\n);\nTableHeader.displayName = \"TableHeader\";\n\nconst TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n ({ className, ...props }, ref) => (\n <tbody ref={ref} className={cn(\"[&_tr:last-child]:border-0\", className)} {...props} />\n )\n);\nTableBody.displayName = \"TableBody\";\n\nconst TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n ({ className, ...props }, ref) => (\n <tfoot ref={ref} className={cn(\"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\", className)} {...props} />\n )\n);\nTableFooter.displayName = \"TableFooter\";\n\nconst TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(\n ({ className, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted\", className)}\n {...props}\n />\n )\n);\nTableRow.displayName = \"TableRow\";\n\nconst TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(\n ({ className, ...props }, ref) => (\n <th\n ref={ref}\n className={cn(\n \"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n className\n )}\n {...props}\n />\n )\n);\nTableHead.displayName = \"TableHead\";\n\nconst TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(\n ({ className, ...props }, ref) => (\n <td ref={ref} className={cn(\"p-4 align-middle [&:has([role=checkbox])]:pr-0\", className)} {...props} />\n )\n);\nTableCell.displayName = \"TableCell\";\n\nconst TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(\n ({ className, ...props }, ref) => (\n <caption ref={ref} className={cn(\"mt-4 text-sm text-muted-foreground\", className)} {...props} />\n )\n);\nTableCaption.displayName = \"TableCaption\";\n\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Combines class names using clsx and tailwind-merge — the standard shadcn/ui helper.\n * Tailwind classes that conflict (e.g. `p-2 p-4`) get merged so the last one wins.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import { useEffect, useRef, useState, type RefObject } from \"react\";\n\n/**\n * Tracks whether a scroll container has scrolled past its top edge, for sticky-header shadow effects.\n *\n * Returns `targetRef` (attach to the scroll container itself or any element inside it) and\n * `isScrolled` (true once the container's `scrollTop > 0`). On mount the hook walks up the DOM\n * from the target to find the nearest overflow ancestor (or uses the target itself if it already\n * has `overflowY: auto | scroll`), then listens to its `scroll` event.\n *\n * Used to surface a subtle \"scroll lift\" shadow under sticky headers so users know there is\n * content hidden above.\n *\n * **Why a scroll listener and not a sentinel + IntersectionObserver:** an earlier version used a\n * 1px sentinel as the first child of the scroll container. The sentinel's height pushed the thead\n * down by one pixel in normal flow, and as soon as the user scrolled the thead snapped up to its\n * sticky position — a visible 1px jitter. A scroll listener has no layout footprint.\n *\n * `passive: true` so the listener doesn't block native scroll smoothness; we never call\n * `preventDefault`.\n */\nexport function useScrollShadow(): {\n targetRef: RefObject<HTMLDivElement | null>;\n isScrolled: boolean;\n} {\n const targetRef = useRef<HTMLDivElement>(null);\n const [isScrolled, setIsScrolled] = useState(false);\n\n useEffect(() => {\n const target = targetRef.current;\n if (!target) return;\n\n let scrollContainer: HTMLElement | null = target;\n while (scrollContainer) {\n const overflowY = getComputedStyle(scrollContainer).overflowY;\n if (overflowY === \"auto\" || overflowY === \"scroll\") break;\n scrollContainer = scrollContainer.parentElement;\n }\n if (!scrollContainer) return;\n\n setIsScrolled(scrollContainer.scrollTop > 0);\n\n const handleScroll = () => {\n if (!scrollContainer) return;\n setIsScrolled(scrollContainer.scrollTop > 0);\n };\n scrollContainer.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => scrollContainer.removeEventListener(\"scroll\", handleScroll);\n }, []);\n\n return { targetRef, isScrolled };\n}\n"],"mappings":";AAAA,YAAY,WAAW;;;ACAvB,SAAS,YAA6B;AACtC,SAAS,eAAe;AAMjB,SAAS,MAAM,QAA8B;AAClD,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;ADKI;AALJ,IAAM,QAAc,iBAGlB,CAAC,EAAE,WAAW,oBAAoB,GAAG,MAAM,GAAG,QAC9C,oBAAC,SAAI,WAAW,GAAG,iCAAiC,kBAAkB,GACpE,8BAAC,WAAM,KAAU,WAAW,GAAG,iCAAiC,SAAS,GAAI,GAAG,OAAO,GACzF,CACD;AACD,MAAM,cAAc;AAEpB,IAAM,cAAoB;AAAA,EACxB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAAQ,oBAAC,WAAM,KAAU,WAAW,GAAG,mBAAmB,SAAS,GAAI,GAAG,OAAO;AAC7G;AACA,YAAY,cAAc;AAE1B,IAAM,YAAkB;AAAA,EACtB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB,oBAAC,WAAM,KAAU,WAAW,GAAG,8BAA8B,SAAS,GAAI,GAAG,OAAO;AAExF;AACA,UAAU,cAAc;AAExB,IAAM,cAAoB;AAAA,EACxB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB,oBAAC,WAAM,KAAU,WAAW,GAAG,2DAA2D,SAAS,GAAI,GAAG,OAAO;AAErH;AACA,YAAY,cAAc;AAE1B,IAAM,WAAiB;AAAA,EACrB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW,GAAG,+EAA+E,SAAS;AAAA,MACrG,GAAG;AAAA;AAAA,EACN;AAEJ;AACA,SAAS,cAAc;AAEvB,IAAM,YAAkB;AAAA,EACtB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AACA,UAAU,cAAc;AAExB,IAAM,YAAkB;AAAA,EACtB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB,oBAAC,QAAG,KAAU,WAAW,GAAG,kDAAkD,SAAS,GAAI,GAAG,OAAO;AAEzG;AACA,UAAU,cAAc;AAExB,IAAM,eAAqB;AAAA,EACzB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB,oBAAC,aAAQ,KAAU,WAAW,GAAG,sCAAsC,SAAS,GAAI,GAAG,OAAO;AAElG;AACA,aAAa,cAAc;;;AE3E3B,SAAS,WAAW,QAAQ,gBAAgC;AAqBrD,SAAS,kBAGd;AACA,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAElD,YAAU,MAAM;AACd,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ;AAEb,QAAI,kBAAsC;AAC1C,WAAO,iBAAiB;AACtB,YAAM,YAAY,iBAAiB,eAAe,EAAE;AACpD,UAAI,cAAc,UAAU,cAAc,SAAU;AACpD,wBAAkB,gBAAgB;AAAA,IACpC;AACA,QAAI,CAAC,gBAAiB;AAEtB,kBAAc,gBAAgB,YAAY,CAAC;AAE3C,UAAM,eAAe,MAAM;AACzB,UAAI,CAAC,gBAAiB;AACtB,oBAAc,gBAAgB,YAAY,CAAC;AAAA,IAC7C;AACA,oBAAgB,iBAAiB,UAAU,cAAc,EAAE,SAAS,KAAK,CAAC;AAC1E,WAAO,MAAM,gBAAgB,oBAAoB,UAAU,YAAY;AAAA,EACzE,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,WAAW,WAAW;AACjC;","names":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-MEJESPTZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,21 +1,44 @@
1
+ import {
2
+ markdownToPlainText
3
+ } from "./chunk-NMKKU2UG.js";
1
4
  import {
2
5
  typographyMixin,
3
6
  typographyTypes
4
7
  } from "./chunk-SV24ONND.js";
8
+ import {
9
+ ArtifactServices,
10
+ artifactToClipboardText
11
+ } from "./chunk-BX5TCEPR.js";
5
12
  import {
6
13
  colors
7
14
  } from "./chunk-JF3P66JF.js";
8
15
 
9
- // src/components/blocks/block-context/block-context.tsx
10
- import { createContext, useContext } from "react";
11
- var BlockContext = createContext(null);
12
- function useBlockContext() {
13
- return useContext(BlockContext) ?? {};
14
- }
15
-
16
16
  // src/components/blocks/types.ts
17
17
  var blockTypes = {
18
- TEXT: "TEXT"
18
+ TEXT: "TEXT",
19
+ ARTIFACT_REF: "ARTIFACT_REF"
20
+ };
21
+
22
+ // src/components/blocks/artifact-ref-block/artifact-ref-block.tsx
23
+ import { jsx } from "react/jsx-runtime";
24
+ function ArtifactRefBlock({ block }) {
25
+ const { artifact } = block.payload;
26
+ const entry = ArtifactServices[artifact.type];
27
+ if (!entry) return null;
28
+ const { Component } = entry;
29
+ return /* @__PURE__ */ jsx(Component, { artifact });
30
+ }
31
+
32
+ // src/components/blocks/block-services/artifact-ref-block-service.ts
33
+ var ArtifactRefBlockService = {
34
+ type: blockTypes.ARTIFACT_REF,
35
+ Component: ArtifactRefBlock,
36
+ /**
37
+ * Delegates to the registered artifact component's `toClipboardText`. Falls back to
38
+ * `[Artifact: <title>]` via `artifactToClipboardText`'s own fallback when the artifact type
39
+ * has no registered component, so copy never silently produces an empty string.
40
+ */
41
+ toClipboardText: (block) => artifactToClipboardText(block.payload.artifact)
19
42
  };
20
43
 
21
44
  // src/components/blocks/text-block/text-block.tsx
@@ -34,32 +57,11 @@ var TableScroll = styled.div`
34
57
  overflow-x: auto;
35
58
  overflow-y: hidden;
36
59
  `;
37
- var TableActionsWrapper = styled.div`
38
- display: flex;
39
- justify-content: flex-start;
40
- margin-block: 8px 0;
41
- `;
42
- var TableActionsDivider = styled.div`
43
- height: 1px;
44
- background: ${colors["brown-40"]};
45
- margin-block: 8px 0;
46
- `;
47
60
 
48
61
  // src/components/blocks/scrollable-table/scrollable-table.tsx
49
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
50
- function ScrollableTable({ node: _node, counter, ...props }) {
51
- const currentIndex = counter ? counter.value : 0;
52
- if (counter) {
53
- counter.value += 1;
54
- }
55
- const { TableActions } = useBlockContext();
56
- return /* @__PURE__ */ jsxs(TableContainer, { children: [
57
- /* @__PURE__ */ jsx(TableScroll, { children: /* @__PURE__ */ jsx("table", { ...props }) }),
58
- TableActions && /* @__PURE__ */ jsxs(Fragment, { children: [
59
- /* @__PURE__ */ jsx(TableActionsWrapper, { children: /* @__PURE__ */ jsx(TableActions, { tableIndex: currentIndex }) }),
60
- /* @__PURE__ */ jsx(TableActionsDivider, {})
61
- ] })
62
- ] });
62
+ import { jsx as jsx2 } from "react/jsx-runtime";
63
+ function ScrollableTable({ node: _node, ...props }) {
64
+ return /* @__PURE__ */ jsx2(TableContainer, { children: /* @__PURE__ */ jsx2(TableScroll, { children: /* @__PURE__ */ jsx2("table", { ...props }) }) });
63
65
  }
64
66
 
65
67
  // src/components/blocks/text-block/sanitize.ts
@@ -190,41 +192,47 @@ var TextBlockWrapper = styled2.div`
190
192
  `;
191
193
 
192
194
  // src/components/blocks/text-block/text-block.tsx
193
- import { jsx as jsx2 } from "react/jsx-runtime";
195
+ import { jsx as jsx3 } from "react/jsx-runtime";
196
+ var markdownComponents = {
197
+ table: ((tableProps) => /* @__PURE__ */ jsx3(ScrollableTable, { ...tableProps }))
198
+ };
194
199
  function TextBlock({ block }) {
195
- const counter = { value: 0 };
196
- const markdownComponents = {
197
- table: ((tableProps) => /* @__PURE__ */ jsx2(ScrollableTable, { ...tableProps, counter }))
198
- };
199
- return /* @__PURE__ */ jsx2(TextBlockWrapper, { children: /* @__PURE__ */ jsx2(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], components: markdownComponents, children: sanitize(block.payload.text) }) });
200
+ return /* @__PURE__ */ jsx3(TextBlockWrapper, { children: /* @__PURE__ */ jsx3(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], components: markdownComponents, children: sanitize(block.payload.text) }) });
200
201
  }
201
202
 
202
203
  // src/components/blocks/block-services/text-block-service.ts
203
204
  var TextBlockService = {
204
205
  type: blockTypes.TEXT,
205
- Component: TextBlock
206
+ Component: TextBlock,
207
+ /**
208
+ * Converts the block's markdown source to plain text so the final clipboard value is clean
209
+ * when pasted into tools that don't render markdown. Each block type owns its own clipboard
210
+ * conversion so `useCopyToClipboard` can write text verbatim without a second pass.
211
+ */
212
+ toClipboardText: (block) => markdownToPlainText(block.payload.text)
206
213
  };
207
214
 
208
215
  // src/components/blocks/block-services/index.ts
209
216
  var BlockServices = {
210
- [TextBlockService.type]: TextBlockService
217
+ [blockTypes.TEXT]: TextBlockService,
218
+ [blockTypes.ARTIFACT_REF]: ArtifactRefBlockService
211
219
  };
212
220
 
213
221
  // src/components/blocks/block/block.tsx
214
- import { jsx as jsx3 } from "react/jsx-runtime";
222
+ import { jsx as jsx4 } from "react/jsx-runtime";
215
223
  function Block({ block }) {
216
224
  const service = BlockServices[block.type];
217
225
  if (!service) return null;
218
226
  const { Component } = service;
219
- return /* @__PURE__ */ jsx3(Component, { block });
227
+ return /* @__PURE__ */ jsx4(Component, { block });
220
228
  }
221
229
 
222
230
  export {
223
- BlockContext,
224
- useBlockContext,
225
231
  blockTypes,
232
+ ArtifactRefBlock,
233
+ ArtifactRefBlockService,
226
234
  TextBlockService,
227
235
  BlockServices,
228
236
  Block
229
237
  };
230
- //# sourceMappingURL=chunk-R3BGPOAM.js.map
238
+ //# sourceMappingURL=chunk-MXOPG747.js.map
@@ -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/text-block/text-block.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/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 { ArtifactRecord } from \"@/components/artifacts/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} 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 */\nexport interface ArtifactRefBlockRecord {\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: ArtifactRecord;\n };\n}\n\n/** Discriminated union of all supported content blocks. Additive — new types extend this union. */\nexport type BlockRecord = TextBlockRecord | ArtifactRefBlockRecord;\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 { 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 * This is the single bridge between the block layer and the artifact layer. It does the\n * `artifact.type → concrete component` lookup directly via the `ArtifactServices` map —\n * there is intentionally no separate `<ArtifactComponent>` JSX dispatcher in between.\n *\n * Unknown artifact types render as `null` (the map only contains types we know how to\n * render). Older clients receiving a future artifact type they don't yet support degrade\n * silently rather than crashing the chat.\n */\nexport function ArtifactRefBlock({ block }: BlockComponentProps<ArtifactRefBlockRecord>): ReactNode {\n const { artifact } = block.payload;\n const entry = ArtifactServices[artifact.type];\n if (!entry) return null;\n const { Component } = entry;\n return <Component artifact={artifact} />;\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 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 type { BlockComponentProps, TextBlockRecord } from \"../types\";\nimport { sanitize } from \"./sanitize\";\nimport { TextBlockWrapper } from \"./text-block-styles\";\n\nconst markdownComponents = {\n table: ((tableProps: { node?: unknown; children?: ReactNode }) => (\n <ScrollableTable {...tableProps} />\n )) as React.ComponentType<unknown>,\n};\n\n/** Renders a TEXT block as Markdown with GFM (tables, strikethrough) and line breaks. */\nexport function TextBlock({ block }: BlockComponentProps<TextBlockRecord>): ReactNode {\n return (\n <TextBlockWrapper>\n <ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]} components={markdownComponents}>\n {sanitize(block.payload.text)}\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 & 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 { 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 { 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};\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/** Dispatches a single block to its registered concrete component. Unknown types render as a no-op. */\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"]}
@@ -5,6 +5,9 @@ import {
5
5
  colors
6
6
  } from "./chunk-JF3P66JF.js";
7
7
 
8
+ // src/components/icon-button/icon-button.tsx
9
+ import { forwardRef } from "react";
10
+
8
11
  // src/components/icon-button/icon-button-styles.ts
9
12
  import styled from "styled-components";
10
13
  var PADDING = {
@@ -54,7 +57,7 @@ var ICON_SIZE_BY_BUTTON_SIZE = {
54
57
  small: 16,
55
58
  medium: 20
56
59
  };
57
- function IconButton({
60
+ var IconButton = forwardRef(function IconButton2({
58
61
  icon,
59
62
  size = "small",
60
63
  variant = "short",
@@ -64,11 +67,14 @@ function IconButton({
64
67
  className,
65
68
  id,
66
69
  as,
67
- "aria-label": ariaLabel
68
- }) {
70
+ "aria-label": ariaLabel,
71
+ ...rest
72
+ }, ref) {
73
+ const isNativeButton = as === void 0 || as === "button";
69
74
  return /* @__PURE__ */ jsx(
70
75
  Button,
71
76
  {
77
+ ref,
72
78
  className,
73
79
  id,
74
80
  as,
@@ -78,12 +84,14 @@ function IconButton({
78
84
  disabled,
79
85
  onClick: disabled ? void 0 : onClick,
80
86
  "aria-label": ariaLabel,
87
+ ...isNativeButton ? { type: "button" } : {},
88
+ ...rest,
81
89
  children: /* @__PURE__ */ jsx(Icon, { icon, size: ICON_SIZE_BY_BUTTON_SIZE[size] })
82
90
  }
83
91
  );
84
- }
92
+ });
85
93
 
86
94
  export {
87
95
  IconButton
88
96
  };
89
- //# sourceMappingURL=chunk-MLCRDVQ2.js.map
97
+ //# sourceMappingURL=chunk-OBOXCBDL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/icon-button/icon-button.tsx","../src/components/icon-button/icon-button-styles.ts"],"sourcesContent":["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/** Ghost-style icon 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 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 $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 styled from \"styled-components\";\n\nimport { colors } from \"../../tokens/colors\";\nimport type { 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\nexport const Button = styled.button<{\n $size: IconButtonSize;\n $variant: IconButtonVariant;\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 background: ${({ $isSelected }) => ($isSelected ? colors[\"brown-40\"] : \"transparent\")};\n color: ${colors[\"brown-100\"]};\n cursor: pointer;\n transition: background-color 100ms ease;\n\n &:hover {\n background: ${({ $isSelected }) => ($isSelected ? colors[\"brown-40\"] : colors[\"brown-20\"])};\n }\n\n &:active {\n background: ${colors[\"brown-40\"]};\n }\n\n &:disabled {\n color: ${colors[\"brown-50\"]};\n background: transparent;\n cursor: not-allowed;\n }\n`;\n"],"mappings":";;;;;;;;AAAA,SAAS,kBAAkB;;;ACA3B,OAAO,YAAY;AAKnB,IAAM,UAAqE;AAAA,EACzE,OAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;AAEA,IAAM,gBAAgD;AAAA,EACpD,OAAO;AAAA,EACP,QAAQ;AACV;AAEO,IAAM,SAAS,OAAO;AAAA;AAAA;AAAA;AAAA,aAQhB,CAAC,EAAE,OAAO,SAAS,MAAM,QAAQ,QAAQ,EAAE,KAAK,CAAC;AAAA;AAAA,mBAE3C,CAAC,EAAE,MAAM,MAAM,cAAc,KAAK,CAAC;AAAA,gBACtC,CAAC,EAAE,YAAY,MAAO,cAAc,OAAO,UAAU,IAAI,aAAc;AAAA,WAC5E,OAAO,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,kBAKZ,CAAC,EAAE,YAAY,MAAO,cAAc,OAAO,UAAU,IAAI,OAAO,UAAU,CAAE;AAAA;AAAA;AAAA;AAAA,kBAI5E,OAAO,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA,aAIvB,OAAO,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;;;ADEzB;AA1CN,IAAM,2BAA2D;AAAA,EAC/D,OAAO;AAAA,EACP,QAAQ;AACV;AAIO,IAAM,aAAa,WAA+C,SAASA,YAChF;AAAA,EACE;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV,aAAa;AAAA,EACb,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,GAAG;AACL,GACA,KACA;AAIA,QAAM,iBAAiB,OAAO,UAAa,OAAO;AAClD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,MACb;AAAA,MACA,SAAS,WAAW,SAAY;AAAA,MAChC,cAAY;AAAA,MACX,GAAI,iBAAiB,EAAE,MAAM,SAAkB,IAAI,CAAC;AAAA,MACpD,GAAG;AAAA,MAEJ,8BAAC,QAAK,MAAY,MAAM,yBAAyB,IAAI,GAAG;AAAA;AAAA,EAC1D;AAEJ,CAAC;","names":["IconButton"]}
@@ -1,17 +1,20 @@
1
- import {
2
- markdownToPlainText
3
- } from "./chunk-NMKKU2UG.js";
4
1
  import {
5
2
  Block,
6
- BlockContext
7
- } from "./chunk-R3BGPOAM.js";
8
- import {
9
- IconButton
10
- } from "./chunk-MLCRDVQ2.js";
3
+ BlockServices
4
+ } from "./chunk-MXOPG747.js";
11
5
  import {
12
6
  typographyMixin,
13
7
  typographyTypes
14
8
  } from "./chunk-SV24ONND.js";
9
+ import {
10
+ IconCheck,
11
+ IconCopy,
12
+ IconThumbDown,
13
+ IconThumbUp
14
+ } from "./chunk-BX5TCEPR.js";
15
+ import {
16
+ IconButton
17
+ } from "./chunk-OBOXCBDL.js";
15
18
  import {
16
19
  colors
17
20
  } from "./chunk-JF3P66JF.js";
@@ -19,6 +22,23 @@ import {
19
22
  // src/components/chat/chat-message/chat-message.tsx
20
23
  import { useMemo } from "react";
21
24
 
25
+ // src/components/chat/hooks/copy-message-text.ts
26
+ function copyMessageText(message) {
27
+ const parts = [];
28
+ for (const block of message.blocks) {
29
+ const service = BlockServices[block.type];
30
+ if (!service) continue;
31
+ if (!service.toClipboardText) continue;
32
+ const text = service.toClipboardText(block);
33
+ if (text) parts.push(text);
34
+ }
35
+ if (parts.length === 0) return message.fallbackText;
36
+ return parts.join("\n\n");
37
+ }
38
+
39
+ // src/components/chat/chat-message-actions/chat-message-actions.tsx
40
+ import { useCallback as useCallback2 } from "react";
41
+
22
42
  // src/components/chat/chat-context/chat-context.ts
23
43
  import { createContext, useContext } from "react";
24
44
  var ChatContext = createContext(null);
@@ -30,81 +50,6 @@ function useChatContext() {
30
50
  return context;
31
51
  }
32
52
 
33
- // src/components/chat/chat-message-actions/chat-message-actions.tsx
34
- import { useCallback as useCallback2 } from "react";
35
-
36
- // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
37
- import { forwardRef, createElement } from "react";
38
-
39
- // node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs
40
- var defaultAttributes = {
41
- outline: {
42
- xmlns: "http://www.w3.org/2000/svg",
43
- width: 24,
44
- height: 24,
45
- viewBox: "0 0 24 24",
46
- fill: "none",
47
- stroke: "currentColor",
48
- strokeWidth: 2,
49
- strokeLinecap: "round",
50
- strokeLinejoin: "round"
51
- },
52
- filled: {
53
- xmlns: "http://www.w3.org/2000/svg",
54
- width: 24,
55
- height: 24,
56
- viewBox: "0 0 24 24",
57
- fill: "currentColor",
58
- stroke: "none"
59
- }
60
- };
61
-
62
- // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
63
- var createReactComponent = (type, iconName, iconNamePascal, iconNode) => {
64
- const Component = forwardRef(
65
- ({ color = "currentColor", size = 24, stroke = 2, title, className, children, ...rest }, ref) => createElement(
66
- "svg",
67
- {
68
- ref,
69
- ...defaultAttributes[type],
70
- width: size,
71
- height: size,
72
- className: [`tabler-icon`, `tabler-icon-${iconName}`, className].join(" "),
73
- ...type === "filled" ? {
74
- fill: color
75
- } : {
76
- strokeWidth: stroke,
77
- stroke: color
78
- },
79
- ...rest
80
- },
81
- [
82
- title && createElement("title", { key: "svg-title" }, title),
83
- ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
84
- ...Array.isArray(children) ? children : [children]
85
- ]
86
- )
87
- );
88
- Component.displayName = `${iconNamePascal}`;
89
- return Component;
90
- };
91
-
92
- // node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs
93
- var __iconNode = [["path", { "d": "M5 12l5 5l10 -10", "key": "svg-0" }]];
94
- var IconCheck = createReactComponent("outline", "check", "Check", __iconNode);
95
-
96
- // node_modules/@tabler/icons-react/dist/esm/icons/IconCopy.mjs
97
- 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" }]];
98
- var IconCopy = createReactComponent("outline", "copy", "Copy", __iconNode2);
99
-
100
- // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbDown.mjs
101
- var __iconNode3 = [["path", { "d": "M7 13v-8a1 1 0 0 0 -1 -1h-2a1 1 0 0 0 -1 1v7a1 1 0 0 0 1 1h3a4 4 0 0 1 4 4v1a2 2 0 0 0 4 0v-5h3a2 2 0 0 0 2 -2l-1 -5a2 3 0 0 0 -2 -2h-7a3 3 0 0 0 -3 3", "key": "svg-0" }]];
102
- var IconThumbDown = createReactComponent("outline", "thumb-down", "ThumbDown", __iconNode3);
103
-
104
- // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbUp.mjs
105
- var __iconNode4 = [["path", { "d": "M7 11v8a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-7a1 1 0 0 1 1 -1h3a4 4 0 0 0 4 -4v-1a2 2 0 0 1 4 0v5h3a2 2 0 0 1 2 2l-1 5a2 3 0 0 1 -2 2h-7a3 3 0 0 1 -3 -3", "key": "svg-0" }]];
106
- var IconThumbUp = createReactComponent("outline", "thumb-up", "ThumbUp", __iconNode4);
107
-
108
53
  // src/components/chat/hooks/use-copy-to-clipboard.ts
109
54
  import { useState, useCallback, useRef, useEffect } from "react";
110
55
 
@@ -127,7 +72,7 @@ function useCopyToClipboard() {
127
72
  };
128
73
  }, []);
129
74
  const copy = useCallback((text) => {
130
- copyToClipboard(markdownToPlainText(text), {
75
+ copyToClipboard(text, {
131
76
  onSuccess: () => {
132
77
  setIsCopied(true);
133
78
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -238,26 +183,28 @@ var MessageBubble = styled2.div`
238
183
  padding: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "12px 16px" : "0"};
239
184
  border-radius: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "16px" : "0"};
240
185
  max-width: 100%;
186
+ width: 100%;
241
187
  min-width: 0;
188
+ /* Vertical rhythm between consecutive blocks — renderer-owned, not agent-emitted.
189
+ * Industry standard (Claude, ChatGPT, Vercel AI Elements): the chat renderer applies
190
+ * spacing via CSS; agents emit semantic blocks only. Value matches the MessageContainer
191
+ * gap. TODO: replace 12px with a clay spacing token when a spacing scale is added. */
192
+ display: flex;
193
+ flex-direction: column;
194
+ gap: 12px;
242
195
  `;
243
196
 
244
197
  // src/components/chat/chat-message/chat-message.tsx
245
198
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
246
199
  function ChatMessage({ message }) {
247
- const { TableActions: ChatTableActions } = useChatContext();
248
- const blockContextValue = useMemo(
249
- () => ({
250
- TableActions: ChatTableActions ? ({ tableIndex }) => /* @__PURE__ */ jsx2(ChatTableActions, { messageId: message.id, tableIndex }) : void 0
251
- }),
252
- [ChatTableActions, message.id]
253
- );
200
+ const clipboardText = useMemo(() => copyMessageText(message), [message]);
254
201
  return /* @__PURE__ */ jsx2(MessageRow, { $role: message.role, role: "article", "aria-label": `${message.role} message`, children: /* @__PURE__ */ jsxs2(MessageContainer, { $role: message.role, children: [
255
- /* @__PURE__ */ jsx2(MessageBubble, { $role: message.role, children: /* @__PURE__ */ jsx2(BlockContext.Provider, { value: blockContextValue, children: message.blocks.map((block, index) => /* @__PURE__ */ jsx2(Block, { block }, index)) }) }),
202
+ /* @__PURE__ */ jsx2(MessageBubble, { $role: message.role, "data-testid": "message-bubble", children: message.blocks.map((block, index) => /* @__PURE__ */ jsx2(Block, { block }, index)) }),
256
203
  /* @__PURE__ */ jsx2(
257
204
  ChatMessageActions,
258
205
  {
259
206
  messageId: message.id,
260
- content: message.fallbackText,
207
+ content: clipboardText,
261
208
  role: message.role,
262
209
  isHelpful: message.isHelpful
263
210
  }
@@ -266,26 +213,11 @@ function ChatMessage({ message }) {
266
213
  }
267
214
 
268
215
  export {
216
+ copyMessageText,
269
217
  ChatContext,
270
218
  useChatContext,
271
219
  useCopyToClipboard,
272
220
  MESSAGE_ROLE,
273
221
  ChatMessage
274
222
  };
275
- /*! Bundled license information:
276
-
277
- @tabler/icons-react/dist/esm/defaultAttributes.mjs:
278
- @tabler/icons-react/dist/esm/createReactComponent.mjs:
279
- @tabler/icons-react/dist/esm/icons/IconCheck.mjs:
280
- @tabler/icons-react/dist/esm/icons/IconCopy.mjs:
281
- @tabler/icons-react/dist/esm/icons/IconThumbDown.mjs:
282
- @tabler/icons-react/dist/esm/icons/IconThumbUp.mjs:
283
- @tabler/icons-react/dist/esm/tabler-icons-react.mjs:
284
- (**
285
- * @license @tabler/icons-react v3.37.1 - MIT
286
- *
287
- * This source code is licensed under the MIT license.
288
- * See the LICENSE file in the root directory of this source tree.
289
- *)
290
- */
291
- //# sourceMappingURL=chunk-WS4IPADR.js.map
223
+ //# sourceMappingURL=chunk-OUW6PUEB.js.map