@proveanything/smartlinks-utils-ui 0.11.7 → 0.11.9

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.
@@ -7,7 +7,7 @@ import { cn } from '../../chunk-L7FQ52F5.js';
7
7
  import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KA4MKRHL.js';
8
8
  export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KA4MKRHL.js';
9
9
  import { createContext, useMemo, useState, useEffect, useCallback, useRef, isValidElement, useLayoutEffect, useContext, useSyncExternalStore, createElement } from 'react';
10
- import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Rows3, ChevronRight, Eraser, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, CopyPlus, AlertCircle, Undo2, Save, Loader2, ArrowRight, Globe2, Check, Settings2 } from 'lucide-react';
10
+ import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Rows3, ChevronRight, Eraser, FilePlus2, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, CopyPlus, AlertCircle, Undo2, Save, Loader2, ArrowRight, Globe2, Check, Settings2 } from 'lucide-react';
11
11
  import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
12
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
13
  import { createPortal } from 'react-dom';
@@ -240,6 +240,7 @@ var DEFAULT_I18N = {
240
240
  pasteWarnContinue: "Continue",
241
241
  duplicateAction: "Duplicate",
242
242
  duplicateToast: "Duplicated {name}. Review and Save.",
243
+ copyAndNewRuleAction: "Copy and start new rule",
243
244
  itemsAllLabel: "All items",
244
245
  subtitleEmpty: "Not set",
245
246
  subtitleConfigured: "Configured",
@@ -1527,7 +1528,9 @@ function useShellClipboard(args) {
1527
1528
  onCopyOverride,
1528
1529
  onPasteOverride,
1529
1530
  onLeftSelectRef,
1530
- onCreateItemDraftRef
1531
+ onCreateItemDraftRef,
1532
+ onCreateRuleFromClipboardRef,
1533
+ isRuleTab
1531
1534
  } = args;
1532
1535
  const clipboard = useRecordClipboard({
1533
1536
  appId,
@@ -1642,7 +1645,17 @@ function useShellClipboard(args) {
1642
1645
  canCopy: true,
1643
1646
  canPaste: !!clipboard.entry && editorPasteCompat?.status !== "denied",
1644
1647
  pasteSourceLabel: clipboard.entry?.sourceLabel,
1645
- pasteWillReplace: resolved.source === "self" && !!clipboard.entry
1648
+ pasteWillReplace: resolved.source === "self" && !!clipboard.entry,
1649
+ // Editor-pane sibling of the row menu's "Copy and start new rule".
1650
+ // Only meaningful in singleton mode on the rules tab — for the rule
1651
+ // record being edited OR the global record (so the admin can spin a
1652
+ // new rule off either in one click).
1653
+ onCopyAndNewRule: !isCollection && !!isRuleTab && !!onCreateRuleFromClipboardRef ? () => {
1654
+ copyCurrent();
1655
+ window.setTimeout(() => {
1656
+ onCreateRuleFromClipboardRef?.current?.();
1657
+ }, 0);
1658
+ } : void 0
1646
1659
  } : void 0;
1647
1660
  const [pendingPasteTarget, setPendingPasteTarget] = useState(null);
1648
1661
  useEffect(() => {
@@ -1660,6 +1673,7 @@ function useShellClipboard(args) {
1660
1673
  const summaryHasData = record.data != null;
1661
1674
  const sourceParsed = record.scope;
1662
1675
  const canDuplicate = summaryHasData && isCollection;
1676
+ const canCopyAndNewRule = summaryHasData && !isCollection && !!isRuleTab && !!onCreateRuleFromClipboardRef;
1663
1677
  return {
1664
1678
  onCopy: summaryHasData ? () => {
1665
1679
  const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
@@ -1675,6 +1689,19 @@ function useShellClipboard(args) {
1675
1689
  variant: "copy"
1676
1690
  });
1677
1691
  } : void 0,
1692
+ onCopyAndNewRule: canCopyAndNewRule ? () => {
1693
+ const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
1694
+ clipboard.set({
1695
+ value,
1696
+ sourceScope: sourceParsed,
1697
+ sourceRecordId: record.id ?? void 0,
1698
+ sourceLabel: record.label
1699
+ });
1700
+ onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: anchorKey(record.scope) });
1701
+ window.setTimeout(() => {
1702
+ onCreateRuleFromClipboardRef?.current?.();
1703
+ }, 0);
1704
+ } : void 0,
1678
1705
  onDuplicate: canDuplicate ? () => {
1679
1706
  const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
1680
1707
  clipboard.set({
@@ -2911,9 +2938,10 @@ function useEditorBridge(args) {
2911
2938
  if (isEqualSpec(target, lastTargetRef.current)) return;
2912
2939
  lastTargetRef.current = target;
2913
2940
  const isCreate = !!target.createMode;
2941
+ const prefersExplicitInitialData = !target?.createMode && !target?.recordId && target?.initialData !== void 0;
2914
2942
  const id = selection.selectTarget({
2915
2943
  spec: target,
2916
- seed: isCreate || resolved.source === "empty" ? void 0 : {
2944
+ seed: isCreate || prefersExplicitInitialData || resolved.source === "empty" ? void 0 : {
2917
2945
  value: resolved.data ?? (defaultData?.() ?? null),
2918
2946
  facetRule: resolved.facetRule ?? null,
2919
2947
  source: resolved.source,
@@ -2929,6 +2957,7 @@ function useEditorBridge(args) {
2929
2957
  if (!id) return;
2930
2958
  if (resolved.source === "empty" && !resolved.recordId) return;
2931
2959
  if (target?.createMode) return;
2960
+ if (!target?.recordId && target?.initialData !== void 0) return;
2932
2961
  selection.hydrate(id, resolved);
2933
2962
  }, [resolved.source, resolved.recordId, resolved.sourceRef, resolved.data, resolved.facetRule, target?.createMode]);
2934
2963
  const editorId = selection.currentEditorId ?? editorIdRef.current;
@@ -3009,6 +3038,7 @@ function useShellEditorTarget(args) {
3009
3038
  ruleWizardStep,
3010
3039
  ruleWizardDraftKey,
3011
3040
  ruleWizardInitialData,
3041
+ explicitSingletonInitialData,
3012
3042
  ruleWizardRule,
3013
3043
  resolved,
3014
3044
  defaultData,
@@ -3022,7 +3052,8 @@ function useShellEditorTarget(args) {
3022
3052
  const label = deriveDraftLabel ? deriveDraftLabel(resolved.data, editingTargetScope) : void 0;
3023
3053
  const rawScope = editingTargetScope.raw ?? "";
3024
3054
  const isDraftScope = rawScope.includes("__draft__") || rawScope.includes("item:draft:");
3025
- const draftKey = isDraftScope ? isCollection && selectedItemId && isDraftId2(selectedItemId) ? `item:${selectedItemId}` : ruleWizardDraftKey ?? `${activeScope}:${draftKind ?? "wizard"}` : void 0;
3055
+ const explicitSingletonDraft = !isCollection && activeScope === "product" && draftKind !== null;
3056
+ const draftKey = isDraftScope ? isCollection && selectedItemId && isDraftId2(selectedItemId) ? `item:${selectedItemId}` : ruleWizardDraftKey ?? `${activeScope}:${draftKind ?? "wizard"}` : explicitSingletonDraft ? `singleton:${editingTargetScope.raw}:${draftKind}` : void 0;
3026
3057
  const itemDraftCreate = isCollection && !!selectedItemId && isDraftId2(selectedItemId);
3027
3058
  const createMode = itemDraftCreate || isDraftScope || isCollection && !!selectedItemId && !resolved.recordId;
3028
3059
  return {
@@ -3031,7 +3062,7 @@ function useShellEditorTarget(args) {
3031
3062
  // otherwise the store's findExistingEditorIdFor would match the rule's
3032
3063
  // editor, focus it instead of minting a new draft, and the next save
3033
3064
  // would update the rule record in place.
3034
- recordId: createMode ? void 0 : resolved.recordId,
3065
+ recordId: createMode || explicitSingletonDraft ? void 0 : resolved.recordId,
3035
3066
  createMode,
3036
3067
  // We deliberately do NOT propagate `editingTargetScope.raw` (e.g.
3037
3068
  // `rule:<ulid>`) into `spec.ref`. `record.ref` is a host-owned
@@ -3045,7 +3076,7 @@ function useShellEditorTarget(args) {
3045
3076
  label,
3046
3077
  draftKey,
3047
3078
  isDraftScope,
3048
- initialData: createMode ? ruleWizardInitialData : void 0
3079
+ initialData: createMode ? ruleWizardInitialData : explicitSingletonInitialData ?? void 0
3049
3080
  };
3050
3081
  }, [
3051
3082
  editingTargetScope?.raw,
@@ -3059,6 +3090,7 @@ function useShellEditorTarget(args) {
3059
3090
  ruleWizardStep,
3060
3091
  ruleWizardDraftKey,
3061
3092
  ruleWizardInitialData,
3093
+ explicitSingletonInitialData,
3062
3094
  ruleWizardRule
3063
3095
  ]);
3064
3096
  const editorCtx = useEditorBridge({
@@ -3305,6 +3337,7 @@ var statusToneLabel = (tone) => {
3305
3337
  var RowContextMenu = ({
3306
3338
  onCopy,
3307
3339
  onDuplicate,
3340
+ onCopyAndNewRule,
3308
3341
  actions,
3309
3342
  i18n
3310
3343
  }) => {
@@ -3355,8 +3388,8 @@ var RowContextMenu = ({
3355
3388
  };
3356
3389
  }, [open]);
3357
3390
  const hasActions = (actions?.length ?? 0) > 0;
3358
- if (!onCopy && !onDuplicate && !hasActions) return null;
3359
- const showDivider = (onCopy || onDuplicate) && hasActions;
3391
+ if (!onCopy && !onDuplicate && !onCopyAndNewRule && !hasActions) return null;
3392
+ const showDivider = (onCopy || onDuplicate || onCopyAndNewRule) && hasActions;
3360
3393
  return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: "ra-row-menu-wrap relative", children: [
3361
3394
  /* @__PURE__ */ jsx(
3362
3395
  "button",
@@ -3419,6 +3452,23 @@ var RowContextMenu = ({
3419
3452
  ]
3420
3453
  }
3421
3454
  ),
3455
+ onCopyAndNewRule && /* @__PURE__ */ jsxs(
3456
+ "button",
3457
+ {
3458
+ type: "button",
3459
+ role: "menuitem",
3460
+ className: "ra-row-menu-item",
3461
+ onClick: (e) => {
3462
+ e.stopPropagation();
3463
+ setOpen(false);
3464
+ onCopyAndNewRule();
3465
+ },
3466
+ children: [
3467
+ /* @__PURE__ */ jsx(FilePlus2, { className: "w-3.5 h-3.5 opacity-70", "aria-hidden": "true" }),
3468
+ /* @__PURE__ */ jsx("span", { children: i18n.copyAndNewRuleAction })
3469
+ ]
3470
+ }
3471
+ ),
3422
3472
  showDivider && /* @__PURE__ */ jsx("div", { className: "ra-row-menu-divider", role: "separator" }),
3423
3473
  hasActions && actions.map((action) => {
3424
3474
  const Icon = action.icon;
@@ -3493,7 +3543,7 @@ var RuleLabelLookupProvider = ({
3493
3543
  return /* @__PURE__ */ jsx(RuleLabelLookupContext.Provider, { value: v, children });
3494
3544
  };
3495
3545
  var DefaultRecordRow = ({ record, ctx, compact = false }) => {
3496
- const { selected, onSelect, isDirty, hasError, onCopy, onDuplicate, actions } = ctx;
3546
+ const { selected, onSelect, isDirty, hasError, onCopy, onDuplicate, onCopyAndNewRule, actions } = ctx;
3497
3547
  const ruleLabelLookup = useRuleLabelLookup();
3498
3548
  const ScopeIcon = record.scope.kind && record.scope.kind !== "collection" ? DEFAULT_ICONS.scope[record.scope.kind] : DEFAULT_ICONS.scope.product;
3499
3549
  const tone = resolveTone(void 0, record.status);
@@ -3567,15 +3617,17 @@ var DefaultRecordRow = ({ record, ctx, compact = false }) => {
3567
3617
  }
3568
3618
  )
3569
3619
  ] }),
3570
- (onCopy || onDuplicate || actions && actions.length > 0) && /* @__PURE__ */ jsx(
3620
+ (onCopy || onDuplicate || onCopyAndNewRule || actions && actions.length > 0) && /* @__PURE__ */ jsx(
3571
3621
  RowContextMenu,
3572
3622
  {
3573
3623
  onCopy,
3574
3624
  onDuplicate,
3625
+ onCopyAndNewRule,
3575
3626
  actions,
3576
3627
  i18n: {
3577
3628
  copy: DEFAULT_I18N.copy,
3578
- duplicateAction: DEFAULT_I18N.duplicateAction
3629
+ duplicateAction: DEFAULT_I18N.duplicateAction,
3630
+ copyAndNewRuleAction: DEFAULT_I18N.copyAndNewRuleAction
3579
3631
  }
3580
3632
  }
3581
3633
  )
@@ -4623,6 +4675,22 @@ function RecordEditor({
4623
4675
  ]
4624
4676
  }
4625
4677
  ),
4678
+ clipboard.onCopyAndNewRule && /* @__PURE__ */ jsxs(
4679
+ "button",
4680
+ {
4681
+ type: "button",
4682
+ onClick: clipboard.onCopyAndNewRule,
4683
+ disabled: !clipboard.canCopy || !!ctx.isSaving,
4684
+ title: clipboard.copyAndNewRuleLabel ?? i18n.copyAndNewRuleAction ?? "Copy and start new rule",
4685
+ "aria-label": clipboard.copyAndNewRuleLabel ?? i18n.copyAndNewRuleAction ?? "Copy and start new rule",
4686
+ className: "text-xs px-2.5 py-1.5 rounded-md border transition-opacity disabled:opacity-40 hover:bg-[hsl(var(--ra-muted))] inline-flex items-center gap-1.5",
4687
+ style: { borderColor: "hsl(var(--ra-border))", color: "hsl(var(--ra-text))" },
4688
+ children: [
4689
+ /* @__PURE__ */ jsx(FilePlus2, { className: "w-3 h-3" }),
4690
+ clipboard.copyAndNewRuleLabel ?? i18n.copyAndNewRuleAction ?? "Copy and start new rule"
4691
+ ]
4692
+ }
4693
+ ),
4626
4694
  /* @__PURE__ */ jsxs(
4627
4695
  "button",
4628
4696
  {
@@ -5782,7 +5850,7 @@ function DefaultItemTable({
5782
5850
  const extra = rowActions ? rowActions(item) ?? void 0 : void 0;
5783
5851
  const cb = rowClipboard ? rowClipboard(item) : null;
5784
5852
  const hasExtras = !!(extra && extra.length > 0);
5785
- if (!cb?.onCopy && !cb?.onDuplicate && !hasExtras) return null;
5853
+ if (!cb?.onCopy && !cb?.onDuplicate && !cb?.onCopyAndNewRule && !hasExtras) return null;
5786
5854
  return /* @__PURE__ */ jsx(
5787
5855
  "span",
5788
5856
  {
@@ -5793,10 +5861,12 @@ function DefaultItemTable({
5793
5861
  {
5794
5862
  onCopy: cb?.onCopy,
5795
5863
  onDuplicate: cb?.onDuplicate,
5864
+ onCopyAndNewRule: cb?.onCopyAndNewRule,
5796
5865
  actions: extra ?? void 0,
5797
5866
  i18n: {
5798
5867
  copy: DEFAULT_I18N.copy,
5799
- duplicateAction: DEFAULT_I18N.duplicateAction
5868
+ duplicateAction: DEFAULT_I18N.duplicateAction,
5869
+ copyAndNewRuleAction: DEFAULT_I18N.copyAndNewRuleAction
5800
5870
  }
5801
5871
  }
5802
5872
  )
@@ -5926,7 +5996,7 @@ function DefaultItemCards({
5926
5996
  /* @__PURE__ */ jsx("div", { className: "ra-item-card-title", children: item.label }),
5927
5997
  item.subtitle && /* @__PURE__ */ jsx("div", { className: "ra-item-card-sub", children: item.subtitle })
5928
5998
  ] }),
5929
- (cb?.onCopy || cb?.onDuplicate || extraActions && extraActions.length > 0) && /* @__PURE__ */ jsx(
5999
+ (cb?.onCopy || cb?.onDuplicate || cb?.onCopyAndNewRule || extraActions && extraActions.length > 0) && /* @__PURE__ */ jsx(
5930
6000
  "span",
5931
6001
  {
5932
6002
  className: "ra-item-card-menu",
@@ -5937,10 +6007,12 @@ function DefaultItemCards({
5937
6007
  {
5938
6008
  onCopy: cb?.onCopy,
5939
6009
  onDuplicate: cb?.onDuplicate,
6010
+ onCopyAndNewRule: cb?.onCopyAndNewRule,
5940
6011
  actions: extraActions,
5941
6012
  i18n: {
5942
6013
  copy: DEFAULT_I18N.copy,
5943
- duplicateAction: DEFAULT_I18N.duplicateAction
6014
+ duplicateAction: DEFAULT_I18N.duplicateAction,
6015
+ copyAndNewRuleAction: DEFAULT_I18N.copyAndNewRuleAction
5944
6016
  }
5945
6017
  }
5946
6018
  )
@@ -6591,18 +6663,12 @@ function NewRuleWizard({
6591
6663
  onCancel,
6592
6664
  onNext,
6593
6665
  onBack,
6594
- canChooseSeed = false,
6595
- seedMode = null,
6596
- onSeedModeChange,
6597
- pasteSourceLabel,
6598
6666
  facets,
6599
6667
  children,
6600
- itemNoun = "record",
6601
- seedPromptMode = "inline"
6668
+ itemNoun = "record"
6602
6669
  }) {
6603
6670
  const preview = useRulePreview({ SL, collectionId, appId, rule });
6604
6671
  const canProceed = isFacetRuleValid(rule);
6605
- const showSeedPicker = (canChooseSeed || pasteSourceLabel) && seedPromptMode === "inline";
6606
6672
  if (step === 1) {
6607
6673
  return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col overflow-hidden", children: [
6608
6674
  /* @__PURE__ */ jsx(
@@ -6615,68 +6681,17 @@ function NewRuleWizard({
6615
6681
  onCancel
6616
6682
  }
6617
6683
  ),
6618
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 overflow-auto px-5 py-4", children: [
6619
- /* @__PURE__ */ jsx(
6620
- FacetRuleEditor,
6621
- {
6622
- value: rule,
6623
- onChange: onRuleChange,
6624
- facets,
6625
- collectionId,
6626
- preview,
6627
- description: "The new rule will apply to every product whose facets match every clause below."
6628
- }
6629
- ),
6630
- showSeedPicker && /* @__PURE__ */ jsxs(
6631
- "div",
6632
- {
6633
- className: "mt-4 rounded-lg border p-3",
6634
- style: {
6635
- borderColor: "hsl(var(--ra-border))",
6636
- background: "hsl(var(--ra-surface))"
6637
- },
6638
- children: [
6639
- /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
6640
- /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold", style: { color: "hsl(var(--ra-text))" }, children: "Start from" }),
6641
- /* @__PURE__ */ jsx("div", { className: "text-xs", style: { color: "hsl(var(--ra-muted-text))" }, children: "Choose whether the new rule starts empty, copies the current global record, or pastes from your clipboard." })
6642
- ] }),
6643
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
6644
- /* @__PURE__ */ jsx(
6645
- "button",
6646
- {
6647
- type: "button",
6648
- className: "ra-btn",
6649
- "data-variant": seedMode === "blank" || seedMode == null ? "primary" : "ghost",
6650
- onClick: () => onSeedModeChange?.("blank"),
6651
- children: "Start blank"
6652
- }
6653
- ),
6654
- canChooseSeed && /* @__PURE__ */ jsx(
6655
- "button",
6656
- {
6657
- type: "button",
6658
- className: "ra-btn",
6659
- "data-variant": seedMode === "global" ? "primary" : "ghost",
6660
- onClick: () => onSeedModeChange?.("global"),
6661
- children: "Copy from global"
6662
- }
6663
- ),
6664
- pasteSourceLabel && /* @__PURE__ */ jsx(
6665
- "button",
6666
- {
6667
- type: "button",
6668
- className: "ra-btn",
6669
- "data-variant": seedMode === "paste" ? "primary" : "ghost",
6670
- onClick: () => onSeedModeChange?.("paste"),
6671
- title: `Paste from ${pasteSourceLabel}`,
6672
- children: "Paste from clipboard"
6673
- }
6674
- )
6675
- ] })
6676
- ]
6677
- }
6678
- )
6679
- ] }),
6684
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-auto px-5 py-4", children: /* @__PURE__ */ jsx(
6685
+ FacetRuleEditor,
6686
+ {
6687
+ value: rule,
6688
+ onChange: onRuleChange,
6689
+ facets,
6690
+ collectionId,
6691
+ preview,
6692
+ description: "The new rule will apply to every product whose facets match every clause below."
6693
+ }
6694
+ ) }),
6680
6695
  /* @__PURE__ */ jsx(
6681
6696
  WizardFooter,
6682
6697
  {
@@ -6832,6 +6847,78 @@ function WizardFooter({
6832
6847
  }
6833
6848
  );
6834
6849
  }
6850
+ function CreateRecordChooser({
6851
+ title,
6852
+ body,
6853
+ primaryLabel,
6854
+ onPrimary,
6855
+ secondaryLabel,
6856
+ onSecondary,
6857
+ tertiaryLabel,
6858
+ onTertiary
6859
+ }) {
6860
+ return /* @__PURE__ */ jsx("div", { className: "h-full flex items-center justify-center px-6 py-10", children: /* @__PURE__ */ jsxs("div", { className: "max-w-xl w-full text-center space-y-4", children: [
6861
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
6862
+ /* @__PURE__ */ jsx(
6863
+ "h3",
6864
+ {
6865
+ className: "text-base font-semibold m-0",
6866
+ style: { color: "hsl(var(--ra-text))" },
6867
+ children: title
6868
+ }
6869
+ ),
6870
+ /* @__PURE__ */ jsx(
6871
+ "p",
6872
+ {
6873
+ className: "text-sm m-0",
6874
+ style: { color: "hsl(var(--ra-muted-text))" },
6875
+ children: body
6876
+ }
6877
+ )
6878
+ ] }),
6879
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
6880
+ /* @__PURE__ */ jsxs(
6881
+ "button",
6882
+ {
6883
+ type: "button",
6884
+ onClick: onPrimary,
6885
+ className: "ra-btn",
6886
+ "data-variant": "primary",
6887
+ children: [
6888
+ /* @__PURE__ */ jsx(FilePlus2, { "aria-hidden": "true", className: "w-4 h-4" }),
6889
+ /* @__PURE__ */ jsx("span", { children: primaryLabel })
6890
+ ]
6891
+ }
6892
+ ),
6893
+ secondaryLabel && onSecondary && /* @__PURE__ */ jsxs(
6894
+ "button",
6895
+ {
6896
+ type: "button",
6897
+ onClick: onSecondary,
6898
+ className: "ra-btn",
6899
+ "data-variant": "ghost",
6900
+ children: [
6901
+ /* @__PURE__ */ jsx(Copy, { "aria-hidden": "true", className: "w-4 h-4" }),
6902
+ /* @__PURE__ */ jsx("span", { children: secondaryLabel })
6903
+ ]
6904
+ }
6905
+ ),
6906
+ tertiaryLabel && onTertiary && /* @__PURE__ */ jsxs(
6907
+ "button",
6908
+ {
6909
+ type: "button",
6910
+ onClick: onTertiary,
6911
+ className: "ra-btn",
6912
+ "data-variant": "ghost",
6913
+ children: [
6914
+ /* @__PURE__ */ jsx(Copy, { "aria-hidden": "true", className: "w-4 h-4" }),
6915
+ /* @__PURE__ */ jsx("span", { children: tertiaryLabel })
6916
+ ]
6917
+ }
6918
+ )
6919
+ ] })
6920
+ ] }) });
6921
+ }
6835
6922
  var RuleGroupEditDialog = ({
6836
6923
  open,
6837
6924
  ctx,
@@ -8200,6 +8287,33 @@ function RecordsAdminShellInner(props) {
8200
8287
  }
8201
8288
  return void 0;
8202
8289
  }, [ruleWizardSeedMode, globalSourceRecord, directGlobalSeedData, resolvedGlobalSeed.data, onCopyOverride, defaultData, wizardClipboard.entry]);
8290
+ const explicitSingletonInitialData = useMemo(() => {
8291
+ if (isCollection || activeScope !== "product" || selectedRecordId !== DRAFT_ID3) return void 0;
8292
+ if (draftKind === "global") {
8293
+ const globalSeedSource = globalSourceRecord?.data ?? directGlobalSeedData ?? resolvedGlobalSeed.data;
8294
+ if (!globalSeedSource) return defaultData?.();
8295
+ const globalSeedScope = globalSourceRecord?.scope ?? parseRef("");
8296
+ if (onCopyOverride) {
8297
+ return onCopyOverride({ value: globalSeedSource, scope: globalSeedScope });
8298
+ }
8299
+ try {
8300
+ return structuredClone(globalSeedSource);
8301
+ } catch {
8302
+ return JSON.parse(JSON.stringify(globalSeedSource));
8303
+ }
8304
+ }
8305
+ return defaultData?.() ?? {};
8306
+ }, [
8307
+ isCollection,
8308
+ activeScope,
8309
+ selectedRecordId,
8310
+ draftKind,
8311
+ globalSourceRecord,
8312
+ directGlobalSeedData,
8313
+ resolvedGlobalSeed.data,
8314
+ onCopyOverride,
8315
+ defaultData
8316
+ ]);
8203
8317
  const refetchAll = useCallback(async () => {
8204
8318
  await Promise.all([
8205
8319
  recordList.refetch(),
@@ -8218,6 +8332,7 @@ function RecordsAdminShellInner(props) {
8218
8332
  ruleWizardStep,
8219
8333
  ruleWizardDraftKey,
8220
8334
  ruleWizardInitialData,
8335
+ explicitSingletonInitialData,
8221
8336
  ruleWizardRule,
8222
8337
  resolved: {
8223
8338
  data: resolved.data,
@@ -8341,6 +8456,7 @@ function RecordsAdminShellInner(props) {
8341
8456
  ]);
8342
8457
  const onLeftSelectRef = useRef(null);
8343
8458
  const onCreateItemDraftRef = useRef(null);
8459
+ const onCreateRuleFromClipboardRef = useRef(null);
8344
8460
  const previewReopenAnchorRef = useRef(null);
8345
8461
  const { runWithGuard } = useDirtyNavigation({
8346
8462
  strategy: dirtyStrategy,
@@ -8397,7 +8513,9 @@ function RecordsAdminShellInner(props) {
8397
8513
  onCopyOverride,
8398
8514
  onPasteOverride,
8399
8515
  onLeftSelectRef,
8400
- onCreateItemDraftRef
8516
+ onCreateItemDraftRef,
8517
+ onCreateRuleFromClipboardRef,
8518
+ isRuleTab: activeScope === "rule" || activeScope === "collection"
8401
8519
  });
8402
8520
  const editorClipboard = shellClipboard.editorClipboard;
8403
8521
  const rowClipboard = shellClipboard.rowClipboard;
@@ -8767,7 +8885,7 @@ function RecordsAdminShellInner(props) {
8767
8885
  setSelectedRecordId(null);
8768
8886
  setSelectedItemId(null);
8769
8887
  setDraftKind("rule");
8770
- setRuleWizardSeedMode(seed ?? "blank");
8888
+ setRuleWizardSeedMode(seed ?? null);
8771
8889
  setRuleWizardDraftKey(null);
8772
8890
  setRuleWizardRule({ all: [] });
8773
8891
  setRuleWizardStep(1);
@@ -8787,19 +8905,20 @@ function RecordsAdminShellInner(props) {
8787
8905
  setRuleWizardStep(2);
8788
8906
  } else {
8789
8907
  setRuleWizardStep(2);
8790
- if (ruleWizardSeedMode == null) {
8791
- setRuleWizardSeedMode(singletonGlobalSeedAvailable ? "global" : "blank");
8792
- }
8793
- setRuleWizardDraftKey(mintRuleWizardDraftKey());
8794
- setSelectedRecordId(DRAFT_ID3);
8795
8908
  }
8796
- }, [cardinality, mintRuleWizardDraftKey, ruleWizardSeedMode, singletonGlobalSeedAvailable, setRuleWizardSeedMode]);
8909
+ }, [cardinality]);
8910
+ const startRuleWizardDraft = useCallback((seed) => {
8911
+ setRuleWizardSeedMode(seed);
8912
+ setRuleWizardDraftKey(mintRuleWizardDraftKey());
8913
+ setSelectedRecordId(DRAFT_ID3);
8914
+ }, [mintRuleWizardDraftKey]);
8797
8915
  const onRuleWizardBack = useCallback(() => {
8798
8916
  setRuleWizardStep(1);
8799
8917
  setRuleWizardDraftKey(null);
8918
+ setRuleWizardSeedMode(null);
8800
8919
  setSelectedRecordId(null);
8801
8920
  setSelectedItemId(null);
8802
- }, [setRuleWizardDraftKey]);
8921
+ }, [setRuleWizardDraftKey, setRuleWizardSeedMode]);
8803
8922
  const onRuleWizardCreateItem = useCallback(() => {
8804
8923
  if (!isCollection) return;
8805
8924
  const id = coerceDraftItemId2(generateItemId);
@@ -8817,6 +8936,13 @@ function RecordsAdminShellInner(props) {
8817
8936
  setDraftKind("global");
8818
8937
  });
8819
8938
  }, [runWithGuard, activeScope]);
8939
+ const onCreateProductRecord = useCallback((seed) => {
8940
+ void runWithGuard(() => {
8941
+ if (activeScope !== "product") setActiveScope("product");
8942
+ setSelectedRecordId(DRAFT_ID3);
8943
+ setDraftKind(seed === "global" ? "global" : "item");
8944
+ });
8945
+ }, [runWithGuard, activeScope]);
8820
8946
  const filteredRuleItems = useMemo(
8821
8947
  () => isRuleTab ? applyRuleFilters(recordList.items, ruleFilters) : recordList.items,
8822
8948
  [isRuleTab, recordList.items, ruleFilters]
@@ -9018,6 +9144,7 @@ function RecordsAdminShellInner(props) {
9018
9144
  };
9019
9145
  onLeftSelectRef.current = onLeftSelect;
9020
9146
  onCreateItemDraftRef.current = onItemCreate;
9147
+ onCreateRuleFromClipboardRef.current = () => onCreateRule("paste");
9021
9148
  return /* @__PURE__ */ jsx(RuleLabelLookupProvider, { value: ruleLabelLookup, children: /* @__PURE__ */ jsxs(
9022
9149
  "div",
9023
9150
  {
@@ -9239,37 +9366,20 @@ function RecordsAdminShellInner(props) {
9239
9366
  }
9240
9367
  ) }),
9241
9368
  /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-2.5 border-b", style: { borderColor: "hsl(var(--ra-border))" }, children: [
9242
- isRuleTab && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
9243
- /* @__PURE__ */ jsxs(
9244
- "button",
9245
- {
9246
- type: "button",
9247
- onClick: () => onCreateRule(),
9248
- className: "ra-btn w-full",
9249
- "data-variant": "primary",
9250
- "aria-label": "New rule",
9251
- children: [
9252
- /* @__PURE__ */ jsx(Plus, { className: "w-3.5 h-3.5", "aria-hidden": "true" }),
9253
- /* @__PURE__ */ jsx("span", { children: "New rule" })
9254
- ]
9255
- }
9256
- ),
9257
- wizardClipboard.entry && /* @__PURE__ */ jsxs(
9258
- "button",
9259
- {
9260
- type: "button",
9261
- onClick: () => onCreateRule("paste"),
9262
- className: "ra-btn w-full",
9263
- "data-variant": "ghost",
9264
- "aria-label": "Paste as new rule",
9265
- title: wizardClipboard.entry.sourceLabel ? `Paste from ${wizardClipboard.entry.sourceLabel}` : "Paste from clipboard",
9266
- children: [
9267
- /* @__PURE__ */ jsx(ClipboardPaste, { className: "w-3.5 h-3.5", "aria-hidden": "true" }),
9268
- /* @__PURE__ */ jsx("span", { children: "Paste as new rule" })
9269
- ]
9270
- }
9271
- )
9272
- ] }),
9369
+ isRuleTab && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: /* @__PURE__ */ jsxs(
9370
+ "button",
9371
+ {
9372
+ type: "button",
9373
+ onClick: () => onCreateRule(),
9374
+ className: "ra-btn w-full",
9375
+ "data-variant": "primary",
9376
+ "aria-label": "New rule",
9377
+ children: [
9378
+ /* @__PURE__ */ jsx(Plus, { className: "w-3.5 h-3.5", "aria-hidden": "true" }),
9379
+ /* @__PURE__ */ jsx("span", { children: "New rule" })
9380
+ ]
9381
+ }
9382
+ ) }),
9273
9383
  showCreateGlobal && /* @__PURE__ */ jsxs(
9274
9384
  "button",
9275
9385
  {
@@ -9419,72 +9529,23 @@ function RecordsAdminShellInner(props) {
9419
9529
  onCancel: onCancelRuleWizard,
9420
9530
  onNext: onRuleWizardNext,
9421
9531
  onBack: onRuleWizardBack,
9422
- canChooseSeed: !isCollection && singletonGlobalSeedAvailable,
9423
- seedMode: ruleWizardSeedMode,
9424
- onSeedModeChange: setRuleWizardSeedMode,
9425
- pasteSourceLabel: wizardClipboard.entry?.sourceLabel ?? (wizardClipboard.entry ? "clipboard" : void 0),
9426
9532
  facets: ruleWizardFacets,
9427
9533
  itemNoun,
9428
- seedPromptMode: !isCollection ? "after-next" : "inline",
9429
9534
  children: [
9430
- ruleWizardStep === 2 && !isCollection && /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col overflow-hidden", children: [
9431
- /* @__PURE__ */ jsxs(
9432
- "div",
9433
- {
9434
- className: "border-b px-5 py-3",
9435
- style: { borderColor: "hsl(var(--ra-border))", background: "hsl(var(--ra-surface))" },
9436
- children: [
9437
- /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
9438
- /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold", style: { color: "hsl(var(--ra-text))" }, children: "Start from" }),
9439
- /* @__PURE__ */ jsx("div", { className: "text-xs", style: { color: "hsl(var(--ra-muted-text))" }, children: "Choose whether the new rule starts empty, copies the current global record, or pastes from your clipboard." })
9440
- ] }),
9441
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
9442
- /* @__PURE__ */ jsx(
9443
- "button",
9444
- {
9445
- type: "button",
9446
- className: "ra-btn",
9447
- "data-variant": ruleWizardSeedMode === "blank" || ruleWizardSeedMode == null ? "primary" : "ghost",
9448
- onClick: () => {
9449
- setRuleWizardSeedMode("blank");
9450
- setRuleWizardDraftKey(mintRuleWizardDraftKey());
9451
- },
9452
- children: "Start blank"
9453
- }
9454
- ),
9455
- singletonGlobalSeedAvailable && /* @__PURE__ */ jsx(
9456
- "button",
9457
- {
9458
- type: "button",
9459
- className: "ra-btn",
9460
- "data-variant": ruleWizardSeedMode === "global" ? "primary" : "ghost",
9461
- onClick: () => {
9462
- setRuleWizardSeedMode("global");
9463
- setRuleWizardDraftKey(mintRuleWizardDraftKey());
9464
- },
9465
- children: "Copy from global"
9466
- }
9467
- ),
9468
- wizardClipboard.entry && /* @__PURE__ */ jsx(
9469
- "button",
9470
- {
9471
- type: "button",
9472
- className: "ra-btn",
9473
- "data-variant": ruleWizardSeedMode === "paste" ? "primary" : "ghost",
9474
- onClick: () => {
9475
- setRuleWizardSeedMode("paste");
9476
- setRuleWizardDraftKey(mintRuleWizardDraftKey());
9477
- },
9478
- title: wizardClipboard.entry.sourceLabel ? `Paste from ${wizardClipboard.entry.sourceLabel}` : "Paste from clipboard",
9479
- children: "Paste from clipboard"
9480
- }
9481
- )
9482
- ] })
9483
- ]
9484
- }
9485
- ),
9486
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: editingTargetScope && renderEditorWithPreview() })
9487
- ] }),
9535
+ ruleWizardStep === 2 && !isCollection && !editingTargetScope && /* @__PURE__ */ jsx(
9536
+ CreateRecordChooser,
9537
+ {
9538
+ title: `Create your first ${itemNoun} for this rule`,
9539
+ body: `The rule is set. Now choose how to create the first ${itemNoun} that should apply to every matching product.`,
9540
+ primaryLabel: "Start blank",
9541
+ onPrimary: () => startRuleWizardDraft("blank"),
9542
+ secondaryLabel: singletonGlobalSeedAvailable ? "Copy from global" : void 0,
9543
+ onSecondary: singletonGlobalSeedAvailable ? () => startRuleWizardDraft("global") : void 0,
9544
+ tertiaryLabel: wizardClipboard.entry ? "Paste from clipboard" : void 0,
9545
+ onTertiary: wizardClipboard.entry ? () => startRuleWizardDraft("paste") : void 0
9546
+ }
9547
+ ),
9548
+ ruleWizardStep === 2 && !isCollection && !!editingTargetScope && renderEditorWithPreview(),
9488
9549
  ruleWizardStep === 2 && isCollection && !selectedItemId && /* @__PURE__ */ jsx("div", { className: "h-full flex items-center justify-center px-6 py-10", children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm text-center space-y-3", children: [
9489
9550
  /* @__PURE__ */ jsxs(
9490
9551
  "h3",
@@ -9599,6 +9660,17 @@ function RecordsAdminShellInner(props) {
9599
9660
  )
9600
9661
  }
9601
9662
  ),
9663
+ ruleWizardStep === null && isProductTab && selectedProductId && !isCollection && editingTargetScope && resolved.source === "empty" && selectedRecordId === null ? /* @__PURE__ */ jsx(
9664
+ CreateRecordChooser,
9665
+ {
9666
+ title: "No record set for this product",
9667
+ body: `Choose whether to create a fresh ${itemNoun} for this product or start from the global default.`,
9668
+ primaryLabel: "Start blank",
9669
+ onPrimary: () => onCreateProductRecord("blank"),
9670
+ secondaryLabel: singletonGlobalSeedAvailable ? "Copy from global" : void 0,
9671
+ onSecondary: singletonGlobalSeedAvailable ? () => onCreateProductRecord("global") : void 0
9672
+ }
9673
+ ) : null,
9602
9674
  ruleWizardStep === null && !isProductTab && editingTargetScope && (!isCollection || selectedItemId) && renderEditorWithPreview()
9603
9675
  ] })
9604
9676
  ]
@@ -9725,7 +9797,7 @@ var RecordBrowser = ({
9725
9797
  };
9726
9798
  var initials2 = (s) => s.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]?.toUpperCase() ?? "").join("") || "?";
9727
9799
  var DefaultRecordCard = ({ record, ctx, variant = "grid" }) => {
9728
- const { selected, onSelect, isDirty, hasError, onCopy, onDuplicate, actions } = ctx;
9800
+ const { selected, onSelect, isDirty, hasError, onCopy, onDuplicate, onCopyAndNewRule, actions } = ctx;
9729
9801
  const aspect = variant === "gallery" ? "aspect-video" : "aspect-square";
9730
9802
  return /* @__PURE__ */ jsxs(
9731
9803
  "button",
@@ -9786,15 +9858,17 @@ var DefaultRecordCard = ({ record, ctx, variant = "grid" }) => {
9786
9858
  /* @__PURE__ */ jsxs("div", { className: "p-2.5 min-w-0", children: [
9787
9859
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5 min-w-0", children: [
9788
9860
  /* @__PURE__ */ jsx("div", { className: "ra-row-title flex-1 min-w-0", children: record.label }),
9789
- (onCopy || onDuplicate || actions && actions.length > 0) && /* @__PURE__ */ jsx(
9861
+ (onCopy || onDuplicate || onCopyAndNewRule || actions && actions.length > 0) && /* @__PURE__ */ jsx(
9790
9862
  RowContextMenu,
9791
9863
  {
9792
9864
  onCopy,
9793
9865
  onDuplicate,
9866
+ onCopyAndNewRule,
9794
9867
  actions,
9795
9868
  i18n: {
9796
9869
  copy: DEFAULT_I18N.copy,
9797
- duplicateAction: DEFAULT_I18N.duplicateAction
9870
+ duplicateAction: DEFAULT_I18N.duplicateAction,
9871
+ copyAndNewRuleAction: DEFAULT_I18N.copyAndNewRuleAction
9798
9872
  }
9799
9873
  }
9800
9874
  )