@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.
@@ -701,6 +701,10 @@ function materialIdentity(material) {
701
701
  }
702
702
  return `payload:${id}`;
703
703
  }
704
+ function snapshotContent(snapshot) {
705
+ const { sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits } = snapshot;
706
+ return JSON.stringify({ sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits });
707
+ }
704
708
  function useFence() {
705
709
  const mounted = React7.useRef(true);
706
710
  const nextId = React7.useRef(0);
@@ -749,7 +753,12 @@ async function buildArtifact(draft, format, label) {
749
753
  const extension = format === "plain" ? "txt" : format === "compact-json" ? "json" : format;
750
754
  return serialize(draft, format, { spreadsheetSafe: format === "csv" || format === "tsv", filename: `${filenameFor(label)}.${extension}` });
751
755
  }
752
- function ContentTransferMenu({ source, surface, label, projection, sections, preferences, table, formatSources, variants = EMPTY_VARIANTS, actions = [], capabilities: local, disabled, enabled = true, className, stopPropagation = true, hide = [], controllerRef }) {
756
+ function ContentTransferMenu(props) {
757
+ const inherited = React7.useContext(CapabilityContext);
758
+ const identityKey = props.capabilities?.identityKey ?? inherited.identityKey;
759
+ return /* @__PURE__ */ jsx10(ContentTransferMenuSession, { ...props }, identityKey ?? "anonymous");
760
+ }
761
+ function ContentTransferMenuSession({ source, surface, label, projection, sections, preferences, table, formatSources, variants = EMPTY_VARIANTS, actions = [], capabilities: local, disabled, enabled = true, className, stopPropagation = true, hide = [], controllerRef }) {
753
762
  const triggerRef = React7.useRef(null);
754
763
  const inherited = React7.useContext(CapabilityContext);
755
764
  const nearestSurface = React7.useContext(SurfaceContext);
@@ -798,7 +807,7 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
798
807
  const captured = await material.capture(signal);
799
808
  sourceMaterial = { id: captured.snapshot.sourceId, label: captured.snapshot.label, capture: async () => captured };
800
809
  } else if ("kind" in material) {
801
- sourceMaterial = applyMenuProjection ? { id: "direct", label: sourceLabel, capture: async () => directSource(material, { label: sourceLabel, ...projection ? { projection } : {}, ...sections ? { sections } : {} }) } : material;
810
+ sourceMaterial = { id: materialIdentity(material), label: sourceLabel, capture: async () => directSource(material, { sourceId: materialIdentity(material), label: sourceLabel, ...applyMenuProjection && projection ? { projection } : {}, ...applyMenuProjection && sections ? { sections } : {} }) };
802
811
  } else if (applyMenuProjection && (projection || sections)) {
803
812
  sourceMaterial = {
804
813
  id: material.id,
@@ -815,7 +824,7 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
815
824
  } else {
816
825
  sourceMaterial = material;
817
826
  }
818
- const raw = "kind" in sourceMaterial ? directSource(sourceMaterial) : await sourceMaterial.capture({ scope, signal });
827
+ const raw = await sourceMaterial.capture({ scope, signal });
819
828
  const constrained = resolveTransferPreferences(providerPreferences, preferences, raw.snapshot.limits);
820
829
  const result = await capture({ id: raw.snapshot.sourceId, label: raw.snapshot.label, capture: async () => ({ ...raw, snapshot: { ...raw.snapshot, limits: constrained.limits } }) }, signal, scope);
821
830
  if (scope !== "all-matching" || !collectPages) return result;
@@ -875,31 +884,117 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
875
884
  fence.finish(controller);
876
885
  }
877
886
  }, [captureOne, capturePrimary, fence, formatSources, initialScope, preferences, providerPreferences, report, transport]);
878
- const openWorkspace = React7.useCallback(async (key = primaryWorkspaceKey, variant) => {
879
- const variantMaterial = variant && "kind" in variant.source ? { id: `variant:${key}`, label: variant.label, capture: async () => directSource(variant.source, { label: variant.label }) } : variant?.source;
880
- const captureWorkspace = variantMaterial ? (scope, signal) => captureOne(variantMaterial, scope, signal, true) : capturePrimary;
881
- workspaceCaptures.current.set(key, captureWorkspace);
882
- if (workspaces[key]) {
883
- setWorkspaceKey(key);
884
- setWorkspaceOpen(true);
885
- return;
886
- }
887
- const { controller, id } = fence.begin();
888
- try {
889
- const result = await captureWorkspace(initialScope, controller.signal);
890
- if (!fence.valid(id, controller)) return;
891
- const resolved = resolveTransferPreferences(providerPreferences, preferences, result.snapshot.limits);
892
- const original = createDraft(result.snapshot);
893
- const base = { ...result, key, original, draft: original, scope: initialScope, preferences: resolved, ...result.collectionError ? { error: result.collectionError } : {} };
894
- setWorkspaces((current) => ({ ...current, [key]: { ...base, draft: rebuild(base, initialControls(base)) } }));
895
- setWorkspaceKey(key);
896
- setWorkspaceOpen(true);
897
- } catch (error) {
898
- if (fence.valid(id, controller)) setNotice(errorMessage(error, "Could not capture content"));
899
- } finally {
900
- fence.finish(controller);
901
- }
902
- }, [captureOne, capturePrimary, fence, initialScope, preferences, primaryWorkspaceKey, providerPreferences, workspaces]);
887
+ const openWorkspace = React7.useCallback(
888
+ async (key = primaryWorkspaceKey, variant) => {
889
+ const variantMaterial = variant && "kind" in variant.source ? {
890
+ id: `variant:${key}`,
891
+ label: variant.label,
892
+ capture: async () => directSource(variant.source, {
893
+ sourceId: `variant:${key}`,
894
+ label: variant.label
895
+ })
896
+ } : variant?.source;
897
+ const captureWorkspace = (scope, signal, collectPages = true) => captureOne(variantMaterial ?? primary, scope, signal, true, collectPages);
898
+ workspaceCaptures.current.set(key, captureWorkspace);
899
+ const existing = workspaces[key];
900
+ const { controller, id } = fence.begin();
901
+ if (existing) {
902
+ try {
903
+ const allMatching = existing.scope === "all-matching";
904
+ const fresh = await captureWorkspace(
905
+ existing.scope,
906
+ controller.signal,
907
+ !allMatching
908
+ );
909
+ if (!fence.valid(id, controller) || !workspaceCaptures.current.has(key))
910
+ return;
911
+ if (allMatching && (!existing.allMatching || !fresh.allMatching))
912
+ throw new Error(
913
+ "This source can no longer provide an all-matching snapshot."
914
+ );
915
+ const collectionIdentity = (value) => value && {
916
+ query: value.source.query,
917
+ projection: value.projection,
918
+ columns: value.columns,
919
+ limits: value.limits
920
+ };
921
+ const changed = allMatching ? JSON.stringify(collectionIdentity(existing.allMatching)) !== JSON.stringify(collectionIdentity(fresh.allMatching)) : snapshotContent(existing.snapshot) !== snapshotContent(fresh.snapshot);
922
+ setWorkspaces((current) => {
923
+ const retained = current[key];
924
+ if (!retained) return current;
925
+ return {
926
+ ...current,
927
+ [key]: {
928
+ ...retained,
929
+ error: fresh.collectionError,
930
+ ...allMatching ? { sourceStale: changed } : changed ? { pending: { ...fresh, scope: retained.scope } } : { pending: void 0 }
931
+ }
932
+ };
933
+ });
934
+ setWorkspaceKey(key);
935
+ setWorkspaceOpen(true);
936
+ } catch (error) {
937
+ if (fence.valid(id, controller) && workspaceCaptures.current.has(key)) {
938
+ setWorkspaces(
939
+ (current) => current[key] ? {
940
+ ...current,
941
+ [key]: {
942
+ ...current[key],
943
+ error: errorMessage(error, "Could not refresh source")
944
+ }
945
+ } : current
946
+ );
947
+ setWorkspaceKey(key);
948
+ setWorkspaceOpen(true);
949
+ }
950
+ } finally {
951
+ fence.finish(controller);
952
+ }
953
+ return;
954
+ }
955
+ try {
956
+ const result = await captureWorkspace(initialScope, controller.signal);
957
+ if (!fence.valid(id, controller)) return;
958
+ const resolved = resolveTransferPreferences(
959
+ providerPreferences,
960
+ preferences,
961
+ result.snapshot.limits
962
+ );
963
+ const original = createDraft(result.snapshot);
964
+ const base = {
965
+ ...result,
966
+ key,
967
+ original,
968
+ draft: original,
969
+ scope: initialScope,
970
+ preferences: resolved,
971
+ ...result.collectionError ? { error: result.collectionError } : {}
972
+ };
973
+ setWorkspaces((current) => ({
974
+ ...current,
975
+ [key]: { ...base, draft: rebuild(base, initialControls(base)) }
976
+ }));
977
+ setWorkspaceKey(key);
978
+ setWorkspaceOpen(true);
979
+ } catch (error) {
980
+ if (fence.valid(id, controller))
981
+ setNotice(errorMessage(error, "Could not capture content"));
982
+ } finally {
983
+ fence.finish(controller);
984
+ }
985
+ },
986
+ [
987
+ captureOne,
988
+ capturePrimary,
989
+ fence,
990
+ initialScope,
991
+ preferences,
992
+ primaryWorkspaceKey,
993
+ primary,
994
+ providerPreferences,
995
+ workspaces
996
+ ]
997
+ );
903
998
  React7.useImperativeHandle(controllerRef, () => ({
904
999
  focusTrigger: () => triggerRef.current?.focus(),
905
1000
  prepare: async (requestedSource, options) => {
@@ -1042,6 +1137,9 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1042
1137
  const [instruction, setInstruction] = React7.useState("");
1043
1138
  const [busy, setBusy] = React7.useState(null);
1044
1139
  const [aiFailed, setAiFailed] = React7.useState(false);
1140
+ React7.useEffect(() => {
1141
+ setAiFailed(false);
1142
+ }, [state.snapshot.id, state.draft.revision]);
1045
1143
  const activeAi = React7.useRef(null);
1046
1144
  const activeCapture = React7.useRef(null);
1047
1145
  React7.useEffect(() => {
@@ -1119,7 +1217,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1119
1217
  setBusy("capture");
1120
1218
  try {
1121
1219
  const result = await captureScope(scope, controller.signal);
1122
- if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, error: result.collectionError });
1220
+ if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, sourceStale: false, error: result.collectionError });
1123
1221
  } catch (error) {
1124
1222
  if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "Refresh failed") });
1125
1223
  } finally {
@@ -1352,7 +1450,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1352
1450
  activeCapture.current = null;
1353
1451
  setBusy(null);
1354
1452
  }, children: "Stop loading rows" }) : null,
1355
- ai && aiSession.current?.recover && busy !== "ai" ? /* @__PURE__ */ jsx10(Button, { type: "button", variant: "outline", onClick: () => void runAi(true), children: aiFailed ? "Retry or rejoin AI run" : "Rejoin AI run" }) : null,
1453
+ ai && aiFailed && aiSession.current?.recover && busy !== "ai" ? /* @__PURE__ */ jsx10(Button, { type: "button", variant: "outline", onClick: () => void runAi(true), children: aiFailed ? "Retry or rejoin AI run" : "Rejoin AI run" }) : null,
1356
1454
  ai ? /* @__PURE__ */ jsx10("p", { className: "matrx-alchemy-meta", children: "AI creates a preview. It never copies automatically. Closing detaches this view without cancelling server work." }) : null,
1357
1455
  state.error ? /* @__PURE__ */ jsx10("p", { role: "alert", className: "matrx-alchemy-error", children: state.error }) : null
1358
1456
  ] }) : null
@@ -1362,6 +1460,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1362
1460
  /* @__PURE__ */ jsx10(Button, { type: "button", variant: "outline", onClick: () => setPendingControls(null), children: "Keep edited draft" }),
1363
1461
  /* @__PURE__ */ jsx10(Button, { type: "button", onClick: applyPendingControls, children: "Rebuild from source" })
1364
1462
  ] }) : null,
1463
+ state.sourceStale ? /* @__PURE__ */ jsx10("p", { role: "alert", className: "matrx-alchemy-decision", children: "The source changed since this draft. Use Refresh source to review the latest data." }) : null,
1365
1464
  state.pending ? /* @__PURE__ */ jsxs6("div", { className: "matrx-alchemy-decision", role: "alert", children: [
1366
1465
  /* @__PURE__ */ jsx10("p", { children: "A fresh source snapshot is ready." }),
1367
1466
  /* @__PURE__ */ jsx10(Button, { type: "button", variant: "outline", onClick: () => resolveCapture("keep"), children: "Keep current snapshot and draft" }),
@@ -1479,7 +1578,17 @@ var sourceFor = (id, label, build) => ({
1479
1578
  }
1480
1579
  });
1481
1580
  function MatrxCopyMenu(props) {
1482
- return /* @__PURE__ */ jsx11(MatrxCopyMenuSession, { ...props }, props.sourceId ?? props.label);
1581
+ const inherited = useContentTransferCapabilities();
1582
+ return /* @__PURE__ */ jsx11(
1583
+ MatrxCopyMenuSession,
1584
+ {
1585
+ ...props
1586
+ },
1587
+ JSON.stringify([
1588
+ inherited.identityKey ?? null,
1589
+ props.sourceId ?? props.label
1590
+ ])
1591
+ );
1483
1592
  }
1484
1593
  function MatrxCopyMenuSession(props) {
1485
1594
  const instance = React8.useId();
@@ -1507,7 +1616,7 @@ function MatrxCopyMenuSession(props) {
1507
1616
  })) : void 0;
1508
1617
  const agent = aiVisible && props.agent !== void 0 ? make("agent", () => asPayload(resolve(props.agent))) : void 0;
1509
1618
  const structuredAgent = aiVisible && props.agent !== void 0 ? make("structured-agent", () => structuredPayload(resolve(props.agent))) : void 0;
1510
- const table = exportVisible && props.export?.sheetRows ? make("rows", () => {
1619
+ const table = (exportVisible || props.primarySource === "table") && props.export?.sheetRows ? make("rows", () => {
1511
1620
  const value = normalizeTransferJson(props.export.sheetRows());
1512
1621
  if (!Array.isArray(value) || value.some(
1513
1622
  (row) => row === null || typeof row !== "object" || Array.isArray(row)
@@ -1654,7 +1763,7 @@ function MatrxCopyMenuSession(props) {
1654
1763
  return props.sendToSheet(draft.payload.rows, props.label);
1655
1764
  }
1656
1765
  });
1657
- const source = json ?? table ?? structuredAgent ?? human ?? builderVariants[0]?.source;
1766
+ const source = (props.primarySource === "table" ? table : void 0) ?? json ?? table ?? structuredAgent ?? human ?? builderVariants[0]?.source;
1658
1767
  if (!source && !actions.length) return null;
1659
1768
  const target = source ?? make("actions", () => ({ kind: "text", text: props.label }));
1660
1769
  return /* @__PURE__ */ jsxs7(Fragment2, { children: [
@@ -1674,7 +1783,7 @@ function MatrxCopyMenuSession(props) {
1674
1783
  formatSources: {
1675
1784
  ...human ? { plain: human, markdown: humanMarkdown, html: humanMarkdown } : {},
1676
1785
  ...json ? { json, "compact-json": json } : {},
1677
- ...table ? { csv: table, tsv: table } : {}
1786
+ ...table && exportVisible ? { csv: table, tsv: table } : {}
1678
1787
  },
1679
1788
  ...props.disabled !== void 0 ? { disabled: props.disabled } : {},
1680
1789
  className: cn(
@@ -1947,8 +2056,9 @@ function SourceConfiguration({
1947
2056
  import { jsx as jsx12 } from "react/jsx-runtime";
1948
2057
  function CopyButtons(props) {
1949
2058
  const { CopyControls } = useMatrxDataTableHost();
1950
- if (CopyControls) return /* @__PURE__ */ jsx12(CopyControls, { ...props });
1951
- return /* @__PURE__ */ jsx12(MatrxCopyMenu, { ...props });
2059
+ const copyProps = props.export?.sheetRows ? { ...props, primarySource: "table" } : props;
2060
+ if (CopyControls) return /* @__PURE__ */ jsx12(CopyControls, { ...copyProps });
2061
+ return /* @__PURE__ */ jsx12(MatrxCopyMenu, { ...copyProps });
1952
2062
  }
1953
2063
 
1954
2064
  // src/data-table/MatrxUuidCell.tsx