@object-ui/react 17.5.0 → 17.6.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +334 -0
  2. package/README.md +9 -8
  3. package/dist/LazyPluginLoader.d.ts +14 -7
  4. package/dist/LazyPluginLoader.js +14 -7
  5. package/dist/SchemaRenderer.js +434 -22
  6. package/dist/context/HostNavigationContext.d.ts +82 -0
  7. package/dist/context/HostNavigationContext.js +65 -0
  8. package/dist/context/RelatedRecordActionsContext.d.ts +15 -0
  9. package/dist/context/index.d.ts +13 -12
  10. package/dist/context/index.js +13 -12
  11. package/dist/element-data-source/ElementDataSourceGate.d.ts +75 -2
  12. package/dist/element-data-source/ElementDataSourceGate.js +81 -2
  13. package/dist/hooks/index.d.ts +35 -35
  14. package/dist/hooks/index.js +35 -35
  15. package/dist/hooks/useActionEngine.js +1 -1
  16. package/dist/hooks/useCapabilityGate.js +6 -5
  17. package/dist/hooks/useClientNotifications.js +2 -2
  18. package/dist/hooks/useCrudShortcuts.js +1 -1
  19. package/dist/hooks/useDatasetDimensionLabels.js +1 -1
  20. package/dist/hooks/useDiscovery.js +1 -1
  21. package/dist/hooks/useElementDataSource.js +5 -2
  22. package/dist/hooks/useExpression.d.ts +24 -3
  23. package/dist/hooks/useExpression.js +56 -4
  24. package/dist/hooks/useNavigationOverlay.d.ts +1 -1
  25. package/dist/hooks/usePageAssignment.js +1 -1
  26. package/dist/hooks/usePageTransition.js +1 -1
  27. package/dist/hooks/usePageVariableActionBridge.js +2 -2
  28. package/dist/hooks/useViewData.js +1 -1
  29. package/dist/hooks/useViewTransition.js +1 -1
  30. package/dist/index.d.ts +11 -11
  31. package/dist/index.js +10 -10
  32. package/dist/schema-input.d.ts +16 -5
  33. package/dist/schema-input.js +15 -4
  34. package/dist/spec-bridge/SpecBridge.d.ts +1 -1
  35. package/dist/spec-bridge/SpecBridge.js +2 -2
  36. package/dist/spec-bridge/bridges/form-view.d.ts +99 -23
  37. package/dist/spec-bridge/bridges/form-view.js +51 -7
  38. package/dist/spec-bridge/bridges/list-view.d.ts +1 -1
  39. package/dist/spec-bridge/bridges/list-view.js +37 -10
  40. package/dist/spec-bridge/index.d.ts +4 -4
  41. package/dist/spec-bridge/index.js +3 -3
  42. package/dist/utils/error-message.d.ts +59 -0
  43. package/dist/utils/error-message.js +116 -1
  44. package/dist/utils/unevaluatedExpression.d.ts +69 -0
  45. package/dist/utils/unevaluatedExpression.js +194 -0
  46. package/dist/utils/visibilityDiagnostic.d.ts +119 -0
  47. package/dist/utils/visibilityDiagnostic.js +284 -0
  48. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,339 @@
1
1
  # @object-ui/react
2
2
 
3
+ ## 17.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f1d4748: Remove the retired `striped` / `bordered` / `virtualScroll` list-view surface
8
+
9
+ objectstack#7176 retired `list.striped`, `list.bordered` and `list.virtualScroll`
10
+ from the spec after measuring every objectui reader as pass-through: each one
11
+ copied the key onward and no renderer ever applied it. objectui stops declaring,
12
+ typing and forwarding them.
13
+
14
+ Off the chain: the `@objectstack/spec` list-view bridge in `@object-ui/react`,
15
+ `ListView`'s child-view props in `@object-ui/plugin-list`, both `ObjectView`
16
+ relays (`@object-ui/plugin-view` and `@object-ui/app-shell`), the `ObjectGridSchema`
17
+ and `NamedListView` declarations in `@object-ui/types` (interface and zod),
18
+ `ObjectGrid.component.yml` in `@object-ui/components`, and the page-block
19
+ inspector's `striped` / `bordered` toggles in the metadata-admin designer.
20
+
21
+ Behaviour is unchanged: nothing read these keys, so nothing rendered differently
22
+ for them. Stored view metadata that still carries one keeps validating — the keys
23
+ are simply no longer relayed. `ListViewSchema` continues to take the spec's
24
+ list-view fields by reference, so the protocol's own retirement tombstones
25
+ arrive with the next `@objectstack/spec` bump and reject the keys at the
26
+ authoring boundary. Restoring any of the three as live surface requires an
27
+ implementation card filed first, per the ruling.
28
+ - 8c0d52e: A form's ruled `submitBehavior.url` redirect can now be performed by the HOST, so a destination stays inside a console mounted at a sub-path (objectui#4989 defect 4).
29
+
30
+ `ObjectForm` and `WizardForm` accept a relative in-app path and used to travel to it with `window.location.assign`. A rooted path resolves against the ORIGIN there, so under a mounted host — `<BrowserRouter basename="/_console">`, which the framework CLI configures for every embedded deployment — an authored `/thanks` left the application. The destination was correct and the navigation was wrong, and a published renderer cannot fix that alone: only the host knows its mount.
31
+
32
+ **New: `HostNavigationContext` (`@object-ui/react`).** A host offers its own navigate; a renderer uses it when it is there:
33
+
34
+ ```tsx
35
+ import { HostNavigationProvider } from '@object-ui/react';
36
+ import { useNavigate } from 'react-router-dom';
37
+
38
+ function Bridge({ children }) {
39
+ const navigate = useNavigate();
40
+ return (
41
+ <HostNavigationProvider value={{ navigate: (to, o) => navigate(to, { replace: o?.replace ?? false }) }}>
42
+ {children}
43
+ </HostNavigationProvider>
44
+ );
45
+ }
46
+ ```
47
+
48
+ `@object-ui/app-shell`'s `ConsoleShell` now mounts that bridge above every console route, so the console's own routes, and its basename, are what a post-submit redirect resolves against — and the redirect becomes an SPA transition instead of a full page load.
49
+
50
+ **Nothing changes for a host that wires nothing.** With no provider the behaviour is byte-for-byte what it was: one `window.location.assign` of the resolved path after the declared `delayMs`. A host with no router has no basename, so origin-rooted resolution is already right there — the seam changes what a MOUNTED host gets and nothing else. `useHostNavigation()` outside a provider answers `{ navigate: undefined }` and never throws.
51
+
52
+ Two mechanisms were weighed and rejected by the ruling, recorded so they are not re-proposed. **Reading React Router's context when a router happens to be present** is implicit, and unavailable anyway: a React context is a module-instance object, so reading the host's means importing `react-router` into the renderer — which `@object-ui/plugin-form` declares in none of its dependency fields, whose published build externalises every bare specifier, and which two real consumers (`apps/site`, `packages/plugin-view`) do not install. **Requiring `react-router` as a peer** is the honest version of the same thing and costs the package its property of dropping into any React application.
53
+
54
+ What the host takes on by supplying a navigate: the destination becomes a client-side transition, so an in-app path with no matching route renders the host's not-found instead of a full page load. That is the host's routing table to answer for, which is why the choice is the host's. The contract verdict is unchanged either way — a destination `@objectstack/spec` refuses is refused identically with or without a seam, so an injected navigate can never launder a value the authoring door rejects.
55
+
56
+ `navigateOnSuccess` is a different declared key with its own open contract question (objectui#5034) and is deliberately untouched here.
57
+ - dbbd38a: fix(react): `bridgeListView` emits the column spelling the spec declares
58
+
59
+ `mapColumn` took a spec-canonical `ListColumn` — whose columns are **already**
60
+ spelled `field` / `label` — and down-translated every one of them to
61
+ `{ accessorKey, header }` before emitting the `object-grid` node, which
62
+ `ObjectGrid` then translated back. A round trip through a spelling
63
+ `ListColumnSchema` refuses by name, on a value that arrived canonical. The
64
+ bridge now forwards the declared shape, and the tolerance branch on the other
65
+ side retires in the same release (see `@object-ui/plugin-grid`).
66
+
67
+ **Output shape.** `bridgeListView` / `SpecBridge.transformListView` emit
68
+ `columns: [{ field, label?, … }]`. Code reading `node.columns[i].accessorKey`
69
+ off a bridged node reads `field` instead; `header` becomes `label`. The bare
70
+ string shorthand `columns: ['name']` now maps to `{ field: 'name' }`.
71
+
72
+ **No label is invented any more.** `header: col.label ?? col.field` turned "the
73
+ author declared no label" into "the author declared the machine name", and that
74
+ synthesized value pre-empted `ObjectGrid`'s own header chain — the column's
75
+ label, then the **object field's** label, then the prettified machine name —
76
+ whose middle step exists so a localized field label wins on a non-English app.
77
+ A bridged view therefore rendered raw machine names where a directly authored
78
+ `object-grid` rendered the field's real label. A bare `{ field }` column now
79
+ reaches that chain intact.
80
+
81
+ Speaking the declared spelling also routes bridged views through the renderer's
82
+ full ListColumn path rather than its type-inference-only one: object-schema
83
+ field enrichment, `hidden` filtering, primary-field auto-linking, and per-column
84
+ `link` / `action` handling now apply to a bridged `ListView` exactly as they do
85
+ to an authored grid.
86
+
87
+ ### Patch Changes
88
+
89
+ - 516663d: RecordAttachmentsPanel no longer offers a Retry for an api-disabled `sys_attachment` read.
90
+
91
+ `OBJECT_API_DISABLED` (404, `enable.apiEnabled: false`) and its sibling
92
+ `OBJECT_API_METHOD_NOT_ALLOWED` (405, the operation is absent from
93
+ `enable.apiMethods`) are pure functions of the object's metadata — no user, no
94
+ session, no request body — so every retry of every persona re-fetches the
95
+ identical refusal. Before this change both landed in `RecordAttachmentsPanel`'s
96
+ `unavailable` state and offered a Retry that was guaranteed to change nothing,
97
+ the same wrong advice `ListView`'s error panel already stops giving for list
98
+ reads.
99
+
100
+ The panel gains a fifth status, `api-unavailable`: no Retry button, and honest
101
+ copy ("The attachments list is not available on this object.", new
102
+ `detail.attachmentsApiUnavailable` key in all ten locale packs) instead of
103
+ "We couldn't load the attachments for this record." The pre-existing `denied`
104
+ (authorization) and `unavailable` (network/5xx/expired-session) states and
105
+ their affordances are unchanged.
106
+
107
+ `ListView.classifyLoadError` — the classifier that already separated this case
108
+ into its own `api-disabled` kind for list views — is lifted out of
109
+ `packages/plugin-list/src/ListView.tsx`'s module scope into
110
+ `@object-ui/react` (`classifyLoadError`, `LoadErrorKind`), so both surfaces
111
+ consume one classification instead of `RecordAttachmentsPanel` re-deriving it.
112
+ `ListView`'s own behavior is unchanged — it now imports the function it
113
+ previously defined locally. The classifier delegates its api-disabled check to
114
+ `isApiAccessDeniedError` (`@object-ui/data-objectstack`), removing a second,
115
+ independently-maintained copy of the same code list.
116
+ - d374caf: SpecBridge's form-view bridge stops reading `defaultSort` and `aria`, two keys spec 17
117
+ retired on the FormView carrier (`retiredKey()` tombstones — authoring either is a parse
118
+ error). Both guards were unreachable for any FormView that passed validation, and both
119
+ ends were measured dead before removal: no form renderer reads either off the node
120
+ (`plugin-form` reads neither, and `SchemaRenderer`'s ARIA injection resolves flat
121
+ `ariaLabel`/`ariaDescribedBy`/`role`, never a nested `aria` object). Behaviour for
122
+ spec-valid metadata is unchanged; a host that fed the exported bridge a raw pre-17
123
+ document no longer gets these two keys copied onto a node slot nothing consumed. The
124
+ LIST view's `aria` pass-through is untouched — that carrier stayed live and is applied by
125
+ `ListView`.
126
+ - af5e292: Emit explicit file extensions on relative import specifiers, so the published
127
+ entries can be imported by Node's own ESM resolver.
128
+
129
+ `@object-ui/react`'s built entry re-exported through extensionless relative
130
+ specifiers (`export * from './SchemaRenderer'`). Node does not extension-search
131
+ relative specifiers, so `import('@object-ui/react')` under plain Node — an SSR
132
+ host, or any consumer without a bundler — failed with `ERR_MODULE_NOT_FOUND`.
133
+ Bundled consumers were never affected and are unchanged by this.
134
+
135
+ `@object-ui/types`, `@object-ui/core` and `@object-ui/i18n` carried the same
136
+ emission; `@object-ui/react`'s entry stayed unloadable until they were fixed
137
+ too, because evaluation crosses into them. No exported API changed.
138
+ - 0046d8f: Evaluate expressions written under a node's `properties`, not just under `props`
139
+
140
+ `properties` is the spec spelling of a node's config bag and `props` is the
141
+ legacy alias, but `SchemaRenderer`'s evaluation memo only ran the expression
142
+ evaluator over `props`. Renderers in the `element:*` namespace read
143
+ `schema.properties` first, so a node written the canonical way handed the
144
+ renderer the raw `${…}` source and rendered it verbatim, while the same node
145
+ written with the alias evaluated correctly — writing the spec-compliant form
146
+ was the way to lose your expressions.
147
+
148
+ `schema.properties` values are now evaluated per value, exactly as `props`
149
+ already was, and the evaluation runs before the existing
150
+ `properties`-to-top-level hoist so a key means the same thing whether it is read
151
+ as `schema.properties.x`, as `schema.x`, or as the spread `x` React prop.
152
+ Evaluation stays shallow on both spellings (nested objects and arrays are passed
153
+ through, not walked), and `properties` keeps its precedence over `props`.
154
+ - 70a774b: A node writing both `properties` and `props` now gets ONE answer per key, and it is the canonical `properties` one — on both read channels.
155
+
156
+ `properties` is the spec spelling of a node's config bag and `props` is the
157
+ annotated legacy alias, but which one actually reached the screen depended on
158
+ how the receiving renderer happened to read it — and the two channels disagreed
159
+ in opposite directions:
160
+
161
+ - **config bag** (`schema.properties.x`) — the `element:*` family's
162
+ `readProps()` merges `{ ...schema.props, ...schema.properties }`, so
163
+ `properties` won.
164
+ - **React prop** (`x` arriving as a prop) — `SchemaRenderer`'s `createElement`
165
+ spread the hoisted `properties.*` values first and then
166
+ `...(evaluatedSchema.props || {})` last, which overwrote them, so `props` won.
167
+
168
+ Measured on one render of one such node: the bag read `FROM_PROPERTIES` while
169
+ the same key read as a React prop gave `FROM_PROPS`. Which value rendered was
170
+ decided by nothing an author can see — only by whether their chosen component
171
+ belonged to the `readProps()` family.
172
+
173
+ The React-prop channel now declines to let the legacy alias override a key the
174
+ canonical bag also declares; the config-bag order was already correct and is
175
+ unchanged. Scope is co-occurrence only: a key that only `props` declares still
176
+ works exactly as before, and a node that writes one spelling is untouched. The
177
+ `props` alias is not retired here — only its precedence against a co-present
178
+ canonical spelling is settled.
179
+ - dfc6975: Related-list "+ New" now honours `userActions.create` predicates, and the grid
180
+ toolbar's inline-edit affordance is gated on `update` permission (objectui#4646,
181
+ objectui#4647).
182
+
183
+ Two declared-but-unenforced gaps on the same toolbar surface.
184
+
185
+ **#4646 — `createPredicates` had a producer and no consumer.**
186
+ `@objectstack/spec@17.0.0` widened `userActions.create` to
187
+ `z.union([z.boolean(), RowCrudActionOverrideSchema])`, so `resolveCrudAffordances`
188
+ emits `createPredicates` — and nothing in objectui read them, against roughly
189
+ fifteen consumption sites apiece for `editPredicates` / `deletePredicates`. The
190
+ symptom: a parent record entering a frozen state correctly greyed its children's
191
+ row Edit/Delete while the related list's "+ New" stayed fully live, so the user
192
+ filled in the whole child form to earn a server 409. The related-list toolbar now
193
+ evaluates `visibleWhen` / `disabledWhen` **once against the host parent record**,
194
+ per the spec docblock's binding for this key, on top of the existing
195
+ `o.create ∧ can(child, 'create')` check. `visibleWhen` hides "+ New" and fails
196
+ CLOSED; `disabledWhen` greys it and fails SOFT — the same evaluator, fail
197
+ directions and hidden-vs-disabled split the record header already uses for
198
+ edit/delete (objectui#4419 / PR #4515). A bare-boolean `userActions.create` is
199
+ untouched: with no predicates there is nothing to evaluate.
200
+
201
+ **#4647 — the inline-edit toggle was the one ungated affordance on its toolbar.**
202
+ It rendered on "grid view ∧ the host wired `onInlineEditChange` ∧ not the compact
203
+ toolbar", and every host wires that callback unconditionally. New and Import are
204
+ hidden for an account without the grant and the bulk-delete entry on the same
205
+ toolbar ANDs `can(obj, 'delete')`, but a read-only principal could flip inline
206
+ edit, modify cells and press "Save all" to earn a server 403. It is now gated on
207
+ the object's resolved edit affordance ∧ `can(object, 'update')`, mirroring that
208
+ bulk-delete gate. The gate is applied at all three sites that carry this
209
+ affordance — the wide toolbar's toggle, the compact toolbar's settings-popover
210
+ entry (which previously had no gate at all, not even the callback), and the
211
+ `editable` mode handed to the grid, so a stored view carrying `inlineEdit: true`
212
+ can no longer drop a read-only principal into editable cells with no toggle to
213
+ press.
214
+
215
+ `ListViewSchema.userActions.editInline` is also consumed now: an explicit `false`
216
+ withholds the affordance wholesale, which authors previously could not do.
217
+
218
+ **Behaviour change for read-only users, stated plainly.** Where the UI used to
219
+ offer inline editing and let the server refuse it, it now declines to offer the
220
+ entry point at all. No data access changes — the server gate was and remains the
221
+ enforcement boundary; this only stops the UI walking users into round-trips
222
+ guaranteed to fail. Accounts *with* the grant see no change, and hosts with no
223
+ `PermissionProvider` mounted (standalone embeds, the Studio designer) keep
224
+ today's behaviour, since `can()` answers `true` there by design.
225
+
226
+ One deliberate non-change: the absent case of `userActions.editInline` defers to
227
+ the host's existing `inlineEdit` channel rather than enforcing the spec's
228
+ `.default(false)`. Enforcing that default would remove the toggle from every
229
+ stored console list view in one release, since nothing folds a legacy key into
230
+ `editInline` and no existing view declares it. This follows the rule the
231
+ surrounding toolbar-flag block already states for itself — defaults chosen to
232
+ match what the flags have always done. `InterfaceListPage`, the key's other
233
+ consumer, reads the absent case as OFF, because the ADR-0047 interface page has
234
+ no such host channel to defer to.
235
+ - 144ef9b: Dev builds now shout when an unevaluated `${…}` expression reaches the DOM.
236
+
237
+ A value only reaches the user if `SchemaRenderer` EVALUATES it and the renderer
238
+ READS IT BACK, and those two sets do not fully overlap. Where they miss, the
239
+ failure was silent. Measured on a real render with `dataSource: { n: 99 }`:
240
+ `{ type: 'ui:statistic', value: '${data.n}' }` puts the literal text `${data.n}`
241
+ on screen, because the evaluation memo covers `content`, the `properties` /
242
+ `props` bags and the predicate keys and passes every other top-level key through
243
+ untouched. An author — increasingly, an AI authoring metadata — got no signal at
244
+ all: a literal `${data.n}` in front of a user reads like a data problem rather
245
+ than a contract violation.
246
+
247
+ `SchemaRenderer` now reports such a value once per node via `console.error`,
248
+ naming the node type and id, the key the raw source survived on (spelled the way
249
+ it was authored — `properties.value`, not the hoisted top-level copy), the
250
+ expression source verbatim, and the channels that do work today. It also catches
251
+ the second, harder shape: an expression that WAS evaluated but THREW, which
252
+ `ExpressionEvaluator` returns as its source text — indistinguishable on screen
253
+ from a key that was never evaluated.
254
+
255
+ Diagnostic only. No evaluation behaviour changes, no DOM attribute is added, and
256
+ the whole module is behind the module-load `NODE_ENV` constant a bundler folds
257
+ away, so production pays nothing.
258
+
259
+ Two boundaries are deliberate. The scan is exactly as deep as evaluation is —
260
+ shallow — because a nested `aria: { label: '${…}' }` keeps its raw source today
261
+ by decision (objectui#4799 pinned that shallowness on both bags), and a deeper
262
+ scan would report a shape the engine has not yet decided to change. And schema
263
+ METADATA is never reported: `visible` / `visibleWhen` / `hidden` / `disabled` /
264
+ … hold raw predicate source by design, and the diagnostic reads the set of
265
+ values that actually leaves for the DOM, after those keys have been stripped.
266
+
267
+ Part of objectui#4795 (Direction 3, per the maintainer's 2026-08-17 ruling).
268
+ Widening the set of evaluated text keys is Direction 1 and stays deferred behind
269
+ its named restart condition; this change deliberately does not pre-empt it.
270
+ - Updated dependencies [88085e3]
271
+ - Updated dependencies [69251bf]
272
+ - Updated dependencies [57e668f]
273
+ - Updated dependencies [516663d]
274
+ - Updated dependencies [41ac1b7]
275
+ - Updated dependencies [1eaf0a1]
276
+ - Updated dependencies [2533ec5]
277
+ - Updated dependencies [bbe8b86]
278
+ - Updated dependencies [8477be5]
279
+ - Updated dependencies [279fb13]
280
+ - Updated dependencies [2e82ab2]
281
+ - Updated dependencies [ad07b65]
282
+ - Updated dependencies [41f498b]
283
+ - Updated dependencies [ef0d150]
284
+ - Updated dependencies [1ef236e]
285
+ - Updated dependencies [f34226e]
286
+ - Updated dependencies [564b605]
287
+ - Updated dependencies [e1d4251]
288
+ - Updated dependencies [1184192]
289
+ - Updated dependencies [a2a9747]
290
+ - Updated dependencies [a1609a6]
291
+ - Updated dependencies [37f6844]
292
+ - Updated dependencies [2b50261]
293
+ - Updated dependencies [ac600e5]
294
+ - Updated dependencies [c1ef923]
295
+ - Updated dependencies [af5e292]
296
+ - Updated dependencies [7f96b10]
297
+ - Updated dependencies [167ec42]
298
+ - Updated dependencies [cf4f8a6]
299
+ - Updated dependencies [f1d4748]
300
+ - Updated dependencies [bea374e]
301
+ - Updated dependencies [b1119ec]
302
+ - Updated dependencies [9f23d2b]
303
+ - Updated dependencies [578e025]
304
+ - Updated dependencies [af025ee]
305
+ - Updated dependencies [d109a4d]
306
+ - Updated dependencies [3d053bb]
307
+ - Updated dependencies [598c89a]
308
+ - Updated dependencies [b8b9af4]
309
+ - Updated dependencies [31676be]
310
+ - Updated dependencies [9ce096f]
311
+ - Updated dependencies [e05db88]
312
+ - Updated dependencies [ad13d63]
313
+ - Updated dependencies [5ffcc14]
314
+ - Updated dependencies [d971e51]
315
+ - Updated dependencies [97abb24]
316
+ - Updated dependencies [deb157a]
317
+ - Updated dependencies [9c60144]
318
+ - Updated dependencies [d2ce342]
319
+ - Updated dependencies [a8411ad]
320
+ - Updated dependencies [9695da7]
321
+ - Updated dependencies [58b8346]
322
+ - Updated dependencies [a9e17b4]
323
+ - Updated dependencies [b8ce7dc]
324
+ - Updated dependencies [8871c14]
325
+ - Updated dependencies [dfc6975]
326
+ - Updated dependencies [3cf4de0]
327
+ - Updated dependencies [c9dc811]
328
+ - Updated dependencies [d871f8e]
329
+ - Updated dependencies [a0b9e91]
330
+ - Updated dependencies [99bd015]
331
+ - Updated dependencies [21e4585]
332
+ - @object-ui/types@17.6.0
333
+ - @object-ui/i18n@17.6.0
334
+ - @object-ui/core@17.6.0
335
+ - @object-ui/data-objectstack@17.6.0
336
+
3
337
  ## 17.5.0
4
338
 
5
339
  ### Minor Changes
package/README.md CHANGED
@@ -158,18 +158,19 @@ falling back to the object's full scope. Use `useElementDataSourceSchema` (plus
158
158
  the exported `ElementDataSourceErrorPanel` / `ElementDataSourceLoadingPanel`) when
159
159
  a block cannot be wrapped — a renderer whose hooks must run before the panels.
160
160
 
161
- ### useRegistry
161
+ ### ComponentRegistry
162
162
 
163
- Access the component registry:
163
+ There is no registry hook: the registry is a process-level singleton exported
164
+ by `@object-ui/core`, so read it directly. Subscribe to it only when a lazily
165
+ registered plugin must trigger a re-render.
164
166
 
165
167
  ```tsx
166
- import { useRegistry } from '@object-ui/react'
168
+ import { ComponentRegistry } from '@object-ui/core'
167
169
 
168
- function MyComponent() {
169
- const registry = useRegistry()
170
- const Component = registry.get('button')
171
-
172
- return <Component {...props} />
170
+ function MyComponent(props: Record<string, unknown>) {
171
+ const Component = ComponentRegistry.get('button')
172
+
173
+ return Component ? <Component {...props} /> : null
173
174
  }
174
175
  ```
175
176
 
@@ -30,26 +30,33 @@ export interface LazyPluginOptions {
30
30
  /**
31
31
  * Create a lazy-loaded plugin component with Suspense wrapper
32
32
  *
33
- * @param importFn - Dynamic import function that returns a module with default export
33
+ * @param importFn - Dynamic import function resolving to a module whose
34
+ * `default` is the component to render. ObjectUI plugin packages export
35
+ * their components by NAME and have no `default`, so a bare
36
+ * `() => import('@object-ui/plugin-<x>')` does NOT satisfy this — it hands
37
+ * over the module namespace object, which is neither a component at runtime
38
+ * nor assignable here. Name the component you want, as the examples do.
34
39
  * @param options - Configuration options for the lazy plugin
35
40
  * @returns A component that lazy loads the plugin
36
41
  *
37
42
  * @example
38
43
  * ```tsx
39
- * // Basic usage
44
+ * // Basic usage. `@object-ui/plugin-grid` has no default export, so unwrap the
45
+ * // named component. Use the `async` form: the `.then((m) => ({ default: m.X }))`
46
+ * // spelling infers `P` as `never` on one branch of the `then` overload.
40
47
  * const ObjectGrid = createLazyPlugin(
41
- * () => import('@object-ui/plugin-grid')
48
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid })
42
49
  * );
43
50
  *
44
51
  * // With custom fallback
45
- * const ObjectGrid = createLazyPlugin(
46
- * () => import('@object-ui/plugin-grid'),
52
+ * const ObjectGridWithFallback = createLazyPlugin(
53
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid }),
47
54
  * { fallback: <div>Loading grid...</div> }
48
55
  * );
49
56
  *
50
57
  * // With retry and error handling
51
- * const ObjectGrid = createLazyPlugin(
52
- * () => import('@object-ui/plugin-grid'),
58
+ * const ObjectGridWithRetry = createLazyPlugin(
59
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid }),
53
60
  * {
54
61
  * retries: 3,
55
62
  * errorFallback: ({ error, retry }) => (
@@ -58,26 +58,33 @@ function createRetryImport(importFn, retries, retryDelay) {
58
58
  /**
59
59
  * Create a lazy-loaded plugin component with Suspense wrapper
60
60
  *
61
- * @param importFn - Dynamic import function that returns a module with default export
61
+ * @param importFn - Dynamic import function resolving to a module whose
62
+ * `default` is the component to render. ObjectUI plugin packages export
63
+ * their components by NAME and have no `default`, so a bare
64
+ * `() => import('@object-ui/plugin-<x>')` does NOT satisfy this — it hands
65
+ * over the module namespace object, which is neither a component at runtime
66
+ * nor assignable here. Name the component you want, as the examples do.
62
67
  * @param options - Configuration options for the lazy plugin
63
68
  * @returns A component that lazy loads the plugin
64
69
  *
65
70
  * @example
66
71
  * ```tsx
67
- * // Basic usage
72
+ * // Basic usage. `@object-ui/plugin-grid` has no default export, so unwrap the
73
+ * // named component. Use the `async` form: the `.then((m) => ({ default: m.X }))`
74
+ * // spelling infers `P` as `never` on one branch of the `then` overload.
68
75
  * const ObjectGrid = createLazyPlugin(
69
- * () => import('@object-ui/plugin-grid')
76
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid })
70
77
  * );
71
78
  *
72
79
  * // With custom fallback
73
- * const ObjectGrid = createLazyPlugin(
74
- * () => import('@object-ui/plugin-grid'),
80
+ * const ObjectGridWithFallback = createLazyPlugin(
81
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid }),
75
82
  * { fallback: <div>Loading grid...</div> }
76
83
  * );
77
84
  *
78
85
  * // With retry and error handling
79
- * const ObjectGrid = createLazyPlugin(
80
- * () => import('@object-ui/plugin-grid'),
86
+ * const ObjectGridWithRetry = createLazyPlugin(
87
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid }),
81
88
  * {
82
89
  * retries: 3,
83
90
  * errorFallback: ({ error, retry }) => (