@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.
@@ -1231,6 +1231,10 @@ function materialIdentity(material) {
1231
1231
  }
1232
1232
  return `payload:${id}`;
1233
1233
  }
1234
+ function snapshotContent(snapshot) {
1235
+ const { sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits } = snapshot;
1236
+ return JSON.stringify({ sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits });
1237
+ }
1234
1238
  function useFence() {
1235
1239
  const mounted = React8.useRef(true);
1236
1240
  const nextId = React8.useRef(0);
@@ -1333,7 +1337,7 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
1333
1337
  const captured = await material.capture(signal);
1334
1338
  sourceMaterial = { id: captured.snapshot.sourceId, label: captured.snapshot.label, capture: async () => captured };
1335
1339
  } else if ("kind" in material) {
1336
- sourceMaterial = applyMenuProjection ? { id: "direct", label: sourceLabel, capture: async () => directSource2(material, { label: sourceLabel, ...projection ? { projection } : {}, ...sections ? { sections } : {} }) } : material;
1340
+ sourceMaterial = { id: materialIdentity(material), label: sourceLabel, capture: async () => directSource2(material, { sourceId: materialIdentity(material), label: sourceLabel, ...applyMenuProjection && projection ? { projection } : {}, ...applyMenuProjection && sections ? { sections } : {} }) };
1337
1341
  } else if (applyMenuProjection && (projection || sections)) {
1338
1342
  sourceMaterial = {
1339
1343
  id: material.id,
@@ -1350,7 +1354,7 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
1350
1354
  } else {
1351
1355
  sourceMaterial = material;
1352
1356
  }
1353
- const raw = "kind" in sourceMaterial ? directSource2(sourceMaterial) : await sourceMaterial.capture({ scope, signal });
1357
+ const raw = await sourceMaterial.capture({ scope, signal });
1354
1358
  const constrained = resolveTransferPreferences(providerPreferences, preferences, raw.snapshot.limits);
1355
1359
  const result = await capture({ id: raw.snapshot.sourceId, label: raw.snapshot.label, capture: async () => ({ ...raw, snapshot: { ...raw.snapshot, limits: constrained.limits } }) }, signal, scope);
1356
1360
  if (scope !== "all-matching" || !collectPages) return result;
@@ -1410,31 +1414,117 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
1410
1414
  fence.finish(controller);
1411
1415
  }
1412
1416
  }, [captureOne, capturePrimary, fence, formatSources, initialScope, preferences, providerPreferences, report, transport]);
1413
- const openWorkspace = React8.useCallback(async (key = primaryWorkspaceKey, variant) => {
1414
- const variantMaterial = variant && "kind" in variant.source ? { id: `variant:${key}`, label: variant.label, capture: async () => directSource2(variant.source, { label: variant.label }) } : variant?.source;
1415
- const captureWorkspace = variantMaterial ? (scope, signal) => captureOne(variantMaterial, scope, signal, true) : capturePrimary;
1416
- workspaceCaptures.current.set(key, captureWorkspace);
1417
- if (workspaces[key]) {
1418
- setWorkspaceKey(key);
1419
- setWorkspaceOpen(true);
1420
- return;
1421
- }
1422
- const { controller, id } = fence.begin();
1423
- try {
1424
- const result = await captureWorkspace(initialScope, controller.signal);
1425
- if (!fence.valid(id, controller)) return;
1426
- const resolved = resolveTransferPreferences(providerPreferences, preferences, result.snapshot.limits);
1427
- const original = createDraft(result.snapshot);
1428
- const base = { ...result, key, original, draft: original, scope: initialScope, preferences: resolved, ...result.collectionError ? { error: result.collectionError } : {} };
1429
- setWorkspaces((current) => ({ ...current, [key]: { ...base, draft: rebuild(base, initialControls(base)) } }));
1430
- setWorkspaceKey(key);
1431
- setWorkspaceOpen(true);
1432
- } catch (error) {
1433
- if (fence.valid(id, controller)) setNotice(errorMessage(error, "Could not capture content"));
1434
- } finally {
1435
- fence.finish(controller);
1436
- }
1437
- }, [captureOne, capturePrimary, fence, initialScope, preferences, primaryWorkspaceKey, providerPreferences, workspaces]);
1417
+ const openWorkspace = React8.useCallback(
1418
+ async (key = primaryWorkspaceKey, variant) => {
1419
+ const variantMaterial = variant && "kind" in variant.source ? {
1420
+ id: `variant:${key}`,
1421
+ label: variant.label,
1422
+ capture: async () => directSource2(variant.source, {
1423
+ sourceId: `variant:${key}`,
1424
+ label: variant.label
1425
+ })
1426
+ } : variant?.source;
1427
+ const captureWorkspace = (scope, signal, collectPages = true) => captureOne(variantMaterial ?? primary, scope, signal, true, collectPages);
1428
+ workspaceCaptures.current.set(key, captureWorkspace);
1429
+ const existing = workspaces[key];
1430
+ const { controller, id } = fence.begin();
1431
+ if (existing) {
1432
+ try {
1433
+ const allMatching = existing.scope === "all-matching";
1434
+ const fresh = await captureWorkspace(
1435
+ existing.scope,
1436
+ controller.signal,
1437
+ !allMatching
1438
+ );
1439
+ if (!fence.valid(id, controller) || !workspaceCaptures.current.has(key))
1440
+ return;
1441
+ if (allMatching && (!existing.allMatching || !fresh.allMatching))
1442
+ throw new Error(
1443
+ "This source can no longer provide an all-matching snapshot."
1444
+ );
1445
+ const collectionIdentity = (value) => value && {
1446
+ query: value.source.query,
1447
+ projection: value.projection,
1448
+ columns: value.columns,
1449
+ limits: value.limits
1450
+ };
1451
+ const changed = allMatching ? JSON.stringify(collectionIdentity(existing.allMatching)) !== JSON.stringify(collectionIdentity(fresh.allMatching)) : snapshotContent(existing.snapshot) !== snapshotContent(fresh.snapshot);
1452
+ setWorkspaces((current) => {
1453
+ const retained = current[key];
1454
+ if (!retained) return current;
1455
+ return {
1456
+ ...current,
1457
+ [key]: {
1458
+ ...retained,
1459
+ error: fresh.collectionError,
1460
+ ...allMatching ? { sourceStale: changed } : changed ? { pending: { ...fresh, scope: retained.scope } } : { pending: void 0 }
1461
+ }
1462
+ };
1463
+ });
1464
+ setWorkspaceKey(key);
1465
+ setWorkspaceOpen(true);
1466
+ } catch (error) {
1467
+ if (fence.valid(id, controller) && workspaceCaptures.current.has(key)) {
1468
+ setWorkspaces(
1469
+ (current) => current[key] ? {
1470
+ ...current,
1471
+ [key]: {
1472
+ ...current[key],
1473
+ error: errorMessage(error, "Could not refresh source")
1474
+ }
1475
+ } : current
1476
+ );
1477
+ setWorkspaceKey(key);
1478
+ setWorkspaceOpen(true);
1479
+ }
1480
+ } finally {
1481
+ fence.finish(controller);
1482
+ }
1483
+ return;
1484
+ }
1485
+ try {
1486
+ const result = await captureWorkspace(initialScope, controller.signal);
1487
+ if (!fence.valid(id, controller)) return;
1488
+ const resolved = resolveTransferPreferences(
1489
+ providerPreferences,
1490
+ preferences,
1491
+ result.snapshot.limits
1492
+ );
1493
+ const original = createDraft(result.snapshot);
1494
+ const base = {
1495
+ ...result,
1496
+ key,
1497
+ original,
1498
+ draft: original,
1499
+ scope: initialScope,
1500
+ preferences: resolved,
1501
+ ...result.collectionError ? { error: result.collectionError } : {}
1502
+ };
1503
+ setWorkspaces((current) => ({
1504
+ ...current,
1505
+ [key]: { ...base, draft: rebuild(base, initialControls(base)) }
1506
+ }));
1507
+ setWorkspaceKey(key);
1508
+ setWorkspaceOpen(true);
1509
+ } catch (error) {
1510
+ if (fence.valid(id, controller))
1511
+ setNotice(errorMessage(error, "Could not capture content"));
1512
+ } finally {
1513
+ fence.finish(controller);
1514
+ }
1515
+ },
1516
+ [
1517
+ captureOne,
1518
+ capturePrimary,
1519
+ fence,
1520
+ initialScope,
1521
+ preferences,
1522
+ primaryWorkspaceKey,
1523
+ primary,
1524
+ providerPreferences,
1525
+ workspaces
1526
+ ]
1527
+ );
1438
1528
  React8.useImperativeHandle(controllerRef, () => ({
1439
1529
  focusTrigger: () => triggerRef.current?.focus(),
1440
1530
  prepare: async (requestedSource, options) => {
@@ -1657,7 +1747,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1657
1747
  setBusy("capture");
1658
1748
  try {
1659
1749
  const result = await captureScope(scope, controller.signal);
1660
- if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, error: result.collectionError });
1750
+ if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, sourceStale: false, error: result.collectionError });
1661
1751
  } catch (error) {
1662
1752
  if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "Refresh failed") });
1663
1753
  } finally {
@@ -1900,6 +1990,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1900
1990
  /* @__PURE__ */ jsx8(Button, { type: "button", variant: "outline", onClick: () => setPendingControls(null), children: "Keep edited draft" }),
1901
1991
  /* @__PURE__ */ jsx8(Button, { type: "button", onClick: applyPendingControls, children: "Rebuild from source" })
1902
1992
  ] }) : null,
1993
+ state.sourceStale ? /* @__PURE__ */ jsx8("p", { role: "alert", className: "matrx-alchemy-decision", children: "The source changed since this draft. Use Refresh source to review the latest data." }) : null,
1903
1994
  state.pending ? /* @__PURE__ */ jsxs5("div", { className: "matrx-alchemy-decision", role: "alert", children: [
1904
1995
  /* @__PURE__ */ jsx8("p", { children: "A fresh source snapshot is ready." }),
1905
1996
  /* @__PURE__ */ jsx8(Button, { type: "button", variant: "outline", onClick: () => resolveCapture("keep"), children: "Keep current snapshot and draft" }),