@proveanything/smartlinks-utils-ui 0.7.8 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
- import { styleInject } from '../../chunk-B34MMOND.js';
1
+ import { styleInject } from '../../chunk-F2YH3NVP.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
- import { listRecords, parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, updateRecord, createRecord, upsertRecord, removeRecord } from '../../chunk-KFKVGUUP.js';
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
- import { createContext, useMemo, useState, useEffect, useCallback, useRef, useContext, useSyncExternalStore, createElement, useId } from 'react';
7
+ import { createContext, useState, useEffect, useCallback, useMemo, useRef, useContext, useSyncExternalStore, createElement, useId } from 'react';
8
8
  import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Rows3, List, ChevronRight, Eraser, ClipboardPaste, Box, X, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, Search, CornerDownLeft, Circle, AlertCircle, Undo2, Save, Loader2, XCircle, ArrowRight, BookOpen, Target, Settings2 } from 'lucide-react';
9
- import { useQueryClient, useInfiniteQuery, useQuery } from '@tanstack/react-query';
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';
12
12
 
@@ -223,155 +223,6 @@ var resolutionChain = (target, supportedScopes) => {
223
223
  }
224
224
  return Array.from(new Set(chain));
225
225
  };
226
- var defaultClassify = (r) => {
227
- if (!r.data) return "empty";
228
- const keys = Object.keys(r.data);
229
- if (keys.length === 0) return "empty";
230
- return "configured";
231
- };
232
- var matchesScope = (kind, rec, _p) => {
233
- const hasRule = !!rec.facetRule;
234
- if (kind === "rule") return hasRule;
235
- if (hasRule) return false;
236
- const productId = rec.productId ?? void 0;
237
- const variantId = rec.variantId ?? void 0;
238
- const batchId = rec.batchId ?? void 0;
239
- const proofId = rec.proofId ?? void 0;
240
- if (kind === "collection") {
241
- return !productId && !variantId && !batchId && !proofId;
242
- }
243
- if (kind === "product") return !!productId && !variantId && !batchId;
244
- if (kind === "variant") return !!variantId;
245
- if (kind === "batch") return !!batchId;
246
- return false;
247
- };
248
- var matchesContext = (p, ctx) => {
249
- if (!ctx) return true;
250
- if (ctx.productId && p.productId !== ctx.productId) return false;
251
- if (ctx.variantId && p.variantId !== ctx.variantId) return false;
252
- if (ctx.batchId && p.batchId !== ctx.batchId) return false;
253
- return true;
254
- };
255
- var toSummary = (rec) => {
256
- const ref = rec.ref ?? "";
257
- const productId = rec.productId ?? void 0;
258
- const variantId = rec.variantId ?? void 0;
259
- const batchId = rec.batchId ?? void 0;
260
- const proofId = rec.proofId ?? void 0;
261
- const scope = {
262
- kind: batchId ? "batch" : variantId ? "variant" : productId ? "product" : "collection",
263
- raw: ref,
264
- productId,
265
- variantId,
266
- batchId,
267
- proofId
268
- };
269
- const facetRule = rec.facetRule ?? null;
270
- if (facetRule) scope.kind = "rule";
271
- const ruleLabel = facetRule && facetRule.all && facetRule.all.length > 0 ? facetRule.all.length === 1 ? "Rule \xB7 1 facet" : `Rule \xB7 ${facetRule.all.length} facets` : null;
272
- const fallbackLabel = scope.batchId ?? scope.variantId ?? scope.productId ?? ruleLabel ?? (ref || "Global record");
273
- return {
274
- id: rec.id,
275
- ref,
276
- scope,
277
- data: rec.data ?? null,
278
- status: "configured",
279
- label: fallbackLabel,
280
- updatedAt: rec.updatedAt,
281
- facetRule
282
- };
283
- };
284
- var QK_BASE = ["records-admin", "list"];
285
- var useRecordList = (args) => {
286
- const {
287
- ctx,
288
- scopeKind,
289
- search = "",
290
- filter = "all",
291
- classify: classify2,
292
- enabled = true,
293
- scaffolder,
294
- contextScope,
295
- pageSize = 100
296
- } = args;
297
- const queryClient = useQueryClient();
298
- const queryKey = useMemo(
299
- () => [
300
- ...QK_BASE,
301
- ctx.collectionId,
302
- ctx.appId,
303
- ctx.recordType,
304
- scopeKind,
305
- contextScope?.productId ?? null,
306
- contextScope?.variantId ?? null,
307
- contextScope?.batchId ?? null
308
- ],
309
- [ctx.collectionId, ctx.appId, ctx.recordType, scopeKind, contextScope?.productId, contextScope?.variantId, contextScope?.batchId]
310
- );
311
- const query = useInfiniteQuery({
312
- queryKey,
313
- enabled,
314
- initialPageParam: 0,
315
- queryFn: async ({ pageParam }) => {
316
- const offset = pageParam;
317
- const { data, total: total2, hasMore } = await listRecords(ctx, { limit: pageSize, offset });
318
- return { data, total: total2, hasMore, nextOffset: offset + data.length };
319
- },
320
- getNextPageParam: (last) => last.hasMore ? last.nextOffset : void 0,
321
- staleTime: 3e4
322
- });
323
- const [scaffolded, setScaffolded] = useState(null);
324
- const rawItems = useMemo(() => {
325
- const all = query.data?.pages.flatMap((p) => p.data) ?? [];
326
- const cls = classify2 ?? defaultClassify;
327
- 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) }));
328
- }, [query.data, scopeKind, classify2, contextScope]);
329
- useEffect(() => {
330
- if (!scaffolder) {
331
- setScaffolded(null);
332
- return;
333
- }
334
- let cancelled = false;
335
- Promise.resolve(scaffolder(rawItems)).then((next) => {
336
- if (!cancelled) setScaffolded(next);
337
- });
338
- return () => {
339
- cancelled = true;
340
- };
341
- }, [rawItems, scaffolder]);
342
- const items = scaffolded ?? rawItems;
343
- const filtered = useMemo(() => {
344
- let out = items;
345
- if (filter !== "all") out = out.filter((r) => r.status === filter);
346
- if (search.trim()) {
347
- const q = search.trim().toLowerCase();
348
- out = out.filter((r) => `${r.label} ${r.subtitle ?? ""} ${r.ref}`.toLowerCase().includes(q));
349
- }
350
- return out;
351
- }, [items, filter, search]);
352
- const counts = useMemo(() => ({
353
- all: items.length,
354
- configured: items.filter((r) => r.status === "configured").length,
355
- partial: items.filter((r) => r.status === "partial").length,
356
- empty: items.filter((r) => r.status === "empty").length
357
- }), [items]);
358
- const refetch = useCallback(() => {
359
- queryClient.invalidateQueries({ queryKey: [...QK_BASE, ctx.collectionId, ctx.appId, ctx.recordType] });
360
- }, [queryClient, ctx.collectionId, ctx.appId, ctx.recordType]);
361
- const total = query.data?.pages[query.data.pages.length - 1]?.total ?? items.length;
362
- return {
363
- allItems: items,
364
- items: filtered,
365
- total,
366
- counts,
367
- isLoading: query.isLoading,
368
- error: query.error ?? null,
369
- refetch,
370
- hasNextPage: query.hasNextPage,
371
- isFetchingNextPage: query.isFetchingNextPage,
372
- fetchNextPage: query.fetchNextPage
373
- };
374
- };
375
226
 
376
227
  // src/components/RecordsAdmin/data/resolveRecord.ts
377
228
  var resolveRecord = async (args) => {
@@ -525,563 +376,303 @@ function useResolvedRecord(args) {
525
376
  error: query.error ?? null
526
377
  };
527
378
  }
528
- var isFacetRuleValid = (rule) => {
529
- if (!rule || !Array.isArray(rule.all) || rule.all.length === 0) return false;
530
- return rule.all.every(
531
- (c) => !!c.facetKey && Array.isArray(c.anyOf) && c.anyOf.length > 0
532
- );
533
- };
534
- function useRulePreview(args) {
535
- const {
536
- SL,
537
- collectionId,
538
- appId,
539
- rule,
540
- limit = 20,
541
- debounceMs = 350,
542
- enabled = true
543
- } = args;
544
- const [debouncedRule, setDebouncedRule] = useState(rule);
545
- const timer = useRef(null);
379
+ var RT_KEY = (recordType) => recordType ?? "_default";
380
+ var lsKey = (appId, recordType) => `ra:intro:${appId}:${RT_KEY(recordType)}`;
381
+ var useIntroDismissed = (SL, collectionId, appId, recordType) => {
382
+ const [dismissed, setDismissed] = useState(() => {
383
+ try {
384
+ return localStorage.getItem(lsKey(appId, recordType)) === "1";
385
+ } catch {
386
+ return false;
387
+ }
388
+ });
546
389
  useEffect(() => {
547
- if (timer.current) clearTimeout(timer.current);
548
- timer.current = setTimeout(() => setDebouncedRule(rule), debounceMs);
390
+ let cancelled = false;
391
+ (async () => {
392
+ try {
393
+ const cfg = await SL?.appConfiguration?.getConfig?.({ collectionId, appId, admin: true });
394
+ if (cancelled) return;
395
+ const flag = cfg?._meta?.introDismissed?.[RT_KEY(recordType)];
396
+ if (flag) setDismissed(true);
397
+ } catch {
398
+ }
399
+ })();
549
400
  return () => {
550
- if (timer.current) clearTimeout(timer.current);
401
+ cancelled = true;
551
402
  };
552
- }, [rule, debounceMs]);
553
- const valid = isFacetRuleValid(debouncedRule);
403
+ }, [SL, collectionId, appId, recordType]);
404
+ const dismiss = useCallback(async () => {
405
+ setDismissed(true);
406
+ try {
407
+ localStorage.setItem(lsKey(appId, recordType), "1");
408
+ } catch {
409
+ }
410
+ try {
411
+ const cfg = await SL?.appConfiguration?.getConfig?.({ collectionId, appId, admin: true }).catch(() => ({}));
412
+ const next = {
413
+ ...cfg ?? {},
414
+ _meta: {
415
+ ...cfg?._meta ?? {},
416
+ introDismissed: { ...cfg?._meta?.introDismissed ?? {}, [RT_KEY(recordType)]: true }
417
+ }
418
+ };
419
+ await SL?.appConfiguration?.setConfig?.({ collectionId, appId, admin: true, config: next });
420
+ } catch {
421
+ }
422
+ }, [SL, collectionId, appId, recordType]);
423
+ const undismiss = useCallback(() => {
424
+ setDismissed(false);
425
+ try {
426
+ localStorage.removeItem(lsKey(appId, recordType));
427
+ } catch {
428
+ }
429
+ }, [appId, recordType]);
430
+ return { dismissed, dismiss, undismiss };
431
+ };
432
+ var useScopeProbe = ({ SL, collectionId, admin = true, enabled = true }) => {
554
433
  const query = useQuery({
555
- queryKey: ["records-admin", "preview-rule", collectionId, appId, debouncedRule, limit],
556
- enabled: enabled && !!collectionId && !!appId && valid,
557
- staleTime: 3e4,
558
- queryFn: () => SL.app.records.previewRule(collectionId, appId, {
559
- facetRule: debouncedRule,
560
- limit
561
- })
434
+ queryKey: ["records-admin", "scope-probe", collectionId, admin],
435
+ enabled: enabled && !!collectionId && !!SL?.collection?.get,
436
+ staleTime: 5 * 6e4,
437
+ queryFn: async () => {
438
+ const c = await SL.collection.get(collectionId, admin);
439
+ return {
440
+ hasVariants: !!c?.variants,
441
+ hasBatches: !!c?.batches
442
+ };
443
+ }
562
444
  });
563
445
  return {
564
- totalMatches: query.data?.total ?? null,
565
- sampleProductIds: (query.data?.matchingProducts ?? []).map((p) => p.productId),
566
- isLoading: query.isFetching,
567
- isStale: rule !== debouncedRule,
446
+ hasVariants: query.data?.hasVariants ?? false,
447
+ hasBatches: query.data?.hasBatches ?? false,
448
+ isLoading: query.isLoading,
568
449
  error: query.error ?? null
569
450
  };
570
- }
571
- var createStore = () => {
572
- const map = /* @__PURE__ */ new Map();
573
- const listeners = /* @__PURE__ */ new Set();
574
- let nextOrder = 0;
575
- let cachedList = [];
576
- const recompute = () => {
577
- cachedList = Array.from(map.values()).sort((a, b) => a.order - b.order);
578
- };
579
- const emit = () => {
580
- recompute();
581
- listeners.forEach((l) => l());
582
- };
583
- return {
584
- list: () => cachedList,
585
- get: (key) => map.get(key),
586
- has: (key) => map.has(key),
587
- upsertDraft(input) {
588
- const existing = map.get(input.key);
589
- const order = existing?.order ?? input.order ?? nextOrder++;
590
- const status = input.status ?? existing?.status ?? "dirty";
591
- map.set(input.key, {
592
- ...input,
593
- order,
594
- status
595
- });
596
- emit();
597
- },
598
- setStatus(key, status, error) {
599
- const existing = map.get(key);
600
- if (!existing) return;
601
- map.set(key, { ...existing, status, error });
602
- emit();
603
- },
604
- clearDraft(key) {
605
- if (map.delete(key)) emit();
606
- },
607
- clearAll() {
608
- if (map.size === 0) return;
609
- map.clear();
610
- emit();
611
- },
612
- subscribe(listener) {
613
- listeners.add(listener);
614
- return () => {
615
- listeners.delete(listener);
616
- };
617
- }
618
- };
619
- };
620
- var DirtyDraftContext = createContext(null);
621
- var DirtyDraftProvider = ({ children }) => {
622
- const storeRef = useRef(null);
623
- if (!storeRef.current) storeRef.current = createStore();
624
- return /* @__PURE__ */ jsx(DirtyDraftContext.Provider, { value: storeRef.current, children });
625
- };
626
- var useDirtyDraftStore = () => {
627
- const ctx = useContext(DirtyDraftContext);
628
- const fallbackRef = useRef(null);
629
- if (!ctx && !fallbackRef.current) fallbackRef.current = createStore();
630
- return ctx ?? fallbackRef.current;
631
- };
632
- var useDirtyDrafts = () => {
633
- const store = useDirtyDraftStore();
634
- return useSyncExternalStore(
635
- useCallback((cb) => store.subscribe(cb), [store]),
636
- useCallback(() => store.list(), [store]),
637
- useCallback(() => store.list(), [store])
638
- );
639
- };
640
- var useDirtyDraft = (key) => {
641
- const store = useDirtyDraftStore();
642
- const getSnapshot = useCallback(
643
- () => key ? store.get(key) : void 0,
644
- [store, key]
645
- );
646
- return useSyncExternalStore(
647
- useCallback((cb) => store.subscribe(cb), [store]),
648
- getSnapshot,
649
- getSnapshot
650
- );
651
451
  };
652
- var buildDraftKey = (opts) => {
653
- if (opts.recordId) return opts.recordId;
654
- const kind = opts.draftKind ?? "rec";
655
- return `draft:${kind}:${opts.scopeRaw}`;
656
- };
657
- var useDirtyDraftActions = () => {
658
- const store = useDirtyDraftStore();
659
- const drafts = useDirtyDrafts();
660
- const count = useMemo(() => drafts.filter((d) => d.status !== "saved").length, [drafts]);
661
- const errorCount = useMemo(() => drafts.filter((d) => d.status === "error").length, [drafts]);
662
- return { drafts, count, errorCount, store };
452
+ var defaultClassify = (r) => {
453
+ if (!r.data) return "empty";
454
+ const keys = Object.keys(r.data);
455
+ if (keys.length === 0) return "empty";
456
+ return "configured";
663
457
  };
664
-
665
- // src/components/RecordsAdmin/hooks/useRecordEditor.ts
666
- var isEqual = (a, b) => {
667
- try {
668
- return JSON.stringify(a) === JSON.stringify(b);
669
- } catch {
670
- return false;
458
+ var matchesScope = (kind, rec, _p) => {
459
+ const hasRule = !!rec.facetRule;
460
+ if (kind === "rule") return hasRule;
461
+ if (hasRule) return false;
462
+ const productId = rec.productId ?? void 0;
463
+ const variantId = rec.variantId ?? void 0;
464
+ const batchId = rec.batchId ?? void 0;
465
+ const proofId = rec.proofId ?? void 0;
466
+ if (kind === "collection") {
467
+ return !productId && !variantId && !batchId && !proofId;
671
468
  }
469
+ if (kind === "product") return !!productId && !variantId && !batchId;
470
+ if (kind === "variant") return !!variantId;
471
+ if (kind === "batch") return !!batchId;
472
+ return false;
672
473
  };
673
- var cloneSeed = (resolved, defaultData) => {
674
- if (resolved.source === "self") return resolved.data;
675
- if (resolved.source === "inherited" && resolved.data != null) {
676
- try {
677
- return structuredClone(resolved.data);
678
- } catch {
679
- return JSON.parse(JSON.stringify(resolved.data));
680
- }
681
- }
682
- return defaultData?.() ?? {};
474
+ var matchesContext = (p, ctx) => {
475
+ if (!ctx) return true;
476
+ if (ctx.productId && p.productId !== ctx.productId) return false;
477
+ if (ctx.variantId && p.variantId !== ctx.variantId) return false;
478
+ if (ctx.batchId && p.batchId !== ctx.batchId) return false;
479
+ return true;
683
480
  };
684
- function useRecordEditor(args) {
481
+ var toSummary = (rec) => {
482
+ const ref = rec.ref ?? "";
483
+ const productId = rec.productId ?? void 0;
484
+ const variantId = rec.variantId ?? void 0;
485
+ const batchId = rec.batchId ?? void 0;
486
+ const proofId = rec.proofId ?? void 0;
487
+ const scope = {
488
+ kind: batchId ? "batch" : variantId ? "variant" : productId ? "product" : "collection",
489
+ raw: ref,
490
+ productId,
491
+ variantId,
492
+ batchId,
493
+ proofId
494
+ };
495
+ const facetRule = rec.facetRule ?? null;
496
+ if (facetRule) scope.kind = "rule";
497
+ const ruleLabel = facetRule && facetRule.all && facetRule.all.length > 0 ? facetRule.all.length === 1 ? "Rule \xB7 1 facet" : `Rule \xB7 ${facetRule.all.length} facets` : null;
498
+ const fallbackLabel = scope.batchId ?? scope.variantId ?? scope.productId ?? ruleLabel ?? (ref || "Global record");
499
+ return {
500
+ id: rec.id,
501
+ ref,
502
+ scope,
503
+ data: rec.data ?? null,
504
+ status: "configured",
505
+ label: fallbackLabel,
506
+ updatedAt: rec.updatedAt,
507
+ facetRule
508
+ };
509
+ };
510
+ var QK_BASE = ["records-admin", "list"];
511
+ var useRecordList = (args) => {
685
512
  const {
686
513
  ctx,
687
- scope,
688
- resolved,
689
- defaultData,
690
- onSaved,
691
- onDeleted,
692
- onSaveError,
693
- reseed = "always",
694
- initialFacetRule = null,
695
- createMode = false,
696
- deriveDraftLabel
514
+ scopeKind,
515
+ search = "",
516
+ filter = "all",
517
+ classify: classify2,
518
+ enabled = true,
519
+ scaffolder,
520
+ contextScope,
521
+ pageSize = 100
697
522
  } = args;
698
523
  const queryClient = useQueryClient();
699
- const draftStore = useDirtyDraftStore();
700
- const draftKey = buildDraftKey({
701
- recordId: resolved.recordId,
702
- scopeRaw: scope.raw,
703
- draftKind: scope.kind
704
- });
705
- const initialDraft = draftStore.get(draftKey);
706
- const cleanSeed = cloneSeed(resolved, defaultData);
707
- const seed = initialDraft && reseed === "preserve-dirty" ? initialDraft.value : cleanSeed;
708
- const seedFacetRule = initialDraft && reseed === "preserve-dirty" ? initialDraft.facetRule : initialFacetRule;
709
- const [value, setValue] = useState(seed);
710
- const [savedSnapshot, setSavedSnapshot] = useState(
711
- initialDraft ? initialDraft.baseline : cleanSeed
712
- );
713
- const [facetRule, setFacetRule] = useState(seedFacetRule);
714
- const [savedFacetRule, setSavedFacetRule] = useState(
715
- initialDraft ? initialDraft.baselineFacetRule : initialFacetRule
524
+ const queryKey = useMemo(
525
+ () => [
526
+ ...QK_BASE,
527
+ ctx.collectionId,
528
+ ctx.appId,
529
+ ctx.recordType,
530
+ scopeKind,
531
+ contextScope?.productId ?? null,
532
+ contextScope?.variantId ?? null,
533
+ contextScope?.batchId ?? null
534
+ ],
535
+ [ctx.collectionId, ctx.appId, ctx.recordType, scopeKind, contextScope?.productId, contextScope?.variantId, contextScope?.batchId]
716
536
  );
717
- const [userInteracted, setUserInteracted] = useState(!!initialDraft);
718
- const [optimisticSource, setOptimisticSource] = useState(null);
719
- const [isSaving, setIsSaving] = useState(false);
720
- const [saveError, setSaveError] = useState(null);
721
- const valueRef = useRef(seed);
722
- useEffect(() => {
723
- valueRef.current = value;
724
- }, [value]);
537
+ const query = useInfiniteQuery({
538
+ queryKey,
539
+ enabled,
540
+ initialPageParam: 0,
541
+ queryFn: async ({ pageParam }) => {
542
+ const offset = pageParam;
543
+ const { data, total: total2, hasMore } = await listRecords(ctx, { limit: pageSize, offset });
544
+ return { data, total: total2, hasMore, nextOffset: offset + data.length };
545
+ },
546
+ getNextPageParam: (last) => last.hasMore ? last.nextOffset : void 0,
547
+ staleTime: 3e4
548
+ });
549
+ const [scaffolded, setScaffolded] = useState(null);
550
+ const rawItems = useMemo(() => {
551
+ const all = query.data?.pages.flatMap((p) => p.data) ?? [];
552
+ const cls = classify2 ?? defaultClassify;
553
+ return all.map((rec) => ({ rec, summary: toSummary(rec) })).filter(({ rec, summary }) => matchesScope(scopeKind, rec, summary.scope)).filter(({ summary }) => matchesContext(summary.scope, contextScope)).map(({ summary }) => ({ ...summary, status: cls(summary) }));
554
+ }, [query.data, scopeKind, classify2, contextScope]);
725
555
  useEffect(() => {
726
- const fresh = cloneSeed(resolved, defaultData);
727
- if (reseed === "preserve-dirty") {
728
- const hasUnsaved = !isEqual(valueRef.current, savedSnapshot);
729
- if (!hasUnsaved) setValue(fresh);
730
- } else {
731
- setValue(fresh);
732
- }
733
- setSavedSnapshot(fresh);
734
- setFacetRule(initialFacetRule);
735
- setSavedFacetRule(initialFacetRule);
736
- setOptimisticSource(null);
737
- setUserInteracted(false);
738
- }, [scope.raw, resolved.source, resolved.sourceRef]);
739
- const valueDiff = !isEqual(value, savedSnapshot) || !isEqual(facetRule, savedFacetRule);
740
- const isDirty = userInteracted && valueDiff;
741
- const handleChange = useCallback((next) => {
742
- setUserInteracted(true);
743
- setValue(next);
744
- }, []);
745
- const handleFacetRuleChange = useCallback((next) => {
746
- setUserInteracted(true);
747
- setFacetRule(next);
748
- }, []);
749
- const save = useCallback(async () => {
750
- const anchors = parsedRefToScope(scope);
751
- const hasAnchors = !!(anchors.productId || anchors.variantId || anchors.batchId || anchors.proofId);
752
- const hasRule = !!(facetRule && facetRule.all && facetRule.all.length > 0);
753
- const isRuleScope2 = scope.kind === "rule";
754
- if (isRuleScope2 && !hasAnchors && !hasRule && !resolved.recordId && !createMode) {
755
- console.warn("[useRecordEditor] save() bailed \u2014 rule scope with no clauses, no recordId, not in createMode");
556
+ if (!scaffolder) {
557
+ setScaffolded(null);
756
558
  return;
757
559
  }
758
- const previousSnapshot = savedSnapshot;
759
- const previousRuleSnapshot = savedFacetRule;
760
- resolved.source;
761
- const cacheKey = resolvedRecordQueryKey({
762
- collectionId: ctx.collectionId,
763
- appId: ctx.appId,
764
- recordType: ctx.recordType,
765
- productId: scope.productId,
766
- variantId: scope.variantId,
767
- batchId: scope.batchId,
768
- facetId: scope.facetId,
769
- facetValue: scope.facetValue,
770
- proofId: scope.proofId,
771
- recordId: resolved.recordId,
772
- withParent: true
773
- });
774
- const previousCache = queryClient.getQueryData(cacheKey);
775
- setSaveError(null);
776
- setIsSaving(true);
777
- setSavedSnapshot(value);
778
- setSavedFacetRule(facetRule);
779
- setOptimisticSource("self");
780
- queryClient.setQueryData(cacheKey, {
781
- data: value,
782
- source: "self",
783
- sourceRef: scope.raw,
784
- recordId: resolved.recordId,
785
- parentValue: previousCache?.parentValue ?? resolved.parentValue
560
+ let cancelled = false;
561
+ Promise.resolve(scaffolder(rawItems)).then((next) => {
562
+ if (!cancelled) setScaffolded(next);
786
563
  });
787
- try {
788
- if (resolved.recordId && resolved.source === "self") {
789
- await updateRecord(ctx, resolved.recordId, {
790
- data: value,
791
- facetRule
792
- });
793
- } else if (createMode) {
794
- await createRecord(ctx, {
795
- ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
796
- scope: anchors,
797
- data: value,
798
- facetRule
799
- });
800
- } else {
801
- await upsertRecord(ctx, {
802
- // External ref only when the underlying scope already carries
803
- // one (e.g. rule:{id} for an existing rule record). Empty for
804
- // anchor-only writes server addresses by anchors.
805
- ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
806
- scope: anchors,
807
- data: value,
808
- facetRule
809
- });
810
- }
811
- draftStore.clearDraft(draftKey);
812
- onSaved?.();
813
- } catch (err) {
814
- setSavedSnapshot(previousSnapshot);
815
- setSavedFacetRule(previousRuleSnapshot);
816
- setOptimisticSource(null);
817
- if (previousCache !== void 0) {
818
- queryClient.setQueryData(cacheKey, previousCache);
819
- } else {
820
- queryClient.invalidateQueries({ queryKey: cacheKey });
564
+ return () => {
565
+ cancelled = true;
566
+ };
567
+ }, [rawItems, scaffolder]);
568
+ const items = scaffolded ?? rawItems;
569
+ const filtered = useMemo(() => {
570
+ let out = items;
571
+ if (filter !== "all") out = out.filter((r) => r.status === filter);
572
+ if (search.trim()) {
573
+ const q = search.trim().toLowerCase();
574
+ out = out.filter((r) => `${r.label} ${r.subtitle ?? ""} ${r.ref}`.toLowerCase().includes(q));
575
+ }
576
+ return out;
577
+ }, [items, filter, search]);
578
+ const counts = useMemo(() => ({
579
+ all: items.length,
580
+ configured: items.filter((r) => r.status === "configured").length,
581
+ partial: items.filter((r) => r.status === "partial").length,
582
+ empty: items.filter((r) => r.status === "empty").length
583
+ }), [items]);
584
+ const refetch = useCallback(() => {
585
+ queryClient.invalidateQueries({ queryKey: [...QK_BASE, ctx.collectionId, ctx.appId, ctx.recordType] });
586
+ }, [queryClient, ctx.collectionId, ctx.appId, ctx.recordType]);
587
+ const total = query.data?.pages[query.data.pages.length - 1]?.total ?? items.length;
588
+ return {
589
+ allItems: items,
590
+ items: filtered,
591
+ total,
592
+ counts,
593
+ isLoading: query.isLoading,
594
+ error: query.error ?? null,
595
+ refetch,
596
+ hasNextPage: query.hasNextPage,
597
+ isFetchingNextPage: query.isFetchingNextPage,
598
+ fetchNextPage: query.fetchNextPage
599
+ };
600
+ };
601
+ var LOG = "[RecordsAdmin/useFacetBrowse]";
602
+ var QK = ["records-admin", "facet-browse"];
603
+ var toScaffoldSummary = (facet, value) => {
604
+ const facetKey = facet.key ?? "";
605
+ const valueKey = value.key ?? "";
606
+ const ref = buildRef({ facetId: facetKey, facetValue: valueKey });
607
+ return {
608
+ id: null,
609
+ ref,
610
+ scope: parseRef(ref),
611
+ data: null,
612
+ status: "empty",
613
+ label: value.name ?? valueKey ?? "Untitled value",
614
+ subtitle: facet.name ?? facetKey ?? void 0
615
+ };
616
+ };
617
+ var useFacetBrowse = ({
618
+ SL,
619
+ collectionId,
620
+ existing,
621
+ search = "",
622
+ filter = "all",
623
+ enabled = true
624
+ }) => {
625
+ const queryClient = useQueryClient();
626
+ const hasAdminList = !!SL?.facets?.list;
627
+ const hasPublicList = !!SL?.facets?.publicList;
628
+ const hasAnyList = hasAdminList || hasPublicList;
629
+ const queryEnabled = enabled && !!collectionId && hasAnyList;
630
+ const query = useQuery({
631
+ queryKey: [...QK, collectionId],
632
+ enabled: queryEnabled,
633
+ staleTime: 3e4,
634
+ queryFn: async () => {
635
+ const t0 = performance.now();
636
+ try {
637
+ if (SL?.facets?.list) {
638
+ console.info(`${LOG} \u2192 SL.facets.list("${collectionId}", { includeValues: true })`);
639
+ const res = await SL.facets.list(collectionId, { includeValues: true });
640
+ const items = res?.items ?? [];
641
+ console.info(
642
+ `${LOG} \u2190 SL.facets.list ok in ${Math.round(performance.now() - t0)}ms \u2014 ${items.length} facet(s)`,
643
+ items
644
+ );
645
+ return items;
646
+ }
647
+ if (SL?.facets?.publicList) {
648
+ console.info(`${LOG} \u2192 SL.facets.publicList("${collectionId}", { includeValues: true })`);
649
+ const res = await SL.facets.publicList(collectionId, { includeValues: true });
650
+ const items = res?.items ?? [];
651
+ console.info(
652
+ `${LOG} \u2190 SL.facets.publicList ok in ${Math.round(performance.now() - t0)}ms \u2014 ${items.length} facet(s)`,
653
+ items
654
+ );
655
+ return items;
656
+ }
657
+ console.warn(`${LOG} queryFn ran but no facets API is available on SL`);
658
+ return [];
659
+ } catch (err) {
660
+ console.error(`${LOG} \u2716 facets fetch failed`, err);
661
+ throw err;
821
662
  }
822
- setSaveError(err);
823
- onSaveError?.(err);
824
- throw err;
825
- } finally {
826
- setIsSaving(false);
827
663
  }
828
- }, [scope.raw, value, savedSnapshot, facetRule, savedFacetRule, resolved.source, resolved.parentValue, resolved.recordId, createMode]);
829
- const reset = useCallback(() => {
830
- setValue(savedSnapshot);
831
- setFacetRule(savedFacetRule);
832
- setUserInteracted(false);
833
- draftStore.clearDraft(draftKey);
834
- }, [savedSnapshot, savedFacetRule]);
835
- const remove = useCallback(async () => {
836
- if (resolved.source !== "self") return;
837
- if (!resolved.recordId) return;
838
- await removeRecord(ctx, resolved.recordId);
839
- draftStore.clearDraft(draftKey);
840
- onDeleted?.();
841
- }, [resolved.source, resolved.recordId]);
842
- const prevDraftKeyRef = useRef(draftKey);
843
- const prevScopeRawRef = useRef(scope.raw);
664
+ });
665
+ const lastLoggedRef = useRef("");
844
666
  useEffect(() => {
845
- const prevKey = prevDraftKeyRef.current;
846
- const prevScopeRaw = prevScopeRawRef.current;
847
- if (prevKey && prevKey !== draftKey && prevScopeRaw === scope.raw) {
848
- const stale = draftStore.get(prevKey);
849
- if (stale) draftStore.clearDraft(prevKey);
667
+ const signature = `${enabled}|${collectionId}|${hasAdminList}|${hasPublicList}`;
668
+ if (signature === lastLoggedRef.current) return;
669
+ lastLoggedRef.current = signature;
670
+ if (!enabled) {
671
+ console.info(`${LOG} skipped \u2014 enabled=false (shell not on facet tab yet)`);
672
+ return;
850
673
  }
851
- prevDraftKeyRef.current = draftKey;
852
- prevScopeRawRef.current = scope.raw;
853
- }, [draftKey, scope.raw]);
854
- useEffect(() => {
855
- if (!isDirty) {
856
- return;
857
- }
858
- const anchors = parsedRefToScope(scope);
859
- const saveKind = resolved.recordId && resolved.source === "self" ? "update" : createMode ? "create" : "upsert";
860
- const deriveLabel = () => {
861
- if (deriveDraftLabel) {
862
- try {
863
- const custom = deriveDraftLabel(value, scope);
864
- if (typeof custom === "string" && custom.trim()) return custom.trim();
865
- } catch {
866
- }
867
- }
868
- const KEYS = ["title", "name", "label", "heading", "question", "slug"];
869
- const pickString = (obj) => {
870
- if (!obj || typeof obj !== "object") return void 0;
871
- for (const key of KEYS) {
872
- const raw = obj[key];
873
- if (typeof raw === "string" && raw.trim()) return raw.trim();
874
- }
875
- return void 0;
876
- };
877
- const v = value;
878
- const top = pickString(v);
879
- if (top) return top;
880
- if (v && typeof v === "object") {
881
- for (const wrapper of ["display", "content", "meta", "data"]) {
882
- const nested = pickString(v[wrapper]);
883
- if (nested) return nested;
884
- }
885
- }
886
- if (scope.raw?.startsWith("item:")) return "Untitled item";
887
- if (scope.kind === "rule") return "Rule";
888
- if (scope.kind && scope.kind !== "collection") {
889
- return scope.kind.charAt(0).toUpperCase() + scope.kind.slice(1);
890
- }
891
- return "Default";
892
- };
893
- draftStore.upsertDraft({
894
- key: draftKey,
895
- label: deriveLabel(),
896
- context: scope.kind,
897
- scopeRaw: scope.raw,
898
- recordId: resolved.recordId,
899
- value,
900
- facetRule,
901
- baseline: savedSnapshot,
902
- baselineFacetRule: savedFacetRule,
903
- createMode,
904
- scopeAnchors: anchors,
905
- ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
906
- saveKind,
907
- save: async () => {
908
- await save();
909
- }
910
- });
911
- }, [isDirty, value, facetRule, savedSnapshot, savedFacetRule, scope.raw, resolved.recordId, resolved.source, createMode, save]);
912
- const effectiveSource = optimisticSource ?? resolved.source;
913
- const isRuleScope = scope.kind === "rule";
914
- const ruleValid = !isRuleScope || isFacetRuleValid(facetRule);
915
- const canSave = ruleValid;
916
- const cannotSaveReason = !ruleValid ? "Pick at least one value for every facet in the rule before saving." : void 0;
917
- return {
918
- value,
919
- onChange: handleChange,
920
- source: effectiveSource,
921
- recordId: resolved.recordId,
922
- parentValue: resolved.parentValue,
923
- scope,
924
- isDirty,
925
- save,
926
- reset,
927
- remove,
928
- canRemove: effectiveSource === "self",
929
- canSave,
930
- cannotSaveReason,
931
- isSaving,
932
- saveError,
933
- facetRule,
934
- onFacetRuleChange: handleFacetRuleChange
935
- };
936
- }
937
- var RT_KEY = (recordType) => recordType ?? "_default";
938
- var lsKey = (appId, recordType) => `ra:intro:${appId}:${RT_KEY(recordType)}`;
939
- var useIntroDismissed = (SL, collectionId, appId, recordType) => {
940
- const [dismissed, setDismissed] = useState(() => {
941
- try {
942
- return localStorage.getItem(lsKey(appId, recordType)) === "1";
943
- } catch {
944
- return false;
945
- }
946
- });
947
- useEffect(() => {
948
- let cancelled = false;
949
- (async () => {
950
- try {
951
- const cfg = await SL?.appConfiguration?.getConfig?.({ collectionId, appId, admin: true });
952
- if (cancelled) return;
953
- const flag = cfg?._meta?.introDismissed?.[RT_KEY(recordType)];
954
- if (flag) setDismissed(true);
955
- } catch {
956
- }
957
- })();
958
- return () => {
959
- cancelled = true;
960
- };
961
- }, [SL, collectionId, appId, recordType]);
962
- const dismiss = useCallback(async () => {
963
- setDismissed(true);
964
- try {
965
- localStorage.setItem(lsKey(appId, recordType), "1");
966
- } catch {
967
- }
968
- try {
969
- const cfg = await SL?.appConfiguration?.getConfig?.({ collectionId, appId, admin: true }).catch(() => ({}));
970
- const next = {
971
- ...cfg ?? {},
972
- _meta: {
973
- ...cfg?._meta ?? {},
974
- introDismissed: { ...cfg?._meta?.introDismissed ?? {}, [RT_KEY(recordType)]: true }
975
- }
976
- };
977
- await SL?.appConfiguration?.setConfig?.({ collectionId, appId, admin: true, config: next });
978
- } catch {
979
- }
980
- }, [SL, collectionId, appId, recordType]);
981
- const undismiss = useCallback(() => {
982
- setDismissed(false);
983
- try {
984
- localStorage.removeItem(lsKey(appId, recordType));
985
- } catch {
986
- }
987
- }, [appId, recordType]);
988
- return { dismissed, dismiss, undismiss };
989
- };
990
- var useScopeProbe = ({ SL, collectionId, admin = true, enabled = true }) => {
991
- const query = useQuery({
992
- queryKey: ["records-admin", "scope-probe", collectionId, admin],
993
- enabled: enabled && !!collectionId && !!SL?.collection?.get,
994
- staleTime: 5 * 6e4,
995
- queryFn: async () => {
996
- const c = await SL.collection.get(collectionId, admin);
997
- return {
998
- hasVariants: !!c?.variants,
999
- hasBatches: !!c?.batches
1000
- };
1001
- }
1002
- });
1003
- return {
1004
- hasVariants: query.data?.hasVariants ?? false,
1005
- hasBatches: query.data?.hasBatches ?? false,
1006
- isLoading: query.isLoading,
1007
- error: query.error ?? null
1008
- };
1009
- };
1010
- var LOG = "[RecordsAdmin/useFacetBrowse]";
1011
- var QK = ["records-admin", "facet-browse"];
1012
- var toScaffoldSummary = (facet, value) => {
1013
- const facetKey = facet.key ?? "";
1014
- const valueKey = value.key ?? "";
1015
- const ref = buildRef({ facetId: facetKey, facetValue: valueKey });
1016
- return {
1017
- id: null,
1018
- ref,
1019
- scope: parseRef(ref),
1020
- data: null,
1021
- status: "empty",
1022
- label: value.name ?? valueKey ?? "Untitled value",
1023
- subtitle: facet.name ?? facetKey ?? void 0
1024
- };
1025
- };
1026
- var useFacetBrowse = ({
1027
- SL,
1028
- collectionId,
1029
- existing,
1030
- search = "",
1031
- filter = "all",
1032
- enabled = true
1033
- }) => {
1034
- const queryClient = useQueryClient();
1035
- const hasAdminList = !!SL?.facets?.list;
1036
- const hasPublicList = !!SL?.facets?.publicList;
1037
- const hasAnyList = hasAdminList || hasPublicList;
1038
- const queryEnabled = enabled && !!collectionId && hasAnyList;
1039
- const query = useQuery({
1040
- queryKey: [...QK, collectionId],
1041
- enabled: queryEnabled,
1042
- staleTime: 3e4,
1043
- queryFn: async () => {
1044
- const t0 = performance.now();
1045
- try {
1046
- if (SL?.facets?.list) {
1047
- console.info(`${LOG} \u2192 SL.facets.list("${collectionId}", { includeValues: true })`);
1048
- const res = await SL.facets.list(collectionId, { includeValues: true });
1049
- const items = res?.items ?? [];
1050
- console.info(
1051
- `${LOG} \u2190 SL.facets.list ok in ${Math.round(performance.now() - t0)}ms \u2014 ${items.length} facet(s)`,
1052
- items
1053
- );
1054
- return items;
1055
- }
1056
- if (SL?.facets?.publicList) {
1057
- console.info(`${LOG} \u2192 SL.facets.publicList("${collectionId}", { includeValues: true })`);
1058
- const res = await SL.facets.publicList(collectionId, { includeValues: true });
1059
- const items = res?.items ?? [];
1060
- console.info(
1061
- `${LOG} \u2190 SL.facets.publicList ok in ${Math.round(performance.now() - t0)}ms \u2014 ${items.length} facet(s)`,
1062
- items
1063
- );
1064
- return items;
1065
- }
1066
- console.warn(`${LOG} queryFn ran but no facets API is available on SL`);
1067
- return [];
1068
- } catch (err) {
1069
- console.error(`${LOG} \u2716 facets fetch failed`, err);
1070
- throw err;
1071
- }
1072
- }
1073
- });
1074
- const lastLoggedRef = useRef("");
1075
- useEffect(() => {
1076
- const signature = `${enabled}|${collectionId}|${hasAdminList}|${hasPublicList}`;
1077
- if (signature === lastLoggedRef.current) return;
1078
- lastLoggedRef.current = signature;
1079
- if (!enabled) {
1080
- console.info(`${LOG} skipped \u2014 enabled=false (shell not on facet tab yet)`);
1081
- return;
1082
- }
1083
- if (!collectionId) {
1084
- console.warn(`${LOG} skipped \u2014 no collectionId provided`);
674
+ if (!collectionId) {
675
+ console.warn(`${LOG} skipped \u2014 no collectionId provided`);
1085
676
  return;
1086
677
  }
1087
678
  if (!hasAnyList) {
@@ -1249,81 +840,261 @@ var useProductChildren = (args) => {
1249
840
  refetch
1250
841
  };
1251
842
  };
1252
- var MESSAGE_TYPE = "smartlinks:unsaved-changes";
1253
- function useUnsavedGuard({
1254
- isDirty,
1255
- label,
1256
- confirm,
1257
- disableBeforeUnload = false,
1258
- disableParentMessage = false
1259
- }) {
1260
- useEffect(() => {
1261
- if (disableBeforeUnload || !isDirty || typeof window === "undefined") return;
1262
- const handler = (e) => {
1263
- e.preventDefault();
1264
- e.returnValue = "";
1265
- return "";
1266
- };
1267
- window.addEventListener("beforeunload", handler);
1268
- return () => window.removeEventListener("beforeunload", handler);
1269
- }, [isDirty, disableBeforeUnload]);
1270
- useEffect(() => {
1271
- if (disableParentMessage || typeof window === "undefined") return;
1272
- if (window.parent === window) return;
1273
- try {
1274
- window.parent.postMessage({
1275
- type: MESSAGE_TYPE,
1276
- isDirty,
1277
- label: label ?? null
1278
- }, "*");
1279
- } catch {
1280
- }
1281
- }, [isDirty, label, disableParentMessage]);
1282
- useEffect(() => {
1283
- if (disableParentMessage || typeof window === "undefined") return;
1284
- if (window.parent === window) return;
1285
- return () => {
1286
- try {
1287
- window.parent.postMessage({ type: MESSAGE_TYPE, isDirty: false, label: label ?? null }, "*");
1288
- } catch {
1289
- }
1290
- };
1291
- }, [disableParentMessage, label]);
1292
- const confirmDiscard = useCallback(
1293
- async (message) => {
1294
- if (!isDirty) return true;
1295
- const msg = message ?? "You have unsaved changes. Discard them?";
1296
- if (confirm) {
1297
- try {
1298
- return !!await confirm(msg);
1299
- } catch {
1300
- return false;
1301
- }
843
+ var EMPTY_RULE_FILTERS = { facetKeys: [], minClauses: null };
844
+ var COMPLEXITY_THRESHOLDS = [3, 5, 10];
845
+ var ruleOf = (r) => r.facetRule ?? null;
846
+ function RuleFilterChips({ source, value, onChange }) {
847
+ const facetKeyEntries = useMemo(() => {
848
+ const counts = /* @__PURE__ */ new Map();
849
+ for (const r of source) {
850
+ const rule = ruleOf(r);
851
+ if (!rule) continue;
852
+ for (const c of rule.all ?? []) {
853
+ counts.set(c.facetKey, (counts.get(c.facetKey) ?? 0) + 1);
1302
854
  }
1303
- if (typeof window === "undefined") return true;
1304
- return window.confirm(msg);
1305
- },
1306
- [isDirty, confirm]
1307
- );
1308
- return { confirmDiscard };
1309
- }
1310
- var fallbackConfirm = async (i18n) => {
1311
- if (typeof window === "undefined") return "discard";
1312
- const msg = `${i18n?.title ?? "Unsaved changes"}
1313
-
1314
- ${i18n?.body ?? "OK to save & continue, Cancel to discard."}`;
1315
- return window.confirm(msg) ? "save" : "discard";
1316
- };
1317
- var useDirtyNavigation = ({
1318
- strategy,
1319
- isDirty,
1320
- save,
1321
- reset,
1322
- confirm,
1323
- i18n
1324
- }) => {
1325
- const runWithGuard = useCallback(
1326
- async (action) => {
855
+ }
856
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
857
+ }, [source]);
858
+ const maxClauses = useMemo(() => {
859
+ let max = 0;
860
+ for (const r of source) {
861
+ const rule = ruleOf(r);
862
+ if (!rule) continue;
863
+ max = Math.max(max, rule.all?.length ?? 0);
864
+ }
865
+ return max;
866
+ }, [source]);
867
+ if (facetKeyEntries.length === 0 && maxClauses < 2) return null;
868
+ const toggleKey = (key) => {
869
+ const has = value.facetKeys.includes(key);
870
+ const next = has ? value.facetKeys.filter((k) => k !== key) : [...value.facetKeys, key];
871
+ onChange({ ...value, facetKeys: next });
872
+ };
873
+ const setMin = (n) => onChange({ ...value, minClauses: n });
874
+ const hasAny = value.facetKeys.length > 0 || value.minClauses != null;
875
+ return /* @__PURE__ */ jsxs("div", { className: "ra-rule-filters", children: [
876
+ /* @__PURE__ */ jsx("div", { className: "ra-rule-filters-row", role: "group", "aria-label": "Filter rules by facet", children: facetKeyEntries.map(([key, count]) => {
877
+ const active = value.facetKeys.includes(key);
878
+ return /* @__PURE__ */ jsxs(
879
+ "button",
880
+ {
881
+ type: "button",
882
+ onClick: () => toggleKey(key),
883
+ className: "ra-rule-filter-chip",
884
+ "data-active": active ? "true" : "false",
885
+ "aria-pressed": active,
886
+ title: `Show rules using ${key}`,
887
+ children: [
888
+ /* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-label", children: key }),
889
+ /* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-count", children: count })
890
+ ]
891
+ },
892
+ key
893
+ );
894
+ }) }),
895
+ maxClauses >= 2 && /* @__PURE__ */ jsx("div", { className: "ra-rule-filters-row", role: "group", "aria-label": "Filter by clause count", children: COMPLEXITY_THRESHOLDS.filter((n) => maxClauses >= n).map((n) => {
896
+ const active = value.minClauses === n;
897
+ return /* @__PURE__ */ jsxs(
898
+ "button",
899
+ {
900
+ type: "button",
901
+ onClick: () => setMin(active ? null : n),
902
+ className: "ra-rule-filter-chip",
903
+ "data-tone": "complexity",
904
+ "data-active": active ? "true" : "false",
905
+ "aria-pressed": active,
906
+ title: `Only rules with \u2265 ${n} facets`,
907
+ children: [
908
+ "\u2265 ",
909
+ n,
910
+ " facets"
911
+ ]
912
+ },
913
+ n
914
+ );
915
+ }) }),
916
+ hasAny && /* @__PURE__ */ jsx(
917
+ "button",
918
+ {
919
+ type: "button",
920
+ onClick: () => onChange(EMPTY_RULE_FILTERS),
921
+ className: "ra-rule-filter-clear",
922
+ "aria-label": "Clear rule filters",
923
+ children: "Clear filters"
924
+ }
925
+ )
926
+ ] });
927
+ }
928
+ function applyRuleFilters(items, filters) {
929
+ if (filters.facetKeys.length === 0 && filters.minClauses == null) return items;
930
+ return items.filter((r) => {
931
+ const rule = ruleOf(r);
932
+ if (!rule) return false;
933
+ if (filters.minClauses != null && (rule.all?.length ?? 0) < filters.minClauses) return false;
934
+ if (filters.facetKeys.length > 0) {
935
+ const keys = new Set((rule.all ?? []).map((c) => c.facetKey));
936
+ if (!filters.facetKeys.some((k) => keys.has(k))) return false;
937
+ }
938
+ return true;
939
+ });
940
+ }
941
+
942
+ // src/components/RecordsAdmin/hooks/useShellBrowser.ts
943
+ function useShellBrowser(opts) {
944
+ const {
945
+ ctx,
946
+ SL,
947
+ collectionId,
948
+ activeScope,
949
+ contextScope,
950
+ probeIsLoading,
951
+ selectedProductId,
952
+ drillTab,
953
+ classify: classify2
954
+ } = opts;
955
+ const [search, setSearch] = useState("");
956
+ const [filter, setFilter] = useState("all");
957
+ const [ruleFilters, setRuleFilters] = useState(EMPTY_RULE_FILTERS);
958
+ const [facetBrowseFilter, setFacetBrowseFilter] = useState(null);
959
+ useEffect(() => {
960
+ setSearch("");
961
+ setFilter("all");
962
+ setRuleFilters(EMPTY_RULE_FILTERS);
963
+ setFacetBrowseFilter(null);
964
+ }, [activeScope]);
965
+ const productBrowse = useProductBrowse({
966
+ SL,
967
+ collectionId,
968
+ search: activeScope === "product" ? search : "",
969
+ enabled: activeScope === "product" && !contextScope?.productId
970
+ });
971
+ const recordListEnabled = (activeScope === "rule" || activeScope === "collection") && !probeIsLoading;
972
+ const recordList = useRecordList({
973
+ ctx,
974
+ scopeKind: activeScope,
975
+ search,
976
+ filter,
977
+ classify: classify2,
978
+ contextScope,
979
+ enabled: recordListEnabled
980
+ });
981
+ const facetBrowse = useFacetBrowse({
982
+ SL,
983
+ collectionId,
984
+ existing: [],
985
+ enabled: (activeScope === "rule" || activeScope === "collection") && !probeIsLoading
986
+ });
987
+ const variantChildren = useProductChildren({
988
+ SL,
989
+ collectionId,
990
+ productId: selectedProductId,
991
+ kind: drillTab === "variant" ? "variant" : null
992
+ });
993
+ const batchChildren = useProductChildren({
994
+ SL,
995
+ collectionId,
996
+ productId: selectedProductId,
997
+ kind: drillTab === "batch" ? "batch" : null
998
+ });
999
+ const refetchAll = useCallback(() => {
1000
+ productBrowse.refetch();
1001
+ recordList.refetch();
1002
+ facetBrowse.refetch();
1003
+ variantChildren.refetch?.();
1004
+ batchChildren.refetch?.();
1005
+ }, [productBrowse, recordList, facetBrowse, variantChildren, batchChildren]);
1006
+ return {
1007
+ search,
1008
+ setSearch,
1009
+ filter,
1010
+ setFilter,
1011
+ ruleFilters,
1012
+ setRuleFilters,
1013
+ facetBrowseFilter,
1014
+ setFacetBrowseFilter,
1015
+ productBrowse,
1016
+ recordList,
1017
+ facetBrowse,
1018
+ variantChildren,
1019
+ batchChildren,
1020
+ refetchAll
1021
+ };
1022
+ }
1023
+ var MESSAGE_TYPE = "smartlinks:unsaved-changes";
1024
+ function useUnsavedGuard({
1025
+ isDirty,
1026
+ label,
1027
+ confirm,
1028
+ disableBeforeUnload = false,
1029
+ disableParentMessage = false
1030
+ }) {
1031
+ useEffect(() => {
1032
+ if (disableBeforeUnload || !isDirty || typeof window === "undefined") return;
1033
+ const handler = (e) => {
1034
+ e.preventDefault();
1035
+ e.returnValue = "";
1036
+ return "";
1037
+ };
1038
+ window.addEventListener("beforeunload", handler);
1039
+ return () => window.removeEventListener("beforeunload", handler);
1040
+ }, [isDirty, disableBeforeUnload]);
1041
+ useEffect(() => {
1042
+ if (disableParentMessage || typeof window === "undefined") return;
1043
+ if (window.parent === window) return;
1044
+ try {
1045
+ window.parent.postMessage({
1046
+ type: MESSAGE_TYPE,
1047
+ isDirty,
1048
+ label: label ?? null
1049
+ }, "*");
1050
+ } catch {
1051
+ }
1052
+ }, [isDirty, label, disableParentMessage]);
1053
+ useEffect(() => {
1054
+ if (disableParentMessage || typeof window === "undefined") return;
1055
+ if (window.parent === window) return;
1056
+ return () => {
1057
+ try {
1058
+ window.parent.postMessage({ type: MESSAGE_TYPE, isDirty: false, label: label ?? null }, "*");
1059
+ } catch {
1060
+ }
1061
+ };
1062
+ }, [disableParentMessage, label]);
1063
+ const confirmDiscard = useCallback(
1064
+ async (message) => {
1065
+ if (!isDirty) return true;
1066
+ const msg = message ?? "You have unsaved changes. Discard them?";
1067
+ if (confirm) {
1068
+ try {
1069
+ return !!await confirm(msg);
1070
+ } catch {
1071
+ return false;
1072
+ }
1073
+ }
1074
+ if (typeof window === "undefined") return true;
1075
+ return window.confirm(msg);
1076
+ },
1077
+ [isDirty, confirm]
1078
+ );
1079
+ return { confirmDiscard };
1080
+ }
1081
+ var fallbackConfirm = async (i18n) => {
1082
+ if (typeof window === "undefined") return "discard";
1083
+ const msg = `${i18n?.title ?? "Unsaved changes"}
1084
+
1085
+ ${i18n?.body ?? "OK to save & continue, Cancel to discard."}`;
1086
+ return window.confirm(msg) ? "save" : "discard";
1087
+ };
1088
+ var useDirtyNavigation = ({
1089
+ strategy,
1090
+ isDirty,
1091
+ save,
1092
+ reset,
1093
+ confirm,
1094
+ i18n
1095
+ }) => {
1096
+ const runWithGuard = useCallback(
1097
+ async (action) => {
1327
1098
  if (!isDirty || strategy === "keep") {
1328
1099
  action();
1329
1100
  return true;
@@ -1495,61 +1266,22 @@ var useConfirmDialog = () => {
1495
1266
  )
1496
1267
  };
1497
1268
  };
1498
- var DEFAULTS2 = {
1499
- title: "Replace existing values?",
1500
- body: "This destination already has saved values. Replace them?",
1501
- confirmLabel: "Replace",
1502
- cancelLabel: "Cancel"
1503
- };
1504
- var usePasteConfirm = () => {
1505
- const [open, setOpen] = useState(false);
1506
- const [state, setState] = useState(DEFAULTS2);
1507
- const resolverRef = useRef(null);
1508
- const confirm = useCallback((i18n) => {
1509
- setState({ ...DEFAULTS2, ...i18n ?? {} });
1510
- setOpen(true);
1511
- return new Promise((resolve) => {
1512
- resolverRef.current = resolve;
1513
- });
1514
- }, []);
1515
- const handleChoice = useCallback((choice) => {
1516
- setOpen(false);
1517
- const r = resolverRef.current;
1518
- resolverRef.current = null;
1519
- r?.(choice === "discard");
1520
- }, []);
1521
- return {
1522
- confirm,
1523
- dialog: /* @__PURE__ */ jsx(
1524
- ConfirmDialog,
1525
- {
1526
- open,
1527
- title: state.title,
1528
- body: state.body,
1529
- saveLabel: "",
1530
- discardLabel: state.confirmLabel,
1531
- cancelLabel: state.cancelLabel,
1532
- onChoice: handleChoice
1533
- }
1534
- )
1535
- };
1536
- };
1537
- var stores = /* @__PURE__ */ new Map();
1538
- var storageKey = (key) => `ra:clipboard:${key}`;
1539
- var getStore = (key) => {
1540
- let s = stores.get(key);
1541
- if (!s) {
1542
- s = { entry: null, listeners: /* @__PURE__ */ new Set() };
1543
- if (typeof window !== "undefined") {
1544
- try {
1545
- const raw = window.sessionStorage.getItem(storageKey(key));
1546
- if (raw) s.entry = JSON.parse(raw);
1547
- } catch {
1548
- }
1549
- }
1550
- stores.set(key, s);
1551
- }
1552
- return s;
1269
+ var stores = /* @__PURE__ */ new Map();
1270
+ var storageKey = (key) => `ra:clipboard:${key}`;
1271
+ var getStore = (key) => {
1272
+ let s = stores.get(key);
1273
+ if (!s) {
1274
+ s = { entry: null, listeners: /* @__PURE__ */ new Set() };
1275
+ if (typeof window !== "undefined") {
1276
+ try {
1277
+ const raw = window.sessionStorage.getItem(storageKey(key));
1278
+ if (raw) s.entry = JSON.parse(raw);
1279
+ } catch {
1280
+ }
1281
+ }
1282
+ stores.set(key, s);
1283
+ }
1284
+ return s;
1553
1285
  };
1554
1286
  var persist = (key, entry) => {
1555
1287
  if (typeof window === "undefined") return;
@@ -1611,6 +1343,256 @@ function cloneValue(value) {
1611
1343
  }
1612
1344
  }
1613
1345
  }
1346
+ var DEFAULTS2 = {
1347
+ title: "Replace existing values?",
1348
+ body: "This destination already has saved values. Replace them?",
1349
+ confirmLabel: "Replace",
1350
+ cancelLabel: "Cancel"
1351
+ };
1352
+ var usePasteConfirm = () => {
1353
+ const [open, setOpen] = useState(false);
1354
+ const [state, setState] = useState(DEFAULTS2);
1355
+ const resolverRef = useRef(null);
1356
+ const confirm = useCallback((i18n) => {
1357
+ setState({ ...DEFAULTS2, ...i18n ?? {} });
1358
+ setOpen(true);
1359
+ return new Promise((resolve) => {
1360
+ resolverRef.current = resolve;
1361
+ });
1362
+ }, []);
1363
+ const handleChoice = useCallback((choice) => {
1364
+ setOpen(false);
1365
+ const r = resolverRef.current;
1366
+ resolverRef.current = null;
1367
+ r?.(choice === "discard");
1368
+ }, []);
1369
+ return {
1370
+ confirm,
1371
+ dialog: /* @__PURE__ */ jsx(
1372
+ ConfirmDialog,
1373
+ {
1374
+ open,
1375
+ title: state.title,
1376
+ body: state.body,
1377
+ saveLabel: "",
1378
+ discardLabel: state.confirmLabel,
1379
+ cancelLabel: state.cancelLabel,
1380
+ onChoice: handleChoice
1381
+ }
1382
+ )
1383
+ };
1384
+ };
1385
+ var ClipboardToast = ({ message, variant = "copy", onDismiss }) => {
1386
+ useEffect(() => {
1387
+ const t = window.setTimeout(onDismiss, 2500);
1388
+ return () => window.clearTimeout(t);
1389
+ }, [message, onDismiss]);
1390
+ const Icon = variant === "paste" ? ClipboardPaste : Copy;
1391
+ return /* @__PURE__ */ jsxs(
1392
+ "div",
1393
+ {
1394
+ role: "status",
1395
+ "aria-live": "polite",
1396
+ className: "ra-clipboard-toast",
1397
+ children: [
1398
+ /* @__PURE__ */ jsx(Icon, { className: "w-3.5 h-3.5 shrink-0", "aria-hidden": "true" }),
1399
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: message })
1400
+ ]
1401
+ }
1402
+ );
1403
+ };
1404
+ function useShellClipboard(args) {
1405
+ const {
1406
+ enabled,
1407
+ appId,
1408
+ recordType,
1409
+ i18n,
1410
+ editorCtx,
1411
+ editingScope,
1412
+ editorHeaderLabel,
1413
+ isCollection,
1414
+ selectedItemId,
1415
+ selectedRecordId,
1416
+ resolved,
1417
+ onTelemetry,
1418
+ onCopyOverride,
1419
+ onPasteOverride,
1420
+ onLeftSelectRef
1421
+ } = args;
1422
+ const clipboard = useRecordClipboard({
1423
+ appId,
1424
+ recordType: recordType ?? "__default"
1425
+ });
1426
+ const pasteConfirm = usePasteConfirm();
1427
+ const [notice, setNotice] = useState(null);
1428
+ const copyCurrent = useCallback(() => {
1429
+ if (!enabled || !editingScope) return;
1430
+ const sourceValue = onCopyOverride ? onCopyOverride({ value: editorCtx.value, scope: editingScope }) : cloneValue(editorCtx.value);
1431
+ clipboard.set({
1432
+ value: sourceValue,
1433
+ sourceScope: editingScope,
1434
+ sourceRecordId: resolved.recordId,
1435
+ sourceLabel: editorHeaderLabel
1436
+ });
1437
+ onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: editingScope.raw });
1438
+ setNotice({
1439
+ message: i18n.copyToast.replace("{name}", editorHeaderLabel ?? editingScope.raw),
1440
+ variant: "copy"
1441
+ });
1442
+ }, [
1443
+ enabled,
1444
+ editingScope,
1445
+ onCopyOverride,
1446
+ editorCtx.value,
1447
+ clipboard,
1448
+ editorHeaderLabel,
1449
+ onTelemetry,
1450
+ recordType,
1451
+ i18n.copyToast,
1452
+ resolved.recordId
1453
+ ]);
1454
+ const pasteCurrent = useCallback(async () => {
1455
+ if (!enabled || !editingScope || !clipboard.entry) return;
1456
+ const compat = checkPasteCompatibility(clipboard.entry.sourceScope, editingScope);
1457
+ if (compat.status === "denied") {
1458
+ setNotice({ message: compat.reason ?? "Paste not allowed here", variant: "paste" });
1459
+ return;
1460
+ }
1461
+ if (compat.status === "warn") {
1462
+ const ok = await pasteConfirm.confirm({
1463
+ title: i18n.pasteWarnTitle,
1464
+ body: compat.reason ?? "",
1465
+ confirmLabel: i18n.pasteWarnContinue,
1466
+ cancelLabel: i18n.pasteConfirmCancel
1467
+ });
1468
+ if (!ok) return;
1469
+ }
1470
+ const willReplace = resolved.source === "self";
1471
+ if (willReplace) {
1472
+ const ok = await pasteConfirm.confirm({
1473
+ title: i18n.pasteConfirmTitle,
1474
+ body: i18n.pasteConfirmBody.replace(
1475
+ "{destination}",
1476
+ editorHeaderLabel ?? editingScope.raw
1477
+ ),
1478
+ confirmLabel: i18n.pasteConfirmReplace,
1479
+ cancelLabel: i18n.pasteConfirmCancel
1480
+ });
1481
+ if (!ok) return;
1482
+ }
1483
+ const sourceParsed = clipboard.entry.sourceScope;
1484
+ const transformed = onPasteOverride ? onPasteOverride(
1485
+ { value: clipboard.entry.value, sourceScope: sourceParsed },
1486
+ { scope: editingScope, currentValue: editorCtx.value ?? null }
1487
+ ) : cloneValue(clipboard.entry.value);
1488
+ if (transformed === null) return;
1489
+ editorCtx.onChange(transformed);
1490
+ onTelemetry?.({
1491
+ type: "clipboard.paste",
1492
+ recordType,
1493
+ sourceRef: clipboard.entry.sourceScope.raw,
1494
+ destinationRef: editingScope.raw,
1495
+ replaced: willReplace
1496
+ });
1497
+ setNotice({
1498
+ message: i18n.pasteToast.replace("{name}", clipboard.entry.sourceLabel ?? clipboard.entry.sourceScope.raw),
1499
+ variant: "paste"
1500
+ });
1501
+ }, [
1502
+ enabled,
1503
+ editingScope,
1504
+ clipboard.entry,
1505
+ pasteConfirm,
1506
+ resolved.source,
1507
+ onPasteOverride,
1508
+ editorCtx,
1509
+ onTelemetry,
1510
+ recordType,
1511
+ editorHeaderLabel,
1512
+ i18n.pasteWarnTitle,
1513
+ i18n.pasteWarnContinue,
1514
+ i18n.pasteConfirmCancel,
1515
+ i18n.pasteConfirmTitle,
1516
+ i18n.pasteConfirmBody,
1517
+ i18n.pasteConfirmReplace,
1518
+ i18n.pasteToast
1519
+ ]);
1520
+ const editorPasteCompat = useMemo(() => {
1521
+ if (!enabled || !clipboard.entry || !editingScope) return null;
1522
+ const sameRecord = clipboard.entry.sourceRecordId && resolved.recordId && clipboard.entry.sourceRecordId === resolved.recordId;
1523
+ const sameAnchor = !clipboard.entry.sourceRecordId && clipboard.entry.sourceScope.raw === editingScope.raw;
1524
+ if (sameRecord || sameAnchor) return { status: "denied" };
1525
+ return checkPasteCompatibility(clipboard.entry.sourceScope, editingScope);
1526
+ }, [enabled, clipboard.entry, editingScope, resolved.recordId]);
1527
+ const editorClipboard = enabled && editingScope ? {
1528
+ onCopy: copyCurrent,
1529
+ onPaste: () => {
1530
+ void pasteCurrent();
1531
+ },
1532
+ canCopy: true,
1533
+ canPaste: !!clipboard.entry && editorPasteCompat?.status !== "denied",
1534
+ pasteSourceLabel: clipboard.entry?.sourceLabel,
1535
+ pasteWillReplace: resolved.source === "self" && !!clipboard.entry
1536
+ } : void 0;
1537
+ const [pendingPasteTarget, setPendingPasteTarget] = useState(null);
1538
+ useEffect(() => {
1539
+ if (!pendingPasteTarget) return;
1540
+ if (!editingScope) return;
1541
+ const matched = pendingPasteTarget.kind === "record" ? (isCollection ? selectedItemId : selectedRecordId) === pendingPasteTarget.recordId : editingScope.raw === pendingPasteTarget.ref;
1542
+ if (!matched) return;
1543
+ const t = window.setTimeout(() => {
1544
+ setPendingPasteTarget(null);
1545
+ void pasteCurrent();
1546
+ }, 0);
1547
+ return () => window.clearTimeout(t);
1548
+ }, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent]);
1549
+ const rowClipboard = enabled ? (record) => {
1550
+ const summaryHasData = record.data != null;
1551
+ const sourceParsed = record.scope;
1552
+ const compat = clipboard.entry ? checkPasteCompatibility(clipboard.entry.sourceScope, sourceParsed) : null;
1553
+ const sameTarget = clipboard.entry ? clipboard.entry.sourceRecordId && record.id ? clipboard.entry.sourceRecordId === record.id : clipboard.entry.sourceScope.raw === record.ref : false;
1554
+ return {
1555
+ onCopy: summaryHasData ? () => {
1556
+ const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
1557
+ clipboard.set({
1558
+ value,
1559
+ sourceScope: sourceParsed,
1560
+ sourceRecordId: record.id ?? void 0,
1561
+ sourceLabel: record.label
1562
+ });
1563
+ onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: record.ref });
1564
+ setNotice({
1565
+ message: i18n.copyToast.replace("{name}", record.label),
1566
+ variant: "copy"
1567
+ });
1568
+ } : void 0,
1569
+ onPaste: () => {
1570
+ onLeftSelectRef.current?.(record);
1571
+ setPendingPasteTarget(
1572
+ record.id ? { kind: "record", recordId: record.id } : { kind: "anchor", ref: record.ref }
1573
+ );
1574
+ },
1575
+ canPaste: !!clipboard.entry && !sameTarget && compat?.status !== "denied",
1576
+ pasteWillReplace: record.status === "configured",
1577
+ clipboardSourceLabel: clipboard.entry?.sourceLabel
1578
+ };
1579
+ } : void 0;
1580
+ const toast = notice ? /* @__PURE__ */ jsx(
1581
+ ClipboardToast,
1582
+ {
1583
+ message: notice.message,
1584
+ variant: notice.variant,
1585
+ onDismiss: () => setNotice(null)
1586
+ }
1587
+ ) : null;
1588
+ return {
1589
+ editorClipboard,
1590
+ rowClipboard,
1591
+ confirmDialog: pasteConfirm.dialog,
1592
+ toast,
1593
+ store: clipboard
1594
+ };
1595
+ }
1614
1596
  var LABELS = {
1615
1597
  collection: "Global",
1616
1598
  product: "Products",
@@ -2002,104 +1984,6 @@ var ProductList = RecordList;
2002
1984
  var FacetList = RecordList;
2003
1985
  var VariantList = RecordList;
2004
1986
  var BatchList = RecordList;
2005
- var EMPTY_RULE_FILTERS = { facetKeys: [], minClauses: null };
2006
- var COMPLEXITY_THRESHOLDS = [3, 5, 10];
2007
- var ruleOf = (r) => r.facetRule ?? null;
2008
- function RuleFilterChips({ source, value, onChange }) {
2009
- const facetKeyEntries = useMemo(() => {
2010
- const counts = /* @__PURE__ */ new Map();
2011
- for (const r of source) {
2012
- const rule = ruleOf(r);
2013
- if (!rule) continue;
2014
- for (const c of rule.all ?? []) {
2015
- counts.set(c.facetKey, (counts.get(c.facetKey) ?? 0) + 1);
2016
- }
2017
- }
2018
- return Array.from(counts.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
2019
- }, [source]);
2020
- const maxClauses = useMemo(() => {
2021
- let max = 0;
2022
- for (const r of source) {
2023
- const rule = ruleOf(r);
2024
- if (!rule) continue;
2025
- max = Math.max(max, rule.all?.length ?? 0);
2026
- }
2027
- return max;
2028
- }, [source]);
2029
- if (facetKeyEntries.length === 0 && maxClauses < 2) return null;
2030
- const toggleKey = (key) => {
2031
- const has = value.facetKeys.includes(key);
2032
- const next = has ? value.facetKeys.filter((k) => k !== key) : [...value.facetKeys, key];
2033
- onChange({ ...value, facetKeys: next });
2034
- };
2035
- const setMin = (n) => onChange({ ...value, minClauses: n });
2036
- const hasAny = value.facetKeys.length > 0 || value.minClauses != null;
2037
- return /* @__PURE__ */ jsxs("div", { className: "ra-rule-filters", children: [
2038
- /* @__PURE__ */ jsx("div", { className: "ra-rule-filters-row", role: "group", "aria-label": "Filter rules by facet", children: facetKeyEntries.map(([key, count]) => {
2039
- const active = value.facetKeys.includes(key);
2040
- return /* @__PURE__ */ jsxs(
2041
- "button",
2042
- {
2043
- type: "button",
2044
- onClick: () => toggleKey(key),
2045
- className: "ra-rule-filter-chip",
2046
- "data-active": active ? "true" : "false",
2047
- "aria-pressed": active,
2048
- title: `Show rules using ${key}`,
2049
- children: [
2050
- /* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-label", children: key }),
2051
- /* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-count", children: count })
2052
- ]
2053
- },
2054
- key
2055
- );
2056
- }) }),
2057
- maxClauses >= 2 && /* @__PURE__ */ jsx("div", { className: "ra-rule-filters-row", role: "group", "aria-label": "Filter by clause count", children: COMPLEXITY_THRESHOLDS.filter((n) => maxClauses >= n).map((n) => {
2058
- const active = value.minClauses === n;
2059
- return /* @__PURE__ */ jsxs(
2060
- "button",
2061
- {
2062
- type: "button",
2063
- onClick: () => setMin(active ? null : n),
2064
- className: "ra-rule-filter-chip",
2065
- "data-tone": "complexity",
2066
- "data-active": active ? "true" : "false",
2067
- "aria-pressed": active,
2068
- title: `Only rules with \u2265 ${n} facets`,
2069
- children: [
2070
- "\u2265 ",
2071
- n,
2072
- " facets"
2073
- ]
2074
- },
2075
- n
2076
- );
2077
- }) }),
2078
- hasAny && /* @__PURE__ */ jsx(
2079
- "button",
2080
- {
2081
- type: "button",
2082
- onClick: () => onChange(EMPTY_RULE_FILTERS),
2083
- className: "ra-rule-filter-clear",
2084
- "aria-label": "Clear rule filters",
2085
- children: "Clear filters"
2086
- }
2087
- )
2088
- ] });
2089
- }
2090
- function applyRuleFilters(items, filters) {
2091
- if (filters.facetKeys.length === 0 && filters.minClauses == null) return items;
2092
- return items.filter((r) => {
2093
- const rule = ruleOf(r);
2094
- if (!rule) return false;
2095
- if (filters.minClauses != null && (rule.all?.length ?? 0) < filters.minClauses) return false;
2096
- if (filters.facetKeys.length > 0) {
2097
- const keys = new Set((rule.all ?? []).map((c) => c.facetKey));
2098
- if (!filters.facetKeys.some((k) => keys.has(k))) return false;
2099
- }
2100
- return true;
2101
- });
2102
- }
2103
1987
  var COLLAPSED_FACET_CAP = 6;
2104
1988
  function FacetBrowseFilter({
2105
1989
  facets,
@@ -2481,6 +2365,103 @@ var createDefaultDeepLinkAdapter = (paramNames) => ({
2481
2365
  }
2482
2366
  });
2483
2367
 
2368
+ // src/components/RecordsAdmin/data/postMessageDeepLinkAdapter.ts
2369
+ var CONTEXT_KEYS = [
2370
+ "dark",
2371
+ "appId",
2372
+ "collectionId",
2373
+ "productId",
2374
+ "proofId",
2375
+ "lang",
2376
+ "theme"
2377
+ ];
2378
+ var findQueryString = (loc) => {
2379
+ if (loc.search && loc.search.length > 1) {
2380
+ return loc.search.startsWith("?") ? loc.search.slice(1) : loc.search;
2381
+ }
2382
+ if (loc.hash && loc.hash.includes("?")) {
2383
+ return loc.hash.slice(loc.hash.indexOf("?") + 1);
2384
+ }
2385
+ return "";
2386
+ };
2387
+ var findPath = (loc) => {
2388
+ if (loc.hash && loc.hash.startsWith("#")) {
2389
+ const hash = loc.hash.slice(1);
2390
+ const qIdx = hash.indexOf("?");
2391
+ const hashPath = qIdx >= 0 ? hash.slice(0, qIdx) : hash;
2392
+ if (hashPath) return hashPath;
2393
+ }
2394
+ return loc.pathname || "/";
2395
+ };
2396
+ var isInSmartLinksIframe = () => {
2397
+ if (typeof window === "undefined") return false;
2398
+ try {
2399
+ return window.parent != null && window.parent !== window;
2400
+ } catch {
2401
+ return true;
2402
+ }
2403
+ };
2404
+ var createPostMessageDeepLinkAdapter = (paramNames) => {
2405
+ const lastShellState = {};
2406
+ const post = (path) => {
2407
+ if (typeof window === "undefined") return;
2408
+ const params = new URLSearchParams(findQueryString(window.location));
2409
+ const context = {};
2410
+ const state = {};
2411
+ params.forEach((value, key) => {
2412
+ if (key === "theme") return;
2413
+ if (CONTEXT_KEYS.includes(key)) context[key] = value;
2414
+ else state[key] = value;
2415
+ });
2416
+ const overlay = (key, paramKey) => {
2417
+ const v = lastShellState[key];
2418
+ if (v == null || v === "") delete state[paramKey];
2419
+ else state[paramKey] = v;
2420
+ };
2421
+ overlay("recordId", paramNames.recordId);
2422
+ overlay("scope", paramNames.scope);
2423
+ overlay("view", paramNames.view);
2424
+ try {
2425
+ window.parent.postMessage({
2426
+ type: "smartlinks-route-change",
2427
+ path,
2428
+ context,
2429
+ state,
2430
+ appId: context.appId
2431
+ }, "*");
2432
+ } catch {
2433
+ }
2434
+ };
2435
+ return {
2436
+ read() {
2437
+ if (typeof window === "undefined") return {};
2438
+ const params = new URLSearchParams(findQueryString(window.location));
2439
+ return {
2440
+ recordId: params.get(paramNames.recordId),
2441
+ scope: params.get(paramNames.scope),
2442
+ view: params.get(paramNames.view)
2443
+ };
2444
+ },
2445
+ write(partial) {
2446
+ if (typeof window === "undefined") return;
2447
+ if ("recordId" in partial) lastShellState.recordId = partial.recordId ?? null;
2448
+ if ("scope" in partial) lastShellState.scope = partial.scope ?? null;
2449
+ if ("view" in partial) lastShellState.view = partial.view ?? null;
2450
+ post(findPath(window.location));
2451
+ },
2452
+ subscribe(listener) {
2453
+ if (typeof window === "undefined") return () => {
2454
+ };
2455
+ window.addEventListener("popstate", listener);
2456
+ window.addEventListener("hashchange", listener);
2457
+ return () => {
2458
+ window.removeEventListener("popstate", listener);
2459
+ window.removeEventListener("hashchange", listener);
2460
+ };
2461
+ }
2462
+ };
2463
+ };
2464
+
2484
2465
  // src/components/RecordsAdmin/hooks/useDeepLinkState.ts
2485
2466
  var SMART_PUSH = ["record.open", "record.close", "scope"];
2486
2467
  var classify = (mode, kind) => {
@@ -2500,7 +2481,7 @@ function useDeepLinkState(options) {
2500
2481
  if (!enabled) return null;
2501
2482
  if (options?.adapter) return options.adapter;
2502
2483
  if (!defaultAdapterRef.current) {
2503
- defaultAdapterRef.current = createDefaultDeepLinkAdapter(paramNames);
2484
+ defaultAdapterRef.current = isInSmartLinksIframe() ? createPostMessageDeepLinkAdapter(paramNames) : createDefaultDeepLinkAdapter(paramNames);
2504
2485
  }
2505
2486
  return defaultAdapterRef.current;
2506
2487
  }, [enabled, options?.adapter, paramNames]);
@@ -2874,6 +2855,49 @@ function RecordEditor({
2874
2855
  )
2875
2856
  ] });
2876
2857
  }
2858
+ var isFacetRuleValid = (rule) => {
2859
+ if (!rule || !Array.isArray(rule.all) || rule.all.length === 0) return false;
2860
+ return rule.all.every(
2861
+ (c) => !!c.facetKey && Array.isArray(c.anyOf) && c.anyOf.length > 0
2862
+ );
2863
+ };
2864
+ function useRulePreview(args) {
2865
+ const {
2866
+ SL,
2867
+ collectionId,
2868
+ appId,
2869
+ rule,
2870
+ limit = 20,
2871
+ debounceMs = 350,
2872
+ enabled = true
2873
+ } = args;
2874
+ const [debouncedRule, setDebouncedRule] = useState(rule);
2875
+ const timer = useRef(null);
2876
+ useEffect(() => {
2877
+ if (timer.current) clearTimeout(timer.current);
2878
+ timer.current = setTimeout(() => setDebouncedRule(rule), debounceMs);
2879
+ return () => {
2880
+ if (timer.current) clearTimeout(timer.current);
2881
+ };
2882
+ }, [rule, debounceMs]);
2883
+ const valid = isFacetRuleValid(debouncedRule);
2884
+ const query = useQuery({
2885
+ queryKey: ["records-admin", "preview-rule", collectionId, appId, debouncedRule, limit],
2886
+ enabled: enabled && !!collectionId && !!appId && valid,
2887
+ staleTime: 3e4,
2888
+ queryFn: () => SL.app.records.previewRule(collectionId, appId, {
2889
+ facetRule: debouncedRule,
2890
+ limit
2891
+ })
2892
+ });
2893
+ return {
2894
+ totalMatches: query.data?.total ?? null,
2895
+ sampleProductIds: (query.data?.matchingProducts ?? []).map((p) => p.productId),
2896
+ isLoading: query.isFetching,
2897
+ isStale: rule !== debouncedRule,
2898
+ error: query.error ?? null
2899
+ };
2900
+ }
2877
2901
  function summariseRule(clauses, facets) {
2878
2902
  let valueCount = 0;
2879
2903
  const parts = clauses.map((c) => {
@@ -4270,11 +4294,11 @@ var statusDot = (status) => {
4270
4294
  }
4271
4295
  };
4272
4296
  var UnsavedTray = ({
4273
- drafts,
4297
+ items,
4274
4298
  isSaving,
4275
4299
  onSaveAll,
4276
4300
  onDiscardAll,
4277
- onOpenDraft,
4301
+ onOpenItem,
4278
4302
  onJumpToError,
4279
4303
  saveLabel,
4280
4304
  discardLabel,
@@ -4290,27 +4314,8 @@ var UnsavedTray = ({
4290
4314
  const wrapRef = useRef(null);
4291
4315
  const SI = SaveIcon ?? Save;
4292
4316
  const DI = DiscardIcon ?? Undo2;
4293
- const uniqueDrafts = useMemo(() => {
4294
- const byBucket = /* @__PURE__ */ new Map();
4295
- for (const d of drafts) {
4296
- const bucket = d.recordId || d.scopeRaw || d.key;
4297
- const existing = byBucket.get(bucket);
4298
- if (!existing) {
4299
- byBucket.set(bucket, d);
4300
- continue;
4301
- }
4302
- const existingSynthetic = existing.key.startsWith("draft:");
4303
- const incomingSynthetic = d.key.startsWith("draft:");
4304
- if (existingSynthetic && !incomingSynthetic) byBucket.set(bucket, d);
4305
- }
4306
- return Array.from(byBucket.values()).sort((a, b) => a.order - b.order);
4307
- }, [drafts]);
4308
- const liveDrafts = useMemo(
4309
- () => uniqueDrafts.filter((d) => d.status !== "saved"),
4310
- [uniqueDrafts]
4311
- );
4312
- const total = liveDrafts.length;
4313
- const errors = liveDrafts.filter((d) => d.status === "error").length;
4317
+ const total = items.length;
4318
+ const errors = items.filter((i) => i.status === "error").length;
4314
4319
  const isSingle = total === 1;
4315
4320
  useEffect(() => {
4316
4321
  if (!open) return;
@@ -4321,7 +4326,7 @@ var UnsavedTray = ({
4321
4326
  return () => window.removeEventListener("mousedown", onDoc);
4322
4327
  }, [open]);
4323
4328
  if (total === 0) return null;
4324
- const countLabel = isSingle ? liveDrafts[0].label || "this record" : countTemplate.replace("{n}", String(total));
4329
+ const countLabel = isSingle ? items[0].label || "this record" : countTemplate.replace("{n}", String(total));
4325
4330
  return /* @__PURE__ */ jsxs(
4326
4331
  "div",
4327
4332
  {
@@ -4391,14 +4396,14 @@ var UnsavedTray = ({
4391
4396
  }
4392
4397
  )
4393
4398
  ] }),
4394
- open && !isSingle && /* @__PURE__ */ jsx("div", { className: "ra-unsaved-popover", role: "menu", children: liveDrafts.map((d) => /* @__PURE__ */ jsxs(
4399
+ open && !isSingle && /* @__PURE__ */ jsx("div", { className: "ra-unsaved-popover", role: "menu", children: items.map((it) => /* @__PURE__ */ jsxs(
4395
4400
  "button",
4396
4401
  {
4397
4402
  type: "button",
4398
4403
  className: "ra-unsaved-popover-row",
4399
4404
  onClick: () => {
4400
4405
  setOpen(false);
4401
- onOpenDraft?.(d);
4406
+ onOpenItem?.(it);
4402
4407
  },
4403
4408
  role: "menuitem",
4404
4409
  children: [
@@ -4406,25 +4411,93 @@ var UnsavedTray = ({
4406
4411
  "span",
4407
4412
  {
4408
4413
  className: "ra-unsaved-popover-dot",
4409
- style: { background: statusDot(d.status) },
4414
+ style: { background: statusDot(it.status) },
4410
4415
  "aria-hidden": "true"
4411
4416
  }
4412
4417
  ),
4413
- /* @__PURE__ */ jsx("span", { className: "ra-unsaved-popover-label", children: d.label || "Default" }),
4414
- d.context && /* @__PURE__ */ jsx("span", { className: "ra-unsaved-popover-ctx", children: d.context }),
4415
- d.status === "error" && /* @__PURE__ */ jsx("span", { className: "ra-unsaved-popover-err", children: "failed" })
4418
+ /* @__PURE__ */ jsx("span", { className: "ra-unsaved-popover-label", children: it.label || "Default" }),
4419
+ it.status === "error" && /* @__PURE__ */ jsx("span", { className: "ra-unsaved-popover-err", children: "failed" })
4416
4420
  ]
4417
4421
  },
4418
- d.key
4422
+ it.editorId
4419
4423
  )) })
4420
4424
  ]
4421
4425
  }
4422
4426
  );
4423
4427
  };
4428
+ var HEADER_PILL_OVERFLOW_THRESHOLD = 5;
4429
+ function canRenderInHeader(items) {
4430
+ if (items.length === 0) return false;
4431
+ if (items.length >= HEADER_PILL_OVERFLOW_THRESHOLD) return false;
4432
+ if (items.some((i) => i.status === "error")) return false;
4433
+ return true;
4434
+ }
4435
+ var HeaderUnsavedPill = ({
4436
+ items,
4437
+ isSaving,
4438
+ onSaveAll,
4439
+ onDiscardAll,
4440
+ saveLabel,
4441
+ discardLabel,
4442
+ saveAllLabel,
4443
+ discardAllLabel,
4444
+ countTemplate,
4445
+ SaveIcon,
4446
+ DiscardIcon
4447
+ }) => {
4448
+ const SI = SaveIcon ?? Save;
4449
+ const DI = DiscardIcon ?? Undo2;
4450
+ if (items.length === 0) return null;
4451
+ const total = items.length;
4452
+ const isSingle = total === 1;
4453
+ return /* @__PURE__ */ jsxs(
4454
+ "div",
4455
+ {
4456
+ className: "ra-unsaved-pill",
4457
+ role: "status",
4458
+ "aria-live": "polite",
4459
+ onClick: (e) => e.stopPropagation(),
4460
+ onMouseDown: (e) => e.stopPropagation(),
4461
+ onTouchStart: (e) => e.stopPropagation(),
4462
+ children: [
4463
+ !isSingle && /* @__PURE__ */ jsxs(Fragment, { children: [
4464
+ /* @__PURE__ */ jsx("span", { className: "ra-unsaved-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(AlertCircle, { className: "w-3.5 h-3.5" }) }),
4465
+ /* @__PURE__ */ jsx("span", { className: "ra-unsaved-pill-text", children: countTemplate.replace("{n}", String(total)) })
4466
+ ] }),
4467
+ /* @__PURE__ */ jsxs(
4468
+ "button",
4469
+ {
4470
+ type: "button",
4471
+ className: "ra-unsaved-btn ra-unsaved-btn-ghost",
4472
+ onClick: onDiscardAll,
4473
+ disabled: isSaving,
4474
+ children: [
4475
+ /* @__PURE__ */ jsx(DI, { className: "w-3 h-3" }),
4476
+ isSingle ? discardLabel : discardAllLabel
4477
+ ]
4478
+ }
4479
+ ),
4480
+ /* @__PURE__ */ jsxs(
4481
+ "button",
4482
+ {
4483
+ type: "button",
4484
+ className: "ra-unsaved-btn ra-unsaved-btn-primary",
4485
+ onClick: onSaveAll,
4486
+ disabled: isSaving,
4487
+ children: [
4488
+ /* @__PURE__ */ jsx(SI, { className: "w-3 h-3" }),
4489
+ isSingle ? saveLabel : saveAllLabel
4490
+ ]
4491
+ }
4492
+ )
4493
+ ]
4494
+ }
4495
+ );
4496
+ };
4424
4497
  var SaveAllProgress = ({
4425
4498
  open,
4426
- drafts,
4427
- store,
4499
+ items,
4500
+ saveOne,
4428
4501
  onClose,
4429
4502
  onJumpToError,
4430
4503
  i18n
@@ -4432,42 +4505,44 @@ var SaveAllProgress = ({
4432
4505
  const [running, setRunning] = useState(false);
4433
4506
  const [done, setDone] = useState(false);
4434
4507
  const stopRef = useRef(false);
4435
- const [batch, setBatch] = useState([]);
4508
+ const [batchIds, setBatchIds] = useState([]);
4436
4509
  useEffect(() => {
4437
4510
  if (!open) {
4438
4511
  setRunning(false);
4439
4512
  setDone(false);
4440
4513
  stopRef.current = false;
4441
- setBatch([]);
4514
+ setBatchIds([]);
4442
4515
  return;
4443
4516
  }
4444
- const initial = drafts.filter((d) => d.status !== "saved");
4445
- setBatch(initial);
4517
+ const initial = items.map((i) => i.editorId);
4518
+ setBatchIds(initial);
4446
4519
  void runBatch(initial);
4447
4520
  }, [open]);
4448
- const runBatch = async (items) => {
4521
+ const runBatch = async (ids) => {
4449
4522
  setRunning(true);
4450
4523
  setDone(false);
4451
4524
  stopRef.current = false;
4452
- for (const item of items) {
4525
+ for (const id of ids) {
4453
4526
  if (stopRef.current) break;
4454
- const live = store.get(item.key);
4455
- if (!live) continue;
4456
- if (live.status === "saved") continue;
4457
- store.setStatus(live.key, "saving");
4458
4527
  try {
4459
- await live.save();
4460
- if (store.has(live.key)) store.setStatus(live.key, "saved");
4461
- } catch (err) {
4462
- store.setStatus(live.key, "error", err);
4528
+ await saveOne(id);
4529
+ } catch {
4463
4530
  }
4464
4531
  }
4465
4532
  setRunning(false);
4466
4533
  setDone(true);
4467
4534
  };
4468
- const visible = batch.map((d) => store.get(d.key) ?? d);
4535
+ const visible = useMemo(() => batchIds.map((id) => {
4536
+ const live = items.find((i) => i.editorId === id);
4537
+ return live ?? {
4538
+ editorId: id,
4539
+ label: "",
4540
+ status: "saved",
4541
+ scope: { kind: "collection", raw: "" }
4542
+ };
4543
+ }), [batchIds, items]);
4469
4544
  const total = visible.length;
4470
- const completed = visible.filter((d) => d.status === "saved" || d.status === "error").length;
4545
+ const completed = visible.filter((d) => d.status !== "dirty" && d.status !== "saving").length;
4471
4546
  const errors = visible.filter((d) => d.status === "error");
4472
4547
  const successAll = done && errors.length === 0 && completed === total;
4473
4548
  useEffect(() => {
@@ -4516,7 +4591,7 @@ var SaveAllProgress = ({
4516
4591
  ] }),
4517
4592
  /* @__PURE__ */ jsx("span", { className: "ra-saveall-label", children: d.label || "Default" }),
4518
4593
  d.status === "error" && /* @__PURE__ */ jsx("span", { className: "ra-saveall-err", title: String(d.error?.message ?? d.error ?? ""), children: d.error?.message ?? "Save failed" })
4519
- ] }, d.key)) }),
4594
+ ] }, d.editorId)) }),
4520
4595
  /* @__PURE__ */ jsxs("div", { className: "ra-saveall-actions", children: [
4521
4596
  running && /* @__PURE__ */ jsx(
4522
4597
  "button",
@@ -4547,7 +4622,7 @@ var SaveAllProgress = ({
4547
4622
  {
4548
4623
  type: "button",
4549
4624
  className: "ra-unsaved-btn ra-unsaved-btn-primary",
4550
- onClick: () => void runBatch(errors),
4625
+ onClick: () => void runBatch(errors.map((e) => e.editorId)),
4551
4626
  children: i18n.retryFailed
4552
4627
  }
4553
4628
  )
@@ -4568,30 +4643,683 @@ var SaveAllProgress = ({
4568
4643
  }
4569
4644
  );
4570
4645
  };
4571
- var ClipboardToast = ({ message, variant = "copy", onDismiss }) => {
4572
- useEffect(() => {
4573
- const t = window.setTimeout(onDismiss, 2500);
4574
- return () => window.clearTimeout(t);
4575
- }, [message, onDismiss]);
4576
- const Icon = variant === "paste" ? ClipboardPaste : Copy;
4577
- return /* @__PURE__ */ jsxs(
4578
- "div",
4579
- {
4580
- role: "status",
4581
- "aria-live": "polite",
4582
- className: "ra-clipboard-toast",
4583
- children: [
4584
- /* @__PURE__ */ jsx(Icon, { className: "w-3.5 h-3.5 shrink-0", "aria-hidden": "true" }),
4585
- /* @__PURE__ */ jsx("span", { className: "truncate", children: message })
4586
- ]
4587
- }
4588
- );
4589
- };
4590
4646
 
4591
- // src/components/RecordsAdmin/data/csv.ts
4592
- var escapeCell = (s) => {
4593
- if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
4594
- return s;
4647
+ // src/components/RecordsAdmin/editor-session/editorStore.ts
4648
+ var isEqual = (a, b) => {
4649
+ if (a === b) return true;
4650
+ try {
4651
+ return JSON.stringify(a) === JSON.stringify(b);
4652
+ } catch {
4653
+ return false;
4654
+ }
4655
+ };
4656
+ var cloneDeep = (v) => {
4657
+ if (v == null) return v;
4658
+ try {
4659
+ return structuredClone(v);
4660
+ } catch {
4661
+ return JSON.parse(JSON.stringify(v));
4662
+ }
4663
+ };
4664
+ var TITLE_KEYS = ["title", "name", "label", "heading", "question", "slug"];
4665
+ var TITLE_WRAPPERS = ["display", "content", "meta", "data"];
4666
+ var deriveDefaultLabel = (value, spec) => {
4667
+ const pickString = (obj) => {
4668
+ if (!obj || typeof obj !== "object") return void 0;
4669
+ for (const key of TITLE_KEYS) {
4670
+ const raw = obj[key];
4671
+ if (typeof raw === "string" && raw.trim()) return raw.trim();
4672
+ }
4673
+ return void 0;
4674
+ };
4675
+ const v = value;
4676
+ const top = pickString(v);
4677
+ if (top) return top;
4678
+ if (v && typeof v === "object") {
4679
+ for (const wrapper of TITLE_WRAPPERS) {
4680
+ const nested = pickString(v[wrapper]);
4681
+ if (nested) return nested;
4682
+ }
4683
+ }
4684
+ if (spec.scope.raw?.startsWith("item:")) return "Untitled item";
4685
+ if (spec.scope.kind === "rule") return "Rule";
4686
+ if (spec.scope.kind && spec.scope.kind !== "collection") {
4687
+ return spec.scope.kind.charAt(0).toUpperCase() + spec.scope.kind.slice(1);
4688
+ }
4689
+ return "Default";
4690
+ };
4691
+ var editorIdCounter = 0;
4692
+ var mintEditorId = () => {
4693
+ editorIdCounter += 1;
4694
+ return `ed_${Date.now().toString(36)}_${editorIdCounter.toString(36)}`;
4695
+ };
4696
+ var recomputePinned = (e) => {
4697
+ if (e.status === "saving" || e.status === "error") return true;
4698
+ if (e.status === "dirty") return true;
4699
+ return false;
4700
+ };
4701
+ var recomputeStatus = (e) => {
4702
+ if (e.status === "saving") return "saving";
4703
+ if (e.status === "error") return "error";
4704
+ const valueDiff = !isEqual(e.value, e.baseline);
4705
+ const ruleDiff = !isEqual(e.facetRule, e.baselineFacetRule);
4706
+ if (valueDiff || ruleDiff) return "dirty";
4707
+ return e.status === "saved" ? "saved" : "idle";
4708
+ };
4709
+ var createEditorStore = () => {
4710
+ const map = /* @__PURE__ */ new Map();
4711
+ const listeners = /* @__PURE__ */ new Set();
4712
+ let cachedList = [];
4713
+ let nextOrder = 0;
4714
+ const recompute = () => {
4715
+ cachedList = Array.from(map.values()).sort((a, b) => a.order - b.order);
4716
+ };
4717
+ const emit = () => {
4718
+ recompute();
4719
+ listeners.forEach((l) => l());
4720
+ };
4721
+ const update = (editorId, mut) => {
4722
+ const prev = map.get(editorId);
4723
+ if (!prev) return false;
4724
+ const draft = { ...prev };
4725
+ const result = mut(draft);
4726
+ const next = result ?? draft;
4727
+ next.status = recomputeStatus(next);
4728
+ next.pinned = recomputePinned(next);
4729
+ map.set(editorId, next);
4730
+ emit();
4731
+ return true;
4732
+ };
4733
+ return {
4734
+ list: () => cachedList,
4735
+ get: (editorId) => map.get(editorId),
4736
+ findExistingEditorIdFor(spec) {
4737
+ if (spec.recordId) {
4738
+ for (const entry of map.values()) {
4739
+ if (entry.recordId === spec.recordId) return entry.editorId;
4740
+ }
4741
+ }
4742
+ const wantCreate = !!spec.createMode;
4743
+ for (const entry of map.values()) {
4744
+ if (entry.recordId) continue;
4745
+ if (entry.spec.scope.raw !== spec.scope.raw) continue;
4746
+ if (!!entry.spec.createMode !== wantCreate) continue;
4747
+ return entry.editorId;
4748
+ }
4749
+ return void 0;
4750
+ },
4751
+ open(input) {
4752
+ const editorId = input.editorId ?? mintEditorId();
4753
+ if (map.has(editorId)) return editorId;
4754
+ const seedValue = input.seed?.value ?? input.defaultValue;
4755
+ const seedFacetRule = input.seed?.facetRule ?? input.spec.initialFacetRule ?? null;
4756
+ const source = input.seed?.source ?? "empty";
4757
+ const value = cloneDeep(seedValue);
4758
+ const baseline = cloneDeep(seedValue);
4759
+ const entry = {
4760
+ editorId,
4761
+ spec: input.spec,
4762
+ saveSpec: input.saveSpec,
4763
+ value,
4764
+ baseline,
4765
+ facetRule: seedFacetRule,
4766
+ baselineFacetRule: seedFacetRule,
4767
+ status: "idle",
4768
+ source,
4769
+ recordId: input.seed?.recordId ?? input.spec.recordId,
4770
+ parentValue: input.seed?.parentValue ?? null,
4771
+ label: input.label ?? input.spec.label ?? deriveDefaultLabel(value, input.spec),
4772
+ order: nextOrder++,
4773
+ lastActiveAt: Date.now(),
4774
+ pinned: false,
4775
+ mounted: false
4776
+ };
4777
+ map.set(editorId, entry);
4778
+ emit();
4779
+ return editorId;
4780
+ },
4781
+ close(editorId) {
4782
+ const entry = map.get(editorId);
4783
+ if (!entry) return false;
4784
+ if (entry.pinned) return false;
4785
+ map.delete(editorId);
4786
+ emit();
4787
+ return true;
4788
+ },
4789
+ clearUnpinned() {
4790
+ let changed = false;
4791
+ for (const [id, entry] of map.entries()) {
4792
+ if (!entry.pinned) {
4793
+ map.delete(id);
4794
+ changed = true;
4795
+ }
4796
+ }
4797
+ if (changed) emit();
4798
+ },
4799
+ setValue(editorId, next) {
4800
+ update(editorId, (e) => {
4801
+ const prev = e.value;
4802
+ const resolved = typeof next === "function" ? next(prev) : next;
4803
+ e.value = resolved;
4804
+ if (e.status === "saved" || e.status === "error") {
4805
+ e.status = "idle";
4806
+ e.error = void 0;
4807
+ }
4808
+ if (!e.spec.label) {
4809
+ e.label = deriveDefaultLabel(e.value, e.spec);
4810
+ }
4811
+ });
4812
+ },
4813
+ setFacetRule(editorId, next) {
4814
+ update(editorId, (e) => {
4815
+ e.facetRule = next;
4816
+ if (e.status === "saved" || e.status === "error") {
4817
+ e.status = "idle";
4818
+ e.error = void 0;
4819
+ }
4820
+ });
4821
+ },
4822
+ hydrateFromResolver(editorId, resolved) {
4823
+ const entry = map.get(editorId);
4824
+ if (!entry) return;
4825
+ if (entry.status === "dirty" || entry.status === "saving" || entry.status === "error") return;
4826
+ if (entry.status === "saved") {
4827
+ update(editorId, (e) => {
4828
+ e.source = resolved.source;
4829
+ if (resolved.recordId) e.recordId = resolved.recordId;
4830
+ e.parentValue = resolved.parentValue ?? null;
4831
+ });
4832
+ return;
4833
+ }
4834
+ update(editorId, (e) => {
4835
+ const fresh = resolved.data == null ? e.value : cloneDeep(resolved.data);
4836
+ e.value = fresh;
4837
+ e.baseline = cloneDeep(fresh);
4838
+ const rule = resolved.facetRule ?? e.facetRule;
4839
+ e.facetRule = rule;
4840
+ e.baselineFacetRule = rule;
4841
+ e.source = resolved.source;
4842
+ if (resolved.recordId) e.recordId = resolved.recordId;
4843
+ e.parentValue = resolved.parentValue ?? null;
4844
+ if (!e.spec.label) e.label = deriveDefaultLabel(fresh, e.spec);
4845
+ });
4846
+ },
4847
+ markMounted(editorId) {
4848
+ update(editorId, (e) => {
4849
+ e.mounted = true;
4850
+ });
4851
+ },
4852
+ markActive(editorId) {
4853
+ update(editorId, (e) => {
4854
+ e.lastActiveAt = Date.now();
4855
+ });
4856
+ },
4857
+ async save(editorId) {
4858
+ const entry = map.get(editorId);
4859
+ if (!entry) return;
4860
+ if (entry.status !== "dirty" && entry.status !== "error") return;
4861
+ const persistedValue = entry.value;
4862
+ const persistedFacetRule = entry.facetRule;
4863
+ const { ctx, anchors } = entry.saveSpec;
4864
+ const spec = entry.spec;
4865
+ update(editorId, (e) => {
4866
+ e.status = "saving";
4867
+ e.error = void 0;
4868
+ });
4869
+ try {
4870
+ let nextRecordId = entry.recordId;
4871
+ if (entry.recordId && entry.source === "self") {
4872
+ await updateRecord(ctx, entry.recordId, {
4873
+ data: persistedValue,
4874
+ facetRule: persistedFacetRule
4875
+ });
4876
+ } else if (spec.createMode) {
4877
+ const created = await createRecord(ctx, {
4878
+ ref: spec.scope.kind === "rule" && spec.scope.raw ? spec.scope.raw : spec.ref,
4879
+ scope: anchors,
4880
+ data: persistedValue,
4881
+ facetRule: persistedFacetRule
4882
+ });
4883
+ nextRecordId = created?.id ?? nextRecordId;
4884
+ } else {
4885
+ const upserted = await upsertRecord(ctx, {
4886
+ ref: spec.scope.kind === "rule" && spec.scope.raw ? spec.scope.raw : spec.ref,
4887
+ scope: anchors,
4888
+ data: persistedValue,
4889
+ facetRule: persistedFacetRule
4890
+ });
4891
+ nextRecordId = upserted.record?.id ?? nextRecordId;
4892
+ }
4893
+ update(editorId, (e) => {
4894
+ e.baseline = cloneDeep(persistedValue);
4895
+ e.baselineFacetRule = persistedFacetRule;
4896
+ e.recordId = nextRecordId;
4897
+ e.source = "self";
4898
+ e.status = "saved";
4899
+ e.error = void 0;
4900
+ });
4901
+ } catch (err) {
4902
+ update(editorId, (e) => {
4903
+ e.status = "error";
4904
+ e.error = err;
4905
+ });
4906
+ throw err;
4907
+ }
4908
+ },
4909
+ reset(editorId) {
4910
+ update(editorId, (e) => {
4911
+ e.value = cloneDeep(e.baseline);
4912
+ e.facetRule = e.baselineFacetRule;
4913
+ e.status = "idle";
4914
+ e.error = void 0;
4915
+ });
4916
+ },
4917
+ async remove(editorId) {
4918
+ const entry = map.get(editorId);
4919
+ if (!entry) return;
4920
+ if (entry.source !== "self") return;
4921
+ if (!entry.recordId) return;
4922
+ await removeRecord(entry.saveSpec.ctx, entry.recordId);
4923
+ map.delete(editorId);
4924
+ emit();
4925
+ },
4926
+ enforcePoolLimit(max, exceptEditorId) {
4927
+ const alive = Array.from(map.values());
4928
+ if (alive.length <= max) return;
4929
+ const evictable = alive.filter((e) => !e.pinned && e.editorId !== exceptEditorId).sort((a, b) => a.lastActiveAt - b.lastActiveAt);
4930
+ let toEvict = alive.length - max;
4931
+ let changed = false;
4932
+ for (const entry of evictable) {
4933
+ if (toEvict <= 0) break;
4934
+ map.delete(entry.editorId);
4935
+ toEvict -= 1;
4936
+ changed = true;
4937
+ }
4938
+ if (changed) emit();
4939
+ },
4940
+ subscribe(listener) {
4941
+ listeners.add(listener);
4942
+ return () => {
4943
+ listeners.delete(listener);
4944
+ };
4945
+ }
4946
+ };
4947
+ };
4948
+ var buildSaveSpec = (ctx, spec) => ({
4949
+ ctx,
4950
+ anchors: parsedRefToScope(spec.scope)
4951
+ });
4952
+ var EditorSessionContext = createContext(null);
4953
+ var useEditorSessionContext = () => {
4954
+ const ctx = useContext(EditorSessionContext);
4955
+ if (!ctx) {
4956
+ throw new Error(
4957
+ "[smartlinks-ui] useEditorSession / useEditorSelection / useDirtyOverview must be used inside <EditorSessionProvider> (RecordsAdminShell mounts one for you)."
4958
+ );
4959
+ }
4960
+ return ctx;
4961
+ };
4962
+ var EditorSessionProvider = ({
4963
+ ctx,
4964
+ children,
4965
+ maxOpenEditors = 8,
4966
+ defaultValueFactory
4967
+ }) => {
4968
+ const storeRef = useRef(null);
4969
+ if (!storeRef.current) storeRef.current = createEditorStore();
4970
+ const currentRef = useRef(void 0);
4971
+ const listenersRef = useRef(/* @__PURE__ */ new Set());
4972
+ const subscribeCurrent = useCallback((cb) => {
4973
+ listenersRef.current.add(cb);
4974
+ return () => {
4975
+ listenersRef.current.delete(cb);
4976
+ };
4977
+ }, []);
4978
+ const getCurrent = useCallback(() => currentRef.current, []);
4979
+ const setCurrentEditorId = useCallback((id) => {
4980
+ if (currentRef.current === id) return;
4981
+ currentRef.current = id;
4982
+ listenersRef.current.forEach((l) => l());
4983
+ }, []);
4984
+ const currentEditorId = useSyncExternalStore(subscribeCurrent, getCurrent, getCurrent);
4985
+ const value = useMemo(() => ({
4986
+ store: storeRef.current,
4987
+ ctx,
4988
+ currentEditorId,
4989
+ setCurrentEditorId,
4990
+ maxOpenEditors,
4991
+ defaultValueFactory
4992
+ }), [ctx, currentEditorId, setCurrentEditorId, maxOpenEditors, defaultValueFactory]);
4993
+ return /* @__PURE__ */ jsx(EditorSessionContext.Provider, { value, children });
4994
+ };
4995
+ var useEntry = (editorId) => {
4996
+ const { store } = useEditorSessionContext();
4997
+ const subscribe = useCallback((cb) => store.subscribe(cb), [store]);
4998
+ const getSnapshot = useCallback(
4999
+ () => editorId ? store.get(editorId) : void 0,
5000
+ [store, editorId]
5001
+ );
5002
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
5003
+ };
5004
+ var useStoreList = () => {
5005
+ const { store } = useEditorSessionContext();
5006
+ const subscribe = useCallback((cb) => store.subscribe(cb), [store]);
5007
+ const getSnapshot = useCallback(() => store.list(), [store]);
5008
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
5009
+ };
5010
+ function useEditorSession(editorId) {
5011
+ const { store, currentEditorId, ctx } = useEditorSessionContext();
5012
+ const queryClient = useQueryClient();
5013
+ const entry = useEntry(editorId);
5014
+ const onChange = useCallback((next) => {
5015
+ if (!editorId) return;
5016
+ store.setValue(editorId, next);
5017
+ }, [store, editorId]);
5018
+ const onFacetRuleChange = useCallback((next) => {
5019
+ if (!editorId) return;
5020
+ store.setFacetRule(editorId, next);
5021
+ }, [store, editorId]);
5022
+ const save = useCallback(async () => {
5023
+ if (!editorId) return;
5024
+ try {
5025
+ await store.save(editorId);
5026
+ const e = store.get(editorId);
5027
+ if (e) {
5028
+ const key = resolvedRecordQueryKey({
5029
+ collectionId: ctx.collectionId,
5030
+ appId: ctx.appId,
5031
+ recordType: ctx.recordType,
5032
+ productId: e.spec.scope.productId,
5033
+ variantId: e.spec.scope.variantId,
5034
+ batchId: e.spec.scope.batchId,
5035
+ facetId: e.spec.scope.facetId,
5036
+ facetValue: e.spec.scope.facetValue,
5037
+ proofId: e.spec.scope.proofId,
5038
+ recordId: e.recordId,
5039
+ withParent: true
5040
+ });
5041
+ queryClient.invalidateQueries({ queryKey: key });
5042
+ }
5043
+ } catch {
5044
+ }
5045
+ }, [store, editorId, ctx, queryClient]);
5046
+ const reset = useCallback(() => {
5047
+ if (!editorId) return;
5048
+ store.reset(editorId);
5049
+ }, [store, editorId]);
5050
+ const remove = useCallback(async () => {
5051
+ if (!editorId) return;
5052
+ await store.remove(editorId);
5053
+ }, [store, editorId]);
5054
+ if (!editorId || !entry) return null;
5055
+ const isRuleScope = entry.spec.scope.kind === "rule";
5056
+ const ruleValid = !isRuleScope || isFacetRuleValid(entry.facetRule);
5057
+ const canSave = ruleValid;
5058
+ const cannotSaveReason = !ruleValid ? "Pick at least one value for every facet in the rule before saving." : void 0;
5059
+ return {
5060
+ editorId,
5061
+ value: entry.value,
5062
+ onChange,
5063
+ source: entry.source,
5064
+ recordId: entry.recordId,
5065
+ parentValue: entry.parentValue,
5066
+ scope: entry.spec.scope,
5067
+ isDirty: entry.status === "dirty" || entry.status === "saving" || entry.status === "error",
5068
+ status: entry.status,
5069
+ error: entry.error ?? null,
5070
+ save,
5071
+ reset,
5072
+ remove,
5073
+ canRemove: entry.source === "self" && !!entry.recordId,
5074
+ canSave,
5075
+ cannotSaveReason,
5076
+ facetRule: entry.facetRule,
5077
+ onFacetRuleChange,
5078
+ isActive: editorId === currentEditorId
5079
+ };
5080
+ }
5081
+ var useEditorSelection = () => {
5082
+ const { store, ctx, currentEditorId, setCurrentEditorId, maxOpenEditors, defaultValueFactory } = useEditorSessionContext();
5083
+ const openEditors = useStoreList();
5084
+ const selectTarget = useCallback((input) => {
5085
+ const existing = store.findExistingEditorIdFor(input.spec);
5086
+ if (existing) {
5087
+ store.markActive(existing);
5088
+ setCurrentEditorId(existing);
5089
+ store.enforcePoolLimit(maxOpenEditors, existing);
5090
+ return existing;
5091
+ }
5092
+ const editorId = store.open({
5093
+ spec: input.spec,
5094
+ saveSpec: buildSaveSpec(ctx, input.spec),
5095
+ seed: input.seed ?? null,
5096
+ defaultValue: input.defaultValue ?? defaultValueFactory?.() ?? {},
5097
+ label: input.label
5098
+ });
5099
+ setCurrentEditorId(editorId);
5100
+ store.enforcePoolLimit(maxOpenEditors, editorId);
5101
+ return editorId;
5102
+ }, [store, ctx, setCurrentEditorId, maxOpenEditors, defaultValueFactory]);
5103
+ const focus = useCallback((editorId) => {
5104
+ store.markActive(editorId);
5105
+ setCurrentEditorId(editorId);
5106
+ }, [store, setCurrentEditorId]);
5107
+ const blur = useCallback(() => setCurrentEditorId(void 0), [setCurrentEditorId]);
5108
+ const close = useCallback((editorId) => {
5109
+ const closed = store.close(editorId);
5110
+ if (closed && currentEditorId === editorId) setCurrentEditorId(void 0);
5111
+ return closed;
5112
+ }, [store, currentEditorId, setCurrentEditorId]);
5113
+ const hydrate = useCallback((editorId, resolved) => {
5114
+ store.hydrateFromResolver(editorId, resolved);
5115
+ }, [store]);
5116
+ return { currentEditorId, openEditors, selectTarget, focus, blur, close, hydrate };
5117
+ };
5118
+ var overviewItemFromEntry = (e) => ({
5119
+ editorId: e.editorId,
5120
+ label: e.label,
5121
+ status: e.status,
5122
+ error: e.error,
5123
+ scope: e.spec.scope,
5124
+ recordId: e.recordId
5125
+ });
5126
+ var useDirtyOverview = () => {
5127
+ const { store } = useEditorSessionContext();
5128
+ const list = useStoreList();
5129
+ const items = useMemo(
5130
+ () => list.filter((e) => e.status === "dirty" || e.status === "saving" || e.status === "error").map(overviewItemFromEntry),
5131
+ [list]
5132
+ );
5133
+ const count = items.length;
5134
+ const errorCount = useMemo(() => items.filter((i) => i.status === "error").length, [items]);
5135
+ const saveAll = useCallback(async () => {
5136
+ const ids = list.filter((e) => e.status === "dirty" || e.status === "error").map((e) => e.editorId);
5137
+ for (const id of ids) {
5138
+ try {
5139
+ await store.save(id);
5140
+ } catch {
5141
+ }
5142
+ }
5143
+ }, [store, list]);
5144
+ const discardAll = useCallback(() => {
5145
+ const ids = list.filter((e) => e.status === "dirty" || e.status === "error").map((e) => e.editorId);
5146
+ ids.forEach((id) => store.reset(id));
5147
+ }, [store, list]);
5148
+ const saveOne = useCallback(async (editorId) => {
5149
+ await store.save(editorId);
5150
+ }, [store]);
5151
+ return { items, count, errorCount, saveAll, discardAll, saveOne };
5152
+ };
5153
+ var EditorMountPool = ({
5154
+ renderSlot,
5155
+ keepMountedHidden = true,
5156
+ fallback = null,
5157
+ className
5158
+ }) => {
5159
+ const { openEditors, currentEditorId } = useEditorSelection();
5160
+ const ids = useMemo(
5161
+ () => openEditors.map((e) => e.editorId).sort(),
5162
+ [openEditors]
5163
+ );
5164
+ if (!currentEditorId && ids.length === 0) {
5165
+ return /* @__PURE__ */ jsx(Fragment, { children: fallback });
5166
+ }
5167
+ const visibleIds = keepMountedHidden ? ids : currentEditorId ? [currentEditorId] : [];
5168
+ return /* @__PURE__ */ jsx("div", { className, style: { display: "contents" }, children: visibleIds.map((id) => {
5169
+ const isActive = id === currentEditorId;
5170
+ return /* @__PURE__ */ jsx(EditorPoolSlot, { editorId: id, isActive, children: renderSlot(id) }, id);
5171
+ }) });
5172
+ };
5173
+ var EditorPoolSlot = ({ editorId, isActive, children }) => {
5174
+ const inertProps = isActive ? {} : { inert: "" };
5175
+ return /* @__PURE__ */ jsx(
5176
+ "div",
5177
+ {
5178
+ "data-editor-slot": editorId,
5179
+ "data-active": isActive ? "true" : "false",
5180
+ "aria-hidden": isActive ? void 0 : true,
5181
+ style: {
5182
+ display: isActive ? "contents" : "none"
5183
+ },
5184
+ ...inertProps,
5185
+ children
5186
+ }
5187
+ );
5188
+ };
5189
+ function useEditorSlotContext(editorId) {
5190
+ const session = useEditorSession(editorId);
5191
+ return useMemo(() => {
5192
+ if (!session) return null;
5193
+ return {
5194
+ value: session.value,
5195
+ onChange: session.onChange,
5196
+ source: session.source,
5197
+ recordId: session.recordId,
5198
+ parentValue: session.parentValue,
5199
+ scope: session.scope,
5200
+ isDirty: session.isDirty,
5201
+ save: session.save,
5202
+ reset: session.reset,
5203
+ remove: session.remove,
5204
+ canRemove: session.canRemove,
5205
+ canSave: session.canSave,
5206
+ cannotSaveReason: session.cannotSaveReason,
5207
+ isSaving: session.status === "saving",
5208
+ saveError: session.error,
5209
+ facetRule: session.facetRule,
5210
+ onFacetRuleChange: session.onFacetRuleChange
5211
+ };
5212
+ }, [session]);
5213
+ }
5214
+ var isEqualSpec = (a, b) => {
5215
+ if (a === b) return true;
5216
+ if (!a || !b) return false;
5217
+ if (a.scope.raw !== b.scope.raw) return false;
5218
+ if ((a.recordId ?? null) !== (b.recordId ?? null)) return false;
5219
+ if (!!a.createMode !== !!b.createMode) return false;
5220
+ return true;
5221
+ };
5222
+ function useEditorBridge(args) {
5223
+ const { target, resolved, defaultData, onSaved, onDeleted } = args;
5224
+ const selection = useEditorSelection();
5225
+ const lastTargetRef = useRef(null);
5226
+ const editorIdRef = useRef(void 0);
5227
+ useEffect(() => {
5228
+ if (!target) {
5229
+ selection.blur();
5230
+ lastTargetRef.current = null;
5231
+ editorIdRef.current = void 0;
5232
+ return;
5233
+ }
5234
+ if (isEqualSpec(target, lastTargetRef.current)) return;
5235
+ lastTargetRef.current = target;
5236
+ const id = selection.selectTarget({
5237
+ spec: target,
5238
+ seed: resolved.source === "empty" ? void 0 : {
5239
+ value: resolved.data ?? (defaultData?.() ?? null),
5240
+ facetRule: resolved.facetRule ?? null,
5241
+ source: resolved.source,
5242
+ parentValue: resolved.parentValue ?? null,
5243
+ recordId: resolved.recordId
5244
+ },
5245
+ defaultValue: defaultData?.() ?? {}
5246
+ });
5247
+ editorIdRef.current = id;
5248
+ }, [target?.scope.raw, target?.recordId, target?.createMode]);
5249
+ useEffect(() => {
5250
+ const id = editorIdRef.current;
5251
+ if (!id) return;
5252
+ if (resolved.source === "empty" && !resolved.recordId) return;
5253
+ selection.hydrate(id, resolved);
5254
+ }, [resolved.source, resolved.recordId, resolved.sourceRef, resolved.data, resolved.facetRule]);
5255
+ const editorId = selection.currentEditorId ?? editorIdRef.current;
5256
+ const session = useEditorSession(editorId);
5257
+ const prevStatusRef = useRef(null);
5258
+ useEffect(() => {
5259
+ const prev = prevStatusRef.current;
5260
+ const next = session?.status ?? null;
5261
+ prevStatusRef.current = next;
5262
+ if (next === "saved" && prev !== "saved") {
5263
+ onSaved?.();
5264
+ }
5265
+ }, [session?.status]);
5266
+ const remove = useMemo(() => async () => {
5267
+ if (!session) return;
5268
+ await session.remove();
5269
+ onDeleted?.();
5270
+ }, [session?.remove]);
5271
+ if (!session) {
5272
+ const sentinelScope = target?.scope ?? { kind: "collection", raw: "" };
5273
+ return {
5274
+ value: defaultData?.() ?? {},
5275
+ onChange: () => {
5276
+ },
5277
+ source: "empty",
5278
+ recordId: void 0,
5279
+ parentValue: null,
5280
+ scope: sentinelScope,
5281
+ isDirty: false,
5282
+ save: async () => {
5283
+ },
5284
+ reset: () => {
5285
+ },
5286
+ remove: async () => {
5287
+ },
5288
+ canRemove: false,
5289
+ canSave: false,
5290
+ cannotSaveReason: void 0,
5291
+ isSaving: false,
5292
+ saveError: null,
5293
+ facetRule: null,
5294
+ onFacetRuleChange: () => {
5295
+ }
5296
+ };
5297
+ }
5298
+ return {
5299
+ value: session.value,
5300
+ onChange: session.onChange,
5301
+ source: session.source,
5302
+ recordId: session.recordId,
5303
+ parentValue: session.parentValue,
5304
+ scope: session.scope,
5305
+ isDirty: session.isDirty,
5306
+ save: session.save,
5307
+ reset: session.reset,
5308
+ remove,
5309
+ canRemove: session.canRemove,
5310
+ canSave: session.canSave,
5311
+ cannotSaveReason: session.cannotSaveReason,
5312
+ isSaving: session.status === "saving",
5313
+ saveError: session.error,
5314
+ facetRule: session.facetRule,
5315
+ onFacetRuleChange: session.onFacetRuleChange
5316
+ };
5317
+ }
5318
+
5319
+ // src/components/RecordsAdmin/data/csv.ts
5320
+ var escapeCell = (s) => {
5321
+ if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
5322
+ return s;
4595
5323
  };
4596
5324
  var parseLine = (line) => {
4597
5325
  const out = [];
@@ -4692,11 +5420,58 @@ var downloadBlob = (blob, filename) => {
4692
5420
  URL.revokeObjectURL(url);
4693
5421
  };
4694
5422
 
5423
+ // src/components/RecordsAdmin/hooks/useShellCsvBulk.ts
5424
+ function useShellCsvBulk(args) {
5425
+ const { csvSchema, recordType, label, items, ctx, refetchAll, onTelemetry } = args;
5426
+ const fileBase = useMemo(
5427
+ () => recordType ?? (label.toLowerCase().replace(/\s+/g, "-") || "records"),
5428
+ [recordType, label]
5429
+ );
5430
+ const handleExport = useCallback(() => {
5431
+ if (!csvSchema) return;
5432
+ const blob = exportCsv(items, csvSchema);
5433
+ downloadBlob(blob, `${fileBase}.csv`);
5434
+ onTelemetry?.({ type: "csv.export", recordType, rows: items.length });
5435
+ }, [csvSchema, items, fileBase, onTelemetry, recordType]);
5436
+ const handleImport = useCallback(() => {
5437
+ if (!csvSchema) return;
5438
+ const inp = document.createElement("input");
5439
+ inp.type = "file";
5440
+ inp.accept = ".csv,text/csv";
5441
+ inp.onchange = async () => {
5442
+ const file = inp.files?.[0];
5443
+ if (!file) return;
5444
+ const report = await importCsv(file, csvSchema, ctx);
5445
+ onTelemetry?.({ type: "csv.import", recordType, rows: report.total, errors: report.failed });
5446
+ if (report.failed > 0) {
5447
+ downloadBlob(
5448
+ new Blob([report.annotatedCsv], { type: "text/csv" }),
5449
+ `${fileBase}-errors.csv`
5450
+ );
5451
+ }
5452
+ refetchAll();
5453
+ };
5454
+ inp.click();
5455
+ }, [csvSchema, ctx, fileBase, onTelemetry, recordType, refetchAll]);
5456
+ return useMemo(
5457
+ () => csvSchema ? { onImportCsv: handleImport, onExportCsv: handleExport } : {},
5458
+ [csvSchema, handleImport, handleExport]
5459
+ );
5460
+ }
5461
+
4695
5462
  // src/components/RecordsAdmin/shell/tokens.css
4696
5463
  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');
4697
5464
 
4698
5465
  // src/components/RecordsAdmin/shell/shell.css
4699
- 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@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-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");
5466
+ 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");
5467
+ var EditorPoolBody = ({
5468
+ editorId,
5469
+ renderEditor
5470
+ }) => {
5471
+ const ctx = useEditorSlotContext(editorId);
5472
+ if (!ctx) return null;
5473
+ return renderEditor(ctx);
5474
+ };
4700
5475
  var TOP_LEVEL_SCOPES = ["collection", "rule", "product"];
4701
5476
  var WARNED_FACET_DEPRECATED = false;
4702
5477
  var DRAFT_ID = "__draft__";
@@ -4722,7 +5497,23 @@ var productItemToSummary = (p) => {
4722
5497
  };
4723
5498
  };
4724
5499
  function RecordsAdminShell(props) {
4725
- return /* @__PURE__ */ jsx(DirtyDraftProvider, { children: /* @__PURE__ */ jsx(RecordsAdminShellInner, { ...props }) });
5500
+ const ctx = useMemo(
5501
+ () => ({
5502
+ SL: props.SL,
5503
+ collectionId: props.collectionId,
5504
+ appId: props.appId,
5505
+ recordType: props.recordType
5506
+ }),
5507
+ [props.SL, props.collectionId, props.appId, props.recordType]
5508
+ );
5509
+ return /* @__PURE__ */ jsx(
5510
+ EditorSessionProvider,
5511
+ {
5512
+ ctx,
5513
+ defaultValueFactory: props.defaultData,
5514
+ children: /* @__PURE__ */ jsx(RecordsAdminShellInner, { ...props })
5515
+ }
5516
+ );
4726
5517
  }
4727
5518
  function RecordsAdminShellInner(props) {
4728
5519
  const {
@@ -4839,6 +5630,7 @@ function RecordsAdminShellInner(props) {
4839
5630
  () => ({ SL, collectionId, appId, recordType }),
4840
5631
  [SL, collectionId, appId, recordType]
4841
5632
  );
5633
+ const queryClient = useQueryClient();
4842
5634
  const probe = useScopeProbe({ SL, collectionId });
4843
5635
  const topLevelScopes = useMemo(() => {
4844
5636
  const requested = requestedScopes ?? [];
@@ -4880,10 +5672,6 @@ function RecordsAdminShellInner(props) {
4880
5672
  useEffect(() => {
4881
5673
  setActiveScope(initialScope);
4882
5674
  }, [initialScope]);
4883
- const [search, setSearch] = useState("");
4884
- const [filter, setFilter] = useState("all");
4885
- const [ruleFilters, setRuleFilters] = useState(EMPTY_RULE_FILTERS);
4886
- const [facetBrowseFilter, setFacetBrowseFilter] = useState(null);
4887
5675
  const [selectedRecordId, setSelectedRecordId] = useState(null);
4888
5676
  const [draftKind, setDraftKind] = useState(null);
4889
5677
  const [ruleWizardStep, setRuleWizardStep] = useState(null);
@@ -4925,40 +5713,32 @@ function RecordsAdminShellInner(props) {
4925
5713
  if (requested === "header" && !headerWillRender) return "footer";
4926
5714
  return requested;
4927
5715
  }, [intro?.reopenAffordance, headerWillRender]);
4928
- const productBrowse = useProductBrowse({
5716
+ const browser = useShellBrowser({
5717
+ ctx,
4929
5718
  SL,
4930
5719
  collectionId,
4931
- search: activeScope === "product" ? search : "",
4932
- enabled: activeScope === "product" && !contextScope?.productId
5720
+ activeScope,
5721
+ contextScope,
5722
+ probeIsLoading: probe.isLoading,
5723
+ selectedProductId,
5724
+ drillTab,
5725
+ classify: classify2
4933
5726
  });
4934
- const recordListEnabled = (activeScope === "rule" || activeScope === "collection") && !probe.isLoading;
4935
- const recordList = useRecordList({
4936
- ctx,
4937
- scopeKind: activeScope,
5727
+ const {
4938
5728
  search,
5729
+ setSearch,
4939
5730
  filter,
4940
- classify: classify2,
4941
- contextScope,
4942
- enabled: recordListEnabled
4943
- });
4944
- const facetBrowse = useFacetBrowse({
4945
- SL,
4946
- collectionId,
4947
- existing: [],
4948
- enabled: (activeScope === "rule" || activeScope === "collection") && !probe.isLoading
4949
- });
4950
- const variantChildren = useProductChildren({
4951
- SL,
4952
- collectionId,
4953
- productId: selectedProductId,
4954
- kind: drillTab === "variant" ? "variant" : null
4955
- });
4956
- const batchChildren = useProductChildren({
4957
- SL,
4958
- collectionId,
4959
- productId: selectedProductId,
4960
- kind: drillTab === "batch" ? "batch" : null
4961
- });
5731
+ setFilter,
5732
+ ruleFilters,
5733
+ setRuleFilters,
5734
+ facetBrowseFilter,
5735
+ setFacetBrowseFilter,
5736
+ productBrowse,
5737
+ recordList,
5738
+ facetBrowse,
5739
+ variantChildren,
5740
+ batchChildren
5741
+ } = browser;
4962
5742
  useEffect(() => {
4963
5743
  if (activeScope !== "product") return;
4964
5744
  if (selectedProductId) return;
@@ -4975,10 +5755,6 @@ function RecordsAdminShellInner(props) {
4975
5755
  if (first?.id) setSelectedRecordId(first.id);
4976
5756
  }, [activeScope, selectedRecordId, recordList.items, cardinality]);
4977
5757
  useEffect(() => {
4978
- setSearch("");
4979
- setFilter("all");
4980
- setRuleFilters(EMPTY_RULE_FILTERS);
4981
- setFacetBrowseFilter(null);
4982
5758
  setSelectedRecordId(null);
4983
5759
  setDraftKind(null);
4984
5760
  }, [activeScope]);
@@ -5115,9 +5891,35 @@ function RecordsAdminShellInner(props) {
5115
5891
  batchChildren.refetch();
5116
5892
  if (isCollection) collectionItems.refetch();
5117
5893
  }, [productBrowse, recordList, facetBrowse, variantChildren, batchChildren, isCollection, collectionItems]);
5118
- const editorCtx = useRecordEditor({
5119
- ctx,
5120
- scope: editingTargetScope ?? parseRef(""),
5894
+ const editorTargetSpec = useMemo(() => {
5895
+ if (!editingTargetScope) return null;
5896
+ const initialFacetRule = editingTargetScope.kind === "rule" ? ruleWizardStep === 2 && ruleWizardRule ? ruleWizardRule : resolved.facetRule ?? { all: [] } : ruleWizardStep === 2 && ruleWizardRule && isCollection && !!selectedItemId ? ruleWizardRule : null;
5897
+ const label2 = deriveDraftLabel ? deriveDraftLabel(resolved.data, editingTargetScope) : void 0;
5898
+ return {
5899
+ scope: editingTargetScope,
5900
+ recordId: resolved.recordId,
5901
+ // Collection-cardinality item drafts must `create` (insert a new
5902
+ // row) instead of upsert — multiple items can share the same scope
5903
+ // + recordType, so an upsert would collide on the server's dedupe
5904
+ // key. Flip on whenever the right pane shows an item that hasn't
5905
+ // been saved yet (no resolved.recordId).
5906
+ createMode: isCollection && !!selectedItemId && !resolved.recordId,
5907
+ ref: editingTargetScope.kind === "rule" ? editingTargetScope.raw : void 0,
5908
+ initialFacetRule,
5909
+ label: label2
5910
+ };
5911
+ }, [
5912
+ editingTargetScope?.raw,
5913
+ editingTargetScope?.kind,
5914
+ resolved.recordId,
5915
+ resolved.facetRule,
5916
+ isCollection,
5917
+ selectedItemId,
5918
+ ruleWizardStep,
5919
+ ruleWizardRule
5920
+ ]);
5921
+ const editorCtx = useEditorBridge({
5922
+ target: editorTargetSpec,
5121
5923
  resolved: {
5122
5924
  data: resolved.data,
5123
5925
  source: resolved.source,
@@ -5127,18 +5929,6 @@ function RecordsAdminShellInner(props) {
5127
5929
  facetRule: resolved.facetRule
5128
5930
  },
5129
5931
  defaultData,
5130
- reseed: dirtyStrategy === "keep" ? "preserve-dirty" : "always",
5131
- // Collection-cardinality item drafts must `create` (insert a new row)
5132
- // instead of `upsert` — multiple items can share the same scope +
5133
- // recordType, so an upsert would collide on the server's dedupe key.
5134
- // Flip on whenever the right pane shows an item that hasn't been
5135
- // saved yet (no resolved.recordId).
5136
- createMode: isCollection && !!selectedItemId && !resolved.recordId,
5137
- // Seed an empty rule for freshly-minted `rule:{id}` refs so the Targeting
5138
- // section can render its empty-state picker. For existing rule records
5139
- // pull the saved rule off the resolved record. Pinned scopes get `null`
5140
- // and the Targeting section stays hidden.
5141
- initialFacetRule: editingTargetScope?.kind === "rule" ? ruleWizardStep === 2 && ruleWizardRule ? ruleWizardRule : resolved.facetRule ?? { all: [] } : ruleWizardStep === 2 && ruleWizardRule && isCollection && !!selectedItemId ? ruleWizardRule : null,
5142
5932
  onSaved: () => {
5143
5933
  onTelemetry?.({ type: "record.save", recordType, ref: editingTargetScope?.raw ?? "", isCreate: resolved.source !== "self" });
5144
5934
  if (ruleWizardStep !== null) {
@@ -5158,8 +5948,7 @@ function RecordsAdminShellInner(props) {
5158
5948
  setSelectedBatchId(void 0);
5159
5949
  }
5160
5950
  refetchAll();
5161
- },
5162
- deriveDraftLabel
5951
+ }
5163
5952
  });
5164
5953
  useUnsavedGuard({
5165
5954
  isDirty: editorCtx.isDirty,
@@ -5171,23 +5960,65 @@ function RecordsAdminShellInner(props) {
5171
5960
  disableParentMessage: dirtyStrategy === "keep"
5172
5961
  });
5173
5962
  const dirtyConfirm = useConfirmDialog();
5174
- const pasteConfirm = usePasteConfirm();
5175
- const draftStore = useDirtyDraftStore();
5176
- const dirtyDrafts = useDirtyDrafts();
5177
- const dirtyKeys = useMemo(
5178
- () => new Set(dirtyDrafts.filter((d) => d.status !== "saved").map((d) => d.key)),
5179
- [dirtyDrafts]
5180
- );
5181
- const errorKeys = useMemo(
5182
- () => new Set(dirtyDrafts.filter((d) => d.status === "error").map((d) => d.key)),
5183
- [dirtyDrafts]
5184
- );
5963
+ const dirtyOverview = useDirtyOverview();
5964
+ const editorSelection = useEditorSelection();
5965
+ const dirtyItems = dirtyOverview.items;
5966
+ const dirtyKeys = useMemo(() => {
5967
+ const s = /* @__PURE__ */ new Set();
5968
+ for (const it of dirtyItems) {
5969
+ if (it.status === "saved") continue;
5970
+ if (it.recordId) s.add(it.recordId);
5971
+ if (it.scope?.raw) s.add(it.scope.raw);
5972
+ }
5973
+ return s;
5974
+ }, [dirtyItems]);
5975
+ const errorKeys = useMemo(() => {
5976
+ const s = /* @__PURE__ */ new Set();
5977
+ for (const it of dirtyItems) {
5978
+ if (it.status !== "error") continue;
5979
+ if (it.recordId) s.add(it.recordId);
5980
+ if (it.scope?.raw) s.add(it.scope.raw);
5981
+ }
5982
+ return s;
5983
+ }, [dirtyItems]);
5185
5984
  const [saveAllOpen, setSaveAllOpen] = useState(false);
5186
- const clipboard = useRecordClipboard({
5187
- appId,
5188
- recordType: recordType ?? "__default"
5189
- });
5190
- const [clipboardNotice, setClipboardNotice] = useState(null);
5985
+ const handleSaveAll = () => setSaveAllOpen(true);
5986
+ const handleDiscardAll = () => {
5987
+ dirtyOverview.discardAll();
5988
+ };
5989
+ const showHeaderUnsavedPill = dirtyStrategy === "keep" && canRenderInHeader(dirtyItems);
5990
+ const showTrayBanner = dirtyStrategy === "keep" && !showHeaderUnsavedPill && dirtyItems.length > 0;
5991
+ const composedHeaderActions = useMemo(() => {
5992
+ if (!showHeaderUnsavedPill) return headerActions;
5993
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5994
+ /* @__PURE__ */ jsx(
5995
+ HeaderUnsavedPill,
5996
+ {
5997
+ items: dirtyItems,
5998
+ isSaving: saveAllOpen,
5999
+ onSaveAll: handleSaveAll,
6000
+ onDiscardAll: handleDiscardAll,
6001
+ saveLabel: actionLabels?.save ?? i18n.save,
6002
+ discardLabel: actionLabels?.discard ?? i18n.discard,
6003
+ saveAllLabel: actionLabels?.saveAll ?? i18n.saveAll,
6004
+ discardAllLabel: actionLabels?.revertAll ?? i18n.discardAll,
6005
+ countTemplate: i18n.unsavedTrayCount,
6006
+ SaveIcon: actionIcons?.save,
6007
+ DiscardIcon: actionIcons?.discard
6008
+ }
6009
+ ),
6010
+ headerActions
6011
+ ] });
6012
+ }, [
6013
+ showHeaderUnsavedPill,
6014
+ headerActions,
6015
+ dirtyItems,
6016
+ saveAllOpen,
6017
+ actionLabels,
6018
+ actionIcons,
6019
+ i18n
6020
+ ]);
6021
+ const onLeftSelectRef = useRef(null);
5191
6022
  const { runWithGuard } = useDirtyNavigation({
5192
6023
  strategy: dirtyStrategy,
5193
6024
  isDirty: editorCtx.isDirty,
@@ -5202,32 +6033,15 @@ function RecordsAdminShellInner(props) {
5202
6033
  cancel: i18n.unsavedPromptCancel
5203
6034
  }
5204
6035
  });
5205
- const handleExport = () => {
5206
- if (!csvSchema) return;
5207
- const fileBase = recordType ?? (label.toLowerCase().replace(/\s+/g, "-") || "records");
5208
- const blob = exportCsv(recordList.items, csvSchema);
5209
- downloadBlob(blob, `${fileBase}.csv`);
5210
- onTelemetry?.({ type: "csv.export", recordType, rows: recordList.items.length });
5211
- };
5212
- const handleImport = () => {
5213
- if (!csvSchema) return;
5214
- const inp = document.createElement("input");
5215
- inp.type = "file";
5216
- inp.accept = ".csv,text/csv";
5217
- inp.onchange = async () => {
5218
- const file = inp.files?.[0];
5219
- if (!file) return;
5220
- const report = await importCsv(file, csvSchema, ctx);
5221
- onTelemetry?.({ type: "csv.import", recordType, rows: report.total, errors: report.failed });
5222
- if (report.failed > 0) {
5223
- const fileBase = recordType ?? (label.toLowerCase().replace(/\s+/g, "-") || "records");
5224
- downloadBlob(new Blob([report.annotatedCsv], { type: "text/csv" }), `${fileBase}-errors.csv`);
5225
- }
5226
- refetchAll();
5227
- };
5228
- inp.click();
5229
- };
5230
- const csvBulk = csvSchema ? { onImportCsv: handleImport, onExportCsv: handleExport } : {};
6036
+ const csvBulk = useShellCsvBulk({
6037
+ csvSchema,
6038
+ recordType,
6039
+ label,
6040
+ items: recordList.items,
6041
+ ctx,
6042
+ refetchAll,
6043
+ onTelemetry
6044
+ });
5231
6045
  const [previewScope, setPreviewScope] = useState(null);
5232
6046
  const [drawerOpen, setDrawerOpen] = useState(false);
5233
6047
  const [sidePreviewOpen, setSidePreviewOpen] = useState(true);
@@ -5256,158 +6070,25 @@ function RecordsAdminShellInner(props) {
5256
6070
  }
5257
6071
  return void 0;
5258
6072
  }, [activeScope, editingScope, selectedProductId, productBrowse.items]);
5259
- const copyCurrent = useCallback(() => {
5260
- if (!enableClipboard || !editingScope) return;
5261
- const sourceValue = onCopyOverride ? onCopyOverride({ value: editorCtx.value, scope: editingScope }) : cloneValue(editorCtx.value);
5262
- clipboard.set({
5263
- value: sourceValue,
5264
- sourceScope: editingScope,
5265
- sourceRecordId: resolved.recordId,
5266
- sourceLabel: editorHeaderLabel
5267
- });
5268
- onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: editingScope.raw });
5269
- setClipboardNotice({
5270
- message: i18n.copyToast.replace("{name}", editorHeaderLabel ?? editingScope.raw),
5271
- variant: "copy"
5272
- });
5273
- }, [
5274
- enableClipboard,
6073
+ const shellClipboard = useShellClipboard({
6074
+ enabled: !!enableClipboard,
6075
+ appId,
6076
+ recordType,
6077
+ i18n,
6078
+ editorCtx,
5275
6079
  editingScope,
5276
- onCopyOverride,
5277
- editorCtx.value,
5278
- clipboard,
5279
6080
  editorHeaderLabel,
6081
+ isCollection,
6082
+ selectedItemId,
6083
+ selectedRecordId,
6084
+ resolved: { source: resolved.source, recordId: resolved.recordId },
5280
6085
  onTelemetry,
5281
- recordType,
5282
- i18n.copyToast
5283
- ]);
5284
- const pasteCurrent = useCallback(async () => {
5285
- if (!enableClipboard || !editingScope || !clipboard.entry) return;
5286
- const compat = checkPasteCompatibility(clipboard.entry.sourceScope, editingScope);
5287
- if (compat.status === "denied") {
5288
- setClipboardNotice({ message: compat.reason ?? "Paste not allowed here", variant: "paste" });
5289
- return;
5290
- }
5291
- if (compat.status === "warn") {
5292
- const ok = await pasteConfirm.confirm({
5293
- title: i18n.pasteWarnTitle,
5294
- body: compat.reason ?? "",
5295
- confirmLabel: i18n.pasteWarnContinue,
5296
- cancelLabel: i18n.pasteConfirmCancel
5297
- });
5298
- if (!ok) return;
5299
- }
5300
- const willReplace = resolved.source === "self";
5301
- if (willReplace) {
5302
- const ok = await pasteConfirm.confirm({
5303
- title: i18n.pasteConfirmTitle,
5304
- body: i18n.pasteConfirmBody.replace(
5305
- "{destination}",
5306
- editorHeaderLabel ?? editingScope.raw
5307
- ),
5308
- confirmLabel: i18n.pasteConfirmReplace,
5309
- cancelLabel: i18n.pasteConfirmCancel
5310
- });
5311
- if (!ok) return;
5312
- }
5313
- const sourceParsed = clipboard.entry.sourceScope;
5314
- const transformed = onPasteOverride ? onPasteOverride(
5315
- { value: clipboard.entry.value, sourceScope: sourceParsed },
5316
- { scope: editingScope, currentValue: editorCtx.value ?? null }
5317
- ) : cloneValue(clipboard.entry.value);
5318
- if (transformed === null) return;
5319
- editorCtx.onChange(transformed);
5320
- onTelemetry?.({
5321
- type: "clipboard.paste",
5322
- recordType,
5323
- sourceRef: clipboard.entry.sourceScope.raw,
5324
- destinationRef: editingScope.raw,
5325
- replaced: willReplace
5326
- });
5327
- setClipboardNotice({
5328
- message: i18n.pasteToast.replace("{name}", clipboard.entry.sourceLabel ?? clipboard.entry.sourceScope.raw),
5329
- variant: "paste"
5330
- });
5331
- }, [
5332
- enableClipboard,
5333
- editingScope,
5334
- clipboard.entry,
5335
- pasteConfirm,
5336
- resolved.source,
6086
+ onCopyOverride,
5337
6087
  onPasteOverride,
5338
- editorCtx,
5339
- onTelemetry,
5340
- recordType,
5341
- editorHeaderLabel,
5342
- i18n.pasteWarnTitle,
5343
- i18n.pasteWarnContinue,
5344
- i18n.pasteConfirmCancel,
5345
- i18n.pasteConfirmTitle,
5346
- i18n.pasteConfirmBody,
5347
- i18n.pasteConfirmReplace,
5348
- i18n.pasteToast
5349
- ]);
5350
- const editorPasteCompat = useMemo(() => {
5351
- if (!enableClipboard || !clipboard.entry || !editingScope) return null;
5352
- const sameRecord = clipboard.entry.sourceRecordId && resolved.recordId && clipboard.entry.sourceRecordId === resolved.recordId;
5353
- const sameAnchor = !clipboard.entry.sourceRecordId && clipboard.entry.sourceScope.raw === editingScope.raw;
5354
- if (sameRecord || sameAnchor) return { status: "denied" };
5355
- return checkPasteCompatibility(clipboard.entry.sourceScope, editingScope);
5356
- }, [enableClipboard, clipboard.entry, editingScope]);
5357
- const editorClipboard = enableClipboard && editingScope ? {
5358
- onCopy: copyCurrent,
5359
- onPaste: () => {
5360
- void pasteCurrent();
5361
- },
5362
- canCopy: true,
5363
- canPaste: !!clipboard.entry && editorPasteCompat?.status !== "denied",
5364
- pasteSourceLabel: clipboard.entry?.sourceLabel,
5365
- pasteWillReplace: resolved.source === "self" && !!clipboard.entry
5366
- } : void 0;
5367
- const [pendingPasteTarget, setPendingPasteTarget] = useState(null);
5368
- useEffect(() => {
5369
- if (!pendingPasteTarget) return;
5370
- if (!editingScope) return;
5371
- const matched = pendingPasteTarget.kind === "record" ? (isCollection ? selectedItemId : selectedRecordId) === pendingPasteTarget.recordId : editingScope.raw === pendingPasteTarget.ref;
5372
- if (!matched) return;
5373
- const t = window.setTimeout(() => {
5374
- setPendingPasteTarget(null);
5375
- void pasteCurrent();
5376
- }, 0);
5377
- return () => window.clearTimeout(t);
5378
- }, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent]);
5379
- const rowClipboard = enableClipboard ? (record) => {
5380
- const summaryHasData = record.data != null;
5381
- const sourceParsed = record.scope;
5382
- const compat = clipboard.entry ? checkPasteCompatibility(clipboard.entry.sourceScope, sourceParsed) : null;
5383
- const sameTarget = clipboard.entry ? clipboard.entry.sourceRecordId && record.id ? clipboard.entry.sourceRecordId === record.id : clipboard.entry.sourceScope.raw === record.ref : false;
5384
- return {
5385
- onCopy: summaryHasData ? () => {
5386
- const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
5387
- clipboard.set({
5388
- value,
5389
- sourceScope: sourceParsed,
5390
- sourceRecordId: record.id ?? void 0,
5391
- sourceLabel: record.label
5392
- });
5393
- onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: record.ref });
5394
- setClipboardNotice({
5395
- message: i18n.copyToast.replace("{name}", record.label),
5396
- variant: "copy"
5397
- });
5398
- } : void 0,
5399
- onPaste: () => {
5400
- onLeftSelectRef.current?.(record);
5401
- setPendingPasteTarget(
5402
- record.id ? { kind: "record", recordId: record.id } : { kind: "anchor", ref: record.ref }
5403
- );
5404
- },
5405
- canPaste: !!clipboard.entry && !sameTarget && compat?.status !== "denied",
5406
- pasteWillReplace: record.status === "configured",
5407
- clipboardSourceLabel: clipboard.entry?.sourceLabel
5408
- };
5409
- } : void 0;
5410
- const onLeftSelectRef = useRef(null);
6088
+ onLeftSelectRef
6089
+ });
6090
+ const editorClipboard = shellClipboard.editorClipboard;
6091
+ const rowClipboard = shellClipboard.rowClipboard;
5411
6092
  const baseScopeRef = editingScope?.raw ?? "";
5412
6093
  const itemNounLabel = itemNoun || "item";
5413
6094
  const buildItemUrlValue = useCallback((id) => {
@@ -5422,11 +6103,52 @@ function RecordsAdminShellInner(props) {
5422
6103
  scopeRef: baseScopeRef,
5423
6104
  previousSelectedItemId: selectedItemId
5424
6105
  });
6106
+ if (!isDraftId(itemId)) {
6107
+ const row = collectionItems.items.find((it) => it.itemId === itemId || it.id === itemId);
6108
+ if (row && row.data !== void 0) {
6109
+ const cacheKey = resolvedRecordQueryKey({
6110
+ collectionId,
6111
+ appId,
6112
+ recordType,
6113
+ productId: editingScope?.productId,
6114
+ variantId: editingScope?.variantId,
6115
+ batchId: editingScope?.batchId,
6116
+ facetId: editingScope?.facetId,
6117
+ facetValue: editingScope?.facetValue,
6118
+ proofId: editingScope?.proofId,
6119
+ recordId: itemId,
6120
+ withParent: true
6121
+ });
6122
+ if (queryClient.getQueryData(cacheKey) === void 0) {
6123
+ queryClient.setQueryData(cacheKey, {
6124
+ data: row.data,
6125
+ source: "self",
6126
+ sourceRef: row.ref || void 0,
6127
+ recordId: itemId,
6128
+ facetRule: row.facetRule ?? null
6129
+ });
6130
+ }
6131
+ }
6132
+ }
5425
6133
  setSelectedItemId(itemId);
5426
6134
  onTelemetry?.({ type: "item.open", recordType, scopeRef: baseScopeRef, itemId });
5427
6135
  deepLinkState.emit({ recordId: itemId, scope: baseScopeRef || null }, "record.open");
5428
6136
  });
5429
- }, [isCollection, runWithGuard, onTelemetry, recordType, baseScopeRef, deepLinkState, buildItemUrlValue, selectedItemId]);
6137
+ }, [
6138
+ isCollection,
6139
+ runWithGuard,
6140
+ onTelemetry,
6141
+ recordType,
6142
+ baseScopeRef,
6143
+ deepLinkState,
6144
+ buildItemUrlValue,
6145
+ selectedItemId,
6146
+ collectionItems.items,
6147
+ queryClient,
6148
+ collectionId,
6149
+ appId,
6150
+ editingScope
6151
+ ]);
5430
6152
  const onItemCreate = useCallback(() => {
5431
6153
  if (!isCollection) return;
5432
6154
  void runWithGuard(() => {
@@ -5557,7 +6279,19 @@ function RecordsAdminShellInner(props) {
5557
6279
  clipboard: editorClipboard,
5558
6280
  actionLabels,
5559
6281
  actionIcons,
5560
- children: renderEditor(editorCtx)
6282
+ children: /* @__PURE__ */ jsx(
6283
+ EditorMountPool,
6284
+ {
6285
+ renderSlot: (slotId) => /* @__PURE__ */ jsx(
6286
+ EditorPoolBody,
6287
+ {
6288
+ editorId: slotId,
6289
+ renderEditor
6290
+ }
6291
+ ),
6292
+ fallback: renderEditor(editorCtx)
6293
+ }
6294
+ )
5561
6295
  }
5562
6296
  );
5563
6297
  const withNav = (node) => itemNav ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full min-h-0", children: [
@@ -5791,15 +6525,8 @@ function RecordsAdminShellInner(props) {
5791
6525
  "data-density": density,
5792
6526
  children: [
5793
6527
  dirtyConfirm.dialog,
5794
- pasteConfirm.dialog,
5795
- clipboardNotice && /* @__PURE__ */ jsx(
5796
- ClipboardToast,
5797
- {
5798
- message: clipboardNotice.message,
5799
- variant: clipboardNotice.variant,
5800
- onDismiss: () => setClipboardNotice(null)
5801
- }
5802
- ),
6528
+ shellClipboard.confirmDialog,
6529
+ shellClipboard.toast,
5803
6530
  (() => {
5804
6531
  const showFloatHelp = !!intro && dismissed && resolvedReopenAffordance === "footer" && !headerWillRender;
5805
6532
  if (!showFloatHelp) return null;
@@ -5851,7 +6578,7 @@ function RecordsAdminShellInner(props) {
5851
6578
  title: title ?? label ?? recordType ?? "",
5852
6579
  subtitle,
5853
6580
  headerIcon,
5854
- headerActions,
6581
+ headerActions: composedHeaderActions,
5855
6582
  showStats,
5856
6583
  showHeaderIcon,
5857
6584
  recordType,
@@ -5878,25 +6605,25 @@ function RecordsAdminShellInner(props) {
5878
6605
  onShowIntro: undismiss
5879
6606
  }
5880
6607
  ) : null,
5881
- dirtyStrategy === "keep" && (dirtyDrafts.some((d) => d.status !== "saved") || editorCtx.isDirty) && /* @__PURE__ */ jsx(
6608
+ showTrayBanner && /* @__PURE__ */ jsx(
5882
6609
  UnsavedTray,
5883
6610
  {
5884
- drafts: dirtyDrafts,
6611
+ items: dirtyItems,
5885
6612
  isSaving: saveAllOpen,
5886
- onSaveAll: () => setSaveAllOpen(true),
5887
- onDiscardAll: () => {
5888
- if (editorCtx.isDirty) editorCtx.reset();
5889
- draftStore.clearAll();
5890
- },
5891
- onOpenDraft: (d) => {
5892
- if (d.recordId) {
5893
- setSelectedRecordId(d.recordId);
6613
+ onSaveAll: handleSaveAll,
6614
+ onDiscardAll: handleDiscardAll,
6615
+ onOpenItem: (it) => {
6616
+ editorSelection.focus(it.editorId);
6617
+ if (it.recordId) {
6618
+ setSelectedRecordId(it.recordId);
5894
6619
  setDraftKind(null);
5895
6620
  }
5896
6621
  },
5897
6622
  onJumpToError: () => {
5898
- const first = dirtyDrafts.find((d) => d.status === "error");
5899
- if (first?.recordId) {
6623
+ const first = dirtyItems.find((it) => it.status === "error");
6624
+ if (!first) return;
6625
+ editorSelection.focus(first.editorId);
6626
+ if (first.recordId) {
5900
6627
  setSelectedRecordId(first.recordId);
5901
6628
  setDraftKind(null);
5902
6629
  }
@@ -5916,12 +6643,13 @@ function RecordsAdminShellInner(props) {
5916
6643
  SaveAllProgress,
5917
6644
  {
5918
6645
  open: saveAllOpen,
5919
- drafts: dirtyDrafts,
5920
- store: draftStore,
6646
+ items: dirtyItems,
6647
+ saveOne: dirtyOverview.saveOne,
5921
6648
  onClose: () => setSaveAllOpen(false),
5922
- onJumpToError: (d) => {
5923
- if (d.recordId) {
5924
- setSelectedRecordId(d.recordId);
6649
+ onJumpToError: (it) => {
6650
+ editorSelection.focus(it.editorId);
6651
+ if (it.recordId) {
6652
+ setSelectedRecordId(it.recordId);
5925
6653
  setDraftKind(null);
5926
6654
  }
5927
6655
  },
@@ -6431,6 +7159,414 @@ var ResolvedPreview = ({ children }) => /* @__PURE__ */ jsxs("div", { className:
6431
7159
  /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide mb-2", style: { color: "hsl(var(--ra-muted-text))" }, children: "Public preview" }),
6432
7160
  children
6433
7161
  ] });
7162
+ var createStore = () => {
7163
+ const map = /* @__PURE__ */ new Map();
7164
+ const listeners = /* @__PURE__ */ new Set();
7165
+ let nextOrder = 0;
7166
+ let cachedList = [];
7167
+ const recompute = () => {
7168
+ cachedList = Array.from(map.values()).sort((a, b) => a.order - b.order);
7169
+ };
7170
+ const emit = () => {
7171
+ recompute();
7172
+ listeners.forEach((l) => l());
7173
+ };
7174
+ return {
7175
+ list: () => cachedList,
7176
+ get: (key) => map.get(key),
7177
+ has: (key) => map.has(key),
7178
+ upsertDraft(input) {
7179
+ const existing = map.get(input.key);
7180
+ const order = existing?.order ?? input.order ?? nextOrder++;
7181
+ const status = input.status ?? existing?.status ?? "dirty";
7182
+ map.set(input.key, {
7183
+ ...input,
7184
+ order,
7185
+ status
7186
+ });
7187
+ emit();
7188
+ },
7189
+ setStatus(key, status, error) {
7190
+ const existing = map.get(key);
7191
+ if (!existing) return;
7192
+ map.set(key, { ...existing, status, error });
7193
+ emit();
7194
+ },
7195
+ clearDraft(key) {
7196
+ if (map.delete(key)) emit();
7197
+ },
7198
+ clearAll() {
7199
+ if (map.size === 0) return;
7200
+ map.clear();
7201
+ emit();
7202
+ },
7203
+ subscribe(listener) {
7204
+ listeners.add(listener);
7205
+ return () => {
7206
+ listeners.delete(listener);
7207
+ };
7208
+ }
7209
+ };
7210
+ };
7211
+ var DirtyDraftContext = createContext(null);
7212
+ var DirtyDraftProvider = ({ children }) => {
7213
+ const storeRef = useRef(null);
7214
+ if (!storeRef.current) storeRef.current = createStore();
7215
+ return /* @__PURE__ */ jsx(DirtyDraftContext.Provider, { value: storeRef.current, children });
7216
+ };
7217
+ var useDirtyDraftStore = () => {
7218
+ const ctx = useContext(DirtyDraftContext);
7219
+ const fallbackRef = useRef(null);
7220
+ if (!ctx && !fallbackRef.current) fallbackRef.current = createStore();
7221
+ return ctx ?? fallbackRef.current;
7222
+ };
7223
+ var useDirtyDrafts = () => {
7224
+ const store = useDirtyDraftStore();
7225
+ return useSyncExternalStore(
7226
+ useCallback((cb) => store.subscribe(cb), [store]),
7227
+ useCallback(() => store.list(), [store]),
7228
+ useCallback(() => store.list(), [store])
7229
+ );
7230
+ };
7231
+ var useDirtyDraft = (key) => {
7232
+ const store = useDirtyDraftStore();
7233
+ const getSnapshot = useCallback(
7234
+ () => key ? store.get(key) : void 0,
7235
+ [store, key]
7236
+ );
7237
+ return useSyncExternalStore(
7238
+ useCallback((cb) => store.subscribe(cb), [store]),
7239
+ getSnapshot,
7240
+ getSnapshot
7241
+ );
7242
+ };
7243
+ var buildDraftKey = (opts) => {
7244
+ if (opts.recordId) return opts.recordId;
7245
+ const kind = opts.draftKind ?? "rec";
7246
+ return `draft:${kind}:${opts.scopeRaw}`;
7247
+ };
7248
+ var useDirtyDraftActions = () => {
7249
+ const store = useDirtyDraftStore();
7250
+ const drafts = useDirtyDrafts();
7251
+ const count = useMemo(() => drafts.filter((d) => d.status !== "saved").length, [drafts]);
7252
+ const errorCount = useMemo(() => drafts.filter((d) => d.status === "error").length, [drafts]);
7253
+ return { drafts, count, errorCount, store };
7254
+ };
7255
+
7256
+ // src/components/RecordsAdmin/hooks/useRecordEditor.ts
7257
+ var isEqual2 = (a, b) => {
7258
+ try {
7259
+ return JSON.stringify(a) === JSON.stringify(b);
7260
+ } catch {
7261
+ return false;
7262
+ }
7263
+ };
7264
+ var cloneSeed = (resolved, defaultData) => {
7265
+ if (resolved.source === "self") return resolved.data;
7266
+ if (resolved.source === "inherited" && resolved.data != null) {
7267
+ try {
7268
+ return structuredClone(resolved.data);
7269
+ } catch {
7270
+ return JSON.parse(JSON.stringify(resolved.data));
7271
+ }
7272
+ }
7273
+ return defaultData?.() ?? {};
7274
+ };
7275
+ var getScopedResolvedRecordId = (scope, resolved) => {
7276
+ if (!resolved.recordId) return void 0;
7277
+ if (scope.itemId && scope.itemId === resolved.recordId) return resolved.recordId;
7278
+ const scopeRaw = scope.raw || "";
7279
+ const sourceRef = resolved.sourceRef || "";
7280
+ if (scopeRaw === sourceRef) return resolved.recordId;
7281
+ return void 0;
7282
+ };
7283
+ function useRecordEditor(args) {
7284
+ const {
7285
+ ctx,
7286
+ scope,
7287
+ resolved,
7288
+ defaultData,
7289
+ onSaved,
7290
+ onDeleted,
7291
+ onSaveError,
7292
+ reseed = "always",
7293
+ initialFacetRule = null,
7294
+ createMode = false,
7295
+ deriveDraftLabel
7296
+ } = args;
7297
+ const queryClient = useQueryClient();
7298
+ const draftStore = useDirtyDraftStore();
7299
+ const scopedResolvedRecordId = getScopedResolvedRecordId(scope, resolved);
7300
+ const draftKey = buildDraftKey({
7301
+ // Scope identity must win during record switches. `resolved.recordId`
7302
+ // can briefly lag behind the newly-selected target while the resolver is
7303
+ // catching up, and keying drafts off that stale id lets one edited item
7304
+ // borrow another item's draft. Once the resolver confirms the matching
7305
+ // record for this scope, we transparently migrate onto the concrete UUID.
7306
+ recordId: scopedResolvedRecordId,
7307
+ scopeRaw: scope.raw,
7308
+ draftKind: scope.kind
7309
+ });
7310
+ const initialDraft = draftStore.get(draftKey);
7311
+ const cleanSeed = cloneSeed(resolved, defaultData);
7312
+ const seed = initialDraft && reseed === "preserve-dirty" ? initialDraft.value : cleanSeed;
7313
+ const seedFacetRule = initialDraft && reseed === "preserve-dirty" ? initialDraft.facetRule : initialFacetRule;
7314
+ const [value, setValue] = useState(seed);
7315
+ const [savedSnapshot, setSavedSnapshot] = useState(
7316
+ initialDraft ? initialDraft.baseline : cleanSeed
7317
+ );
7318
+ const [facetRule, setFacetRule] = useState(seedFacetRule);
7319
+ const [savedFacetRule, setSavedFacetRule] = useState(
7320
+ initialDraft ? initialDraft.baselineFacetRule : initialFacetRule
7321
+ );
7322
+ const [userInteracted, setUserInteracted] = useState(!!initialDraft);
7323
+ const [optimisticSource, setOptimisticSource] = useState(null);
7324
+ const [isSaving, setIsSaving] = useState(false);
7325
+ const [saveError, setSaveError] = useState(null);
7326
+ const valueRef = useRef(seed);
7327
+ useEffect(() => {
7328
+ valueRef.current = value;
7329
+ }, [value]);
7330
+ const prevTargetRef = useRef({
7331
+ scopeRaw: scope.raw,
7332
+ recordId: resolved.recordId
7333
+ });
7334
+ useEffect(() => {
7335
+ const prev = prevTargetRef.current;
7336
+ const targetChanged = prev.scopeRaw !== scope.raw || (prev.recordId ?? null) !== (resolved.recordId ?? null);
7337
+ prevTargetRef.current = { scopeRaw: scope.raw, recordId: resolved.recordId };
7338
+ const fresh = cloneSeed(resolved, defaultData);
7339
+ if (targetChanged) {
7340
+ const incomingKey = buildDraftKey({
7341
+ recordId: getScopedResolvedRecordId(scope, resolved),
7342
+ scopeRaw: scope.raw,
7343
+ draftKind: scope.kind
7344
+ });
7345
+ const incomingDraft = draftStore.get(incomingKey);
7346
+ if (incomingDraft && reseed === "preserve-dirty") {
7347
+ setValue(incomingDraft.value);
7348
+ setSavedSnapshot(incomingDraft.baseline);
7349
+ setFacetRule(incomingDraft.facetRule);
7350
+ setSavedFacetRule(incomingDraft.baselineFacetRule);
7351
+ setUserInteracted(true);
7352
+ } else {
7353
+ setValue(fresh);
7354
+ setSavedSnapshot(fresh);
7355
+ setFacetRule(initialFacetRule);
7356
+ setSavedFacetRule(initialFacetRule);
7357
+ setUserInteracted(false);
7358
+ }
7359
+ } else {
7360
+ if (reseed === "preserve-dirty") {
7361
+ const hasUnsaved = !isEqual2(valueRef.current, savedSnapshot);
7362
+ if (!hasUnsaved) setValue(fresh);
7363
+ } else {
7364
+ setValue(fresh);
7365
+ }
7366
+ setSavedSnapshot(fresh);
7367
+ setFacetRule(initialFacetRule);
7368
+ setSavedFacetRule(initialFacetRule);
7369
+ }
7370
+ setOptimisticSource(null);
7371
+ }, [scope.raw, resolved.recordId, resolved.source, resolved.sourceRef]);
7372
+ const valueDiff = !isEqual2(value, savedSnapshot) || !isEqual2(facetRule, savedFacetRule);
7373
+ const isDirty = userInteracted && valueDiff;
7374
+ const handleChange = useCallback((next) => {
7375
+ setUserInteracted(true);
7376
+ setValue(next);
7377
+ }, []);
7378
+ const handleFacetRuleChange = useCallback((next) => {
7379
+ setUserInteracted(true);
7380
+ setFacetRule(next);
7381
+ }, []);
7382
+ const save = useCallback(async () => {
7383
+ const anchors = parsedRefToScope(scope);
7384
+ const hasAnchors = !!(anchors.productId || anchors.variantId || anchors.batchId || anchors.proofId);
7385
+ const hasRule = !!(facetRule && facetRule.all && facetRule.all.length > 0);
7386
+ const isRuleScope2 = scope.kind === "rule";
7387
+ if (isRuleScope2 && !hasAnchors && !hasRule && !resolved.recordId && !createMode) {
7388
+ console.warn("[useRecordEditor] save() bailed \u2014 rule scope with no clauses, no recordId, not in createMode");
7389
+ return;
7390
+ }
7391
+ const previousSnapshot = savedSnapshot;
7392
+ const previousRuleSnapshot = savedFacetRule;
7393
+ resolved.source;
7394
+ const cacheKey = resolvedRecordQueryKey({
7395
+ collectionId: ctx.collectionId,
7396
+ appId: ctx.appId,
7397
+ recordType: ctx.recordType,
7398
+ productId: scope.productId,
7399
+ variantId: scope.variantId,
7400
+ batchId: scope.batchId,
7401
+ facetId: scope.facetId,
7402
+ facetValue: scope.facetValue,
7403
+ proofId: scope.proofId,
7404
+ recordId: resolved.recordId,
7405
+ withParent: true
7406
+ });
7407
+ const previousCache = queryClient.getQueryData(cacheKey);
7408
+ setSaveError(null);
7409
+ setIsSaving(true);
7410
+ setSavedSnapshot(value);
7411
+ setSavedFacetRule(facetRule);
7412
+ setOptimisticSource("self");
7413
+ queryClient.setQueryData(cacheKey, {
7414
+ data: value,
7415
+ source: "self",
7416
+ sourceRef: scope.raw,
7417
+ recordId: resolved.recordId,
7418
+ parentValue: previousCache?.parentValue ?? resolved.parentValue
7419
+ });
7420
+ try {
7421
+ if (resolved.recordId && resolved.source === "self") {
7422
+ await updateRecord(ctx, resolved.recordId, {
7423
+ data: value,
7424
+ facetRule
7425
+ });
7426
+ } else if (createMode) {
7427
+ await createRecord(ctx, {
7428
+ ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
7429
+ scope: anchors,
7430
+ data: value,
7431
+ facetRule
7432
+ });
7433
+ } else {
7434
+ await upsertRecord(ctx, {
7435
+ // External ref only when the underlying scope already carries
7436
+ // one (e.g. rule:{id} for an existing rule record). Empty for
7437
+ // anchor-only writes — server addresses by anchors.
7438
+ ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
7439
+ scope: anchors,
7440
+ data: value,
7441
+ facetRule
7442
+ });
7443
+ }
7444
+ draftStore.clearDraft(draftKey);
7445
+ onSaved?.();
7446
+ } catch (err) {
7447
+ setSavedSnapshot(previousSnapshot);
7448
+ setSavedFacetRule(previousRuleSnapshot);
7449
+ setOptimisticSource(null);
7450
+ if (previousCache !== void 0) {
7451
+ queryClient.setQueryData(cacheKey, previousCache);
7452
+ } else {
7453
+ queryClient.invalidateQueries({ queryKey: cacheKey });
7454
+ }
7455
+ setSaveError(err);
7456
+ onSaveError?.(err);
7457
+ throw err;
7458
+ } finally {
7459
+ setIsSaving(false);
7460
+ }
7461
+ }, [scope.raw, value, savedSnapshot, facetRule, savedFacetRule, resolved.source, resolved.parentValue, resolved.recordId, createMode, draftKey]);
7462
+ const reset = useCallback(() => {
7463
+ setValue(savedSnapshot);
7464
+ setFacetRule(savedFacetRule);
7465
+ setUserInteracted(false);
7466
+ draftStore.clearDraft(draftKey);
7467
+ }, [savedSnapshot, savedFacetRule, draftKey]);
7468
+ const remove = useCallback(async () => {
7469
+ if (resolved.source !== "self") return;
7470
+ if (!resolved.recordId) return;
7471
+ await removeRecord(ctx, resolved.recordId);
7472
+ draftStore.clearDraft(draftKey);
7473
+ onDeleted?.();
7474
+ }, [resolved.source, resolved.recordId, draftKey]);
7475
+ const prevDraftKeyRef = useRef(draftKey);
7476
+ const prevScopeRawRef = useRef(scope.raw);
7477
+ useEffect(() => {
7478
+ const prevKey = prevDraftKeyRef.current;
7479
+ const prevScopeRaw = prevScopeRawRef.current;
7480
+ if (prevKey && prevKey !== draftKey && prevScopeRaw === scope.raw) {
7481
+ const stale = draftStore.get(prevKey);
7482
+ if (stale) draftStore.clearDraft(prevKey);
7483
+ }
7484
+ prevDraftKeyRef.current = draftKey;
7485
+ prevScopeRawRef.current = scope.raw;
7486
+ }, [draftKey, scope.raw]);
7487
+ useEffect(() => {
7488
+ if (!isDirty) {
7489
+ return;
7490
+ }
7491
+ const anchors = parsedRefToScope(scope);
7492
+ const saveKind = resolved.recordId && resolved.source === "self" ? "update" : createMode ? "create" : "upsert";
7493
+ const deriveLabel = () => {
7494
+ if (deriveDraftLabel) {
7495
+ try {
7496
+ const custom = deriveDraftLabel(value, scope);
7497
+ if (typeof custom === "string" && custom.trim()) return custom.trim();
7498
+ } catch {
7499
+ }
7500
+ }
7501
+ const KEYS = ["title", "name", "label", "heading", "question", "slug"];
7502
+ const pickString = (obj) => {
7503
+ if (!obj || typeof obj !== "object") return void 0;
7504
+ for (const key of KEYS) {
7505
+ const raw = obj[key];
7506
+ if (typeof raw === "string" && raw.trim()) return raw.trim();
7507
+ }
7508
+ return void 0;
7509
+ };
7510
+ const v = value;
7511
+ const top = pickString(v);
7512
+ if (top) return top;
7513
+ if (v && typeof v === "object") {
7514
+ for (const wrapper of ["display", "content", "meta", "data"]) {
7515
+ const nested = pickString(v[wrapper]);
7516
+ if (nested) return nested;
7517
+ }
7518
+ }
7519
+ if (scope.raw?.startsWith("item:")) return "Untitled item";
7520
+ if (scope.kind === "rule") return "Rule";
7521
+ if (scope.kind && scope.kind !== "collection") {
7522
+ return scope.kind.charAt(0).toUpperCase() + scope.kind.slice(1);
7523
+ }
7524
+ return "Default";
7525
+ };
7526
+ draftStore.upsertDraft({
7527
+ key: draftKey,
7528
+ label: deriveLabel(),
7529
+ context: scope.kind,
7530
+ scopeRaw: scope.raw,
7531
+ recordId: resolved.recordId,
7532
+ value,
7533
+ facetRule,
7534
+ baseline: savedSnapshot,
7535
+ baselineFacetRule: savedFacetRule,
7536
+ createMode,
7537
+ scopeAnchors: anchors,
7538
+ ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
7539
+ saveKind,
7540
+ save: async () => {
7541
+ await save();
7542
+ }
7543
+ });
7544
+ }, [isDirty, value, facetRule, savedSnapshot, savedFacetRule, scope.raw, resolved.recordId, resolved.source, createMode, save]);
7545
+ const effectiveSource = optimisticSource ?? resolved.source;
7546
+ const isRuleScope = scope.kind === "rule";
7547
+ const ruleValid = !isRuleScope || isFacetRuleValid(facetRule);
7548
+ const canSave = ruleValid;
7549
+ const cannotSaveReason = !ruleValid ? "Pick at least one value for every facet in the rule before saving." : void 0;
7550
+ return {
7551
+ value,
7552
+ onChange: handleChange,
7553
+ source: effectiveSource,
7554
+ recordId: resolved.recordId,
7555
+ parentValue: resolved.parentValue,
7556
+ scope,
7557
+ isDirty,
7558
+ save,
7559
+ reset,
7560
+ remove,
7561
+ canRemove: effectiveSource === "self",
7562
+ canSave,
7563
+ cannotSaveReason,
7564
+ isSaving,
7565
+ saveError,
7566
+ facetRule,
7567
+ onFacetRuleChange: handleFacetRuleChange
7568
+ };
7569
+ }
6434
7570
  var resolveAllQueryKey = (args) => [
6435
7571
  "records-admin",
6436
7572
  "resolve-all",
@@ -6671,6 +7807,6 @@ function useMergedRecord(args) {
6671
7807
  };
6672
7808
  }
6673
7809
 
6674
- 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, downloadBlob, exportCsv, importCsv, 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 };
7810
+ 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 };
6675
7811
  //# sourceMappingURL=index.js.map
6676
7812
  //# sourceMappingURL=index.js.map