@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.
- package/dist/chunk-5UQQYXCX.js +3 -0
- package/dist/chunk-5UQQYXCX.js.map +1 -0
- package/dist/{chunk-UDYVH7QF.js → chunk-CBIKDA3S.js} +34 -3
- package/dist/chunk-CBIKDA3S.js.map +1 -0
- package/dist/components/AssetPicker/index.css +1505 -0
- package/dist/components/AssetPicker/index.css.map +1 -0
- package/dist/components/AssetPicker/index.js +2 -2
- package/dist/components/ConditionsEditor/index.css +1505 -0
- package/dist/components/ConditionsEditor/index.css.map +1 -0
- package/dist/components/ConditionsEditor/index.js +1 -1
- package/dist/components/FontPicker/index.css +1505 -0
- package/dist/components/FontPicker/index.css.map +1 -0
- package/dist/components/FontPicker/index.js +1 -1
- package/dist/components/IconPicker/index.css +1505 -0
- package/dist/components/IconPicker/index.css.map +1 -0
- package/dist/components/IconPicker/index.js +1 -1
- package/dist/components/RecordsAdmin/index.css +3646 -0
- package/dist/components/RecordsAdmin/index.css.map +1 -0
- package/dist/components/RecordsAdmin/index.d.ts +113 -9
- package/dist/components/RecordsAdmin/index.js +875 -130
- package/dist/components/RecordsAdmin/index.js.map +1 -1
- package/dist/index.css +1505 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-NSQRNFT2.js +0 -28
- package/dist/chunk-NSQRNFT2.js.map +0 -1
- package/dist/chunk-UDYVH7QF.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
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,
|
|
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:
|
|
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 ??
|
|
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
|
|
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:
|
|
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
|
-
...
|
|
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 =
|
|
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,
|
|
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 ?? ""}
|
|
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: [...
|
|
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
|
|
720
|
+
const synthScopeRaw = buildRef({ facetId: facetKey, facetValue: valueKey });
|
|
607
721
|
return {
|
|
608
722
|
id: null,
|
|
609
|
-
ref,
|
|
610
|
-
scope: parseRef(
|
|
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
|
|
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
|
-
|
|
810
|
+
const k = anchorKey(scaffold.scope);
|
|
811
|
+
return existingByAnchor.get(k) ?? scaffold;
|
|
695
812
|
})
|
|
696
813
|
);
|
|
697
|
-
const
|
|
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 ?? ""}
|
|
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:
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
2287
|
-
//
|
|
2288
|
-
//
|
|
2289
|
-
//
|
|
2290
|
-
|
|
2404
|
+
// `record.ref` is a host-owned external handle. The framework
|
|
2405
|
+
// never writes to it — rule 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.
|
|
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
|
-
|
|
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 &&
|
|
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 &&
|
|
3271
|
+
const dirtyAnchorMatch = dirtyAnchorKey != null && !!itemAnchorKey && itemAnchorKey === dirtyAnchorKey;
|
|
3129
3272
|
const idInStore = !!(item.id && dirtyKeys?.has(item.id));
|
|
3130
|
-
const
|
|
3131
|
-
const errorInStore = !!(item.id && errorKeys?.has(item.id) ||
|
|
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 ||
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
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
|
|
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
|
-
() => [...
|
|
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
|
|
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 =
|
|
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
|
|
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({
|
|
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
|
-
|
|
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: () =>
|
|
4158
|
-
className: "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
5268
|
+
key
|
|
4826
5269
|
);
|
|
4827
5270
|
}
|
|
4828
5271
|
return /* @__PURE__ */ jsxs(
|
|
@@ -4854,7 +5297,7 @@ function DefaultItemCards({
|
|
|
4854
5297
|
)
|
|
4855
5298
|
]
|
|
4856
5299
|
},
|
|
4857
|
-
|
|
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) ||
|
|
5058
|
-
const hasError = !!(id && errorKeys?.has(id) ||
|
|
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
|
-
) },
|
|
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:
|
|
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 (!
|
|
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:
|
|
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:
|
|
6707
|
-
|
|
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
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
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
|
|
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
|
|
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
|
|
7843
|
-
if (
|
|
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
|
-
|
|
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
|
-
//
|
|
7892
|
-
|
|
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
|
-
|
|
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
|
|
8003
|
-
const ruleValid = !
|
|
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
|