@ai-matrx/design-system 0.18.2 → 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.
@@ -648,6 +648,10 @@ function materialIdentity(material) {
648
648
  }
649
649
  return `payload:${id}`;
650
650
  }
651
+ function snapshotContent(snapshot) {
652
+ const { sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits } = snapshot;
653
+ return JSON.stringify({ sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits });
654
+ }
651
655
  function useFence() {
652
656
  const mounted = React7.useRef(true);
653
657
  const nextId = React7.useRef(0);
@@ -750,7 +754,7 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
750
754
  const captured = await material.capture(signal);
751
755
  sourceMaterial = { id: captured.snapshot.sourceId, label: captured.snapshot.label, capture: async () => captured };
752
756
  } else if ("kind" in material) {
753
- sourceMaterial = applyMenuProjection ? { id: "direct", label: sourceLabel, capture: async () => directSource(material, { label: sourceLabel, ...projection ? { projection } : {}, ...sections ? { sections } : {} }) } : material;
757
+ sourceMaterial = { id: materialIdentity(material), label: sourceLabel, capture: async () => directSource(material, { sourceId: materialIdentity(material), label: sourceLabel, ...applyMenuProjection && projection ? { projection } : {}, ...applyMenuProjection && sections ? { sections } : {} }) };
754
758
  } else if (applyMenuProjection && (projection || sections)) {
755
759
  sourceMaterial = {
756
760
  id: material.id,
@@ -767,7 +771,7 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
767
771
  } else {
768
772
  sourceMaterial = material;
769
773
  }
770
- const raw = "kind" in sourceMaterial ? directSource(sourceMaterial) : await sourceMaterial.capture({ scope, signal });
774
+ const raw = await sourceMaterial.capture({ scope, signal });
771
775
  const constrained = resolveTransferPreferences(providerPreferences, preferences, raw.snapshot.limits);
772
776
  const result = await capture({ id: raw.snapshot.sourceId, label: raw.snapshot.label, capture: async () => ({ ...raw, snapshot: { ...raw.snapshot, limits: constrained.limits } }) }, signal, scope);
773
777
  if (scope !== "all-matching" || !collectPages) return result;
@@ -827,31 +831,117 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
827
831
  fence.finish(controller);
828
832
  }
829
833
  }, [captureOne, capturePrimary, fence, formatSources, initialScope, preferences, providerPreferences, report, transport]);
830
- const openWorkspace = React7.useCallback(async (key = primaryWorkspaceKey, variant) => {
831
- const variantMaterial = variant && "kind" in variant.source ? { id: `variant:${key}`, label: variant.label, capture: async () => directSource(variant.source, { label: variant.label }) } : variant?.source;
832
- const captureWorkspace = variantMaterial ? (scope, signal) => captureOne(variantMaterial, scope, signal, true) : capturePrimary;
833
- workspaceCaptures.current.set(key, captureWorkspace);
834
- if (workspaces[key]) {
835
- setWorkspaceKey(key);
836
- setWorkspaceOpen(true);
837
- return;
838
- }
839
- const { controller, id } = fence.begin();
840
- try {
841
- const result = await captureWorkspace(initialScope, controller.signal);
842
- if (!fence.valid(id, controller)) return;
843
- const resolved = resolveTransferPreferences(providerPreferences, preferences, result.snapshot.limits);
844
- const original = createDraft(result.snapshot);
845
- const base = { ...result, key, original, draft: original, scope: initialScope, preferences: resolved, ...result.collectionError ? { error: result.collectionError } : {} };
846
- setWorkspaces((current) => ({ ...current, [key]: { ...base, draft: rebuild(base, initialControls(base)) } }));
847
- setWorkspaceKey(key);
848
- setWorkspaceOpen(true);
849
- } catch (error) {
850
- if (fence.valid(id, controller)) setNotice(errorMessage(error, "Could not capture content"));
851
- } finally {
852
- fence.finish(controller);
853
- }
854
- }, [captureOne, capturePrimary, fence, initialScope, preferences, primaryWorkspaceKey, providerPreferences, workspaces]);
834
+ const openWorkspace = React7.useCallback(
835
+ async (key = primaryWorkspaceKey, variant) => {
836
+ const variantMaterial = variant && "kind" in variant.source ? {
837
+ id: `variant:${key}`,
838
+ label: variant.label,
839
+ capture: async () => directSource(variant.source, {
840
+ sourceId: `variant:${key}`,
841
+ label: variant.label
842
+ })
843
+ } : variant?.source;
844
+ const captureWorkspace = (scope, signal, collectPages = true) => captureOne(variantMaterial ?? primary, scope, signal, true, collectPages);
845
+ workspaceCaptures.current.set(key, captureWorkspace);
846
+ const existing = workspaces[key];
847
+ const { controller, id } = fence.begin();
848
+ if (existing) {
849
+ try {
850
+ const allMatching = existing.scope === "all-matching";
851
+ const fresh = await captureWorkspace(
852
+ existing.scope,
853
+ controller.signal,
854
+ !allMatching
855
+ );
856
+ if (!fence.valid(id, controller) || !workspaceCaptures.current.has(key))
857
+ return;
858
+ if (allMatching && (!existing.allMatching || !fresh.allMatching))
859
+ throw new Error(
860
+ "This source can no longer provide an all-matching snapshot."
861
+ );
862
+ const collectionIdentity = (value) => value && {
863
+ query: value.source.query,
864
+ projection: value.projection,
865
+ columns: value.columns,
866
+ limits: value.limits
867
+ };
868
+ const changed = allMatching ? JSON.stringify(collectionIdentity(existing.allMatching)) !== JSON.stringify(collectionIdentity(fresh.allMatching)) : snapshotContent(existing.snapshot) !== snapshotContent(fresh.snapshot);
869
+ setWorkspaces((current) => {
870
+ const retained = current[key];
871
+ if (!retained) return current;
872
+ return {
873
+ ...current,
874
+ [key]: {
875
+ ...retained,
876
+ error: fresh.collectionError,
877
+ ...allMatching ? { sourceStale: changed } : changed ? { pending: { ...fresh, scope: retained.scope } } : { pending: void 0 }
878
+ }
879
+ };
880
+ });
881
+ setWorkspaceKey(key);
882
+ setWorkspaceOpen(true);
883
+ } catch (error) {
884
+ if (fence.valid(id, controller) && workspaceCaptures.current.has(key)) {
885
+ setWorkspaces(
886
+ (current) => current[key] ? {
887
+ ...current,
888
+ [key]: {
889
+ ...current[key],
890
+ error: errorMessage(error, "Could not refresh source")
891
+ }
892
+ } : current
893
+ );
894
+ setWorkspaceKey(key);
895
+ setWorkspaceOpen(true);
896
+ }
897
+ } finally {
898
+ fence.finish(controller);
899
+ }
900
+ return;
901
+ }
902
+ try {
903
+ const result = await captureWorkspace(initialScope, controller.signal);
904
+ if (!fence.valid(id, controller)) return;
905
+ const resolved = resolveTransferPreferences(
906
+ providerPreferences,
907
+ preferences,
908
+ result.snapshot.limits
909
+ );
910
+ const original = createDraft(result.snapshot);
911
+ const base = {
912
+ ...result,
913
+ key,
914
+ original,
915
+ draft: original,
916
+ scope: initialScope,
917
+ preferences: resolved,
918
+ ...result.collectionError ? { error: result.collectionError } : {}
919
+ };
920
+ setWorkspaces((current) => ({
921
+ ...current,
922
+ [key]: { ...base, draft: rebuild(base, initialControls(base)) }
923
+ }));
924
+ setWorkspaceKey(key);
925
+ setWorkspaceOpen(true);
926
+ } catch (error) {
927
+ if (fence.valid(id, controller))
928
+ setNotice(errorMessage(error, "Could not capture content"));
929
+ } finally {
930
+ fence.finish(controller);
931
+ }
932
+ },
933
+ [
934
+ captureOne,
935
+ capturePrimary,
936
+ fence,
937
+ initialScope,
938
+ preferences,
939
+ primaryWorkspaceKey,
940
+ primary,
941
+ providerPreferences,
942
+ workspaces
943
+ ]
944
+ );
855
945
  React7.useImperativeHandle(controllerRef, () => ({
856
946
  focusTrigger: () => triggerRef.current?.focus(),
857
947
  prepare: async (requestedSource, options) => {
@@ -1074,7 +1164,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1074
1164
  setBusy("capture");
1075
1165
  try {
1076
1166
  const result = await captureScope(scope, controller.signal);
1077
- if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, error: result.collectionError });
1167
+ if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, sourceStale: false, error: result.collectionError });
1078
1168
  } catch (error) {
1079
1169
  if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "Refresh failed") });
1080
1170
  } finally {
@@ -1317,6 +1407,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1317
1407
  /* @__PURE__ */ jsx7(Button, { type: "button", variant: "outline", onClick: () => setPendingControls(null), children: "Keep edited draft" }),
1318
1408
  /* @__PURE__ */ jsx7(Button, { type: "button", onClick: applyPendingControls, children: "Rebuild from source" })
1319
1409
  ] }) : null,
1410
+ state.sourceStale ? /* @__PURE__ */ jsx7("p", { role: "alert", className: "matrx-alchemy-decision", children: "The source changed since this draft. Use Refresh source to review the latest data." }) : null,
1320
1411
  state.pending ? /* @__PURE__ */ jsxs4("div", { className: "matrx-alchemy-decision", role: "alert", children: [
1321
1412
  /* @__PURE__ */ jsx7("p", { children: "A fresh source snapshot is ready." }),
1322
1413
  /* @__PURE__ */ jsx7(Button, { type: "button", variant: "outline", onClick: () => resolveCapture("keep"), children: "Keep current snapshot and draft" }),