@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.
@@ -2378,6 +2378,10 @@ function materialIdentity(material) {
2378
2378
  }
2379
2379
  return `payload:${id}`;
2380
2380
  }
2381
+ function snapshotContent(snapshot) {
2382
+ const { sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits } = snapshot;
2383
+ return JSON.stringify({ sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits });
2384
+ }
2381
2385
  function useFence() {
2382
2386
  const mounted = React12.useRef(true);
2383
2387
  const nextId = React12.useRef(0);
@@ -2426,7 +2430,12 @@ async function buildArtifact(draft, format, label) {
2426
2430
  const extension = format === "plain" ? "txt" : format === "compact-json" ? "json" : format;
2427
2431
  return (0, import_content_transfer.serialize)(draft, format, { spreadsheetSafe: format === "csv" || format === "tsv", filename: `${filenameFor(label)}.${extension}` });
2428
2432
  }
2429
- function ContentTransferMenu({ source, surface, label, projection, sections, preferences, table, formatSources, variants = EMPTY_VARIANTS, actions = [], capabilities: local, disabled, enabled = true, className, stopPropagation = true, hide = [], controllerRef }) {
2433
+ function ContentTransferMenu(props) {
2434
+ const inherited = React12.useContext(CapabilityContext);
2435
+ const identityKey = props.capabilities?.identityKey ?? inherited.identityKey;
2436
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ContentTransferMenuSession, { ...props }, identityKey ?? "anonymous");
2437
+ }
2438
+ function ContentTransferMenuSession({ source, surface, label, projection, sections, preferences, table, formatSources, variants = EMPTY_VARIANTS, actions = [], capabilities: local, disabled, enabled = true, className, stopPropagation = true, hide = [], controllerRef }) {
2430
2439
  const triggerRef = React12.useRef(null);
2431
2440
  const inherited = React12.useContext(CapabilityContext);
2432
2441
  const nearestSurface = React12.useContext(SurfaceContext);
@@ -2475,7 +2484,7 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
2475
2484
  const captured = await material.capture(signal);
2476
2485
  sourceMaterial = { id: captured.snapshot.sourceId, label: captured.snapshot.label, capture: async () => captured };
2477
2486
  } else if ("kind" in material) {
2478
- sourceMaterial = applyMenuProjection ? { id: "direct", label: sourceLabel, capture: async () => (0, import_content_transfer.directSource)(material, { label: sourceLabel, ...projection ? { projection } : {}, ...sections ? { sections } : {} }) } : material;
2487
+ 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 } : {} }) };
2479
2488
  } else if (applyMenuProjection && (projection || sections)) {
2480
2489
  sourceMaterial = {
2481
2490
  id: material.id,
@@ -2492,7 +2501,7 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
2492
2501
  } else {
2493
2502
  sourceMaterial = material;
2494
2503
  }
2495
- const raw = "kind" in sourceMaterial ? (0, import_content_transfer.directSource)(sourceMaterial) : await sourceMaterial.capture({ scope, signal });
2504
+ const raw = await sourceMaterial.capture({ scope, signal });
2496
2505
  const constrained = (0, import_content_transfer.resolveTransferPreferences)(providerPreferences, preferences, raw.snapshot.limits);
2497
2506
  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);
2498
2507
  if (scope !== "all-matching" || !collectPages) return result;
@@ -2552,31 +2561,117 @@ function ContentTransferMenu({ source, surface, label, projection, sections, pre
2552
2561
  fence.finish(controller);
2553
2562
  }
2554
2563
  }, [captureOne, capturePrimary, fence, formatSources, initialScope, preferences, providerPreferences, report, transport]);
2555
- const openWorkspace = React12.useCallback(async (key = primaryWorkspaceKey, variant) => {
2556
- 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;
2557
- const captureWorkspace = variantMaterial ? (scope, signal) => captureOne(variantMaterial, scope, signal, true) : capturePrimary;
2558
- workspaceCaptures.current.set(key, captureWorkspace);
2559
- if (workspaces[key]) {
2560
- setWorkspaceKey(key);
2561
- setWorkspaceOpen(true);
2562
- return;
2563
- }
2564
- const { controller, id } = fence.begin();
2565
- try {
2566
- const result = await captureWorkspace(initialScope, controller.signal);
2567
- if (!fence.valid(id, controller)) return;
2568
- const resolved = (0, import_content_transfer.resolveTransferPreferences)(providerPreferences, preferences, result.snapshot.limits);
2569
- const original = (0, import_content_transfer.createDraft)(result.snapshot);
2570
- const base = { ...result, key, original, draft: original, scope: initialScope, preferences: resolved, ...result.collectionError ? { error: result.collectionError } : {} };
2571
- setWorkspaces((current) => ({ ...current, [key]: { ...base, draft: rebuild(base, initialControls(base)) } }));
2572
- setWorkspaceKey(key);
2573
- setWorkspaceOpen(true);
2574
- } catch (error) {
2575
- if (fence.valid(id, controller)) setNotice(errorMessage(error, "Could not capture content"));
2576
- } finally {
2577
- fence.finish(controller);
2578
- }
2579
- }, [captureOne, capturePrimary, fence, initialScope, preferences, primaryWorkspaceKey, providerPreferences, workspaces]);
2564
+ const openWorkspace = React12.useCallback(
2565
+ async (key = primaryWorkspaceKey, variant) => {
2566
+ const variantMaterial = variant && "kind" in variant.source ? {
2567
+ id: `variant:${key}`,
2568
+ label: variant.label,
2569
+ capture: async () => (0, import_content_transfer.directSource)(variant.source, {
2570
+ sourceId: `variant:${key}`,
2571
+ label: variant.label
2572
+ })
2573
+ } : variant?.source;
2574
+ const captureWorkspace = (scope, signal, collectPages = true) => captureOne(variantMaterial ?? primary, scope, signal, true, collectPages);
2575
+ workspaceCaptures.current.set(key, captureWorkspace);
2576
+ const existing = workspaces[key];
2577
+ const { controller, id } = fence.begin();
2578
+ if (existing) {
2579
+ try {
2580
+ const allMatching = existing.scope === "all-matching";
2581
+ const fresh = await captureWorkspace(
2582
+ existing.scope,
2583
+ controller.signal,
2584
+ !allMatching
2585
+ );
2586
+ if (!fence.valid(id, controller) || !workspaceCaptures.current.has(key))
2587
+ return;
2588
+ if (allMatching && (!existing.allMatching || !fresh.allMatching))
2589
+ throw new Error(
2590
+ "This source can no longer provide an all-matching snapshot."
2591
+ );
2592
+ const collectionIdentity = (value) => value && {
2593
+ query: value.source.query,
2594
+ projection: value.projection,
2595
+ columns: value.columns,
2596
+ limits: value.limits
2597
+ };
2598
+ const changed = allMatching ? JSON.stringify(collectionIdentity(existing.allMatching)) !== JSON.stringify(collectionIdentity(fresh.allMatching)) : snapshotContent(existing.snapshot) !== snapshotContent(fresh.snapshot);
2599
+ setWorkspaces((current) => {
2600
+ const retained = current[key];
2601
+ if (!retained) return current;
2602
+ return {
2603
+ ...current,
2604
+ [key]: {
2605
+ ...retained,
2606
+ error: fresh.collectionError,
2607
+ ...allMatching ? { sourceStale: changed } : changed ? { pending: { ...fresh, scope: retained.scope } } : { pending: void 0 }
2608
+ }
2609
+ };
2610
+ });
2611
+ setWorkspaceKey(key);
2612
+ setWorkspaceOpen(true);
2613
+ } catch (error) {
2614
+ if (fence.valid(id, controller) && workspaceCaptures.current.has(key)) {
2615
+ setWorkspaces(
2616
+ (current) => current[key] ? {
2617
+ ...current,
2618
+ [key]: {
2619
+ ...current[key],
2620
+ error: errorMessage(error, "Could not refresh source")
2621
+ }
2622
+ } : current
2623
+ );
2624
+ setWorkspaceKey(key);
2625
+ setWorkspaceOpen(true);
2626
+ }
2627
+ } finally {
2628
+ fence.finish(controller);
2629
+ }
2630
+ return;
2631
+ }
2632
+ try {
2633
+ const result = await captureWorkspace(initialScope, controller.signal);
2634
+ if (!fence.valid(id, controller)) return;
2635
+ const resolved = (0, import_content_transfer.resolveTransferPreferences)(
2636
+ providerPreferences,
2637
+ preferences,
2638
+ result.snapshot.limits
2639
+ );
2640
+ const original = (0, import_content_transfer.createDraft)(result.snapshot);
2641
+ const base = {
2642
+ ...result,
2643
+ key,
2644
+ original,
2645
+ draft: original,
2646
+ scope: initialScope,
2647
+ preferences: resolved,
2648
+ ...result.collectionError ? { error: result.collectionError } : {}
2649
+ };
2650
+ setWorkspaces((current) => ({
2651
+ ...current,
2652
+ [key]: { ...base, draft: rebuild(base, initialControls(base)) }
2653
+ }));
2654
+ setWorkspaceKey(key);
2655
+ setWorkspaceOpen(true);
2656
+ } catch (error) {
2657
+ if (fence.valid(id, controller))
2658
+ setNotice(errorMessage(error, "Could not capture content"));
2659
+ } finally {
2660
+ fence.finish(controller);
2661
+ }
2662
+ },
2663
+ [
2664
+ captureOne,
2665
+ capturePrimary,
2666
+ fence,
2667
+ initialScope,
2668
+ preferences,
2669
+ primaryWorkspaceKey,
2670
+ primary,
2671
+ providerPreferences,
2672
+ workspaces
2673
+ ]
2674
+ );
2580
2675
  React12.useImperativeHandle(controllerRef, () => ({
2581
2676
  focusTrigger: () => triggerRef.current?.focus(),
2582
2677
  prepare: async (requestedSource, options) => {
@@ -2719,6 +2814,9 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
2719
2814
  const [instruction, setInstruction] = React12.useState("");
2720
2815
  const [busy, setBusy] = React12.useState(null);
2721
2816
  const [aiFailed, setAiFailed] = React12.useState(false);
2817
+ React12.useEffect(() => {
2818
+ setAiFailed(false);
2819
+ }, [state.snapshot.id, state.draft.revision]);
2722
2820
  const activeAi = React12.useRef(null);
2723
2821
  const activeCapture = React12.useRef(null);
2724
2822
  React12.useEffect(() => {
@@ -2796,7 +2894,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
2796
2894
  setBusy("capture");
2797
2895
  try {
2798
2896
  const result = await captureScope(scope, controller.signal);
2799
- if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, error: result.collectionError });
2897
+ if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, sourceStale: false, error: result.collectionError });
2800
2898
  } catch (error) {
2801
2899
  if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "Refresh failed") });
2802
2900
  } finally {
@@ -3029,7 +3127,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
3029
3127
  activeCapture.current = null;
3030
3128
  setBusy(null);
3031
3129
  }, children: "Stop loading rows" }) : null,
3032
- ai && aiSession.current?.recover && busy !== "ai" ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Button, { type: "button", variant: "outline", onClick: () => void runAi(true), children: aiFailed ? "Retry or rejoin AI run" : "Rejoin AI run" }) : null,
3130
+ ai && aiFailed && aiSession.current?.recover && busy !== "ai" ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Button, { type: "button", variant: "outline", onClick: () => void runAi(true), children: aiFailed ? "Retry or rejoin AI run" : "Rejoin AI run" }) : null,
3033
3131
  ai ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "matrx-alchemy-meta", children: "AI creates a preview. It never copies automatically. Closing detaches this view without cancelling server work." }) : null,
3034
3132
  state.error ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { role: "alert", className: "matrx-alchemy-error", children: state.error }) : null
3035
3133
  ] }) : null
@@ -3039,6 +3137,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
3039
3137
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Button, { type: "button", variant: "outline", onClick: () => setPendingControls(null), children: "Keep edited draft" }),
3040
3138
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Button, { type: "button", onClick: applyPendingControls, children: "Rebuild from source" })
3041
3139
  ] }) : null,
3140
+ state.sourceStale ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { role: "alert", className: "matrx-alchemy-decision", children: "The source changed since this draft. Use Refresh source to review the latest data." }) : null,
3042
3141
  state.pending ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "matrx-alchemy-decision", role: "alert", children: [
3043
3142
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { children: "A fresh source snapshot is ready." }),
3044
3143
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Button, { type: "button", variant: "outline", onClick: () => resolveCapture("keep"), children: "Keep current snapshot and draft" }),
@@ -3156,7 +3255,17 @@ var sourceFor = (id, label, build) => ({
3156
3255
  }
3157
3256
  });
3158
3257
  function MatrxCopyMenu(props) {
3159
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(MatrxCopyMenuSession, { ...props }, props.sourceId ?? props.label);
3258
+ const inherited = useContentTransferCapabilities();
3259
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3260
+ MatrxCopyMenuSession,
3261
+ {
3262
+ ...props
3263
+ },
3264
+ JSON.stringify([
3265
+ inherited.identityKey ?? null,
3266
+ props.sourceId ?? props.label
3267
+ ])
3268
+ );
3160
3269
  }
3161
3270
  function MatrxCopyMenuSession(props) {
3162
3271
  const instance = React13.useId();
@@ -3184,7 +3293,7 @@ function MatrxCopyMenuSession(props) {
3184
3293
  })) : void 0;
3185
3294
  const agent = aiVisible && props.agent !== void 0 ? make("agent", () => asPayload(resolve(props.agent))) : void 0;
3186
3295
  const structuredAgent = aiVisible && props.agent !== void 0 ? make("structured-agent", () => structuredPayload(resolve(props.agent))) : void 0;
3187
- const table = exportVisible && props.export?.sheetRows ? make("rows", () => {
3296
+ const table = (exportVisible || props.primarySource === "table") && props.export?.sheetRows ? make("rows", () => {
3188
3297
  const value = (0, import_content_transfer2.normalizeTransferJson)(props.export.sheetRows());
3189
3298
  if (!Array.isArray(value) || value.some(
3190
3299
  (row) => row === null || typeof row !== "object" || Array.isArray(row)
@@ -3331,7 +3440,7 @@ function MatrxCopyMenuSession(props) {
3331
3440
  return props.sendToSheet(draft.payload.rows, props.label);
3332
3441
  }
3333
3442
  });
3334
- const source = json ?? table ?? structuredAgent ?? human ?? builderVariants[0]?.source;
3443
+ const source = (props.primarySource === "table" ? table : void 0) ?? json ?? table ?? structuredAgent ?? human ?? builderVariants[0]?.source;
3335
3444
  if (!source && !actions.length) return null;
3336
3445
  const target = source ?? make("actions", () => ({ kind: "text", text: props.label }));
3337
3446
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
@@ -3351,7 +3460,7 @@ function MatrxCopyMenuSession(props) {
3351
3460
  formatSources: {
3352
3461
  ...human ? { plain: human, markdown: humanMarkdown, html: humanMarkdown } : {},
3353
3462
  ...json ? { json, "compact-json": json } : {},
3354
- ...table ? { csv: table, tsv: table } : {}
3463
+ ...table && exportVisible ? { csv: table, tsv: table } : {}
3355
3464
  },
3356
3465
  ...props.disabled !== void 0 ? { disabled: props.disabled } : {},
3357
3466
  className: cn(
@@ -3651,8 +3760,9 @@ ${JSON.stringify(value.data, null, 2)}
3651
3760
  var import_jsx_runtime21 = require("react/jsx-runtime");
3652
3761
  function CopyButtons(props) {
3653
3762
  const { CopyControls } = useMatrxDataTableHost();
3654
- if (CopyControls) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CopyControls, { ...props });
3655
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(MatrxCopyMenu, { ...props });
3763
+ const copyProps = props.export?.sheetRows ? { ...props, primarySource: "table" } : props;
3764
+ if (CopyControls) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CopyControls, { ...copyProps });
3765
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(MatrxCopyMenu, { ...copyProps });
3656
3766
  }
3657
3767
  function jsonExportItem(build, label) {
3658
3768
  return { id: "json", label, build: () => ({ content: JSON.stringify(build(), null, 2), extension: "json", mime: "application/json" }) };