@ai-matrx/design-system 0.18.1 → 0.18.3

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.
@@ -708,6 +708,10 @@ function materialIdentity(material) {
708
708
  }
709
709
  return `payload:${id}`;
710
710
  }
711
+ function snapshotContent(snapshot) {
712
+ const { sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits } = snapshot;
713
+ return JSON.stringify({ sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits });
714
+ }
711
715
  function useFence() {
712
716
  const mounted = React7.useRef(true);
713
717
  const nextId = React7.useRef(0);
@@ -756,7 +760,12 @@ async function buildArtifact(draft, format, label) {
756
760
  const extension = format === "plain" ? "txt" : format === "compact-json" ? "json" : format;
757
761
  return (0, import_content_transfer.serialize)(draft, format, { spreadsheetSafe: format === "csv" || format === "tsv", filename: `${filenameFor(label)}.${extension}` });
758
762
  }
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 }) {
763
+ function ContentTransferMenu(props) {
764
+ const inherited = React7.useContext(CapabilityContext);
765
+ const identityKey = props.capabilities?.identityKey ?? inherited.identityKey;
766
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ContentTransferMenuSession, { ...props }, identityKey ?? "anonymous");
767
+ }
768
+ function ContentTransferMenuSession({ source, surface, label, projection, sections, preferences, table, formatSources, variants = EMPTY_VARIANTS, actions = [], capabilities: local, disabled, enabled = true, className, stopPropagation = true, hide = [], controllerRef }) {
760
769
  const triggerRef = React7.useRef(null);
761
770
  const inherited = React7.useContext(CapabilityContext);
762
771
  const nearestSurface = React7.useContext(SurfaceContext);
@@ -805,7 +814,7 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
805
814
  const captured = await material.capture(signal);
806
815
  sourceMaterial = { id: captured.snapshot.sourceId, label: captured.snapshot.label, capture: async () => captured };
807
816
  } 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;
817
+ sourceMaterial = { id: materialIdentity(material), label: sourceLabel, capture: async () => (0, import_content_transfer.directSource)(material, { sourceId: materialIdentity(material), label: sourceLabel, ...applyMenuProjection && projection ? { projection } : {}, ...applyMenuProjection && sections ? { sections } : {} }) };
809
818
  } else if (applyMenuProjection && (projection || sections)) {
810
819
  sourceMaterial = {
811
820
  id: material.id,
@@ -822,7 +831,7 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
822
831
  } else {
823
832
  sourceMaterial = material;
824
833
  }
825
- const raw = "kind" in sourceMaterial ? (0, import_content_transfer.directSource)(sourceMaterial) : await sourceMaterial.capture({ scope, signal });
834
+ const raw = await sourceMaterial.capture({ scope, signal });
826
835
  const constrained = (0, import_content_transfer.resolveTransferPreferences)(providerPreferences, preferences, raw.snapshot.limits);
827
836
  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
837
  if (scope !== "all-matching" || !collectPages) return result;
@@ -882,31 +891,117 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
882
891
  fence.finish(controller);
883
892
  }
884
893
  }, [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]);
894
+ const openWorkspace = React7.useCallback(
895
+ async (key = primaryWorkspaceKey, variant) => {
896
+ const variantMaterial = variant && "kind" in variant.source ? {
897
+ id: `variant:${key}`,
898
+ label: variant.label,
899
+ capture: async () => (0, import_content_transfer.directSource)(variant.source, {
900
+ sourceId: `variant:${key}`,
901
+ label: variant.label
902
+ })
903
+ } : variant?.source;
904
+ const captureWorkspace = (scope, signal, collectPages = true) => captureOne(variantMaterial ?? primary, scope, signal, true, collectPages);
905
+ workspaceCaptures.current.set(key, captureWorkspace);
906
+ const existing = workspaces[key];
907
+ const { controller, id } = fence.begin();
908
+ if (existing) {
909
+ try {
910
+ const allMatching = existing.scope === "all-matching";
911
+ const fresh = await captureWorkspace(
912
+ existing.scope,
913
+ controller.signal,
914
+ !allMatching
915
+ );
916
+ if (!fence.valid(id, controller) || !workspaceCaptures.current.has(key))
917
+ return;
918
+ if (allMatching && (!existing.allMatching || !fresh.allMatching))
919
+ throw new Error(
920
+ "This source can no longer provide an all-matching snapshot."
921
+ );
922
+ const collectionIdentity = (value) => value && {
923
+ query: value.source.query,
924
+ projection: value.projection,
925
+ columns: value.columns,
926
+ limits: value.limits
927
+ };
928
+ const changed = allMatching ? JSON.stringify(collectionIdentity(existing.allMatching)) !== JSON.stringify(collectionIdentity(fresh.allMatching)) : snapshotContent(existing.snapshot) !== snapshotContent(fresh.snapshot);
929
+ setWorkspaces((current) => {
930
+ const retained = current[key];
931
+ if (!retained) return current;
932
+ return {
933
+ ...current,
934
+ [key]: {
935
+ ...retained,
936
+ error: fresh.collectionError,
937
+ ...allMatching ? { sourceStale: changed } : changed ? { pending: { ...fresh, scope: retained.scope } } : { pending: void 0 }
938
+ }
939
+ };
940
+ });
941
+ setWorkspaceKey(key);
942
+ setWorkspaceOpen(true);
943
+ } catch (error) {
944
+ if (fence.valid(id, controller) && workspaceCaptures.current.has(key)) {
945
+ setWorkspaces(
946
+ (current) => current[key] ? {
947
+ ...current,
948
+ [key]: {
949
+ ...current[key],
950
+ error: errorMessage(error, "Could not refresh source")
951
+ }
952
+ } : current
953
+ );
954
+ setWorkspaceKey(key);
955
+ setWorkspaceOpen(true);
956
+ }
957
+ } finally {
958
+ fence.finish(controller);
959
+ }
960
+ return;
961
+ }
962
+ try {
963
+ const result = await captureWorkspace(initialScope, controller.signal);
964
+ if (!fence.valid(id, controller)) return;
965
+ const resolved = (0, import_content_transfer.resolveTransferPreferences)(
966
+ providerPreferences,
967
+ preferences,
968
+ result.snapshot.limits
969
+ );
970
+ const original = (0, import_content_transfer.createDraft)(result.snapshot);
971
+ const base = {
972
+ ...result,
973
+ key,
974
+ original,
975
+ draft: original,
976
+ scope: initialScope,
977
+ preferences: resolved,
978
+ ...result.collectionError ? { error: result.collectionError } : {}
979
+ };
980
+ setWorkspaces((current) => ({
981
+ ...current,
982
+ [key]: { ...base, draft: rebuild(base, initialControls(base)) }
983
+ }));
984
+ setWorkspaceKey(key);
985
+ setWorkspaceOpen(true);
986
+ } catch (error) {
987
+ if (fence.valid(id, controller))
988
+ setNotice(errorMessage(error, "Could not capture content"));
989
+ } finally {
990
+ fence.finish(controller);
991
+ }
992
+ },
993
+ [
994
+ captureOne,
995
+ capturePrimary,
996
+ fence,
997
+ initialScope,
998
+ preferences,
999
+ primaryWorkspaceKey,
1000
+ primary,
1001
+ providerPreferences,
1002
+ workspaces
1003
+ ]
1004
+ );
910
1005
  React7.useImperativeHandle(controllerRef, () => ({
911
1006
  focusTrigger: () => triggerRef.current?.focus(),
912
1007
  prepare: async (requestedSource, options) => {
@@ -1049,6 +1144,9 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1049
1144
  const [instruction, setInstruction] = React7.useState("");
1050
1145
  const [busy, setBusy] = React7.useState(null);
1051
1146
  const [aiFailed, setAiFailed] = React7.useState(false);
1147
+ React7.useEffect(() => {
1148
+ setAiFailed(false);
1149
+ }, [state.snapshot.id, state.draft.revision]);
1052
1150
  const activeAi = React7.useRef(null);
1053
1151
  const activeCapture = React7.useRef(null);
1054
1152
  React7.useEffect(() => {
@@ -1126,7 +1224,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1126
1224
  setBusy("capture");
1127
1225
  try {
1128
1226
  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 });
1227
+ if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, sourceStale: false, error: result.collectionError });
1130
1228
  } catch (error) {
1131
1229
  if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "Refresh failed") });
1132
1230
  } finally {
@@ -1359,7 +1457,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1359
1457
  activeCapture.current = null;
1360
1458
  setBusy(null);
1361
1459
  }, 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,
1460
+ ai && aiFailed && 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
1461
  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
1462
  state.error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { role: "alert", className: "matrx-alchemy-error", children: state.error }) : null
1365
1463
  ] }) : null
@@ -1369,6 +1467,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1369
1467
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", onClick: () => setPendingControls(null), children: "Keep edited draft" }),
1370
1468
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", onClick: applyPendingControls, children: "Rebuild from source" })
1371
1469
  ] }) : null,
1470
+ state.sourceStale ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { role: "alert", className: "matrx-alchemy-decision", children: "The source changed since this draft. Use Refresh source to review the latest data." }) : null,
1372
1471
  state.pending ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "matrx-alchemy-decision", role: "alert", children: [
1373
1472
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: "A fresh source snapshot is ready." }),
1374
1473
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { type: "button", variant: "outline", onClick: () => resolveCapture("keep"), children: "Keep current snapshot and draft" }),
@@ -1486,7 +1585,17 @@ var sourceFor = (id, label, build) => ({
1486
1585
  }
1487
1586
  });
1488
1587
  function MatrxCopyMenu(props) {
1489
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MatrxCopyMenuSession, { ...props }, props.sourceId ?? props.label);
1588
+ const inherited = useContentTransferCapabilities();
1589
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1590
+ MatrxCopyMenuSession,
1591
+ {
1592
+ ...props
1593
+ },
1594
+ JSON.stringify([
1595
+ inherited.identityKey ?? null,
1596
+ props.sourceId ?? props.label
1597
+ ])
1598
+ );
1490
1599
  }
1491
1600
  function MatrxCopyMenuSession(props) {
1492
1601
  const instance = React8.useId();
@@ -1514,7 +1623,7 @@ function MatrxCopyMenuSession(props) {
1514
1623
  })) : void 0;
1515
1624
  const agent = aiVisible && props.agent !== void 0 ? make("agent", () => asPayload(resolve(props.agent))) : void 0;
1516
1625
  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", () => {
1626
+ const table = (exportVisible || props.primarySource === "table") && props.export?.sheetRows ? make("rows", () => {
1518
1627
  const value = (0, import_content_transfer2.normalizeTransferJson)(props.export.sheetRows());
1519
1628
  if (!Array.isArray(value) || value.some(
1520
1629
  (row) => row === null || typeof row !== "object" || Array.isArray(row)
@@ -1661,7 +1770,7 @@ function MatrxCopyMenuSession(props) {
1661
1770
  return props.sendToSheet(draft.payload.rows, props.label);
1662
1771
  }
1663
1772
  });
1664
- const source = json ?? table ?? structuredAgent ?? human ?? builderVariants[0]?.source;
1773
+ const source = (props.primarySource === "table" ? table : void 0) ?? json ?? table ?? structuredAgent ?? human ?? builderVariants[0]?.source;
1665
1774
  if (!source && !actions.length) return null;
1666
1775
  const target = source ?? make("actions", () => ({ kind: "text", text: props.label }));
1667
1776
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
@@ -1681,7 +1790,7 @@ function MatrxCopyMenuSession(props) {
1681
1790
  formatSources: {
1682
1791
  ...human ? { plain: human, markdown: humanMarkdown, html: humanMarkdown } : {},
1683
1792
  ...json ? { json, "compact-json": json } : {},
1684
- ...table ? { csv: table, tsv: table } : {}
1793
+ ...table && exportVisible ? { csv: table, tsv: table } : {}
1685
1794
  },
1686
1795
  ...props.disabled !== void 0 ? { disabled: props.disabled } : {},
1687
1796
  className: cn(
@@ -1954,8 +2063,9 @@ function SourceConfiguration({
1954
2063
  var import_jsx_runtime12 = require("react/jsx-runtime");
1955
2064
  function CopyButtons(props) {
1956
2065
  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 });
2066
+ const copyProps = props.export?.sheetRows ? { ...props, primarySource: "table" } : props;
2067
+ if (CopyControls) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CopyControls, { ...copyProps });
2068
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MatrxCopyMenu, { ...copyProps });
1959
2069
  }
1960
2070
 
1961
2071
  // src/data-table/MatrxUuidCell.tsx