@proveanything/smartlinks-utils-ui 0.12.4 → 0.12.6

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.
@@ -455,6 +455,14 @@ interface RecordsAdminI18n {
455
455
  duplicateAction: string;
456
456
  /** Toast shown after a successful Duplicate. `{name}` = source label. */
457
457
  duplicateToast: string;
458
+ /** Duplicate-to picker labels. */
459
+ duplicateDialogTitle: string;
460
+ duplicateTargetGlobal: string;
461
+ duplicateTargetSameRule: string;
462
+ duplicateTargetExistingRules: string;
463
+ duplicateTargetNewRule: string;
464
+ duplicateConfirm: string;
465
+ duplicateCancel: string;
458
466
  /**
459
467
  * Row-menu label for the "Copy and start new rule" action — shown on
460
468
  * the rules tab in singleton mode. One-click flow: copies the row to
@@ -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, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, 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, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, Sparkles, 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,13 @@ var DEFAULT_I18N = {
240
240
  pasteWarnContinue: "Continue",
241
241
  duplicateAction: "Duplicate",
242
242
  duplicateToast: "Duplicated {name}. Review and Save.",
243
+ duplicateDialogTitle: 'Duplicate "{name}" to:',
244
+ duplicateTargetGlobal: "Global",
245
+ duplicateTargetSameRule: "Same rule as source",
246
+ duplicateTargetExistingRules: "Existing rules",
247
+ duplicateTargetNewRule: "New rule\u2026",
248
+ duplicateConfirm: "Duplicate",
249
+ duplicateCancel: "Cancel",
243
250
  copyAndNewRuleAction: "Copy and start new rule",
244
251
  itemsAllLabel: "All items",
245
252
  subtitleEmpty: "Not set",
@@ -1621,6 +1628,199 @@ var ClipboardToast = ({ message, variant = "copy", onDismiss }) => {
1621
1628
  }
1622
1629
  );
1623
1630
  };
1631
+ var DEFAULT_I18N2 = {
1632
+ title: 'Duplicate "{name}" to:',
1633
+ global: "Global",
1634
+ sameRule: "Same rule as source",
1635
+ existingRules: "Existing rules",
1636
+ newRule: "New rule\u2026",
1637
+ confirm: "Duplicate",
1638
+ cancel: "Cancel"
1639
+ };
1640
+ function DuplicateTargetPicker({
1641
+ open,
1642
+ sourceLabel,
1643
+ sourceHasRule,
1644
+ rules,
1645
+ supportsRules,
1646
+ supportsGlobal = true,
1647
+ onCancel,
1648
+ onConfirm,
1649
+ i18n
1650
+ }) {
1651
+ const t = { ...DEFAULT_I18N2, ...i18n ?? {} };
1652
+ const [picked, setPicked] = useState(null);
1653
+ const containerRef = useRef(null);
1654
+ useEffect(() => {
1655
+ if (!open) {
1656
+ setPicked(null);
1657
+ return;
1658
+ }
1659
+ setPicked(sourceHasRule && supportsRules ? { kind: "sameRule" } : supportsGlobal ? { kind: "global" } : null);
1660
+ }, [open, sourceHasRule, supportsRules, supportsGlobal]);
1661
+ useEffect(() => {
1662
+ if (!open) return;
1663
+ const onKey = (e) => {
1664
+ if (e.key === "Escape") {
1665
+ e.preventDefault();
1666
+ onCancel();
1667
+ }
1668
+ };
1669
+ window.addEventListener("keydown", onKey);
1670
+ return () => window.removeEventListener("keydown", onKey);
1671
+ }, [open, onCancel]);
1672
+ const sortedRules = useMemo(() => rules.slice().sort((a, b) => {
1673
+ if (b.count !== a.count) return b.count - a.count;
1674
+ return a.summary.localeCompare(b.summary);
1675
+ }), [rules]);
1676
+ if (!open || typeof document === "undefined") return null;
1677
+ const stop = (e) => {
1678
+ e.stopPropagation();
1679
+ };
1680
+ const isSame = (a, b) => {
1681
+ if (!a || a.kind !== b.kind) return false;
1682
+ if (a.kind === "existingRule" && b.kind === "existingRule") {
1683
+ return a.ruleHash === b.ruleHash;
1684
+ }
1685
+ return true;
1686
+ };
1687
+ const RadioRow = ({
1688
+ target,
1689
+ label,
1690
+ sublabel,
1691
+ icon: Icon
1692
+ }) => {
1693
+ const checked = isSame(picked, target);
1694
+ return /* @__PURE__ */ jsxs(
1695
+ "button",
1696
+ {
1697
+ type: "button",
1698
+ onClick: () => setPicked(target),
1699
+ className: "w-full text-left px-3 py-2 rounded-md border transition-colors flex items-start gap-2.5",
1700
+ style: {
1701
+ borderColor: checked ? "hsl(var(--ra-accent))" : "hsl(var(--ra-border))",
1702
+ background: checked ? "hsl(var(--ra-accent) / 0.08)" : "transparent",
1703
+ color: "hsl(var(--ra-text))"
1704
+ },
1705
+ children: [
1706
+ /* @__PURE__ */ jsx(
1707
+ "span",
1708
+ {
1709
+ className: "mt-0.5 inline-flex items-center justify-center h-4 w-4 rounded-full shrink-0",
1710
+ style: {
1711
+ border: `2px solid ${checked ? "hsl(var(--ra-accent))" : "hsl(var(--ra-border))"}`
1712
+ },
1713
+ children: checked && /* @__PURE__ */ jsx(
1714
+ "span",
1715
+ {
1716
+ className: "h-2 w-2 rounded-full",
1717
+ style: { background: "hsl(var(--ra-accent))" }
1718
+ }
1719
+ )
1720
+ }
1721
+ ),
1722
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 mt-0.5 shrink-0" }),
1723
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
1724
+ /* @__PURE__ */ jsx("span", { className: "block text-sm", children: label }),
1725
+ sublabel && /* @__PURE__ */ jsx("span", { className: "block text-xs truncate", style: { color: "hsl(var(--ra-muted-text))" }, children: sublabel })
1726
+ ] })
1727
+ ]
1728
+ }
1729
+ );
1730
+ };
1731
+ const showRuleSection = supportsRules && (sortedRules.length > 0 || sourceHasRule);
1732
+ return createPortal(
1733
+ /* @__PURE__ */ jsx(
1734
+ "div",
1735
+ {
1736
+ className: "fixed inset-0 z-[1000] flex items-center justify-center",
1737
+ style: { background: "hsl(0 0% 0% / 0.4)" },
1738
+ onClick: onCancel,
1739
+ onMouseDown: stop,
1740
+ onTouchStart: stop,
1741
+ children: /* @__PURE__ */ jsxs(
1742
+ "div",
1743
+ {
1744
+ ref: containerRef,
1745
+ role: "dialog",
1746
+ "aria-modal": "true",
1747
+ className: "w-[min(28rem,calc(100vw-2rem))] max-h-[min(36rem,calc(100vh-2rem))] flex flex-col rounded-lg shadow-xl",
1748
+ style: { background: "hsl(var(--ra-surface))", border: "1px solid hsl(var(--ra-border))" },
1749
+ onClick: stop,
1750
+ onMouseDown: stop,
1751
+ onTouchStart: stop,
1752
+ children: [
1753
+ /* @__PURE__ */ jsx("header", { className: "px-4 py-3 border-b", style: { borderColor: "hsl(var(--ra-border))" }, children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", style: { color: "hsl(var(--ra-text))" }, children: t.title.replace("{name}", sourceLabel) }) }),
1754
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3", children: [
1755
+ supportsGlobal && /* @__PURE__ */ jsx(RadioRow, { target: { kind: "global" }, label: t.global, icon: Globe2 }),
1756
+ showRuleSection && /* @__PURE__ */ jsxs(Fragment, { children: [
1757
+ sourceHasRule && /* @__PURE__ */ jsx(RadioRow, { target: { kind: "sameRule" }, label: t.sameRule, icon: Target }),
1758
+ sortedRules.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
1759
+ /* @__PURE__ */ jsx(
1760
+ "div",
1761
+ {
1762
+ className: "text-[10px] uppercase tracking-wide mb-1.5 px-1",
1763
+ style: { color: "hsl(var(--ra-muted-text))" },
1764
+ children: t.existingRules
1765
+ }
1766
+ ),
1767
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: sortedRules.map((r) => /* @__PURE__ */ jsx(
1768
+ RadioRow,
1769
+ {
1770
+ target: { kind: "existingRule", ruleHash: r.hash },
1771
+ label: r.summary,
1772
+ sublabel: `${r.count} record${r.count === 1 ? "" : "s"}`,
1773
+ icon: Target
1774
+ },
1775
+ r.hash
1776
+ )) })
1777
+ ] }),
1778
+ /* @__PURE__ */ jsx(RadioRow, { target: { kind: "newRule" }, label: t.newRule, icon: Sparkles })
1779
+ ] })
1780
+ ] }),
1781
+ /* @__PURE__ */ jsxs(
1782
+ "footer",
1783
+ {
1784
+ className: "px-4 py-3 border-t flex items-center justify-end gap-2",
1785
+ style: { borderColor: "hsl(var(--ra-border))" },
1786
+ children: [
1787
+ /* @__PURE__ */ jsx(
1788
+ "button",
1789
+ {
1790
+ type: "button",
1791
+ onClick: onCancel,
1792
+ className: "text-xs px-3 py-1.5 rounded-md border hover:bg-[hsl(var(--ra-muted))]",
1793
+ style: { borderColor: "hsl(var(--ra-border))", color: "hsl(var(--ra-text))" },
1794
+ children: t.cancel
1795
+ }
1796
+ ),
1797
+ /* @__PURE__ */ jsxs(
1798
+ "button",
1799
+ {
1800
+ type: "button",
1801
+ disabled: !picked,
1802
+ onClick: () => {
1803
+ if (picked) onConfirm(picked);
1804
+ },
1805
+ className: "text-xs px-3 py-1.5 rounded-md font-medium transition-opacity disabled:opacity-40 inline-flex items-center gap-1.5",
1806
+ style: { background: "hsl(var(--ra-accent))", color: "hsl(var(--ra-surface))" },
1807
+ children: [
1808
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
1809
+ t.confirm
1810
+ ]
1811
+ }
1812
+ )
1813
+ ]
1814
+ }
1815
+ )
1816
+ ]
1817
+ }
1818
+ )
1819
+ }
1820
+ ),
1821
+ document.body
1822
+ );
1823
+ }
1624
1824
  function useShellClipboard(args) {
1625
1825
  const {
1626
1826
  enabled,
@@ -1640,7 +1840,11 @@ function useShellClipboard(args) {
1640
1840
  onLeftSelectRef,
1641
1841
  onCreateItemDraftRef,
1642
1842
  onCreateRuleFromClipboardRef,
1643
- isRuleTab
1843
+ isRuleTab,
1844
+ ruleCatalogue,
1845
+ ruleCatalogueRef,
1846
+ supportsRules = true,
1847
+ onCreateItemDraftAtScopeRef
1644
1848
  } = args;
1645
1849
  const clipboard = useRecordClipboard({
1646
1850
  appId,
@@ -1770,17 +1974,102 @@ function useShellClipboard(args) {
1770
1974
  // wired item-draft creation. Pushes the editor value to the clipboard
1771
1975
  // (so the existing pendingPasteTarget effect lands it on the new
1772
1976
  // draft) and mints a fresh draft.
1773
- onDuplicate: isCollection && !!onCreateItemDraftRef ? () => {
1774
- copyCurrent();
1775
- window.setTimeout(() => {
1776
- const create = onCreateItemDraftRef?.current;
1777
- if (!create) return;
1778
- const newId = create();
1779
- if (newId) setPendingPasteTarget({ kind: "record", recordId: newId });
1780
- }, 0);
1977
+ // Collection-cardinality clone affordance opens the Duplicate-To
1978
+ // picker so the admin chooses the destination scope (Global, an
1979
+ // existing rule, a new rule, or the source's own rule). We only show
1980
+ // this when the host wired the at-scope draft creator, since without
1981
+ // it we have nowhere to mint the duplicate.
1982
+ onDuplicate: isCollection && !!onCreateItemDraftAtScopeRef ? () => {
1983
+ if (!editingScope) return;
1984
+ openDuplicatePicker({
1985
+ value: editorCtx.value,
1986
+ scope: editingScope,
1987
+ label: editorHeaderLabel ?? editingScope.raw,
1988
+ facetRule: resolved.facetRule ?? null,
1989
+ recordId: resolved.recordId
1990
+ });
1781
1991
  } : void 0
1782
1992
  } : void 0;
1783
1993
  const [pendingPasteTarget, setPendingPasteTarget] = useState(null);
1994
+ const [pendingSeed, setPendingSeed] = useState(null);
1995
+ const [pickerOpen, setPickerOpen] = useState(false);
1996
+ const pickerSourceRef = useRef(null);
1997
+ const openDuplicatePicker = useCallback((source) => {
1998
+ pickerSourceRef.current = {
1999
+ value: source.value,
2000
+ scope: source.scope,
2001
+ label: source.label,
2002
+ facetRule: source.facetRule ?? null,
2003
+ recordId: source.recordId
2004
+ };
2005
+ setPickerOpen(true);
2006
+ }, []);
2007
+ const onDuplicatePickerCancel = useCallback(() => {
2008
+ setPickerOpen(false);
2009
+ pickerSourceRef.current = null;
2010
+ }, []);
2011
+ const onDuplicatePickerConfirm = useCallback((target) => {
2012
+ const source = pickerSourceRef.current;
2013
+ setPickerOpen(false);
2014
+ pickerSourceRef.current = null;
2015
+ if (!source) return;
2016
+ const transformed = onCopyOverride ? onCopyOverride({ value: source.value, scope: source.scope }) : cloneValue(source.value);
2017
+ if (target.kind === "newRule") {
2018
+ clipboard.set({
2019
+ value: transformed,
2020
+ sourceScope: source.scope,
2021
+ sourceRecordId: source.recordId,
2022
+ sourceLabel: source.label
2023
+ });
2024
+ onTelemetry?.({
2025
+ type: "clipboard.copy",
2026
+ recordType,
2027
+ sourceRef: anchorKey(source.scope)
2028
+ });
2029
+ window.setTimeout(() => onCreateRuleFromClipboardRef?.current?.(), 0);
2030
+ return;
2031
+ }
2032
+ const create = onCreateItemDraftAtScopeRef?.current;
2033
+ if (!create) return;
2034
+ let scopeArg;
2035
+ if (target.kind === "global") {
2036
+ scopeArg = { kind: "global" };
2037
+ } else if (target.kind === "sameRule") {
2038
+ if (!source.facetRule) return;
2039
+ scopeArg = { kind: "rule", rule: source.facetRule };
2040
+ } else {
2041
+ const list = ruleCatalogueRef?.current ?? ruleCatalogue ?? [];
2042
+ const hit = list.find((r) => r.hash === target.ruleHash);
2043
+ if (!hit) return;
2044
+ scopeArg = { kind: "rule", rule: hit.rule };
2045
+ }
2046
+ const newId = create(scopeArg);
2047
+ if (!newId) return;
2048
+ setPendingPasteTarget({ kind: "record", recordId: newId });
2049
+ setPendingSeed({
2050
+ value: transformed,
2051
+ sourceScope: source.scope,
2052
+ sourceLabel: source.label
2053
+ });
2054
+ onTelemetry?.({
2055
+ type: "clipboard.copy",
2056
+ recordType,
2057
+ sourceRef: anchorKey(source.scope)
2058
+ });
2059
+ setNotice({
2060
+ message: i18n.duplicateToast.replace("{name}", source.label),
2061
+ variant: "copy"
2062
+ });
2063
+ }, [
2064
+ onCopyOverride,
2065
+ clipboard,
2066
+ onTelemetry,
2067
+ recordType,
2068
+ onCreateRuleFromClipboardRef,
2069
+ onCreateItemDraftAtScopeRef,
2070
+ ruleCatalogue,
2071
+ i18n.duplicateToast
2072
+ ]);
1784
2073
  useEffect(() => {
1785
2074
  if (!pendingPasteTarget) return;
1786
2075
  if (!editingScope) return;
@@ -1788,14 +2077,31 @@ function useShellClipboard(args) {
1788
2077
  if (!matched) return;
1789
2078
  const t = window.setTimeout(() => {
1790
2079
  setPendingPasteTarget(null);
2080
+ if (pendingSeed) {
2081
+ const seed = pendingSeed;
2082
+ setPendingSeed(null);
2083
+ const finalValue = onPasteOverride ? onPasteOverride(
2084
+ { value: seed.value, sourceScope: seed.sourceScope },
2085
+ { scope: editingScope, currentValue: null }
2086
+ ) ?? seed.value : seed.value;
2087
+ editorCtx.onChange(finalValue);
2088
+ onTelemetry?.({
2089
+ type: "clipboard.paste",
2090
+ recordType,
2091
+ sourceRef: anchorKey(editingScope),
2092
+ destinationRef: editingScope.raw,
2093
+ replaced: false
2094
+ });
2095
+ return;
2096
+ }
1791
2097
  void pasteCurrent();
1792
2098
  }, 0);
1793
2099
  return () => window.clearTimeout(t);
1794
- }, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent]);
2100
+ }, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent, pendingSeed, editorCtx, onTelemetry, recordType, onPasteOverride]);
1795
2101
  const rowClipboard = enabled ? (record) => {
1796
2102
  const summaryHasData = record.data != null;
1797
2103
  const sourceParsed = record.scope;
1798
- const canDuplicate = summaryHasData && isCollection;
2104
+ const canDuplicate = summaryHasData && isCollection && !!onCreateItemDraftAtScopeRef;
1799
2105
  const canCopyAndNewRule = summaryHasData && !isCollection && !!isRuleTab && !!onCreateRuleFromClipboardRef;
1800
2106
  return {
1801
2107
  onCopy: summaryHasData ? () => {
@@ -1826,24 +2132,12 @@ function useShellClipboard(args) {
1826
2132
  }, 0);
1827
2133
  } : void 0,
1828
2134
  onDuplicate: canDuplicate ? () => {
1829
- const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
1830
- clipboard.set({
1831
- value,
1832
- sourceScope: sourceParsed,
1833
- sourceRecordId: record.id ?? void 0,
1834
- sourceLabel: record.label
1835
- });
1836
- onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: anchorKey(record.scope) });
1837
- onLeftSelectRef.current?.(record);
1838
- const create = onCreateItemDraftRef?.current;
1839
- if (!create) return;
1840
- window.setTimeout(() => {
1841
- const newId = create();
1842
- if (newId) setPendingPasteTarget({ kind: "record", recordId: newId });
1843
- }, 0);
1844
- setNotice({
1845
- message: i18n.duplicateToast.replace("{name}", record.label),
1846
- variant: "copy"
2135
+ openDuplicatePicker({
2136
+ value: record.data,
2137
+ scope: sourceParsed,
2138
+ label: record.label,
2139
+ facetRule: record.facetRule ?? null,
2140
+ recordId: record.id ?? void 0
1847
2141
  });
1848
2142
  } : void 0
1849
2143
  };
@@ -1856,10 +2150,36 @@ function useShellClipboard(args) {
1856
2150
  onDismiss: () => setNotice(null)
1857
2151
  }
1858
2152
  ) : null;
2153
+ const duplicatePicker = enabled ? /* @__PURE__ */ jsx(
2154
+ DuplicateTargetPicker,
2155
+ {
2156
+ open: pickerOpen,
2157
+ sourceLabel: pickerSourceRef.current?.label ?? "",
2158
+ sourceHasRule: !!pickerSourceRef.current?.facetRule,
2159
+ rules: (ruleCatalogueRef?.current ?? ruleCatalogue ?? []).map((r) => ({
2160
+ hash: r.hash,
2161
+ summary: r.summary,
2162
+ count: r.count
2163
+ })),
2164
+ supportsRules,
2165
+ onCancel: onDuplicatePickerCancel,
2166
+ onConfirm: onDuplicatePickerConfirm,
2167
+ i18n: {
2168
+ title: i18n.duplicateDialogTitle,
2169
+ global: i18n.duplicateTargetGlobal,
2170
+ sameRule: i18n.duplicateTargetSameRule,
2171
+ existingRules: i18n.duplicateTargetExistingRules,
2172
+ newRule: i18n.duplicateTargetNewRule,
2173
+ confirm: i18n.duplicateConfirm,
2174
+ cancel: i18n.duplicateCancel
2175
+ }
2176
+ }
2177
+ ) : null;
1859
2178
  return {
1860
2179
  editorClipboard,
1861
2180
  rowClipboard,
1862
2181
  confirmDialog: pasteConfirm.dialog,
2182
+ duplicatePicker,
1863
2183
  toast,
1864
2184
  store: clipboard
1865
2185
  };
@@ -9178,6 +9498,8 @@ function RecordsAdminShellInner(props) {
9178
9498
  ]);
9179
9499
  const onLeftSelectRef = useRef(null);
9180
9500
  const onCreateItemDraftRef = useRef(null);
9501
+ const onCreateItemDraftAtScopeRef = useRef(null);
9502
+ const ruleCatalogueRef = useRef([]);
9181
9503
  const onCreateRuleFromClipboardRef = useRef(null);
9182
9504
  const previewReopenAnchorRef = useRef(null);
9183
9505
  const { runWithGuard } = useDirtyNavigation({
@@ -9210,7 +9532,7 @@ function RecordsAdminShellInner(props) {
9210
9532
  setDrawerOpen,
9211
9533
  sidePreviewOpen,
9212
9534
  setSidePreviewOpen,
9213
- editorHeaderLabel,
9535
+ editorHeaderLabel: editorHeaderLabelRaw,
9214
9536
  editorHeaderSubtitle,
9215
9537
  editorHeaderMeta
9216
9538
  } = useShellPreviewPane({
@@ -9219,6 +9541,14 @@ function RecordsAdminShellInner(props) {
9219
9541
  selectedProductId,
9220
9542
  productBrowseItems: productLookupItems
9221
9543
  });
9544
+ const editorHeaderLabel = useMemo(() => {
9545
+ if (editorHeaderLabelRaw) return editorHeaderLabelRaw;
9546
+ if (isCollection && selectedItemId && !isDraftId3(selectedItemId)) {
9547
+ const hit = collectionItems.items.find((r) => r.id === selectedItemId || r.itemId === selectedItemId);
9548
+ if (hit?.label) return hit.label;
9549
+ }
9550
+ return void 0;
9551
+ }, [editorHeaderLabelRaw, isCollection, selectedItemId, collectionItems.items]);
9222
9552
  const shellClipboard = useShellClipboard({
9223
9553
  enabled: !!enableClipboard,
9224
9554
  appId,
@@ -9230,14 +9560,17 @@ function RecordsAdminShellInner(props) {
9230
9560
  isCollection,
9231
9561
  selectedItemId,
9232
9562
  selectedRecordId,
9233
- resolved: { source: resolved.source, recordId: resolved.recordId },
9563
+ resolved: { source: resolved.source, recordId: resolved.recordId, facetRule: resolved.facetRule ?? null },
9234
9564
  onTelemetry,
9235
9565
  onCopyOverride,
9236
9566
  onPasteOverride,
9237
9567
  onLeftSelectRef,
9238
9568
  onCreateItemDraftRef,
9239
9569
  onCreateRuleFromClipboardRef,
9240
- isRuleTab: activeScope === "rule" || activeScope === "collection"
9570
+ isRuleTab: activeScope === "rule" || activeScope === "collection",
9571
+ ruleCatalogueRef,
9572
+ supportsRules: effectiveTopLevelScopes.includes("rule"),
9573
+ onCreateItemDraftAtScopeRef
9241
9574
  });
9242
9575
  const editorClipboard = shellClipboard.editorClipboard;
9243
9576
  const rowClipboard = shellClipboard.rowClipboard;
@@ -9736,6 +10069,10 @@ function RecordsAdminShellInner(props) {
9736
10069
  const onRuleWizardNext = useCallback(() => {
9737
10070
  if (cardinality === "collection") {
9738
10071
  setRuleWizardStep(2);
10072
+ if (ruleWizardSeedMode) {
10073
+ const id = coerceDraftItemId2(generateItemId);
10074
+ setSelectedItemId(id);
10075
+ }
9739
10076
  } else {
9740
10077
  setRuleWizardStep(2);
9741
10078
  if (ruleWizardSeedMode) {
@@ -9743,7 +10080,7 @@ function RecordsAdminShellInner(props) {
9743
10080
  setSelectedRecordId(DRAFT_ID3);
9744
10081
  }
9745
10082
  }
9746
- }, [cardinality, ruleWizardSeedMode, mintRuleWizardDraftKey]);
10083
+ }, [cardinality, ruleWizardSeedMode, mintRuleWizardDraftKey, generateItemId]);
9747
10084
  const startRuleWizardDraft = useCallback((seed) => {
9748
10085
  setRuleWizardSeedMode(seed);
9749
10086
  setRuleWizardDraftKey(mintRuleWizardDraftKey());
@@ -10019,6 +10356,26 @@ function RecordsAdminShellInner(props) {
10019
10356
  onLeftSelectRef.current = onLeftSelect;
10020
10357
  onCreateItemDraftRef.current = onItemCreate;
10021
10358
  onCreateRuleFromClipboardRef.current = () => onCreateRule("paste");
10359
+ ruleCatalogueRef.current = ruleCatalogue;
10360
+ onCreateItemDraftAtScopeRef.current = (target) => {
10361
+ if (!isCollection) return null;
10362
+ if (target.kind === "global") {
10363
+ if (activeScope !== "collection") setActiveScope("collection");
10364
+ setSelectedRecordId(null);
10365
+ setDraftKind(null);
10366
+ const id2 = onItemCreate();
10367
+ return id2 ?? null;
10368
+ }
10369
+ if (activeScope !== "rule") setActiveScope("rule");
10370
+ setRuleWizardRule(target.rule);
10371
+ setRuleWizardSeedMode(null);
10372
+ setRuleWizardDraftKey(null);
10373
+ setRuleWizardStep(2);
10374
+ setDraftKind("rule");
10375
+ setSelectedRecordId(null);
10376
+ const id = onItemCreate();
10377
+ return id ?? null;
10378
+ };
10022
10379
  return /* @__PURE__ */ jsx(RuleLabelLookupProvider, { value: ruleLabelLookup, children: /* @__PURE__ */ jsxs(
10023
10380
  "div",
10024
10381
  {
@@ -10027,6 +10384,7 @@ function RecordsAdminShellInner(props) {
10027
10384
  children: [
10028
10385
  dirtyConfirm.dialog,
10029
10386
  shellClipboard.confirmDialog,
10387
+ shellClipboard.duplicatePicker,
10030
10388
  shellClipboard.toast,
10031
10389
  (() => {
10032
10390
  const showFloatHelp = !!intro && dismissed && resolvedReopenAffordance === "footer" && !headerWillRender;