@ai-matrx/design-system 0.17.3 → 0.18.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 (54) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +1 -0
  3. package/dist/content-transfer/alchemy.svg +1 -0
  4. package/dist/content-transfer/icon.cjs +28 -0
  5. package/dist/content-transfer/icon.cjs.map +1 -0
  6. package/dist/content-transfer/icon.d.cts +4 -0
  7. package/dist/content-transfer/icon.d.ts +4 -0
  8. package/dist/content-transfer/icon.js +8 -0
  9. package/dist/content-transfer/icon.js.map +1 -0
  10. package/dist/content-transfer.cjs +1978 -0
  11. package/dist/content-transfer.cjs.map +1 -0
  12. package/dist/content-transfer.css +382 -0
  13. package/dist/content-transfer.d.cts +208 -0
  14. package/dist/content-transfer.d.ts +208 -0
  15. package/dist/content-transfer.js +1974 -0
  16. package/dist/content-transfer.js.map +1 -0
  17. package/dist/data-table/copy-helpers.d.cts +1 -1
  18. package/dist/data-table/copy-helpers.d.ts +1 -1
  19. package/dist/data-table/copy-types.cjs.map +1 -1
  20. package/dist/data-table/copy-types.d.cts +2 -0
  21. package/dist/data-table/copy-types.d.ts +2 -0
  22. package/dist/data-table/data-row-inspector.cjs +1603 -319
  23. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  24. package/dist/data-table/data-row-inspector.js +1634 -324
  25. package/dist/data-table/data-row-inspector.js.map +1 -1
  26. package/dist/data-table/data-row-window.cjs +1531 -245
  27. package/dist/data-table/data-row-window.cjs.map +1 -1
  28. package/dist/data-table/data-row-window.js +1561 -249
  29. package/dist/data-table/data-row-window.js.map +1 -1
  30. package/dist/data-table/filter-engine.d.cts +1 -1
  31. package/dist/data-table/filter-engine.d.ts +1 -1
  32. package/dist/data-table/index.cjs +2558 -1146
  33. package/dist/data-table/index.cjs.map +1 -1
  34. package/dist/data-table/index.d.cts +2 -2
  35. package/dist/data-table/index.d.ts +2 -2
  36. package/dist/data-table/index.js +2589 -1151
  37. package/dist/data-table/index.js.map +1 -1
  38. package/dist/data-table/infer-filter.d.cts +1 -1
  39. package/dist/data-table/infer-filter.d.ts +1 -1
  40. package/dist/data-table/layered-filters.d.cts +1 -1
  41. package/dist/data-table/layered-filters.d.ts +1 -1
  42. package/dist/data-table/query-control.d.cts +1 -1
  43. package/dist/data-table/query-control.d.ts +1 -1
  44. package/dist/data-table/types.cjs.map +1 -1
  45. package/dist/data-table/types.d.cts +1 -1
  46. package/dist/data-table/types.d.ts +1 -1
  47. package/dist/data-table/url-state.d.cts +1 -1
  48. package/dist/data-table/url-state.d.ts +1 -1
  49. package/dist/data-table/xlsx.d.cts +1 -1
  50. package/dist/data-table/xlsx.d.ts +1 -1
  51. package/dist/styles.css +2 -0
  52. package/dist/{types-pZ95iFk5.d.ts → types-BhSkeZQ_.d.ts} +14 -0
  53. package/dist/{types-4fmLG5AN.d.cts → types-CD4xGL5Z.d.cts} +14 -0
  54. package/package.json +25 -2
@@ -35,7 +35,7 @@ __export(DataRowWindow_exports, {
35
35
  DataRowWindow: () => DataRowWindow
36
36
  });
37
37
  module.exports = __toCommonJS(DataRowWindow_exports);
38
- var import_react5 = require("react");
38
+ var import_react4 = require("react");
39
39
 
40
40
  // src/dialog.tsx
41
41
  var DialogPrimitive = __toESM(require("@radix-ui/react-dialog"), 1);
@@ -95,6 +95,48 @@ function XIcon(props) {
95
95
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })
96
96
  ] });
97
97
  }
98
+ function radixProps(props) {
99
+ return {
100
+ xmlns: "http://www.w3.org/2000/svg",
101
+ viewBox: "0 0 15 15",
102
+ width: 15,
103
+ height: 15,
104
+ fill: "none",
105
+ "aria-hidden": true,
106
+ ...props
107
+ };
108
+ }
109
+ function CheckIcon(props) {
110
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...radixProps(props), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
111
+ "path",
112
+ {
113
+ d: "M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z",
114
+ fill: "currentColor",
115
+ fillRule: "evenodd",
116
+ clipRule: "evenodd"
117
+ }
118
+ ) });
119
+ }
120
+ function DotFilledIcon(props) {
121
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...radixProps(props), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
122
+ "path",
123
+ {
124
+ d: "M9.875 7.5C9.875 8.81168 8.81168 9.875 7.5 9.875C6.18832 9.875 5.125 8.81168 5.125 7.5C5.125 6.18832 6.18832 5.125 7.5 5.125C8.81168 5.125 9.875 6.18832 9.875 7.5Z",
125
+ fill: "currentColor"
126
+ }
127
+ ) });
128
+ }
129
+ function RadixChevronRightIcon(props) {
130
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...radixProps(props), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
131
+ "path",
132
+ {
133
+ d: "M6.1584 3.13508C6.35985 2.94621 6.67627 2.95642 6.86514 3.15788L10.6151 7.15788C10.7954 7.3502 10.7954 7.64949 10.6151 7.84182L6.86514 11.8418C6.67627 12.0433 6.35985 12.0535 6.1584 11.8646C5.95694 11.6757 5.94673 11.3593 6.1356 11.1579L9.565 7.49985L6.1356 3.84182C5.94673 3.64036 5.95694 3.32394 6.1584 3.13508Z",
134
+ fill: "currentColor",
135
+ fillRule: "evenodd",
136
+ clipRule: "evenodd"
137
+ }
138
+ ) });
139
+ }
98
140
 
99
141
  // src/portal-container.tsx
100
142
  var React = __toESM(require("react"), 1);
@@ -381,7 +423,6 @@ function Icon({ children, ...props }) {
381
423
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", ...props, children });
382
424
  }
383
425
  var Check = (p) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Icon, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m5 12 4 4L19 6" }) });
384
- var SlidersHorizontal = (p) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Icon, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M4 7h16M4 17h16M9 7v0M15 17v0" }) });
385
426
  var Pencil = (p) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Icon, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m4 20 4-1 11-11-3-3L5 16l-1 4z" }) });
386
427
  var Eye = (p) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Icon, { ...p, children: [
387
428
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M2 12s3-6 10-6 10 6 10 6-3 6-10 6S2 12 2 12z" }),
@@ -409,9 +450,13 @@ function useMatrxDataTableHost() {
409
450
  return (0, import_react.useContext)(HostContext);
410
451
  }
411
452
 
412
- // src/data-table/copy.tsx
413
- var import_react3 = require("react");
414
- var import_clipboard = require("@ai-matrx/kit/clipboard");
453
+ // src/content-transfer/matrx-copy.tsx
454
+ var React8 = __toESM(require("react"), 1);
455
+ var import_content_transfer2 = require("@ai-matrx/kit/content-transfer");
456
+
457
+ // src/content-transfer/index.tsx
458
+ var React7 = __toESM(require("react"), 1);
459
+ var import_content_transfer = require("@ai-matrx/kit/content-transfer");
415
460
 
416
461
  // src/button.tsx
417
462
  var import_react_slot = require("@radix-ui/react-slot");
@@ -484,230 +529,1471 @@ var Button = React5.forwardRef(
484
529
  );
485
530
  Button.displayName = "Button";
486
531
 
487
- // src/popover.tsx
488
- var PopoverPrimitive = __toESM(require("@radix-ui/react-popover"), 1);
532
+ // src/dropdown-menu.tsx
533
+ var DropdownMenuPrimitive = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
489
534
  var React6 = __toESM(require("react"), 1);
490
535
  var import_jsx_runtime9 = require("react/jsx-runtime");
491
- var Popover = PopoverPrimitive.Root;
492
- var PopoverTrigger = PopoverPrimitive.Trigger;
493
- var PopoverContent = React6.forwardRef(
494
- ({
495
- className,
496
- align = "center",
497
- sideOffset = 4,
498
- container,
499
- animated = true,
536
+ var DropdownMenu = DropdownMenuPrimitive.Root;
537
+ var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
538
+ var MENU_SURFACE_CLASS = "z-[10001] min-w-[8rem] max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto overflow-x-hidden overscroll-contain rounded-md border bg-popover p-1 text-popover-foreground";
539
+ var MENU_ITEM_CLASS = "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
540
+ var MENU_INDICATOR_ITEM_CLASS = "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
541
+ var DropdownMenuSubTrigger = React6.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
542
+ DropdownMenuPrimitive.SubTrigger,
543
+ {
544
+ ref,
545
+ "data-slot": "dropdown-menu-sub-trigger",
546
+ className: cn(
547
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
548
+ inset && "pl-8",
549
+ className
550
+ ),
551
+ ...props,
552
+ children: [
553
+ children,
554
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RadixChevronRightIcon, { className: "ml-auto h-4 w-4" })
555
+ ]
556
+ }
557
+ ));
558
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
559
+ var DropdownMenuSubContent = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
560
+ DropdownMenuPrimitive.SubContent,
561
+ {
562
+ ref,
563
+ "data-slot": "dropdown-menu-sub-content",
564
+ className: cn(MENU_SURFACE_CLASS, "shadow-lg", MOTION_POPPER, className),
565
+ ...props
566
+ }
567
+ ));
568
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
569
+ var DropdownMenuContent = React6.forwardRef(({ className, sideOffset = 4, container, ...props }, ref) => {
570
+ const portalContainer = usePortalContainer(container);
571
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DropdownMenuPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
572
+ DropdownMenuPrimitive.Content,
573
+ {
574
+ ref,
575
+ "data-slot": "dropdown-menu-content",
576
+ sideOffset,
577
+ className: cn(
578
+ MENU_SURFACE_CLASS,
579
+ "shadow-md",
580
+ MOTION_POPPER,
581
+ className
582
+ ),
583
+ ...props
584
+ }
585
+ ) });
586
+ });
587
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
588
+ var DropdownMenuItem = React6.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
589
+ DropdownMenuPrimitive.Item,
590
+ {
591
+ ref,
592
+ "data-slot": "dropdown-menu-item",
593
+ className: cn(MENU_ITEM_CLASS, inset && "pl-8", className),
594
+ ...props
595
+ }
596
+ ));
597
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
598
+ var DropdownMenuCheckboxItem = React6.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
599
+ DropdownMenuPrimitive.CheckboxItem,
600
+ {
601
+ ref,
602
+ "data-slot": "dropdown-menu-checkbox-item",
603
+ className: cn(MENU_INDICATOR_ITEM_CLASS, className),
604
+ ...props,
605
+ children: [
606
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CheckIcon, { className: "h-4 w-4" }) }) }),
607
+ children
608
+ ]
609
+ }
610
+ ));
611
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
612
+ var DropdownMenuRadioItem = React6.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
613
+ DropdownMenuPrimitive.RadioItem,
614
+ {
615
+ ref,
616
+ "data-slot": "dropdown-menu-radio-item",
617
+ className: cn(MENU_INDICATOR_ITEM_CLASS, className),
618
+ ...props,
619
+ children: [
620
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DotFilledIcon, { className: "h-4 w-4 fill-current" }) }) }),
621
+ children
622
+ ]
623
+ }
624
+ ));
625
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
626
+ var DropdownMenuLabel = React6.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
627
+ DropdownMenuPrimitive.Label,
628
+ {
629
+ ref,
630
+ "data-slot": "dropdown-menu-label",
631
+ className: cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className),
632
+ ...props
633
+ }
634
+ ));
635
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
636
+ var DropdownMenuSeparator = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
637
+ DropdownMenuPrimitive.Separator,
638
+ {
639
+ ref,
640
+ "data-slot": "dropdown-menu-separator",
641
+ className: cn("-mx-1 my-1 h-px bg-muted", className),
642
+ ...props
643
+ }
644
+ ));
645
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
646
+ var DropdownMenuShortcut = ({
647
+ className,
648
+ ...props
649
+ }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
650
+ "span",
651
+ {
652
+ "data-slot": "dropdown-menu-shortcut",
653
+ className: cn("ml-auto text-xs tracking-widest opacity-60", className),
500
654
  ...props
501
- }, ref) => {
502
- const portalContainer = usePortalContainer(container);
503
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PopoverPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
504
- PopoverPrimitive.Content,
505
- {
506
- ref,
507
- align,
508
- sideOffset,
509
- className: cn(
510
- // Cap to the viewport space Radix measured and scroll — a popover taller
511
- // than the screen must never trap the user with unreachable content.
512
- // z must EQUAL the dialog layer (z-[10000]), never exceed it: with equal
513
- // z, DOM portal order decides, so a dialog opened FROM a popover stacks
514
- // above it, and a popover opened from a dialog still stacks above the
515
- // dialog. z-[10001] buried dialogs behind fullscreen popovers.
516
- "z-[10000] w-72 max-h-[var(--radix-popover-content-available-height)] overflow-y-auto overscroll-contain rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
517
- animated && MOTION_POPPER,
518
- className
519
- ),
520
- ...props
521
- }
522
- ) });
523
655
  }
524
656
  );
525
- PopoverContent.displayName = PopoverPrimitive.Content.displayName;
657
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
526
658
 
527
- // src/data-table/useTableNotice.tsx
528
- var import_react2 = require("react");
659
+ // src/content-transfer/index.tsx
529
660
  var import_jsx_runtime10 = require("react/jsx-runtime");
530
- function useTableNotice() {
531
- const host = useMatrxDataTableHost();
532
- const [message, setMessage] = (0, import_react2.useState)(null);
533
- const notify = {
534
- success(text) {
535
- if (host.notify) host.notify.success(text);
536
- else setMessage({ text, error: false });
537
- },
538
- error(text) {
539
- if (host.notify) host.notify.error(text);
540
- else setMessage({ text, error: true });
661
+ function AlchemyGlyph(props) {
662
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", ...props, children: [
663
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M4.5 5.5h8v11h-8z" }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M8.5 9.5h8v9h-8z" }),
665
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m18.5 3 .65 1.85L21 5.5l-1.85.65L18.5 8l-.65-1.85L16 5.5l1.85-.65z" })
666
+ ] });
667
+ }
668
+ var CapabilityContext = React7.createContext({});
669
+ var useContentTransferCapabilities = () => React7.useContext(CapabilityContext);
670
+ var SurfaceContext = React7.createContext(null);
671
+ function MenuPresentation({ label, hint, icon: Icon2 }) {
672
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
673
+ Icon2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon2, { className: "matrx-alchemy-menu-icon" }) : null,
674
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
675
+ label,
676
+ hint ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("small", { className: "matrx-alchemy-menu-hint", children: hint }) : null
677
+ ] })
678
+ ] });
679
+ }
680
+ var LABEL = { plain: "Plain text", markdown: "Markdown", html: "Formatted", json: "JSON", "compact-json": "Compact JSON", csv: "CSV", tsv: "Google Sheets", xlsx: "Excel workbook" };
681
+ var BUILT_INS = Object.freeze(Object.keys(LABEL).map((id) => ({ id })));
682
+ var COPIES = ["plain", "markdown", "html", "json", "compact-json", "csv", "tsv"];
683
+ var DOWNLOADS = [...COPIES, "xlsx"];
684
+ var EMPTY_VARIANTS = [];
685
+ var SCOPE_LABEL = { target: "This content", row: "This row", selected: "Selected rows", view: "Current view", loaded: "Loaded rows", "all-matching": "All matching rows" };
686
+ var filenameFor = (label) => label.toLocaleLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") || "export";
687
+ var estimateTokens = (text) => Math.ceil(text.length / 4);
688
+ var errorMessage = (error, fallback) => error instanceof Error ? error.message : fallback;
689
+ var isAbort = (error) => error instanceof DOMException && error.name === "AbortError";
690
+ var supportsBuiltIn = (format, payload) => !["csv", "tsv", "xlsx"].includes(format) || payload.kind === "rows";
691
+ var outcomeMessage = (result) => {
692
+ if (result.status === "success") {
693
+ const success = result;
694
+ return success.delivered === "action" ? success.message ?? "Action completed" : success.delivered === "download-started" ? "Download started" : success.message ?? "Copied";
695
+ }
696
+ return result.status === "degraded" ? `Copied as plain text: ${result.reason}` : result.status === "cancelled" ? "Cancelled" : result.message;
697
+ };
698
+ var materialIds = /* @__PURE__ */ new WeakMap();
699
+ var nextMaterialId = 0;
700
+ function materialIdentity(material) {
701
+ if (!material) return "missing";
702
+ if ("instanceId" in material) return `surface:${material.instanceId}`;
703
+ if ("id" in material) return `source:${material.id}`;
704
+ let id = materialIds.get(material);
705
+ if (id === void 0) {
706
+ id = ++nextMaterialId;
707
+ materialIds.set(material, id);
708
+ }
709
+ return `payload:${id}`;
710
+ }
711
+ function useFence() {
712
+ const mounted = React7.useRef(true);
713
+ const nextId = React7.useRef(0);
714
+ const active = React7.useRef(/* @__PURE__ */ new Set());
715
+ React7.useEffect(() => {
716
+ mounted.current = true;
717
+ return () => {
718
+ mounted.current = false;
719
+ nextId.current += 1;
720
+ active.current.forEach((c) => c.abort());
721
+ active.current.clear();
722
+ };
723
+ }, []);
724
+ const begin = React7.useCallback(() => {
725
+ const controller = new AbortController();
726
+ active.current.add(controller);
727
+ return { controller, id: ++nextId.current };
728
+ }, []);
729
+ const finish = React7.useCallback((controller) => active.current.delete(controller), []);
730
+ const valid = React7.useCallback((id, controller) => mounted.current && !controller.signal.aborted && nextId.current === id, []);
731
+ const abortAll = React7.useCallback(() => {
732
+ nextId.current += 1;
733
+ active.current.forEach((c) => c.abort());
734
+ active.current.clear();
735
+ }, []);
736
+ return { begin, finish, valid, abortAll };
737
+ }
738
+ async function xlsxArtifact(draft, label) {
739
+ if (draft.payload.kind !== "rows") throw new Error("Excel workbook export requires tabular content.");
740
+ const XLSX = await import("xlsx");
741
+ const columns = draft.payload.columns.filter((c) => c.visible && c.exportable !== false && c.classification !== "secret" && c.classification !== "credential");
742
+ const cells = draft.payload.rows.map((row) => columns.map((column) => {
743
+ const value = (0, import_content_transfer.readTransferCell)(row, column);
744
+ return value === null || ["string", "number", "boolean"].includes(typeof value) ? value : value === void 0 ? "" : JSON.stringify(value);
745
+ }));
746
+ const sheet = XLSX.utils.aoa_to_sheet([columns.map((c) => c.label), ...cells]);
747
+ const book = XLSX.utils.book_new();
748
+ XLSX.utils.book_append_sheet(book, sheet, label.replace(/[\\/?*[\]:]/g, " ").trim().slice(0, 31) || "Data");
749
+ const written = XLSX.write(book, { type: "array", bookType: "xlsx", compression: true });
750
+ return (0, import_content_transfer.sealTransferArtifact)({ snapshotId: draft.snapshotId, draftRevision: draft.revision, format: "xlsx", plainText: (0, import_content_transfer.serialize)(draft, "tsv", { spreadsheetSafe: true }).plainText, file: { filename: `${filenameFor(label)}.xlsx`, mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", bytes: written instanceof Uint8Array ? written : new Uint8Array(written) }, omissions: [...draft.omissions] });
751
+ }
752
+ async function buildArtifact(draft, format, label) {
753
+ if (!supportsBuiltIn(format, draft.payload)) throw new Error(`${LABEL[format]} is available for tabular content only.`);
754
+ if (format === "xlsx") return xlsxArtifact(draft, label);
755
+ if (format === "html" && draft.payload.kind === "markdown") return (0, import_content_transfer.serializeMarkdownRich)(draft, `${filenameFor(label)}.html`);
756
+ const extension = format === "plain" ? "txt" : format === "compact-json" ? "json" : format;
757
+ return (0, import_content_transfer.serialize)(draft, format, { spreadsheetSafe: format === "csv" || format === "tsv", filename: `${filenameFor(label)}.${extension}` });
758
+ }
759
+ function ContentTransferMenu({ source, surface, label, projection, sections, preferences, table, formatSources, variants = EMPTY_VARIANTS, actions = [], capabilities: local, disabled, enabled = true, className, stopPropagation = true, hide = [], controllerRef }) {
760
+ const triggerRef = React7.useRef(null);
761
+ const inherited = React7.useContext(CapabilityContext);
762
+ const nearestSurface = React7.useContext(SurfaceContext);
763
+ const fence = useFence();
764
+ const [menuCapture, setMenuCapture] = React7.useState(null);
765
+ const [menuStatus, setMenuStatus] = React7.useState("idle");
766
+ const [workspaces, setWorkspaces] = React7.useState({});
767
+ const [workspaceKey, setWorkspaceKey] = React7.useState(null);
768
+ const [workspaceOpen, setWorkspaceOpen] = React7.useState(false);
769
+ const workspaceCaptures = React7.useRef(/* @__PURE__ */ new Map());
770
+ const externalWorkspaceKeys = React7.useRef(/* @__PURE__ */ new Set());
771
+ const [notice, setNotice] = React7.useState(null);
772
+ const scopes = React7.useMemo(() => table?.availableScopes ?? ["target"], [table]);
773
+ const initialScope = table?.initialScope ?? "target";
774
+ if (!scopes.includes(initialScope)) throw new Error("Alchemy initialScope must be included in availableScopes.");
775
+ const primary = surface ?? source ?? nearestSurface;
776
+ const sourceLabel = label ?? (surface?.surfaceName ?? (source && "label" in source ? source.label : nearestSurface?.surfaceName) ?? "content");
777
+ const primaryWorkspaceKey = `primary:${materialIdentity(primary)}`;
778
+ const nearestWorkspaceKey = source && !surface && nearestSurface ? `surface:${materialIdentity(nearestSurface)}` : null;
779
+ const variantWorkspaceKeys = React7.useMemo(() => new Map(variants.map((variant) => [variant.id, `variant:${variant.id}:${materialIdentity(variant.source)}`])), [variants]);
780
+ const transport = local?.transport ?? inherited.transport ?? (0, import_content_transfer.createBrowserTransport)();
781
+ const ai = local?.ai ?? inherited.ai;
782
+ const providerPreferences = React7.useMemo(() => (0, import_content_transfer.resolveTransferPreferences)(inherited.preferences, local?.preferences), [inherited.preferences, local?.preferences]);
783
+ const adapters = React7.useMemo(() => (0, import_content_transfer.resolveTransferRegistry)(BUILT_INS, inherited.formats ?? [], local?.formats ?? []).filter((x) => "build" in x), [inherited.formats, local?.formats]);
784
+ const registeredActions = React7.useMemo(() => (0, import_content_transfer.resolveTransferRegistry)(inherited.actions ?? [], local?.actions ?? [], actions), [actions, inherited.actions, local?.actions]);
785
+ React7.useMemo(() => (0, import_content_transfer.resolveTransferRegistry)(variants), [variants]);
786
+ const report = React7.useCallback((result) => {
787
+ inherited.onOutcome?.(result);
788
+ local?.onOutcome?.(result);
789
+ setNotice(outcomeMessage(result));
790
+ }, [inherited, local]);
791
+ React7.useEffect(() => {
792
+ const valid = /* @__PURE__ */ new Set([primaryWorkspaceKey, ...nearestWorkspaceKey ? [nearestWorkspaceKey] : [], ...variantWorkspaceKeys.values(), ...externalWorkspaceKeys.current]);
793
+ setWorkspaces((current) => Object.fromEntries(Object.entries(current).filter(([key]) => valid.has(key))));
794
+ for (const key of workspaceCaptures.current.keys()) if (!valid.has(key)) workspaceCaptures.current.delete(key);
795
+ if (workspaceKey && !valid.has(workspaceKey)) {
796
+ setWorkspaceKey(null);
797
+ setWorkspaceOpen(false);
541
798
  }
542
- };
543
- const notice = message ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { role: message.error ? "alert" : "status", className: message.error ? "text-xs text-destructive" : "text-xs text-muted-foreground", children: [
544
- message.text,
545
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", "aria-label": "Dismiss notification", className: "ml-2 underline", onClick: () => setMessage(null), children: "Dismiss" })
546
- ] }) : null;
547
- return { notify, notice };
799
+ }, [nearestWorkspaceKey, primaryWorkspaceKey, variantWorkspaceKeys, workspaceKey]);
800
+ const captureOne = React7.useCallback(async (material, scope, signal, applyMenuProjection = false, collectPages = true) => {
801
+ if (!material) throw new Error("Alchemy needs a source or a mounted transfer surface.");
802
+ let sourceMaterial;
803
+ if ("instanceId" in material) {
804
+ if (!material.isMounted()) throw new Error("The requested transfer surface is no longer mounted.");
805
+ const captured = await material.capture(signal);
806
+ sourceMaterial = { id: captured.snapshot.sourceId, label: captured.snapshot.label, capture: async () => captured };
807
+ } else if ("kind" in material) {
808
+ sourceMaterial = applyMenuProjection ? { id: "direct", label: sourceLabel, capture: async () => (0, import_content_transfer.directSource)(material, { label: sourceLabel, ...projection ? { projection } : {}, ...sections ? { sections } : {} }) } : material;
809
+ } else if (applyMenuProjection && (projection || sections)) {
810
+ sourceMaterial = {
811
+ id: material.id,
812
+ label: material.label,
813
+ capture: async (request) => {
814
+ const captured = await material.capture(request);
815
+ return {
816
+ ...captured,
817
+ projection: { rules: [...captured.projection.rules, ...projection?.rules ?? []] },
818
+ snapshot: { ...captured.snapshot, ...sections ? { sections } : {} }
819
+ };
820
+ }
821
+ };
822
+ } else {
823
+ sourceMaterial = material;
824
+ }
825
+ const raw = "kind" in sourceMaterial ? (0, import_content_transfer.directSource)(sourceMaterial) : await sourceMaterial.capture({ scope, signal });
826
+ const constrained = (0, import_content_transfer.resolveTransferPreferences)(providerPreferences, preferences, raw.snapshot.limits);
827
+ const result = await (0, import_content_transfer.capture)({ id: raw.snapshot.sourceId, label: raw.snapshot.label, capture: async () => ({ ...raw, snapshot: { ...raw.snapshot, limits: constrained.limits } }) }, signal, scope);
828
+ if (scope !== "all-matching" || !collectPages) return result;
829
+ if (!result.allMatching) throw new Error("This source cannot provide a stable all-matching export.");
830
+ const collected = await (0, import_content_transfer.collectAllMatching)(result.allMatching, signal);
831
+ if (result.snapshot.payload.kind !== "rows") throw new Error("All-matching export requires tabular content.");
832
+ return { snapshot: (0, import_content_transfer.projectSnapshot)({ ...result.snapshot, payload: { kind: "rows", rows: collected.rows, columns: result.snapshot.payload.columns }, coverage: collected.coverage, omissions: [...collected.omissions] }, { rules: [] }), allMatching: result.allMatching, ...collected.error ? { collectionError: collected.error.message } : {} };
833
+ }, [preferences, projection, providerPreferences, sections, sourceLabel]);
834
+ const capturePrimary = React7.useCallback((scope, signal) => captureOne(primary, scope, signal, true), [captureOne, primary]);
835
+ workspaceCaptures.current.set(primaryWorkspaceKey, capturePrimary);
836
+ if (nearestWorkspaceKey && nearestSurface) workspaceCaptures.current.set(nearestWorkspaceKey, (scope, signal) => captureOne(nearestSurface, scope, signal, true));
837
+ for (const variant of variants) {
838
+ const key = variantWorkspaceKeys.get(variant.id);
839
+ workspaceCaptures.current.set(key, (scope, signal) => captureOne(variant.source, scope, signal, true));
840
+ }
841
+ const preflight = React7.useCallback(async () => {
842
+ const { controller, id } = fence.begin();
843
+ setMenuCapture(null);
844
+ setMenuStatus("loading");
845
+ try {
846
+ const result = await captureOne(primary, initialScope, controller.signal, true, false);
847
+ if (fence.valid(id, controller)) {
848
+ setMenuCapture(result);
849
+ setMenuStatus("idle");
850
+ }
851
+ } catch (error) {
852
+ if (fence.valid(id, controller)) {
853
+ setMenuCapture(null);
854
+ setMenuStatus(errorMessage(error, "Could not inspect content"));
855
+ }
856
+ } finally {
857
+ fence.finish(controller);
858
+ }
859
+ }, [captureOne, fence, initialScope, primary]);
860
+ const transfer = React7.useCallback(async (format, download, alternate) => {
861
+ const { controller, id } = fence.begin();
862
+ try {
863
+ const explicit = alternate?.source ?? (typeof format === "string" && format !== "xlsx" ? formatSources?.[format] : void 0);
864
+ const result = await (explicit ? captureOne(explicit, initialScope, controller.signal, true) : capturePrimary(initialScope, controller.signal));
865
+ if (result.collectionError) throw new Error(`The complete source could not be collected: ${result.collectionError}`);
866
+ if (result.snapshot.coverage.status !== "complete" || result.snapshot.omissions.length > 0) {
867
+ const reason = result.snapshot.coverage.reason ?? `${result.snapshot.omissions.length} omission${result.snapshot.omissions.length === 1 ? "" : "s"}`;
868
+ throw new Error(`Quick transfer is partial (${reason}). Open Prepare for AI to review it before delivery.`);
869
+ }
870
+ const resolved = (0, import_content_transfer.resolveTransferPreferences)(providerPreferences, preferences, result.snapshot.limits);
871
+ const draft = (0, import_content_transfer.applyTransferLimits)((0, import_content_transfer.createDraft)(result.snapshot), resolved.limits);
872
+ const revision = draft.revision;
873
+ if (draft.omissions.length > result.snapshot.omissions.length) throw new Error("Quick transfer requires content reductions. Open Prepare for AI to review them before delivery.");
874
+ const artifact = typeof format === "string" ? await buildArtifact(draft, format, result.snapshot.label) : await format.build(draft, controller.signal);
875
+ if (artifact.omissions.length > draft.omissions.length) throw new Error(`Quick format requires ${artifact.omissions.length - draft.omissions.length} adjustment${artifact.omissions.length - draft.omissions.length === 1 ? "" : "s"}. Open Prepare for AI to review before delivery.`);
876
+ if (!fence.valid(id, controller) || artifact.snapshotId !== result.snapshot.id || artifact.draftRevision !== revision) return;
877
+ const outcome = await transport[download ? "download" : "copy"](artifact, controller.signal);
878
+ if (fence.valid(id, controller)) report(outcome.status === "success" && alternate?.successMessage ? { ...outcome, message: alternate.successMessage } : outcome);
879
+ } catch (error) {
880
+ if (fence.valid(id, controller)) report({ status: "error", code: "prepare-failed", message: errorMessage(error, "Preparation failed"), retryable: true });
881
+ } finally {
882
+ fence.finish(controller);
883
+ }
884
+ }, [captureOne, capturePrimary, fence, formatSources, initialScope, preferences, providerPreferences, report, transport]);
885
+ const openWorkspace = React7.useCallback(async (key = primaryWorkspaceKey, variant) => {
886
+ const variantMaterial = variant && "kind" in variant.source ? { id: `variant:${key}`, label: variant.label, capture: async () => (0, import_content_transfer.directSource)(variant.source, { label: variant.label }) } : variant?.source;
887
+ const captureWorkspace = variantMaterial ? (scope, signal) => captureOne(variantMaterial, scope, signal, true) : capturePrimary;
888
+ workspaceCaptures.current.set(key, captureWorkspace);
889
+ if (workspaces[key]) {
890
+ setWorkspaceKey(key);
891
+ setWorkspaceOpen(true);
892
+ return;
893
+ }
894
+ const { controller, id } = fence.begin();
895
+ try {
896
+ const result = await captureWorkspace(initialScope, controller.signal);
897
+ if (!fence.valid(id, controller)) return;
898
+ const resolved = (0, import_content_transfer.resolveTransferPreferences)(providerPreferences, preferences, result.snapshot.limits);
899
+ const original = (0, import_content_transfer.createDraft)(result.snapshot);
900
+ const base = { ...result, key, original, draft: original, scope: initialScope, preferences: resolved, ...result.collectionError ? { error: result.collectionError } : {} };
901
+ setWorkspaces((current) => ({ ...current, [key]: { ...base, draft: rebuild(base, initialControls(base)) } }));
902
+ setWorkspaceKey(key);
903
+ setWorkspaceOpen(true);
904
+ } catch (error) {
905
+ if (fence.valid(id, controller)) setNotice(errorMessage(error, "Could not capture content"));
906
+ } finally {
907
+ fence.finish(controller);
908
+ }
909
+ }, [captureOne, capturePrimary, fence, initialScope, preferences, primaryWorkspaceKey, providerPreferences, workspaces]);
910
+ React7.useImperativeHandle(controllerRef, () => ({
911
+ focusTrigger: () => triggerRef.current?.focus(),
912
+ prepare: async (requestedSource, options) => {
913
+ const requestedLabel = options?.label ?? ("label" in requestedSource ? requestedSource.label : "content");
914
+ const requestedKey = `controller:${options?.id ?? "source"}:${materialIdentity(requestedSource)}`;
915
+ externalWorkspaceKeys.current.add(requestedKey);
916
+ await openWorkspace(requestedKey, { source: requestedSource, label: requestedLabel });
917
+ }
918
+ }), [openWorkspace]);
919
+ const runAction = React7.useCallback(async (action) => {
920
+ const { controller, id } = fence.begin();
921
+ try {
922
+ const actionSource = action.source;
923
+ const { snapshot } = actionSource ? await captureOne(actionSource, initialScope, controller.signal, true) : await capturePrimary(initialScope, controller.signal);
924
+ const resolved = (0, import_content_transfer.resolveTransferPreferences)(providerPreferences, preferences, snapshot.limits);
925
+ const draft = (0, import_content_transfer.applyTransferLimits)((0, import_content_transfer.createDraft)(snapshot), resolved.limits);
926
+ if (!action.supports(snapshot)) throw new Error(`${action.label} is unavailable for this content.`);
927
+ const result = await action.run({ snapshot, draft, signal: controller.signal });
928
+ if (fence.valid(id, controller) && draft.snapshotId === snapshot.id) report(result);
929
+ } catch (error) {
930
+ if (fence.valid(id, controller)) report({ status: "error", code: "action-failed", message: errorMessage(error, "Action failed"), retryable: true });
931
+ } finally {
932
+ fence.finish(controller);
933
+ }
934
+ }, [captureOne, capturePrimary, fence, initialScope, preferences, providerPreferences, report]);
935
+ if (!enabled) return null;
936
+ const payload = menuCapture?.snapshot.payload ?? (source && "kind" in source ? source : void 0);
937
+ const quickVariants = (section) => variants.filter((v) => v.copyLabel && (v.section ?? "ai") === section).map((v) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuItem, { "aria-label": v.ariaLabel, onSelect: () => void transfer("markdown", false, v), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MenuPresentation, { ...v, label: v.copyLabel }) }, `copy:${v.id}`));
938
+ const visibleCopies = COPIES.filter((f) => Boolean(formatSources?.[f]) || !["csv", "tsv"].includes(f) || payload?.kind === "rows");
939
+ const visibleDownloads = DOWNLOADS.filter((f) => f !== "xlsx" && Boolean(formatSources?.[f]) || !["csv", "tsv", "xlsx"].includes(f) || payload?.kind === "rows");
940
+ const visibleAdapters = menuCapture ? adapters.filter((a) => a.supports(menuCapture.snapshot.payload)) : [];
941
+ const visibleActions = menuCapture ? registeredActions.filter((a) => a.supports(menuCapture.snapshot)) : [];
942
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: cn("matrx-alchemy", className), onClick: stopPropagation ? (event) => event.stopPropagation() : void 0, children: [
943
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DropdownMenu, { onOpenChange: (open) => {
944
+ if (open) void preflight();
945
+ }, children: [
946
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { ref: triggerRef, type: "button", className: "matrx-alchemy-trigger", disabled, "aria-label": `Copy, transform or export ${sourceLabel}`, title: "Alchemy \u2014 copy, transform, export", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AlchemyGlyph, {}) }) }),
947
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DropdownMenuContent, { align: "end", className: "matrx-alchemy-menu", children: [
948
+ menuStatus === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuItem, { disabled: true, children: "Inspecting content\u2026" }) : menuStatus !== "idle" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuItem, { disabled: true, children: menuStatus }) : null,
949
+ !hide.includes("copy") ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuLabel, { children: "Copy" }),
951
+ quickVariants("copy"),
952
+ visibleCopies.map((format) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuItem, { onSelect: () => void transfer(format, false), children: format === "plain" ? "Copy" : `Copy ${LABEL[format]}` }, format)),
953
+ visibleAdapters.map((adapter) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DropdownMenuItem, { onSelect: () => void transfer(adapter, false), children: [
954
+ "Copy ",
955
+ adapter.label
956
+ ] }, adapter.id))
957
+ ] }) : null,
958
+ !hide.includes("prepare") ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
959
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuSeparator, {}),
960
+ quickVariants("ai"),
961
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuItem, { onSelect: () => void openWorkspace(), children: "Prepare for AI\u2026" }),
962
+ nearestWorkspaceKey && nearestSurface ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuItem, { onSelect: () => void openWorkspace(nearestWorkspaceKey, { source: nearestSurface, label: nearestSurface.surfaceName }), children: "Prepare this surface\u2026" }) : null,
963
+ variants.map((variant) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuItem, { onSelect: () => void openWorkspace(variantWorkspaceKeys.get(variant.id), variant), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MenuPresentation, { ...variant, label: `Prepare ${variant.label}\u2026` }) }, variant.id))
964
+ ] }) : null,
965
+ !hide.includes("download") ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
966
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuSeparator, {}),
967
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuLabel, { children: "Download" }),
968
+ visibleDownloads.map((format) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DropdownMenuItem, { onSelect: () => void transfer(format, true), children: [
969
+ "Download ",
970
+ LABEL[format]
971
+ ] }, format)),
972
+ visibleAdapters.map((adapter) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DropdownMenuItem, { onSelect: () => void transfer(adapter, true), children: [
973
+ "Download ",
974
+ adapter.label
975
+ ] }, `download:${adapter.id}`))
976
+ ] }) : null,
977
+ visibleActions.length ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuSeparator, {}) : null,
978
+ visibleActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DropdownMenuItem, { "aria-label": action.ariaLabel, onSelect: () => void runAction(action), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MenuPresentation, { ...action, label: action.label }) }, action.id))
979
+ ] })
980
+ ] }),
981
+ notice ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "matrx-alchemy-notice", role: "status", children: notice }) : null,
982
+ Object.values(workspaces).map((saved) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PreparationWorkspace, { restoreFocus: () => triggerRef.current?.focus(), open: workspaceOpen && workspaceKey === saved.key, state: saved, scopes, onClose: () => setWorkspaceOpen(false), onChange: (next) => setWorkspaces((current) => ({ ...current, [saved.key]: next })), transport, adapters, captureScope: workspaceCaptures.current.get(saved.key) ?? capturePrimary, ...ai ? { ai } : {}, onOutcome: report }, saved.key))
983
+ ] });
548
984
  }
549
-
550
- // src/data-table/copy-payload.ts
551
- function tableAgentText(value) {
552
- if (typeof value === "string") return value;
553
- const attributes = Object.entries(value.attributes ?? {}).filter(([, entry]) => entry !== null && entry !== void 0 && entry !== "").map(([key, entry]) => ` ${key}="${String(entry)}"`).join("");
554
- const context = Object.entries(value.context ?? {}).filter(([, entry]) => entry !== null && entry !== void 0 && entry !== "").map(([key, entry]) => `<${key}>${String(entry)}</${key}>`).join("\n");
555
- const url = typeof window === "undefined" ? "" : window.location.href;
556
- const route = typeof window === "undefined" ? "" : window.location.pathname;
557
- return `<${value.kind}${attributes}>
558
- <context>
559
- <location>${value.location}</location>
560
- ${url ? `<url>${url}</url>` : ""}
561
- ${route ? `<route>${route}</route>` : ""}
562
- <copied>${value.description}</copied>
563
- <copied-at>${(/* @__PURE__ */ new Date()).toISOString()}</copied-at>
564
- ${context}
565
- </context>
566
- ${value.summary ? `<summary>
567
- ${value.summary}
568
- </summary>
569
- ` : ""}<data format="json">
570
- \`\`\`json
571
- ${JSON.stringify(value.data, null, 2)}
572
- \`\`\`
573
- </data>
574
- </${value.kind}>`;
985
+ function initialControls(state) {
986
+ const rows = state.original.payload.kind === "rows" ? state.original.payload : null;
987
+ return {
988
+ preset: state.preferences.initialPreset,
989
+ sections: state.snapshot.sections.filter((s) => s.includedByDefault).map((s) => s.id),
990
+ search: "",
991
+ filters: {},
992
+ sort: "",
993
+ direction: "asc",
994
+ top: state.preferences.limits.maxRows,
995
+ visibleColumns: rows?.columns.filter((c) => c.visible).map((c) => c.id) ?? [],
996
+ selectedOnly: false,
997
+ selectedIndices: [],
998
+ maxDepth: state.preferences.limits.maxDepth,
999
+ maxStringChars: state.preferences.limits.maxStringChars,
1000
+ maxArrayItems: state.preferences.limits.maxArrayItems ?? null
1001
+ };
575
1002
  }
576
-
577
- // src/data-table/copy.tsx
578
- var import_jsx_runtime11 = require("react/jsx-runtime");
579
- function resolve(value) {
580
- return typeof value === "function" ? value() : value;
1003
+ function presetLimits(preset, controls, preferences) {
1004
+ const min = (choice, cap) => choice == null ? cap : cap == null ? choice : Math.min(choice, cap);
1005
+ if (preset === "full") return preferences.limits;
1006
+ const request = preset === "brief" ? { maxDepth: 4, maxStringChars: 800, maxArrayItems: 25 } : preset === "compact" ? { maxDepth: 8, maxStringChars: 4e3, maxArrayItems: 100 } : { maxDepth: controls.maxDepth, maxStringChars: controls.maxStringChars, maxArrayItems: controls.maxArrayItems };
1007
+ return { ...preferences.limits, maxDepth: min(request.maxDepth, preferences.limits.maxDepth), maxStringChars: min(request.maxStringChars, preferences.limits.maxStringChars), maxArrayItems: min(request.maxArrayItems, preferences.limits.maxArrayItems ?? null) };
581
1008
  }
582
- function download(item) {
583
- if (!item.build) throw new Error(`No export action is available for ${item.label}.`);
584
- const file = item.build();
585
- const url = URL.createObjectURL(new Blob([file.content], { type: file.mime }));
586
- try {
587
- const link = document.createElement("a");
588
- link.href = url;
589
- link.download = `${item.label.replace(/[^a-z0-9]+/gi, "-")}.${file.extension}`;
590
- document.body.append(link);
591
- link.click();
592
- link.remove();
593
- } finally {
594
- URL.revokeObjectURL(url);
1009
+ function rebuild(state, controls) {
1010
+ let draft = (0, import_content_transfer.resetDraft)(state.snapshot);
1011
+ if (state.snapshot.sections.length) draft = (0, import_content_transfer.applySectionSelection)(draft, state.snapshot, controls.sections);
1012
+ if (draft.payload.kind === "rows") draft = (0, import_content_transfer.transformRows)(draft, {
1013
+ search: controls.search,
1014
+ filters: controls.filters,
1015
+ ...controls.sort ? { sort: { column: controls.sort, direction: controls.direction } } : {},
1016
+ ...controls.selectedOnly ? { selectedIndices: controls.selectedIndices } : {},
1017
+ visibleColumns: controls.visibleColumns,
1018
+ structuredProjection: "visible",
1019
+ top: controls.top
1020
+ });
1021
+ const limits = presetLimits(controls.preset, controls, state.preferences);
1022
+ if (draft.payload.kind === "json" || draft.payload.kind === "registered") {
1023
+ draft = (0, import_content_transfer.reduceJson)(draft, { maxDepth: limits.maxDepth, maxStringChars: limits.maxStringChars, maxArrayItems: limits.maxArrayItems ?? null });
595
1024
  }
1025
+ return (0, import_content_transfer.applyTransferLimits)(draft, limits);
596
1026
  }
597
- function CopyButtons(props) {
598
- const { CopyControls } = useMatrxDataTableHost();
599
- if (CopyControls) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CopyControls, { ...props });
600
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DefaultCopyButtons, { ...props });
1027
+ function isBuiltInFormat(format) {
1028
+ return format in LABEL;
601
1029
  }
602
- function DefaultCopyButtons({ human, agent, json, label, size = "icon", appearance = "segmented", disabled = false, stopPropagation = true, className, aiVariants = [], agentVariant, aiCustom, export: exportConfig, hide }) {
603
- const { notify, notice } = useTableNotice();
604
- const clipboard = (0, import_clipboard.useClipboard)({ notify: (message, kind) => notify[kind](message) });
605
- const [busy, setBusy] = (0, import_react3.useState)(false);
606
- const [modal, setModal] = (0, import_react3.useState)(null);
607
- const run = async (operation) => {
608
- if (busy) return;
609
- setBusy(true);
1030
+ function preview(draft, format) {
1031
+ const resolved = isBuiltInFormat(format) ? format : "plain";
1032
+ return (0, import_content_transfer.serialize)(draft, resolved === "xlsx" ? "tsv" : resolved, { spreadsheetSafe: resolved === "csv" || resolved === "tsv" || resolved === "xlsx" }).plainText;
1033
+ }
1034
+ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onChange, transport, ai, adapters, onOutcome, captureScope }) {
1035
+ const fence = useFence();
1036
+ const latest = React7.useRef(state);
1037
+ latest.current = state;
1038
+ const aiSession = React7.useRef(void 0);
1039
+ if (!aiSession.current && ai) aiSession.current = ai.fork?.() ?? ai;
1040
+ const [controls, setControls] = React7.useState(() => initialControls(state));
1041
+ const [controlsHistory, setControlsHistory] = React7.useState([]);
1042
+ const [pendingControls, setPendingControls] = React7.useState(null);
1043
+ const requestedFormat = state.preferences.initialFormat;
1044
+ const [format, setFormat] = React7.useState(() => isBuiltInFormat(requestedFormat) ? supportsBuiltIn(requestedFormat, state.draft.payload) ? requestedFormat : "plain" : adapters.some((adapter) => adapter.id === requestedFormat && adapter.supports(state.draft.payload)) ? requestedFormat : "plain");
1045
+ const [buffer, setBuffer] = React7.useState(() => isBuiltInFormat(format) ? preview(state.draft, format) : "Preparing format preview\u2026");
1046
+ const [bufferError, setBufferError] = React7.useState(null);
1047
+ const [formatError, setFormatError] = React7.useState(null);
1048
+ const [preparedArtifact, setPreparedArtifact] = React7.useState(null);
1049
+ const [instruction, setInstruction] = React7.useState("");
1050
+ const [busy, setBusy] = React7.useState(null);
1051
+ const [aiFailed, setAiFailed] = React7.useState(false);
1052
+ const activeAi = React7.useRef(null);
1053
+ const activeCapture = React7.useRef(null);
1054
+ React7.useEffect(() => {
1055
+ if (bufferError) return;
1056
+ const adapter = adapters.find((candidate) => candidate.id === format);
1057
+ const controller = new AbortController();
1058
+ const snapshotId = state.snapshot.id;
1059
+ const revision = state.draft.revision;
1060
+ setPreparedArtifact(null);
1061
+ setFormatError(null);
1062
+ if (adapter) setBuffer("Preparing format preview\u2026");
1063
+ const artifactPromise = adapter ? adapter.build(state.draft, controller.signal) : buildArtifact(state.draft, isBuiltInFormat(format) ? format : "plain", state.snapshot.label);
1064
+ void artifactPromise.then((artifact) => {
1065
+ if (!controller.signal.aborted && artifact.snapshotId === snapshotId && artifact.draftRevision === revision) {
1066
+ setPreparedArtifact(artifact);
1067
+ setBuffer(artifact.plainText);
1068
+ }
1069
+ }).catch((error) => {
1070
+ if (!controller.signal.aborted) setFormatError(errorMessage(error, "Format preview failed"));
1071
+ });
1072
+ return () => controller.abort();
1073
+ }, [adapters, bufferError, format, state.draft, state.snapshot.id]);
1074
+ const close = React7.useCallback(() => {
1075
+ activeAi.current?.abort();
1076
+ activeCapture.current?.abort();
1077
+ activeAi.current = null;
1078
+ activeCapture.current = null;
1079
+ fence.abortAll();
1080
+ setBusy(null);
1081
+ onClose();
1082
+ }, [fence, onClose]);
1083
+ const changeControls = (next) => {
1084
+ if (state.draft.manuallyEdited) {
1085
+ setPendingControls(next);
1086
+ return;
1087
+ }
1088
+ setControlsHistory((history) => [...history, controls]);
1089
+ setControls(next);
1090
+ onChange({ ...state, draft: rebuild(state, next), error: void 0 });
1091
+ };
1092
+ const applyPendingControls = () => {
1093
+ if (!pendingControls) return;
1094
+ setControlsHistory((history) => [...history, controls]);
1095
+ setControls(pendingControls);
1096
+ setBufferError(null);
1097
+ onChange({ ...state, draft: rebuild(state, pendingControls), error: void 0 });
1098
+ setPendingControls(null);
1099
+ };
1100
+ const editBuffer = (text) => {
1101
+ setBuffer(text);
1102
+ setPreparedArtifact(null);
1103
+ setFormatError(null);
1104
+ const payload = state.draft.payload;
1105
+ if (payload.kind === "json" || payload.kind === "registered") {
1106
+ try {
1107
+ const value = JSON.parse(text);
1108
+ const next = payload.kind === "json" ? { kind: "json", value } : { kind: "registered", format: payload.format, value };
1109
+ setBufferError(null);
1110
+ onChange({ ...state, draft: (0, import_content_transfer.editDraft)(state.draft, next), error: void 0 });
1111
+ } catch {
1112
+ setBufferError("JSON is incomplete or invalid. Fix it before copying or downloading.");
1113
+ }
1114
+ return;
1115
+ }
1116
+ setBufferError(null);
1117
+ const kind = payload.kind === "markdown" || format === "markdown" ? "markdown" : "text";
1118
+ if (payload.kind === "rows" || !isBuiltInFormat(format)) setFormat("plain");
1119
+ onChange({ ...state, draft: (0, import_content_transfer.editDraft)(state.draft, { kind, text }), error: void 0 });
1120
+ };
1121
+ const requestCapture = async (scope) => {
1122
+ activeCapture.current?.abort();
1123
+ const { controller, id } = fence.begin();
1124
+ activeCapture.current = controller;
1125
+ const revision = state.draft.revision;
1126
+ setBusy("capture");
1127
+ try {
1128
+ const result = await captureScope(scope, controller.signal);
1129
+ if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, error: result.collectionError });
1130
+ } catch (error) {
1131
+ if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "Refresh failed") });
1132
+ } finally {
1133
+ fence.finish(controller);
1134
+ if (activeCapture.current === controller) activeCapture.current = null;
1135
+ if (fence.valid(id, controller)) setBusy(null);
1136
+ }
1137
+ };
1138
+ const resolveCapture = (choice) => {
1139
+ if (!state.pending) return;
1140
+ if (choice === "keep") {
1141
+ const { pending: _pending, ...kept } = state;
1142
+ onChange({ ...kept, error: void 0 });
1143
+ return;
1144
+ }
1145
+ const pending = state.pending;
1146
+ const preferences = (0, import_content_transfer.resolveTransferPreferences)({}, state.preferences, pending.snapshot.limits);
1147
+ const base = { ...pending, key: state.key, original: (0, import_content_transfer.createDraft)(pending.snapshot), draft: (0, import_content_transfer.createDraft)(pending.snapshot), scope: pending.scope, preferences, ...pending.collectionError ? { error: pending.collectionError } : {} };
1148
+ const nextControls = initialControls(base);
1149
+ setControls(nextControls);
1150
+ setControlsHistory([]);
1151
+ setPendingControls(null);
1152
+ setBufferError(null);
1153
+ onChange({ ...base, draft: rebuild(base, nextControls) });
1154
+ };
1155
+ const runAi = async (recover = false) => {
1156
+ const adapter = aiSession.current;
1157
+ if (!adapter || !adapter.supports(state.snapshot) || !recover && !instruction.trim() || recover && !adapter.recover) return;
1158
+ activeAi.current?.abort();
1159
+ const { controller, id } = fence.begin();
1160
+ activeAi.current = controller;
1161
+ const snapshotId = state.snapshot.id;
1162
+ const revision = state.draft.revision;
1163
+ setBusy("ai");
1164
+ setAiFailed(false);
610
1165
  try {
611
- await operation();
1166
+ const next = recover ? await adapter.recover({ snapshot: state.snapshot, draft: state.draft, signal: controller.signal }) : await adapter.run({ snapshot: state.snapshot, draft: state.draft, instruction, signal: controller.signal });
1167
+ const current = latest.current;
1168
+ if (!fence.valid(id, controller) || current.snapshot.id !== snapshotId || current.draft.revision !== revision || next.snapshotId !== snapshotId || next.sourceId !== state.draft.sourceId || next.sourceRevision !== state.draft.sourceRevision || next.revision <= revision) return;
1169
+ try {
1170
+ (0, import_content_transfer.serialize)(next, "plain");
1171
+ } catch {
1172
+ onChange({ ...current, error: "AI returned an invalid transfer draft." });
1173
+ setAiFailed(true);
1174
+ return;
1175
+ }
1176
+ onChange({ ...current, draft: next, error: void 0 });
1177
+ setBufferError(null);
612
1178
  } catch (error) {
613
- notify.error(error instanceof Error ? error.message : String(error));
1179
+ if (!isAbort(error) && fence.valid(id, controller)) {
1180
+ setAiFailed(true);
1181
+ onChange({ ...latest.current, error: errorMessage(error, "AI preparation failed") });
1182
+ }
614
1183
  } finally {
615
- setBusy(false);
1184
+ fence.finish(controller);
1185
+ if (activeAi.current === controller) activeAi.current = null;
1186
+ if (fence.valid(id, controller)) setBusy(null);
616
1187
  }
617
1188
  };
618
- const faithfulAgent = agent === void 0 ? [] : [{
619
- id: agentVariant?.id ?? "copy-for-ai",
620
- label: agentVariant?.label ?? "Copy for AI",
621
- ...agentVariant?.hint === void 0 ? {} : { hint: agentVariant.hint },
622
- section: "ai",
623
- build: () => resolve(agent)
624
- }];
625
- const orderedAgent = agentVariant?.position === "last" ? [...aiVariants, ...faithfulAgent] : [...faithfulAgent, ...aiVariants];
626
- const variants = [
627
- ...json === void 0 ? [] : [{ id: "json", label: "Copy JSON", section: "copy", build: () => JSON.stringify(resolve(json), null, 2) }],
628
- ...orderedAgent
629
- ].filter((variant) => !hide?.includes(variant.section ?? "ai"));
630
- const exports = hide?.includes("export") ? void 0 : exportConfig;
631
- const showCustom = aiCustom && !hide?.includes("ai");
632
- const buttonClass = `${size === "xs" ? "h-8 w-8 lg:h-5 lg:w-8" : ""} ${appearance === "bare" ? "border-0 bg-transparent shadow-none" : ""}`;
633
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: `inline-flex flex-wrap items-center gap-0.5 ${className ?? ""}`, onClick: stopPropagation ? (event) => event.stopPropagation() : void 0, children: [
634
- human !== void 0 && !hide?.includes("copy") ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", variant: "ghost", size: "icon", className: buttonClass, disabled: disabled || busy, "aria-label": `Copy ${label}`, onClick: () => void run(() => clipboard.copyText(resolve(human), `${label} copied`)), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Copy, { className: "h-3.5 w-3.5" }) }) : null,
635
- variants.length > 0 || showCustom || exports ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Popover, { children: [
636
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", variant: "ghost", size: "icon", className: buttonClass, disabled: disabled || busy, "aria-label": `Copy ${label} for AI or export`, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SlidersHorizontal, { className: "h-3.5 w-3.5" }) }) }),
637
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(PopoverContent, { align: "end", className: "w-64 p-1", children: [
638
- variants.map((variant) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", variant: "ghost", size: "sm", className: "w-full justify-start", disabled: busy, onClick: () => void run(async () => {
639
- if (variant.onSelect) variant.onSelect();
640
- else if (variant.modal) setModal(variant.id);
641
- else if (variant.build) await clipboard.copyText(tableAgentText(await variant.build()), variant.successMessage ?? `${label} copied`);
642
- else throw new Error(`No copy action is available for ${variant.label}.`);
643
- }), children: variant.label }, variant.id)),
644
- showCustom ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", variant: "ghost", size: "sm", className: "w-full justify-start", onClick: () => setModal("custom"), children: aiCustom.label ?? "Custom copy\u2026" }) : null,
645
- exports?.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", variant: "ghost", size: "sm", className: "w-full justify-start", disabled: busy, onClick: () => void run(async () => {
646
- if (item.onSelect) await item.onSelect();
647
- else download(item);
648
- }), children: item.label }, item.id)),
649
- exports?.sheetRows ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", variant: "ghost", size: "sm", className: "w-full justify-start", onClick: () => void run(async () => {
650
- const rows = exports.sheetRows?.();
651
- if (!rows) return;
652
- const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))];
653
- const value = (input) => String(input ?? "").replace(/[\t\r\n]+/g, " ");
654
- await clipboard.copyText([keys.map(value).join(" "), ...rows.map((row) => keys.map((key) => value(row[key])).join(" "))].join("\n"), "Copied for pasting into Google Sheets");
655
- }), children: "Copy for Google Sheets" }) : null
1189
+ const cancelAi = async () => {
1190
+ const aiController = activeAi.current;
1191
+ const { controller, id } = fence.begin();
1192
+ try {
1193
+ await aiSession.current?.cancel?.();
1194
+ } catch (error) {
1195
+ if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "AI cancellation failed") });
1196
+ } finally {
1197
+ aiController?.abort();
1198
+ fence.finish(controller);
1199
+ if (activeAi.current === aiController) activeAi.current = null;
1200
+ if (fence.valid(id, controller)) setBusy(null);
1201
+ }
1202
+ };
1203
+ const [deliveryNotice, setDeliveryNotice] = React7.useState(null);
1204
+ React7.useEffect(() => {
1205
+ setDeliveryNotice(null);
1206
+ }, [state.draft.revision]);
1207
+ const reportDelivery = (outcome) => {
1208
+ setDeliveryNotice(outcomeMessage(outcome));
1209
+ onOutcome(outcome);
1210
+ };
1211
+ const deliver = async (download) => {
1212
+ if (bufferError) return;
1213
+ const { controller, id } = fence.begin();
1214
+ const snapshotId = state.snapshot.id;
1215
+ const revision = state.draft.revision;
1216
+ setBusy(download ? "download" : "copy");
1217
+ try {
1218
+ if (!preparedArtifact) throw new Error("Wait for the selected format preview before delivery.");
1219
+ const artifact = preparedArtifact;
1220
+ const current = latest.current;
1221
+ if (!fence.valid(id, controller) || current.snapshot.id !== snapshotId || current.draft.revision !== revision || artifact.snapshotId !== snapshotId || artifact.draftRevision !== revision) return;
1222
+ const result = await transport[download ? "download" : "copy"](artifact, controller.signal);
1223
+ if (fence.valid(id, controller)) reportDelivery(result);
1224
+ } catch (error) {
1225
+ if (fence.valid(id, controller)) reportDelivery({ status: "error", code: "prepare-failed", message: errorMessage(error, "Preparation failed"), retryable: true });
1226
+ } finally {
1227
+ fence.finish(controller);
1228
+ if (fence.valid(id, controller)) setBusy(null);
1229
+ }
1230
+ };
1231
+ const rows = state.original.payload.kind === "rows" ? state.original.payload : null;
1232
+ const formats = COPIES.filter((f) => supportsBuiltIn(f, state.draft.payload));
1233
+ const workspaceAdapters = adapters.filter((adapter) => adapter.supports(state.draft.payload));
1234
+ const displayedOmissions = preparedArtifact?.omissions ?? state.draft.omissions;
1235
+ const limitText = Object.entries(state.preferences.limits).filter(([, value]) => value != null).map(([key, value]) => `${key.replace(/^max/, "max ")} ${Number(value).toLocaleString()}`).join(" \xB7 ");
1236
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Dialog, { open, onOpenChange: (next) => {
1237
+ if (!next) close();
1238
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DialogContent, { onCloseAutoFocus: (event) => {
1239
+ event.preventDefault();
1240
+ restoreFocus();
1241
+ }, className: "matrx-alchemy-workspace max-w-5xl", "aria-busy": busy !== null, children: [
1242
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DialogHeader, { children: [
1243
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DialogTitle, { children: [
1244
+ "Prepare ",
1245
+ state.snapshot.label
1246
+ ] }),
1247
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DialogDescription, { children: [
1248
+ "Captured ",
1249
+ state.snapshot.unsavedChanges ? "from your unsaved draft" : "as an immutable snapshot",
1250
+ ". Your source is never edited."
656
1251
  ] })
1252
+ ] }),
1253
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "matrx-alchemy-workspace-grid", children: [
1254
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "matrx-alchemy-main", children: [
1255
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "matrx-alchemy-toolbar", children: [
1256
+ scopes.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1257
+ "Scope",
1258
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("select", { "aria-label": "Transfer scope", value: state.scope, disabled: busy !== null, onChange: (e) => void requestCapture(e.target.value), children: scopes.map((scope) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: scope, children: SCOPE_LABEL[scope] }, scope)) })
1259
+ ] }) : null,
1260
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1261
+ "Detail",
1262
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("select", { "aria-label": "Detail preset", value: controls.preset, onChange: (e) => changeControls({ ...controls, preset: e.target.value }), children: [
1263
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "full", children: "Full" }),
1264
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "compact", children: "Compact" }),
1265
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "brief", children: "Brief" }),
1266
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "custom", children: "Custom" })
1267
+ ] })
1268
+ ] }),
1269
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", size: "sm", variant: "outline", disabled: !state.draft.history?.length && !controlsHistory.length || busy !== null, onClick: () => {
1270
+ setBufferError(null);
1271
+ if (controlsHistory.length) {
1272
+ const next = controlsHistory[controlsHistory.length - 1];
1273
+ setControls(next);
1274
+ setControlsHistory((history) => history.slice(0, -1));
1275
+ onChange({ ...state, draft: rebuild(state, next), error: void 0 });
1276
+ } else onChange({ ...state, draft: (0, import_content_transfer.undoDraft)(state.draft), error: void 0 });
1277
+ }, children: "Undo" }),
1278
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", size: "sm", variant: "outline", disabled: busy !== null, onClick: () => {
1279
+ const next = initialControls(state);
1280
+ setControls(next);
1281
+ setControlsHistory([]);
1282
+ setPendingControls(null);
1283
+ setBufferError(null);
1284
+ onChange({ ...state, draft: rebuild(state, next), error: void 0 });
1285
+ }, children: "Reset" }),
1286
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", size: "sm", variant: "outline", disabled: busy !== null, onClick: () => void requestCapture(state.scope), children: "Refresh source" })
1287
+ ] }),
1288
+ controls.preset === "custom" && (state.draft.payload.kind === "json" || state.draft.payload.kind === "registered") ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("fieldset", { className: "matrx-alchemy-options", children: [
1289
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("legend", { children: "JSON limits" }),
1290
+ [["maxDepth", "Maximum depth"], ["maxStringChars", "Maximum string characters"], ["maxArrayItems", "Maximum array items"]].map(([key, text]) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1291
+ text,
1292
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "number", min: "0", value: controls[key] ?? "", onChange: (e) => changeControls({ ...controls, [key]: Number.isNaN(e.target.valueAsNumber) ? null : e.target.valueAsNumber }) })
1293
+ ] }, key))
1294
+ ] }) : null,
1295
+ rows ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(RowControls, { rows, controls, change: changeControls }) : null,
1296
+ state.snapshot.sections.length ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("fieldset", { className: "matrx-alchemy-options", children: [
1297
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("legend", { children: "Sections" }),
1298
+ state.snapshot.sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1299
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "checkbox", checked: controls.sections.includes(section.id), onChange: (e) => changeControls({ ...controls, sections: e.target.checked ? [...controls.sections, section.id] : controls.sections.filter((id) => id !== section.id) }) }),
1300
+ " ",
1301
+ section.label
1302
+ ] }, section.id))
1303
+ ] }) : null,
1304
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "matrx-alchemy-coverage", children: [
1305
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
1306
+ state.snapshot.coverage.included.toLocaleString(),
1307
+ " included",
1308
+ state.snapshot.coverage.total == null ? "" : ` of ${state.snapshot.coverage.total.toLocaleString()}`,
1309
+ " \xB7 ",
1310
+ state.snapshot.coverage.status
1311
+ ] }),
1312
+ state.snapshot.coverage.reason ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: state.snapshot.coverage.reason }) : null,
1313
+ limitText ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
1314
+ "Limits: ",
1315
+ limitText
1316
+ ] }) : null,
1317
+ displayedOmissions.length ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("details", { children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("summary", { children: [
1319
+ displayedOmissions.length,
1320
+ " omissions or adjustments"
1321
+ ] }),
1322
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { children: displayedOmissions.map((o, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { children: [
1323
+ o.path || "/",
1324
+ ": ",
1325
+ o.reason,
1326
+ o.count == null ? "" : ` (${o.count})`
1327
+ ] }, `${o.path}-${o.reason}-${i}`)) })
1328
+ ] }) : null
1329
+ ] }),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "matrx-alchemy-preview-label", children: [
1331
+ "Output format",
1332
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("select", { "aria-label": "Output format", value: format, onChange: (e) => setFormat(e.target.value), children: [
1333
+ formats.map((f) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: f, children: LABEL[f] }, f)),
1334
+ state.draft.payload.kind === "rows" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "xlsx", children: "Excel workbook" }) : null,
1335
+ workspaceAdapters.map((adapter) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: adapter.id, children: adapter.label }, adapter.id))
1336
+ ] })
1337
+ ] }),
1338
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("textarea", { className: "matrx-alchemy-preview", "aria-label": "Prepared output", "aria-invalid": bufferError ? true : void 0, "aria-describedby": bufferError ? "alchemy-buffer-error" : void 0, value: buffer, onChange: (e) => editBuffer(e.target.value) }),
1339
+ bufferError ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { id: "alchemy-buffer-error", role: "alert", className: "matrx-alchemy-error", children: bufferError }) : null,
1340
+ formatError ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { role: "alert", className: "matrx-alchemy-error", children: formatError }) : null,
1341
+ bufferError ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", disabled: busy !== null, onClick: () => {
1342
+ const fallback = (0, import_content_transfer.editDraft)(state.draft, { kind: "text", text: buffer });
1343
+ setBufferError(null);
1344
+ onChange({ ...state, draft: fallback, error: void 0 });
1345
+ setFormat("plain");
1346
+ }, children: "Use invalid JSON as plain text" }) : null
1347
+ ] }),
1348
+ ai || busy === "capture" || state.error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("aside", { children: [
1349
+ ai ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1350
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1351
+ "AI instruction",
1352
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("textarea", { value: instruction, onChange: (e) => setInstruction(e.target.value), placeholder: "Tell AI how to prepare this content" })
1353
+ ] }),
1354
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", disabled: !aiSession.current || !instruction.trim() || busy !== null || !aiSession.current.supports(state.snapshot), onClick: () => void runAi(false), children: "Generate preview" }),
1355
+ busy === "ai" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", onClick: () => void cancelAi(), children: aiSession.current?.cancel ? "Cancel AI" : "Stop waiting" }) : null
1356
+ ] }) : null,
1357
+ busy === "capture" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", onClick: () => {
1358
+ activeCapture.current?.abort();
1359
+ activeCapture.current = null;
1360
+ setBusy(null);
1361
+ }, children: "Stop loading rows" }) : null,
1362
+ ai && aiSession.current?.recover && busy !== "ai" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", onClick: () => void runAi(true), children: aiFailed ? "Retry or rejoin AI run" : "Rejoin AI run" }) : null,
1363
+ ai ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "matrx-alchemy-meta", children: "AI creates a preview. It never copies automatically. Closing detaches this view without cancelling server work." }) : null,
1364
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { role: "alert", className: "matrx-alchemy-error", children: state.error }) : null
1365
+ ] }) : null
1366
+ ] }),
1367
+ pendingControls ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "matrx-alchemy-decision", role: "alert", children: [
1368
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: "This control would replace your manual edit." }),
1369
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", onClick: () => setPendingControls(null), children: "Keep edited draft" }),
1370
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", onClick: applyPendingControls, children: "Rebuild from source" })
1371
+ ] }) : null,
1372
+ state.pending ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "matrx-alchemy-decision", role: "alert", children: [
1373
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: "A fresh source snapshot is ready." }),
1374
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", onClick: () => resolveCapture("keep"), children: "Keep current snapshot and draft" }),
1375
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", onClick: () => resolveCapture("regenerate"), children: "Regenerate from fresh source" })
657
1376
  ] }) : null,
658
- variants.map((variant) => variant.modal && modal === variant.id ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: variant.modal({ open: true, onOpenChange: (open) => setModal(open ? variant.id : null) }) }, variant.id) : null),
659
- modal === "custom" && aiCustom ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CustomCopy, { source: aiCustom, onClose: () => setModal(null) }) : null,
660
- notice
1377
+ deliveryNotice ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { role: "status", className: "matrx-alchemy-meta", children: deliveryNotice }) : null,
1378
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DialogFooter, { children: [
1379
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "matrx-alchemy-meta", children: [
1380
+ buffer.length.toLocaleString(),
1381
+ " characters \xB7 ~",
1382
+ estimateTokens(buffer).toLocaleString(),
1383
+ " tokens"
1384
+ ] }),
1385
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", onClick: close, children: "Close" }),
1386
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", disabled: busy !== null || Boolean(bufferError || formatError) || !preparedArtifact, onClick: () => void deliver(true), children: "Download prepared output" }),
1387
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", disabled: busy !== null || Boolean(bufferError || formatError) || !preparedArtifact, onClick: () => void deliver(false), children: "Copy prepared output" })
1388
+ ] })
1389
+ ] }) });
1390
+ }
1391
+ function RowControls({ rows, controls, change }) {
1392
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1393
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "matrx-alchemy-toolbar", children: [
1394
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1395
+ "Search",
1396
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { value: controls.search, onChange: (e) => change({ ...controls, search: e.target.value }) })
1397
+ ] }),
1398
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1399
+ "Sort",
1400
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("select", { value: controls.sort, onChange: (e) => change({ ...controls, sort: e.target.value }), children: [
1401
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "", children: "None" }),
1402
+ rows.columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: column.id, children: column.label }, column.id))
1403
+ ] })
1404
+ ] }),
1405
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1406
+ "Direction",
1407
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("select", { value: controls.direction, disabled: !controls.sort, onChange: (e) => change({ ...controls, direction: e.target.value }), children: [
1408
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "asc", children: "Ascending" }),
1409
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "desc", children: "Descending" })
1410
+ ] })
1411
+ ] }),
1412
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1413
+ "Top rows",
1414
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "number", min: "0", value: controls.top ?? "", onChange: (e) => change({ ...controls, top: Number.isNaN(e.target.valueAsNumber) ? null : e.target.valueAsNumber }) })
1415
+ ] })
1416
+ ] }),
1417
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("fieldset", { className: "matrx-alchemy-options", children: [
1418
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("legend", { children: "Columns and filters" }),
1419
+ rows.columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "matrx-alchemy-column", children: [
1420
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1421
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "checkbox", checked: controls.visibleColumns.includes(column.id), onChange: (e) => change({ ...controls, visibleColumns: e.target.checked ? [...controls.visibleColumns, column.id] : controls.visibleColumns.filter((id) => id !== column.id) }) }),
1422
+ " ",
1423
+ column.label
1424
+ ] }),
1425
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { "aria-label": `Filter ${column.label}`, placeholder: "Filter", value: controls.filters[column.id] ?? "", onChange: (e) => change({ ...controls, filters: { ...controls.filters, [column.id]: e.target.value } }) })
1426
+ ] }, column.id))
1427
+ ] }),
1428
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("fieldset", { className: "matrx-alchemy-options", children: [
1429
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("legend", { children: "Rows" }),
1430
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1431
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "checkbox", checked: controls.selectedOnly, onChange: (e) => change({ ...controls, selectedOnly: e.target.checked }) }),
1432
+ " Only selected rows"
1433
+ ] }),
1434
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "matrx-alchemy-row-list", children: rows.rows.map((row, index) => {
1435
+ const summary = rows.columns.filter((column) => column.visible).slice(0, 2).map((column) => String((0, import_content_transfer.readTransferCell)(row, column) ?? "")).filter(Boolean).join(" \xB7 ");
1436
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { children: [
1437
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "checkbox", checked: controls.selectedIndices.includes(index), onChange: (e) => change({ ...controls, selectedIndices: e.target.checked ? [...controls.selectedIndices, index] : controls.selectedIndices.filter((value) => value !== index) }) }),
1438
+ " Row ",
1439
+ index + 1,
1440
+ summary ? ` \u2014 ${summary}` : ""
1441
+ ] }, index);
1442
+ }) })
1443
+ ] })
661
1444
  ] });
662
1445
  }
663
- function CustomCopy({ source, onClose }) {
664
- const [values, setValues] = (0, import_react3.useState)(() => Object.fromEntries(source.options.map((option) => [option.key, option.default])));
665
- const { notify, notice } = useTableNotice();
666
- const clipboard = (0, import_clipboard.useClipboard)({ notify: (message, kind) => notify[kind](message) });
1446
+
1447
+ // src/content-transfer/matrx-copy.tsx
1448
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1449
+ var resolve = (value) => typeof value === "function" ? value() : value;
1450
+ var asPayload = (value) => ({
1451
+ kind: "markdown",
1452
+ text: typeof value === "string" ? value : (0, import_content_transfer2.buildAgentPayload)(value)
1453
+ });
1454
+ var structuredPayload = (value) => {
1455
+ if (typeof value === "string") return { kind: "markdown", text: value };
1456
+ const clean = (record) => Object.fromEntries(
1457
+ Object.entries(record ?? {}).filter(([, entry]) => entry !== void 0)
1458
+ );
1459
+ return {
1460
+ kind: "json",
1461
+ value: (0, import_content_transfer2.normalizeTransferJson)({
1462
+ kind: value.kind,
1463
+ location: value.location,
1464
+ description: value.description,
1465
+ data: value.data,
1466
+ ...value.summary !== void 0 ? { summary: value.summary } : {},
1467
+ attributes: clean(value.attributes),
1468
+ context: clean(value.context)
1469
+ })
1470
+ };
1471
+ };
1472
+ var actionDone = (message) => ({
1473
+ status: "success",
1474
+ delivered: "action",
1475
+ mimeTypes: [],
1476
+ message
1477
+ });
1478
+ var sourceFor = (id, label, build) => ({
1479
+ id,
1480
+ label,
1481
+ capture: async ({ signal }) => {
1482
+ signal.throwIfAborted();
1483
+ const payload = await build();
1484
+ signal.throwIfAborted();
1485
+ return (0, import_content_transfer2.directSource)(payload, { sourceId: id, label });
1486
+ }
1487
+ });
1488
+ function MatrxCopyMenu(props) {
1489
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MatrxCopyMenuSession, { ...props }, props.sourceId ?? props.label);
1490
+ }
1491
+ function MatrxCopyMenuSession(props) {
1492
+ const instance = React8.useId();
1493
+ const controller = React8.useRef(null);
1494
+ const capabilities = useContentTransferCapabilities();
1495
+ const transport = capabilities.transport ?? (0, import_content_transfer2.createBrowserTransport)();
1496
+ const [modal, setModal] = React8.useState(null);
1497
+ const [configuration, setConfiguration] = React8.useState(null);
1498
+ const copyVisible = !props.hide?.includes("copy");
1499
+ const aiVisible = !props.hide?.includes("ai");
1500
+ const exportVisible = !props.hide?.includes("export");
1501
+ const make = (suffix, build, label = props.label) => sourceFor(
1502
+ `${props.sourceId ?? `${instance}:${props.label}`}:${suffix}`,
1503
+ label,
1504
+ build
1505
+ );
1506
+ const human = copyVisible && props.human !== void 0 ? make("human", () => ({ kind: "text", text: resolve(props.human) })) : void 0;
1507
+ const humanMarkdown = copyVisible && props.human !== void 0 ? make("human-markdown", () => ({
1508
+ kind: "markdown",
1509
+ text: resolve(props.human)
1510
+ })) : void 0;
1511
+ const json = aiVisible && props.json !== void 0 ? make("json", () => ({
1512
+ kind: "json",
1513
+ value: (0, import_content_transfer2.normalizeTransferJson)(resolve(props.json))
1514
+ })) : void 0;
1515
+ const agent = aiVisible && props.agent !== void 0 ? make("agent", () => asPayload(resolve(props.agent))) : void 0;
1516
+ const structuredAgent = aiVisible && props.agent !== void 0 ? make("structured-agent", () => structuredPayload(resolve(props.agent))) : void 0;
1517
+ const table = exportVisible && props.export?.sheetRows ? make("rows", () => {
1518
+ const value = (0, import_content_transfer2.normalizeTransferJson)(props.export.sheetRows());
1519
+ if (!Array.isArray(value) || value.some(
1520
+ (row) => row === null || typeof row !== "object" || Array.isArray(row)
1521
+ ))
1522
+ throw new Error("Sheet rows must be JSON objects.");
1523
+ const rows = value;
1524
+ const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))];
1525
+ return {
1526
+ kind: "rows",
1527
+ rows,
1528
+ columns: keys.map((key) => ({
1529
+ id: key,
1530
+ label: key,
1531
+ path: `/${key.replace(/~/g, "~0").replace(/\//g, "~1")}`,
1532
+ visible: true
1533
+ }))
1534
+ };
1535
+ }) : void 0;
1536
+ const builderVariants = aiVisible ? (props.aiVariants ?? []).filter(
1537
+ (variant) => variant.build && !variant.onSelect && !variant.modal
1538
+ ).map((variant) => ({
1539
+ ...variant,
1540
+ copyLabel: variant.label,
1541
+ source: make(
1542
+ `variant:${variant.id}`,
1543
+ async () => asPayload(await variant.build()),
1544
+ variant.label
1545
+ )
1546
+ })) : [];
1547
+ const faithful = agent ? [
1548
+ {
1549
+ id: props.agentVariant?.id ?? "faithful",
1550
+ label: props.agentVariant?.label ?? "Full AI context",
1551
+ source: agent,
1552
+ copyLabel: props.agentVariant?.label ?? "Copy for AI",
1553
+ ...props.agentVariant?.hint ? { hint: props.agentVariant.hint } : {}
1554
+ }
1555
+ ] : [];
1556
+ const variants = props.agentVariant?.position === "last" ? [...builderVariants, ...faithful] : [...faithful, ...builderVariants];
1557
+ const actions = [];
1558
+ if (aiVisible)
1559
+ for (const variant of props.aiVariants ?? []) {
1560
+ if (!variant.onSelect && !variant.modal) continue;
1561
+ actions.push({
1562
+ ...variant,
1563
+ id: `variant-action:${variant.id}`,
1564
+ label: variant.label,
1565
+ supports: () => true,
1566
+ run: async () => {
1567
+ if (variant.onSelect) await variant.onSelect();
1568
+ else setModal(variant);
1569
+ return actionDone(`Opened ${variant.label}`);
1570
+ }
1571
+ });
1572
+ }
1573
+ if (aiVisible && props.aiCustom)
1574
+ actions.push({
1575
+ id: "configure-source",
1576
+ label: props.aiCustom.label ?? "Customize source\u2026",
1577
+ ...props.aiCustom.hint ? { hint: props.aiCustom.hint } : {},
1578
+ supports: () => true,
1579
+ run: async () => {
1580
+ setConfiguration({ custom: props.aiCustom });
1581
+ return actionDone("Source options opened");
1582
+ }
1583
+ });
1584
+ if (aiVisible && props.groomer)
1585
+ actions.push({
1586
+ id: "groom-source",
1587
+ label: "Customize sections\u2026",
1588
+ supports: () => true,
1589
+ run: async () => {
1590
+ setConfiguration({ groomer: props.groomer() });
1591
+ return actionDone("Section options opened");
1592
+ }
1593
+ });
1594
+ if (exportVisible)
1595
+ for (const item of props.export?.items ?? [])
1596
+ actions.push({
1597
+ id: `export:${item.id}`,
1598
+ label: item.label,
1599
+ supports: () => true,
1600
+ ...item.build && !item.onSelect ? {
1601
+ source: make(
1602
+ `export:${item.id}`,
1603
+ async () => {
1604
+ const file = item.build();
1605
+ const bytes = new Uint8Array(
1606
+ await new Blob([file.content], {
1607
+ type: file.mime
1608
+ }).arrayBuffer()
1609
+ );
1610
+ return {
1611
+ kind: "registered",
1612
+ format: "matrx-file",
1613
+ value: {
1614
+ bytes: Array.from(bytes),
1615
+ mime: file.mime,
1616
+ extension: file.extension
1617
+ }
1618
+ };
1619
+ },
1620
+ item.label
1621
+ )
1622
+ } : {},
1623
+ run: async ({ snapshot, draft, signal }) => {
1624
+ if (item.onSelect) {
1625
+ await item.onSelect();
1626
+ return actionDone(`Opened ${item.label}`);
1627
+ }
1628
+ const value = draft.payload.kind === "registered" ? draft.payload.value : null;
1629
+ if (!value || Array.isArray(value) || typeof value !== "object" || !Array.isArray(value.bytes) || typeof value.mime !== "string" || typeof value.extension !== "string" || value.bytes.some(
1630
+ (byte) => typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255
1631
+ ))
1632
+ throw new Error(`${item.label} did not capture a valid file.`);
1633
+ if (draft.omissions.some((omission) => omission.reason !== "duplicate"))
1634
+ throw new Error(
1635
+ "This custom file exceeds the configured export limits or contains excluded content. Use a standard format or revise the source options."
1636
+ );
1637
+ const bytes = new Uint8Array(value.bytes);
1638
+ const filename = `${props.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "export"}.${value.extension.replace(/[^a-zA-Z0-9.-]/g, "") || "bin"}`;
1639
+ return transport.download(
1640
+ (0, import_content_transfer2.sealTransferArtifact)({
1641
+ snapshotId: snapshot.id,
1642
+ draftRevision: draft.revision,
1643
+ format: `export:${item.id}`,
1644
+ plainText: "",
1645
+ file: { filename, mime: value.mime, bytes },
1646
+ omissions: [...draft.omissions]
1647
+ }),
1648
+ signal
1649
+ );
1650
+ }
1651
+ });
1652
+ if (exportVisible && table && props.sendToSheet)
1653
+ actions.push({
1654
+ id: "send-google-sheet",
1655
+ label: "Send to Google Sheet",
1656
+ source: table,
1657
+ supports: () => true,
1658
+ run: async ({ draft }) => {
1659
+ if (draft.payload.kind !== "rows")
1660
+ throw new Error("Google Sheets requires captured rows.");
1661
+ return props.sendToSheet(draft.payload.rows, props.label);
1662
+ }
1663
+ });
1664
+ const source = json ?? table ?? structuredAgent ?? human ?? builderVariants[0]?.source;
1665
+ if (!source && !actions.length) return null;
1666
+ const target = source ?? make("actions", () => ({ kind: "text", text: props.label }));
1667
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1668
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1669
+ ContentTransferMenu,
1670
+ {
1671
+ source: target,
1672
+ controllerRef: controller,
1673
+ label: props.label,
1674
+ variants,
1675
+ actions,
1676
+ hide: [
1677
+ ...!copyVisible || !source ? ["copy"] : [],
1678
+ ...!aiVisible || !source ? ["prepare"] : [],
1679
+ ...!exportVisible || !source ? ["download"] : []
1680
+ ],
1681
+ formatSources: {
1682
+ ...human ? { plain: human, markdown: humanMarkdown, html: humanMarkdown } : {},
1683
+ ...json ? { json, "compact-json": json } : {},
1684
+ ...table ? { csv: table, tsv: table } : {}
1685
+ },
1686
+ ...props.disabled !== void 0 ? { disabled: props.disabled } : {},
1687
+ className: cn(
1688
+ props.className,
1689
+ props.size === "xs" && "matrx-alchemy-xs",
1690
+ props.size === "sm" && "matrx-alchemy-sm"
1691
+ ),
1692
+ ...props.stopPropagation !== void 0 ? { stopPropagation: props.stopPropagation } : {}
1693
+ }
1694
+ ),
1695
+ modal?.modal?.({
1696
+ open: true,
1697
+ onOpenChange: (open) => {
1698
+ if (!open) setModal(null);
1699
+ }
1700
+ }),
1701
+ configuration ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1702
+ SourceConfiguration,
1703
+ {
1704
+ restoreFocus: () => controller.current?.focusTrigger(),
1705
+ configuration,
1706
+ onClose: () => setConfiguration(null),
1707
+ onPrepare: async (payload, label) => {
1708
+ if (!controller.current)
1709
+ throw new Error("Alchemy is no longer mounted.");
1710
+ await controller.current.prepare(payload, {
1711
+ label,
1712
+ id: `${instance}:configured:${crypto.randomUUID()}`
1713
+ });
1714
+ setConfiguration(null);
1715
+ }
1716
+ },
1717
+ `${instance}:configuration`
1718
+ ) : null
1719
+ ] });
1720
+ }
1721
+ function SourceConfiguration({
1722
+ restoreFocus,
1723
+ configuration,
1724
+ onClose,
1725
+ onPrepare
1726
+ }) {
1727
+ const custom = configuration.custom;
1728
+ const handingOff = React8.useRef(false);
1729
+ const groomer = configuration.groomer;
1730
+ const [values, setValues] = React8.useState(
1731
+ () => Object.fromEntries(
1732
+ custom?.options.map((option) => [option.key, option.default]) ?? []
1733
+ )
1734
+ );
1735
+ const [sections, setSections] = React8.useState(
1736
+ () => Object.fromEntries(
1737
+ groomer?.sections.map((section) => [
1738
+ section.id,
1739
+ section.defaultSelection ?? "full"
1740
+ ]) ?? []
1741
+ )
1742
+ );
1743
+ const [error, setError] = React8.useState(null);
1744
+ const [busy, setBusy] = React8.useState(false);
1745
+ const title = custom?.dialogTitle ?? groomer?.label ?? "Customize source";
1746
+ let prepared = null;
1747
+ let buildError = null;
667
1748
  let text = "";
668
- let error = null;
669
1749
  try {
670
- const built = source.build(values);
671
- text = tableAgentText(source.wrap(built.text, values, built.meta));
672
- } catch (raw) {
673
- error = raw instanceof Error ? raw.message : String(raw);
1750
+ if (custom) {
1751
+ const result = custom.build(values);
1752
+ prepared = custom.wrap(result.text, values, result.meta);
1753
+ } else if (groomer) {
1754
+ const data = {};
1755
+ const omitted = [];
1756
+ for (const section of groomer.sections) {
1757
+ const level = sections[section.id] ?? "full";
1758
+ if (level === "off") omitted.push(section.id);
1759
+ else {
1760
+ const value = section.build(level);
1761
+ if (value !== void 0 && value !== null) data[section.id] = value;
1762
+ }
1763
+ }
1764
+ prepared = {
1765
+ kind: groomer.kind,
1766
+ location: groomer.location,
1767
+ description: groomer.description,
1768
+ data,
1769
+ ...groomer.summary ? { summary: groomer.summary } : {},
1770
+ attributes: {
1771
+ ...groomer.attributes,
1772
+ dropped_sections: omitted.join(",")
1773
+ },
1774
+ ...groomer.context ? { context: groomer.context } : {}
1775
+ };
1776
+ }
1777
+ text = prepared ? (0, import_content_transfer2.buildAgentPayload)(prepared) : "";
1778
+ } catch (cause) {
1779
+ buildError = cause instanceof Error ? cause.message : "Could not prepare this source.";
674
1780
  }
675
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Dialog, { open: true, onOpenChange: (open) => {
676
- if (!open) onClose();
677
- }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogContent, { children: [
678
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogTitle, { children: source.dialogTitle ?? "Custom copy" }) }),
679
- source.dialogDescription ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-sm", children: source.dialogDescription }) : null,
680
- source.options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
681
- option.label,
682
- option.kind === "toggle" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("input", { type: "checkbox", checked: values[option.key] === true, onChange: (e) => setValues({ ...values, [option.key]: e.target.checked }) }) : option.kind === "preset" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("select", { "aria-label": option.label, value: Number(values[option.key]), onChange: (e) => setValues({ ...values, [option.key]: Number(e.target.value) }), children: option.options.map((choice) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: choice.value, children: choice.label }, choice.value)) }) : option.kind === "slider" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("input", { "aria-label": option.label, type: "range", min: 0, max: option.stops.length - 1, step: 1, value: Math.max(0, option.stops.indexOf(Number(values[option.key]))), onChange: (e) => setValues({ ...values, [option.key]: option.stops[Number(e.target.value)] ?? option.default }) }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "flex gap-1", children: [
683
- option.presets?.map((choice) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", size: "sm", variant: "outline", onClick: () => setValues({ ...values, [option.key]: choice.value }), children: choice.label }, choice.value)),
684
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("input", { "aria-label": option.label, type: "number", value: Number(values[option.key]), min: option.min, step: option.step, onChange: (e) => {
685
- if (Number.isFinite(e.target.valueAsNumber)) setValues({ ...values, [option.key]: e.target.valueAsNumber });
686
- } })
687
- ] })
688
- ] }, option.key)),
689
- error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { role: "alert", className: "text-destructive", children: error }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap text-xs", children: text }),
690
- notice,
691
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogFooter, { children: [
692
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", variant: "outline", onClick: onClose, children: "Close" }),
693
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", disabled: error !== null, onClick: () => void clipboard.copyText(text, "Custom copy copied"), children: "Copy" })
694
- ] })
695
- ] }) });
1781
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1782
+ Dialog,
1783
+ {
1784
+ open: true,
1785
+ onOpenChange: (open) => {
1786
+ if (!open && !busy) onClose();
1787
+ },
1788
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1789
+ DialogContent,
1790
+ {
1791
+ onCloseAutoFocus: (event) => {
1792
+ event.preventDefault();
1793
+ if (!handingOff.current) restoreFocus();
1794
+ },
1795
+ className: "matrx-alchemy-workspace",
1796
+ children: [
1797
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogHeader, { children: [
1798
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogTitle, { children: title }),
1799
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogDescription, { children: custom?.dialogDescription ?? "Choose the source detail, then prepare, edit, summarize, copy or export it in Alchemy." })
1800
+ ] }),
1801
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "matrx-alchemy-main matrx-alchemy-toolbar", children: [
1802
+ custom?.options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { children: [
1803
+ option.label,
1804
+ "hint" in option && option.hint ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("small", { children: option.hint }) : null,
1805
+ option.kind === "toggle" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1806
+ "input",
1807
+ {
1808
+ type: "checkbox",
1809
+ checked: Boolean(values[option.key]),
1810
+ onChange: (event) => setValues({ ...values, [option.key]: event.target.checked })
1811
+ }
1812
+ ) : option.kind === "preset" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1813
+ "select",
1814
+ {
1815
+ value: Number(values[option.key]),
1816
+ onChange: (event) => setValues({
1817
+ ...values,
1818
+ [option.key]: Number(event.target.value)
1819
+ }),
1820
+ children: option.options.map((choice) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: choice.value, children: choice.label }, choice.value))
1821
+ }
1822
+ ) : option.kind === "slider" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1823
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1824
+ "input",
1825
+ {
1826
+ type: "range",
1827
+ min: 0,
1828
+ max: Math.max(0, option.stops.length - 1),
1829
+ step: 1,
1830
+ value: option.stops.indexOf(Number(values[option.key])),
1831
+ onChange: (event) => setValues({
1832
+ ...values,
1833
+ [option.key]: option.stops[Number(event.target.value)]
1834
+ })
1835
+ }
1836
+ ),
1837
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: option.formatValue(Number(values[option.key])) })
1838
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1839
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1840
+ "input",
1841
+ {
1842
+ type: "number",
1843
+ min: option.min,
1844
+ step: option.step,
1845
+ value: Number(values[option.key]),
1846
+ onChange: (event) => setValues({
1847
+ ...values,
1848
+ [option.key]: Number(event.target.value)
1849
+ })
1850
+ }
1851
+ ),
1852
+ option.presets?.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1853
+ Button,
1854
+ {
1855
+ type: "button",
1856
+ variant: "ghost",
1857
+ onClick: () => setValues({ ...values, [option.key]: preset.value }),
1858
+ children: preset.label
1859
+ },
1860
+ preset.value
1861
+ ))
1862
+ ] })
1863
+ ] }, option.key)),
1864
+ groomer ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1865
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { children: ["full", "compact", "brief"].map((level) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1866
+ Button,
1867
+ {
1868
+ type: "button",
1869
+ variant: "ghost",
1870
+ onClick: () => setSections(
1871
+ Object.fromEntries(
1872
+ groomer.sections.map((section) => [
1873
+ section.id,
1874
+ level === "brief" && section.cuttable ? "off" : level
1875
+ ])
1876
+ )
1877
+ ),
1878
+ children: level
1879
+ },
1880
+ level
1881
+ )) }),
1882
+ groomer.sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { children: [
1883
+ section.title,
1884
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1885
+ "select",
1886
+ {
1887
+ value: sections[section.id],
1888
+ onChange: (event) => setSections({
1889
+ ...sections,
1890
+ [section.id]: event.target.value
1891
+ }),
1892
+ children: [
1893
+ ["full", "compact", "brief"].map((level) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: level, children: section.levelLabels?.[level] ?? level }, level)),
1894
+ section.cuttable ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "off", children: "Exclude" }) : null
1895
+ ]
1896
+ }
1897
+ ),
1898
+ section.description ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: section.description }) : null
1899
+ ] }, section.id))
1900
+ ] }) : null
1901
+ ] }),
1902
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { children: [
1903
+ text.length.toLocaleString(),
1904
+ " characters \xB7 approximately",
1905
+ " ",
1906
+ Math.ceil(text.length / 4).toLocaleString(),
1907
+ " tokens"
1908
+ ] }),
1909
+ error || buildError ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { role: "alert", children: error ?? buildError }) : null,
1910
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogFooter, { children: [
1911
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1912
+ Button,
1913
+ {
1914
+ type: "button",
1915
+ variant: "ghost",
1916
+ disabled: busy,
1917
+ onClick: onClose,
1918
+ children: "Cancel"
1919
+ }
1920
+ ),
1921
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1922
+ Button,
1923
+ {
1924
+ type: "button",
1925
+ disabled: busy || !prepared || Boolean(buildError),
1926
+ onClick: async () => {
1927
+ if (!prepared) return;
1928
+ setBusy(true);
1929
+ setError(null);
1930
+ try {
1931
+ handingOff.current = true;
1932
+ await onPrepare(structuredPayload(prepared), title);
1933
+ } catch (cause) {
1934
+ handingOff.current = false;
1935
+ setError(
1936
+ cause instanceof Error ? cause.message : "Could not open Alchemy."
1937
+ );
1938
+ } finally {
1939
+ setBusy(false);
1940
+ }
1941
+ },
1942
+ children: busy ? "Preparing\u2026" : "Prepare in Alchemy"
1943
+ }
1944
+ )
1945
+ ] })
1946
+ ]
1947
+ }
1948
+ )
1949
+ }
1950
+ );
1951
+ }
1952
+
1953
+ // src/data-table/copy.tsx
1954
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1955
+ function CopyButtons(props) {
1956
+ const { CopyControls } = useMatrxDataTableHost();
1957
+ if (CopyControls) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CopyControls, { ...props });
1958
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MatrxCopyMenu, { ...props });
696
1959
  }
697
1960
 
698
1961
  // src/data-table/MatrxUuidCell.tsx
699
- var import_react4 = require("react");
1962
+ var import_react3 = require("react");
1963
+
1964
+ // src/data-table/useTableNotice.tsx
1965
+ var import_react2 = require("react");
1966
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1967
+ function useTableNotice() {
1968
+ const host = useMatrxDataTableHost();
1969
+ const [message, setMessage] = (0, import_react2.useState)(null);
1970
+ const notify = {
1971
+ success(text) {
1972
+ if (host.notify) host.notify.success(text);
1973
+ else setMessage({ text, error: false });
1974
+ },
1975
+ error(text) {
1976
+ if (host.notify) host.notify.error(text);
1977
+ else setMessage({ text, error: true });
1978
+ }
1979
+ };
1980
+ const notice = message ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { role: message.error ? "alert" : "status", className: message.error ? "text-xs text-destructive" : "text-xs text-muted-foreground", children: [
1981
+ message.text,
1982
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", "aria-label": "Dismiss notification", className: "ml-2 underline", onClick: () => setMessage(null), children: "Dismiss" })
1983
+ ] }) : null;
1984
+ return { notify, notice };
1985
+ }
700
1986
 
701
1987
  // src/tooltip.tsx
702
1988
  var TooltipPrimitive = __toESM(require("@radix-ui/react-tooltip"), 1);
703
- var React7 = __toESM(require("react"), 1);
704
- var import_jsx_runtime12 = require("react/jsx-runtime");
1989
+ var React9 = __toESM(require("react"), 1);
1990
+ var import_jsx_runtime14 = require("react/jsx-runtime");
705
1991
  var TooltipProvider = TooltipPrimitive.Provider;
706
1992
  var Tooltip = TooltipPrimitive.Root;
707
1993
  var TooltipTrigger = TooltipPrimitive.Trigger;
708
- var TooltipContent = React7.forwardRef(({ className, sideOffset = 4, container, animated = true, ...props }, ref) => {
1994
+ var TooltipContent = React9.forwardRef(({ className, sideOffset = 4, container, animated = true, ...props }, ref) => {
709
1995
  const resolved = usePortalContainer(container);
710
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TooltipPrimitive.Portal, { container: resolved ?? null, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1996
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TooltipPrimitive.Portal, { container: resolved ?? null, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
711
1997
  TooltipPrimitive.Content,
712
1998
  {
713
1999
  ref,
@@ -725,7 +2011,7 @@ var TooltipContent = React7.forwardRef(({ className, sideOffset = 4, container,
725
2011
  TooltipContent.displayName = TooltipPrimitive.Content.displayName;
726
2012
 
727
2013
  // src/data-table/MatrxUuidCell.tsx
728
- var import_jsx_runtime13 = require("react/jsx-runtime");
2014
+ var import_jsx_runtime15 = require("react/jsx-runtime");
729
2015
  var SHORT_LEN = 8;
730
2016
  function shortUuid(id, len = SHORT_LEN) {
731
2017
  return id.slice(0, len);
@@ -764,18 +2050,18 @@ function MatrxUuidCell({
764
2050
  }) {
765
2051
  const { resolveEntityDoors, ResourcePeek } = useMatrxDataTableHost();
766
2052
  const { notify, notice } = useTableNotice();
767
- const [copied, setCopied] = (0, import_react4.useState)(false);
768
- const [opening, setOpening] = (0, import_react4.useState)(false);
769
- const [peekOpen, setPeekOpen] = (0, import_react4.useState)(false);
770
- const timer = (0, import_react4.useRef)(null);
771
- (0, import_react4.useEffect)(
2053
+ const [copied, setCopied] = (0, import_react3.useState)(false);
2054
+ const [opening, setOpening] = (0, import_react3.useState)(false);
2055
+ const [peekOpen, setPeekOpen] = (0, import_react3.useState)(false);
2056
+ const timer = (0, import_react3.useRef)(null);
2057
+ (0, import_react3.useEffect)(
772
2058
  () => () => {
773
2059
  if (timer.current) clearTimeout(timer.current);
774
2060
  },
775
2061
  []
776
2062
  );
777
2063
  if (!value) {
778
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-muted-foreground/40", children: "\u2014" });
2064
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-muted-foreground/40", children: "\u2014" });
779
2065
  }
780
2066
  const doors = token ? resolveEntityDoors?.(token, value, href) ?? null : null;
781
2067
  const resolvedHref = doors?.href ?? href ?? null;
@@ -818,13 +2104,13 @@ function MatrxUuidCell({
818
2104
  };
819
2105
  const idNode = (() => {
820
2106
  if (forbidden) {
821
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "inline-flex min-w-0 items-center gap-1 font-mono text-xs text-muted-foreground/70 tabular-nums", children: [
822
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "min-w-0 truncate", children: display }),
823
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ShieldOff, { className: "h-3 w-3 shrink-0 opacity-60", "aria-hidden": true })
2107
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "inline-flex min-w-0 items-center gap-1 font-mono text-xs text-muted-foreground/70 tabular-nums", children: [
2108
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "min-w-0 truncate", children: display }),
2109
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ShieldOff, { className: "h-3 w-3 shrink-0 opacity-60", "aria-hidden": true })
824
2110
  ] });
825
2111
  }
826
2112
  if (onOpen) {
827
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2113
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
828
2114
  "button",
829
2115
  {
830
2116
  type: "button",
@@ -833,14 +2119,14 @@ function MatrxUuidCell({
833
2119
  disabled: opening,
834
2120
  className: "inline-flex min-w-0 items-center gap-1 font-mono text-xs text-sky-600 hover:underline dark:text-sky-400 disabled:opacity-60",
835
2121
  children: [
836
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "min-w-0 truncate tabular-nums", children: display }),
837
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PanelRight, { className: "h-3 w-3 shrink-0 opacity-50" })
2122
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "min-w-0 truncate tabular-nums", children: display }),
2123
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PanelRight, { className: "h-3 w-3 shrink-0 opacity-50" })
838
2124
  ]
839
2125
  }
840
2126
  );
841
2127
  }
842
2128
  if (resolvedHref) {
843
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2129
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
844
2130
  "a",
845
2131
  {
846
2132
  href: resolvedHref,
@@ -850,15 +2136,15 @@ function MatrxUuidCell({
850
2136
  onClick: stop,
851
2137
  className: "inline-flex min-w-0 items-center gap-1 font-mono text-xs text-sky-600 hover:underline dark:text-sky-400",
852
2138
  children: [
853
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "min-w-0 truncate tabular-nums", children: display }),
854
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ExternalLink, { className: "h-3 w-3 shrink-0 opacity-50" })
2139
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "min-w-0 truncate tabular-nums", children: display }),
2140
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ExternalLink, { className: "h-3 w-3 shrink-0 opacity-50" })
855
2141
  ]
856
2142
  }
857
2143
  );
858
2144
  }
859
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "min-w-0 truncate font-mono text-xs text-muted-foreground tabular-nums select-all", children: display });
2145
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "min-w-0 truncate font-mono text-xs text-muted-foreground tabular-nums select-all", children: display });
860
2146
  })();
861
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2147
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
862
2148
  "span",
863
2149
  {
864
2150
  "data-matrx-cell-control": true,
@@ -868,11 +2154,11 @@ function MatrxUuidCell({
868
2154
  ),
869
2155
  onClick: stop,
870
2156
  children: [
871
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Tooltip, { children: [
872
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "inline-flex min-w-0 max-w-full items-center", children: idNode }) }),
873
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TooltipContent, { side: "top", className: "max-w-sm break-all font-mono", children: forbidden ? `No access \xB7 ${tip}` : tip })
2157
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Tooltip, { children: [
2158
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "inline-flex min-w-0 max-w-full items-center", children: idNode }) }),
2159
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TooltipContent, { side: "top", className: "max-w-sm break-all font-mono", children: forbidden ? `No access \xB7 ${tip}` : tip })
874
2160
  ] }) }),
875
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2161
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
876
2162
  "button",
877
2163
  {
878
2164
  type: "button",
@@ -883,10 +2169,10 @@ function MatrxUuidCell({
883
2169
  "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:text-foreground focus:outline-none",
884
2170
  copied && "text-primary"
885
2171
  ),
886
- children: copied ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Copy, { className: "h-3 w-3" })
2172
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Copy, { className: "h-3 w-3" })
887
2173
  }
888
2174
  ),
889
- canPeek ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2175
+ canPeek ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
890
2176
  "button",
891
2177
  {
892
2178
  type: "button",
@@ -897,10 +2183,10 @@ function MatrxUuidCell({
897
2183
  "aria-label": `Quick look at ${label ?? "record"}`,
898
2184
  title: `Quick look at ${label ?? "record"}`,
899
2185
  className: "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:text-foreground focus:outline-none",
900
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Lightbulb, { className: "h-3 w-3" })
2186
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Lightbulb, { className: "h-3 w-3" })
901
2187
  }
902
2188
  ) : null,
903
- onOpen && resolvedHref && !forbidden ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2189
+ onOpen && resolvedHref && !forbidden ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
904
2190
  "a",
905
2191
  {
906
2192
  href: resolvedHref,
@@ -910,12 +2196,12 @@ function MatrxUuidCell({
910
2196
  "aria-label": `Open ${label ?? "record"} in a new tab`,
911
2197
  title: `Open ${label ?? "record"} in a new tab`,
912
2198
  className: "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:text-foreground focus:outline-none",
913
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ExternalLink, { className: "h-3 w-3" })
2199
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ExternalLink, { className: "h-3 w-3" })
914
2200
  }
915
2201
  ) : null,
916
2202
  trailing,
917
2203
  notice,
918
- canPeek && peekOpen && ResourcePeek ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2204
+ canPeek && peekOpen && ResourcePeek ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
919
2205
  ResourcePeek,
920
2206
  {
921
2207
  kind: doors?.peekKind ?? token ?? "record",
@@ -935,7 +2221,7 @@ function isUuidValue(value) {
935
2221
  }
936
2222
 
937
2223
  // src/data-table/DataRowInspector.tsx
938
- var import_jsx_runtime14 = require("react/jsx-runtime");
2224
+ var import_jsx_runtime16 = require("react/jsx-runtime");
939
2225
  var noFieldToken = () => null;
940
2226
  function formatValue(value) {
941
2227
  if (value == null) return "\u2014";
@@ -964,7 +2250,7 @@ function DataRowInspector({
964
2250
  tokenForField = noFieldToken
965
2251
  }) {
966
2252
  const entries = isPlainObject(row) ? Object.entries(row) : [["value", row]];
967
- const fieldButtons = (key, value) => fieldCopy ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "absolute right-1 top-1 opacity-0 transition-opacity focus-within:opacity-100 group-hover/field:opacity-100", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2253
+ const fieldButtons = (key, value) => fieldCopy ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "absolute right-1 top-1 opacity-0 transition-opacity focus-within:opacity-100 group-hover/field:opacity-100", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
968
2254
  CopyButtons,
969
2255
  {
970
2256
  size: "xs",
@@ -979,21 +2265,21 @@ function DataRowInspector({
979
2265
  })
980
2266
  }
981
2267
  ) }) : null;
982
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2268
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
983
2269
  "div",
984
2270
  {
985
2271
  className: cn("flex h-full min-h-0 flex-col overflow-hidden", className),
986
2272
  children: [
987
- title ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "shrink-0 border-b border-border px-3 py-2 text-sm font-medium", children: title }) : null,
988
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "min-h-0 flex-1 overflow-auto p-3", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dl", { className: "space-y-2", children: entries.map(([key, value]) => {
2273
+ title ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "shrink-0 border-b border-border px-3 py-2 text-sm font-medium", children: title }) : null,
2274
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "min-h-0 flex-1 overflow-auto p-3", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dl", { className: "space-y-2", children: entries.map(([key, value]) => {
989
2275
  if (isUuidValue(value)) {
990
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2276
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
991
2277
  "div",
992
2278
  {
993
2279
  className: "group/field relative grid grid-cols-[minmax(7rem,9rem)_1fr] gap-2 rounded-md border border-border/60 bg-muted/20 px-2.5 py-2",
994
2280
  children: [
995
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dt", { className: "truncate text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: key }),
996
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dd", { className: "min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2281
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dt", { className: "truncate text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: key }),
2282
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dd", { className: "min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
997
2283
  MatrxUuidCell,
998
2284
  {
999
2285
  value,
@@ -1008,13 +2294,13 @@ function DataRowInspector({
1008
2294
  }
1009
2295
  const formatted = formatValue(value);
1010
2296
  const multiline = formatted.includes("\n") || formatted.length > 80;
1011
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2297
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
1012
2298
  "div",
1013
2299
  {
1014
2300
  className: "group/field relative grid grid-cols-[minmax(7rem,9rem)_1fr] gap-2 rounded-md border border-border/60 bg-muted/20 py-2 pl-2.5 pr-12",
1015
2301
  children: [
1016
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dt", { className: "truncate text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: key }),
1017
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2302
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dt", { className: "truncate text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: key }),
2303
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1018
2304
  "dd",
1019
2305
  {
1020
2306
  className: cn(
@@ -1036,7 +2322,7 @@ function DataRowInspector({
1036
2322
  }
1037
2323
 
1038
2324
  // src/data-table/DataRowWindow.tsx
1039
- var import_jsx_runtime15 = require("react/jsx-runtime");
2325
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1040
2326
  function DataRowWindow({
1041
2327
  isOpen,
1042
2328
  onClose,
@@ -1056,15 +2342,15 @@ function DataRowWindow({
1056
2342
  const hasEdit = editContent != null;
1057
2343
  const hasTabs = hasEdit && children == null;
1058
2344
  const initialTab = defaultTab ?? (hasEdit ? "edit" : "view");
1059
- const [tab, setTab] = (0, import_react5.useState)(initialTab);
1060
- (0, import_react5.useEffect)(() => {
2345
+ const [tab, setTab] = (0, import_react4.useState)(initialTab);
2346
+ (0, import_react4.useEffect)(() => {
1061
2347
  if (!isOpen) return;
1062
2348
  setTab(defaultTab ?? (hasEdit ? "edit" : "view"));
1063
2349
  }, [isOpen, windowId, hasEdit, defaultTab]);
1064
2350
  if (!isOpen) return null;
1065
- const viewBody = viewContent ?? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DataRowInspector, { row });
1066
- const body = children ?? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex h-full min-h-0 flex-1 flex-col overflow-hidden", children: hasTabs ? tab === "edit" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto", children: editContent }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto", children: viewBody }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto", children: viewBody }) });
1067
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2351
+ const viewBody = viewContent ?? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DataRowInspector, { row });
2352
+ const body = children ?? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex h-full min-h-0 flex-1 flex-col overflow-hidden", children: hasTabs ? tab === "edit" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto", children: editContent }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto", children: viewBody }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto", children: viewBody }) });
2353
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1068
2354
  WindowPanel,
1069
2355
  {
1070
2356
  id: windowId,
@@ -1078,26 +2364,26 @@ function DataRowWindow({
1078
2364
  bodyClassName: "flex min-h-0 flex-1 flex-col overflow-hidden p-0",
1079
2365
  sidebarDefaultSize: 140,
1080
2366
  sidebarMinSize: 110,
1081
- sidebar: hasTabs ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2367
+ sidebar: hasTabs ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1082
2368
  "nav",
1083
2369
  {
1084
2370
  "aria-label": "Row window tabs",
1085
2371
  className: "flex h-full flex-col gap-0.5 overflow-y-auto p-1.5",
1086
2372
  children: [
1087
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2373
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1088
2374
  TabButton,
1089
2375
  {
1090
2376
  active: tab === "view",
1091
- icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Eye, { className: "h-4 w-4 shrink-0" }),
2377
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Eye, { className: "h-4 w-4 shrink-0" }),
1092
2378
  label: "View",
1093
2379
  onClick: () => setTab("view")
1094
2380
  }
1095
2381
  ),
1096
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2382
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1097
2383
  TabButton,
1098
2384
  {
1099
2385
  active: tab === "edit",
1100
- icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Pencil, { className: "h-4 w-4 shrink-0" }),
2386
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Pencil, { className: "h-4 w-4 shrink-0" }),
1101
2387
  label: "Edit",
1102
2388
  onClick: () => setTab("edit")
1103
2389
  }
@@ -1115,7 +2401,7 @@ function TabButton({
1115
2401
  label,
1116
2402
  onClick
1117
2403
  }) {
1118
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2404
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1119
2405
  "button",
1120
2406
  {
1121
2407
  type: "button",
@@ -1128,7 +2414,7 @@ function TabButton({
1128
2414
  ),
1129
2415
  children: [
1130
2416
  icon,
1131
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "min-w-0 flex-1 truncate", children: label })
2417
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "min-w-0 flex-1 truncate", children: label })
1132
2418
  ]
1133
2419
  }
1134
2420
  );