@proveanything/smartlinks-utils-ui 0.9.0 → 0.9.1

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.
@@ -1,11 +1,11 @@
1
- import { styleInject } from '../../chunk-NSQRNFT2.js';
1
+ import '../../chunk-5UQQYXCX.js';
2
2
  import { FacetRuleEditor } from '../../chunk-JMCV6FOW.js';
3
3
  import { useFacets } from '../../chunk-4LHF5JB7.js';
4
4
  import { cn } from '../../chunk-L7FQ52F5.js';
5
5
  import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KFKVGUUP.js';
6
6
  export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KFKVGUUP.js';
7
7
  import { createContext, useState, useEffect, useCallback, useMemo, useRef, useContext, useSyncExternalStore, createElement, useId } from 'react';
8
- import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Rows3, List, ChevronRight, Eraser, ClipboardPaste, Box, X, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, Search, CornerDownLeft, Circle, AlertCircle, Undo2, Save, Loader2, XCircle, ArrowRight, BookOpen, Target, Settings2 } from 'lucide-react';
8
+ 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, Rows3, ChevronRight, Eraser, ClipboardPaste, Box, X, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, Search, CornerDownLeft, Circle, AlertCircle, Undo2, Save, Loader2, XCircle, ArrowRight, BookOpen, Globe2, Target, Check, Settings2 } from 'lucide-react';
9
9
  import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
10
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
11
  import { createPortal } from 'react-dom';
@@ -18,7 +18,8 @@ var DEFAULT_ICONS = {
18
18
  batch: Boxes,
19
19
  facet: Tag,
20
20
  universal: Globe,
21
- rule: SlidersHorizontal
21
+ rule: SlidersHorizontal,
22
+ all: List
22
23
  },
23
24
  status: { own: CheckCircle2, inherited: ArrowDownLeft, missing: CircleDashed },
24
25
  action: {
@@ -143,7 +144,9 @@ var DEFAULT_I18N = {
143
144
  itemsAllLabel: "All items",
144
145
  subtitleEmpty: "Not set",
145
146
  subtitleConfigured: "Configured",
146
- subtitleInherited: "Inherited"
147
+ subtitleInherited: "Inherited",
148
+ rulesTabTooltip: "Use rules to scope records to a targeted audience or a subset of products.",
149
+ rulesEmptyBody: "Create a rule to target a subset of products or a specific audience \u2014 for example, \u201Conly premium tier customers in Germany\u201D."
147
150
  };
148
151
 
149
152
  // src/components/RecordsAdmin/types/presentation.ts
@@ -204,6 +207,17 @@ var buildRef = (a) => {
204
207
  if (a.proofId) parts.push(`proof:${a.proofId}`);
205
208
  return parts.join("/");
206
209
  };
210
+ var anchorKey = (scope) => {
211
+ if (!scope) return "";
212
+ return buildRef({
213
+ productId: scope.productId,
214
+ variantId: scope.variantId,
215
+ batchId: scope.batchId,
216
+ facetId: scope.facetId,
217
+ facetValue: scope.facetValue,
218
+ proofId: scope.proofId
219
+ });
220
+ };
207
221
  var resolutionChain = (target, supportedScopes) => {
208
222
  const chain = [];
209
223
  if (supportedScopes.includes("batch") && target.batchId && target.productId) {
@@ -224,6 +238,38 @@ var resolutionChain = (target, supportedScopes) => {
224
238
  return Array.from(new Set(chain));
225
239
  };
226
240
 
241
+ // src/components/RecordsAdmin/data/ruleHash.ts
242
+ var normaliseRule = (rule) => {
243
+ if (!rule || !Array.isArray(rule.all) || rule.all.length === 0) return null;
244
+ const clauses = rule.all.map((clause) => {
245
+ const facetKey = String(clause.facetKey ?? "").trim();
246
+ const anyOfRaw = clause.anyOf ?? [];
247
+ const anyOf = Array.isArray(anyOfRaw) ? Array.from(new Set(anyOfRaw.map((v) => String(v)))).sort() : [];
248
+ return { facetKey, anyOf };
249
+ }).filter((c) => c.facetKey && c.anyOf.length > 0).sort((a, b) => a.facetKey.localeCompare(b.facetKey));
250
+ if (clauses.length === 0) return null;
251
+ return { all: clauses };
252
+ };
253
+ var fnv1a = (s) => {
254
+ let h = 2166136261;
255
+ for (let i = 0; i < s.length; i += 1) {
256
+ h ^= s.charCodeAt(i);
257
+ h = Math.imul(h, 16777619);
258
+ }
259
+ return (h >>> 0).toString(16).padStart(8, "0");
260
+ };
261
+ var ruleHash = (rule) => {
262
+ const norm = normaliseRule(rule);
263
+ if (!norm) return null;
264
+ return fnv1a(JSON.stringify(norm));
265
+ };
266
+ var summariseRule = (rule) => {
267
+ const norm = normaliseRule(rule);
268
+ if (!norm) return "No rule";
269
+ return norm.all.map((c) => `${c.facetKey}=${c.anyOf.join(",")}`).join(" \xB7 ");
270
+ };
271
+ var rulesEqual = (a, b) => ruleHash(a) === ruleHash(b);
272
+
227
273
  // src/components/RecordsAdmin/data/resolveRecord.ts
228
274
  var resolveRecord = async (args) => {
229
275
  const target = parsedRefToTarget(args.target);
@@ -449,6 +495,70 @@ var useScopeProbe = ({ SL, collectionId, admin = true, enabled = true }) => {
449
495
  error: query.error ?? null
450
496
  };
451
497
  };
498
+ var QK_BASE = ["records-admin", "scope-counts"];
499
+ var classify = (rec) => {
500
+ const hasRule = !!rec.facetRule;
501
+ if (hasRule) return "rule";
502
+ const productId = rec.productId ?? void 0;
503
+ const variantId = rec.variantId ?? void 0;
504
+ const batchId = rec.batchId ?? void 0;
505
+ if (batchId) return "batch";
506
+ if (variantId) return "variant";
507
+ if (productId) return "product";
508
+ return "collection";
509
+ };
510
+ var useScopeCounts = (args) => {
511
+ const { ctx, enabled = true, maxRecords = 500, pageSize = 100 } = args;
512
+ const queryKey = useMemo(
513
+ () => [...QK_BASE, ctx.collectionId, ctx.appId, ctx.recordType ?? null],
514
+ [ctx.collectionId, ctx.appId, ctx.recordType]
515
+ );
516
+ const query = useQuery({
517
+ queryKey,
518
+ enabled: enabled && !!ctx.collectionId && !!ctx.appId,
519
+ staleTime: 3e4,
520
+ queryFn: async () => {
521
+ const all = [];
522
+ let offset = 0;
523
+ let truncated = false;
524
+ for (let page = 0; page < Math.ceil(maxRecords / pageSize); page += 1) {
525
+ const res = await listRecords(ctx, { limit: pageSize, offset });
526
+ all.push(...res.data);
527
+ if (!res.hasMore) break;
528
+ offset += res.data.length;
529
+ if (all.length >= maxRecords) {
530
+ truncated = true;
531
+ break;
532
+ }
533
+ }
534
+ return { records: all, truncated };
535
+ }
536
+ });
537
+ const result = useMemo(() => {
538
+ const records = query.data?.records ?? [];
539
+ const truncated = query.data?.truncated ?? false;
540
+ const counts = {
541
+ collection: 0,
542
+ product: 0,
543
+ variant: 0,
544
+ batch: 0,
545
+ facet: 0,
546
+ rule: 0,
547
+ all: 0
548
+ };
549
+ for (const rec of records) counts[classify(rec)] += 1;
550
+ counts.all = records.length;
551
+ return {
552
+ counts,
553
+ total: records.length,
554
+ isLoading: query.isLoading,
555
+ error: query.error ?? null,
556
+ truncated
557
+ };
558
+ }, [query.data, query.isLoading, query.error]);
559
+ return result;
560
+ };
561
+ var scopeCountsQueryKey = (collectionId, appId, recordType) => [...QK_BASE, collectionId, appId, recordType ?? null];
452
562
  var defaultClassify = (r) => {
453
563
  if (!r.data) return "empty";
454
564
  const keys = Object.keys(r.data);
@@ -457,6 +567,7 @@ var defaultClassify = (r) => {
457
567
  };
458
568
  var matchesScope = (kind, rec, _p) => {
459
569
  const hasRule = !!rec.facetRule;
570
+ if (kind === "all") return true;
460
571
  if (kind === "rule") return hasRule;
461
572
  if (hasRule) return false;
462
573
  const productId = rec.productId ?? void 0;
@@ -479,14 +590,14 @@ var matchesContext = (p, ctx) => {
479
590
  return true;
480
591
  };
481
592
  var toSummary = (rec) => {
482
- const ref = rec.ref ?? "";
483
593
  const productId = rec.productId ?? void 0;
484
594
  const variantId = rec.variantId ?? void 0;
485
595
  const batchId = rec.batchId ?? void 0;
486
596
  const proofId = rec.proofId ?? void 0;
597
+ const synthRaw = batchId ? `product:${productId ?? ""}/batch:${batchId}` : variantId ? `product:${productId ?? ""}/variant:${variantId}` : productId ? `product:${productId}` : "";
487
598
  const scope = {
488
599
  kind: batchId ? "batch" : variantId ? "variant" : productId ? "product" : "collection",
489
- raw: ref,
600
+ raw: synthRaw,
490
601
  productId,
491
602
  variantId,
492
603
  batchId,
@@ -495,10 +606,13 @@ var toSummary = (rec) => {
495
606
  const facetRule = rec.facetRule ?? null;
496
607
  if (facetRule) scope.kind = "rule";
497
608
  const ruleLabel = facetRule && facetRule.all && facetRule.all.length > 0 ? facetRule.all.length === 1 ? "Rule \xB7 1 facet" : `Rule \xB7 ${facetRule.all.length} facets` : null;
498
- const fallbackLabel = scope.batchId ?? scope.variantId ?? scope.productId ?? ruleLabel ?? (ref || "Global record");
609
+ const fallbackLabel = scope.batchId ?? scope.variantId ?? scope.productId ?? ruleLabel ?? "Global record";
499
610
  return {
500
611
  id: rec.id,
501
- ref,
612
+ // `RecordSummary.ref` is preserved on the type for host display only
613
+ // (e.g. row renderers that want to show whatever the host put in
614
+ // `record.ref`). Framework code never uses it.
615
+ ref: rec.ref ?? "",
502
616
  scope,
503
617
  data: rec.data ?? null,
504
618
  status: "configured",
@@ -507,14 +621,14 @@ var toSummary = (rec) => {
507
621
  facetRule
508
622
  };
509
623
  };
510
- var QK_BASE = ["records-admin", "list"];
624
+ var QK_BASE2 = ["records-admin", "list"];
511
625
  var useRecordList = (args) => {
512
626
  const {
513
627
  ctx,
514
628
  scopeKind,
515
629
  search = "",
516
630
  filter = "all",
517
- classify: classify2,
631
+ classify: classify3,
518
632
  enabled = true,
519
633
  scaffolder,
520
634
  contextScope,
@@ -523,7 +637,7 @@ var useRecordList = (args) => {
523
637
  const queryClient = useQueryClient();
524
638
  const queryKey = useMemo(
525
639
  () => [
526
- ...QK_BASE,
640
+ ...QK_BASE2,
527
641
  ctx.collectionId,
528
642
  ctx.appId,
529
643
  ctx.recordType,
@@ -549,9 +663,9 @@ var useRecordList = (args) => {
549
663
  const [scaffolded, setScaffolded] = useState(null);
550
664
  const rawItems = useMemo(() => {
551
665
  const all = query.data?.pages.flatMap((p) => p.data) ?? [];
552
- const cls = classify2 ?? defaultClassify;
666
+ const cls = classify3 ?? defaultClassify;
553
667
  return all.map((rec) => ({ rec, summary: toSummary(rec) })).filter(({ rec, summary }) => matchesScope(scopeKind, rec, summary.scope)).filter(({ summary }) => matchesContext(summary.scope, contextScope)).map(({ summary }) => ({ ...summary, status: cls(summary) }));
554
- }, [query.data, scopeKind, classify2, contextScope]);
668
+ }, [query.data, scopeKind, classify3, contextScope]);
555
669
  useEffect(() => {
556
670
  if (!scaffolder) {
557
671
  setScaffolded(null);
@@ -571,7 +685,7 @@ var useRecordList = (args) => {
571
685
  if (filter !== "all") out = out.filter((r) => r.status === filter);
572
686
  if (search.trim()) {
573
687
  const q = search.trim().toLowerCase();
574
- out = out.filter((r) => `${r.label} ${r.subtitle ?? ""} ${r.ref}`.toLowerCase().includes(q));
688
+ out = out.filter((r) => `${r.label} ${r.subtitle ?? ""}`.toLowerCase().includes(q));
575
689
  }
576
690
  return out;
577
691
  }, [items, filter, search]);
@@ -582,7 +696,7 @@ var useRecordList = (args) => {
582
696
  empty: items.filter((r) => r.status === "empty").length
583
697
  }), [items]);
584
698
  const refetch = useCallback(() => {
585
- queryClient.invalidateQueries({ queryKey: [...QK_BASE, ctx.collectionId, ctx.appId, ctx.recordType] });
699
+ queryClient.invalidateQueries({ queryKey: [...QK_BASE2, ctx.collectionId, ctx.appId, ctx.recordType] });
586
700
  }, [queryClient, ctx.collectionId, ctx.appId, ctx.recordType]);
587
701
  const total = query.data?.pages[query.data.pages.length - 1]?.total ?? items.length;
588
702
  return {
@@ -603,11 +717,11 @@ var QK = ["records-admin", "facet-browse"];
603
717
  var toScaffoldSummary = (facet, value) => {
604
718
  const facetKey = facet.key ?? "";
605
719
  const valueKey = value.key ?? "";
606
- const ref = buildRef({ facetId: facetKey, facetValue: valueKey });
720
+ const synthScopeRaw = buildRef({ facetId: facetKey, facetValue: valueKey });
607
721
  return {
608
722
  id: null,
609
- ref,
610
- scope: parseRef(ref),
723
+ ref: "",
724
+ scope: parseRef(synthScopeRaw),
611
725
  data: null,
612
726
  status: "empty",
613
727
  label: value.name ?? valueKey ?? "Untitled value",
@@ -687,14 +801,18 @@ var useFacetBrowse = ({
687
801
  );
688
802
  }, [enabled, collectionId, hasAdminList, hasPublicList, hasAnyList, SL]);
689
803
  const mergedItems = useMemo(() => {
690
- const existingByRef = new Map(existing.map((item) => [item.ref, item]));
804
+ const existingByAnchor = new Map(
805
+ existing.map((item) => [anchorKey(item.scope), item]).filter(([k]) => !!k)
806
+ );
691
807
  const scaffolded = (query.data ?? []).flatMap(
692
808
  (facet) => (facet.values ?? []).filter((value) => !!facet.key && !!value.key).map((value) => {
693
809
  const scaffold = toScaffoldSummary(facet, value);
694
- return existingByRef.get(scaffold.ref) ?? scaffold;
810
+ const k = anchorKey(scaffold.scope);
811
+ return existingByAnchor.get(k) ?? scaffold;
695
812
  })
696
813
  );
697
- const extras = existing.filter((item) => !scaffolded.some((scaffold) => scaffold.ref === item.ref));
814
+ const scaffoldedKeys = new Set(scaffolded.map((s) => anchorKey(s.scope)));
815
+ const extras = existing.filter((item) => !scaffoldedKeys.has(anchorKey(item.scope)));
698
816
  return [...scaffolded, ...extras];
699
817
  }, [existing, query.data]);
700
818
  const filteredItems = useMemo(() => {
@@ -702,7 +820,7 @@ var useFacetBrowse = ({
702
820
  if (filter !== "all") next = next.filter((item) => item.status === filter);
703
821
  if (search.trim()) {
704
822
  const q = search.trim().toLowerCase();
705
- next = next.filter((item) => `${item.label} ${item.subtitle ?? ""} ${item.ref}`.toLowerCase().includes(q));
823
+ next = next.filter((item) => `${item.label} ${item.subtitle ?? ""}`.toLowerCase().includes(q));
706
824
  }
707
825
  return next;
708
826
  }, [mergedItems, filter, search]);
@@ -950,7 +1068,7 @@ function useShellBrowser(opts) {
950
1068
  probeIsLoading,
951
1069
  selectedProductId,
952
1070
  drillTab,
953
- classify: classify2
1071
+ classify: classify3
954
1072
  } = opts;
955
1073
  const [search, setSearch] = useState("");
956
1074
  const [filter, setFilter] = useState("all");
@@ -968,13 +1086,13 @@ function useShellBrowser(opts) {
968
1086
  search: activeScope === "product" ? search : "",
969
1087
  enabled: activeScope === "product" && !contextScope?.productId
970
1088
  });
971
- const recordListEnabled = (activeScope === "rule" || activeScope === "collection") && !probeIsLoading;
1089
+ const recordListEnabled = (activeScope === "rule" || activeScope === "collection" || activeScope === "all") && !probeIsLoading;
972
1090
  const recordList = useRecordList({
973
1091
  ctx,
974
1092
  scopeKind: activeScope,
975
1093
  search,
976
1094
  filter,
977
- classify: classify2,
1095
+ classify: classify3,
978
1096
  contextScope,
979
1097
  enabled: recordListEnabled
980
1098
  });
@@ -982,7 +1100,7 @@ function useShellBrowser(opts) {
982
1100
  SL,
983
1101
  collectionId,
984
1102
  existing: [],
985
- enabled: (activeScope === "rule" || activeScope === "collection") && !probeIsLoading
1103
+ enabled: (activeScope === "rule" || activeScope === "collection" || activeScope === "all") && !probeIsLoading
986
1104
  });
987
1105
  const variantChildren = useProductChildren({
988
1106
  SL,
@@ -1588,7 +1706,7 @@ function useShellClipboard(args) {
1588
1706
  const summaryHasData = record.data != null;
1589
1707
  const sourceParsed = record.scope;
1590
1708
  const compat = clipboard.entry ? checkPasteCompatibility(clipboard.entry.sourceScope, sourceParsed) : null;
1591
- const sameTarget = clipboard.entry ? clipboard.entry.sourceRecordId && record.id ? clipboard.entry.sourceRecordId === record.id : clipboard.entry.sourceScope.raw === record.ref : false;
1709
+ const sameTarget = clipboard.entry ? clipboard.entry.sourceRecordId && record.id ? clipboard.entry.sourceRecordId === record.id : clipboard.entry.sourceScope.raw === anchorKey(record.scope) : false;
1592
1710
  return {
1593
1711
  onCopy: summaryHasData ? () => {
1594
1712
  const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
@@ -1598,7 +1716,7 @@ function useShellClipboard(args) {
1598
1716
  sourceRecordId: record.id ?? void 0,
1599
1717
  sourceLabel: record.label
1600
1718
  });
1601
- onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: record.ref });
1719
+ onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: anchorKey(record.scope) });
1602
1720
  setNotice({
1603
1721
  message: i18n.copyToast.replace("{name}", record.label),
1604
1722
  variant: "copy"
@@ -1607,7 +1725,7 @@ function useShellClipboard(args) {
1607
1725
  onPaste: () => {
1608
1726
  onLeftSelectRef.current?.(record);
1609
1727
  setPendingPasteTarget(
1610
- record.id ? { kind: "record", recordId: record.id } : { kind: "anchor", ref: record.ref }
1728
+ record.id ? { kind: "record", recordId: record.id } : { kind: "anchor", ref: anchorKey(record.scope) }
1611
1729
  );
1612
1730
  },
1613
1731
  canPaste: !!clipboard.entry && !sameTarget && compat?.status !== "denied",
@@ -1879,12 +1997,12 @@ var useEditingScope = (args) => {
1879
1997
  const isCollection = cardinality === "collection";
1880
1998
  const editingItemRecordId = isCollection ? selectedItemId : null;
1881
1999
  const editingScope = useMemo(() => {
1882
- if (activeScope === "rule" || activeScope === "collection") {
2000
+ if (activeScope === "rule" || activeScope === "collection" || activeScope === "all") {
1883
2001
  if (activeScope === "rule" && ruleWizardStep === 2 && selectedRecordId === null) {
1884
2002
  return { ...parseRef(""), kind: "rule", raw: "rule:__draft__" };
1885
2003
  }
1886
2004
  if (selectedRecordId === null) {
1887
- if (activeScope === "collection" && cardinality === "collection") {
2005
+ if ((activeScope === "collection" || activeScope === "all") && cardinality === "collection") {
1888
2006
  return parseRef("");
1889
2007
  }
1890
2008
  return null;
@@ -2283,11 +2401,14 @@ var createEditorStore = () => {
2283
2401
  });
2284
2402
  } else if (spec.createMode) {
2285
2403
  const created = await createRecord(ctx, {
2286
- // Draft scopes synthesise placeholder refs (`rule:__draft__`,
2287
- // `/item:draft:...`) for shell-internal routing only never
2288
- // forward them to the SDK as an external ref. Saved rules
2289
- // still pass their real `rule:{id}` string through.
2290
- ref: spec.isDraftScope ? spec.ref : spec.scope.kind === "rule" && spec.scope.raw ? spec.scope.raw : spec.ref,
2404
+ // `record.ref` is a host-owned external handle. The framework
2405
+ // never writes to itrule identity is content-addressed via
2406
+ // `facetRule`, record identity via `record.id` (UUID).
2407
+ // `spec.ref` is always `undefined` from `useShellEditorTarget`
2408
+ // for the same reason; we keep the prop on the spec so hosts
2409
+ // that wire the editor session directly can still pass an
2410
+ // explicit external ref if they choose.
2411
+ ref: spec.ref,
2291
2412
  scope: anchors,
2292
2413
  data: persistedValue,
2293
2414
  facetRule: persistedFacetRule
@@ -2295,7 +2416,7 @@ var createEditorStore = () => {
2295
2416
  nextRecordId = created?.id ?? nextRecordId;
2296
2417
  } else {
2297
2418
  const upserted = await upsertRecord(ctx, {
2298
- ref: spec.isDraftScope ? spec.ref : spec.scope.kind === "rule" && spec.scope.raw ? spec.scope.raw : spec.ref,
2419
+ ref: spec.ref,
2299
2420
  scope: anchors,
2300
2421
  data: persistedValue,
2301
2422
  facetRule: persistedFacetRule
@@ -2706,7 +2827,14 @@ function useShellEditorTarget(args) {
2706
2827
  // would update the rule record in place.
2707
2828
  recordId: createMode ? void 0 : resolved.recordId,
2708
2829
  createMode,
2709
- ref: editingTargetScope.kind === "rule" && !isDraftScope ? editingTargetScope.raw : void 0,
2830
+ // We deliberately do NOT propagate `editingTargetScope.raw` (e.g.
2831
+ // `rule:<ulid>`) into `spec.ref`. `record.ref` is a host-owned
2832
+ // EXTERNAL HANDLE — this framework must remain agnostic about how
2833
+ // hosts use it. Rule identity is content-addressed via `facetRule`
2834
+ // (see `data/ruleHash`); record identity is the UUID `record.id`.
2835
+ // Whatever the SDK chooses to back-fill in `ref` is the SDK's
2836
+ // problem, not ours, and we never write to it.
2837
+ ref: void 0,
2710
2838
  initialFacetRule,
2711
2839
  label,
2712
2840
  draftKey,
@@ -2813,7 +2941,8 @@ var LABELS = {
2813
2941
  facet: "Shared",
2814
2942
  variant: "Variants",
2815
2943
  batch: "Batches",
2816
- rule: "Rules"
2944
+ rule: "Rules",
2945
+ all: "All"
2817
2946
  };
2818
2947
  var ScopeTabs = ({
2819
2948
  scopes,
@@ -2821,6 +2950,7 @@ var ScopeTabs = ({
2821
2950
  onChange,
2822
2951
  loading = false,
2823
2952
  counts,
2953
+ tooltips,
2824
2954
  icons
2825
2955
  }) => {
2826
2956
  const iconMap = icons ?? DEFAULT_ICONS.scope;
@@ -2828,6 +2958,7 @@ var ScopeTabs = ({
2828
2958
  const Icon = iconMap[s] ?? DEFAULT_ICONS.scope[s];
2829
2959
  const isActive = active === s;
2830
2960
  const count = counts?.[s];
2961
+ const tooltip = tooltips?.[s];
2831
2962
  return /* @__PURE__ */ jsxs(
2832
2963
  "button",
2833
2964
  {
@@ -2837,6 +2968,7 @@ var ScopeTabs = ({
2837
2968
  onClick: () => onChange(s),
2838
2969
  disabled: loading,
2839
2970
  className: "ra-tab",
2971
+ title: tooltip,
2840
2972
  children: [
2841
2973
  /* @__PURE__ */ jsx(Icon, { className: cn("ra-tab-icon w-3.5 h-3.5", loading && "animate-pulse") }),
2842
2974
  /* @__PURE__ */ jsx("span", { children: LABELS[s] }),
@@ -3069,6 +3201,15 @@ var DefaultRecordRow = ({ record, ctx, compact = false }) => {
3069
3201
  compact && /* @__PURE__ */ jsx(StatusIcon, { status: record.status, size: "0.85rem" }),
3070
3202
  !compact && record.scope.kind && record.scope.kind !== "collection" && /* @__PURE__ */ jsx("span", { className: "ra-row-scope", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ScopeIcon, { className: "w-3 h-3" }) }),
3071
3203
  record.badges?.slice(0, 1).map((b, i) => /* @__PURE__ */ jsx("span", { className: "ra-chip", "data-tone": "muted", children: b.label }, `${b.label}-${i}`)),
3204
+ record.facetRule && !compact && /* @__PURE__ */ jsx(
3205
+ "span",
3206
+ {
3207
+ className: "ra-row-rule-pip",
3208
+ title: ruleSummary ? `Targeted: ${ruleSummary}` : "This record has targeting rules",
3209
+ "aria-label": ruleSummary ? `Targeted: ${ruleSummary}` : "This record has targeting rules",
3210
+ children: /* @__PURE__ */ jsx(SlidersHorizontal, { className: "w-3 h-3", "aria-hidden": "true" })
3211
+ }
3212
+ ),
3072
3213
  hasError ? /* @__PURE__ */ jsx(
3073
3214
  "span",
3074
3215
  {
@@ -3117,22 +3258,24 @@ var RecordList = ({
3117
3258
  presentation = "list",
3118
3259
  renderListRow,
3119
3260
  groupBy,
3261
+ renderGroupActions,
3120
3262
  rowClipboard,
3121
3263
  i18n
3122
3264
  }) => {
3123
3265
  const buildCtx = (item) => {
3124
3266
  const cb = rowClipboard ? rowClipboard(item) : null;
3267
+ const itemAnchorKey = anchorKey(item.scope);
3125
3268
  const idMatch = selectedId != null && item.id != null && item.id === selectedId;
3126
- const anchorMatch = selectedAnchorKey != null && item.ref === selectedAnchorKey;
3269
+ const anchorMatch = selectedAnchorKey != null && !!itemAnchorKey && itemAnchorKey === selectedAnchorKey;
3127
3270
  const dirtyIdMatch = dirtyId != null && item.id != null && item.id === dirtyId;
3128
- const dirtyAnchorMatch = dirtyAnchorKey != null && item.ref === dirtyAnchorKey;
3271
+ const dirtyAnchorMatch = dirtyAnchorKey != null && !!itemAnchorKey && itemAnchorKey === dirtyAnchorKey;
3129
3272
  const idInStore = !!(item.id && dirtyKeys?.has(item.id));
3130
- const refInStore = !!(item.ref && dirtyKeys?.has(item.ref));
3131
- const errorInStore = !!(item.id && errorKeys?.has(item.id) || item.ref && errorKeys?.has(item.ref));
3273
+ const anchorInStore = !!(itemAnchorKey && dirtyKeys?.has(itemAnchorKey));
3274
+ const errorInStore = !!(item.id && errorKeys?.has(item.id) || itemAnchorKey && errorKeys?.has(itemAnchorKey));
3132
3275
  return {
3133
3276
  selected: idMatch || anchorMatch,
3134
3277
  onSelect: () => onSelect(item),
3135
- isDirty: dirtyIdMatch || dirtyAnchorMatch || idInStore || refInStore,
3278
+ isDirty: dirtyIdMatch || dirtyAnchorMatch || idInStore || anchorInStore,
3136
3279
  hasError: errorInStore,
3137
3280
  i18n,
3138
3281
  ...cb ?? {}
@@ -3154,19 +3297,28 @@ var RecordList = ({
3154
3297
  }, [items, groupBy]);
3155
3298
  const renderItems = (rows) => {
3156
3299
  const compact = presentation === "compact";
3157
- return /* @__PURE__ */ jsx("ul", { children: rows.map((item) => {
3300
+ return /* @__PURE__ */ jsx("ul", { children: rows.map((item, idx) => {
3158
3301
  const ctx = buildCtx(item);
3159
- return /* @__PURE__ */ jsx("li", { children: renderListRow ? renderListRow(item, ctx) : /* @__PURE__ */ jsx(DefaultRecordRow, { record: item, ctx, compact }) }, item.id ?? item.ref);
3302
+ const key = item.id ?? (anchorKey(item.scope) || `pos:${idx}`);
3303
+ return /* @__PURE__ */ jsx("li", { children: renderListRow ? renderListRow(item, ctx) : /* @__PURE__ */ jsx(DefaultRecordRow, { record: item, ctx, compact }) }, key);
3160
3304
  }) });
3161
3305
  };
3162
3306
  if (groups) {
3163
- return /* @__PURE__ */ jsx(GroupedList, { groups, renderItems });
3307
+ return /* @__PURE__ */ jsx(
3308
+ GroupedList,
3309
+ {
3310
+ groups,
3311
+ renderItems,
3312
+ renderGroupActions
3313
+ }
3314
+ );
3164
3315
  }
3165
3316
  return renderItems(items);
3166
3317
  };
3167
3318
  var GroupedList = ({
3168
3319
  groups,
3169
- renderItems
3320
+ renderItems,
3321
+ renderGroupActions
3170
3322
  }) => {
3171
3323
  const Chevron = DEFAULT_ICONS.group.chevron;
3172
3324
  const [open, setOpen] = useState(
@@ -3174,22 +3326,34 @@ var GroupedList = ({
3174
3326
  );
3175
3327
  return /* @__PURE__ */ jsx(Fragment, { children: groups.map((g) => {
3176
3328
  const isOpen = open[g.key] !== false;
3329
+ const actions = renderGroupActions?.({ key: g.key, label: g.label, items: g.items });
3177
3330
  return /* @__PURE__ */ jsxs("section", { className: "ra-group", "data-open": isOpen ? "true" : "false", children: [
3178
- /* @__PURE__ */ jsxs(
3179
- "button",
3180
- {
3181
- type: "button",
3182
- className: "ra-group-summary",
3183
- onClick: () => setOpen((s) => ({ ...s, [g.key]: !isOpen })),
3184
- "aria-expanded": isOpen,
3185
- children: [
3186
- /* @__PURE__ */ jsx(Chevron, { className: "ra-group-chevron w-3 h-3" }),
3187
- g.icon,
3188
- /* @__PURE__ */ jsx("span", { className: "ra-group-name", children: g.label }),
3189
- /* @__PURE__ */ jsx("span", { className: "ra-group-count", children: g.items.length })
3190
- ]
3191
- }
3192
- ),
3331
+ /* @__PURE__ */ jsxs("div", { className: "ra-group-summary-row flex items-center", children: [
3332
+ /* @__PURE__ */ jsxs(
3333
+ "button",
3334
+ {
3335
+ type: "button",
3336
+ className: "ra-group-summary flex-1 min-w-0",
3337
+ onClick: () => setOpen((s) => ({ ...s, [g.key]: !isOpen })),
3338
+ "aria-expanded": isOpen,
3339
+ children: [
3340
+ /* @__PURE__ */ jsx(Chevron, { className: "ra-group-chevron w-3 h-3" }),
3341
+ g.icon,
3342
+ /* @__PURE__ */ jsx("span", { className: "ra-group-name", children: g.label }),
3343
+ /* @__PURE__ */ jsx("span", { className: "ra-group-count", children: g.items.length })
3344
+ ]
3345
+ }
3346
+ ),
3347
+ actions ? /* @__PURE__ */ jsx(
3348
+ "div",
3349
+ {
3350
+ className: "ra-group-actions flex-shrink-0 pr-2",
3351
+ onClick: (e) => e.stopPropagation(),
3352
+ onMouseDown: (e) => e.stopPropagation(),
3353
+ children: actions
3354
+ }
3355
+ ) : null
3356
+ ] }),
3193
3357
  /* @__PURE__ */ jsx("div", { className: "ra-group-body", children: renderItems(g.items) })
3194
3358
  ] }, g.key);
3195
3359
  }) });
@@ -3442,7 +3606,7 @@ function useItemViewPref(args) {
3442
3606
  }, [key]);
3443
3607
  return [value, set];
3444
3608
  }
3445
- var QK_BASE2 = ["records-admin", "collection-items"];
3609
+ var QK_BASE3 = ["records-admin", "collection-items"];
3446
3610
  var canonicalFacetRule = (rule) => {
3447
3611
  if (!rule || !Array.isArray(rule.all)) return "";
3448
3612
  const clauses = rule.all.map((c) => ({
@@ -3475,23 +3639,24 @@ function useCollectionItems(args) {
3475
3639
  ctx,
3476
3640
  scope,
3477
3641
  facetRule,
3642
+ includeAll = false,
3478
3643
  pageSize = 100,
3479
3644
  toSummary: toSummary2 = defaultToSummary,
3480
3645
  enabled = true
3481
3646
  } = args;
3482
3647
  const queryClient = useQueryClient();
3483
- const scopeRef = scope?.raw ?? "";
3648
+ const scopeRef = includeAll ? "__all__" : scope?.raw ?? "";
3484
3649
  const ruleSignature = useMemo(
3485
- () => scope?.kind === "rule" ? canonicalFacetRule(facetRule) : "",
3486
- [scope?.kind, facetRule]
3650
+ () => !includeAll && scope?.kind === "rule" ? canonicalFacetRule(facetRule) : "",
3651
+ [includeAll, scope?.kind, facetRule]
3487
3652
  );
3488
3653
  const queryKey = useMemo(
3489
- () => [...QK_BASE2, ctx.collectionId, ctx.appId, ctx.recordType, scopeRef, ruleSignature],
3654
+ () => [...QK_BASE3, ctx.collectionId, ctx.appId, ctx.recordType, scopeRef, ruleSignature],
3490
3655
  [ctx.collectionId, ctx.appId, ctx.recordType, scopeRef, ruleSignature]
3491
3656
  );
3492
3657
  const query = useInfiniteQuery({
3493
3658
  queryKey,
3494
- enabled: enabled && !!scope,
3659
+ enabled: enabled && (includeAll || !!scope),
3495
3660
  initialPageParam: 0,
3496
3661
  queryFn: async ({ pageParam }) => {
3497
3662
  const offset = pageParam;
@@ -3505,12 +3670,13 @@ function useCollectionItems(args) {
3505
3670
  staleTime: 15e3
3506
3671
  });
3507
3672
  const items = useMemo(() => {
3508
- if (!scope) return [];
3673
+ if (!includeAll && !scope) return [];
3509
3674
  const all = query.data?.pages.flatMap((p) => p.data) ?? [];
3510
- const relevant = all.filter((rec) => recordMatchesScope(rec, scope, ruleSignature));
3675
+ const relevant = includeAll ? all : all.filter((rec) => recordMatchesScope(rec, scope, ruleSignature));
3511
3676
  return relevant.map((rec) => {
3512
3677
  const ref = rec.ref ?? "";
3513
3678
  const parsed = parseRef(ref);
3679
+ const recFacetRule = rec.facetRule ?? null;
3514
3680
  const stableItemId = rec.id;
3515
3681
  const base = {
3516
3682
  id: rec.id,
@@ -3520,11 +3686,12 @@ function useCollectionItems(args) {
3520
3686
  status: rec.data ? "configured" : "empty",
3521
3687
  label: stableItemId,
3522
3688
  updatedAt: rec.updatedAt,
3523
- itemId: stableItemId
3689
+ itemId: stableItemId,
3690
+ facetRule: recFacetRule
3524
3691
  };
3525
3692
  return toSummary2(rec, base);
3526
3693
  }).filter((x) => x !== null);
3527
- }, [query.data, toSummary2, scope, ruleSignature]);
3694
+ }, [query.data, toSummary2, scope, ruleSignature, includeAll]);
3528
3695
  const refetch = useCallback(() => {
3529
3696
  queryClient.invalidateQueries({ queryKey });
3530
3697
  }, [queryClient, queryKey]);
@@ -3700,7 +3867,7 @@ var createPostMessageDeepLinkAdapter = (paramNames) => {
3700
3867
 
3701
3868
  // src/components/RecordsAdmin/hooks/useDeepLinkState.ts
3702
3869
  var SMART_PUSH = ["record.open", "record.close", "scope"];
3703
- var classify = (mode, kind) => {
3870
+ var classify2 = (mode, kind) => {
3704
3871
  if (mode === "push") return "push";
3705
3872
  if (mode === "replace") return "replace";
3706
3873
  return SMART_PUSH.includes(kind) ? "push" : "replace";
@@ -3737,7 +3904,7 @@ function useDeepLinkState(options) {
3737
3904
  }, [adapter]);
3738
3905
  const emit = useCallback((partial, kind) => {
3739
3906
  if (!adapter) return;
3740
- const mode = classify(history, kind);
3907
+ const mode = classify2(history, kind);
3741
3908
  adapter.write(partial, mode);
3742
3909
  setUrlState((prev) => ({ ...prev, ...partial }));
3743
3910
  }, [adapter, history]);
@@ -3914,6 +4081,7 @@ function RecordEditor({
3914
4081
  children,
3915
4082
  preview,
3916
4083
  targeting,
4084
+ targetingControl,
3917
4085
  bulkActions,
3918
4086
  footerExtra,
3919
4087
  onBeforeDelete,
@@ -4005,7 +4173,8 @@ function RecordEditor({
4005
4173
  children: headerMeta
4006
4174
  }
4007
4175
  ),
4008
- bulkActions && /* @__PURE__ */ jsx(BulkActionsMenu, { ...bulkActions, i18n })
4176
+ bulkActions && /* @__PURE__ */ jsx(BulkActionsMenu, { ...bulkActions, i18n }),
4177
+ targetingControl
4009
4178
  ] })
4010
4179
  ]
4011
4180
  }
@@ -4114,7 +4283,7 @@ function RecordEditor({
4114
4283
  )
4115
4284
  ] });
4116
4285
  }
4117
- function summariseRule(clauses, facets) {
4286
+ function summariseRule2(clauses, facets) {
4118
4287
  let valueCount = 0;
4119
4288
  const parts = clauses.map((c) => {
4120
4289
  const facet = facets.find((f) => f.key === c.facetKey);
@@ -4127,20 +4296,36 @@ function summariseRule(clauses, facets) {
4127
4296
  });
4128
4297
  return { text: parts.join(" \xB7 "), clauseCount: clauses.length, valueCount };
4129
4298
  }
4130
- function RecordTargeting({ SL, collectionId, appId, ctx }) {
4299
+ function RecordTargeting({
4300
+ SL,
4301
+ collectionId,
4302
+ appId,
4303
+ ctx,
4304
+ forceOpen,
4305
+ onOpenChange
4306
+ }) {
4131
4307
  const facets = useFacets(collectionId, void 0);
4132
4308
  const preview = useRulePreview({ SL, collectionId, appId, rule: ctx.facetRule ?? null });
4133
4309
  const clauses = ctx.facetRule?.all ?? [];
4134
4310
  const hasAnyClause = clauses.length > 0;
4311
+ const hasInflightRule = ctx.facetRule != null;
4135
4312
  const isInvalid = ctx.canSave === false;
4136
- const shouldAutoOpen = !hasAnyClause || isInvalid;
4313
+ const shouldAutoOpen = hasInflightRule && (!hasAnyClause || isInvalid);
4137
4314
  const [open, setOpen] = useState(shouldAutoOpen);
4138
4315
  useEffect(() => {
4139
4316
  if (shouldAutoOpen) setOpen(true);
4140
4317
  }, [shouldAutoOpen]);
4141
- const summary = useMemo(() => summariseRule(clauses, facets), [clauses, facets]);
4318
+ useEffect(() => {
4319
+ if (forceOpen) setOpen(true);
4320
+ }, [forceOpen]);
4321
+ const summary = useMemo(() => summariseRule2(clauses, facets), [clauses, facets]);
4142
4322
  if (!ctx.onFacetRuleChange) return null;
4323
+ if (!hasInflightRule) return null;
4143
4324
  const headerTone = isInvalid ? { color: "hsl(var(--ra-danger, 0 70% 45%))" } : { color: "hsl(var(--ra-muted-text))" };
4325
+ const setOpenAndNotify = (next) => {
4326
+ setOpen(next);
4327
+ onOpenChange?.(next);
4328
+ };
4144
4329
  return /* @__PURE__ */ jsxs(
4145
4330
  "div",
4146
4331
  {
@@ -4150,12 +4335,12 @@ function RecordTargeting({ SL, collectionId, appId, ctx }) {
4150
4335
  background: "hsl(var(--ra-muted) / 0.3)"
4151
4336
  },
4152
4337
  children: [
4153
- /* @__PURE__ */ jsxs(
4338
+ /* @__PURE__ */ jsx("div", { className: "w-full flex items-center gap-2 px-3 py-2", children: /* @__PURE__ */ jsxs(
4154
4339
  "button",
4155
4340
  {
4156
4341
  type: "button",
4157
- onClick: () => setOpen((o) => !o),
4158
- className: "w-full flex items-center gap-2 px-3 py-2 text-left hover:bg-[hsl(var(--ra-muted)/0.5)] rounded-lg transition-colors",
4342
+ onClick: () => setOpenAndNotify(!open),
4343
+ className: "flex items-center gap-2 flex-1 min-w-0 text-left rounded transition-colors hover:bg-[hsl(var(--ra-muted)/0.5)] -mx-1 px-1 py-0.5",
4159
4344
  "aria-expanded": open,
4160
4345
  children: [
4161
4346
  open ? /* @__PURE__ */ jsx(ChevronDown, { className: "w-3.5 h-3.5 shrink-0", style: headerTone }) : /* @__PURE__ */ jsx(ChevronRight, { className: "w-3.5 h-3.5 shrink-0", style: headerTone }),
@@ -4173,7 +4358,7 @@ function RecordTargeting({ SL, collectionId, appId, ctx }) {
4173
4358
  {
4174
4359
  className: "text-xs truncate flex-1 min-w-0",
4175
4360
  style: { color: "hsl(var(--ra-text))" },
4176
- title: summary.text || void 0,
4361
+ title: hasAnyClause ? summary.text : void 0,
4177
4362
  children: hasAnyClause ? summary.text || `${summary.clauseCount} facet${summary.clauseCount === 1 ? "" : "s"}` : /* @__PURE__ */ jsx("span", { style: { color: "hsl(var(--ra-muted-text))", fontStyle: "italic" }, children: "No rule yet \u2014 pick a facet to begin" })
4178
4363
  }
4179
4364
  ),
@@ -4206,7 +4391,7 @@ function RecordTargeting({ SL, collectionId, appId, ctx }) {
4206
4391
  /* @__PURE__ */ jsx(Settings2, { className: "w-3.5 h-3.5 shrink-0", style: headerTone })
4207
4392
  ]
4208
4393
  }
4209
- ),
4394
+ ) }),
4210
4395
  open && /* @__PURE__ */ jsx(
4211
4396
  "div",
4212
4397
  {
@@ -4228,6 +4413,262 @@ function RecordTargeting({ SL, collectionId, appId, ctx }) {
4228
4413
  }
4229
4414
  );
4230
4415
  }
4416
+ function deepCloneRule(rule) {
4417
+ if (typeof structuredClone === "function") return structuredClone(rule);
4418
+ return JSON.parse(JSON.stringify(rule));
4419
+ }
4420
+ function TargetingPopover({
4421
+ ctx,
4422
+ catalogue,
4423
+ onCustomise,
4424
+ disabled
4425
+ }) {
4426
+ const [open, setOpen] = useState(false);
4427
+ const triggerRef = useRef(null);
4428
+ const [anchor, setAnchor] = useState(null);
4429
+ const currentHash = useMemo(() => ruleHash(ctx.facetRule), [ctx.facetRule]);
4430
+ const currentMatchesPreset = !!currentHash && catalogue.some((e) => e.hash === currentHash);
4431
+ const sharedCount = useMemo(() => {
4432
+ if (!currentHash) return 0;
4433
+ const entry = catalogue.find((e) => e.hash === currentHash);
4434
+ if (!entry) return 0;
4435
+ return Math.max(0, entry.count - 1);
4436
+ }, [catalogue, currentHash]);
4437
+ useEffect(() => {
4438
+ if (!open || !triggerRef.current) return;
4439
+ const r = triggerRef.current.getBoundingClientRect();
4440
+ setAnchor({ top: r.bottom + 6, right: window.innerWidth - r.right });
4441
+ }, [open]);
4442
+ useEffect(() => {
4443
+ if (!open) return;
4444
+ const onKey = (e) => {
4445
+ if (e.key === "Escape") {
4446
+ e.stopPropagation();
4447
+ setOpen(false);
4448
+ }
4449
+ };
4450
+ const onDown = (e) => {
4451
+ const t = e.target;
4452
+ if (!t) return;
4453
+ if (triggerRef.current?.contains(t)) return;
4454
+ const card = document.getElementById("ra-targeting-popover-card");
4455
+ if (card?.contains(t)) return;
4456
+ setOpen(false);
4457
+ };
4458
+ window.addEventListener("keydown", onKey, true);
4459
+ window.addEventListener("mousedown", onDown, true);
4460
+ return () => {
4461
+ window.removeEventListener("keydown", onKey, true);
4462
+ window.removeEventListener("mousedown", onDown, true);
4463
+ };
4464
+ }, [open]);
4465
+ if (disabled || !ctx.onFacetRuleChange) return null;
4466
+ const mode = !ctx.facetRule ? "global" : currentMatchesPreset ? "preset" : "custom";
4467
+ const onPickGlobal = () => {
4468
+ ctx.onFacetRuleChange?.(null);
4469
+ setOpen(false);
4470
+ };
4471
+ const onPickPreset = (entry) => {
4472
+ ctx.onFacetRuleChange?.(deepCloneRule(entry.rule));
4473
+ setOpen(false);
4474
+ };
4475
+ const onPickCustom = () => {
4476
+ if (!ctx.facetRule) ctx.onFacetRuleChange?.({ all: [] });
4477
+ onCustomise();
4478
+ setOpen(false);
4479
+ };
4480
+ const triggerLabel = mode === "global" ? "Global" : "Targeted";
4481
+ const TriggerIcon = mode === "global" ? Globe2 : Target;
4482
+ const popover = open && anchor ? createPortal(
4483
+ /* @__PURE__ */ jsxs(
4484
+ "div",
4485
+ {
4486
+ id: "ra-targeting-popover-card",
4487
+ role: "dialog",
4488
+ "aria-label": "Targeting",
4489
+ className: "ra-shell",
4490
+ style: {
4491
+ position: "fixed",
4492
+ top: anchor.top,
4493
+ right: anchor.right,
4494
+ zIndex: 60,
4495
+ width: 320,
4496
+ maxHeight: "min(70vh, 480px)",
4497
+ overflowY: "auto",
4498
+ background: "hsl(var(--ra-surface))",
4499
+ border: "1px solid hsl(var(--ra-border))",
4500
+ borderRadius: 8,
4501
+ boxShadow: "0 8px 24px hsl(0 0% 0% / 0.12)"
4502
+ },
4503
+ onMouseDown: (e) => e.stopPropagation(),
4504
+ onClick: (e) => e.stopPropagation(),
4505
+ children: [
4506
+ /* @__PURE__ */ jsx(
4507
+ "div",
4508
+ {
4509
+ className: "px-3 py-2 border-b text-[11px] uppercase tracking-wide font-medium",
4510
+ style: {
4511
+ borderColor: "hsl(var(--ra-border))",
4512
+ color: "hsl(var(--ra-muted-text))"
4513
+ },
4514
+ children: "Targeting"
4515
+ }
4516
+ ),
4517
+ sharedCount > 0 && /* @__PURE__ */ jsxs(
4518
+ "div",
4519
+ {
4520
+ className: "mx-3 mt-2 px-2 py-1.5 text-[11px] rounded flex items-start gap-1.5",
4521
+ style: {
4522
+ background: "hsl(var(--ra-muted) / 0.5)",
4523
+ color: "hsl(var(--ra-muted-text))"
4524
+ },
4525
+ children: [
4526
+ /* @__PURE__ */ jsx(Info, { className: "w-3 h-3 mt-0.5 shrink-0" }),
4527
+ /* @__PURE__ */ jsxs("span", { children: [
4528
+ /* @__PURE__ */ jsx("strong", { style: { color: "hsl(var(--ra-text))" }, children: sharedCount }),
4529
+ " ",
4530
+ "other record",
4531
+ sharedCount === 1 ? "" : "s",
4532
+ " share this rule. Changing it here only affects this record. To retarget all of them, use ",
4533
+ /* @__PURE__ */ jsx("em", { children: "Edit rule" }),
4534
+ " on the rail group."
4535
+ ] })
4536
+ ]
4537
+ }
4538
+ ),
4539
+ /* @__PURE__ */ jsx(
4540
+ Option,
4541
+ {
4542
+ active: mode === "global",
4543
+ onClick: onPickGlobal,
4544
+ icon: /* @__PURE__ */ jsx(Globe2, { className: "w-3.5 h-3.5" }),
4545
+ title: "Apply globally",
4546
+ subtitle: "Applies to every product in the collection."
4547
+ }
4548
+ ),
4549
+ catalogue.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
4550
+ /* @__PURE__ */ jsx(
4551
+ "div",
4552
+ {
4553
+ className: "px-3 pt-2 pb-1 text-[10px] uppercase tracking-wide",
4554
+ style: { color: "hsl(var(--ra-muted-text))" },
4555
+ children: "Existing rule sets"
4556
+ }
4557
+ ),
4558
+ catalogue.map((entry) => /* @__PURE__ */ jsx(
4559
+ Option,
4560
+ {
4561
+ active: mode === "preset" && currentHash === entry.hash,
4562
+ onClick: () => onPickPreset(entry),
4563
+ icon: /* @__PURE__ */ jsx(Target, { className: "w-3.5 h-3.5" }),
4564
+ title: entry.summary || "Untitled rule",
4565
+ subtitle: `${entry.count} record${entry.count === 1 ? "" : "s"}`
4566
+ },
4567
+ entry.hash
4568
+ ))
4569
+ ] }),
4570
+ /* @__PURE__ */ jsx(
4571
+ "div",
4572
+ {
4573
+ className: "border-t mt-1",
4574
+ style: { borderColor: "hsl(var(--ra-border) / 0.6)" }
4575
+ }
4576
+ ),
4577
+ /* @__PURE__ */ jsx(
4578
+ Option,
4579
+ {
4580
+ active: mode === "custom",
4581
+ onClick: onPickCustom,
4582
+ icon: /* @__PURE__ */ jsx(Pencil, { className: "w-3.5 h-3.5" }),
4583
+ title: mode === "custom" ? "Customise rule" : "Create a new rule",
4584
+ subtitle: "Open the rule editor below to author a bespoke rule."
4585
+ }
4586
+ )
4587
+ ]
4588
+ }
4589
+ ),
4590
+ document.body
4591
+ ) : null;
4592
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4593
+ /* @__PURE__ */ jsxs(
4594
+ "button",
4595
+ {
4596
+ ref: triggerRef,
4597
+ type: "button",
4598
+ onClick: () => setOpen((o) => !o),
4599
+ className: "text-[11px] inline-flex items-center gap-1 px-2 py-1 rounded-md border transition-colors hover:bg-[hsl(var(--ra-muted))]",
4600
+ style: {
4601
+ borderColor: "hsl(var(--ra-border))",
4602
+ color: "hsl(var(--ra-text))"
4603
+ },
4604
+ "aria-haspopup": "dialog",
4605
+ "aria-expanded": open,
4606
+ title: "Use rules to scope this record to a targeted audience",
4607
+ children: [
4608
+ /* @__PURE__ */ jsx(TriggerIcon, { className: "w-3 h-3" }),
4609
+ triggerLabel,
4610
+ mode === "preset" && /* @__PURE__ */ jsx(Check, { className: "w-3 h-3", style: { color: "hsl(var(--ra-muted-text))" } })
4611
+ ]
4612
+ }
4613
+ ),
4614
+ popover
4615
+ ] });
4616
+ }
4617
+ function Option({
4618
+ active,
4619
+ onClick,
4620
+ icon,
4621
+ title,
4622
+ subtitle
4623
+ }) {
4624
+ return /* @__PURE__ */ jsxs(
4625
+ "button",
4626
+ {
4627
+ type: "button",
4628
+ onClick,
4629
+ className: "w-full text-left px-3 py-2 flex items-start gap-2 transition-colors hover:bg-[hsl(var(--ra-muted)/0.6)]",
4630
+ style: {
4631
+ background: active ? "hsl(var(--ra-accent) / 0.08)" : void 0
4632
+ },
4633
+ children: [
4634
+ /* @__PURE__ */ jsx(
4635
+ "span",
4636
+ {
4637
+ className: "mt-0.5 shrink-0",
4638
+ style: { color: active ? "hsl(var(--ra-accent))" : "hsl(var(--ra-muted-text))" },
4639
+ children: icon
4640
+ }
4641
+ ),
4642
+ /* @__PURE__ */ jsxs("span", { className: "flex-1 min-w-0", children: [
4643
+ /* @__PURE__ */ jsx(
4644
+ "span",
4645
+ {
4646
+ className: "block text-xs font-medium truncate",
4647
+ style: { color: "hsl(var(--ra-text))" },
4648
+ title,
4649
+ children: title
4650
+ }
4651
+ ),
4652
+ subtitle && /* @__PURE__ */ jsx(
4653
+ "span",
4654
+ {
4655
+ className: "block text-[11px] truncate",
4656
+ style: { color: "hsl(var(--ra-muted-text))" },
4657
+ children: subtitle
4658
+ }
4659
+ )
4660
+ ] }),
4661
+ active && /* @__PURE__ */ jsx(
4662
+ Check,
4663
+ {
4664
+ className: "w-3.5 h-3.5 mt-0.5 shrink-0",
4665
+ style: { color: "hsl(var(--ra-accent))" }
4666
+ }
4667
+ )
4668
+ ]
4669
+ }
4670
+ );
4671
+ }
4231
4672
  var TAB_META = {
4232
4673
  product: { icon: Box, label: "Product" },
4233
4674
  variant: { icon: Layers, label: "Variants" },
@@ -4736,9 +5177,10 @@ function DefaultItemTable({
4736
5177
  }
4737
5178
  )
4738
5179
  ] }) }),
4739
- /* @__PURE__ */ jsx("tbody", { children: items.map((item) => {
5180
+ /* @__PURE__ */ jsx("tbody", { children: items.map((item, idx) => {
4740
5181
  const id = item.itemId ?? "";
4741
5182
  const isSelected = !!selectedId && selectedId === id;
5183
+ const key = item.id ?? (id || anchorKey(item.scope) || `pos:${idx}`);
4742
5184
  return /* @__PURE__ */ jsxs(
4743
5185
  "tr",
4744
5186
  {
@@ -4784,7 +5226,7 @@ function DefaultItemTable({
4784
5226
  ] })
4785
5227
  ]
4786
5228
  },
4787
- item.ref
5229
+ key
4788
5230
  );
4789
5231
  }) })
4790
5232
  ] }) });
@@ -4805,8 +5247,9 @@ function DefaultItemCards({
4805
5247
  {
4806
5248
  className: isGallery ? "ra-item-gallery" : "ra-item-cards",
4807
5249
  "data-card-size": cardSize,
4808
- children: items.map((item) => {
5250
+ children: items.map((item, idx) => {
4809
5251
  const id = item.itemId ?? "";
5252
+ const key = item.id ?? (id || anchorKey(item.scope) || `pos:${idx}`);
4810
5253
  const open = () => ctx.onOpen(id);
4811
5254
  const slotCtx = {
4812
5255
  ...ctx,
@@ -4822,7 +5265,7 @@ function DefaultItemCards({
4822
5265
  "data-selected": slotCtx.selected,
4823
5266
  children: renderCard(item, slotCtx)
4824
5267
  },
4825
- item.ref
5268
+ key
4826
5269
  );
4827
5270
  }
4828
5271
  return /* @__PURE__ */ jsxs(
@@ -4854,7 +5297,7 @@ function DefaultItemCards({
4854
5297
  )
4855
5298
  ]
4856
5299
  },
4857
- item.ref
5300
+ key
4858
5301
  );
4859
5302
  })
4860
5303
  }
@@ -5051,11 +5494,13 @@ function SiblingRail({
5051
5494
  isLoading && /* @__PURE__ */ jsx(LoadingState, {}),
5052
5495
  !isLoading && error && /* @__PURE__ */ jsx(ErrorState, { error }),
5053
5496
  !isLoading && !error && items.length === 0 && /* @__PURE__ */ jsx(EmptyState, { title: i18n.noItemsTitle, body: i18n.noItemsBody }),
5054
- !isLoading && !error && items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "ra-sibling-list", children: items.map((item) => {
5497
+ !isLoading && !error && items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "ra-sibling-list", children: items.map((item, idx) => {
5055
5498
  const id = item.itemId ?? "";
5499
+ const key = item.id ?? (id || anchorKey(item.scope) || `pos:${idx}`);
5500
+ const akey = anchorKey(item.scope);
5056
5501
  const selected = selectedItemId === id;
5057
- const isDirty = !!(id && dirtyKeys?.has(id) || item.ref && dirtyKeys?.has(item.ref));
5058
- const hasError = !!(id && errorKeys?.has(id) || item.ref && errorKeys?.has(item.ref));
5502
+ const isDirty = !!(id && dirtyKeys?.has(id) || akey && dirtyKeys?.has(akey));
5503
+ const hasError = !!(id && errorKeys?.has(id) || akey && errorKeys?.has(akey));
5059
5504
  return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
5060
5505
  "button",
5061
5506
  {
@@ -5085,7 +5530,7 @@ function SiblingRail({
5085
5530
  ) : null
5086
5531
  ]
5087
5532
  }
5088
- ) }, item.ref);
5533
+ ) }, key);
5089
5534
  }) })
5090
5535
  ] })
5091
5536
  ] });
@@ -5143,9 +5588,6 @@ var UtilityRow = ({ label, customLabel, introHidden, onShowIntro }) => {
5143
5588
  }
5144
5589
  );
5145
5590
  };
5146
-
5147
- // src/components/AdminPageHeader/admin-page-header.css
5148
- styleInject('.sl-aph {\n width: 100%;\n font-family: var(--ra-font-ui, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);\n color: hsl(var(--ra-text, 222 47% 11%));\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.sl-aph *,\n.sl-aph *::before,\n.sl-aph *::after {\n box-sizing: border-box;\n}\n.sl-aph__row {\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: 0.6rem;\n padding: 0.25rem 0.25rem 0.5rem;\n}\n.sl-aph__main {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: flex-start;\n gap: 0.55rem;\n}\n.sl-aph__aside {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n.sl-aph__text {\n flex: 1;\n min-width: 0;\n}\n.sl-aph__title {\n font-family: var(--ra-font-display, var(--ra-font-ui, ui-sans-serif, system-ui, sans-serif));\n font-weight: 700;\n font-size: 1.2rem;\n line-height: 1.2;\n color: hsl(var(--ra-text, 222 47% 11%));\n letter-spacing: -0.015em;\n margin: 0;\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n}\n.sl-aph__icon {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: flex-start;\n background: transparent;\n color: hsl(var(--ra-text, 222 47% 11%));\n border: 0;\n padding: 0;\n}\n.sl-aph__icon > svg {\n width: 1.05rem;\n height: 1.05rem;\n}\n.sl-aph__subtitle {\n font-size: 0.78rem;\n color: hsl(var(--ra-muted-text, 220 9% 46%));\n margin: 0.1rem 0 0;\n line-height: 1.3;\n}\n.sl-aph__icon-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 2rem;\n height: 2rem;\n padding: 0;\n border-radius: 999px;\n background: transparent;\n border: 1px solid transparent;\n color: hsl(var(--ra-muted-text, 220 9% 46%));\n cursor: pointer;\n transition:\n background .15s ease,\n color .15s ease,\n border-color .15s ease;\n text-decoration: none;\n}\n.sl-aph__icon-btn:hover {\n background: hsl(var(--ra-text, 222 47% 11%) / 0.06);\n color: hsl(var(--ra-text, 222 47% 11%));\n}\n.sl-aph__icon-btn:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring, hsl(222 47% 11% / 0.35));\n}\n.sl-aph__icon-btn > svg {\n width: 1rem;\n height: 1rem;\n}\n.sl-aph__intro {\n position: relative;\n display: flex;\n align-items: center;\n gap: 0.55rem;\n padding: 0.4rem 2rem 0.4rem 0.5rem;\n border-radius: var(--ra-radius, 0.625rem);\n border: 1px solid hsl(var(--ra-info, 214 95% 55%) / 0.30);\n background: hsl(var(--ra-info, 214 95% 55%) / 0.08);\n}\n.sl-aph__intro[data-tone=success] {\n border-color: hsl(var(--ra-success, 142 71% 45%) / 0.30);\n background: hsl(var(--ra-success, 142 71% 45%) / 0.08);\n}\n.sl-aph__intro[data-tone=warning] {\n border-color: hsl(var(--ra-warning, 38 92% 50%) / 0.35);\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.10);\n}\n.sl-aph__intro-icon {\n flex-shrink: 0;\n width: 1.5rem;\n height: 1.5rem;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: hsl(var(--ra-info, 214 95% 55%) / 0.18);\n color: hsl(var(--ra-info, 214 95% 55%));\n}\n.sl-aph__intro[data-tone=success] .sl-aph__intro-icon {\n background: hsl(var(--ra-success, 142 71% 45%) / 0.18);\n color: hsl(var(--ra-success, 142 71% 45%));\n}\n.sl-aph__intro[data-tone=warning] .sl-aph__intro-icon {\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.20);\n color: hsl(var(--ra-warning, 38 92% 50%));\n}\n.sl-aph__intro-body {\n flex: 1;\n min-width: 0;\n}\n.sl-aph__intro-title {\n font-family: var(--ra-font-display, var(--ra-font-ui, ui-sans-serif, system-ui, sans-serif));\n font-weight: var(--ra-title-weight, 600);\n font-size: 0.8rem;\n color: hsl(var(--ra-text, 222 47% 11%));\n margin: 0;\n line-height: 1.2;\n display: inline;\n}\n.sl-aph__intro-text {\n font-size: 0.78rem;\n color: hsl(var(--ra-text, 222 47% 11%) / 0.85);\n line-height: 1.35;\n display: inline;\n margin-left: 0.4rem;\n}\n.sl-aph__intro-action {\n margin-left: 0.375rem;\n}\n.sl-aph__intro-dismiss {\n position: absolute;\n top: 50%;\n right: 0.35rem;\n transform: translateY(-50%);\n width: 1.4rem;\n height: 1.4rem;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 0;\n color: hsl(var(--ra-muted-text, 220 9% 46%));\n cursor: pointer;\n padding: 0;\n}\n.sl-aph__intro-dismiss:hover {\n background: hsl(var(--ra-text, 222 47% 11%) / 0.06);\n color: hsl(var(--ra-text, 222 47% 11%));\n}\n');
5149
5591
  var TONE_ICON2 = {
5150
5592
  info: Lightbulb,
5151
5593
  success: CheckCircle2,
@@ -5496,6 +5938,183 @@ function WizardFooter({
5496
5938
  }
5497
5939
  );
5498
5940
  }
5941
+ var RuleGroupEditDialog = ({
5942
+ open,
5943
+ ctx,
5944
+ currentRule,
5945
+ recordIds,
5946
+ onClose,
5947
+ onApplied
5948
+ }) => {
5949
+ const [draftRule, setDraftRule] = useState(currentRule);
5950
+ const [isApplying, setIsApplying] = useState(false);
5951
+ const [error, setError] = useState(null);
5952
+ const [completed, setCompleted] = useState(() => /* @__PURE__ */ new Set());
5953
+ const cancelledRef = useRef(false);
5954
+ const facets = useFacets(ctx.collectionId, void 0);
5955
+ useEffect(() => {
5956
+ if (!open) return;
5957
+ setDraftRule(currentRule);
5958
+ setError(null);
5959
+ setCompleted(/* @__PURE__ */ new Set());
5960
+ cancelledRef.current = false;
5961
+ }, [open, currentRule]);
5962
+ useEffect(() => {
5963
+ if (!open) return;
5964
+ const prev = document.body.style.overflow;
5965
+ document.body.style.overflow = "hidden";
5966
+ const onKey = (e) => {
5967
+ if (e.key === "Escape" && !isApplying) {
5968
+ e.stopPropagation();
5969
+ onClose();
5970
+ }
5971
+ };
5972
+ window.addEventListener("keydown", onKey, true);
5973
+ return () => {
5974
+ window.removeEventListener("keydown", onKey, true);
5975
+ document.body.style.overflow = prev;
5976
+ };
5977
+ }, [open, isApplying, onClose]);
5978
+ const isUnchanged = useMemo(
5979
+ () => rulesEqual(draftRule, currentRule),
5980
+ [draftRule, currentRule]
5981
+ );
5982
+ const remaining = useMemo(
5983
+ () => recordIds.filter((id) => !completed.has(id)),
5984
+ [recordIds, completed]
5985
+ );
5986
+ const apply = async () => {
5987
+ if (isUnchanged) {
5988
+ onClose();
5989
+ return;
5990
+ }
5991
+ setIsApplying(true);
5992
+ setError(null);
5993
+ cancelledRef.current = false;
5994
+ try {
5995
+ for (const id of remaining) {
5996
+ if (cancelledRef.current) break;
5997
+ await updateRecord(ctx, id, { facetRule: draftRule });
5998
+ setCompleted((prev) => {
5999
+ const next = new Set(prev);
6000
+ next.add(id);
6001
+ return next;
6002
+ });
6003
+ }
6004
+ if (!cancelledRef.current) {
6005
+ onApplied?.(draftRule);
6006
+ onClose();
6007
+ }
6008
+ } catch (err) {
6009
+ setError(err instanceof Error ? err.message : String(err));
6010
+ } finally {
6011
+ setIsApplying(false);
6012
+ }
6013
+ };
6014
+ if (!open || typeof document === "undefined") return null;
6015
+ const total = recordIds.length;
6016
+ const done = completed.size;
6017
+ return createPortal(
6018
+ /* @__PURE__ */ jsxs(
6019
+ "div",
6020
+ {
6021
+ className: "ra-shell ra-confirm-root",
6022
+ role: "presentation",
6023
+ onMouseDown: (e) => e.stopPropagation(),
6024
+ onClick: (e) => e.stopPropagation(),
6025
+ children: [
6026
+ /* @__PURE__ */ jsx("div", { className: "ra-confirm-backdrop", onClick: () => !isApplying && onClose() }),
6027
+ /* @__PURE__ */ jsxs(
6028
+ "div",
6029
+ {
6030
+ role: "dialog",
6031
+ "aria-modal": "true",
6032
+ "aria-labelledby": "ra-rule-bulk-title",
6033
+ className: "ra-confirm-card",
6034
+ style: { maxWidth: 560, width: "92vw" },
6035
+ children: [
6036
+ /* @__PURE__ */ jsxs("div", { className: "ra-confirm-header", style: { justifyContent: "space-between" }, children: [
6037
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
6038
+ /* @__PURE__ */ jsx("span", { className: "ra-confirm-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(AlertTriangle, { className: "w-4 h-4" }) }),
6039
+ /* @__PURE__ */ jsxs("h2", { id: "ra-rule-bulk-title", className: "ra-confirm-title", children: [
6040
+ "Edit rule for ",
6041
+ total,
6042
+ " record",
6043
+ total === 1 ? "" : "s"
6044
+ ] })
6045
+ ] }),
6046
+ /* @__PURE__ */ jsx(
6047
+ "button",
6048
+ {
6049
+ type: "button",
6050
+ className: "ra-confirm-btn ra-confirm-btn-ghost",
6051
+ style: { padding: 4 },
6052
+ onClick: () => !isApplying && onClose(),
6053
+ "aria-label": "Close",
6054
+ children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
6055
+ }
6056
+ )
6057
+ ] }),
6058
+ /* @__PURE__ */ jsxs("div", { className: "px-4 pb-2 text-xs", style: { color: "hsl(var(--ra-muted-text))" }, children: [
6059
+ "Currently targeting:",
6060
+ " ",
6061
+ /* @__PURE__ */ jsx("span", { style: { color: "hsl(var(--ra-text))" }, children: summariseRule(currentRule) })
6062
+ ] }),
6063
+ /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: /* @__PURE__ */ jsx(
6064
+ FacetRuleEditor,
6065
+ {
6066
+ value: draftRule,
6067
+ onChange: setDraftRule,
6068
+ collectionId: ctx.collectionId,
6069
+ description: "The new rule will be applied to every record in this group."
6070
+ }
6071
+ ) }),
6072
+ error && /* @__PURE__ */ jsxs(
6073
+ "div",
6074
+ {
6075
+ className: "mx-4 mb-2 px-3 py-2 text-xs rounded",
6076
+ style: {
6077
+ background: "hsl(var(--ra-danger-soft, var(--destructive) / 0.12))",
6078
+ color: "hsl(var(--ra-danger, var(--destructive)))"
6079
+ },
6080
+ role: "alert",
6081
+ children: [
6082
+ error,
6083
+ done > 0 && ` Applied to ${done} of ${total} so far.`
6084
+ ]
6085
+ }
6086
+ ),
6087
+ /* @__PURE__ */ jsxs("div", { className: "ra-confirm-actions", children: [
6088
+ /* @__PURE__ */ jsx(
6089
+ "button",
6090
+ {
6091
+ type: "button",
6092
+ className: "ra-confirm-btn ra-confirm-btn-ghost",
6093
+ onClick: onClose,
6094
+ disabled: isApplying,
6095
+ children: "Cancel"
6096
+ }
6097
+ ),
6098
+ /* @__PURE__ */ jsx(
6099
+ "button",
6100
+ {
6101
+ type: "button",
6102
+ className: "ra-confirm-btn ra-confirm-btn-primary",
6103
+ onClick: apply,
6104
+ disabled: isApplying || isUnchanged || !facets.length,
6105
+ children: isApplying ? `Applying ${done + 1} of ${total}\u2026` : error ? `Retry (${remaining.length} left)` : `Apply to all ${total}`
6106
+ }
6107
+ )
6108
+ ] })
6109
+ ]
6110
+ }
6111
+ )
6112
+ ]
6113
+ }
6114
+ ),
6115
+ document.body
6116
+ );
6117
+ };
5499
6118
  var statusDot = (status) => {
5500
6119
  switch (status) {
5501
6120
  case "saving":
@@ -6063,12 +6682,6 @@ function useShellCsvBulk(args) {
6063
6682
  [csvSchema, handleImport, handleExport]
6064
6683
  );
6065
6684
  }
6066
-
6067
- // src/components/RecordsAdmin/shell/tokens.css
6068
- styleInject(':root {\n --ra-status-own: var(--ra-emerald, 142 71% 45%);\n --ra-status-shared: var(--ra-amber, 38 92% 50%);\n --ra-status-missing: var(--muted-foreground, 220 9% 46%);\n --ra-accent: var(--primary, 222 47% 11%);\n --ra-surface: var(--card, 0 0% 100%);\n --ra-border: var(--border, 220 13% 91%);\n --ra-text: var(--foreground, 222 47% 11%);\n --ra-muted: var(--muted, 220 14% 96%);\n --ra-muted-text: var(--muted-foreground, 220 9% 46%);\n --ra-radius: var(--radius, 0.625rem);\n --ra-dot-size: 0.5rem;\n --ra-page-bg: var(--background, 220 14% 98%);\n --ra-card-shadow: 0 1px 2px hsl(var(--ra-accent) / 0.04), 0 4px 12px hsl(var(--ra-accent) / 0.05);\n --ra-card-shadow-hover: 0 2px 4px hsl(var(--ra-accent) / 0.06), 0 8px 24px hsl(var(--ra-accent) / 0.08);\n --ra-row-hover: hsl(var(--ra-accent) / 0.05);\n --ra-row-active-bg: hsl(var(--ra-accent) / 0.10);\n --ra-row-active-bd: hsl(var(--ra-accent) / 0.45);\n --ra-focus-ring: hsl(var(--ra-accent) / 0.35);\n --ra-font-display: var(--font-display, var(--font-sans, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif));\n --ra-font-ui: var(--font-sans, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);\n --ra-title-weight: 600;\n --ra-display-weight: 700;\n --ra-info: var(--ra-blue, 214 95% 55%);\n --ra-success: var(--ra-emerald, 142 71% 45%);\n --ra-warning: var(--ra-amber, 38 92% 50%);\n --ra-danger: var(--destructive, 0 72% 51%);\n}\n:root {\n --sl-control-bg: var(--muted, 220 14% 96%);\n --sl-control-fg: var(--muted-foreground, 220 9% 40%);\n --sl-control-border: var(--border, 220 13% 88%);\n --sl-control-active-bg: var(--primary, 222 47% 11%);\n --sl-control-active-fg: var(--primary-foreground, 0 0% 100%);\n --sl-control-active-bd: var(--primary, 222 47% 11%);\n --sl-control-hover-bg: var(--sl-control-active-bg, 222 47% 11%);\n --sl-control-hover-fg: var(--foreground, 222 47% 11%);\n --sl-control-focus-ring: var(--sl-control-active-bg, 222 47% 11%);\n --sl-control-radius: var(--radius, 0.5rem);\n --sl-control-weight: 500;\n --sl-control-active-weight: 600;\n}\n.ra-status-dot {\n display: inline-block;\n width: var(--ra-dot-size);\n height: var(--ra-dot-size);\n border-radius: 9999px;\n flex-shrink: 0;\n}\n.ra-status-own {\n background: hsl(var(--ra-status-own));\n}\n.ra-status-shared {\n background: hsl(var(--ra-status-shared));\n}\n.ra-status-missing {\n background: hsl(var(--ra-status-missing) / 0.4);\n border: 1px solid hsl(var(--ra-status-missing) / 0.6);\n}\n.ra-row-active {\n background: var(--ra-row-active-bg);\n border-color: var(--ra-row-active-bd) !important;\n}\n');
6069
-
6070
- // src/components/RecordsAdmin/shell/shell.css
6071
- styleInject(".ra-shell {\n color: hsl(var(--ra-text));\n background: hsl(var(--ra-page-bg));\n font-family: var(--ra-font-ui);\n}\n.ra-shell *,\n.ra-shell *::before,\n.ra-shell *::after {\n box-sizing: border-box;\n}\n.ra-shell .ra-card {\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: var(--ra-card-shadow);\n}\n.ra-shell .ra-card-hover {\n transition:\n box-shadow .18s ease,\n transform .18s ease,\n border-color .18s ease;\n}\n.ra-shell .ra-card-hover:hover {\n box-shadow: var(--ra-card-shadow-hover);\n}\n.ra-shell .ra-display {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n letter-spacing: -0.01em;\n}\n.ra-shell .ra-title {\n font-weight: var(--ra-title-weight);\n}\n.ra-shell :where(button, [role=button], input, select, textarea, a):focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring);\n border-radius: calc(var(--ra-radius) * 0.6);\n}\n.ra-shell .ra-header {\n display: block;\n width: 100%;\n}\n.ra-shell .ra-header__main {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: flex-start;\n gap: 0.55rem;\n}\n.ra-shell .ra-header-aside {\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-header-icon {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: flex-start;\n background: transparent;\n color: hsl(var(--ra-text));\n border: 0;\n padding: 0;\n margin-top: 0.1rem;\n}\n.ra-shell .ra-header-icon > svg {\n width: 1.05rem;\n height: 1.05rem;\n}\n.ra-shell .ra-header-text {\n flex: 1;\n min-width: 0;\n}\n.ra-shell .ra-header-title {\n font-family: var(--ra-font-display);\n font-weight: 700;\n font-size: 1.2rem;\n line-height: 1.2;\n color: hsl(var(--ra-text));\n letter-spacing: -0.015em;\n margin: 0;\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n}\n.ra-shell .ra-header-subtitle {\n font-size: 0.78rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.1rem;\n line-height: 1.3;\n}\n.ra-shell .ra-header-stats {\n display: flex;\n align-items: stretch;\n gap: 0.15rem;\n padding: 0.15rem 0.4rem;\n border-radius: calc(var(--ra-radius) * 0.75);\n background: hsl(var(--ra-surface) / 0.7);\n border: 1px solid hsl(var(--ra-border));\n}\n.ra-shell .ra-header-stats--titled {\n flex-direction: column;\n align-items: stretch;\n padding: 0.4rem 0.55rem;\n gap: 0.3rem;\n}\n.ra-shell .ra-header-stats .ra-stats-items {\n display: flex;\n align-items: stretch;\n gap: 0.15rem;\n}\n.ra-shell .ra-header-stats .ra-stats-heading {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n color: hsl(var(--ra-muted-text));\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n.ra-shell .ra-header-stats .ra-stats-heading-icon {\n display: inline-flex;\n align-items: center;\n color: hsl(var(--ra-text));\n opacity: 0.75;\n}\n.ra-shell .ra-header-stats .ra-stats-heading-icon > svg {\n width: 0.85rem;\n height: 0.85rem;\n}\n.ra-shell .ra-stat {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 0.15rem 0.45rem;\n min-width: 2.5rem;\n}\n.ra-shell .ra-stat-value {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n font-size: 0.85rem;\n color: hsl(var(--ra-text));\n line-height: 1;\n}\n.ra-shell .ra-stat-label {\n font-size: 0.6rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.15rem;\n}\n.ra-shell .ra-stat-divider {\n width: 1px;\n background: hsl(var(--ra-border));\n margin: 0.25rem 0;\n}\n.ra-shell .ra-header-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n.ra-shell .ra-tabs {\n display: flex;\n gap: 0.25rem;\n padding: 0.25rem;\n background: hsl(var(--sl-control-bg));\n border-radius: var(--sl-control-radius);\n border: 1px solid hsl(var(--sl-control-border));\n}\n.ra-shell .ra-tab {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.4rem 0.7rem;\n border-radius: calc(var(--sl-control-radius) - 2px);\n font-size: 0.78rem;\n font-weight: var(--sl-control-weight);\n color: hsl(var(--sl-control-fg));\n background: transparent;\n border: 0;\n cursor: pointer;\n transition:\n background .15s ease,\n color .15s ease,\n transform .15s ease;\n white-space: nowrap;\n}\n.ra-shell .ra-tab:hover {\n background: hsl(var(--sl-control-hover-bg) / 0.10);\n color: hsl(var(--sl-control-hover-fg));\n}\n.ra-shell .ra-tab:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px hsl(var(--sl-control-focus-ring) / 0.45);\n}\n.ra-shell .ra-tab[aria-selected=true] {\n background: hsl(var(--sl-control-active-bg));\n color: hsl(var(--sl-control-active-fg));\n border-color: hsl(var(--sl-control-active-bd));\n font-weight: var(--sl-control-active-weight);\n box-shadow: 0 1px 2px hsl(var(--sl-control-active-bg) / 0.25);\n}\n.ra-shell .ra-tab[aria-selected=true]:hover {\n background: hsl(var(--sl-control-active-bg) / 0.92);\n color: hsl(var(--sl-control-active-fg));\n}\n.ra-shell .ra-tab[aria-selected=true] .ra-tab-icon {\n color: hsl(var(--sl-control-active-fg));\n}\n.ra-shell .ra-tab[disabled] {\n opacity: .5;\n cursor: not-allowed;\n}\n.ra-shell .ra-tab-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 1.25rem;\n padding: 0 0.35rem;\n height: 1.1rem;\n border-radius: 999px;\n background: hsl(var(--sl-control-active-fg) / 0.20);\n color: hsl(var(--sl-control-active-fg));\n font-size: 0.625rem;\n font-weight: 600;\n line-height: 1;\n}\n.ra-shell .ra-tab[aria-selected=false] .ra-tab-count {\n background: hsl(var(--sl-control-fg) / 0.15);\n color: hsl(var(--sl-control-fg));\n}\n.ra-shell[data-density=compact] .ra-row {\n padding-block: 0.4rem;\n}\n.ra-shell .ra-row {\n display: flex;\n align-items: center;\n gap: 0.55rem;\n width: 100%;\n text-align: left;\n padding: 0.45rem 0.75rem;\n border-left: 3px solid transparent;\n background: transparent;\n border-bottom: 1px solid transparent;\n transition: background .12s ease, border-color .12s ease;\n cursor: pointer;\n color: hsl(var(--ra-text));\n font-family: inherit;\n}\n.ra-shell .ra-row + .ra-row {\n border-top: 1px solid hsl(var(--ra-border) / 0.6);\n}\n.ra-shell .ra-row:hover {\n background: var(--ra-row-hover);\n}\n.ra-shell .ra-row[data-selected=true] {\n background: var(--ra-row-active-bg);\n border-left-color: var(--ra-row-active-bd);\n}\n.ra-shell .ra-row-compact {\n padding-block: 0.3rem;\n}\n.ra-shell .ra-row-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.5rem;\n height: 1.5rem;\n border-radius: calc(var(--ra-radius) * 0.6);\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n flex-shrink: 0;\n}\n.ra-shell .ra-row[data-selected=true] .ra-row-icon {\n background: hsl(var(--ra-accent) / 0.15);\n color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-row-body {\n flex: 1;\n min-width: 0;\n}\n.ra-shell .ra-row-title {\n font-weight: var(--ra-title-weight);\n font-size: 0.8125rem;\n line-height: 1.2;\n color: hsl(var(--ra-text));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-row-sub {\n font-size: 0.6875rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.05rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-row-rule-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 0.2rem;\n margin-top: 0.2rem;\n}\n.ra-shell .ra-rule-chip {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n padding: 0.05rem 0.4rem;\n border-radius: 999px;\n font-size: 0.625rem;\n font-weight: 500;\n line-height: 1.4;\n background: hsl(var(--ra-accent) / 0.10);\n color: hsl(var(--ra-accent));\n border: 1px solid hsl(var(--ra-accent) / 0.20);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-rule-chip-more {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.ra-shell[data-density=compact] .ra-row-rule-chips {\n margin-top: 0.15rem;\n gap: 0.15rem;\n}\n.ra-shell[data-density=compact] .ra-rule-chip {\n font-size: 0.6rem;\n padding: 0.02rem 0.35rem;\n}\n.ra-shell .ra-rule-filters {\n display: flex;\n flex-direction: column;\n gap: 0.3rem;\n}\n.ra-shell .ra-rule-filters-row {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n}\n.ra-shell .ra-rule-filter-chip {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.15rem 0.5rem;\n border-radius: 999px;\n font-size: 0.65rem;\n font-weight: 500;\n line-height: 1.4;\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n border: 1px solid hsl(var(--ra-border));\n cursor: pointer;\n transition:\n background .12s ease,\n color .12s ease,\n border-color .12s ease;\n max-width: 100%;\n}\n.ra-shell .ra-rule-filter-chip:hover {\n background: hsl(var(--ra-accent) / 0.10);\n color: hsl(var(--ra-text));\n border-color: hsl(var(--ra-accent) / 0.25);\n}\n.ra-shell .ra-rule-filter-chip[data-active=true] {\n background: hsl(var(--ra-accent) / 0.15);\n color: hsl(var(--ra-accent));\n border-color: hsl(var(--ra-accent) / 0.40);\n}\n.ra-shell .ra-rule-filter-chip[data-tone=complexity][data-active=true] {\n background: hsl(var(--ra-info) / 0.15);\n color: hsl(var(--ra-info));\n border-color: hsl(var(--ra-info) / 0.40);\n}\n.ra-shell .ra-rule-filter-chip-label {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 9rem;\n}\n.ra-shell .ra-rule-filter-chip-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 1.1rem;\n height: 1rem;\n padding: 0 0.3rem;\n border-radius: 999px;\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-muted-text));\n font-size: 0.6rem;\n font-weight: 600;\n}\n.ra-shell .ra-rule-filter-chip[data-active=true] .ra-rule-filter-chip-count {\n background: hsl(var(--ra-accent) / 0.18);\n color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-rule-filter-clear {\n align-self: flex-start;\n background: transparent;\n border: 0;\n padding: 0;\n color: hsl(var(--ra-muted-text));\n font-size: 0.65rem;\n cursor: pointer;\n text-decoration: underline;\n text-decoration-style: dotted;\n}\n.ra-shell .ra-rule-filter-clear:hover {\n color: hsl(var(--ra-text));\n}\n.ra-shell[data-density=compact] .ra-row {\n padding-block: 0.3rem;\n gap: 0.45rem;\n}\n.ra-shell[data-density=compact] .ra-row-title {\n font-size: 0.78125rem;\n}\n.ra-shell .ra-row-actions {\n display: inline-flex;\n align-items: center;\n gap: 0.15rem;\n margin-left: auto;\n opacity: 0;\n transition: opacity .15s ease;\n}\n.ra-shell .ra-row:hover .ra-row-actions,\n.ra-shell .ra-row:focus-within .ra-row-actions {\n opacity: 1;\n}\n.ra-shell .ra-row-action {\n width: 1.6rem;\n height: 1.6rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 999px;\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border: 0;\n cursor: pointer;\n transition: background .15s ease, color .15s ease;\n}\n.ra-shell .ra-row-action:hover {\n background: hsl(var(--ra-accent) / 0.10);\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-row-action[data-tone=danger]:hover {\n background: hsl(var(--ra-danger) / 0.12);\n color: hsl(var(--ra-danger));\n}\n.ra-shell .ra-chip {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.15rem 0.5rem;\n border-radius: 999px;\n font-size: 0.6875rem;\n font-weight: 500;\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n border: 1px solid hsl(var(--ra-border));\n white-space: nowrap;\n max-width: 14rem;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-chip[data-tone=success] {\n background: hsl(var(--ra-success) / 0.12);\n color: hsl(var(--ra-success));\n border-color: hsl(var(--ra-success) / 0.30);\n}\n.ra-shell .ra-chip[data-tone=warning] {\n background: hsl(var(--ra-warning) / 0.14);\n color: hsl(var(--ra-warning));\n border-color: hsl(var(--ra-warning) / 0.35);\n}\n.ra-shell .ra-chip[data-tone=info] {\n background: hsl(var(--ra-info) / 0.10);\n color: hsl(var(--ra-info));\n border-color: hsl(var(--ra-info) / 0.30);\n}\n.ra-shell .ra-chip[data-tone=danger] {\n background: hsl(var(--ra-danger) / 0.10);\n color: hsl(var(--ra-danger));\n border-color: hsl(var(--ra-danger) / 0.30);\n}\n.ra-shell .ra-chip[data-tone=muted] {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-style: dashed;\n}\n.ra-shell .ra-group {\n border-bottom: 1px solid hsl(var(--ra-border));\n}\n.ra-shell .ra-group:last-child {\n border-bottom: 0;\n}\n.ra-shell .ra-group-summary {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n width: 100%;\n padding: 0.5rem 0.85rem;\n background: hsl(var(--ra-muted) / 0.6);\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: hsl(var(--ra-muted-text));\n border: 0;\n cursor: pointer;\n transition: background .12s ease;\n}\n.ra-shell .ra-group-summary:hover {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-group-summary .ra-group-chevron {\n transition: transform .15s ease;\n}\n.ra-shell .ra-group[data-open=false] .ra-group-chevron {\n transform: rotate(-90deg);\n}\n.ra-shell .ra-group-name {\n flex: 1;\n text-align: left;\n}\n.ra-shell .ra-group-count {\n font-size: 0.65rem;\n font-weight: 600;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: 999px;\n padding: 0.05rem 0.4rem;\n}\n.ra-shell .ra-group[data-open=false] .ra-group-body {\n display: none;\n}\n.ra-shell .ra-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 2.5rem 1.5rem;\n gap: 0.75rem;\n}\n.ra-shell .ra-empty-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 3.25rem;\n height: 3.25rem;\n border-radius: 999px;\n background: hsl(var(--ra-accent) / 0.08);\n color: hsl(var(--ra-accent));\n margin-bottom: 0.25rem;\n}\n.ra-shell .ra-empty-title {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n font-size: 1rem;\n color: hsl(var(--ra-text));\n margin: 0;\n letter-spacing: -0.01em;\n}\n.ra-shell .ra-empty-body {\n font-size: 0.8125rem;\n color: hsl(var(--ra-muted-text));\n max-width: 22rem;\n line-height: 1.45;\n}\n.ra-shell .ra-empty-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-top: 0.25rem;\n flex-wrap: wrap;\n justify-content: center;\n}\n.ra-shell .ra-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.45rem 0.85rem;\n border-radius: calc(var(--ra-radius) * 0.7);\n font-size: 0.8125rem;\n font-weight: 500;\n border: 1px solid hsl(var(--ra-border));\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-text));\n cursor: pointer;\n transition:\n background .15s ease,\n border-color .15s ease,\n box-shadow .15s ease,\n transform .1s ease;\n}\n.ra-shell .ra-btn:hover {\n background: hsl(var(--ra-muted));\n box-shadow: var(--ra-card-shadow);\n}\n.ra-shell .ra-btn:active {\n transform: translateY(1px);\n}\n.ra-shell .ra-btn[data-variant=primary] {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-surface));\n border-color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-btn[data-variant=primary]:hover {\n background: hsl(var(--ra-accent) / 0.92);\n}\n.ra-shell .ra-btn[data-variant=ghost] {\n background: transparent;\n border-color: transparent;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-btn[data-variant=ghost]:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-btn[data-variant=danger] {\n color: hsl(var(--ra-danger));\n}\n.ra-shell .ra-btn[data-variant=danger]:hover {\n background: hsl(var(--ra-danger) / 0.10);\n border-color: hsl(var(--ra-danger) / 0.40);\n}\n.ra-shell .ra-intro {\n position: relative;\n display: flex;\n align-items: center;\n gap: 0.55rem;\n padding: 0.4rem 2rem 0.4rem 0.5rem;\n border-radius: var(--ra-radius);\n border: 1px solid hsl(var(--ra-info) / 0.30);\n background: hsl(var(--ra-info) / 0.08);\n}\n.ra-shell .ra-intro[data-tone=success] {\n border-color: hsl(var(--ra-success) / 0.30);\n background: hsl(var(--ra-success) / 0.08);\n}\n.ra-shell .ra-intro[data-tone=warning] {\n border-color: hsl(var(--ra-warning) / 0.35);\n background: hsl(var(--ra-warning) / 0.10);\n}\n.ra-shell .ra-intro-icon {\n flex-shrink: 0;\n width: 1.5rem;\n height: 1.5rem;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: hsl(var(--ra-info) / 0.18);\n color: hsl(var(--ra-info));\n}\n.ra-shell .ra-intro[data-tone=success] .ra-intro-icon {\n background: hsl(var(--ra-success) / 0.18);\n color: hsl(var(--ra-success));\n}\n.ra-shell .ra-intro[data-tone=warning] .ra-intro-icon {\n background: hsl(var(--ra-warning) / 0.20);\n color: hsl(var(--ra-warning));\n}\n.ra-shell .ra-intro-body {\n flex: 1;\n min-width: 0;\n}\n.ra-shell .ra-intro-title {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-title-weight);\n font-size: 0.8rem;\n color: hsl(var(--ra-text));\n margin: 0;\n line-height: 1.2;\n display: inline;\n}\n.ra-shell .ra-intro-text {\n font-size: 0.78rem;\n color: hsl(var(--ra-text) / 0.85);\n line-height: 1.35;\n display: inline;\n margin-left: 0.4rem;\n}\n.ra-shell .ra-intro-dismiss {\n position: absolute;\n top: 50%;\n right: 0.35rem;\n transform: translateY(-50%);\n width: 1.4rem;\n height: 1.4rem;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 0;\n color: hsl(var(--ra-muted-text));\n cursor: pointer;\n padding: 0;\n flex-shrink: 0;\n}\n.ra-shell .ra-intro-dismiss:hover {\n background: hsl(var(--ra-text) / 0.06);\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-bulk-menu {\n min-width: 12rem;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: calc(var(--ra-radius) * 0.85);\n box-shadow: var(--ra-card-shadow-hover);\n padding: 0.3rem;\n z-index: 60;\n}\n.ra-shell .ra-bulk-item {\n display: flex;\n align-items: center;\n gap: 0.55rem;\n width: 100%;\n padding: 0.45rem 0.6rem;\n border-radius: calc(var(--ra-radius) * 0.6);\n font-size: 0.8125rem;\n color: hsl(var(--ra-text));\n background: transparent;\n border: 0;\n cursor: pointer;\n text-align: left;\n transition: background .12s ease, color .12s ease;\n}\n.ra-shell .ra-bulk-item:hover {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-bulk-item[data-tone=danger] {\n color: hsl(var(--ra-danger));\n}\n.ra-shell .ra-bulk-item[data-tone=danger]:hover {\n background: hsl(var(--ra-danger) / 0.10);\n}\n.ra-shell .ra-bulk-divider {\n height: 1px;\n background: hsl(var(--ra-border));\n margin: 0.25rem 0;\n}\n.ra-shell .ra-preview-rail {\n background: hsl(var(--ra-surface));\n border-left: 1px solid hsl(var(--ra-border));\n box-shadow: -4px 0 16px hsl(var(--ra-accent) / 0.04);\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n.ra-shell .ra-preview-rail-header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n background:\n linear-gradient(\n 180deg,\n hsl(var(--ra-surface)) 0%,\n hsl(var(--ra-surface) / 0.92) 100%);\n border-bottom: 1px solid hsl(var(--ra-border));\n backdrop-filter: blur(6px);\n}\n.ra-shell .ra-preview-rail-title {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-preview-rail-body {\n flex: 1;\n overflow-y: auto;\n padding: 1rem;\n}\n.ra-confirm-root {\n position: fixed;\n inset: 0;\n z-index: 2147483000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem;\n background: transparent;\n}\n.ra-confirm-root .ra-confirm-backdrop {\n position: absolute;\n inset: 0;\n background: hsl(0 0% 0% / 0.45);\n backdrop-filter: blur(2px);\n animation: ra-confirm-fade .12s ease-out;\n}\n.ra-confirm-root .ra-confirm-card {\n position: relative;\n width: min(440px, 100%);\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-text));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: 0 1px 2px hsl(0 0% 0% / 0.08), 0 24px 48px -12px hsl(0 0% 0% / 0.32);\n padding: 1.25rem;\n animation: ra-confirm-pop .14s ease-out;\n}\n.ra-confirm-root .ra-confirm-header {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n margin-bottom: 0.5rem;\n}\n.ra-confirm-root .ra-confirm-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.75rem;\n height: 1.75rem;\n border-radius: 999px;\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.12);\n color: hsl(var(--ra-warning, 38 92% 50%));\n}\n.ra-confirm-root .ra-confirm-title {\n font-family: var(--ra-font-display);\n font-weight: 600;\n font-size: 1rem;\n margin: 0;\n}\n.ra-confirm-root .ra-confirm-body {\n font-size: 0.875rem;\n color: hsl(var(--ra-muted-text));\n margin: 0 0 1.1rem;\n line-height: 1.45;\n}\n.ra-confirm-root .ra-confirm-actions {\n display: flex;\n justify-content: flex-end;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.ra-confirm-root .ra-confirm-btn {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid transparent;\n border-radius: calc(var(--ra-radius) - 2px);\n padding: 0.45rem 0.85rem;\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n transition:\n background-color .12s ease,\n border-color .12s ease,\n color .12s ease;\n}\n.ra-confirm-root .ra-confirm-btn:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring);\n}\n.ra-confirm-root .ra-confirm-btn-ghost {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.ra-confirm-root .ra-confirm-btn-ghost:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-confirm-root .ra-confirm-btn-danger {\n background: transparent;\n color: hsl(var(--ra-danger, 0 72% 51%));\n border-color: hsl(var(--ra-danger, 0 72% 51%) / 0.45);\n}\n.ra-confirm-root .ra-confirm-btn-danger:hover {\n background: hsl(var(--ra-danger, 0 72% 51%) / 0.08);\n border-color: hsl(var(--ra-danger, 0 72% 51%));\n}\n.ra-confirm-root .ra-confirm-btn-primary {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-accent-fg, 0 0% 100%));\n border-color: hsl(var(--ra-accent));\n}\n.ra-confirm-root .ra-confirm-btn-primary:hover {\n filter: brightness(0.95);\n}\n@keyframes ra-confirm-fade {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n@keyframes ra-confirm-pop {\n from {\n opacity: 0;\n transform: translateY(4px) scale(.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ra-shell .ra-unsaved-banner {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.5rem 0.75rem;\n border: 1px solid hsl(var(--ra-warning, 38 92% 50%) / 0.35);\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.08);\n border-radius: var(--ra-radius);\n font-size: 0.8125rem;\n color: hsl(var(--ra-text));\n animation: ra-unsaved-slide .14s ease-out;\n}\n.ra-shell .ra-unsaved-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: hsl(var(--ra-warning, 38 92% 50%));\n flex-shrink: 0;\n}\n.ra-shell .ra-unsaved-text {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ra-shell .ra-unsaved-context {\n color: hsl(var(--ra-muted-text));\n font-weight: 400;\n}\n.ra-shell .ra-unsaved-error {\n color: hsl(var(--ra-danger, 0 72% 51%));\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n font-weight: 500;\n}\n.ra-shell .ra-unsaved-actions {\n display: inline-flex;\n gap: 0.4rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-unsaved-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid transparent;\n border-radius: calc(var(--ra-radius) - 2px);\n padding: 0.3rem 0.6rem;\n font-size: 0.75rem;\n font-weight: 500;\n cursor: pointer;\n transition:\n background-color .12s ease,\n border-color .12s ease,\n color .12s ease,\n opacity .12s ease;\n}\n.ra-shell .ra-unsaved-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ra-shell .ra-unsaved-btn:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring);\n}\n.ra-shell .ra-unsaved-btn-ghost {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.ra-shell .ra-unsaved-btn-ghost:hover:not(:disabled) {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-unsaved-btn-primary {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-accent-fg, 0 0% 100%));\n border-color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-unsaved-btn-primary:hover:not(:disabled) {\n filter: brightness(0.95);\n}\n.sl-aph .ra-unsaved-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid transparent;\n border-radius: calc(var(--ra-radius, 8px) - 2px);\n padding: 0.3rem 0.6rem;\n font-size: 0.75rem;\n font-weight: 500;\n cursor: pointer;\n transition:\n background-color .12s ease,\n border-color .12s ease,\n color .12s ease,\n opacity .12s ease;\n}\n.sl-aph .ra-unsaved-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.sl-aph .ra-unsaved-btn:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring, 0 0 0 3px hsl(var(--ra-accent) / 0.35));\n}\n.sl-aph .ra-unsaved-btn-ghost {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.sl-aph .ra-unsaved-btn-ghost:hover:not(:disabled) {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.sl-aph .ra-unsaved-btn-primary {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-accent-fg, 0 0% 100%));\n border-color: hsl(var(--ra-accent));\n}\n.sl-aph .ra-unsaved-btn-primary:hover:not(:disabled) {\n filter: brightness(0.95);\n}\n.sl-aph .ra-unsaved-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: hsl(var(--ra-warning, 38 92% 50%));\n flex-shrink: 0;\n}\n@keyframes ra-unsaved-slide {\n from {\n opacity: 0;\n transform: translateY(-3px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ra-shell .ra-unsaved-pill,\n.sl-aph .ra-unsaved-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.25rem 0.5rem 0.25rem 0.6rem;\n border: 1px solid hsl(var(--ra-warning, 38 92% 50%) / 0.35);\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.08);\n border-radius: 999px;\n animation: ra-unsaved-slide .14s ease-out;\n}\n.ra-shell .ra-unsaved-pill .ra-unsaved-pill-text,\n.sl-aph .ra-unsaved-pill .ra-unsaved-pill-text {\n font-size: 0.75rem;\n font-weight: 500;\n color: hsl(var(--ra-text));\n white-space: nowrap;\n margin-right: 0.15rem;\n}\n.ra-shell .ra-clipboard-toast {\n position: fixed;\n bottom: 1.25rem;\n left: 50%;\n transform: translateX(-50%);\n z-index: 90;\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n max-width: min(28rem, calc(100vw - 2rem));\n padding: 0.55rem 0.85rem;\n border-radius: 999px;\n background: hsl(var(--ra-text));\n color: hsl(var(--ra-surface));\n font-size: 0.75rem;\n line-height: 1;\n box-shadow: 0 8px 24px -10px hsl(0 0% 0% / 0.45);\n animation: ra-clipboard-pop 0.18s ease-out both;\n pointer-events: none;\n}\n@keyframes ra-clipboard-pop {\n from {\n opacity: 0;\n transform: translate(-50%, 6px);\n }\n to {\n opacity: 1;\n transform: translate(-50%, 0);\n }\n}\n.ra-shell .ra-row-menu-wrap {\n display: inline-flex;\n align-items: center;\n margin-left: 0.25rem;\n}\n.ra-shell .ra-row-menu-trigger {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.5rem;\n height: 1.5rem;\n border-radius: 0.35rem;\n background: transparent;\n color: hsl(var(--ra-muted-text));\n opacity: 0;\n transition:\n opacity .15s ease,\n background .15s ease,\n color .15s ease;\n border: 1px solid transparent;\n}\n.ra-shell .ra-row:hover .ra-row-menu-trigger,\n.ra-shell .ra-card-hover:hover .ra-row-menu-trigger,\n.ra-shell .ra-row-menu-trigger:focus-visible,\n.ra-shell .ra-row-menu-trigger[aria-expanded=true] {\n opacity: 1;\n}\n.ra-shell .ra-row-menu-trigger:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-row-menu {\n position: absolute;\n right: 0;\n top: calc(100% + 4px);\n z-index: 50;\n min-width: 11rem;\n padding: 0.25rem;\n border-radius: 0.5rem;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n box-shadow: 0 12px 28px -10px hsl(0 0% 0% / 0.25);\n display: flex;\n flex-direction: column;\n gap: 0.125rem;\n}\n.ra-shell .ra-row-menu-item {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.4rem 0.55rem;\n border-radius: 0.35rem;\n font-size: 0.75rem;\n color: hsl(var(--ra-text));\n background: transparent;\n border: 0;\n text-align: left;\n width: 100%;\n cursor: pointer;\n}\n.ra-shell .ra-row-menu-item:hover:not(:disabled) {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-row-menu-item:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ra-shell .ra-item-list {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n}\n.ra-shell .ra-item-list-body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 1rem 1.25rem 1.5rem;\n}\n.ra-shell .ra-item-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.75rem 1.25rem;\n border-bottom: 1px solid hsl(var(--ra-border));\n background: hsl(var(--ra-surface));\n}\n.ra-shell .ra-item-toolbar-title {\n display: flex;\n align-items: baseline;\n gap: 0.5rem;\n min-width: 0;\n}\n.ra-shell .ra-item-toolbar-count {\n font-size: 0.7rem;\n font-weight: 600;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-muted));\n border: 1px solid hsl(var(--ra-border));\n border-radius: 999px;\n padding: 0.05rem 0.45rem;\n}\n.ra-shell .ra-item-toolbar-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-item-table-wrap {\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n background: hsl(var(--ra-surface));\n overflow: hidden;\n box-shadow: var(--ra-card-shadow);\n}\n.ra-shell .ra-item-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.85rem;\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-item-table thead th {\n text-align: left;\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: hsl(var(--ra-muted-text));\n padding: 0.65rem 0.85rem;\n background: hsl(var(--ra-muted) / 0.55);\n border-bottom: 1px solid hsl(var(--ra-border));\n}\n.ra-shell .ra-item-table tbody td {\n padding: 0.65rem 0.85rem;\n border-bottom: 1px solid hsl(var(--ra-border) / 0.7);\n vertical-align: middle;\n}\n.ra-shell .ra-item-table tbody tr:last-child td {\n border-bottom: 0;\n}\n.ra-shell .ra-item-row {\n cursor: pointer;\n transition: background .12s ease;\n}\n.ra-shell .ra-item-row:hover {\n background: var(--ra-row-hover);\n}\n.ra-shell .ra-item-row[data-selected=true] {\n background: var(--ra-row-active-bg);\n}\n.ra-shell .ra-item-row-title {\n font-weight: var(--ra-title-weight);\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-item-row-sub {\n font-size: 0.75rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.15rem;\n}\n.ra-shell .ra-item-row-meta {\n font-size: 0.78rem;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-item-row-actions {\n text-align: right;\n white-space: nowrap;\n}\n.ra-shell .ra-item-row-actions .ra-row-action + .ra-row-action {\n margin-left: 0.15rem;\n}\n.ra-shell .ra-item-cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--ra-item-card-min, 240px), 1fr));\n gap: 0.85rem;\n}\n.ra-shell .ra-item-gallery {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--ra-item-gallery-min, 320px), 1fr));\n gap: 1rem;\n}\n.ra-shell .ra-item-cards[data-card-size=sm] {\n --ra-item-card-min: 180px;\n}\n.ra-shell .ra-item-cards[data-card-size=md] {\n --ra-item-card-min: 240px;\n}\n.ra-shell .ra-item-cards[data-card-size=lg] {\n --ra-item-card-min: 320px;\n gap: 1rem;\n}\n.ra-shell .ra-item-gallery[data-card-size=sm] {\n --ra-item-gallery-min: 240px;\n}\n.ra-shell .ra-item-gallery[data-card-size=md] {\n --ra-item-gallery-min: 320px;\n}\n.ra-shell .ra-item-gallery[data-card-size=lg] {\n --ra-item-gallery-min: 420px;\n gap: 1.25rem;\n}\n.ra-shell .ra-item-cards[data-card-size=lg] .ra-item-card-title,\n.ra-shell .ra-item-gallery[data-card-size=lg] .ra-item-card-title {\n font-size: 0.95rem;\n white-space: normal;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n.ra-shell .ra-item-cards[data-card-size=lg] .ra-item-card-body,\n.ra-shell .ra-item-gallery[data-card-size=lg] .ra-item-card-body {\n padding: 0.85rem 1rem 1rem;\n}\n.ra-shell .ra-item-card {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n text-align: left;\n padding: 0;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n overflow: hidden;\n cursor: pointer;\n transition:\n box-shadow .18s ease,\n transform .12s ease,\n border-color .15s ease;\n box-shadow: var(--ra-card-shadow);\n font-family: inherit;\n color: inherit;\n}\n.ra-shell .ra-item-card:hover {\n box-shadow: var(--ra-card-shadow-hover);\n border-color: hsl(var(--ra-accent) / 0.30);\n}\n.ra-shell .ra-item-card[data-selected=true] {\n border-color: hsl(var(--ra-accent) / 0.55);\n box-shadow: var(--ra-card-shadow-hover);\n}\n.ra-shell .ra-item-card-thumb {\n width: 100%;\n aspect-ratio: 1 / 1;\n background:\n linear-gradient(\n 135deg,\n hsl(var(--ra-accent) / 0.12),\n hsl(var(--ra-accent) / 0.04));\n display: flex;\n align-items: center;\n justify-content: center;\n color: hsl(var(--ra-accent));\n overflow: hidden;\n}\n.ra-shell .ra-item-card-thumb--gallery {\n aspect-ratio: 16 / 9;\n}\n.ra-shell .ra-item-card-thumb img {\n width: 100%;\n height: 100%;\n -o-object-fit: cover;\n object-fit: cover;\n}\n.ra-shell .ra-item-card-initials {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n font-size: 1.5rem;\n letter-spacing: 0.02em;\n}\n.ra-shell .ra-item-card-body {\n padding: 0.65rem 0.8rem 0.85rem;\n min-width: 0;\n}\n.ra-shell .ra-item-card-title {\n font-weight: var(--ra-title-weight);\n font-size: 0.85rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-item-card-sub {\n font-size: 0.75rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.15rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-item-card-delete {\n position: absolute;\n top: 0.4rem;\n right: 0.4rem;\n background: hsl(var(--ra-surface) / 0.85);\n backdrop-filter: blur(4px);\n opacity: 0;\n transition: opacity .15s ease;\n}\n.ra-shell .ra-item-card:hover .ra-item-card-delete,\n.ra-shell .ra-item-card:focus-within .ra-item-card-delete {\n opacity: 1;\n}\n.ra-shell .ra-item-nav {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.5rem 1.25rem;\n border-bottom: 1px solid hsl(var(--ra-border));\n background: hsl(var(--ra-surface));\n}\n.ra-shell .ra-item-nav-position {\n font-size: 0.72rem;\n color: hsl(var(--ra-muted-text));\n font-variant-numeric: tabular-nums;\n}\n.ra-shell .ra-item-nav-arrows {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 0.15rem;\n}\n.ra-shell .ra-item-nav-arrows .ra-row-action[disabled] {\n opacity: 0.35;\n cursor: not-allowed;\n}\n.ra-shell .ra-sibling-rail {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n}\n.ra-shell .ra-sibling-back {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.6rem 0.85rem;\n font-size: 0.75rem;\n font-weight: 500;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-muted) / 0.5);\n border: 0;\n border-bottom: 1px solid hsl(var(--ra-border));\n cursor: pointer;\n text-align: left;\n transition: background .12s ease, color .12s ease;\n}\n.ra-shell .ra-sibling-back:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-sibling-heading {\n padding: 0.6rem 0.85rem 0.4rem;\n font-size: 0.65rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-sibling-body {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n.ra-shell .ra-sibling-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.ra-shell .ra-status-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: 9999px;\n}\n.ra-shell .ra-status-icon > svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n.ra-shell .ra-status-icon--own {\n color: hsl(var(--ra-status-own));\n}\n.ra-shell .ra-status-icon--shared {\n color: hsl(var(--ra-status-shared));\n}\n.ra-shell .ra-status-icon--missing {\n color: hsl(var(--ra-status-missing) / 0.7);\n}\n.ra-shell .ra-row-status {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.5rem;\n height: 1.5rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-row-scope {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.25rem;\n height: 1.25rem;\n border-radius: calc(var(--ra-radius) * 0.5);\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n flex-shrink: 0;\n margin-left: auto;\n opacity: 0.55;\n transition:\n opacity .12s ease,\n color .12s ease,\n background .12s ease;\n}\n.ra-shell .ra-row:hover .ra-row-scope {\n opacity: 0.85;\n}\n.ra-shell .ra-row[data-selected=true] .ra-row-scope {\n opacity: 1;\n background: hsl(var(--ra-accent) / 0.12);\n color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-row[data-tone=own] .ra-row-sub {\n color: hsl(var(--ra-status-own));\n}\n.ra-shell .ra-row[data-tone=shared] .ra-row-sub {\n color: hsl(var(--ra-status-shared));\n}\n.ra-shell .ra-row[data-selected=true] {\n background:\n linear-gradient(\n 90deg,\n hsl(var(--ra-accent) / 0.10) 0%,\n hsl(var(--ra-accent) / 0.04) 100%);\n border-left-width: 3px;\n border-left-color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-dirty-pip {\n display: inline-block;\n width: 0.45rem;\n height: 0.45rem;\n border-radius: 9999px;\n background: hsl(var(--ra-warning));\n box-shadow: 0 0 0 2px hsl(var(--ra-warning) / 0.18);\n flex-shrink: 0;\n}\n.ra-shell .ra-error-pip {\n display: inline-block;\n width: 0.45rem;\n height: 0.45rem;\n border-radius: 9999px;\n background: hsl(var(--ra-danger, 0 72% 51%));\n box-shadow: 0 0 0 2px hsl(var(--ra-danger, 0 72% 51%) / 0.22);\n flex-shrink: 0;\n}\n.ra-shell .ra-group-summary {\n background: transparent;\n}\n.ra-shell {\n position: relative;\n}\n.ra-shell .ra-help-float {\n position: absolute;\n top: 0.65rem;\n right: 0.85rem;\n z-index: 5;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.6rem;\n height: 1.6rem;\n padding: 0;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-surface) / 0.85);\n backdrop-filter: blur(6px);\n border: 1px solid hsl(var(--ra-border));\n border-radius: 999px;\n cursor: pointer;\n transition:\n color .12s ease,\n background .12s ease,\n border-color .12s ease;\n}\n.ra-shell .ra-help-float:hover {\n color: hsl(var(--ra-accent));\n border-color: hsl(var(--ra-accent) / 0.4);\n background: hsl(var(--ra-surface));\n}\n.ra-shell .ra-help-float svg {\n width: 0.95rem;\n height: 0.95rem;\n}\n.ra-shell .ra-help-float > span {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n.ra-shell .ra-preview-reopen {\n position: absolute;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n z-index: 4;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.4rem;\n padding: 0.65rem 0.45rem;\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-muted-text));\n border: 1px solid hsl(var(--ra-border));\n border-right: 0;\n border-radius: calc(var(--ra-radius) * 0.85) 0 0 calc(var(--ra-radius) * 0.85);\n box-shadow: var(--ra-card-shadow);\n cursor: pointer;\n transition:\n color .12s ease,\n background .12s ease,\n padding-right .15s ease;\n writing-mode: vertical-rl;\n font-size: 0.7rem;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.ra-shell .ra-preview-reopen:hover {\n color: hsl(var(--ra-accent));\n background: hsl(var(--ra-accent) / 0.04);\n padding-right: 0.6rem;\n}\n.ra-shell .ra-preview-reopen svg {\n width: 0.85rem;\n height: 0.85rem;\n writing-mode: horizontal-tb;\n}\n.ra-shell .ra-unsaved-tray {\n position: relative;\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.5rem 0.75rem;\n border: 1px solid hsl(var(--ra-warning, 38 92% 50%) / 0.35);\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.08);\n border-radius: var(--ra-radius);\n font-size: 0.8125rem;\n color: hsl(var(--ra-text));\n animation: ra-unsaved-slide .14s ease-out;\n}\n.ra-shell .ra-unsaved-count {\n flex: 1;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.15rem 0.4rem;\n margin: -0.15rem -0.4rem;\n background: transparent;\n border: 0;\n color: inherit;\n font: inherit;\n font-weight: 500;\n text-align: left;\n cursor: pointer;\n border-radius: calc(var(--ra-radius) - 4px);\n}\n.ra-shell .ra-unsaved-count:hover {\n background: hsl(var(--ra-muted) / 0.6);\n}\n.ra-shell .ra-unsaved-error-chip {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid hsl(var(--ra-danger, 0 72% 51%) / 0.35);\n background: hsl(var(--ra-danger, 0 72% 51%) / 0.08);\n color: hsl(var(--ra-danger, 0 72% 51%));\n border-radius: 999px;\n padding: 0.15rem 0.55rem;\n font-size: 0.7rem;\n font-weight: 500;\n cursor: pointer;\n}\n.ra-shell .ra-unsaved-error-chip:hover {\n filter: brightness(0.97);\n}\n.ra-shell .ra-unsaved-popover {\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n z-index: 60;\n min-width: 18rem;\n max-height: 18rem;\n overflow: auto;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: 0 12px 30px -10px hsl(0 0% 0% / 0.25);\n padding: 0.3rem;\n display: flex;\n flex-direction: column;\n gap: 0.15rem;\n}\n.ra-shell .ra-unsaved-popover-row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.4rem 0.55rem;\n background: transparent;\n border: 0;\n border-radius: calc(var(--ra-radius) - 2px);\n cursor: pointer;\n text-align: left;\n font: inherit;\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-unsaved-popover-row:hover {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-unsaved-popover-dot {\n width: 0.5rem;\n height: 0.5rem;\n border-radius: 999px;\n flex-shrink: 0;\n}\n.ra-shell .ra-unsaved-popover-label {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 0.8125rem;\n}\n.ra-shell .ra-unsaved-popover-ctx {\n color: hsl(var(--ra-muted-text));\n font-size: 0.7rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n.ra-shell .ra-unsaved-popover-err {\n color: hsl(var(--ra-danger, 0 72% 51%));\n font-size: 0.7rem;\n font-weight: 500;\n}\n.ra-saveall-overlay {\n position: fixed;\n inset: 0;\n z-index: 100;\n background: hsl(0 0% 0% / 0.45);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem;\n animation: ra-confirm-fade .12s ease-out;\n}\n.ra-saveall-card {\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-text));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: 0 24px 48px -16px hsl(0 0% 0% / 0.45);\n width: min(28rem, 100%);\n max-height: min(80vh, 36rem);\n display: flex;\n flex-direction: column;\n animation: ra-confirm-pop .14s ease-out;\n}\n.ra-saveall-header {\n padding: 1rem 1rem 0.5rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.ra-saveall-title {\n font-weight: 600;\n font-size: 0.95rem;\n}\n.ra-saveall-progress {\n height: 4px;\n background: hsl(var(--ra-muted));\n border-radius: 999px;\n overflow: hidden;\n}\n.ra-saveall-progress-bar {\n height: 100%;\n background: hsl(var(--ra-accent));\n transition: width .2s ease;\n}\n.ra-saveall-counter {\n color: hsl(var(--ra-muted-text));\n font-size: 0.75rem;\n font-variant-numeric: tabular-nums;\n}\n.ra-saveall-list {\n list-style: none;\n margin: 0;\n padding: 0.25rem 0.5rem;\n overflow: auto;\n flex: 1;\n}\n.ra-saveall-row {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.45rem 0.5rem;\n border-radius: calc(var(--ra-radius) - 4px);\n font-size: 0.8125rem;\n}\n.ra-saveall-row[data-status=saving] {\n background: hsl(var(--ra-accent) / 0.06);\n}\n.ra-saveall-row[data-status=saved] {\n color: hsl(var(--ra-muted-text));\n}\n.ra-saveall-row[data-status=error] {\n background: hsl(var(--ra-danger, 0 72% 51%) / 0.06);\n}\n.ra-saveall-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: hsl(var(--ra-muted-text));\n}\n.ra-saveall-row[data-status=saved] .ra-saveall-icon {\n color: hsl(var(--ra-success, 142 71% 45%));\n}\n.ra-saveall-row[data-status=saving] .ra-saveall-icon {\n color: hsl(var(--ra-accent));\n}\n.ra-saveall-row[data-status=error] .ra-saveall-icon {\n color: hsl(var(--ra-danger, 0 72% 51%));\n}\n.ra-saveall-label {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-saveall-err {\n color: hsl(var(--ra-danger, 0 72% 51%));\n font-size: 0.7rem;\n max-width: 12rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-saveall-actions {\n padding: 0.75rem 1rem 1rem;\n display: flex;\n justify-content: flex-end;\n gap: 0.4rem;\n border-top: 1px solid hsl(var(--ra-border));\n}\n.ra-spin {\n animation: ra-spin 1s linear infinite;\n}\n@keyframes ra-spin {\n to {\n transform: rotate(360deg);\n }\n}\n");
6072
6685
  var EditorPoolBody = ({
6073
6686
  editorId,
6074
6687
  renderEditor
@@ -6078,6 +6691,7 @@ var EditorPoolBody = ({
6078
6691
  return renderEditor(ctx);
6079
6692
  };
6080
6693
  var TOP_LEVEL_SCOPES = ["collection", "rule", "product"];
6694
+ var OPT_IN_TOP_LEVEL_SCOPES = ["all"];
6081
6695
  var WARNED_FACET_DEPRECATED = false;
6082
6696
  var DRAFT_ID3 = "__draft__";
6083
6697
  var isDraftId3 = (id) => !!id && (id === DRAFT_ID3 || id.startsWith("draft:"));
@@ -6134,7 +6748,7 @@ function RecordsAdminShellInner(props) {
6134
6748
  renderEditor,
6135
6749
  intro,
6136
6750
  csvSchema,
6137
- classify: classify2,
6751
+ classify: classify3,
6138
6752
  defaultData,
6139
6753
  i18n: i18nOverride,
6140
6754
  onTelemetry,
@@ -6256,13 +6870,15 @@ function RecordsAdminShellInner(props) {
6256
6870
  );
6257
6871
  const queryClient = useQueryClient();
6258
6872
  const probe = useScopeProbe({ SL, collectionId });
6873
+ const scopeCounts = useScopeCounts({ ctx });
6259
6874
  const topLevelScopes = useMemo(() => {
6260
6875
  const requested = requestedScopes ?? [];
6876
+ const allowed = /* @__PURE__ */ new Set([...TOP_LEVEL_SCOPES, ...OPT_IN_TOP_LEVEL_SCOPES]);
6261
6877
  if (requested.length > 0) {
6262
6878
  const seen = /* @__PURE__ */ new Set();
6263
6879
  const ordered = [];
6264
6880
  for (const s of requested) {
6265
- if (!TOP_LEVEL_SCOPES.includes(s)) continue;
6881
+ if (!allowed.has(s)) continue;
6266
6882
  if (seen.has(s)) continue;
6267
6883
  seen.add(s);
6268
6884
  ordered.push(s);
@@ -6274,7 +6890,7 @@ function RecordsAdminShellInner(props) {
6274
6890
  const productPinnedFromContext = !!contextScope?.productId;
6275
6891
  const effectiveTopLevelScopes = useMemo(() => {
6276
6892
  if (!productPinnedFromContext || contextScopeMode !== "strict") return topLevelScopes;
6277
- return topLevelScopes.filter((s) => s !== "collection" && s !== "rule");
6893
+ return topLevelScopes.filter((s) => s !== "collection" && s !== "rule" && s !== "all");
6278
6894
  }, [productPinnedFromContext, contextScopeMode, topLevelScopes]);
6279
6895
  useEffect(() => {
6280
6896
  if (requestedScopes.includes("facet") && !WARNED_FACET_DEPRECATED) {
@@ -6340,7 +6956,7 @@ function RecordsAdminShellInner(props) {
6340
6956
  probeIsLoading: probe.isLoading,
6341
6957
  selectedProductId,
6342
6958
  drillTab,
6343
- classify: classify2
6959
+ classify: classify3
6344
6960
  });
6345
6961
  const {
6346
6962
  search,
@@ -6374,13 +6990,16 @@ function RecordsAdminShellInner(props) {
6374
6990
  if (first) setSelectedProductId(first.id);
6375
6991
  }, [activeScope, selectedProductId, productBrowse.items]);
6376
6992
  useEffect(() => {
6377
- if (activeScope !== "rule" && activeScope !== "collection") return;
6993
+ if (activeScope !== "rule" && activeScope !== "collection" && activeScope !== "all") return;
6378
6994
  if (selectedRecordId !== null) return;
6379
6995
  if (ruleWizardStep !== null) return;
6380
6996
  if (draftKind !== null) return;
6381
6997
  if (activeScope === "collection" && cardinality === "collection") {
6382
6998
  return;
6383
6999
  }
7000
+ if (activeScope === "all" && cardinality === "collection") {
7001
+ return;
7002
+ }
6384
7003
  const first = recordList.items[0];
6385
7004
  if (first?.id) setSelectedRecordId(first.id);
6386
7005
  }, [activeScope, selectedRecordId, recordList.items, cardinality, ruleWizardStep, draftKind]);
@@ -6407,6 +7026,10 @@ function RecordsAdminShellInner(props) {
6407
7026
  // facetRule from the selected record's summary so `useCollectionItems`
6408
7027
  // can match by rule equality instead of by anchors.
6409
7028
  facetRule: editingScope?.kind === "rule" ? recordList.items.find((it) => it.id === selectedRecordId)?.facetRule ?? null : null,
7029
+ // 'all' tab + collection cardinality: pull every item-cardinality
7030
+ // record across the whole collection (anchored, ruled, global) so
7031
+ // host-supplied lifecycle grouping has the full picture.
7032
+ includeAll: isCollection && activeScope === "all",
6410
7033
  enabled: isCollection
6411
7034
  });
6412
7035
  useEffect(() => {
@@ -6498,6 +7121,10 @@ function RecordsAdminShellInner(props) {
6498
7121
  setRuleWizardRule(null);
6499
7122
  setDraftKind(null);
6500
7123
  }
7124
+ if (isCreate && selectedRecordId === DRAFT_ID3) {
7125
+ setSelectedRecordId(null);
7126
+ setDraftKind(null);
7127
+ }
6501
7128
  refetchAll();
6502
7129
  },
6503
7130
  onDeleted: () => {
@@ -6703,13 +7330,33 @@ function RecordsAdminShellInner(props) {
6703
7330
  bulkActions: { ...csvBulk, i18n },
6704
7331
  preview: inlinePreviewBody,
6705
7332
  footerExtra: extraFooter,
6706
- targeting: editingTargetScope.kind === "rule" ? /* @__PURE__ */ jsx(
6707
- RecordTargeting,
7333
+ targeting: (
7334
+ // Mount the Targeting section for:
7335
+ // • Native rule scopes (existing behaviour — author the rule).
7336
+ // • Non-pinned Global (collection-root) scopes — admins can grow
7337
+ // a rule on the fly to convert Global → Rule, or stay global.
7338
+ // Pinned scopes (product / variant / batch / proof) are anchored
7339
+ // by ID, not by rule, so the section would be a no-op there.
7340
+ editingTargetScope.kind === "rule" || editingTargetScope.kind === "collection" && !editingTargetScope.productId && !editingTargetScope.variantId && !editingTargetScope.batchId && !editingTargetScope.proofId ? /* @__PURE__ */ jsx(
7341
+ RecordTargeting,
7342
+ {
7343
+ SL,
7344
+ collectionId,
7345
+ appId,
7346
+ ctx: editorCtx,
7347
+ forceOpen: targetingExpandNonce > 0,
7348
+ onOpenChange: (o) => {
7349
+ if (!o) setTargetingExpandNonce(0);
7350
+ }
7351
+ }
7352
+ ) : void 0
7353
+ ),
7354
+ targetingControl: editingTargetScope.kind === "rule" || editingTargetScope.kind === "collection" && !editingTargetScope.productId && !editingTargetScope.variantId && !editingTargetScope.batchId && !editingTargetScope.proofId ? /* @__PURE__ */ jsx(
7355
+ TargetingPopover,
6708
7356
  {
6709
- SL,
6710
- collectionId,
6711
- appId,
6712
- ctx: editorCtx
7357
+ ctx: editorCtx,
7358
+ catalogue: ruleCatalogue,
7359
+ onCustomise: () => setTargetingExpandNonce((n) => n + 1)
6713
7360
  }
6714
7361
  ) : void 0,
6715
7362
  onBeforeDelete: onBeforeDelete && editingTargetScope ? () => onBeforeDelete(editingTargetScope) : void 0,
@@ -6827,7 +7474,6 @@ function RecordsAdminShellInner(props) {
6827
7474
  ]);
6828
7475
  const effectivePresentation = isProductTab ? presentation : "list";
6829
7476
  const showPresentationSwitcher = isProductTab && presentations.length > 1;
6830
- const effectiveGroupBy = groupBy;
6831
7477
  const productListItems = useMemo(() => {
6832
7478
  if (productPinned) {
6833
7479
  const pid = contextScope.productId;
@@ -6845,7 +7491,65 @@ function RecordsAdminShellInner(props) {
6845
7491
  }, [productPinned, contextScope, productBrowse.items, pinnedProduct.item]);
6846
7492
  const isRuleTab = activeScope === "rule";
6847
7493
  const isGlobalTab = activeScope === "collection";
6848
- const isRecordsTab = isRuleTab || isGlobalTab;
7494
+ const isAllTab = activeScope === "all";
7495
+ const isRecordsTab = isRuleTab || isGlobalTab || isAllTab;
7496
+ const effectiveGroupBy = useMemo(() => {
7497
+ if (groupBy) return groupBy;
7498
+ if (isAllTab) return void 0;
7499
+ if (!isRuleTab) return void 0;
7500
+ return (record) => {
7501
+ const hash = ruleHash(record.facetRule);
7502
+ if (!hash) return null;
7503
+ return { key: `rule:${hash}`, label: summariseRule(record.facetRule) };
7504
+ };
7505
+ }, [groupBy, isRuleTab, isAllTab]);
7506
+ const [bulkRuleEditTarget, setBulkRuleEditTarget] = useState(null);
7507
+ const ruleCatalogue = useMemo(() => {
7508
+ const buckets = /* @__PURE__ */ new Map();
7509
+ for (const item of recordList.items) {
7510
+ const hash = ruleHash(item.facetRule);
7511
+ if (!hash || !item.facetRule) continue;
7512
+ const existing = buckets.get(hash);
7513
+ if (existing) {
7514
+ existing.count += 1;
7515
+ } else {
7516
+ buckets.set(hash, {
7517
+ hash,
7518
+ rule: item.facetRule,
7519
+ summary: summariseRule(item.facetRule),
7520
+ count: 1
7521
+ });
7522
+ }
7523
+ }
7524
+ return Array.from(buckets.values()).sort((a, b) => {
7525
+ if (b.count !== a.count) return b.count - a.count;
7526
+ return a.summary.localeCompare(b.summary);
7527
+ });
7528
+ }, [recordList.items]);
7529
+ const [targetingExpandNonce, setTargetingExpandNonce] = useState(0);
7530
+ const renderRuleGroupActions = useCallback(
7531
+ (group) => {
7532
+ if (!isRuleTab || groupBy) return null;
7533
+ const ids = group.items.map((it) => it.id).filter((id) => !!id);
7534
+ if (ids.length < 2) return null;
7535
+ const rule = group.items.find((it) => it.facetRule)?.facetRule ?? null;
7536
+ return /* @__PURE__ */ jsx(
7537
+ "button",
7538
+ {
7539
+ type: "button",
7540
+ className: "text-[11px] px-2 py-0.5 rounded border hover:bg-[hsl(var(--ra-muted))] transition-colors",
7541
+ style: {
7542
+ borderColor: "hsl(var(--ra-border))",
7543
+ color: "hsl(var(--ra-muted-text))"
7544
+ },
7545
+ title: `Edit this rule and apply the change to all ${ids.length} records that share it`,
7546
+ onClick: () => setBulkRuleEditTarget({ rule, recordIds: ids }),
7547
+ children: "Edit rule"
7548
+ }
7549
+ );
7550
+ },
7551
+ [isRuleTab, groupBy]
7552
+ );
6849
7553
  const onCreateRule = useCallback(() => {
6850
7554
  void runWithGuard(() => {
6851
7555
  if (activeScope !== "rule") setActiveScope("rule");
@@ -6944,7 +7648,7 @@ function RecordsAdminShellInner(props) {
6944
7648
  }),
6945
7649
  [i18n.itemsAllLabel, collectionItems.items.length, itemNoun]
6946
7650
  );
6947
- const leftItems = isProductTab ? productListItems : isRuleTab ? applyFacetBrowseFilter(filteredRuleItems) : isGlobalTab && isCollection ? [collectionGlobalAllRow] : isRecordsTab ? applyFacetBrowseFilter(recordList.items) : [];
7651
+ const leftItems = isProductTab ? productListItems : isRuleTab ? applyFacetBrowseFilter(filteredRuleItems) : (isGlobalTab || isAllTab) && isCollection ? [collectionGlobalAllRow] : isRecordsTab ? applyFacetBrowseFilter(recordList.items) : [];
6948
7652
  const leftLoading = isProductTab ? !productPinned && productBrowse.isLoading : isRecordsTab ? recordList.isLoading || probe.isLoading : false;
6949
7653
  const leftError = isProductTab ? productBrowse.error : isRecordsTab ? recordList.error : null;
6950
7654
  const leftSelectedId = isProductTab ? void 0 : selectedRecordId && selectedRecordId !== DRAFT_ID3 ? selectedRecordId : void 0;
@@ -7157,8 +7861,22 @@ function RecordsAdminShellInner(props) {
7157
7861
  },
7158
7862
  loading: probe.isLoading,
7159
7863
  counts: {
7160
- product: productBrowse.items.length
7864
+ // `product` continues to show the catalogue size (browse
7865
+ // affordance), not the per-product record count — that's
7866
+ // the long-standing semantics for the Products tab.
7867
+ product: productBrowse.items.length,
7868
+ // The remaining tabs show actual record counts so hidden
7869
+ // state (e.g. a rule-scoped competition) is visible from
7870
+ // any tab. `useScopeCounts` returns 0 while loading, which
7871
+ // is fine — the badges just appear once data lands.
7872
+ collection: scopeCounts.counts.collection,
7873
+ rule: scopeCounts.counts.rule,
7874
+ variant: scopeCounts.counts.variant,
7875
+ batch: scopeCounts.counts.batch,
7876
+ facet: scopeCounts.counts.facet,
7877
+ all: scopeCounts.counts.all
7161
7878
  },
7879
+ tooltips: { rule: i18n.rulesTabTooltip },
7162
7880
  icons: icons.scope
7163
7881
  }
7164
7882
  ) }),
@@ -7254,7 +7972,7 @@ function RecordsAdminShellInner(props) {
7254
7972
  {
7255
7973
  icon: search ? icons.empty.search : icons.empty.default,
7256
7974
  title: search ? i18n.noResults : i18n.railEmptyTitle,
7257
- body: search ? void 0 : i18n.railEmptyBody
7975
+ body: search ? void 0 : isRuleTab ? i18n.rulesEmptyBody : i18n.railEmptyBody
7258
7976
  }
7259
7977
  )),
7260
7978
  !leftLoading && !leftError && leftItems.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -7272,6 +7990,7 @@ function RecordsAdminShellInner(props) {
7272
7990
  presentation: effectivePresentation,
7273
7991
  renderListRow,
7274
7992
  groupBy: effectiveGroupBy,
7993
+ renderGroupActions: renderRuleGroupActions,
7275
7994
  rowClipboard,
7276
7995
  i18n
7277
7996
  }
@@ -7415,6 +8134,22 @@ function RecordsAdminShellInner(props) {
7415
8134
  ] })
7416
8135
  ]
7417
8136
  }
8137
+ ),
8138
+ /* @__PURE__ */ jsx(
8139
+ RuleGroupEditDialog,
8140
+ {
8141
+ open: !!bulkRuleEditTarget,
8142
+ ctx,
8143
+ currentRule: bulkRuleEditTarget?.rule ?? null,
8144
+ recordIds: bulkRuleEditTarget?.recordIds ?? [],
8145
+ onClose: () => setBulkRuleEditTarget(null),
8146
+ onApplied: () => {
8147
+ recordList.refetch();
8148
+ queryClient.invalidateQueries({
8149
+ queryKey: scopeCountsQueryKey(ctx.collectionId, ctx.appId, ctx.recordType)
8150
+ });
8151
+ }
8152
+ }
7418
8153
  )
7419
8154
  ]
7420
8155
  }
@@ -7839,8 +8574,8 @@ function useRecordEditor(args) {
7839
8574
  const anchors = parsedRefToScope(scope);
7840
8575
  const hasAnchors = !!(anchors.productId || anchors.variantId || anchors.batchId || anchors.proofId);
7841
8576
  const hasRule = !!(facetRule && facetRule.all && facetRule.all.length > 0);
7842
- const isRuleScope2 = scope.kind === "rule";
7843
- if (isRuleScope2 && !hasAnchors && !hasRule && !resolved.recordId && !createMode) {
8577
+ const isRuleScope = scope.kind === "rule";
8578
+ if (isRuleScope && !hasAnchors && !hasRule && !resolved.recordId && !createMode) {
7844
8579
  console.warn("[useRecordEditor] save() bailed \u2014 rule scope with no clauses, no recordId, not in createMode");
7845
8580
  return;
7846
8581
  }
@@ -7881,23 +8616,28 @@ function useRecordEditor(args) {
7881
8616
  });
7882
8617
  } else if (createMode) {
7883
8618
  await createRecord(ctx, {
7884
- ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
8619
+ // The framework does NOT write to `record.ref` that's a
8620
+ // host-owned field. Rule identity flows through `facetRule`
8621
+ // (content-addressed via `ruleHash`); record identity is the
8622
+ // UUID assigned by the server.
8623
+ ref: void 0,
7885
8624
  scope: anchors,
7886
8625
  data: value,
7887
8626
  facetRule
7888
8627
  });
7889
8628
  } else {
7890
8629
  await upsertRecord(ctx, {
7891
- // External ref only when the underlying scope already carries
7892
- // one (e.g. rule:{id} for an existing rule record). Empty for
7893
- // anchor-only writes — server addresses by anchors.
7894
- ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
8630
+ // See note above never write to `ref`.
8631
+ ref: void 0,
7895
8632
  scope: anchors,
7896
8633
  data: value,
7897
8634
  facetRule
7898
8635
  });
7899
8636
  }
7900
8637
  draftStore.clearDraft(draftKey);
8638
+ queryClient.invalidateQueries({
8639
+ queryKey: scopeCountsQueryKey(ctx.collectionId, ctx.appId, ctx.recordType)
8640
+ });
7901
8641
  onSaved?.();
7902
8642
  } catch (err) {
7903
8643
  setSavedSnapshot(previousSnapshot);
@@ -7926,6 +8666,9 @@ function useRecordEditor(args) {
7926
8666
  if (!resolved.recordId) return;
7927
8667
  await removeRecord(ctx, resolved.recordId);
7928
8668
  draftStore.clearDraft(draftKey);
8669
+ queryClient.invalidateQueries({
8670
+ queryKey: scopeCountsQueryKey(ctx.collectionId, ctx.appId, ctx.recordType)
8671
+ });
7929
8672
  onDeleted?.();
7930
8673
  }, [resolved.source, resolved.recordId, draftKey]);
7931
8674
  const prevDraftKeyRef = useRef(draftKey);
@@ -7991,7 +8734,9 @@ function useRecordEditor(args) {
7991
8734
  baselineFacetRule: savedFacetRule,
7992
8735
  createMode,
7993
8736
  scopeAnchors: anchors,
7994
- ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
8737
+ // Draft-store entry never carries a framework-derived `ref`. The
8738
+ // value belongs to the host; we don't even pass it through here.
8739
+ ref: void 0,
7995
8740
  saveKind,
7996
8741
  save: async () => {
7997
8742
  await save();
@@ -7999,8 +8744,8 @@ function useRecordEditor(args) {
7999
8744
  });
8000
8745
  }, [isDirty, value, facetRule, savedSnapshot, savedFacetRule, scope.raw, resolved.recordId, resolved.source, createMode, save]);
8001
8746
  const effectiveSource = optimisticSource ?? resolved.source;
8002
- const isRuleScope = scope.kind === "rule";
8003
- const ruleValid = !isRuleScope || isFacetRuleValid(facetRule);
8747
+ const hasRuleInFlight = !!(facetRule && facetRule.all && facetRule.all.length > 0);
8748
+ const ruleValid = !hasRuleInFlight || isFacetRuleValid(facetRule);
8004
8749
  const canSave = ruleValid;
8005
8750
  const cannotSaveReason = !ruleValid ? "Pick at least one value for every facet in the rule before saving." : void 0;
8006
8751
  return {
@@ -8263,6 +9008,6 @@ function useMergedRecord(args) {
8263
9008
  };
8264
9009
  }
8265
9010
 
8266
- export { ALL_ITEM_VIEWS, ALL_PRESENTATIONS, BatchList, BulkActionsMenu, DEFAULT_DEEP_LINK_PARAM_NAMES, DEFAULT_I18N, DEFAULT_ICONS, DefaultItemCards, DefaultItemTable, DefaultRecordCard, DefaultRecordRow, DeleteButton, DirtyDraftProvider, DrawerPreview, EditorItemNav, EmptyState, ErrorState, FacetList, InheritanceMarker, InheritanceProvider, InlinePreview, IntroCard, ItemListView, ItemViewSwitcher, LoadingState, PresentationSwitcher, PreviewScopePicker, PreviewToggleButton, ProductDrillDown, ProductList, RecordBrowser, RecordEditor, RecordList, RecordsAdminShell, ResolvedPreview, ScopeBreadcrumb, ScopeTabs, SiblingRail, SidePreview, StatusDot, StatusFilterPills, StatusIcon, TabbedPreview, UtilityRow, VariantList, buildDraftKey, buildRef, checkPasteCompatibility, cloneValue, createDefaultDeepLinkAdapter, createPostMessageDeepLinkAdapter, downloadBlob, exportCsv, importCsv, isInSmartLinksIframe, mergeIcons, parseRef, pickHeaderIcon, resolutionChain, resolveRecord, statusToneLabel, useCollectedRecords, useCollectionItems, useDeepLinkState, useDirtyDraft, useDirtyDraftActions, useDirtyDraftStore, useDirtyDrafts, useDirtyNavigation, useFacetBrowse, useIntroDismissed, useItemViewPref, useMergedRecord, usePresentationPref, useProductBrowse, useProductChildren, useRecordClipboard, useRecordEditor, useRecordList, useResolveAllRecords, useResolvedRecord, useRulePreview, useScopeProbe, useUnsavedGuard };
9011
+ export { ALL_ITEM_VIEWS, ALL_PRESENTATIONS, BatchList, BulkActionsMenu, DEFAULT_DEEP_LINK_PARAM_NAMES, DEFAULT_I18N, DEFAULT_ICONS, DefaultItemCards, DefaultItemTable, DefaultRecordCard, DefaultRecordRow, DeleteButton, DirtyDraftProvider, DrawerPreview, EditorItemNav, EmptyState, ErrorState, FacetList, InheritanceMarker, InheritanceProvider, InlinePreview, IntroCard, ItemListView, ItemViewSwitcher, LoadingState, PresentationSwitcher, PreviewScopePicker, PreviewToggleButton, ProductDrillDown, ProductList, RecordBrowser, RecordEditor, RecordList, RecordsAdminShell, ResolvedPreview, ScopeBreadcrumb, ScopeTabs, SiblingRail, SidePreview, StatusDot, StatusFilterPills, StatusIcon, TabbedPreview, UtilityRow, VariantList, buildDraftKey, buildRef, checkPasteCompatibility, cloneValue, createDefaultDeepLinkAdapter, createPostMessageDeepLinkAdapter, downloadBlob, exportCsv, importCsv, isInSmartLinksIframe, mergeIcons, normaliseRule, parseRef, pickHeaderIcon, resolutionChain, resolveRecord, ruleHash, rulesEqual, scopeCountsQueryKey, statusToneLabel, summariseRule, useCollectedRecords, useCollectionItems, useDeepLinkState, useDirtyDraft, useDirtyDraftActions, useDirtyDraftStore, useDirtyDrafts, useDirtyNavigation, useFacetBrowse, useIntroDismissed, useItemViewPref, useMergedRecord, usePresentationPref, useProductBrowse, useProductChildren, useRecordClipboard, useRecordEditor, useRecordList, useResolveAllRecords, useResolvedRecord, useRulePreview, useScopeCounts, useScopeProbe, useUnsavedGuard };
8267
9012
  //# sourceMappingURL=index.js.map
8268
9013
  //# sourceMappingURL=index.js.map