@object-ui/plugin-detail 5.1.1 → 5.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,142 @@
1
1
  # @object-ui/plugin-detail
2
2
 
3
+ ## 5.3.0
4
+
5
+ ## 5.2.1
6
+
7
+ ## 5.2.0
8
+
9
+ ### Minor Changes
10
+
11
+ - 7c441f5: End-to-end @-mention notifications.
12
+
13
+ `@object-ui/plugin-detail` now exports `extractMentions(text, suggestions)`
14
+ — a small utility that resolves `@<label>` tokens in a comment body to
15
+ user ids, using the same suggestion list that drives the in-editor
16
+ dropdown. Handles labels with spaces ("@QA Test"), CJK ("@王小明"),
17
+ longest-match disambiguation ("Anna Lee" wins over "Anna"), and ignores
18
+ unknown @-tokens. 9 unit tests.
19
+
20
+ `@object-ui/app-shell` `RecordDetailView` now:
21
+ 1. Serializes the resolved mention ids into `sys_comment.mentions`
22
+ (previously hard-coded `'[]'`, so servers had no idea who was being
23
+ pinged).
24
+ 2. Fan-outs a `sys_notification` row per mentioned recipient
25
+ (self-mentions are filtered as noise) with the canonical bell-inbox
26
+ shape: `type: 'mention'`, `recipient_id`, `actor_name`, `title`,
27
+ `body` preview (≤140 chars), `source_object`/`source_id`/
28
+ `source_comment_id`, `is_read: false`, `created_at`.
29
+
30
+ The notification write tolerates 404 silently, so deployments without
31
+ a notification collection degrade to the previous behavior (mention
32
+ text + highlight, no inbox row). Spec-compliant servers that emit
33
+ notifications via their own sys_comment after-create hook can ignore
34
+ the client-side write — the bell de-dupes by id at the polling layer.
35
+
36
+ - 70b5570: `record:path` now distinguishes won/lost terminal stages. Stages can opt
37
+ in via the new `terminal: 'won' | 'lost'` property on each stage entry,
38
+ and the renderer also falls back to a value/label heuristic (matches
39
+ `closed_lost`, `lost`, `failed`, `cancelled`, `失败`, `流失`, `丢单`, etc.)
40
+ so existing CRM-style picklists get the treatment without migration.
41
+ - **Lost** stages render in a visually separated group with a left
42
+ border, destructive (red) tint, pill shape, and `✗` glyph — mirroring
43
+ the Salesforce / HubSpot alt-terminus pattern that signals "this
44
+ breaks the forward path, not steps past it."
45
+ - **Won** terminus (the last stage of the forward chevron) gets a subtle
46
+ emerald wash + 🏆 glyph to read as "the goal," even before the record
47
+ reaches it.
48
+ - Mobile pill row distinguishes lost via color, since the layout doesn't
49
+ have room to fork the row.
50
+
51
+ - 3216f8a: `buildDefaultPageSchema` now accepts a `slots.rightRail` override that
52
+ contributes nodes to the aside (right-rail) region. The aside region is
53
+ emitted whenever either the auto-detected reference rail OR
54
+ `slots.rightRail` is non-empty (previously: only when 2+ related lists
55
+ were declared). Slot contributions are appended after the canonical
56
+ `record:reference_rail` so the "related summary" stays anchored at the
57
+ top while plugins can drop activity feeds, workflow status cards,
58
+ presence lists, etc. beneath it.
59
+
60
+ No change for existing schemas — the aside region only renders if
61
+ something opts in.
62
+
63
+ ### Patch Changes
64
+
65
+ - a3cb88f: CRM UX polish batch:
66
+ - Kanban columns: drop the per-column rainbow top stripe. Lane border + header divider are sufficient; cards are now the loudest thing on screen (Linear / HubSpot pattern).
67
+ - Stage chevron (`record:path`): bump completed-stage contrast (emerald-800 text on emerald-500/15, was 700 on /10) and future-stage text from `foreground/70` to `foreground/85` for legibility.
68
+ - i18n: add `notifications.emptyUnread`, `notifications.filterUnread`, `notifications.filterAll` (en + zh) so the InboxPopover Unread/All sub-filter renders in the active locale.
69
+ - 5425608: CRM UX polish pass — calmer enterprise look across detail + kanban.
70
+ - **plugin-kanban**: column headers now use a 2px muted accent stripe with
71
+ neutral foreground titles + a quiet grey count pill instead of full
72
+ rainbow gradient + colored title + colored count. Pipeline boards
73
+ (Opportunity, Case, Task, Lead) look like Salesforce/Linear instead of
74
+ a toy. WIP-limit overflow remains destructive-red so urgency stays loud.
75
+ - **plugin-detail (`record:reference_rail`)**: new `hideEmpty` prop
76
+ (default true) collapses entries whose total === 0 into a single
77
+ `+ N empty (Quotes · Products …)` chip at the bottom of the rail.
78
+ Removes the 4–7 "No records" stack that dominated the aside.
79
+ - **plugin-detail (`record:path`)**: completed stages now render with an
80
+ emerald-tinted background + bold green check instead of low-contrast
81
+ `bg-muted text-muted-foreground` (which read as "light grey on white"
82
+ and was borderline unreadable).
83
+ - **app-shell (`RecordDetailView`)**: record-not-found short-circuit.
84
+ Previously a stale/missing recordId still rendered the page chrome
85
+ (rail, discussion, breadcrumb with the raw id), making invalid links
86
+ look like a partially broken page. Now renders a clean centered
87
+ `Empty` state with database icon + i18n'd "Record not found" copy.
88
+ - **i18n**: added `detail.showEmptyRelated_{one,other}` and
89
+ `empty.recordNotFound{,Description}` keys (en + zh).
90
+
91
+ - 5633edd: feat(detail,grid): tab + selection motion polish
92
+
93
+ **plugin-detail**
94
+ - `DetailTabs` and the auto-tabs path in `DetailView` (5 inline
95
+ `<TabsContent>` instances: details, related, activity, discussion,
96
+ history) now fade in when their tab becomes active, eliminating
97
+ the harsh flash when switching tabs.
98
+
99
+ **plugin-grid**
100
+ - `BulkActionBar` slides in from the bottom + fades in when a
101
+ selection is made, instead of popping into existence.
102
+ - The "N items selected" counter re-animates on every count change
103
+ (re-keyed on the count value with a small `zoom-in-90`), so users
104
+ see clear feedback as they tick/untick rows. `tabular-nums` keeps
105
+ the number from jittering during the animation.
106
+
107
+ All animations are wrapped in `motion-safe:` so prefers-reduced-motion
108
+ users keep the original instant UI. No new deps.
109
+
110
+ **Dialog / Sheet motion audit (informational, no code change)**
111
+
112
+ Verified `packages/components/src/ui/{dialog,alert-dialog,sheet}.tsx`:
113
+ Dialog + AlertDialog use a consistent `duration-200`. Sheet uses an
114
+ asymmetric `open:500ms / close:300ms` — this is the intentional
115
+ shadcn upstream default ("slower open feels purposeful"). No fixes
116
+ needed; these primitives live in the no-touch zone anyway.
117
+
118
+ - e919433: Stop silently assuming USD when a currency field has no `currency`
119
+ configured. For non-USD orgs (e.g. a CNY-based CRM seeded without an
120
+ explicit currency) the cells now render as plain locale-formatted
121
+ numbers (`150,000.00`) instead of `$150,000.00` — which was the #1
122
+ "why is my RMB showing as dollars?" bug.
123
+
124
+ Behavior change is opt-in via omission: when `currency` /
125
+ `defaultCurrency` is set on the field/column, formatting is unchanged.
126
+
127
+ Fixed call sites:
128
+ - `@object-ui/fields`: `formatCurrency`, `formatCompactCurrency`, and
129
+ `CurrencyCellRenderer` no longer default-param `'USD'`.
130
+ - `@object-ui/i18n`: `formatCurrency()` falls back to `formatNumber`
131
+ semantics when `currency` is omitted.
132
+ - `@object-ui/plugin-grid`: column-summary formatter (`Sum: 5,000,000`
133
+ instead of `Sum: $5,000,000.00`).
134
+ - `@object-ui/plugin-detail`: header-highlight currency formatter.
135
+ - `@object-ui/plugin-dashboard`: `ObjectMetricWidget` inferred
136
+ currency now resolves to `undefined` (not `'USD'`) for un-tagged
137
+ fields, so `MetricWidget`'s `isCurrency` heuristic falls through
138
+ to plain number formatting.
139
+
3
140
  ## 5.1.1
4
141
 
5
142
  ## 5.1.0
package/dist/index.js CHANGED
@@ -159,6 +159,8 @@ var Ct = {
159
159
  "detail.nextRecordKey": "Next record (→)",
160
160
  "detail.lastRecord": "Last record (End)",
161
161
  "detail.noRecords": "No records",
162
+ "detail.showEmptyRelated_one": "+ {{count}} empty",
163
+ "detail.showEmptyRelated_other": "+ {{count}} empty",
162
164
  "detail.searchWhileNavigating": "Search while navigating",
163
165
  "detail.searchRecords": "Search records…",
164
166
  "detail.allActivity": "All Activity",
@@ -416,7 +418,7 @@ var Tt = ({ section: e, data: t, className: r, objectSchema: i, objectName: a, i
416
418
  }, e.key))
417
419
  }), o.map((e) => /* @__PURE__ */ Z(L, {
418
420
  value: e.key,
419
- className: "mt-4",
421
+ className: "mt-4 motion-safe:data-[state=active]:animate-in motion-safe:data-[state=active]:fade-in-0 motion-safe:duration-150",
420
422
  children: /* @__PURE__ */ Z(n.Suspense, {
421
423
  fallback: null,
422
424
  children: Array.isArray(e.content) ? /* @__PURE__ */ Z("div", {
@@ -1845,11 +1847,14 @@ var tn = ({ schema: e, dataSource: r, className: i, onEdit: a, onDelete: o, onBa
1845
1847
  try {
1846
1848
  if (i === "currency") {
1847
1849
  let e = Number(t);
1848
- Number.isNaN(e) || (a = new Intl.NumberFormat(void 0, {
1849
- style: "currency",
1850
- currency: n?.currency || r?.currency || "USD",
1851
- maximumFractionDigits: 0
1852
- }).format(e));
1850
+ if (!Number.isNaN(e)) {
1851
+ let t = n?.currency || r?.currency;
1852
+ a = t ? new Intl.NumberFormat(void 0, {
1853
+ style: "currency",
1854
+ currency: t,
1855
+ maximumFractionDigits: 0
1856
+ }).format(e) : new Intl.NumberFormat(void 0, { maximumFractionDigits: 0 }).format(e);
1857
+ }
1853
1858
  } else if (i === "date" || i === "datetime") {
1854
1859
  let e = new Date(t);
1855
1860
  Number.isNaN(e.getTime()) || (a = i === "datetime" ? e.toLocaleString(void 0, {
@@ -2176,12 +2181,12 @@ var tn = ({ schema: e, dataSource: r, className: i, onEdit: a, onDelete: o, onBa
2176
2181
  }),
2177
2182
  /* @__PURE__ */ Z(L, {
2178
2183
  value: "details",
2179
- className: "mt-4",
2184
+ className: "mt-4 motion-safe:data-[state=active]:animate-in motion-safe:data-[state=active]:fade-in-0 motion-safe:duration-150",
2180
2185
  children: a
2181
2186
  }),
2182
2187
  e && /* @__PURE__ */ Z(L, {
2183
2188
  value: "related",
2184
- className: "mt-4",
2189
+ className: "mt-4 motion-safe:data-[state=active]:animate-in motion-safe:data-[state=active]:fade-in-0 motion-safe:duration-150",
2185
2190
  children: /* @__PURE__ */ Z("div", {
2186
2191
  className: "space-y-3",
2187
2192
  children: we.map((e, t) => /* @__PURE__ */ Z(Ot, {
@@ -2207,17 +2212,17 @@ var tn = ({ schema: e, dataSource: r, className: i, onEdit: a, onDelete: o, onBa
2207
2212
  }),
2208
2213
  t && /* @__PURE__ */ Z(L, {
2209
2214
  value: "activity",
2210
- className: "mt-4",
2215
+ className: "mt-4 motion-safe:data-[state=active]:animate-in motion-safe:data-[state=active]:fade-in-0 motion-safe:duration-150",
2211
2216
  children: /* @__PURE__ */ Z(Rt, { activities: q.activities })
2212
2217
  }),
2213
2218
  n && /* @__PURE__ */ Z(L, {
2214
2219
  value: "discussion",
2215
- className: "mt-4",
2220
+ className: "mt-4 motion-safe:data-[state=active]:animate-in motion-safe:data-[state=active]:fade-in-0 motion-safe:duration-150",
2216
2221
  children: u
2217
2222
  }),
2218
2223
  i && /* @__PURE__ */ Z(L, {
2219
2224
  value: "history",
2220
- className: "mt-4",
2225
+ className: "mt-4 motion-safe:data-[state=active]:animate-in motion-safe:data-[state=active]:fade-in-0 motion-safe:duration-150",
2221
2226
  children: /* @__PURE__ */ Z(Gt, {
2222
2227
  entries: q.history.entries,
2223
2228
  loading: q.history.loading,
@@ -3595,14 +3600,24 @@ var Mn = ({ items: e, config: t, filterMode: r, onFilterChange: i, hasMore: a =
3595
3600
  let i = it(), { translateOptions: a } = ot(), { designer: o } = Rn(n), s = Array.isArray(e.stages) ? e.stages : [], c = e.statusField, l = r.useMemo(() => {
3596
3601
  if (s.length === 0 || !c || !i?.objectName) return s;
3597
3602
  let e = a(i.objectName, c, s);
3598
- return Array.isArray(e) && e.length === s.length ? e : s;
3603
+ return Array.isArray(e) && e.length === s.length ? s.map((t, n) => ({
3604
+ ...t,
3605
+ label: e[n]?.label ?? t.label
3606
+ })) : s;
3599
3607
  }, [
3600
3608
  s,
3601
3609
  c,
3602
3610
  i?.objectName,
3603
3611
  a
3604
- ]), u = c && i?.data ? i.data[c] : void 0, d = l.findIndex((e) => e.value === u);
3605
- if (d < 0 && (d = -1), l.length === 0) return /* @__PURE__ */ Z("div", {
3612
+ ]), u = c && i?.data ? i.data[c] : void 0, d = /(^|[_-\s])(closed_)?(lost|failed?|cancell?ed|失败|流失|丢单|败)([_-\s]|$)/i, f = /(^|[_-\s])(closed_)?(won|success|成交|赢|完成)([_-\s]|$)/i, p = l.map((e) => {
3613
+ if (e.terminal) return e.terminal;
3614
+ let t = `${String(e.value ?? "")} ${String(e.label ?? "")}`;
3615
+ if (d.test(t)) return "lost";
3616
+ if (f.test(t)) return "won";
3617
+ }), m = p.findIndex((e) => e === "lost"), h = m === -1 ? l : l.slice(0, m), g = m === -1 ? [] : l.slice(m), _ = m === -1 ? p : p.slice(0, m), v = l.findIndex((e) => e.value === u);
3618
+ v < 0 && (v = -1);
3619
+ let y = m !== -1 && v >= m;
3620
+ if (l.length === 0) return /* @__PURE__ */ Z("div", {
3606
3621
  className: t,
3607
3622
  ...o,
3608
3623
  children: /* @__PURE__ */ Z("div", {
@@ -3610,55 +3625,95 @@ var Mn = ({ items: e, config: t, filterMode: r, onFilterChange: i, hasMore: a =
3610
3625
  children: "record:path — no stages configured"
3611
3626
  })
3612
3627
  });
3613
- let f = (e, t) => {
3628
+ let b = (e, t) => {
3614
3629
  if (l.length !== 1) return e === 0 ? "polygon(0 0, calc(100% - 14px) 0, 100% 50%, calc(100% - 14px) 100%, 0 100%)" : e === t ? "polygon(0 0, 100% 0, 100% 100%, 0 100%, 14px 50%)" : "polygon(0 0, calc(100% - 14px) 0, 100% 50%, calc(100% - 14px) 100%, 0 100%, 14px 50%)";
3615
- }, p = l.length - 1;
3630
+ }, x = h.length - 1;
3616
3631
  return /* @__PURE__ */ Q("div", {
3617
3632
  className: U("w-full", t),
3618
3633
  ...o,
3619
- children: [/* @__PURE__ */ Z("div", {
3620
- className: "hidden sm:flex w-full items-stretch",
3634
+ children: [/* @__PURE__ */ Q("div", {
3635
+ className: "hidden sm:flex w-full items-stretch gap-2",
3621
3636
  role: "list",
3622
3637
  "aria-label": e.aria?.label || "Record path",
3623
- children: l.map((e, t) => {
3624
- let n = d >= 0 && t < d, r = t === d, i = f(t, p);
3625
- return /* @__PURE__ */ Z("div", {
3626
- role: "listitem",
3627
- "aria-current": r ? "step" : void 0,
3628
- style: i ? {
3629
- clipPath: i,
3630
- WebkitClipPath: i
3631
- } : void 0,
3632
- className: U("relative flex-1 min-w-0 px-5 py-2 text-xs font-medium text-center", t > 0 && "-ml-2", l.length === 1 && "rounded-md border", r && "bg-primary text-primary-foreground shadow-sm ring-1 ring-primary/40", n && "bg-muted text-muted-foreground", !r && !n && "bg-background text-foreground/70 border border-border/60"),
3633
- children: /* @__PURE__ */ Q("span", {
3634
- className: "inline-flex items-center gap-1.5 truncate",
3635
- style: {
3636
- paddingLeft: t === 0 ? 0 : `${14 / 2}px`,
3637
- paddingRight: t === p ? 0 : `${14 / 2}px`
3638
- },
3639
- children: [n && /* @__PURE__ */ Z("span", {
3640
- "aria-hidden": !0,
3641
- children: "✓"
3642
- }), e.label]
3643
- })
3644
- }, `${e.value}-${t}`);
3645
- })
3638
+ children: [/* @__PURE__ */ Z("div", {
3639
+ className: "flex flex-1 items-stretch",
3640
+ children: h.map((e, t) => {
3641
+ let n = !y && v >= 0 && t < v, r = !y && t === v, i = _[t] === "won" && t === x, a = b(t, x);
3642
+ return /* @__PURE__ */ Z("div", {
3643
+ role: "listitem",
3644
+ "aria-current": r ? "step" : void 0,
3645
+ style: a ? {
3646
+ clipPath: a,
3647
+ WebkitClipPath: a
3648
+ } : void 0,
3649
+ className: U("relative flex-1 min-w-0 px-5 py-2 text-xs font-medium text-center", t > 0 && "-ml-2", h.length === 1 && "rounded-md border", r && "bg-primary text-primary-foreground shadow-sm ring-1 ring-primary/40", n && "bg-emerald-500/15 text-emerald-800 dark:text-emerald-200", !r && !n && i && "bg-emerald-500/5 text-emerald-700/85 dark:text-emerald-300/85 border border-emerald-500/20", !r && !n && !i && "bg-background text-foreground/85 border border-border/60"),
3650
+ children: /* @__PURE__ */ Q("span", {
3651
+ className: "inline-flex items-center gap-1.5 truncate",
3652
+ style: {
3653
+ paddingLeft: t === 0 ? 0 : `${14 / 2}px`,
3654
+ paddingRight: t === x ? 0 : `${14 / 2}px`
3655
+ },
3656
+ children: [
3657
+ n && /* @__PURE__ */ Z("span", {
3658
+ "aria-hidden": !0,
3659
+ className: "text-emerald-600 dark:text-emerald-400 font-semibold",
3660
+ children: "✓"
3661
+ }),
3662
+ i && !r && /* @__PURE__ */ Z("span", {
3663
+ "aria-hidden": !0,
3664
+ className: "opacity-70",
3665
+ children: "🏆"
3666
+ }),
3667
+ e.label
3668
+ ]
3669
+ })
3670
+ }, `${e.value}-${t}`);
3671
+ })
3672
+ }), g.length > 0 && /* @__PURE__ */ Z("div", {
3673
+ className: "flex items-stretch gap-1 pl-2 border-l border-border/40",
3674
+ "aria-label": "Alternative terminal stages",
3675
+ children: g.map((e, t) => {
3676
+ let n = m + t === v;
3677
+ return /* @__PURE__ */ Z("div", {
3678
+ role: "listitem",
3679
+ "aria-current": n ? "step" : void 0,
3680
+ className: U("shrink-0 px-3 py-2 text-xs font-medium rounded-md border whitespace-nowrap", n && "bg-destructive text-destructive-foreground border-destructive shadow-sm ring-1 ring-destructive/40", !n && "bg-destructive/5 text-destructive/85 border-destructive/20"),
3681
+ children: /* @__PURE__ */ Q("span", {
3682
+ className: "inline-flex items-center gap-1",
3683
+ children: [/* @__PURE__ */ Z("span", {
3684
+ "aria-hidden": !0,
3685
+ className: "opacity-70",
3686
+ children: "✗"
3687
+ }), e.label]
3688
+ })
3689
+ }, `${e.value}-lost-${t}`);
3690
+ })
3691
+ })]
3646
3692
  }), /* @__PURE__ */ Z("div", {
3647
3693
  className: "flex sm:hidden w-full items-stretch gap-1 overflow-x-auto pb-1 -mx-1 px-1 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
3648
3694
  role: "list",
3649
3695
  "aria-label": e.aria?.label || "Record path",
3650
3696
  children: l.map((e, t) => {
3651
- let n = d >= 0 && t < d, r = t === d;
3697
+ let n = p[t] === "lost", r = !n && !y && v >= 0 && t < v, i = t === v;
3652
3698
  return /* @__PURE__ */ Z("div", {
3653
3699
  role: "listitem",
3654
- "aria-current": r ? "step" : void 0,
3655
- className: U("shrink-0 px-3 py-1.5 rounded-full text-xs font-medium border whitespace-nowrap", r && "bg-primary text-primary-foreground border-primary shadow-sm ring-1 ring-primary/40", n && "bg-muted text-muted-foreground border-transparent", !r && !n && "bg-background text-foreground/70 border-border/60"),
3700
+ "aria-current": i ? "step" : void 0,
3701
+ className: U("shrink-0 px-3 py-1.5 rounded-full text-xs font-medium border whitespace-nowrap", n && i && "bg-destructive text-destructive-foreground border-destructive shadow-sm", n && !i && "bg-destructive/5 text-destructive/85 border-destructive/20", !n && i && "bg-primary text-primary-foreground border-primary shadow-sm ring-1 ring-primary/40", !n && r && "bg-emerald-500/15 text-emerald-800 dark:text-emerald-200 border-emerald-500/30", !n && !i && !r && "bg-background text-foreground/85 border-border/60"),
3656
3702
  children: /* @__PURE__ */ Q("span", {
3657
3703
  className: "inline-flex items-center gap-1",
3658
- children: [n && /* @__PURE__ */ Z("span", {
3659
- "aria-hidden": !0,
3660
- children: "✓"
3661
- }), e.label]
3704
+ children: [
3705
+ n && /* @__PURE__ */ Z("span", {
3706
+ "aria-hidden": !0,
3707
+ className: "opacity-70",
3708
+ children: "✗"
3709
+ }),
3710
+ !n && r && /* @__PURE__ */ Z("span", {
3711
+ "aria-hidden": !0,
3712
+ className: "text-emerald-600 dark:text-emerald-400 font-semibold",
3713
+ children: "✓"
3714
+ }),
3715
+ e.label
3716
+ ]
3662
3717
  })
3663
3718
  }, `${e.value}-${t}-m`);
3664
3719
  })
@@ -3758,7 +3813,7 @@ var Mn = ({ items: e, config: t, filterMode: r, onFilterChange: i, hasMore: a =
3758
3813
  };
3759
3814
  }, Gn = (e) => e.replace(/[_-]+/g, " ").replace(/\b\w/g, (e) => e.toUpperCase()).trim(), Kn = (e) => e?.name || e?.title || e?.subject || e?.label || e?.id || "—", qn = ({ schema: e = {}, className: t, ...n }) => {
3760
3815
  let i = it(), a = ot(), { t: o } = $(), { designer: s } = Wn(n), c = gt().appName, l = Array.isArray(e.entries) ? e.entries : Array.isArray(e.properties?.entries) ? e.properties.entries : [], u = i?.recordId, d = i?.dataSource, [f, p] = r.useState({});
3761
- return r.useEffect(() => {
3816
+ if (r.useEffect(() => {
3762
3817
  if (!d?.find || !u || l.length === 0) return;
3763
3818
  let e = !1;
3764
3819
  return l.forEach((t) => {
@@ -3803,10 +3858,18 @@ var Mn = ({ items: e, config: t, filterMode: r, onFilterChange: i, hasMore: a =
3803
3858
  d,
3804
3859
  u,
3805
3860
  JSON.stringify(l.map((e) => `${e.objectName}:${e.relationshipField}:${e.limit ?? 3}`))
3806
- ]), l.length === 0 ? null : /* @__PURE__ */ Z("div", {
3861
+ ]), l.length === 0) return null;
3862
+ let m = e.hideEmpty !== !1, [g, x] = r.useState(!1), S = new Set(l.filter((e) => {
3863
+ let t = f[e.objectName];
3864
+ return m && t && !t.loading && !t.error && t.total === 0;
3865
+ }).map((e) => e.objectName)), C = l.filter((e) => g || !S.has(e.objectName)), w = l.filter((e) => S.has(e.objectName)).map((e) => e.title || (a?.objectLabel ? a.objectLabel({
3866
+ name: e.objectName,
3867
+ label: Gn(e.objectName)
3868
+ }) : Gn(e.objectName)));
3869
+ return /* @__PURE__ */ Q("div", {
3807
3870
  className: U("flex flex-col gap-3", e.className, t),
3808
3871
  ...s,
3809
- children: l.map((e) => {
3872
+ children: [C.map((e) => {
3810
3873
  let t = e.objectName, n = f[t] || {
3811
3874
  loading: !0,
3812
3875
  total: 0,
@@ -3869,7 +3932,23 @@ var Mn = ({ items: e, config: t, filterMode: r, onFilterChange: i, hasMore: a =
3869
3932
  })
3870
3933
  })]
3871
3934
  }, t);
3872
- })
3935
+ }), !g && w.length > 0 && /* @__PURE__ */ Q("button", {
3936
+ type: "button",
3937
+ onClick: () => x(!0),
3938
+ className: "self-start inline-flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors px-2.5 py-1 rounded-md border border-dashed border-border/60 hover:border-border bg-background",
3939
+ title: w.join(" · "),
3940
+ children: [/* @__PURE__ */ Z("span", { children: o("detail.showEmptyRelated", {
3941
+ defaultValue: "+ {{count}} empty",
3942
+ count: w.length
3943
+ }) }), /* @__PURE__ */ Q("span", {
3944
+ className: "truncate max-w-[180px] text-muted-foreground/70",
3945
+ children: [
3946
+ "(",
3947
+ w.join(" · "),
3948
+ ")"
3949
+ ]
3950
+ })]
3951
+ })]
3873
3952
  });
3874
3953
  }, Jn = new Set([
3875
3954
  "id",
@@ -4934,11 +5013,23 @@ function gr(e, t, n) {
4934
5013
  };
4935
5014
  }
4936
5015
  //#endregion
5016
+ //#region src/extractMentions.ts
5017
+ function _r(e, t) {
5018
+ if (!e || !t.length) return [];
5019
+ let n = [...t].sort((e, t) => t.label.length - e.label.length), r = e, i = /* @__PURE__ */ new Set();
5020
+ for (let e of n) {
5021
+ if (!e.label) continue;
5022
+ let t = e.label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), n = RegExp(`@${t}(?![\\p{L}\\p{N}_])`, "gu");
5023
+ n.test(r) && (i.add(e.id), r = r.replace(n, (e) => " ".repeat(e.length)));
5024
+ }
5025
+ return [...i];
5026
+ }
5027
+ //#endregion
4937
5028
  //#region src/synth/buildDefaultPageSchema.ts
4938
- function _r(e) {
5029
+ function vr(e) {
4939
5030
  return e == null ? [] : Array.isArray(e) ? e.filter((e) => e != null) : [e];
4940
5031
  }
4941
- function vr(e) {
5032
+ function yr(e) {
4942
5033
  if (!e) return null;
4943
5034
  if (e.stageField) return e.stageField;
4944
5035
  let t = e.fields || {};
@@ -4954,7 +5045,7 @@ function vr(e) {
4954
5045
  }
4955
5046
  return null;
4956
5047
  }
4957
- function yr(e, t) {
5048
+ function br(e, t) {
4958
5049
  if (!e || !t) return null;
4959
5050
  let n = e.fields?.[t]?.options;
4960
5051
  return !Array.isArray(n) || n.length === 0 ? null : n.map((e) => ({
@@ -4962,7 +5053,7 @@ function yr(e, t) {
4962
5053
  label: e.label
4963
5054
  }));
4964
5055
  }
4965
- function br(e, t, n = 4) {
5056
+ function xr(e, t, n = 4) {
4966
5057
  if (!e) return [];
4967
5058
  if (Array.isArray(e.highlightFields) && e.highlightFields.length > 0) return e.highlightFields.slice(0, n);
4968
5059
  let r = new Set([
@@ -5036,22 +5127,22 @@ function br(e, t, n = 4) {
5036
5127
  }
5037
5128
  return o;
5038
5129
  }
5039
- function xr(e, t = {}) {
5130
+ function Sr(e, t = {}) {
5040
5131
  return {
5041
5132
  type: "page:header",
5042
5133
  recordChrome: t.recordChrome !== !1,
5043
5134
  ...Array.isArray(t.actions) && t.actions.length > 0 ? { actions: t.actions } : {}
5044
5135
  };
5045
5136
  }
5046
- function Sr(e, t) {
5137
+ function Cr(e, t) {
5047
5138
  return !Array.isArray(t) || t.length === 0 ? null : {
5048
5139
  type: "record:quick_actions",
5049
5140
  actions: t,
5050
5141
  location: "record_header"
5051
5142
  };
5052
5143
  }
5053
- function Cr(e, t = {}) {
5054
- let n = t.statusField ?? vr(e), r = t.stages ?? (n ? yr(e, n) : null), i = t.highlightFields ?? br(e, n), a = [];
5144
+ function wr(e, t = {}) {
5145
+ let n = t.statusField ?? yr(e), r = t.stages ?? (n ? br(e, n) : null), i = t.highlightFields ?? xr(e, n), a = [];
5055
5146
  return !t.hideHighlights && i.length > 0 && a.push({
5056
5147
  type: "record:highlights",
5057
5148
  fields: i
@@ -5061,17 +5152,17 @@ function Cr(e, t = {}) {
5061
5152
  stages: r
5062
5153
  }), a;
5063
5154
  }
5064
- function wr(e, t, n) {
5155
+ function Tr(e, t, n) {
5065
5156
  return {
5066
5157
  type: "record:details",
5067
5158
  sections: t,
5068
5159
  ...n && n.length > 0 ? { hideFields: n } : {}
5069
5160
  };
5070
5161
  }
5071
- function Tr(e, t = {}) {
5072
- let n = t.statusField ?? vr(e), r = t.highlightFields ?? br(e, n), i = [{
5162
+ function Er(e, t = {}) {
5163
+ let n = t.statusField ?? yr(e), r = t.highlightFields ?? xr(e, n), i = [{
5073
5164
  label: "Details",
5074
- children: [wr(e, t.sections, r)]
5165
+ children: [Tr(e, t.sections, r)]
5075
5166
  }];
5076
5167
  return !t.hideRelatedTab && Array.isArray(t.related) && t.related.length > 0 && i.push({
5077
5168
  label: "Related",
@@ -5100,15 +5191,15 @@ function Tr(e, t = {}) {
5100
5191
  items: i
5101
5192
  };
5102
5193
  }
5103
- function Er() {
5194
+ function Dr() {
5104
5195
  return { type: "record:discussion" };
5105
5196
  }
5106
- function Dr(e, t = {}) {
5197
+ function Or(e, t = {}) {
5107
5198
  let n = t.slots || {}, r = [];
5108
- "header" in n && n.header !== void 0 ? r.push(..._r(n.header)) : r.push(xr(e, {
5199
+ "header" in n && n.header !== void 0 ? r.push(...vr(n.header)) : r.push(Sr(e, {
5109
5200
  recordChrome: t.recordChrome,
5110
5201
  actions: t.headerActions
5111
- })), "actions" in n && n.actions !== void 0 && r.push(..._r(n.actions)), "highlights" in n && n.highlights !== void 0 ? r.push(..._r(n.highlights)) : r.push(...Cr(e, {
5202
+ })), "actions" in n && n.actions !== void 0 && r.push(...vr(n.actions)), "highlights" in n && n.highlights !== void 0 ? r.push(...vr(n.highlights)) : r.push(...wr(e, {
5112
5203
  highlightFields: t.highlightFields,
5113
5204
  statusField: t.statusField,
5114
5205
  stages: t.stages,
@@ -5116,9 +5207,9 @@ function Dr(e, t = {}) {
5116
5207
  hidePath: t.hidePath
5117
5208
  }));
5118
5209
  let i = !t.hideReferenceRail && Array.isArray(t.related) && t.related.length >= 2, a = t.hideRelatedTab ?? i;
5119
- if ("tabs" in n && n.tabs !== void 0) r.push(..._r(n.tabs));
5210
+ if ("tabs" in n && n.tabs !== void 0) r.push(...vr(n.tabs));
5120
5211
  else if ("details" in n && n.details !== void 0) {
5121
- let i = _r(n.details), o = Tr(e, {
5212
+ let i = vr(n.details), o = Er(e, {
5122
5213
  sections: t.sections,
5123
5214
  related: t.related,
5124
5215
  showActivity: t.showActivity,
@@ -5131,7 +5222,7 @@ function Dr(e, t = {}) {
5131
5222
  ...o.items[0],
5132
5223
  children: i
5133
5224
  }), r.push(o);
5134
- } else r.push(Tr(e, {
5225
+ } else r.push(Er(e, {
5135
5226
  sections: t.sections,
5136
5227
  related: t.related,
5137
5228
  showActivity: t.showActivity,
@@ -5140,17 +5231,15 @@ function Dr(e, t = {}) {
5140
5231
  statusField: t.statusField,
5141
5232
  hideRelatedTab: a
5142
5233
  }));
5143
- "discussion" in n && n.discussion !== void 0 ? r.push(..._r(n.discussion)) : t.hideDiscussion || r.push(Er());
5234
+ "discussion" in n && n.discussion !== void 0 ? r.push(...vr(n.discussion)) : t.hideDiscussion || r.push(Dr());
5144
5235
  let o = [{
5145
5236
  name: "main",
5146
5237
  width: "full",
5147
5238
  components: r
5148
- }];
5149
- return i && o.push({
5150
- name: "aside",
5151
- width: "small",
5152
- className: "hidden xl:flex flex-col gap-4",
5153
- components: [{
5239
+ }], s = vr(n.rightRail);
5240
+ if (i || s.length > 0) {
5241
+ let e = [];
5242
+ i && e.push({
5154
5243
  type: "record:reference_rail",
5155
5244
  entries: t.related.map((e) => ({
5156
5245
  objectName: e.objectName,
@@ -5159,8 +5248,14 @@ function Dr(e, t = {}) {
5159
5248
  icon: e.icon,
5160
5249
  limit: 3
5161
5250
  }))
5162
- }]
5163
- }), {
5251
+ }), s.length > 0 && e.push(...s), o.push({
5252
+ name: "aside",
5253
+ width: "small",
5254
+ className: "hidden xl:flex flex-col gap-4",
5255
+ components: e
5256
+ });
5257
+ }
5258
+ return {
5164
5259
  type: "record",
5165
5260
  pageType: "record",
5166
5261
  object: e?.name,
@@ -5471,4 +5566,4 @@ e.register("detail-view", tn, {
5471
5566
  icon: "PanelRight"
5472
5567
  });
5473
5568
  //#endregion
5474
- export { Rt as ActivityTimeline, En as CommentAttachment, mr as CommentInput, an as ConcurrentUpdateDialog, Ct as DETAIL_DEFAULT_TRANSLATIONS, Tt as DetailSection, Et as DetailTabs, tn as DetailView, ir as DiffView, hn as FieldChangeItem, At as HeaderHighlight, Gt as HistoryTimeline, $n as InlineCreateRelated, hr as MentionAutocomplete, pr as PointInTimeRestore, _n as ReactionPicker, Pn as RecordActivityRenderer, Mn as RecordActivityTimeline, Fn as RecordChatterPanel, Ln as RecordChatterRenderer, Mt as RecordComments, Zn as RecordDetailDrawer, ln as RecordDetailsRenderer, mn as RecordHighlightsRenderer, Un as RecordHistoryRenderer, Qt as RecordMetaFooter, ar as RecordNavigationEnhanced, zn as RecordPathRenderer, Vn as RecordQuickActionsRenderer, qn as RecordReferenceRailRenderer, fn as RecordRelatedListRenderer, Ot as RelatedList, dr as RelationshipGraph, Sn as RichTextCommentInput, kt as SectionGroup, bn as SubscriptionToggle, yn as ThreadedReplies, bt as applyAutoSpan, xt as applyDetailAutoLayout, Sr as buildDefaultActions, wr as buildDefaultDetails, Er as buildDefaultDiscussion, xr as buildDefaultHeader, Cr as buildDefaultHighlights, Dr as buildDefaultPageSchema, Tr as buildDefaultTabs, gr as createMentionFromSuggestion, St as createSafeTranslationHook, br as deriveHighlightFields, Qn as deriveRecordPageHref, yr as deriveStages, vr as detectStatusField, yt as inferDetailColumns, on as isConcurrentUpdateError, vt as isWideFieldType, $ as useDetailTranslation };
5569
+ export { Rt as ActivityTimeline, En as CommentAttachment, mr as CommentInput, an as ConcurrentUpdateDialog, Ct as DETAIL_DEFAULT_TRANSLATIONS, Tt as DetailSection, Et as DetailTabs, tn as DetailView, ir as DiffView, hn as FieldChangeItem, At as HeaderHighlight, Gt as HistoryTimeline, $n as InlineCreateRelated, hr as MentionAutocomplete, pr as PointInTimeRestore, _n as ReactionPicker, Pn as RecordActivityRenderer, Mn as RecordActivityTimeline, Fn as RecordChatterPanel, Ln as RecordChatterRenderer, Mt as RecordComments, Zn as RecordDetailDrawer, ln as RecordDetailsRenderer, mn as RecordHighlightsRenderer, Un as RecordHistoryRenderer, Qt as RecordMetaFooter, ar as RecordNavigationEnhanced, zn as RecordPathRenderer, Vn as RecordQuickActionsRenderer, qn as RecordReferenceRailRenderer, fn as RecordRelatedListRenderer, Ot as RelatedList, dr as RelationshipGraph, Sn as RichTextCommentInput, kt as SectionGroup, bn as SubscriptionToggle, yn as ThreadedReplies, bt as applyAutoSpan, xt as applyDetailAutoLayout, Cr as buildDefaultActions, Tr as buildDefaultDetails, Dr as buildDefaultDiscussion, Sr as buildDefaultHeader, wr as buildDefaultHighlights, Or as buildDefaultPageSchema, Er as buildDefaultTabs, gr as createMentionFromSuggestion, St as createSafeTranslationHook, xr as deriveHighlightFields, Qn as deriveRecordPageHref, br as deriveStages, yr as detectStatusField, _r as extractMentions, yt as inferDetailColumns, on as isConcurrentUpdateError, vt as isWideFieldType, $ as useDetailTranslation };