@object-ui/app-shell 5.0.2 → 5.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/dist/chrome/CommandPalette.d.ts +6 -1
  3. package/dist/chrome/CommandPalette.js +40 -4
  4. package/dist/chrome/ConsoleToaster.js +8 -1
  5. package/dist/chrome/RouteFader.d.ts +32 -0
  6. package/dist/chrome/RouteFader.js +52 -0
  7. package/dist/chrome/index.d.ts +2 -0
  8. package/dist/chrome/index.js +2 -0
  9. package/dist/chrome/toast-helpers.d.ts +48 -0
  10. package/dist/chrome/toast-helpers.js +59 -0
  11. package/dist/console/AppContent.js +108 -2
  12. package/dist/console/home/HomePage.js +5 -4
  13. package/dist/console/marketplace/MarketplaceInstalledPage.d.ts +17 -0
  14. package/dist/console/marketplace/MarketplaceInstalledPage.js +64 -0
  15. package/dist/console/marketplace/MarketplacePackagePage.d.ts +7 -0
  16. package/dist/console/marketplace/MarketplacePackagePage.js +199 -0
  17. package/dist/console/marketplace/MarketplacePage.d.ts +8 -0
  18. package/dist/console/marketplace/MarketplacePage.js +94 -0
  19. package/dist/console/marketplace/PackageIcon.d.ts +19 -0
  20. package/dist/console/marketplace/PackageIcon.js +17 -0
  21. package/dist/console/marketplace/marketplaceApi.d.ts +122 -0
  22. package/dist/console/marketplace/marketplaceApi.js +207 -0
  23. package/dist/index.d.ts +5 -6
  24. package/dist/index.js +4 -5
  25. package/dist/layout/AppHeader.js +25 -21
  26. package/dist/layout/AppSidebar.js +15 -11
  27. package/dist/layout/InboxPopover.js +43 -3
  28. package/dist/types.d.ts +0 -46
  29. package/dist/views/ObjectView.js +20 -7
  30. package/dist/views/RecordDetailView.js +213 -45
  31. package/package.json +25 -25
  32. package/src/styles.css +49 -0
  33. package/dist/components/DashboardRenderer.d.ts +0 -8
  34. package/dist/components/DashboardRenderer.js +0 -16
  35. package/dist/components/FormRenderer.d.ts +0 -8
  36. package/dist/components/FormRenderer.js +0 -31
  37. package/dist/components/ObjectRenderer.d.ts +0 -8
  38. package/dist/components/ObjectRenderer.js +0 -74
  39. package/dist/components/PageRenderer.d.ts +0 -7
  40. package/dist/components/PageRenderer.js +0 -14
package/CHANGELOG.md CHANGED
@@ -1,5 +1,406 @@
1
1
  # @object-ui/app-shell — Changelog
2
2
 
3
+ ## 5.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 9ccda28: security: force DOMPurify to `^3.4.5` via pnpm override
8
+
9
+ Resolves 8 moderate-severity GHSA advisories against the transitive
10
+ `dompurify@3.2.7` pulled in by `monaco-editor`. Vulnerabilities covered:
11
+ - SAFE_FOR_TEMPLATES bypass in RETURN_DOM mode
12
+ - FORBID_TAGS bypassed by function-based ADD_TAGS predicate
13
+ - Prototype Pollution to XSS via CUSTOM_ELEMENT_HANDLING fallback
14
+ - ADD_TAGS function-form short-circuit bypass of FORBID_TAGS
15
+ - ADD_ATTR predicate skipping URI validation
16
+ - USE_PROFILES prototype pollution enabling event handlers
17
+ - mutation-XSS via Re-Contextualization
18
+ - Generic XSS vector
19
+
20
+ No API changes; override is transparent to consumers.
21
+ - @object-ui/types@5.2.1
22
+ - @object-ui/core@5.2.1
23
+ - @object-ui/i18n@5.2.1
24
+ - @object-ui/react@5.2.1
25
+ - @object-ui/components@5.2.1
26
+ - @object-ui/fields@5.2.1
27
+ - @object-ui/layout@5.2.1
28
+ - @object-ui/data-objectstack@5.2.1
29
+ - @object-ui/auth@5.2.1
30
+ - @object-ui/permissions@5.2.1
31
+ - @object-ui/collaboration@5.2.1
32
+ - @object-ui/providers@5.2.1
33
+
34
+ ## 5.2.0
35
+
36
+ ### Minor Changes
37
+
38
+ - 321294c: Cmd-K now shows recently viewed records in its empty state, sourced
39
+ from the existing cloud-synced `sys_user_preference` adapter (already
40
+ wired by `RecentItemsProvider` + `useTrackRouteAsRecent` +
41
+ `RecordDetailView`). Multi-device by construction: open a record on
42
+ laptop, see it in `⌘K → Recently viewed` on phone.
43
+ - Group renders only when input is empty (no competition with search).
44
+ - Limited to the 5 most recent record-type entries.
45
+ - New i18n key `console.commandPalette.recentRecords` (en + zh seeded;
46
+ other locales fall back to `defaultValue: "Recently viewed"`).
47
+
48
+ - b2d1704: feat(cmdk): record search across objects in the Command Palette
49
+ - New `useRecordSearch` hook in `@object-ui/react` debounces a query, fans out
50
+ to `dataSource.find(name, { $search, $top })` across candidate objects, and
51
+ aggregates hits. Race-safe via a monotonic runId; per-object 404s are
52
+ silently dropped via `Promise.allSettled`.
53
+ - `CommandPalette` (`@object-ui/app-shell`) now accepts a `dataSource` prop;
54
+ when supplied, the palette renders a `Records` group at the top with hits
55
+ scoped to the active app's nav objects. Item `value` embeds the live query
56
+ so cmdk's client-side filter doesn't hide async results.
57
+ - Added `console.commandPalette.records` i18n key (`Records` / `记录`).
58
+
59
+ - 921bd28: Console now honors `App.homePageId` for the bare `/console/apps/:appName`
60
+ landing route. Previously it always redirected to the first reachable nav
61
+ item, so CRM-style apps with KPI dashboards still landed users on the
62
+ first object list (e.g. Leads) rather than the configured home page.
63
+
64
+ The new `resolveLandingRoute` looks up the `homePageId` nav item, builds
65
+ its route (object / view / page / dashboard / report), and falls back to
66
+ the existing `findFirstRoute` only when no `homePageId` is set or it
67
+ resolves to a routeless item type.
68
+
69
+ - 3ebba63: Fix silent blank page on shorthand record deep-links.
70
+
71
+ Three related fixes that all addressed the same UX: a user follows a URL
72
+ shaped `/{object}/{recordId}` and sees a completely blank content area.
73
+ 1. **`useNavigationOverlay` produced the broken URL itself.** When
74
+ middle-click / Cmd-click opened a gallery card in a new tab and no
75
+ `onNavigate` was provided, the hook built `/{object}/{id}` — a URL
76
+ shape that does not match any route in the console route table. The
77
+ builder now emits the canonical `/{object}/record/{id}`.
78
+ 2. **Shorthand redirect for externally shared links.** Even with the
79
+ producer fixed, links pasted from email / Slack / older builds
80
+ still use the shorthand. The console now intercepts
81
+ `/{:objectName}/:maybeRecordId` and, when the second segment looks
82
+ like a record id (URL-safe slug ≥ 6 chars, not a reserved keyword),
83
+ redirects to `/{objectName}/record/{recordId}` preserving query and
84
+ hash.
85
+ 3. **Visible 404 fallback.** Routes that match nothing at all now
86
+ render an explicit "Page not found" empty state with a "Go back"
87
+ action instead of leaving the content area blank. Silent failures
88
+ are now visible failures.
89
+
90
+ - a4a0e1d: Add `<PresenceProvider>` abstraction with `useTenantPresence()` and
91
+ `useRecordPresence(objectName, recordId)` hooks. The default source is a
92
+ no-op so hooks return `[]` until a host app wires in a realtime
93
+ transport (WebSocket / SSE). Replaces the two architectural TODOs in
94
+ `AppHeader` (tenant scope) and `RecordDetailView` (record scope) that
95
+ were waiting on this abstraction.
96
+
97
+ `AppHeader` now falls back to `useTenantPresence()` when the
98
+ `presenceUsers` prop is omitted, and `RecordDetailView` renders
99
+ `<PresenceAvatars>` next to the lifecycle badge when other users are
100
+ viewing the same record. Both code paths render exactly as before when
101
+ no provider is mounted, so this change is non-visual for existing
102
+ consumers.
103
+
104
+ ### Patch Changes
105
+
106
+ - 9997cae: DataSource: add optional `bulkUpdate(resource, ids, patch)` for "same patch, many rows" interactions (Slack "mark all as read", Linear "archive selected"). The ObjectStack adapter routes to `POST /api/v1/data/:object/updateMany` so the client pays one HTTP/auth/RLS round-trip instead of N parallel PATCHes, eliminating mark-all-read jank on inboxes with 50+ unread.
107
+
108
+ AppHeader's `markAllRead` now prefers `bulkUpdate`, with a transparent fallback to the per-id loop for adapters that don't implement the helper.
109
+
110
+ - 0a644f0: feat(app-shell): CommandPalette searching indicator
111
+
112
+ When `useRecordSearch` is mid-flight (debounced fetch across objects
113
+ hasn't returned yet), the palette now surfaces a subtle visual:
114
+ - A small pulsing primary-coloured dot next to the **Records** group
115
+ heading, so the user sees that more results may still appear.
116
+ - A `Searching…` placeholder inside the empty state when the user has
117
+ typed something but no hits exist yet — replaces the static
118
+ "No results found." message until the request settles.
119
+
120
+ New i18n key `console.commandPalette.searching` (en + zh).
121
+
122
+ - 5f71924: feat(app-shell): better default toast UX in ConsoleToaster
123
+
124
+ `ConsoleToaster` now ships UX-positive defaults that match the Linear
125
+ / Notion pattern users expect from an enterprise console:
126
+ - `position="top-right"` — keeps the user's primary work area (centre
127
+ - bottom) unobstructed.
128
+ - `closeButton` — every toast has an explicit X so users can dismiss
129
+ rather than wait the duration out.
130
+ - `richColors` — type-aware coloured backgrounds (success / error /
131
+ warning / info) so the kind of message is legible at a glance.
132
+ - `expand` — toast stack expands on hover so users can read multiple
133
+ recent toasts without dismissing.
134
+ - `visibleToasts={4}` — prevents the corner from being overrun.
135
+ - `duration: 4000` — long enough to read + click an `Undo` action.
136
+
137
+ All of these are still overridable via `<ConsoleToaster …>` props.
138
+
139
+ - 5425608: CRM UX polish pass — calmer enterprise look across detail + kanban.
140
+ - **plugin-kanban**: column headers now use a 2px muted accent stripe with
141
+ neutral foreground titles + a quiet grey count pill instead of full
142
+ rainbow gradient + colored title + colored count. Pipeline boards
143
+ (Opportunity, Case, Task, Lead) look like Salesforce/Linear instead of
144
+ a toy. WIP-limit overflow remains destructive-red so urgency stays loud.
145
+ - **plugin-detail (`record:reference_rail`)**: new `hideEmpty` prop
146
+ (default true) collapses entries whose total === 0 into a single
147
+ `+ N empty (Quotes · Products …)` chip at the bottom of the rail.
148
+ Removes the 4–7 "No records" stack that dominated the aside.
149
+ - **plugin-detail (`record:path`)**: completed stages now render with an
150
+ emerald-tinted background + bold green check instead of low-contrast
151
+ `bg-muted text-muted-foreground` (which read as "light grey on white"
152
+ and was borderline unreadable).
153
+ - **app-shell (`RecordDetailView`)**: record-not-found short-circuit.
154
+ Previously a stale/missing recordId still rendered the page chrome
155
+ (rail, discussion, breadcrumb with the raw id), making invalid links
156
+ look like a partially broken page. Now renders a clean centered
157
+ `Empty` state with database icon + i18n'd "Record not found" copy.
158
+ - **i18n**: added `detail.showEmptyRelated_{one,other}` and
159
+ `empty.recordNotFound{,Description}` keys (en + zh).
160
+
161
+ - 710fbe6: feat(app-shell): notification center animation polish
162
+
163
+ InboxPopover now animates every signal that matters for "noticing":
164
+ - Bell button **bounces once** when total pressure increases (new
165
+ notification or approval arrives). Tracks previous total via a ref
166
+ so the very first render — when the server-side counts hydrate —
167
+ does not trigger a spurious bounce.
168
+ - Bell badge **zooms in** on every count change (re-keyed on
169
+ `totalBadge` so each transition is an independent animation).
170
+ - Per-tab counter badges (Notifications / Approvals) get the same
171
+ zoom-in treatment on count change.
172
+ - Notification list rows **fade + slide in from top** with a small
173
+ staggered delay (capped at 6×20ms so a full list never feels
174
+ laggy).
175
+ - Activity rows mirror the same fade/slide pattern.
176
+ - Empty states (`You're all caught up`, `No recent activity`, `No
177
+ pending approvals`) fade in instead of popping in.
178
+ - The unread dot (•) is now always rendered but fades its opacity
179
+ when `is_read` flips, instead of disappearing instantly — gives a
180
+ smooth "marked read" affordance.
181
+
182
+ All animations are wrapped in `motion-safe:` utility variants so
183
+ users with `prefers-reduced-motion` see the previous (instant) UI.
184
+ No new dependencies; reuses `tailwindcss-animate` utilities already
185
+ present in the design system.
186
+
187
+ - 7c441f5: End-to-end @-mention notifications.
188
+
189
+ `@object-ui/plugin-detail` now exports `extractMentions(text, suggestions)`
190
+ — a small utility that resolves `@<label>` tokens in a comment body to
191
+ user ids, using the same suggestion list that drives the in-editor
192
+ dropdown. Handles labels with spaces ("@QA Test"), CJK ("@王小明"),
193
+ longest-match disambiguation ("Anna Lee" wins over "Anna"), and ignores
194
+ unknown @-tokens. 9 unit tests.
195
+
196
+ `@object-ui/app-shell` `RecordDetailView` now:
197
+ 1. Serializes the resolved mention ids into `sys_comment.mentions`
198
+ (previously hard-coded `'[]'`, so servers had no idea who was being
199
+ pinged).
200
+ 2. Fan-outs a `sys_notification` row per mentioned recipient
201
+ (self-mentions are filtered as noise) with the canonical bell-inbox
202
+ shape: `type: 'mention'`, `recipient_id`, `actor_name`, `title`,
203
+ `body` preview (≤140 chars), `source_object`/`source_id`/
204
+ `source_comment_id`, `is_read: false`, `created_at`.
205
+
206
+ The notification write tolerates 404 silently, so deployments without
207
+ a notification collection degrade to the previous behavior (mention
208
+ text + highlight, no inbox row). Spec-compliant servers that emit
209
+ notifications via their own sys_comment after-create hook can ignore
210
+ the client-side write — the bell de-dupes by id at the polling layer.
211
+
212
+ - 072cad0: Always seed @-mention suggestions with the current user so the dropdown
213
+ appears even when the backend has no `sys_user` directory (or the fetch
214
+ fails). Hosts with a real user roster still get the merged list —
215
+ current user first, then directory entries de-duped by id.
216
+
217
+ Previously, typing `@` in the discussion comment box was a no-op on
218
+ example backends that don't serve `sys_user`, making the feature look
219
+ broken. Authors can now at minimum mention themselves; richer rosters
220
+ are merged in automatically when available.
221
+
222
+ - 54e3dfb: Remove unused stub renderers from `@object-ui/app-shell`:
223
+ - `ObjectRenderer` / `ObjectRendererProps`
224
+ - `DashboardRenderer` / `DashboardRendererProps`
225
+ - `PageRenderer` / `PageRendererProps`
226
+ - `FormRenderer` / `FormRendererProps`
227
+
228
+ These were placeholder components that never delegated to a real
229
+ SchemaRenderer — they rendered a literal `"TODO"` string and were not
230
+ consumed anywhere in the monorepo or in the official Console app.
231
+ Because they were non-functional, no working production code could
232
+ have depended on them; this is treated as a patch-level cleanup rather
233
+ than a semver-major break.
234
+
235
+ If you were importing one of the removed stubs (and somehow got past
236
+ the "TODO" placeholder render), the real renderers ship from the
237
+ respective plugin packages:
238
+ - Dashboard → `@object-ui/plugin-dashboard` (`DashboardRenderer`)
239
+ - Page / Object / Form → `@object-ui/react` (`SchemaRenderer`) +
240
+ `@object-ui/plugin-form` / `@object-ui/plugin-grid` etc.
241
+
242
+ - Updated dependencies [de0c5e6]
243
+ - Updated dependencies [9997cae]
244
+ - Updated dependencies [321294c]
245
+ - Updated dependencies [b2d1704]
246
+ - Updated dependencies [0a644f0]
247
+ - Updated dependencies [a3cb88f]
248
+ - Updated dependencies [5425608]
249
+ - Updated dependencies [6c3f018]
250
+ - Updated dependencies [d912a60]
251
+ - Updated dependencies [87bc8ff]
252
+ - Updated dependencies [3ebba63]
253
+ - Updated dependencies [e919433]
254
+ - Updated dependencies [a8d12ec]
255
+ - Updated dependencies [a4a0e1d]
256
+ - Updated dependencies [70b5570]
257
+ - Updated dependencies [aa063db]
258
+ - Updated dependencies [d9c3bae]
259
+ - Updated dependencies [d1442e3]
260
+ - Updated dependencies [7c7400a]
261
+ - Updated dependencies [b703480]
262
+ - Updated dependencies [e7b6eae]
263
+ - @object-ui/types@5.2.0
264
+ - @object-ui/data-objectstack@5.2.0
265
+ - @object-ui/core@5.2.0
266
+ - @object-ui/i18n@5.2.0
267
+ - @object-ui/react@5.2.0
268
+ - @object-ui/fields@5.2.0
269
+ - @object-ui/components@5.2.0
270
+ - @object-ui/collaboration@5.2.0
271
+ - @object-ui/layout@5.2.0
272
+ - @object-ui/auth@5.2.0
273
+ - @object-ui/permissions@5.2.0
274
+ - @object-ui/providers@5.2.0
275
+
276
+ ## 5.1.1
277
+
278
+ ### Patch Changes
279
+
280
+ - Updated dependencies [8955b9c]
281
+ - @object-ui/components@5.1.1
282
+ - @object-ui/fields@5.1.1
283
+ - @object-ui/layout@5.1.1
284
+ - @object-ui/types@5.1.1
285
+ - @object-ui/core@5.1.1
286
+ - @object-ui/i18n@5.1.1
287
+ - @object-ui/react@5.1.1
288
+ - @object-ui/data-objectstack@5.1.1
289
+ - @object-ui/auth@5.1.1
290
+ - @object-ui/permissions@5.1.1
291
+ - @object-ui/collaboration@5.1.1
292
+ - @object-ui/providers@5.1.1
293
+
294
+ ## 5.1.0
295
+
296
+ ### Minor Changes
297
+
298
+ - d1ec6a2: Fold inline-edit into the page-header overflow menu (HubSpot/Lightning
299
+ pattern) and remove the orphan "Edit fields" toolbar row that previously
300
+ floated between the tab strip and the first detail section.
301
+ - `@object-ui/app-shell` `RecordDetailView`: injects a new `sys_inline_edit`
302
+ system action that appears in the ⋯ overflow menu and dispatches a
303
+ `objectui:record:inline-edit-toggle` window CustomEvent (filtered by
304
+ recordId + objectName).
305
+ - `@object-ui/plugin-detail` `DetailView`: listens for that event to
306
+ toggle inline-edit mode; the in-page toolbar now renders only during
307
+ active editing / save error / locked states, so the idle layout flows
308
+ tabs → first section card with no orphan row.
309
+ - `@object-ui/components` layout containers: extended `KNOWN_LABEL_DICT`
310
+ with zh-CN + zh-TW translations for common CRM related-list labels
311
+ (Quotes / Products / Contacts / Accounts / Leads / Opportunities /
312
+ Cases / Campaigns / Approvals / Documents / Emails / Calls / Meetings
313
+ / Open Tasks / Closed Tasks), so authored English labels auto-translate
314
+ in `page:accordion` / `page:tabs` items.
315
+
316
+ - cf30cc2: Polish Lightning record detail page layout.
317
+ - `record:details` sections now render with Card chrome by default when a `title` is present, restoring visual grouping that was missing on pages like the opportunity detail page.
318
+ - Section labels can be translated via the `{ns}.objects.{objectName}._sections.{name}.label` convention. Author each section with a stable `name` (e.g. `info`, `forecast`) and the renderer picks up the locale-specific label automatically. Falls back to the literal `label` when no translation exists.
319
+ - The `page:header` action toolbar now collapses into a `⋯` overflow menu when more than two actions are present. The first business action stays inline; secondary system actions (Edit / Share / Delete) move into the menu, with destructive styling applied to Delete.
320
+ - Header action labels resolve via the `{ns}.objects.{objectName}._actions.{name}.label` convention.
321
+ - Removed the meaningless field-count Badge from collapsible section headers (the `2` chip next to "Description"). Field-count metadata wasn't useful in the header and added visual noise.
322
+ - Synth-path `sys_delete` now carries `variant: 'destructive'` so the overflow menu can color it appropriately.
323
+
324
+ - c0b236f: Platform detail/form polish:
325
+ - **Auto-section grouping**: When an object has no authored `views.form.sections`, the detail page now splits fields into a primary section and a collapsible "More details" section based on a field-type/name heuristic (textarea / markdown / description / notes / remarks). Eliminates the wall-of-fields layout on objects without explicit detail metadata.
326
+ - **FormSection card chrome**: `FormSection` now accepts `showBorder`. Defaults to `true` for titled sections (Card wrapper) and `false` for untitled sections (flat). Same auto-default already applied to `DetailSection`.
327
+ - **Origin breadcrumb**: Navigating from a list/kanban into a record now records the source view; the detail page shows a `← <view label>` back-link above the page header.
328
+ - New i18n key `detail.sectionMoreDetails` (en + zh-CN).
329
+
330
+ ### Patch Changes
331
+
332
+ - d51a577: feat(platform): Discussion attachments + @mention directory + Reference Rail aside
333
+ - **Discussion attachments** — `RichTextCommentInput` now accepts an `extraSlot`
334
+ and a `canSubmitEmpty` flag so hosts can mount the existing
335
+ `CommentAttachment` composer beneath the editor without forking the toolbar.
336
+ `RecordActivityTimeline` plumbs the attachments through
337
+ `DiscussionContext.onUploadAttachments` and submits attachment-only comments.
338
+ - **@mention directory** — `DiscussionContext` gains a `mentionSuggestions`
339
+ field; `RecordDetailView` populates it from the host `sys_user` collection so
340
+ `@` autocomplete in the composer now resolves against real users.
341
+ - **Reference Rail** — New `record:reference_rail` renderer + a dedicated
342
+ `aside` region emitted by `buildDefaultPageSchema` whenever a record has
343
+ ≥ 2 related lists. The rail surfaces a Salesforce/HubSpot-style snapshot
344
+ of related collections (count badge + top 3 records) on `xl+` viewports.
345
+ - **Layout** — `PageRenderer`'s structured-layout `<aside>` wrappers now honor
346
+ `aside.className`, letting schemas attach responsive utilities like
347
+ `hidden xl:flex` to the rail region.
348
+
349
+ - 1976691: Fix the drawer "Open as full page" (maximize) button on the record drawer
350
+ which threw `TypeError: name.indexOf is not a function` and prevented
351
+ navigation to the dedicated detail page.
352
+ - `@object-ui/app-shell` `ObjectView`: pass `objectDef.name` (string) — not
353
+ the whole `objectDef` — into `viewLabel(...)` when computing the
354
+ `originState.from.label` for both drawer-navigate and list-navigate
355
+ flows. Two call sites fixed.
356
+ - `@object-ui/i18n` `useObjectLabel`: harden `stripNamespace` so it
357
+ tolerates non-string inputs and returns an empty string instead of
358
+ throwing, providing a safety net for similar future regressions.
359
+
360
+ - a49f300: feat(detail): per-object Reference Rail opt-out via `objectDef.detail.hideReferenceRail`
361
+
362
+ The Record-detail Reference Rail (right-hand related-list summary cards)
363
+ can now be suppressed on a per-object basis without authoring a full
364
+ custom `Page`. Catalog-style objects (Product, Task) ship with the rail
365
+ off by default; hub objects (Account, Opportunity, Contact, Case) keep it
366
+ on.
367
+ - `RecordDetailView` now reads `(objectDef as any)?.detail?.hideReferenceRail`
368
+ and `…?.hideRelatedTab` and threads them to `buildDefaultPageSchema`.
369
+ - The Reference Rail renderer also accepts entries authored as either a
370
+ flat `entries` array or nested under `properties.entries`, so explicit
371
+ `Page` authors can opt-in via the standard spec shape.
372
+ - See `packages/plugin-detail/README.md` (Reference Rail decision matrix)
373
+ for the rationale and per-object guidance.
374
+
375
+ - e9767b0: Remove dead `sys_presence` REST probes from `RecordDetailView` and `AppHeader`. Real-time
376
+ presence does not belong in a regular REST collection — the feature is being redesigned
377
+ behind a transport-level `<PresenceProvider>` (see ROADMAP). This change removes the
378
+ probe (and associated state / unused UI mounts) so the browser no longer makes silently
379
+ swallowed 404 requests on every record open / app navigation. UI surface area is
380
+ unchanged for end users (the previous code never rendered viewers when the probe failed).
381
+ - Updated dependencies [bd8447d]
382
+ - Updated dependencies [fbd5052]
383
+ - Updated dependencies [d51a577]
384
+ - Updated dependencies [1976691]
385
+ - Updated dependencies [d1ec6a2]
386
+ - Updated dependencies [cf30cc2]
387
+ - Updated dependencies [5b80cfd]
388
+ - Updated dependencies [49b1760]
389
+ - Updated dependencies [c0b236f]
390
+ - Updated dependencies [d548d6b]
391
+ - @object-ui/components@5.1.0
392
+ - @object-ui/react@5.1.0
393
+ - @object-ui/i18n@5.1.0
394
+ - @object-ui/types@5.1.0
395
+ - @object-ui/core@5.1.0
396
+ - @object-ui/data-objectstack@5.1.0
397
+ - @object-ui/fields@5.1.0
398
+ - @object-ui/layout@5.1.0
399
+ - @object-ui/auth@5.1.0
400
+ - @object-ui/collaboration@5.1.0
401
+ - @object-ui/permissions@5.1.0
402
+ - @object-ui/providers@5.1.0
403
+
3
404
  ## 5.0.2
4
405
 
5
406
  ### Patch Changes
@@ -11,6 +11,11 @@ interface CommandPaletteProps {
11
11
  activeApp: any;
12
12
  objects: any[];
13
13
  onAppChange: (name: string) => void;
14
+ /**
15
+ * Optional data source used to power record search across objects. When
16
+ * omitted, the palette behaves exactly as before — nav items only.
17
+ */
18
+ dataSource?: any;
14
19
  }
15
- export declare function CommandPalette({ apps, activeApp, objects: _objects, onAppChange }: CommandPaletteProps): import("react/jsx-runtime").JSX.Element;
20
+ export declare function CommandPalette({ apps, activeApp, objects, onAppChange, dataSource }: CommandPaletteProps): import("react/jsx-runtime").JSX.Element;
16
21
  export {};
@@ -7,17 +7,20 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
7
7
  *
8
8
  * Uses Shadcn's Command (cmdk) component — keyboard-accessible, fuzzy search.
9
9
  */
10
- import { useEffect, useState, useCallback } from 'react';
10
+ import { useEffect, useState, useCallback, useMemo } from 'react';
11
11
  import { useNavigate, useParams } from 'react-router-dom';
12
12
  import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from '@object-ui/components';
13
13
  import { LayoutDashboard, FileText, BarChart3, Moon, Sun, Monitor, Search, Plus, } from 'lucide-react';
14
+ import { useRecordSearch } from '@object-ui/react';
14
15
  import { useTheme } from './ThemeProvider';
15
16
  import { useExpressionContext, evaluateVisibility } from '../providers/ExpressionProvider';
16
17
  import { useObjectTranslation } from '@object-ui/i18n';
17
- import { resolveI18nLabel } from '../utils';
18
+ import { resolveI18nLabel, getRecordDisplayName } from '../utils';
18
19
  import { getIcon } from '../utils/getIcon';
19
- export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange }) {
20
+ import { useRecentItems } from '../context/RecentItemsProvider';
21
+ export function CommandPalette({ apps, activeApp, objects, onAppChange, dataSource }) {
20
22
  const [open, setOpen] = useState(false);
23
+ const [inputValue, setInputValue] = useState('');
21
24
  const navigate = useNavigate();
22
25
  const { appName } = useParams();
23
26
  const { setTheme } = useTheme();
@@ -34,6 +37,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange
34
37
  document.addEventListener('keydown', down);
35
38
  return () => document.removeEventListener('keydown', down);
36
39
  }, []);
40
+ // Reset query when the palette closes so reopening doesn't show stale state.
41
+ useEffect(() => {
42
+ if (!open)
43
+ setInputValue('');
44
+ }, [open]);
37
45
  const baseUrl = `/apps/${appName || activeApp?.name}`;
38
46
  const runCommand = useCallback((command) => {
39
47
  setOpen(false);
@@ -41,7 +49,35 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange
41
49
  }, []);
42
50
  // Extract navigation items from active app, filtering by visibility expressions
43
51
  const navItems = flattenNavigation(activeApp?.navigation || []).filter((item) => evaluateVisibility(item.visible ?? item.visibleOn, evaluator));
44
- return (_jsxs(CommandDialog, { open: open, onOpenChange: setOpen, children: [_jsx(CommandInput, { placeholder: t('console.commandPalette.placeholder') }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: t('console.commandPalette.noResults') }), navItems.filter(i => i.type === 'object').length > 0 && (_jsx(CommandGroup, { heading: t('console.commandPalette.objects'), children: navItems
52
+ // Whitelist of object names visible in this app's nav used as the search
53
+ // scope so we don't fan out to every object in the tenant.
54
+ const searchableObjectNames = useMemo(() => navItems
55
+ .filter((i) => i.type === 'object' && typeof i.objectName === 'string')
56
+ .map((i) => i.objectName),
57
+ // navItems is rebuilt every render (filtered list); use a stable signature.
58
+ [activeApp?.name, navItems.map((i) => i.objectName || '').join('|')]);
59
+ const { results: recordHits, isSearching } = useRecordSearch({
60
+ query: inputValue,
61
+ objects,
62
+ dataSource,
63
+ objectNames: searchableObjectNames,
64
+ enabled: open && Boolean(dataSource),
65
+ getDisplayName: getRecordDisplayName,
66
+ });
67
+ // Cloud-synced (sys_user_preference) recently-visited records,
68
+ // surfaced in the empty state so the palette is useful before the
69
+ // user types anything. Filtered down to record-type entries so we
70
+ // don't double up with the per-app nav above.
71
+ const { recentItems } = useRecentItems();
72
+ const recentRecords = useMemo(() => recentItems.filter((it) => it.type === 'record').slice(0, 5), [recentItems]);
73
+ const showRecentRecords = open && inputValue.trim().length === 0 && recentRecords.length > 0;
74
+ return (_jsxs(CommandDialog, { open: open, onOpenChange: setOpen, children: [_jsx(CommandInput, { placeholder: t('console.commandPalette.placeholder'), value: inputValue, onValueChange: setInputValue }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: isSearching ? (_jsxs("span", { className: "inline-flex items-center gap-2 text-muted-foreground", children: [_jsx("span", { "aria-hidden": true, className: "inline-block h-1.5 w-1.5 rounded-full bg-primary motion-safe:animate-pulse" }), t('console.commandPalette.searching', { defaultValue: 'Searching…' })] })) : (t('console.commandPalette.noResults')) }), showRecentRecords && (_jsx(CommandGroup, { heading: t('console.commandPalette.recentRecords', { defaultValue: 'Recently viewed' }), children: recentRecords.map((item) => (_jsxs(CommandItem, { value: `recent ${item.label} ${item.id}`, onSelect: () => runCommand(() => navigate(item.href)), children: [_jsx(Search, { className: "mr-2 h-4 w-4" }), _jsx("span", { className: "truncate", children: item.label })] }, `recent:${item.id}`))) })), recordHits.length > 0 && (_jsx(CommandGroup, { heading: _jsxs("span", { className: "inline-flex items-center gap-2", children: [t('console.commandPalette.records', { defaultValue: 'Records' }), isSearching && (_jsx("span", { "aria-hidden": true, className: "inline-block h-1.5 w-1.5 rounded-full bg-primary motion-safe:animate-pulse" }))] }), children: recordHits.map((hit) => {
75
+ const Icon = getIcon(hit.icon);
76
+ return (_jsxs(CommandItem, {
77
+ // Embed the live query so cmdk's client-side filter doesn't
78
+ // hide async hits that don't textually match the input.
79
+ value: `record ${inputValue} ${hit.display} ${hit.objectLabel} ${hit.objectName} ${hit.recordId}`, onSelect: () => runCommand(() => navigate(`${baseUrl}/${hit.objectName}/record/${hit.recordId}`)), children: [_jsx(Icon, { className: "mr-2 h-4 w-4" }), _jsx("span", { className: "truncate", children: hit.display }), _jsx("span", { className: "ml-auto text-xs text-muted-foreground", children: hit.objectLabel })] }, `${hit.objectName}:${hit.recordId}`));
80
+ }) })), navItems.filter(i => i.type === 'object').length > 0 && (_jsx(CommandGroup, { heading: t('console.commandPalette.objects'), children: navItems
45
81
  .filter(i => i.type === 'object')
46
82
  .map(item => {
47
83
  const Icon = getIcon(item.icon);
@@ -11,13 +11,20 @@ import { CircleCheck, Info, LoaderCircle, OctagonX, TriangleAlert } from 'lucide
11
11
  import { useTheme } from './ThemeProvider';
12
12
  export function ConsoleToaster(props) {
13
13
  const { theme = 'system' } = useTheme();
14
- return (_jsx(Sonner, { theme: theme, className: "toaster group", icons: {
14
+ return (_jsx(Sonner, { theme: theme, className: "toaster group",
15
+ // UX defaults chosen for an enterprise console — match the Linear /
16
+ // Notion pattern users expect. Callers can still override any of
17
+ // these via the spread `{...props}` below.
18
+ position: "top-right", closeButton: true, richColors: true, expand: true, visibleToasts: 4, icons: {
15
19
  success: _jsx(CircleCheck, { className: "h-4 w-4" }),
16
20
  info: _jsx(Info, { className: "h-4 w-4" }),
17
21
  warning: _jsx(TriangleAlert, { className: "h-4 w-4" }),
18
22
  error: _jsx(OctagonX, { className: "h-4 w-4" }),
19
23
  loading: _jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }),
20
24
  }, toastOptions: {
25
+ // 4s default keeps actionable toasts visible long enough to
26
+ // click an Undo button without feeling sticky.
27
+ duration: 4000,
21
28
  classNames: {
22
29
  toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
23
30
  description: 'group-[.toast]:text-muted-foreground',
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * `RouteFader` — light-touch fade-in animation that replays whenever
10
+ * the route pathname changes.
11
+ *
12
+ * Why this implementation choice:
13
+ * The textbook approach (`<div key={pathname}>`) would remount every
14
+ * page on navigation. That breaks scroll position, loses form state,
15
+ * and re-fetches data that didn't need to refetch.
16
+ *
17
+ * Instead we keep the wrapper stable and replay the CSS animation by
18
+ * manipulating className directly via a layout effect: strip the
19
+ * animation classes, force a reflow, then re-add them. The browser
20
+ * restarts the animation against the same DOM node. React's VDOM
21
+ * doesn't see the temporary class swap — it's pure DOM choreography
22
+ * — so children are never remounted.
23
+ *
24
+ * The animation is gated on `motion-safe:` so users with
25
+ * `prefers-reduced-motion: reduce` see hard page swaps.
26
+ */
27
+ import * as React from 'react';
28
+ export interface RouteFaderProps {
29
+ children: React.ReactNode;
30
+ className?: string;
31
+ }
32
+ export declare function RouteFader({ children, className }: RouteFaderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * ObjectUI
4
+ * Copyright (c) 2024-present ObjectStack Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+ /**
10
+ * `RouteFader` — light-touch fade-in animation that replays whenever
11
+ * the route pathname changes.
12
+ *
13
+ * Why this implementation choice:
14
+ * The textbook approach (`<div key={pathname}>`) would remount every
15
+ * page on navigation. That breaks scroll position, loses form state,
16
+ * and re-fetches data that didn't need to refetch.
17
+ *
18
+ * Instead we keep the wrapper stable and replay the CSS animation by
19
+ * manipulating className directly via a layout effect: strip the
20
+ * animation classes, force a reflow, then re-add them. The browser
21
+ * restarts the animation against the same DOM node. React's VDOM
22
+ * doesn't see the temporary class swap — it's pure DOM choreography
23
+ * — so children are never remounted.
24
+ *
25
+ * The animation is gated on `motion-safe:` so users with
26
+ * `prefers-reduced-motion: reduce` see hard page swaps.
27
+ */
28
+ import * as React from 'react';
29
+ import { useLocation } from 'react-router-dom';
30
+ const ANIM_CLASSES = ['motion-safe:animate-in', 'motion-safe:fade-in-0', 'motion-safe:duration-150'];
31
+ export function RouteFader({ children, className }) {
32
+ const location = useLocation();
33
+ const ref = React.useRef(null);
34
+ const prevPath = React.useRef(location.pathname);
35
+ React.useLayoutEffect(() => {
36
+ if (prevPath.current === location.pathname)
37
+ return;
38
+ prevPath.current = location.pathname;
39
+ const el = ref.current;
40
+ if (!el)
41
+ return;
42
+ // Drop the animation classes, force a reflow read, then re-add
43
+ // them. This is the canonical CSS "restart animation" trick.
44
+ el.classList.remove(...ANIM_CLASSES);
45
+ // Reading `offsetWidth` forces layout, which is what tells the
46
+ // browser to flush the previous animation state.
47
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
48
+ el.offsetWidth;
49
+ el.classList.add(...ANIM_CLASSES);
50
+ }, [location.pathname]);
51
+ return (_jsx("div", { ref: ref, className: [...ANIM_CLASSES, className ?? ''].filter(Boolean).join(' '), children: children }));
52
+ }
@@ -3,6 +3,8 @@ export { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
3
3
  export { OnboardingWalkthrough } from './OnboardingWalkthrough';
4
4
  export { ConditionalAuthWrapper } from './ConditionalAuthWrapper';
5
5
  export { ConsoleToaster } from './ConsoleToaster';
6
+ export { RouteFader } from './RouteFader';
6
7
  export { ErrorBoundary } from './ErrorBoundary';
7
8
  export { LoadingScreen } from './LoadingScreen';
8
9
  export { ThemeProvider, useTheme } from './ThemeProvider';
10
+ export { toastWithUndo, type ToastWithUndoOptions } from './toast-helpers';
@@ -3,6 +3,8 @@ export { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
3
3
  export { OnboardingWalkthrough } from './OnboardingWalkthrough';
4
4
  export { ConditionalAuthWrapper } from './ConditionalAuthWrapper';
5
5
  export { ConsoleToaster } from './ConsoleToaster';
6
+ export { RouteFader } from './RouteFader';
6
7
  export { ErrorBoundary } from './ErrorBoundary';
7
8
  export { LoadingScreen } from './LoadingScreen';
8
9
  export { ThemeProvider, useTheme } from './ThemeProvider';
10
+ export { toastWithUndo } from './toast-helpers';