@proveanything/smartlinks-utils-ui 0.9.3 → 0.9.4

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.
@@ -2417,11 +2417,24 @@ declare const normaliseRule: (rule: FacetRule | null | undefined) => NormalisedR
2417
2417
  * `null` so the caller can route them to a separate "no rule" bucket.
2418
2418
  */
2419
2419
  declare const ruleHash: (rule: FacetRule | null | undefined) => string | null;
2420
+ /**
2421
+ * Optional label lookup — turn raw facet/value KEYS (e.g. `cooking_guide`,
2422
+ * `over-cook`) into the human-readable LABELS the host already shows
2423
+ * elsewhere (e.g. "Cooking Guide", "Over-cook"). Pass via `summariseRule`
2424
+ * so rail headers, right-pane summaries and chips never expose raw keys
2425
+ * when a friendly name is available.
2426
+ */
2427
+ interface RuleLabelLookup {
2428
+ facetLabel?: (facetKey: string) => string | undefined;
2429
+ valueLabel?: (facetKey: string, valueKey: string) => string | undefined;
2430
+ }
2420
2431
  /**
2421
2432
  * Human-readable single-line summary of a rule, used for group headers.
2422
- * Compact ("country=DE,FR · tier=premium") so it fits a rail-width header.
2433
+ * Compact ("Country: Germany, France · Tier: Premium") so it fits a
2434
+ * rail-width header. Falls back to raw keys when no lookup is supplied
2435
+ * or the lookup returns undefined for a given key.
2423
2436
  */
2424
- declare const summariseRule: (rule: FacetRule | null | undefined) => string;
2437
+ declare const summariseRule: (rule: FacetRule | null | undefined, lookup?: RuleLabelLookup) => string;
2425
2438
  /** Comparator: are two rules semantically identical? */
2426
2439
  declare const rulesEqual: (a: FacetRule | null | undefined, b: FacetRule | null | undefined) => boolean;
2427
2440
 
@@ -3,9 +3,9 @@ import '../../chunk-5UQQYXCX.js';
3
3
  import { FacetRuleEditor } from '../../chunk-JMCV6FOW.js';
4
4
  import { useFacets } from '../../chunk-4LHF5JB7.js';
5
5
  import { cn } from '../../chunk-L7FQ52F5.js';
6
- import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KFKVGUUP.js';
7
- export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KFKVGUUP.js';
8
- import { createContext, useState, useEffect, useCallback, useMemo, useRef, useContext, useSyncExternalStore, createElement, useId } from 'react';
6
+ import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KA4MKRHL.js';
7
+ export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KA4MKRHL.js';
8
+ import { createContext, useState, useEffect, useCallback, useMemo, useRef, useContext, useSyncExternalStore, useLayoutEffect, createElement, useId } from 'react';
9
9
  import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Rows3, ChevronRight, Eraser, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, AlertCircle, Undo2, Save, Loader2, XCircle, ArrowRight, BookOpen, Globe2, Target, Check, Settings2 } from 'lucide-react';
10
10
  import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
11
11
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -268,10 +268,12 @@ var ruleHash = (rule) => {
268
268
  if (!norm) return null;
269
269
  return fnv1a(JSON.stringify(norm));
270
270
  };
271
- var summariseRule = (rule) => {
271
+ var summariseRule = (rule, lookup) => {
272
272
  const norm = normaliseRule(rule);
273
273
  if (!norm) return "No rule";
274
- return norm.all.map((c) => `${c.facetKey}=${c.anyOf.join(",")}`).join(" \xB7 ");
274
+ const fLabel = (k) => lookup?.facetLabel?.(k) ?? k;
275
+ const vLabel = (k, v) => lookup?.valueLabel?.(k, v) ?? v;
276
+ return norm.all.map((c) => `${fLabel(c.facetKey)}: ${c.anyOf.map((v) => vLabel(c.facetKey, v)).join(", ")}`).join(" \xB7 ");
275
277
  };
276
278
  var rulesEqual = (a, b) => ruleHash(a) === ruleHash(b);
277
279
 
@@ -281,18 +283,6 @@ var resolveRecord = async (args) => {
281
283
  const editingScope = parsedRefToScope(args.target);
282
284
  const result = await matchRecords(args.ctx, target, { strategy: "all" }).catch(() => null);
283
285
  const entries = result?.data ?? [];
284
- console.info("[RecordsAdmin/resolveRecord]", {
285
- editingScope,
286
- target,
287
- matchCount: entries.length,
288
- winnerAnchors: entries[0] && {
289
- productId: entries[0].productId,
290
- variantId: entries[0].variantId,
291
- batchId: entries[0].batchId,
292
- proofId: entries[0].proofId
293
- },
294
- winnerRef: entries[0]?.ref
295
- });
296
286
  if (entries.length === 0) {
297
287
  return { data: null, source: "empty" };
298
288
  }
@@ -306,16 +296,6 @@ var resolveRecord = async (args) => {
306
296
  },
307
297
  editingScope
308
298
  );
309
- console.info("[RecordsAdmin/resolveRecord] classification", {
310
- winnerIsSelf,
311
- winnerAnchors: {
312
- productId: winner.productId,
313
- variantId: winner.variantId,
314
- batchId: winner.batchId,
315
- proofId: winner.proofId
316
- },
317
- editingScope
318
- });
319
299
  if (winnerIsSelf) {
320
300
  const parent = entries[1];
321
301
  return {
@@ -1834,11 +1814,6 @@ function useShellNavigation(args) {
1834
1814
  const onItemOpen = useCallback((itemId) => {
1835
1815
  if (!isCollection) return;
1836
1816
  void runWithGuard(() => {
1837
- console.info("[RecordsAdminShell] item open requested", {
1838
- itemId,
1839
- scopeRef: baseScopeRef,
1840
- previousSelectedItemId: selectedItemId
1841
- });
1842
1817
  if (!isDraftId(itemId)) {
1843
1818
  const row = collectionItems.items.find((it) => it.itemId === itemId || it.id === itemId);
1844
1819
  if (row && row.data !== void 0) {
@@ -1939,7 +1914,7 @@ function useShellNavigation(args) {
1939
1914
  }
1940
1915
  }
1941
1916
  try {
1942
- const { removeRecord: removeRecord2 } = await import('../../records-AYYQSP7E.js');
1917
+ const { removeRecord: removeRecord2 } = await import('../../records-4NN757SG.js');
1943
1918
  await removeRecord2(ctx, itemId);
1944
1919
  onTelemetry?.({ type: "item.delete", recordType, scopeRef: baseScopeRef, itemId });
1945
1920
  if (selectedItemId === itemId) setSelectedItemId(null);
@@ -2124,17 +2099,20 @@ var useShellDeepLink = (args) => {
2124
2099
  } = args;
2125
2100
  const lastAppliedDLRef = useRef("");
2126
2101
  const [pendingDeepLinkRecordId, setPendingDeepLinkRecordId] = useState(null);
2102
+ const preserveInitialRecordIdRef = useRef(!!deepLinkState.urlState.recordId);
2127
2103
  const warnedMissingRef = useRef(/* @__PURE__ */ new Set());
2128
2104
  useEffect(() => {
2129
2105
  if (!deepLinkState.enabled) return;
2130
2106
  if (selectedItemId) return;
2131
- console.debug("[RecordsAdminShell] rail-scope emit", {
2132
- scope: editingScope?.raw ?? null,
2133
- itemView
2134
- });
2135
- deepLinkState.emit({ scope: editingScope?.raw ?? null, recordId: null }, "scope");
2136
- lastAppliedDLRef.current = `${""}|${editingScope?.raw ?? ""}|${itemView}`;
2137
- }, [deepLinkState.enabled, editingScope?.raw, selectedItemId, itemView]);
2107
+ const echoView = deepLinkState.urlState.view ?? "";
2108
+ const currentDeepLinkedRecordId = pendingDeepLinkRecordId ?? deepLinkState.urlState.recordId ?? null;
2109
+ const preservingPendingRecordId = preserveInitialRecordIdRef.current && !!currentDeepLinkedRecordId;
2110
+ deepLinkState.emit(
2111
+ preservingPendingRecordId ? { scope: editingScope?.raw ?? null } : { scope: editingScope?.raw ?? null, recordId: null },
2112
+ "scope"
2113
+ );
2114
+ lastAppliedDLRef.current = `${preservingPendingRecordId ? currentDeepLinkedRecordId ?? "" : ""}|${editingScope?.raw ?? ""}|${echoView}`;
2115
+ }, [deepLinkState.enabled, deepLinkState.urlState.recordId, editingScope?.raw, selectedItemId, itemView, pendingDeepLinkRecordId]);
2138
2116
  useEffect(() => {
2139
2117
  if (!deepLinkState.enabled) return;
2140
2118
  const { recordId, scope, view } = deepLinkState.urlState;
@@ -2163,6 +2141,7 @@ var useShellDeepLink = (args) => {
2163
2141
  if (selectedRecordId !== null) setSelectedRecordId(null);
2164
2142
  }
2165
2143
  setPendingDeepLinkRecordId(recordId ?? null);
2144
+ if (recordId) preserveInitialRecordIdRef.current = true;
2166
2145
  if (!recordId && selectedItemId !== null) {
2167
2146
  console.info("[RecordsAdminShell] preserving selected item during restore without recordId", {
2168
2147
  selectedItemId,
@@ -2179,6 +2158,7 @@ var useShellDeepLink = (args) => {
2179
2158
  if (hit2) {
2180
2159
  skipNextItemResetRef.current = true;
2181
2160
  setSelectedItemId(pending);
2161
+ preserveInitialRecordIdRef.current = false;
2182
2162
  setPendingDeepLinkRecordId(null);
2183
2163
  return;
2184
2164
  }
@@ -2190,6 +2170,7 @@ var useShellDeepLink = (args) => {
2190
2170
  scope: editingScope?.raw ?? null
2191
2171
  });
2192
2172
  }
2173
+ preserveInitialRecordIdRef.current = false;
2193
2174
  setPendingDeepLinkRecordId(null);
2194
2175
  }
2195
2176
  return;
@@ -2197,6 +2178,7 @@ var useShellDeepLink = (args) => {
2197
2178
  const hit = recordListItems.find((it) => it.id === pending);
2198
2179
  if (hit) {
2199
2180
  if (selectedRecordId !== pending) setSelectedRecordId(pending);
2181
+ preserveInitialRecordIdRef.current = false;
2200
2182
  setPendingDeepLinkRecordId(null);
2201
2183
  return;
2202
2184
  }
@@ -2208,6 +2190,7 @@ var useShellDeepLink = (args) => {
2208
2190
  scope: editingScope?.raw ?? null
2209
2191
  });
2210
2192
  }
2193
+ preserveInitialRecordIdRef.current = false;
2211
2194
  setPendingDeepLinkRecordId(null);
2212
2195
  }
2213
2196
  }, [
@@ -3255,10 +3238,16 @@ var RowContextMenu = ({
3255
3238
  }) => {
3256
3239
  const [open, setOpen] = useState(false);
3257
3240
  const wrapperRef = useRef(null);
3241
+ const triggerRef = useRef(null);
3242
+ const menuRef = useRef(null);
3243
+ const [pos, setPos] = useState(null);
3258
3244
  useEffect(() => {
3259
3245
  if (!open) return;
3260
3246
  const onDoc = (e) => {
3261
- if (!wrapperRef.current?.contains(e.target)) setOpen(false);
3247
+ const t = e.target;
3248
+ if (wrapperRef.current?.contains(t)) return;
3249
+ if (menuRef.current?.contains(t)) return;
3250
+ setOpen(false);
3262
3251
  };
3263
3252
  const onKey = (e) => {
3264
3253
  if (e.key === "Escape") setOpen(false);
@@ -3270,12 +3259,36 @@ var RowContextMenu = ({
3270
3259
  document.removeEventListener("keydown", onKey);
3271
3260
  };
3272
3261
  }, [open]);
3262
+ useLayoutEffect(() => {
3263
+ if (!open) {
3264
+ setPos(null);
3265
+ return;
3266
+ }
3267
+ const update = () => {
3268
+ const el = triggerRef.current;
3269
+ if (!el) return;
3270
+ const r = el.getBoundingClientRect();
3271
+ const menuWidth = menuRef.current?.offsetWidth ?? 176;
3272
+ const margin = 8;
3273
+ const left = Math.max(margin, Math.min(window.innerWidth - menuWidth - margin, r.right - menuWidth));
3274
+ const top = r.bottom + 4;
3275
+ setPos({ top, left });
3276
+ };
3277
+ update();
3278
+ window.addEventListener("resize", update);
3279
+ window.addEventListener("scroll", update, true);
3280
+ return () => {
3281
+ window.removeEventListener("resize", update);
3282
+ window.removeEventListener("scroll", update, true);
3283
+ };
3284
+ }, [open]);
3273
3285
  if (!onCopy && !onPaste) return null;
3274
3286
  const pasteLabel = !canPaste ? i18n.clipboardEmpty : pasteSourceLabel ? i18n.pasteFrom.replace("{name}", pasteSourceLabel) : pasteWillReplace ? i18n.pasteReplace : i18n.paste;
3275
3287
  return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: "ra-row-menu-wrap relative", children: [
3276
3288
  /* @__PURE__ */ jsx(
3277
3289
  "button",
3278
3290
  {
3291
+ ref: triggerRef,
3279
3292
  type: "button",
3280
3293
  className: "ra-row-menu-trigger",
3281
3294
  "aria-label": "Row actions",
@@ -3288,75 +3301,106 @@ var RowContextMenu = ({
3288
3301
  children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "w-3.5 h-3.5", "aria-hidden": "true" })
3289
3302
  }
3290
3303
  ),
3291
- open && /* @__PURE__ */ jsxs("div", { role: "menu", className: "ra-row-menu", onClick: (e) => e.stopPropagation(), children: [
3292
- onCopy && /* @__PURE__ */ jsxs(
3293
- "button",
3304
+ open && typeof document !== "undefined" && createPortal(
3305
+ /* @__PURE__ */ jsxs(
3306
+ "div",
3294
3307
  {
3295
- type: "button",
3296
- role: "menuitem",
3297
- className: "ra-row-menu-item",
3298
- onClick: (e) => {
3299
- e.stopPropagation();
3300
- setOpen(false);
3301
- onCopy();
3302
- },
3308
+ ref: menuRef,
3309
+ role: "menu",
3310
+ className: "ra-row-menu ra-row-menu-portal",
3311
+ style: pos ? { top: pos.top, left: pos.left } : { visibility: "hidden" },
3312
+ onClick: (e) => e.stopPropagation(),
3313
+ onMouseDown: (e) => e.stopPropagation(),
3303
3314
  children: [
3304
- /* @__PURE__ */ jsx(Copy, { className: "w-3 h-3", "aria-hidden": "true" }),
3305
- /* @__PURE__ */ jsx("span", { children: i18n.copy })
3315
+ onCopy && /* @__PURE__ */ jsxs(
3316
+ "button",
3317
+ {
3318
+ type: "button",
3319
+ role: "menuitem",
3320
+ className: "ra-row-menu-item",
3321
+ onClick: (e) => {
3322
+ e.stopPropagation();
3323
+ setOpen(false);
3324
+ onCopy();
3325
+ },
3326
+ children: [
3327
+ /* @__PURE__ */ jsx(Copy, { className: "w-3 h-3", "aria-hidden": "true" }),
3328
+ /* @__PURE__ */ jsx("span", { children: i18n.copy })
3329
+ ]
3330
+ }
3331
+ ),
3332
+ onPaste && /* @__PURE__ */ jsxs(
3333
+ "button",
3334
+ {
3335
+ type: "button",
3336
+ role: "menuitem",
3337
+ className: "ra-row-menu-item",
3338
+ disabled: !canPaste,
3339
+ onClick: (e) => {
3340
+ e.stopPropagation();
3341
+ setOpen(false);
3342
+ if (canPaste) onPaste();
3343
+ },
3344
+ children: [
3345
+ /* @__PURE__ */ jsx(ClipboardPaste, { className: "w-3 h-3", "aria-hidden": "true" }),
3346
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: pasteLabel })
3347
+ ]
3348
+ }
3349
+ )
3306
3350
  ]
3307
3351
  }
3308
3352
  ),
3309
- onPaste && /* @__PURE__ */ jsxs(
3310
- "button",
3311
- {
3312
- type: "button",
3313
- role: "menuitem",
3314
- className: "ra-row-menu-item",
3315
- disabled: !canPaste,
3316
- onClick: (e) => {
3317
- e.stopPropagation();
3318
- setOpen(false);
3319
- if (canPaste) onPaste();
3320
- },
3321
- children: [
3322
- /* @__PURE__ */ jsx(ClipboardPaste, { className: "w-3 h-3", "aria-hidden": "true" }),
3323
- /* @__PURE__ */ jsx("span", { className: "truncate", children: pasteLabel })
3324
- ]
3325
- }
3326
- )
3327
- ] })
3353
+ document.body
3354
+ )
3328
3355
  ] });
3329
3356
  };
3330
3357
 
3331
3358
  // src/components/RecordsAdmin/browser/formatFacetRule.ts
3332
- function formatFacetRule(rule) {
3359
+ function formatFacetRule(rule, lookup) {
3333
3360
  if (!rule || !rule.all || rule.all.length === 0) return null;
3361
+ const fLabel = (k) => lookup?.facetLabel?.(k) ?? k;
3362
+ const vLabel = (k, v) => lookup?.valueLabel?.(k, v) ?? v;
3334
3363
  const parts = rule.all.map((c) => {
3335
3364
  const vals = (c.anyOf ?? []).filter(Boolean);
3336
- if (vals.length === 0) return `${c.facetKey} (no values)`;
3337
- if (vals.length === 1) return `${c.facetKey} = ${vals[0]}`;
3338
- return `${c.facetKey} \u2208 {${vals.join(", ")}}`;
3365
+ const fk = fLabel(c.facetKey);
3366
+ if (vals.length === 0) return `${fk} (no values)`;
3367
+ if (vals.length === 1) return `${fk} = ${vLabel(c.facetKey, vals[0])}`;
3368
+ return `${fk} \u2208 {${vals.map((v) => vLabel(c.facetKey, v)).join(", ")}}`;
3339
3369
  });
3340
3370
  return parts.join(" AND ");
3341
3371
  }
3342
- function summarizeFacetRule(rule) {
3372
+ function summarizeFacetRule(rule, lookup) {
3343
3373
  if (!rule || !rule.all || rule.all.length === 0) return [];
3374
+ const fLabel = (k) => lookup?.facetLabel?.(k) ?? k;
3375
+ const vLabel = (k, v) => lookup?.valueLabel?.(k, v) ?? v;
3344
3376
  return rule.all.map((c) => {
3345
- const values = (c.anyOf ?? []).filter(Boolean);
3377
+ const rawValues = (c.anyOf ?? []).filter(Boolean);
3378
+ const values = rawValues.map((v) => vLabel(c.facetKey, v));
3379
+ const fk = fLabel(c.facetKey);
3346
3380
  let label;
3347
- if (values.length === 0) label = `${c.facetKey}: \u2014`;
3348
- else if (values.length === 1) label = `${c.facetKey}: ${values[0]}`;
3349
- else if (values.length <= 3) label = `${c.facetKey}: ${values.join(", ")}`;
3350
- else label = `${c.facetKey}: ${values.slice(0, 2).join(", ")} +${values.length - 2}`;
3381
+ if (values.length === 0) label = `${fk}: \u2014`;
3382
+ else if (values.length === 1) label = `${fk}: ${values[0]}`;
3383
+ else if (values.length <= 3) label = `${fk}: ${values.join(", ")}`;
3384
+ else label = `${fk}: ${values.slice(0, 2).join(", ")} +${values.length - 2}`;
3351
3385
  return { facetKey: c.facetKey, values, label };
3352
3386
  });
3353
3387
  }
3388
+ var RuleLabelLookupContext = createContext(null);
3389
+ var useRuleLabelLookup = () => useContext(RuleLabelLookupContext) ?? void 0;
3390
+ var RuleLabelLookupProvider = ({
3391
+ value,
3392
+ children
3393
+ }) => {
3394
+ const v = useMemo(() => value, [value]);
3395
+ return /* @__PURE__ */ jsx(RuleLabelLookupContext.Provider, { value: v, children });
3396
+ };
3354
3397
  var DefaultRecordRow = ({ record, ctx, compact = false }) => {
3355
3398
  const { selected, onSelect, isDirty, hasError, onCopy, onPaste, canPaste, pasteWillReplace, clipboardSourceLabel } = ctx;
3399
+ const ruleLabelLookup = useRuleLabelLookup();
3356
3400
  const ScopeIcon = record.scope.kind && record.scope.kind !== "collection" ? DEFAULT_ICONS.scope[record.scope.kind] : DEFAULT_ICONS.scope.product;
3357
3401
  const tone = resolveTone(void 0, record.status);
3358
- const ruleSummary = formatFacetRule(record.facetRule);
3359
- const ruleClauses = summarizeFacetRule(record.facetRule);
3402
+ const ruleSummary = formatFacetRule(record.facetRule, ruleLabelLookup);
3403
+ const ruleClauses = summarizeFacetRule(record.facetRule, ruleLabelLookup);
3360
3404
  const isRuleRecord = ruleClauses.length > 0;
3361
3405
  const i18n = ctx.i18n ?? DEFAULT_I18N;
3362
3406
  const subtitle = record.subtitle ?? (isRuleRecord ? null : ruleSummary) ?? (tone === "missing" ? i18n.subtitleEmpty : tone === "own" ? i18n.subtitleConfigured : i18n.subtitleInherited);
@@ -3397,9 +3441,9 @@ var DefaultRecordRow = ({ record, ctx, compact = false }) => {
3397
3441
  !compact && !isRuleRecord && /* @__PURE__ */ jsx("div", { className: "ra-row-sub", children: subtitle })
3398
3442
  ] }),
3399
3443
  compact && /* @__PURE__ */ jsx(StatusIcon, { status: record.status, size: "0.85rem" }),
3400
- !compact && record.scope.kind && record.scope.kind !== "collection" && /* @__PURE__ */ jsx("span", { className: "ra-row-scope", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ScopeIcon, { className: "w-3 h-3" }) }),
3444
+ !compact && record.scope.kind && record.scope.kind !== "collection" && record.scope.kind !== "rule" && /* @__PURE__ */ jsx("span", { className: "ra-row-scope", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ScopeIcon, { className: "w-3 h-3" }) }),
3401
3445
  record.badges?.slice(0, 1).map((b, i) => /* @__PURE__ */ jsx("span", { className: "ra-chip", "data-tone": "muted", children: b.label }, `${b.label}-${i}`)),
3402
- record.facetRule && !compact && /* @__PURE__ */ jsx(
3446
+ record.facetRule && !compact && !isRuleRecord && /* @__PURE__ */ jsx(
3403
3447
  "span",
3404
3448
  {
3405
3449
  className: "ra-row-rule-pip",
@@ -3907,15 +3951,19 @@ function useCollectionItems(args) {
3907
3951
 
3908
3952
  // src/components/RecordsAdmin/data/deepLinkAdapter.ts
3909
3953
  var findQueryHost = (loc) => {
3910
- if (loc.search && loc.search.length > 1) return "search";
3954
+ if (loc.hash && loc.hash.startsWith("#/")) return "hash";
3911
3955
  if (loc.hash && loc.hash.includes("?")) return "hash";
3956
+ if (loc.search && loc.search.length > 1) return "search";
3912
3957
  return "search";
3913
3958
  };
3914
- var getQueryString = (loc) => {
3915
- const host = findQueryHost(loc);
3916
- if (host === "search") return loc.search.startsWith("?") ? loc.search.slice(1) : loc.search;
3959
+ var getSearchParams = (loc) => new URLSearchParams(loc.search.startsWith("?") ? loc.search.slice(1) : loc.search);
3960
+ var getHashParams = (loc) => {
3917
3961
  const idx = loc.hash.indexOf("?");
3918
- return idx >= 0 ? loc.hash.slice(idx + 1) : "";
3962
+ return new URLSearchParams(idx >= 0 ? loc.hash.slice(idx + 1) : "");
3963
+ };
3964
+ var getReadParams = (loc) => {
3965
+ if (loc.hash && loc.hash.includes("?")) return getHashParams(loc);
3966
+ return getSearchParams(loc);
3919
3967
  };
3920
3968
  var buildUrl = (loc, nextQuery) => {
3921
3969
  const host = findQueryHost(loc);
@@ -3929,7 +3977,7 @@ var buildUrl = (loc, nextQuery) => {
3929
3977
  var createDefaultDeepLinkAdapter = (paramNames) => ({
3930
3978
  read() {
3931
3979
  if (typeof window === "undefined") return {};
3932
- const params = new URLSearchParams(getQueryString(window.location));
3980
+ const params = getReadParams(window.location);
3933
3981
  return {
3934
3982
  recordId: params.get(paramNames.recordId),
3935
3983
  scope: params.get(paramNames.scope),
@@ -3938,7 +3986,7 @@ var createDefaultDeepLinkAdapter = (paramNames) => ({
3938
3986
  },
3939
3987
  write(partial, mode) {
3940
3988
  if (typeof window === "undefined") return;
3941
- const params = new URLSearchParams(getQueryString(window.location));
3989
+ const params = findQueryHost(window.location) === "hash" ? getHashParams(window.location) : getSearchParams(window.location);
3942
3990
  const apply = (key, value) => {
3943
3991
  if (value == null || value === "") params.delete(key);
3944
3992
  else params.set(key, value);
@@ -3972,14 +4020,22 @@ var CONTEXT_KEYS = [
3972
4020
  "lang",
3973
4021
  "theme"
3974
4022
  ];
3975
- var findQueryString = (loc) => {
3976
- if (loc.search && loc.search.length > 1) {
3977
- return loc.search.startsWith("?") ? loc.search.slice(1) : loc.search;
3978
- }
3979
- if (loc.hash && loc.hash.includes("?")) {
3980
- return loc.hash.slice(loc.hash.indexOf("?") + 1);
3981
- }
3982
- return "";
4023
+ var getSearchParams2 = (loc) => new URLSearchParams(loc.search.startsWith("?") ? loc.search.slice(1) : loc.search);
4024
+ var getHashParams2 = (loc) => {
4025
+ const idx = loc.hash.indexOf("?");
4026
+ return new URLSearchParams(idx >= 0 ? loc.hash.slice(idx + 1) : "");
4027
+ };
4028
+ var getReadParams2 = (loc) => {
4029
+ if (loc.hash && loc.hash.includes("?")) return getHashParams2(loc);
4030
+ return getSearchParams2(loc);
4031
+ };
4032
+ var getMergedParams = (loc) => {
4033
+ const merged = getSearchParams2(loc);
4034
+ const hashParams = getHashParams2(loc);
4035
+ hashParams.forEach((value, key) => {
4036
+ merged.set(key, value);
4037
+ });
4038
+ return merged;
3983
4039
  };
3984
4040
  var findPath = (loc) => {
3985
4041
  if (loc.hash && loc.hash.startsWith("#")) {
@@ -4002,7 +4058,7 @@ var createPostMessageDeepLinkAdapter = (paramNames) => {
4002
4058
  const lastShellState = {};
4003
4059
  const post = (path) => {
4004
4060
  if (typeof window === "undefined") return;
4005
- const params = new URLSearchParams(findQueryString(window.location));
4061
+ const params = getMergedParams(window.location);
4006
4062
  const context = {};
4007
4063
  const state = {};
4008
4064
  params.forEach((value, key) => {
@@ -4026,9 +4082,6 @@ var createPostMessageDeepLinkAdapter = (paramNames) => {
4026
4082
  state,
4027
4083
  appId: context.appId
4028
4084
  };
4029
- console.debug("[smartlinks-ui] postMessage \u2192 parent", message, {
4030
- sameWindow: window.parent === window
4031
- });
4032
4085
  window.parent.postMessage(message, "*");
4033
4086
  } catch {
4034
4087
  }
@@ -4036,7 +4089,7 @@ var createPostMessageDeepLinkAdapter = (paramNames) => {
4036
4089
  return {
4037
4090
  read() {
4038
4091
  if (typeof window === "undefined") return {};
4039
- const params = new URLSearchParams(findQueryString(window.location));
4092
+ const params = getReadParams2(window.location);
4040
4093
  return {
4041
4094
  recordId: params.get(paramNames.recordId),
4042
4095
  scope: params.get(paramNames.scope),
@@ -4083,11 +4136,6 @@ function useDeepLinkState(options) {
4083
4136
  if (options?.adapter) return options.adapter;
4084
4137
  if (!defaultAdapterRef.current) {
4085
4138
  const inIframe = isInSmartLinksIframe();
4086
- console.debug("[smartlinks-ui] deep-link adapter selected", {
4087
- adapter: inIframe ? "postMessage" : "default",
4088
- inIframe,
4089
- href: typeof window !== "undefined" ? window.location.href : "(ssr)"
4090
- });
4091
4139
  defaultAdapterRef.current = inIframe ? createPostMessageDeepLinkAdapter(paramNames) : createDefaultDeepLinkAdapter(paramNames);
4092
4140
  }
4093
4141
  return defaultAdapterRef.current;
@@ -4102,14 +4150,9 @@ function useDeepLinkState(options) {
4102
4150
  }, [adapter]);
4103
4151
  const emit = useCallback((partial, kind) => {
4104
4152
  if (!adapter) {
4105
- console.debug("[smartlinks-ui] deep-link emit skipped \u2014 no adapter (deepLink not enabled?)", {
4106
- partial,
4107
- kind
4108
- });
4109
4153
  return;
4110
4154
  }
4111
4155
  const mode = classify2(history, kind);
4112
- console.debug("[smartlinks-ui] deep-link emit", { partial, kind, mode });
4113
4156
  adapter.write(partial, mode);
4114
4157
  setUrlState((prev) => ({ ...prev, ...partial }));
4115
4158
  }, [adapter, history]);
@@ -7209,18 +7252,6 @@ function RecordsAdminShellInner(props) {
7209
7252
  const i18n = { ...DEFAULT_I18N, ...i18nOverride ?? {} };
7210
7253
  const icons = useMemo(() => mergeIcons(iconsOverride), [iconsOverride]);
7211
7254
  const deepLinkState = useDeepLinkState(deepLink);
7212
- const deepLinkLoggedRef = useRef(false);
7213
- if (!deepLinkLoggedRef.current) {
7214
- deepLinkLoggedRef.current = true;
7215
- console.debug("[RecordsAdminShell] deep-link config", {
7216
- enabled: deepLinkState.enabled,
7217
- hostPassedOption: !!deepLink,
7218
- // Resolved adapter mode: 'host' = host-supplied, 'default' = built-in
7219
- // (window.location standalone, postMessage inside the SmartLinks iframe).
7220
- adapterMode: deepLink?.adapter ? "host" : "default",
7221
- paramNames: deepLinkState.paramNames
7222
- });
7223
- }
7224
7255
  const multiOpenWarnedRef = useRef(false);
7225
7256
  if (editorTabs === "multi" && !multiOpenWarnedRef.current) {
7226
7257
  multiOpenWarnedRef.current = true;
@@ -7365,6 +7396,11 @@ function RecordsAdminShellInner(props) {
7365
7396
  variantChildren,
7366
7397
  batchChildren
7367
7398
  } = browser;
7399
+ const ruleScopedList = useRecordList({
7400
+ ctx,
7401
+ scopeKind: "rule",
7402
+ enabled: true
7403
+ });
7368
7404
  const pinnedProduct = useSingleProduct({
7369
7405
  SL,
7370
7406
  collectionId,
@@ -7898,6 +7934,26 @@ function RecordsAdminShellInner(props) {
7898
7934
  const isGlobalTab = activeScope === "collection";
7899
7935
  const isAllTab = activeScope === "all";
7900
7936
  const isRecordsTab = isRuleTab || isGlobalTab || isAllTab;
7937
+ const ruleLabelLookup = useMemo(() => {
7938
+ const map = /* @__PURE__ */ new Map();
7939
+ for (const it of facetBrowse.items) {
7940
+ const k = it.scope.facetId;
7941
+ const v = it.scope.facetValue;
7942
+ if (!k || !v) continue;
7943
+ const facetLabel = it.subtitle ?? k;
7944
+ const valueLabel = it.label ?? v;
7945
+ let info = map.get(k);
7946
+ if (!info) {
7947
+ info = { label: facetLabel, values: /* @__PURE__ */ new Map() };
7948
+ map.set(k, info);
7949
+ }
7950
+ if (!info.values.has(v)) info.values.set(v, valueLabel);
7951
+ }
7952
+ return {
7953
+ facetLabel: (k) => map.get(k)?.label,
7954
+ valueLabel: (k, v) => map.get(k)?.values.get(v)
7955
+ };
7956
+ }, [facetBrowse.items]);
7901
7957
  const effectiveGroupBy = useMemo(() => {
7902
7958
  if (groupBy) return groupBy;
7903
7959
  if (isAllTab) return void 0;
@@ -7906,13 +7962,25 @@ function RecordsAdminShellInner(props) {
7906
7962
  return (record) => {
7907
7963
  const hash = ruleHash(record.facetRule);
7908
7964
  if (!hash) return null;
7909
- return { key: `rule:${hash}`, label: summariseRule(record.facetRule) };
7965
+ return { key: `rule:${hash}`, label: summariseRule(record.facetRule, ruleLabelLookup) };
7910
7966
  };
7911
- }, [groupBy, isRuleTab, isAllTab, isCollection]);
7967
+ }, [groupBy, isRuleTab, isAllTab, isCollection, ruleLabelLookup]);
7912
7968
  const [bulkRuleEditTarget, setBulkRuleEditTarget] = useState(null);
7913
7969
  const ruleCatalogue = useMemo(() => {
7914
7970
  const buckets = /* @__PURE__ */ new Map();
7971
+ const seenIds = /* @__PURE__ */ new Set();
7972
+ const merged = [];
7973
+ for (const item of ruleScopedList.allItems) {
7974
+ if (item.id && seenIds.has(item.id)) continue;
7975
+ if (item.id) seenIds.add(item.id);
7976
+ merged.push(item);
7977
+ }
7915
7978
  for (const item of recordList.items) {
7979
+ if (item.id && seenIds.has(item.id)) continue;
7980
+ if (item.id) seenIds.add(item.id);
7981
+ merged.push(item);
7982
+ }
7983
+ for (const item of merged) {
7916
7984
  const hash = ruleHash(item.facetRule);
7917
7985
  if (!hash || !item.facetRule) continue;
7918
7986
  const existing = buckets.get(hash);
@@ -7922,7 +7990,7 @@ function RecordsAdminShellInner(props) {
7922
7990
  buckets.set(hash, {
7923
7991
  hash,
7924
7992
  rule: item.facetRule,
7925
- summary: summariseRule(item.facetRule),
7993
+ summary: summariseRule(item.facetRule, ruleLabelLookup),
7926
7994
  count: 1
7927
7995
  });
7928
7996
  }
@@ -7931,7 +7999,7 @@ function RecordsAdminShellInner(props) {
7931
7999
  if (b.count !== a.count) return b.count - a.count;
7932
8000
  return a.summary.localeCompare(b.summary);
7933
8001
  });
7934
- }, [recordList.items]);
8002
+ }, [recordList.items, ruleScopedList.allItems, ruleLabelLookup]);
7935
8003
  const [targetingExpandNonce, setTargetingExpandNonce] = useState(0);
7936
8004
  const renderRuleGroupActions = useCallback(
7937
8005
  (group) => {
@@ -8022,8 +8090,13 @@ function RecordsAdminShellInner(props) {
8022
8090
  }
8023
8091
  return Array.from(buckets.values()).map(({ rep, count }) => ({
8024
8092
  ...rep,
8025
- label: summariseRule(rep.facetRule),
8026
- subtitle: `${count} ${itemNoun}${count === 1 ? "" : "s"}`
8093
+ // Title carries the count + identity; the rule chips below render
8094
+ // the friendly facet/value labels (via the lookup) so we don't
8095
+ // repeat the same information in two places. Without this, a row
8096
+ // had three echoes of the same rule (raw-key title, friendly chip,
8097
+ // and the right-pane "Rule · …" header).
8098
+ label: `${count} ${itemNoun}${count === 1 ? "" : "s"}`,
8099
+ subtitle: void 0
8027
8100
  }));
8028
8101
  }, [isRuleTab, isCollection, filteredRuleItems, itemNoun]);
8029
8102
  const activeRuleSummary = useMemo(() => {
@@ -8031,8 +8104,8 @@ function RecordsAdminShellInner(props) {
8031
8104
  if (!selectedRecordId || isDraftId3(selectedRecordId)) return null;
8032
8105
  const hit = recordList.items.find((it) => it.id === selectedRecordId);
8033
8106
  if (!hit?.facetRule) return null;
8034
- return summariseRule(hit.facetRule);
8035
- }, [isRuleTab, isCollection, selectedRecordId, recordList.items]);
8107
+ return summariseRule(hit.facetRule, ruleLabelLookup);
8108
+ }, [isRuleTab, isCollection, selectedRecordId, recordList.items, ruleLabelLookup]);
8036
8109
  const applyFacetBrowseFilter = useCallback(
8037
8110
  (items2) => {
8038
8111
  if (!facetBrowseFilter) return items2;
@@ -8107,7 +8180,7 @@ function RecordsAdminShellInner(props) {
8107
8180
  });
8108
8181
  };
8109
8182
  onLeftSelectRef.current = onLeftSelect;
8110
- return /* @__PURE__ */ jsxs(
8183
+ return /* @__PURE__ */ jsx(RuleLabelLookupProvider, { value: ruleLabelLookup, children: /* @__PURE__ */ jsxs(
8111
8184
  "div",
8112
8185
  {
8113
8186
  className: `ra-shell flex flex-col h-full ${className ?? ""}`,
@@ -8588,7 +8661,7 @@ function RecordsAdminShellInner(props) {
8588
8661
  )
8589
8662
  ]
8590
8663
  }
8591
- );
8664
+ ) });
8592
8665
  }
8593
8666
  var RecordBrowser = ({
8594
8667
  scopes,