@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.
@@ -2375,6 +2375,10 @@ function materialIdentity(material) {
2375
2375
  }
2376
2376
  return `payload:${id}`;
2377
2377
  }
2378
+ function snapshotContent(snapshot) {
2379
+ const { sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits } = snapshot;
2380
+ return JSON.stringify({ sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits });
2381
+ }
2378
2382
  function useFence() {
2379
2383
  const mounted = React12.useRef(true);
2380
2384
  const nextId = React12.useRef(0);
@@ -2423,7 +2427,12 @@ async function buildArtifact(draft, format, label) {
2423
2427
  const extension = format === "plain" ? "txt" : format === "compact-json" ? "json" : format;
2424
2428
  return serialize(draft, format, { spreadsheetSafe: format === "csv" || format === "tsv", filename: `${filenameFor(label)}.${extension}` });
2425
2429
  }
2426
- function ContentTransferMenu({ source, surface, label, projection, sections, preferences, table, formatSources, variants = EMPTY_VARIANTS, actions = [], capabilities: local, disabled, enabled = true, className, stopPropagation = true, hide = [], controllerRef }) {
2430
+ function ContentTransferMenu(props) {
2431
+ const inherited = React12.useContext(CapabilityContext);
2432
+ const identityKey = props.capabilities?.identityKey ?? inherited.identityKey;
2433
+ return /* @__PURE__ */ jsx19(ContentTransferMenuSession, { ...props }, identityKey ?? "anonymous");
2434
+ }
2435
+ function ContentTransferMenuSession({ source, surface, label, projection, sections, preferences, table, formatSources, variants = EMPTY_VARIANTS, actions = [], capabilities: local, disabled, enabled = true, className, stopPropagation = true, hide = [], controllerRef }) {
2427
2436
  const triggerRef = React12.useRef(null);
2428
2437
  const inherited = React12.useContext(CapabilityContext);
2429
2438
  const nearestSurface = React12.useContext(SurfaceContext);
@@ -2472,7 +2481,7 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
2472
2481
  const captured = await material.capture(signal);
2473
2482
  sourceMaterial = { id: captured.snapshot.sourceId, label: captured.snapshot.label, capture: async () => captured };
2474
2483
  } else if ("kind" in material) {
2475
- sourceMaterial = applyMenuProjection ? { id: "direct", label: sourceLabel, capture: async () => directSource(material, { label: sourceLabel, ...projection ? { projection } : {}, ...sections ? { sections } : {} }) } : material;
2484
+ sourceMaterial = { id: materialIdentity(material), label: sourceLabel, capture: async () => directSource(material, { sourceId: materialIdentity(material), label: sourceLabel, ...applyMenuProjection && projection ? { projection } : {}, ...applyMenuProjection && sections ? { sections } : {} }) };
2476
2485
  } else if (applyMenuProjection && (projection || sections)) {
2477
2486
  sourceMaterial = {
2478
2487
  id: material.id,
@@ -2489,7 +2498,7 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
2489
2498
  } else {
2490
2499
  sourceMaterial = material;
2491
2500
  }
2492
- const raw = "kind" in sourceMaterial ? directSource(sourceMaterial) : await sourceMaterial.capture({ scope, signal });
2501
+ const raw = await sourceMaterial.capture({ scope, signal });
2493
2502
  const constrained = resolveTransferPreferences(providerPreferences, preferences, raw.snapshot.limits);
2494
2503
  const result = await capture({ id: raw.snapshot.sourceId, label: raw.snapshot.label, capture: async () => ({ ...raw, snapshot: { ...raw.snapshot, limits: constrained.limits } }) }, signal, scope);
2495
2504
  if (scope !== "all-matching" || !collectPages) return result;
@@ -2549,31 +2558,117 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
2549
2558
  fence.finish(controller);
2550
2559
  }
2551
2560
  }, [captureOne, capturePrimary, fence, formatSources, initialScope, preferences, providerPreferences, report, transport]);
2552
- const openWorkspace = React12.useCallback(async (key = primaryWorkspaceKey, variant) => {
2553
- const variantMaterial = variant && "kind" in variant.source ? { id: `variant:${key}`, label: variant.label, capture: async () => directSource(variant.source, { label: variant.label }) } : variant?.source;
2554
- const captureWorkspace = variantMaterial ? (scope, signal) => captureOne(variantMaterial, scope, signal, true) : capturePrimary;
2555
- workspaceCaptures.current.set(key, captureWorkspace);
2556
- if (workspaces[key]) {
2557
- setWorkspaceKey(key);
2558
- setWorkspaceOpen(true);
2559
- return;
2560
- }
2561
- const { controller, id } = fence.begin();
2562
- try {
2563
- const result = await captureWorkspace(initialScope, controller.signal);
2564
- if (!fence.valid(id, controller)) return;
2565
- const resolved = resolveTransferPreferences(providerPreferences, preferences, result.snapshot.limits);
2566
- const original = createDraft(result.snapshot);
2567
- const base = { ...result, key, original, draft: original, scope: initialScope, preferences: resolved, ...result.collectionError ? { error: result.collectionError } : {} };
2568
- setWorkspaces((current) => ({ ...current, [key]: { ...base, draft: rebuild(base, initialControls(base)) } }));
2569
- setWorkspaceKey(key);
2570
- setWorkspaceOpen(true);
2571
- } catch (error) {
2572
- if (fence.valid(id, controller)) setNotice(errorMessage(error, "Could not capture content"));
2573
- } finally {
2574
- fence.finish(controller);
2575
- }
2576
- }, [captureOne, capturePrimary, fence, initialScope, preferences, primaryWorkspaceKey, providerPreferences, workspaces]);
2561
+ const openWorkspace = React12.useCallback(
2562
+ async (key = primaryWorkspaceKey, variant) => {
2563
+ const variantMaterial = variant && "kind" in variant.source ? {
2564
+ id: `variant:${key}`,
2565
+ label: variant.label,
2566
+ capture: async () => directSource(variant.source, {
2567
+ sourceId: `variant:${key}`,
2568
+ label: variant.label
2569
+ })
2570
+ } : variant?.source;
2571
+ const captureWorkspace = (scope, signal, collectPages = true) => captureOne(variantMaterial ?? primary, scope, signal, true, collectPages);
2572
+ workspaceCaptures.current.set(key, captureWorkspace);
2573
+ const existing = workspaces[key];
2574
+ const { controller, id } = fence.begin();
2575
+ if (existing) {
2576
+ try {
2577
+ const allMatching = existing.scope === "all-matching";
2578
+ const fresh = await captureWorkspace(
2579
+ existing.scope,
2580
+ controller.signal,
2581
+ !allMatching
2582
+ );
2583
+ if (!fence.valid(id, controller) || !workspaceCaptures.current.has(key))
2584
+ return;
2585
+ if (allMatching && (!existing.allMatching || !fresh.allMatching))
2586
+ throw new Error(
2587
+ "This source can no longer provide an all-matching snapshot."
2588
+ );
2589
+ const collectionIdentity = (value) => value && {
2590
+ query: value.source.query,
2591
+ projection: value.projection,
2592
+ columns: value.columns,
2593
+ limits: value.limits
2594
+ };
2595
+ const changed = allMatching ? JSON.stringify(collectionIdentity(existing.allMatching)) !== JSON.stringify(collectionIdentity(fresh.allMatching)) : snapshotContent(existing.snapshot) !== snapshotContent(fresh.snapshot);
2596
+ setWorkspaces((current) => {
2597
+ const retained = current[key];
2598
+ if (!retained) return current;
2599
+ return {
2600
+ ...current,
2601
+ [key]: {
2602
+ ...retained,
2603
+ error: fresh.collectionError,
2604
+ ...allMatching ? { sourceStale: changed } : changed ? { pending: { ...fresh, scope: retained.scope } } : { pending: void 0 }
2605
+ }
2606
+ };
2607
+ });
2608
+ setWorkspaceKey(key);
2609
+ setWorkspaceOpen(true);
2610
+ } catch (error) {
2611
+ if (fence.valid(id, controller) && workspaceCaptures.current.has(key)) {
2612
+ setWorkspaces(
2613
+ (current) => current[key] ? {
2614
+ ...current,
2615
+ [key]: {
2616
+ ...current[key],
2617
+ error: errorMessage(error, "Could not refresh source")
2618
+ }
2619
+ } : current
2620
+ );
2621
+ setWorkspaceKey(key);
2622
+ setWorkspaceOpen(true);
2623
+ }
2624
+ } finally {
2625
+ fence.finish(controller);
2626
+ }
2627
+ return;
2628
+ }
2629
+ try {
2630
+ const result = await captureWorkspace(initialScope, controller.signal);
2631
+ if (!fence.valid(id, controller)) return;
2632
+ const resolved = resolveTransferPreferences(
2633
+ providerPreferences,
2634
+ preferences,
2635
+ result.snapshot.limits
2636
+ );
2637
+ const original = createDraft(result.snapshot);
2638
+ const base = {
2639
+ ...result,
2640
+ key,
2641
+ original,
2642
+ draft: original,
2643
+ scope: initialScope,
2644
+ preferences: resolved,
2645
+ ...result.collectionError ? { error: result.collectionError } : {}
2646
+ };
2647
+ setWorkspaces((current) => ({
2648
+ ...current,
2649
+ [key]: { ...base, draft: rebuild(base, initialControls(base)) }
2650
+ }));
2651
+ setWorkspaceKey(key);
2652
+ setWorkspaceOpen(true);
2653
+ } catch (error) {
2654
+ if (fence.valid(id, controller))
2655
+ setNotice(errorMessage(error, "Could not capture content"));
2656
+ } finally {
2657
+ fence.finish(controller);
2658
+ }
2659
+ },
2660
+ [
2661
+ captureOne,
2662
+ capturePrimary,
2663
+ fence,
2664
+ initialScope,
2665
+ preferences,
2666
+ primaryWorkspaceKey,
2667
+ primary,
2668
+ providerPreferences,
2669
+ workspaces
2670
+ ]
2671
+ );
2577
2672
  React12.useImperativeHandle(controllerRef, () => ({
2578
2673
  focusTrigger: () => triggerRef.current?.focus(),
2579
2674
  prepare: async (requestedSource, options) => {
@@ -2716,6 +2811,9 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
2716
2811
  const [instruction, setInstruction] = React12.useState("");
2717
2812
  const [busy, setBusy] = React12.useState(null);
2718
2813
  const [aiFailed, setAiFailed] = React12.useState(false);
2814
+ React12.useEffect(() => {
2815
+ setAiFailed(false);
2816
+ }, [state.snapshot.id, state.draft.revision]);
2719
2817
  const activeAi = React12.useRef(null);
2720
2818
  const activeCapture = React12.useRef(null);
2721
2819
  React12.useEffect(() => {
@@ -2793,7 +2891,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
2793
2891
  setBusy("capture");
2794
2892
  try {
2795
2893
  const result = await captureScope(scope, controller.signal);
2796
- if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, error: result.collectionError });
2894
+ if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, sourceStale: false, error: result.collectionError });
2797
2895
  } catch (error) {
2798
2896
  if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "Refresh failed") });
2799
2897
  } finally {
@@ -3026,7 +3124,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
3026
3124
  activeCapture.current = null;
3027
3125
  setBusy(null);
3028
3126
  }, children: "Stop loading rows" }) : null,
3029
- ai && aiSession.current?.recover && busy !== "ai" ? /* @__PURE__ */ jsx19(Button, { type: "button", variant: "outline", onClick: () => void runAi(true), children: aiFailed ? "Retry or rejoin AI run" : "Rejoin AI run" }) : null,
3127
+ ai && aiFailed && aiSession.current?.recover && busy !== "ai" ? /* @__PURE__ */ jsx19(Button, { type: "button", variant: "outline", onClick: () => void runAi(true), children: aiFailed ? "Retry or rejoin AI run" : "Rejoin AI run" }) : null,
3030
3128
  ai ? /* @__PURE__ */ jsx19("p", { className: "matrx-alchemy-meta", children: "AI creates a preview. It never copies automatically. Closing detaches this view without cancelling server work." }) : null,
3031
3129
  state.error ? /* @__PURE__ */ jsx19("p", { role: "alert", className: "matrx-alchemy-error", children: state.error }) : null
3032
3130
  ] }) : null
@@ -3036,6 +3134,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
3036
3134
  /* @__PURE__ */ jsx19(Button, { type: "button", variant: "outline", onClick: () => setPendingControls(null), children: "Keep edited draft" }),
3037
3135
  /* @__PURE__ */ jsx19(Button, { type: "button", onClick: applyPendingControls, children: "Rebuild from source" })
3038
3136
  ] }) : null,
3137
+ state.sourceStale ? /* @__PURE__ */ jsx19("p", { role: "alert", className: "matrx-alchemy-decision", children: "The source changed since this draft. Use Refresh source to review the latest data." }) : null,
3039
3138
  state.pending ? /* @__PURE__ */ jsxs13("div", { className: "matrx-alchemy-decision", role: "alert", children: [
3040
3139
  /* @__PURE__ */ jsx19("p", { children: "A fresh source snapshot is ready." }),
3041
3140
  /* @__PURE__ */ jsx19(Button, { type: "button", variant: "outline", onClick: () => resolveCapture("keep"), children: "Keep current snapshot and draft" }),
@@ -3153,7 +3252,17 @@ var sourceFor = (id, label, build) => ({
3153
3252
  }
3154
3253
  });
3155
3254
  function MatrxCopyMenu(props) {
3156
- return /* @__PURE__ */ jsx20(MatrxCopyMenuSession, { ...props }, props.sourceId ?? props.label);
3255
+ const inherited = useContentTransferCapabilities();
3256
+ return /* @__PURE__ */ jsx20(
3257
+ MatrxCopyMenuSession,
3258
+ {
3259
+ ...props
3260
+ },
3261
+ JSON.stringify([
3262
+ inherited.identityKey ?? null,
3263
+ props.sourceId ?? props.label
3264
+ ])
3265
+ );
3157
3266
  }
3158
3267
  function MatrxCopyMenuSession(props) {
3159
3268
  const instance = React13.useId();
@@ -3181,7 +3290,7 @@ function MatrxCopyMenuSession(props) {
3181
3290
  })) : void 0;
3182
3291
  const agent = aiVisible && props.agent !== void 0 ? make("agent", () => asPayload(resolve(props.agent))) : void 0;
3183
3292
  const structuredAgent = aiVisible && props.agent !== void 0 ? make("structured-agent", () => structuredPayload(resolve(props.agent))) : void 0;
3184
- const table = exportVisible && props.export?.sheetRows ? make("rows", () => {
3293
+ const table = (exportVisible || props.primarySource === "table") && props.export?.sheetRows ? make("rows", () => {
3185
3294
  const value = normalizeTransferJson(props.export.sheetRows());
3186
3295
  if (!Array.isArray(value) || value.some(
3187
3296
  (row) => row === null || typeof row !== "object" || Array.isArray(row)
@@ -3328,7 +3437,7 @@ function MatrxCopyMenuSession(props) {
3328
3437
  return props.sendToSheet(draft.payload.rows, props.label);
3329
3438
  }
3330
3439
  });
3331
- const source = json ?? table ?? structuredAgent ?? human ?? builderVariants[0]?.source;
3440
+ const source = (props.primarySource === "table" ? table : void 0) ?? json ?? table ?? structuredAgent ?? human ?? builderVariants[0]?.source;
3332
3441
  if (!source && !actions.length) return null;
3333
3442
  const target = source ?? make("actions", () => ({ kind: "text", text: props.label }));
3334
3443
  return /* @__PURE__ */ jsxs14(Fragment3, { children: [
@@ -3348,7 +3457,7 @@ function MatrxCopyMenuSession(props) {
3348
3457
  formatSources: {
3349
3458
  ...human ? { plain: human, markdown: humanMarkdown, html: humanMarkdown } : {},
3350
3459
  ...json ? { json, "compact-json": json } : {},
3351
- ...table ? { csv: table, tsv: table } : {}
3460
+ ...table && exportVisible ? { csv: table, tsv: table } : {}
3352
3461
  },
3353
3462
  ...props.disabled !== void 0 ? { disabled: props.disabled } : {},
3354
3463
  className: cn(
@@ -3648,8 +3757,9 @@ ${JSON.stringify(value.data, null, 2)}
3648
3757
  import { jsx as jsx21 } from "react/jsx-runtime";
3649
3758
  function CopyButtons(props) {
3650
3759
  const { CopyControls } = useMatrxDataTableHost();
3651
- if (CopyControls) return /* @__PURE__ */ jsx21(CopyControls, { ...props });
3652
- return /* @__PURE__ */ jsx21(MatrxCopyMenu, { ...props });
3760
+ const copyProps = props.export?.sheetRows ? { ...props, primarySource: "table" } : props;
3761
+ if (CopyControls) return /* @__PURE__ */ jsx21(CopyControls, { ...copyProps });
3762
+ return /* @__PURE__ */ jsx21(MatrxCopyMenu, { ...copyProps });
3653
3763
  }
3654
3764
  function jsonExportItem(build, label) {
3655
3765
  return { id: "json", label, build: () => ({ content: JSON.stringify(build(), null, 2), extension: "json", mime: "application/json" }) };