@proveanything/smartlinks-utils-ui 0.10.6 → 0.10.8

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.
@@ -40,4 +40,4 @@ interface IconPickerProps {
40
40
 
41
41
  declare const IconPicker: React$1.FC<IconPickerProps>;
42
42
 
43
- export { IconPicker as I, type IconPickerProps as a, type IconSelection as b, type IconFamily as c, type IconStyle as d };
43
+ export { type IconFamily as I, type IconStyle as a, IconPicker as b, type IconPickerProps as c, type IconSelection as d };
@@ -0,0 +1,92 @@
1
+ import { useId } from 'react';
2
+ import { BookOpen, HelpCircle, AlertTriangle, CheckCircle2, Lightbulb, Info, X } from 'lucide-react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ // src/components/AdminPageHeader/AdminPageHeader.tsx
6
+ var TONE_ICON = {
7
+ info: Lightbulb,
8
+ success: CheckCircle2,
9
+ warning: AlertTriangle
10
+ };
11
+ function AdminPageHeader({
12
+ title,
13
+ subtitle,
14
+ icon,
15
+ helpUrl,
16
+ helpLabel,
17
+ actions,
18
+ aside,
19
+ intro,
20
+ className
21
+ }) {
22
+ const titleId = useId();
23
+ const resolvedHelpLabel = helpLabel ?? "Help & documentation";
24
+ const resolvedReopenLabel = intro?.reopenLabel ?? "How it works";
25
+ const showReopen = !!intro && intro.dismissed && !!intro.onReopen;
26
+ const showIntro = !!intro && !intro.dismissed;
27
+ return /* @__PURE__ */ jsxs("header", { className: `sl-aph${className ? ` ${className}` : ""}`, "aria-labelledby": titleId, children: [
28
+ /* @__PURE__ */ jsxs("div", { className: "sl-aph__row", children: [
29
+ /* @__PURE__ */ jsx("div", { className: "sl-aph__main", children: /* @__PURE__ */ jsxs("div", { className: "sl-aph__text", children: [
30
+ /* @__PURE__ */ jsxs("h1", { className: "sl-aph__title", id: titleId, children: [
31
+ icon ? /* @__PURE__ */ jsx("span", { className: "sl-aph__icon", "aria-hidden": "true", children: icon }) : null,
32
+ /* @__PURE__ */ jsx("span", { children: title })
33
+ ] }),
34
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "sl-aph__subtitle", children: subtitle }) : null
35
+ ] }) }),
36
+ actions || aside || helpUrl || showReopen ? /* @__PURE__ */ jsxs("div", { className: "sl-aph__aside", children: [
37
+ actions,
38
+ aside,
39
+ helpUrl ? /* @__PURE__ */ jsx(
40
+ "a",
41
+ {
42
+ href: helpUrl,
43
+ target: "_blank",
44
+ rel: "noopener noreferrer",
45
+ className: "sl-aph__icon-btn",
46
+ "aria-label": resolvedHelpLabel,
47
+ title: resolvedHelpLabel,
48
+ children: /* @__PURE__ */ jsx(BookOpen, { "aria-hidden": "true" })
49
+ }
50
+ ) : null,
51
+ showReopen ? /* @__PURE__ */ jsx(
52
+ "button",
53
+ {
54
+ type: "button",
55
+ onClick: intro.onReopen,
56
+ className: "sl-aph__icon-btn",
57
+ "aria-label": resolvedReopenLabel,
58
+ title: resolvedReopenLabel,
59
+ children: /* @__PURE__ */ jsx(HelpCircle, { "aria-hidden": "true" })
60
+ }
61
+ ) : null
62
+ ] }) : null
63
+ ] }),
64
+ showIntro ? /* @__PURE__ */ jsx(AdminPageHeaderIntroCard, { intro }) : null
65
+ ] });
66
+ }
67
+ function AdminPageHeaderIntroCard({ intro }) {
68
+ const tone = intro.tone ?? "info";
69
+ const Icon = TONE_ICON[tone] ?? Info;
70
+ return /* @__PURE__ */ jsxs("div", { className: "sl-aph__intro", "data-tone": tone, role: "note", children: [
71
+ /* @__PURE__ */ jsx("div", { className: "sl-aph__intro-icon", children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true", style: { width: "0.95rem", height: "0.95rem" } }) }),
72
+ /* @__PURE__ */ jsxs("div", { className: "sl-aph__intro-body", children: [
73
+ /* @__PURE__ */ jsx("h4", { className: "sl-aph__intro-title", children: intro.title }),
74
+ /* @__PURE__ */ jsx("span", { className: "sl-aph__intro-text", children: intro.body }),
75
+ intro.action ? /* @__PURE__ */ jsx("span", { className: "sl-aph__intro-action", children: intro.action }) : null
76
+ ] }),
77
+ /* @__PURE__ */ jsx(
78
+ "button",
79
+ {
80
+ type: "button",
81
+ onClick: intro.onDismiss,
82
+ "aria-label": "Dismiss",
83
+ className: "sl-aph__intro-dismiss",
84
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", style: { width: "0.875rem", height: "0.875rem" } })
85
+ }
86
+ )
87
+ ] });
88
+ }
89
+
90
+ export { AdminPageHeader };
91
+ //# sourceMappingURL=chunk-2MW54ZVG.js.map
92
+ //# sourceMappingURL=chunk-2MW54ZVG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/AdminPageHeader/AdminPageHeader.tsx"],"names":[],"mappings":"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","file":"chunk-2MW54ZVG.js","sourcesContent":["// =============================================================================\r\n// AdminPageHeader\r\n//\r\n// A standardised page header for any SmartLinks admin surface. Designed to be\r\n// used standalone (lightweight apps that don't need the full RecordsAdminShell)\r\n// or composed inside other shells. Supports:\r\n//\r\n// • Inline icon + title + full-width subtitle\r\n// • Right-aligned slot for app-specific actions (e.g. \"+ New\" button)\r\n// • External help link (icon-button that opens docs in a new tab)\r\n// • Optional dismissible intro card with `?` reopen affordance\r\n// • Optional `aside` slot for extra header content (stats strips, etc.)\r\n//\r\n// Styling is self-contained under `.sl-aph` and reuses the shared `--ra-*`\r\n// design tokens with sensible fallbacks, so it themes correctly whether the\r\n// host has loaded the records-admin token sheet or not.\r\n// =============================================================================\r\nimport { type ReactNode, useId } from 'react';\r\nimport { BookOpen, HelpCircle, X, Lightbulb, CheckCircle2, AlertTriangle, Info } from 'lucide-react';\r\nimport './admin-page-header.css';\r\n\r\nexport type AdminPageHeaderIntroTone = 'info' | 'success' | 'warning';\r\n\r\nexport interface AdminPageHeaderIntro {\r\n /** Short headline shown in bold at the start of the intro card. */\r\n title: string;\r\n /** Body content — plain text or rich nodes. */\r\n body: ReactNode;\r\n /** Visual tone — controls icon and surface tint. Default `'info'`. */\r\n tone?: AdminPageHeaderIntroTone;\r\n /** Optional action node rendered inline after the body (e.g. a Learn-more link). */\r\n action?: ReactNode;\r\n /** Whether the intro is currently dismissed. Controlled. */\r\n dismissed: boolean;\r\n /** Called when the user clicks the dismiss `X`. */\r\n onDismiss: () => void;\r\n /**\r\n * Called when the user clicks the reopen `?` button (only rendered when\r\n * dismissed is true). Omit to hide the reopen affordance entirely.\r\n */\r\n onReopen?: () => void;\r\n /** Tooltip / aria-label for the reopen button. Default \"How it works\". */\r\n reopenLabel?: string;\r\n}\r\n\r\nexport interface AdminPageHeaderProps {\r\n /** Page title — required. */\r\n title: string;\r\n /** Single-line muted subtitle under the title. */\r\n subtitle?: string;\r\n /** Icon rendered inline before the title. Pass a Lucide element or any node. */\r\n icon?: ReactNode;\r\n /**\r\n * External help / documentation URL. When set, renders a small icon-button\r\n * that opens the URL in a new tab. Use this to point at app-specific docs.\r\n */\r\n helpUrl?: string;\r\n /** Tooltip / aria-label for the help link. Default \"Help & documentation\". */\r\n helpLabel?: string;\r\n /**\r\n * Right-aligned action slot — typically a primary \"+ New\" button and/or\r\n * secondary controls. Rendered before the help / intro-reopen icon-buttons.\r\n */\r\n actions?: ReactNode;\r\n /**\r\n * Extra header content rendered between `actions` and the trailing\r\n * help / reopen icon-buttons. Used by RecordsAdminShell for its stats strip;\r\n * standalone apps rarely need it.\r\n */\r\n aside?: ReactNode;\r\n /**\r\n * Optional dismissible intro card rendered below the header row.\r\n * See {@link AdminPageHeaderIntro}.\r\n */\r\n intro?: AdminPageHeaderIntro;\r\n /** Extra class on the root container for layout overrides. */\r\n className?: string;\r\n}\r\n\r\nconst TONE_ICON = {\r\n info: Lightbulb,\r\n success: CheckCircle2,\r\n warning: AlertTriangle,\r\n} as const;\r\n\r\nexport function AdminPageHeader({\r\n title, subtitle, icon, helpUrl, helpLabel,\r\n actions, aside, intro, className,\r\n}: AdminPageHeaderProps) {\r\n const titleId = useId();\r\n const resolvedHelpLabel = helpLabel ?? 'Help & documentation';\r\n const resolvedReopenLabel = intro?.reopenLabel ?? 'How it works';\r\n const showReopen = !!intro && intro.dismissed && !!intro.onReopen;\r\n const showIntro = !!intro && !intro.dismissed;\r\n\r\n return (\r\n <header className={`sl-aph${className ? ` ${className}` : ''}`} aria-labelledby={titleId}>\r\n <div className=\"sl-aph__row\">\r\n <div className=\"sl-aph__main\">\r\n <div className=\"sl-aph__text\">\r\n <h1 className=\"sl-aph__title\" id={titleId}>\r\n {icon ? (\r\n <span className=\"sl-aph__icon\" aria-hidden=\"true\">{icon}</span>\r\n ) : null}\r\n <span>{title}</span>\r\n </h1>\r\n {subtitle ? <p className=\"sl-aph__subtitle\">{subtitle}</p> : null}\r\n </div>\r\n </div>\r\n {(actions || aside || helpUrl || showReopen) ? (\r\n <div className=\"sl-aph__aside\">\r\n {actions}\r\n {aside}\r\n {helpUrl ? (\r\n <a\r\n href={helpUrl}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"sl-aph__icon-btn\"\r\n aria-label={resolvedHelpLabel}\r\n title={resolvedHelpLabel}\r\n >\r\n <BookOpen aria-hidden=\"true\" />\r\n </a>\r\n ) : null}\r\n {showReopen ? (\r\n <button\r\n type=\"button\"\r\n onClick={intro!.onReopen}\r\n className=\"sl-aph__icon-btn\"\r\n aria-label={resolvedReopenLabel}\r\n title={resolvedReopenLabel}\r\n >\r\n <HelpCircle aria-hidden=\"true\" />\r\n </button>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n </div>\r\n {showIntro ? <AdminPageHeaderIntroCard intro={intro!} /> : null}\r\n </header>\r\n );\r\n}\r\n\r\nfunction AdminPageHeaderIntroCard({ intro }: { intro: AdminPageHeaderIntro }) {\r\n const tone = intro.tone ?? 'info';\r\n const Icon = TONE_ICON[tone] ?? Info;\r\n return (\r\n <div className=\"sl-aph__intro\" data-tone={tone} role=\"note\">\r\n <div className=\"sl-aph__intro-icon\">\r\n <Icon aria-hidden=\"true\" style={{ width: '0.95rem', height: '0.95rem' }} />\r\n </div>\r\n <div className=\"sl-aph__intro-body\">\r\n <h4 className=\"sl-aph__intro-title\">{intro.title}</h4>\r\n <span className=\"sl-aph__intro-text\">{intro.body}</span>\r\n {intro.action ? <span className=\"sl-aph__intro-action\">{intro.action}</span> : null}\r\n </div>\r\n <button\r\n type=\"button\"\r\n onClick={intro.onDismiss}\r\n aria-label=\"Dismiss\"\r\n className=\"sl-aph__intro-dismiss\"\r\n >\r\n <X aria-hidden=\"true\" style={{ width: '0.875rem', height: '0.875rem' }} />\r\n </button>\r\n </div>\r\n );\r\n}"]}
@@ -1,4 +1,4 @@
1
- export { a as AssetItem, c as AssetPicker, d as AssetPickerMode, e as AssetPickerProps, f as AssetPickerSelection, g as AssetScope, h as AssetViewMode, u as useAssets } from '../../useAssets-BM1bzzkq.js';
1
+ export { A as AssetItem, a as AssetPicker, b as AssetPickerMode, c as AssetPickerProps, d as AssetPickerSelection, e as AssetScope, f as AssetViewMode, u as useAssets } from '../../useAssets-BAtXv6D5.js';
2
2
  import 'react';
3
3
 
4
4
  interface AppRegistryEntry {
@@ -1,5 +1,5 @@
1
- import { c as IconFamily, d as IconStyle } from '../../IconPicker-BMMQLR5I.js';
2
- export { I as IconPicker, a as IconPickerProps, b as IconSelection } from '../../IconPicker-BMMQLR5I.js';
1
+ import { I as IconFamily, a as IconStyle } from '../../IconPicker-DKpL5Hcc.js';
2
+ export { b as IconPicker, c as IconPickerProps, d as IconSelection } from '../../IconPicker-DKpL5Hcc.js';
3
3
  import 'react';
4
4
 
5
5
  declare function toFaClass(id: string, family: IconFamily, style: IconStyle | null): string;
@@ -962,7 +962,35 @@ interface RailConfig<TData = unknown> {
962
962
  key: string;
963
963
  label: string;
964
964
  icon?: ReactNode;
965
+ /**
966
+ * Whether this bucket should be expanded by default (records-driven
967
+ * accordion mode). Last-write-wins per key. Has no effect in the
968
+ * collection-cardinality lifecycle-rail mode where buckets are flat
969
+ * selectable rows rather than accordion sections.
970
+ */
971
+ defaultOpen?: boolean;
972
+ /**
973
+ * Semantic tone for the bucket — drives a CSS data attribute on the
974
+ * lifecycle bucket row (`data-tone="success" | "warning" | "muted"`).
975
+ * Apps style via `[data-tone="…"]` selectors against existing
976
+ * semantic tokens.
977
+ */
978
+ tone?: 'default' | 'success' | 'warning' | 'muted' | 'danger';
965
979
  } | null;
980
+ /**
981
+ * Initial bucket key to select on first mount of the lifecycle rail
982
+ * (collection cardinality + `'all'` / `'global'` scope + `groupBy`).
983
+ * Ignored when a deep-link state restores a different selection. Pass
984
+ * the same key shape your `groupBy` returns (e.g. `"1-open"`).
985
+ */
986
+ defaultGroupKey?: string;
987
+ /**
988
+ * EXPERIMENTAL — invoked the first time a lifecycle bucket is selected
989
+ * (collection-cardinality lifecycle rail). Hosts can use this to trigger
990
+ * supplemental fetches before the bucket-filtered table renders. May be
991
+ * called more than once per key. API may change before stabilising.
992
+ */
993
+ onGroupExpanded?: (key: string) => void;
966
994
  /** Visual density. Default `comfortable`. */
967
995
  density?: 'comfortable' | 'compact';
968
996
  }
@@ -1696,7 +1724,7 @@ declare const useRecordList: (args: UseRecordListArgs) => {
1696
1724
  };
1697
1725
  isLoading: boolean;
1698
1726
  error: Error | null;
1699
- refetch: () => void;
1727
+ refetch: () => Promise<void>;
1700
1728
  hasNextPage: boolean;
1701
1729
  isFetchingNextPage: boolean;
1702
1730
  fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<{
@@ -1841,7 +1869,7 @@ declare function useCollectionItems<T = unknown>(args: UseCollectionItemsArgs):
1841
1869
  hasMore: boolean;
1842
1870
  nextOffset: number;
1843
1871
  }, unknown>, Error>>;
1844
- refetch: () => void;
1872
+ refetch: () => Promise<void>;
1845
1873
  };
1846
1874
 
1847
1875
  /**
@@ -1,3 +1,4 @@
1
+ import { AdminPageHeader } from '../../chunk-2MW54ZVG.js';
1
2
  import { assertComponentStylesLoaded } from '../../chunk-OLYC54YT.js';
2
3
  import '../../chunk-5UQQYXCX.js';
3
4
  import { FacetRuleEditor } from '../../chunk-JMCV6FOW.js';
@@ -5,8 +6,8 @@ import { useFacets } from '../../chunk-4LHF5JB7.js';
5
6
  import { cn } from '../../chunk-L7FQ52F5.js';
6
7
  import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KA4MKRHL.js';
7
8
  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
- import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Rows3, ChevronRight, Eraser, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, CopyPlus, AlertCircle, Undo2, Save, Loader2, XCircle, ArrowRight, BookOpen, Globe2, Check, Settings2 } from 'lucide-react';
9
+ import { createContext, useState, useEffect, useCallback, useMemo, useRef, useContext, useSyncExternalStore, useLayoutEffect, createElement } from 'react';
10
+ import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Rows3, ChevronRight, Eraser, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, CopyPlus, AlertCircle, Undo2, Save, Loader2, XCircle, ArrowRight, Globe2, Check, Settings2 } from 'lucide-react';
10
11
  import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
11
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
13
  import { createPortal } from 'react-dom';
@@ -690,9 +691,9 @@ var useRecordList = (args) => {
690
691
  partial: items.filter((r) => r.status === "partial").length,
691
692
  empty: items.filter((r) => r.status === "empty").length
692
693
  }), [items]);
693
- const refetch = useCallback(() => {
694
- queryClient.invalidateQueries({ queryKey: [...QK_BASE2, ctx.collectionId, ctx.appId, ctx.recordType] });
695
- }, [queryClient, ctx.collectionId, ctx.appId, ctx.recordType]);
694
+ const refetch = useCallback(() => queryClient.refetchQueries({
695
+ queryKey: [...QK_BASE2, ctx.collectionId, ctx.appId, ctx.recordType]
696
+ }), [queryClient, ctx.collectionId, ctx.appId, ctx.recordType]);
696
697
  const total = query.data?.pages[query.data.pages.length - 1]?.total ?? items.length;
697
698
  return {
698
699
  allItems: items,
@@ -3125,9 +3126,11 @@ var useShellSelection = (args) => {
3125
3126
  }, [contextScope?.batchId]);
3126
3127
  const [selectedItemId, setSelectedItemId] = useState(null);
3127
3128
  const skipNextItemResetRef = useRef(false);
3129
+ const [selectedLifecycleKey, setSelectedLifecycleKey] = useState(null);
3128
3130
  useEffect(() => {
3129
3131
  setSelectedRecordId(null);
3130
3132
  setDraftKind(null);
3133
+ setSelectedLifecycleKey(null);
3131
3134
  }, [activeScope]);
3132
3135
  return {
3133
3136
  activeScope,
@@ -3150,6 +3153,8 @@ var useShellSelection = (args) => {
3150
3153
  setSelectedBatchId,
3151
3154
  selectedItemId,
3152
3155
  setSelectedItemId,
3156
+ selectedLifecycleKey,
3157
+ setSelectedLifecycleKey,
3153
3158
  skipNextItemResetRef
3154
3159
  };
3155
3160
  };
@@ -4086,9 +4091,7 @@ function useCollectionItems(args) {
4086
4091
  return toSummary2(rec, base);
4087
4092
  }).filter((x) => x !== null);
4088
4093
  }, [query.data, toSummary2, scope, ruleSignature, includeAll]);
4089
- const refetch = useCallback(() => {
4090
- queryClient.invalidateQueries({ queryKey });
4091
- }, [queryClient, queryKey]);
4094
+ const refetch = useCallback(() => queryClient.refetchQueries({ queryKey }), [queryClient, queryKey]);
4092
4095
  return {
4093
4096
  items,
4094
4097
  total: query.data?.pages[query.data.pages.length - 1]?.total ?? items.length,
@@ -6193,89 +6196,6 @@ var UtilityRow = ({ label, customLabel, introHidden, onShowIntro }) => {
6193
6196
  }
6194
6197
  );
6195
6198
  };
6196
- var TONE_ICON2 = {
6197
- info: Lightbulb,
6198
- success: CheckCircle2,
6199
- warning: AlertTriangle
6200
- };
6201
- function AdminPageHeader({
6202
- title,
6203
- subtitle,
6204
- icon,
6205
- helpUrl,
6206
- helpLabel,
6207
- actions,
6208
- aside,
6209
- intro,
6210
- className
6211
- }) {
6212
- const titleId = useId();
6213
- const resolvedHelpLabel = helpLabel ?? "Help & documentation";
6214
- const resolvedReopenLabel = intro?.reopenLabel ?? "How it works";
6215
- const showReopen = !!intro && intro.dismissed && !!intro.onReopen;
6216
- const showIntro = !!intro && !intro.dismissed;
6217
- return /* @__PURE__ */ jsxs("header", { className: `sl-aph${className ? ` ${className}` : ""}`, "aria-labelledby": titleId, children: [
6218
- /* @__PURE__ */ jsxs("div", { className: "sl-aph__row", children: [
6219
- /* @__PURE__ */ jsx("div", { className: "sl-aph__main", children: /* @__PURE__ */ jsxs("div", { className: "sl-aph__text", children: [
6220
- /* @__PURE__ */ jsxs("h1", { className: "sl-aph__title", id: titleId, children: [
6221
- icon ? /* @__PURE__ */ jsx("span", { className: "sl-aph__icon", "aria-hidden": "true", children: icon }) : null,
6222
- /* @__PURE__ */ jsx("span", { children: title })
6223
- ] }),
6224
- subtitle ? /* @__PURE__ */ jsx("p", { className: "sl-aph__subtitle", children: subtitle }) : null
6225
- ] }) }),
6226
- actions || aside || helpUrl || showReopen ? /* @__PURE__ */ jsxs("div", { className: "sl-aph__aside", children: [
6227
- actions,
6228
- aside,
6229
- helpUrl ? /* @__PURE__ */ jsx(
6230
- "a",
6231
- {
6232
- href: helpUrl,
6233
- target: "_blank",
6234
- rel: "noopener noreferrer",
6235
- className: "sl-aph__icon-btn",
6236
- "aria-label": resolvedHelpLabel,
6237
- title: resolvedHelpLabel,
6238
- children: /* @__PURE__ */ jsx(BookOpen, { "aria-hidden": "true" })
6239
- }
6240
- ) : null,
6241
- showReopen ? /* @__PURE__ */ jsx(
6242
- "button",
6243
- {
6244
- type: "button",
6245
- onClick: intro.onReopen,
6246
- className: "sl-aph__icon-btn",
6247
- "aria-label": resolvedReopenLabel,
6248
- title: resolvedReopenLabel,
6249
- children: /* @__PURE__ */ jsx(HelpCircle, { "aria-hidden": "true" })
6250
- }
6251
- ) : null
6252
- ] }) : null
6253
- ] }),
6254
- showIntro ? /* @__PURE__ */ jsx(AdminPageHeaderIntroCard, { intro }) : null
6255
- ] });
6256
- }
6257
- function AdminPageHeaderIntroCard({ intro }) {
6258
- const tone = intro.tone ?? "info";
6259
- const Icon = TONE_ICON2[tone] ?? Info;
6260
- return /* @__PURE__ */ jsxs("div", { className: "sl-aph__intro", "data-tone": tone, role: "note", children: [
6261
- /* @__PURE__ */ jsx("div", { className: "sl-aph__intro-icon", children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true", style: { width: "0.95rem", height: "0.95rem" } }) }),
6262
- /* @__PURE__ */ jsxs("div", { className: "sl-aph__intro-body", children: [
6263
- /* @__PURE__ */ jsx("h4", { className: "sl-aph__intro-title", children: intro.title }),
6264
- /* @__PURE__ */ jsx("span", { className: "sl-aph__intro-text", children: intro.body }),
6265
- intro.action ? /* @__PURE__ */ jsx("span", { className: "sl-aph__intro-action", children: intro.action }) : null
6266
- ] }),
6267
- /* @__PURE__ */ jsx(
6268
- "button",
6269
- {
6270
- type: "button",
6271
- onClick: intro.onDismiss,
6272
- "aria-label": "Dismiss",
6273
- className: "sl-aph__intro-dismiss",
6274
- children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", style: { width: "0.875rem", height: "0.875rem" } })
6275
- }
6276
- )
6277
- ] });
6278
- }
6279
6199
  function ShellHeader({
6280
6200
  title,
6281
6201
  subtitle,
@@ -7452,6 +7372,8 @@ function RecordsAdminShellInner(props) {
7452
7372
  renderListRow,
7453
7373
  renderEmpty: renderEmptyState,
7454
7374
  groupBy,
7375
+ defaultGroupKey,
7376
+ onGroupExpanded,
7455
7377
  density = "comfortable"
7456
7378
  } = rail ?? {};
7457
7379
  const {
@@ -7592,8 +7514,11 @@ function RecordsAdminShellInner(props) {
7592
7514
  setSelectedBatchId,
7593
7515
  selectedItemId,
7594
7516
  setSelectedItemId,
7517
+ selectedLifecycleKey,
7518
+ setSelectedLifecycleKey,
7595
7519
  skipNextItemResetRef
7596
7520
  } = selection;
7521
+ const [isReconcilingRecordSelection, setIsReconcilingRecordSelection] = useState(false);
7597
7522
  const { dismissed, dismiss, undismiss } = useIntroDismissed(SL, collectionId, appId, recordType);
7598
7523
  const headerWillRender = useMemo(() => {
7599
7524
  const headerCustomised = !!title || !!subtitle || !!headerIcon || !!headerActions || showStats || !!statsItems || !!statsTitle || !!statsIcon || !!helpUrl;
@@ -7654,6 +7579,7 @@ function RecordsAdminShellInner(props) {
7654
7579
  useEffect(() => {
7655
7580
  if (activeScope !== "rule" && activeScope !== "collection" && activeScope !== "all") return;
7656
7581
  if (selectedRecordId !== null) return;
7582
+ if (isReconcilingRecordSelection) return;
7657
7583
  if (ruleWizardStep !== null) return;
7658
7584
  if (draftKind !== null) return;
7659
7585
  if (activeScope === "collection" && cardinality === "collection") {
@@ -7664,7 +7590,7 @@ function RecordsAdminShellInner(props) {
7664
7590
  }
7665
7591
  const first = recordList.items[0];
7666
7592
  if (first?.id) setSelectedRecordId(first.id);
7667
- }, [activeScope, selectedRecordId, recordList.items, cardinality, ruleWizardStep, draftKind]);
7593
+ }, [activeScope, selectedRecordId, recordList.items, cardinality, ruleWizardStep, draftKind, isReconcilingRecordSelection]);
7668
7594
  const editingScopes = useEditingScope({
7669
7595
  activeScope,
7670
7596
  cardinality,
@@ -7761,14 +7687,15 @@ function RecordsAdminShellInner(props) {
7761
7687
  supportedScopes: supportedForResolution,
7762
7688
  enabled: !!editingTargetScope
7763
7689
  });
7764
- const refetchAll = useCallback(() => {
7765
- productBrowse.refetch();
7766
- recordList.refetch();
7767
- facetBrowse.refetch();
7768
- variantChildren.refetch();
7769
- batchChildren.refetch();
7770
- if (isCollection) collectionItems.refetch();
7771
- }, [productBrowse, recordList, facetBrowse, variantChildren, batchChildren, isCollection, collectionItems]);
7690
+ const refetchAll = useCallback(async () => {
7691
+ await Promise.all([
7692
+ recordList.refetch(),
7693
+ isCollection ? collectionItems.refetch() : Promise.resolve(),
7694
+ queryClient.refetchQueries({
7695
+ queryKey: scopeCountsQueryKey(ctx.collectionId, ctx.appId, ctx.recordType)
7696
+ })
7697
+ ]);
7698
+ }, [recordList, isCollection, collectionItems, queryClient, ctx.collectionId, ctx.appId, ctx.recordType]);
7772
7699
  const { editorCtx } = useShellEditorTarget({
7773
7700
  editingTargetScope,
7774
7701
  isCollection,
@@ -7787,7 +7714,7 @@ function RecordsAdminShellInner(props) {
7787
7714
  },
7788
7715
  defaultData,
7789
7716
  deriveDraftLabel,
7790
- onSaved: (isCreate, savedRecordId) => {
7717
+ onSaved: async (isCreate, savedRecordId) => {
7791
7718
  onTelemetry?.({ type: "record.save", recordType, ref: editingTargetScope?.raw ?? "", isCreate });
7792
7719
  if (ruleWizardStep !== null) {
7793
7720
  setRuleWizardStep(null);
@@ -7795,15 +7722,20 @@ function RecordsAdminShellInner(props) {
7795
7722
  setDraftKind(null);
7796
7723
  }
7797
7724
  if (isCreate && selectedRecordId === DRAFT_ID3) {
7798
- setSelectedRecordId(savedRecordId ?? null);
7725
+ setIsReconcilingRecordSelection(true);
7726
+ setSelectedRecordId(null);
7799
7727
  setDraftKind(null);
7800
7728
  }
7801
7729
  if (isCreate && isCollection && savedRecordId && isDraftId3(selectedItemId)) {
7802
7730
  setSelectedItemId(savedRecordId);
7803
7731
  }
7804
- refetchAll();
7732
+ await refetchAll();
7733
+ if (!isCollection && isCreate && activeScope === "collection") {
7734
+ setSelectedRecordId(savedRecordId ?? null);
7735
+ }
7736
+ setIsReconcilingRecordSelection(false);
7805
7737
  },
7806
- onDeleted: () => {
7738
+ onDeleted: async () => {
7807
7739
  onTelemetry?.({ type: "record.delete", recordType, ref: editingTargetScope?.raw ?? "" });
7808
7740
  if (isCollection && selectedItemId) {
7809
7741
  setSelectedItemId(null);
@@ -7812,10 +7744,12 @@ function RecordsAdminShellInner(props) {
7812
7744
  } else if (drillTab === "batch") {
7813
7745
  setSelectedBatchId(void 0);
7814
7746
  } else if (selectedRecordId) {
7747
+ setIsReconcilingRecordSelection(true);
7815
7748
  setSelectedRecordId(null);
7816
7749
  setDraftKind(null);
7817
7750
  }
7818
- refetchAll();
7751
+ await refetchAll();
7752
+ setIsReconcilingRecordSelection(false);
7819
7753
  }
7820
7754
  });
7821
7755
  useUnsavedGuard({
@@ -8419,23 +8353,104 @@ function RecordsAdminShellInner(props) {
8419
8353
  ref: "",
8420
8354
  scope: parseRef(""),
8421
8355
  data: null,
8422
- status: "empty",
8356
+ // The dashed/dotted circle is the "empty / not set" affordance. The
8357
+ // synthetic All-items row aggregates every item-level record under the
8358
+ // collection scope, so it should read as "configured" whenever any
8359
+ // items exist — otherwise users see the same missing-data indicator
8360
+ // they (correctly) see next to truly empty rows on the Products tab.
8361
+ status: collectionItems.items.length ? "configured" : "empty",
8423
8362
  label: i18n.itemsAllLabel,
8424
8363
  subtitle: collectionItems.items.length ? `${collectionItems.items.length} ${itemNoun}${collectionItems.items.length === 1 ? "" : "s"}` : void 0
8425
8364
  }),
8426
8365
  [i18n.itemsAllLabel, collectionItems.items.length, itemNoun]
8427
8366
  );
8367
+ const isLifecycleRail = (isAllTab || isGlobalTab) && isCollection && !!groupBy;
8368
+ const lifecycleBuckets = useMemo(() => {
8369
+ if (!isLifecycleRail || !groupBy) return [];
8370
+ const map = /* @__PURE__ */ new Map();
8371
+ const order = [];
8372
+ for (const item of collectionItems.items) {
8373
+ const g = groupBy(item) ?? { key: "__other", label: "Other" };
8374
+ let bucket = map.get(g.key);
8375
+ if (!bucket) {
8376
+ bucket = { key: g.key, label: g.label, icon: g.icon, tone: g.tone, items: [] };
8377
+ map.set(g.key, bucket);
8378
+ order.push(g.key);
8379
+ }
8380
+ bucket.items.push(item);
8381
+ }
8382
+ return order.sort().map((k) => map.get(k));
8383
+ }, [isLifecycleRail, groupBy, collectionItems.items]);
8384
+ const LIFECYCLE_PREFIX = "lifecycle:";
8385
+ const lifecycleRows = useMemo(() => {
8386
+ if (!isLifecycleRail) return [];
8387
+ const rows = [
8388
+ {
8389
+ id: `${LIFECYCLE_PREFIX}__all`,
8390
+ ref: "",
8391
+ scope: parseRef(""),
8392
+ data: null,
8393
+ status: collectionItems.items.length ? "configured" : "empty",
8394
+ label: i18n.itemsAllLabel,
8395
+ subtitle: collectionItems.items.length ? `${collectionItems.items.length} ${itemNoun}${collectionItems.items.length === 1 ? "" : "s"}` : void 0
8396
+ }
8397
+ ];
8398
+ for (const b of lifecycleBuckets) {
8399
+ rows.push({
8400
+ id: `${LIFECYCLE_PREFIX}${b.key}`,
8401
+ ref: "",
8402
+ scope: parseRef(""),
8403
+ data: null,
8404
+ status: b.items.length ? "configured" : "empty",
8405
+ label: b.label,
8406
+ subtitle: `${b.items.length} ${itemNoun}${b.items.length === 1 ? "" : "s"}`,
8407
+ // Tone surfaces as a chip; we reuse `badges` for that.
8408
+ badges: b.tone && b.tone !== "default" ? [{ label: b.tone, tone: b.tone === "success" ? "success" : b.tone === "warning" ? "warning" : b.tone === "danger" ? "danger" : "neutral" }] : void 0
8409
+ });
8410
+ }
8411
+ return rows;
8412
+ }, [isLifecycleRail, lifecycleBuckets, collectionItems.items.length, i18n.itemsAllLabel, itemNoun]);
8413
+ const lifecycleSeededRef = useRef(false);
8414
+ useEffect(() => {
8415
+ if (!isLifecycleRail) {
8416
+ lifecycleSeededRef.current = false;
8417
+ return;
8418
+ }
8419
+ if (lifecycleSeededRef.current) return;
8420
+ if (selectedLifecycleKey !== null) {
8421
+ lifecycleSeededRef.current = true;
8422
+ return;
8423
+ }
8424
+ if (!defaultGroupKey) return;
8425
+ if (!lifecycleBuckets.some((b) => b.key === defaultGroupKey)) return;
8426
+ setSelectedLifecycleKey(defaultGroupKey);
8427
+ lifecycleSeededRef.current = true;
8428
+ }, [isLifecycleRail, defaultGroupKey, lifecycleBuckets, selectedLifecycleKey, setSelectedLifecycleKey]);
8429
+ const filteredCollectionItems = useMemo(() => {
8430
+ if (!isLifecycleRail || !selectedLifecycleKey || !groupBy) return collectionItems.items;
8431
+ return collectionItems.items.filter((it) => {
8432
+ const g = groupBy(it) ?? { key: "__other" };
8433
+ return g.key === selectedLifecycleKey;
8434
+ });
8435
+ }, [isLifecycleRail, selectedLifecycleKey, groupBy, collectionItems.items]);
8428
8436
  const leftItems = isProductTab ? productListItems : isRuleTab ? applyFacetBrowseFilter(
8429
8437
  isCollection ? collectionRuleRailItems : filteredRuleItems
8430
- ) : (isGlobalTab || isAllTab) && isCollection ? [collectionGlobalAllRow] : isRecordsTab ? applyFacetBrowseFilter(recordList.items) : [];
8438
+ ) : isLifecycleRail ? lifecycleRows : (isGlobalTab || isAllTab) && isCollection ? [collectionGlobalAllRow] : isRecordsTab ? applyFacetBrowseFilter(recordList.items) : [];
8431
8439
  const leftLoading = isProductTab ? !productPinned && productBrowse.isLoading : isRecordsTab ? recordList.isLoading || probe.isLoading : false;
8432
8440
  const leftError = isProductTab ? productBrowse.error : isRecordsTab ? recordList.error : null;
8433
- const leftSelectedId = isProductTab ? void 0 : selectedRecordId && selectedRecordId !== DRAFT_ID3 ? selectedRecordId : void 0;
8441
+ const leftSelectedId = isProductTab ? void 0 : isLifecycleRail ? `${LIFECYCLE_PREFIX}${selectedLifecycleKey ?? "__all"}` : selectedRecordId && selectedRecordId !== DRAFT_ID3 ? selectedRecordId : void 0;
8434
8442
  const leftSelectedAnchorKey = isProductTab ? selectedProductId ? buildRef({ productId: selectedProductId }) : void 0 : void 0;
8435
8443
  const dirtyId = !editorCtx.isDirty ? void 0 : selectedRecordId && selectedRecordId !== DRAFT_ID3 ? selectedRecordId : void 0;
8436
8444
  const dirtyAnchorKey = !editorCtx.isDirty ? void 0 : isProductTab ? editingScope?.raw : void 0;
8437
8445
  const onLeftSelect = (item) => {
8438
8446
  void runWithGuard(() => {
8447
+ if (isLifecycleRail && typeof item.id === "string" && item.id.startsWith(LIFECYCLE_PREFIX)) {
8448
+ const key = item.id.slice(LIFECYCLE_PREFIX.length);
8449
+ const next = key === "__all" ? null : key;
8450
+ setSelectedLifecycleKey(next);
8451
+ if (next && onGroupExpanded) onGroupExpanded(next);
8452
+ return;
8453
+ }
8439
8454
  if (isProductTab) {
8440
8455
  setSelectedProductId(item.scope.productId);
8441
8456
  setSelectedVariantId(void 0);
@@ -8793,7 +8808,7 @@ function RecordsAdminShellInner(props) {
8793
8808
  // navigational anchor, not a real record — applying the
8794
8809
  // host's groupBy bucketed it under "Other" (its
8795
8810
  // data is null). Skip grouping for that single-row rail.
8796
- (isGlobalTab || isAllTab) && isCollection ? void 0 : effectiveGroupBy
8811
+ (isGlobalTab || isAllTab) && isCollection || isLifecycleRail ? void 0 : effectiveGroupBy
8797
8812
  ),
8798
8813
  renderGroupActions: renderRuleGroupActions,
8799
8814
  rowClipboard,
@@ -8882,7 +8897,7 @@ function RecordsAdminShellInner(props) {
8882
8897
  ruleWizardStep === null && isCollection && editingScope && !selectedItemId && /* @__PURE__ */ jsx(
8883
8898
  ItemListView,
8884
8899
  {
8885
- items: collectionItems.items,
8900
+ items: filteredCollectionItems,
8886
8901
  isLoading: collectionItems.isLoading,
8887
8902
  error: collectionItems.error,
8888
8903
  ctx: itemViewCtx,