@object-ui/react 16.1.0 → 17.2.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 (45) hide show
  1. package/CHANGELOG.md +790 -1
  2. package/README.md +41 -0
  3. package/dist/SchemaRenderer.js +12 -2
  4. package/dist/context/AppShellContext.js +51 -23
  5. package/dist/context/InlineEditContext.d.ts +64 -0
  6. package/dist/context/InlineEditContext.js +8 -2
  7. package/dist/context/NotificationContext.d.ts +245 -17
  8. package/dist/context/NotificationContext.js +222 -20
  9. package/dist/context/ThemeContext.js +4 -8
  10. package/dist/hooks/index.d.ts +2 -1
  11. package/dist/hooks/index.js +3 -1
  12. package/dist/hooks/useAnimation.d.ts +40 -4
  13. package/dist/hooks/useAnimation.js +41 -10
  14. package/dist/hooks/useCapabilityGate.d.ts +16 -0
  15. package/dist/hooks/useCapabilityGate.js +60 -0
  16. package/dist/hooks/useClientNotifications.d.ts +0 -6
  17. package/dist/hooks/useClientNotifications.js +7 -53
  18. package/dist/hooks/useFilterScope.d.ts +30 -0
  19. package/dist/hooks/useFilterScope.js +55 -0
  20. package/dist/hooks/useNavigationOverlay.d.ts +34 -10
  21. package/dist/hooks/useNavigationOverlay.js +35 -1
  22. package/dist/hooks/useOffline.d.ts +51 -33
  23. package/dist/hooks/useOffline.js +3 -3
  24. package/dist/hooks/usePageVariables.js +23 -11
  25. package/dist/hooks/usePerformance.d.ts +3 -3
  26. package/dist/hooks/usePerformance.js +2 -2
  27. package/dist/hooks/useRecordSearch.js +98 -4
  28. package/dist/index.d.ts +2 -1
  29. package/dist/index.js +1 -1
  30. package/dist/spec-bridge/SpecBridge.d.ts +0 -4
  31. package/dist/spec-bridge/SpecBridge.js +0 -12
  32. package/dist/spec-bridge/bridges/list-view.js +5 -11
  33. package/dist/spec-bridge/index.d.ts +0 -2
  34. package/dist/spec-bridge/index.js +0 -2
  35. package/dist/spec-input.d.ts +37 -0
  36. package/dist/spec-input.js +8 -0
  37. package/dist/utils/error-message.d.ts +31 -0
  38. package/dist/utils/error-message.js +49 -0
  39. package/package.json +8 -8
  40. package/dist/hooks/useColumnSummary.d.ts +0 -38
  41. package/dist/hooks/useColumnSummary.js +0 -54
  42. package/dist/spec-bridge/bridges/dashboard.d.ts +0 -81
  43. package/dist/spec-bridge/bridges/dashboard.js +0 -121
  44. package/dist/spec-bridge/bridges/page.d.ts +0 -58
  45. package/dist/spec-bridge/bridges/page.js +0 -101
package/CHANGELOG.md CHANGED
@@ -1,5 +1,794 @@
1
1
  # @object-ui/react
2
2
 
3
+ ## 17.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a889e31: A record's approval band now shows the quorum / per-group tally the server already computes.
8
+
9
+ The showcase's `showcase_committee_quorum` node declares `behavior: 'quorum'` with
10
+ `minApprovals: 2` over three approvers, and even ships a pre-rendered
11
+ `"Committee Sign-off (2 of 3)"` label; `showcase_expense_signoff` declares
12
+ `per_group` (会签) with named manager / finance groups. On the business record
13
+ the approval band rendered none of it — the lock badge, the recall button and
14
+ the approve/reject actions were all correct, but a two-of-three committee step
15
+ looked exactly like a one-approver step. An approver could not see whether their
16
+ own click finalized the node or was one of three, which is the single fact a
17
+ quorum node exists to express (objectstack#4478).
18
+
19
+ Nothing was wrong on the wire, and nothing here papers over the server. The
20
+ framework computes `decision_progress` — `{ behavior, got, need, groups? }`,
21
+ derived from the node's own `node_config_json` snapshot, so the count a client
22
+ shows is the count the engine will enforce. **It attaches that block in
23
+ `getRequest` only**: `listRequests` deliberately skips it, because the
24
+ `sys_approval_action` tally it costs is per row and a list read may return
25
+ hundreds. The record header's `useRecordApprovals` reads
26
+ `GET /approvals/requests?object=…&recordId=…` — the list route — so the
27
+ enrichment was never in the payload it had. The hook now follows up with one
28
+ single read for the ONE pending row and folds the result onto it; a failed or
29
+ mismatched follow-up leaves the row exactly as the list sent it, so a display-only
30
+ enrichment can never take the approval panel down and no tally is ever invented.
31
+
32
+ `InlineEditProvider` carries the block through as `approvalProgress`, and the
33
+ DetailView approval band renders it beside the existing badge: a labelled
34
+ `role="progressbar"` with one tick per required approval for `quorum` /
35
+ `unanimous`, and for `per_group` a chip per group marking which have signed
36
+ (`finance 1/1` ✓, `manager 0/1`). Group names come from the flow author's own
37
+ config, so they need no locale strings; the three new label keys are added to all
38
+ ten packs. `first_response` nodes carry no `decision_progress` and are unchanged —
39
+ one decision is the whole step there, and a "1 of 1" bar would be noise.
40
+
41
+ Scored `minor` rather than `patch`: this is new observable rendering plus a new
42
+ public `approvalProgress` prop / `ApprovalProgress` type on `@object-ui/react`,
43
+ not a behavior correction inside an existing surface.
44
+
45
+ - 09d30a4: Stop declaring 18 `@object-ui/auth` / `@object-ui/components` / `@object-ui/react`
46
+ symbols under names `@objectstack/spec` owns (objectui#3159, objectstack#4115
47
+ batch 5).
48
+
49
+ **Breaking for importers of all three packages** — six exported names changed,
50
+ because the spec exports the same name for a _different_ thing:
51
+
52
+ | package | was | now | what the spec's same-named export actually is |
53
+ | :----------- | :--------------------------- | :----------------------- | :----------------------------------------------------------------------------- |
54
+ | `auth` | `AuthSession` | `AuthClientSession` | the SERVER's session record (`{ id, userId, expiresAt: ISO string, token? }`) |
55
+ | `auth` | `AuthProviderConfig` | `AuthProviderOptions` | an OAuth/OIDC provider registration (`{ id, clientId, clientSecret, scope? }`) |
56
+ | `components` | `FilterCondition` | `FilterBuilderCondition` | the recursive ObjectQL predicate AST (`$and`/`$or`/`$not`) |
57
+ | `components` | `Field` | `FieldContainer` | an object FIELD's metadata and its builder namespace |
58
+ | `react` | `ConflictResolutionStrategy` | `ConflictResolution` | the metadata-MERGE policy (`error \| priority \| first-wins \| last-wins`) |
59
+
60
+ The `react` rename is the odd one out: the new name is the **spec's own** name
61
+ for the union that hook always used, so it is a re-export rather than a dialect.
62
+
63
+ Eleven more keep their names and are now **imported or derived from the spec**
64
+ instead of re-declared: `TenancyPosture`, `DelegableScope` (+`DelegableAdminScope`),
65
+ `AuthUser`, `ShareLinkPermission`, `ShareLinkAudience`, `ShareLink`, `SortItem`,
66
+ `OfflineStrategy`, `OfflineCacheConfig`, `OfflineSyncConfig`, `OfflineConfig`,
67
+ `NavigationConfig`.
68
+
69
+ **Three of the copies were losing information, not just duplicating it.**
70
+
71
+ - `AuthUser` never declared the spec's `positions` or `tenantId` — the
72
+ authorization inputs. Its `[key: string]: unknown` index signature meant the
73
+ omission was invisible at every call site _and_ to any structural comparison
74
+ (the objectstack#4075 mechanism). It now `extends` the spec principal, so the
75
+ display-only fields (`image`, `role`, `roles`, `emailVerified`) are the delta
76
+ and the spec's keys arrive on their own.
77
+ - `useNavigationOverlay`'s copy carried the note _"inline … to avoid importing
78
+ from `@object-ui/types` (which may not be a direct dependency of
79
+ `@object-ui/react`)"_. The vocabulary belongs to `@objectstack/spec`, which
80
+ **is** a direct dependency — the same expired "kept local to avoid a
81
+ dependency" comment objectui#3169 found in `@object-ui/app-shell`.
82
+ - `useOffline` and `usePerformance` both opened with _"Types aligned with
83
+ `@objectstack/spec` v2.0.7"_. The installed spec is 17.0.0-rc.1.
84
+
85
+ `ShareLink` derives from the spec row **minus `password_hash`** — omitted rather
86
+ than optional, because it is the credential itself and typing it in a browser
87
+ package is an invitation to render it. `password_protected` (the boolean the UI
88
+ needs in its place) is the one local addition.
89
+
90
+ The config types derive from each schema's **input** side, not `z.infer`.
91
+ `useOffline(config: OfflineConfig = {})` defaults to the empty object, which the
92
+ output type — every `.default()`ed key required — would reject outright.
93
+
94
+ `@objectstack/spec` moves from `devDependencies` to `dependencies` in
95
+ `@object-ui/components`: its public type surface now references the spec.
96
+
97
+ Scored `minor`, not `major`, per this repo's fixed-group rule — objectui's major
98
+ tracks `@objectstack`, so breaking changes of our own ship as minor with the
99
+ semantics spelled out above (see AGENTS.md §版本号策略). A `major` here would carry
100
+ all 39 packages of the fixed group to `18.0.0` and off objectstack's 17.x line.
101
+
102
+ ### Patch Changes
103
+
104
+ - ea96284: `useMetadataItem` no longer spins forever outside a `<MetadataProvider>` — the "graceful fallback" was the thing that made those consumers impossible to mount.
105
+
106
+ `useMetadata()` built its no-provider fallback **inline on every call**, so outside a provider
107
+ every render produced a new `getItem`. `useMetadataItem` lists `getItem` in its effect deps and,
108
+ on the no-name path, called `setState({ item: null, loading: false, error: null })` with a fresh
109
+ object each run. New identity → effect re-runs → new state object → re-render → new identity:
110
+ an unbreakable loop, synchronous enough to hang inside `render()` rather than fail.
111
+
112
+ So the fallback documented as the graceful path for consumers mounted outside a provider —
113
+ "common in unit tests that only need to assert on rendering" — was precisely what made them
114
+ unmountable. `record:alert` and `record:quick_actions` both call `useMetadataItem`
115
+ unconditionally; each pinned a core and grew unbounded (8.6 GB before the first kill) on a
116
+ `render()` that never returned.
117
+
118
+ Two changes, at the cause and one layer in:
119
+
120
+ - The fallback is a frozen module-level singleton, so its identity is stable across renders.
121
+ - The clear-state path bails out when the state is already cleared, instead of installing an
122
+ equal-but-new object. That covers the same loop arriving by another route — any caller whose
123
+ context value is rebuilt per render, which this interface explicitly invites ("hand-rolled
124
+ context values in tests keep working").
125
+
126
+ Found by `apps/console/src/__tests__/record-block-record-reach.test.tsx` (objectui#3149), which
127
+ could not mount either block until this was fixed.
128
+
129
+ - Updated dependencies [4ae0ac4]
130
+ - Updated dependencies [696e3c1]
131
+ - Updated dependencies [bca45cc]
132
+ - Updated dependencies [a889e31]
133
+ - Updated dependencies [4bf612c]
134
+ - Updated dependencies [335041c]
135
+ - Updated dependencies [b414983]
136
+ - Updated dependencies [256f8cc]
137
+ - Updated dependencies [c5ccbd5]
138
+ - Updated dependencies [d9668a7]
139
+ - Updated dependencies [4b470b9]
140
+ - Updated dependencies [cb82705]
141
+ - Updated dependencies [f572849]
142
+ - Updated dependencies [d3584c6]
143
+ - Updated dependencies [a8ad6c0]
144
+ - Updated dependencies [444457c]
145
+ - Updated dependencies [850033c]
146
+ - Updated dependencies [022e4c3]
147
+ - Updated dependencies [009e25d]
148
+ - Updated dependencies [726b89c]
149
+ - @object-ui/types@17.2.0
150
+ - @object-ui/core@17.2.0
151
+ - @object-ui/i18n@17.2.0
152
+ - @object-ui/data-objectstack@17.2.0
153
+
154
+ ## 17.1.0
155
+
156
+ ### Minor Changes
157
+
158
+ - c4db402: refactor(views): ListView's `aria` and `sharing` are the spec sub-shapes (#2890 scope A step 5)
159
+
160
+ Last rename batch in the ListView vocabulary migration.
161
+
162
+ **`aria`** is now the spec's `AriaPropsSchema`: `label` → `ariaLabel`,
163
+ `describedBy` → `ariaDescribedBy`, folded at the ListView boundary like every
164
+ other legacy key. Two things fall out of adopting the spec shape:
165
+
166
+ - `role` becomes authorable. The list region hardcoded `role="region"`; it now
167
+ reads `aria.role` and falls back to `region`.
168
+ - `aria.live` stays as a documented local extension — it has no spec
169
+ counterpart, and dropping it would silently disable a shipped capability.
170
+ Promote it rather than growing that extension.
171
+
172
+ **`sharing`** is now the spec's `ViewSharingSchema` (`{ type, lockedBy }`),
173
+ imported by reference — the local four-key object is gone. The legacy pair folds
174
+ in: `visibility` collapses onto the two ownership kinds the spec models (only
175
+ `private` is `personal`; `team` / `organization` / `public` are all
176
+ `collaborative`), and a bare `enabled: true` maps to `personal`, which is the
177
+ badge the user already saw (the old title fell back to `'private'`).
178
+
179
+ _Visible change_: the share badge's tooltip shows the spec ownership type, so a
180
+ view authored with `visibility: 'team'` reads "Sharing: collaborative" instead
181
+ of "Sharing: team". The four-value audience has no spec home and nothing but
182
+ that tooltip consumed it; keeping a second audience enum alive would re-open the
183
+ fork this issue closes.
184
+
185
+ Also fixes the **spec bridge**, which was doing the opposite of its job: given a
186
+ spec-shaped `sharing`, `transformListView` _downgraded_ it — inventing a legacy
187
+ `visibility` audience and an `enabled` flag that the renderer then had to fold
188
+ back. Both sides speak `ViewSharing` now, so it passes through.
189
+
190
+ `conditionalFormatting` and `exportOptions` are deliberately **not** folded.
191
+ Both objectui shapes are supersets carrying capability the spec cannot express —
192
+ the `{ field, operator, value }` rule form, and `maxRecords` / `includeHeaders`
193
+ / `fileNamePrefix`. Folding them onto the narrower spec shapes would delete
194
+ working features; they want promotion upstream, not a rename.
195
+
196
+ - 07de839: fix(notifications): the config, `position` and action `variant` are read instead of forked or ignored (#3014 follow-up)
197
+
198
+ The last of the notification contract. After `displayType` (#3071) and `icon`
199
+ (#3076), four gaps of the same family were left:
200
+
201
+ - **the config was 3/4 inert** — only `defaultDuration` was ever read.
202
+ `maxVisible` and `stacking` were carried and ignored, while
203
+ `NotificationBanners` capped at a hard-coded `3` of its own;
204
+ - **its field names forked from `NotificationConfigSchema`** — `position` vs
205
+ `defaultPosition`, a renderer-local `stacking` boolean with no spec
206
+ counterpart, and no `pauseOnHover` at all;
207
+ - **a notification could not declare a `position`.** The #3008 parity guard
208
+ asserted the position _vocabulary_ matched the spec while nothing positioned
209
+ anything by it — a guard passing over an unused value;
210
+ - **`NotificationActionButton.variant` was the shadcn Button vocabulary**
211
+ (`default | destructive | outline`) under a spec-shaped name, forking
212
+ `NotificationActionSchema.variant` (`primary | secondary | link`).
213
+
214
+ **How positioning resolves now** — `notification.position ?? config.defaultPosition
215
+ ?? nothing`, and "nothing" is a real answer:
216
+
217
+ - **declared** → the surface pins itself there, always. `presentNotificationToast`
218
+ passes it per-toast so the contract wins over the container;
219
+ - **undeclared** → the surface keeps its own anchor (a snackbar's bottom edge) or
220
+ defers to the host's toast chrome.
221
+
222
+ That asymmetry is the design decision. The host's sonner container also serves
223
+ toasts that are _not_ spec notifications (the console action runtime's own
224
+ `toast.*` calls), so it stays the fallback authority for placement — never a
225
+ competing one. A declared position a component prop could silently override
226
+ would be the same "validates, then does nothing" shape this whole area is about.
227
+ Hence `defaultPosition` has no fabricated default: "the host didn't say" has to
228
+ be representable.
229
+
230
+ Also: `maxVisible` / `stackDirection` now drive every stacking surface through
231
+ one shared `visibleNotificationStack` (cap keeps the NEWEST, stack grows in the
232
+ declared direction); `pauseOnHover` holds a transient notification's timer and
233
+ resumes it with the time it had left, which needed the provider to track live
234
+ timers rather than fire-and-forget `setTimeout`s. Legacy spellings still resolve:
235
+ `position` folds into `defaultPosition`, and `stacking: false` reads as
236
+ `maxVisible: 1` rather than being ignored.
237
+
238
+ `onToast` now receives the resolved config as a second argument, so the delegate
239
+ can apply the parts of the contract only it can. Existing one-argument handlers
240
+ are unaffected. The spec-parity guard gained the action-variant vocabulary, the
241
+ one notification enum it did not cover.
242
+
243
+ - 2a40b5e: feat(notifications): each spec `displayType` gets its own presentation instead of a toast (#3014)
244
+
245
+ #3008 closed the **contract** half of this: `NotificationContext`'s union matched
246
+ `NotificationTypeSchema`, and `notify()` materialized the declared type so a
247
+ consumer _could_ branch on it. Nothing did. `NotificationProvider` handed every
248
+ item to the host's `onToast` delegate regardless of type, so an author picking
249
+ `banner` or `inline` got a transient overlay — plausible output, wrong output.
250
+
251
+ Each of the five spec types now has a presentation of its own:
252
+
253
+ | `displayType` | Presentation | Rendered by |
254
+ | ------------- | ------------------------------------------------------ | ---------------------------------- |
255
+ | `toast` | transient overlay (unchanged) | the host's `onToast` delegate |
256
+ | `snackbar` | bottom-anchored bar, one at a time, at most one action | `<NotificationSnackbar />` |
257
+ | `banner` | page-width strip **in the content flow** | `<NotificationBanners />` |
258
+ | `alert` | blocking acknowledgement dialog, FIFO queue | `<NotificationAlerts />` |
259
+ | `inline` | in place, at the raising surface | `<NotificationInline scope="…" />` |
260
+
261
+ The four surface components ship from `@object-ui/components` and subscribe via
262
+ `useNotificationsByPresentation(type, scope?)`.
263
+
264
+ **Answers to the three questions the issue left open:**
265
+
266
+ 1. **Banner/inline placement is the host's.** They are not overlays: a banner takes
267
+ space at the top of the content area and an `inline` notification belongs next to
268
+ the thing that raised it. So the context exposes the items and the surfaces
269
+ subscribe, rather than one `onToast`-style delegate positioning everything. An
270
+ `inline` notification carries a `scope` that pairs it with its outlet, so two
271
+ forms on one page don't show each other's messages.
272
+ 2. **`alert` is modal-ish but NOT the action system's `ModalHandler`.** That handler
273
+ resolves a page/object, renders it, and reports an `ActionResult` back to the
274
+ `ActionRunner`; a notification alert has no schema, no target and no result.
275
+ Routing it there would mean synthesizing a page just to say "OK". It renders
276
+ through the `AlertDialog` primitive instead — no second action-modal path.
277
+ 3. **`snackbar` earns its own component.** It supersedes rather than stacks, anchors
278
+ bottom regardless of the toast position config, and takes at most one action.
279
+ Making it a sonner variant is what "presents as a toast" means.
280
+
281
+ **Also fixed:** auto-dismiss now follows the presentation. `toast`/`snackbar` keep
282
+ the transient timer; `banner`/`alert`/`inline` are persistent unless the raiser sets
283
+ `duration` explicitly — a persistent banner used to evaporate on the shared 5s toast
284
+ timer. `dismissible` is honored on the persistent surfaces (an `alert` always keeps
285
+ its acknowledge button; `dismissible: false` only closes the Escape route).
286
+
287
+ `onToast` now receives **only** `toast` items. A provider with no `onToast` remains
288
+ the supported store-only mode (a bell reading `notifications`/`unreadCount`), but
289
+ raising one of the other four types with its surface unmounted warns in dev, naming
290
+ the component to mount — that failure used to be silent.
291
+
292
+ `NOTIFICATION_PRESENTATIONS` is typed `Record<NotificationPresentation, …>`, so a new
293
+ member in the spec enum fails type-check until its presentation is decided; a parity
294
+ test additionally asserts the table covers `NotificationTypeSchema` exactly and that
295
+ no two types share a surface.
296
+
297
+ - ce08d55: chore(deps): upgrade `@objectstack/*` to 17.0.0-rc.0, and let the spec take back what it now owns
298
+
299
+ `spec` / `client` / `formula` / `lint` move from `^16.x` to `^17.0.0-rc.0`. Two
300
+ groups of v17 changes reach this repo, and they pull in opposite directions —
301
+ the spec pruned surface objectui re-exported, and adopted surface objectui had
302
+ been carrying locally.
303
+
304
+ **The spec pruned dead Theme config (objectstack#3494), so the re-exports went
305
+ with it.** `ThemeSchema` dropped `spacing`, `breakpoints`, `logo`, `density`,
306
+ `wcagContrast`, `rtl`, `touchTarget` and `keyboardNavigation` — authorable but
307
+ never enforced, so authoring them was already a silent no-op. `@object-ui/types`
308
+ re-exported those sub-schemas _by reference_ (issue #2231), so they could not
309
+ survive the prune without becoming hand-written mirrors — exactly the second
310
+ de-facto contract AGENTS.md #0.1 forbids. Removed from the public surface:
311
+
312
+ - Types: `Spacing`, `Breakpoints`, `DensityMode`, `WcagContrastLevel`,
313
+ `ThemeLogo`, and the deprecated `SpacingScale` alias
314
+ - Schemas: `SpacingSchema`, `SpacingScaleSchema`, `BreakpointsSchema`,
315
+ `ThemeLogoSchema`, and the `SpacingSchemaType` / `BreakpointsSchemaType` helpers
316
+ - `Theme.spacing`, `Theme.breakpoints` and `Theme.logo`
317
+
318
+ `mergeThemes` no longer merges the three dropped keys. `generateThemeVars` is
319
+ unaffected — it never emitted them, which is why the liveness audit called them
320
+ dead. The one real consumer was `ThemeProvider`, which set the favicon from
321
+ `theme.logo.favicon`; that path is gone, because v17 strips the key at parse and
322
+ it could never arrive again. The live favicon is unaffected: it comes from
323
+ operator branding (`getFaviconUrl()`), applied in the console's `index.html`,
324
+ `main.tsx`, and on route change.
325
+
326
+ Nothing else read the pruned types. In particular the list-density feature is
327
+ untouched — `useDensityMode` and `rowHeightToDensityMode` use `@object-ui/core`'s
328
+ own local `DensityMode`, which never came from the spec.
329
+
330
+ **The spec adopted objectui's ListColumn extensions (objectui#2231), so the
331
+ extension collapsed.** `ListColumnSchema` used to `.extend()` the spec with two
332
+ fields, each carrying a note to promote it upstream rather than grow the
333
+ extension; v17 did exactly that. `summary` is now the spec's
334
+ `union([ColumnSummarySchema, ColumnSummaryConfigSchema])` — the same enum ∪
335
+ `{ type, field }` form `useColumnSummary` reads — and `prefix` is the spec's
336
+ `ColumnPrefixSchema`. `ListColumnSchema` is now a plain by-reference re-export.
337
+ One behavior change rides along: `prefix.type` defaults to `'text'` on parse
338
+ instead of staying `undefined`, so the cell renderer always gets a value.
339
+
340
+ **Node 22 is now the floor.** Every `@objectstack` package declares
341
+ `engines.node: ">=22.0.0"` (objectstack#3825; Node 20 reached EOL 2026-04-30).
342
+ This repo claimed `>=20` and ran CI on Node 20.x, so it promised — and validated
343
+ — a runtime its own core dependency does not support. `engines.node` is now
344
+ `>=22`, CI runs Node 22.x, and the CI/deployment docs say so.
345
+
346
+ The major stays 17: per AGENTS.md the major tracks `@objectstack`'s major, which
347
+ is also 17, and that convention deliberately outranks semver purity — so the
348
+ removals above ship as a minor rather than desyncing the two.
349
+
350
+ - 0ded602: fix(form): a server rejection that names fields now marks those fields (objectstack#3896)
351
+
352
+ The server has always said which field it rejected. `@objectstack/objectql`'s
353
+ validators throw `VALIDATION_FAILED` with `fields[]` — one entry per offending
354
+ field, each with a human `message` — and both the REST layer and the runtime
355
+ dispatcher serve that as a 400 with the entries intact.
356
+
357
+ Every form dropped them. The submit handler caught the rejection, ran the
358
+ message through `extractWriteErrorMessage`, and showed **one undirected toast**:
359
+ the user was told something was wrong but not _what_, on a surface that already
360
+ knows how to mark an input — and already does exactly that for client-side
361
+ validation. On a long form the offending field was often off-screen, so "创建"
362
+ appeared to do nothing.
363
+
364
+ **Now the two failures behave identically, because they share one
365
+ implementation.** The per-field marking, the toast naming the fields, and the
366
+ scroll-and-focus of the first offender (#2793) were extracted from the
367
+ client-side invalid handler; the server path calls the same function. As far as
368
+ the person filling in the form is concerned these are the same event — only the
369
+ referee differs.
370
+
371
+ Three layers, each of which was dropping the detail:
372
+
373
+ - **`@object-ui/react`** — new `extractFieldErrors(err)` (exported alongside
374
+ `extractWriteErrorMessage` / `isPermissionError`) normalises the three shapes
375
+ the error can arrive in: a typed `ValidationError` from the ObjectStack
376
+ adapter, the raw `@objectstack/client` error (whose `details` falls back to the
377
+ whole response body, which is where `fields[]` lands), and a hand-rolled error
378
+ carrying `fields` directly — the server duck-types that shape identically, so
379
+ the client must not be pickier than the server. Entries with no usable `field`
380
+ are **dropped rather than guessed at**: marking an innocent input is worse than
381
+ the generic toast.
382
+ - **`@object-ui/data-objectstack`** — `normaliseClientError` now maps a 400
383
+ `VALIDATION_FAILED` onto the `ValidationError` class that has sat in
384
+ `errors.ts` since the package was written, exported and **never once
385
+ constructed**. Its `validationErrors: Array<{ field, message }>` shape was
386
+ already exactly right. `create` also now normalises at all: only `update` did,
387
+ so a rejected insert reached callers as the raw client error — and a create is
388
+ the path that most often trips required-field validation.
389
+ - **`@object-ui/components`** — the form renderer maps the entries onto
390
+ `form.setError` and takes over the failure, **but only when every rejected
391
+ field has a visible input to carry it**. If the server also rejected something
392
+ the form does not render, it falls through to the banner, whose top-level
393
+ message concatenates every field's reason — so the part the user cannot see
394
+ inline is still said out loud instead of silently dropped.
395
+
396
+ This also removes the need for the client-side predicate mirroring added in
397
+ #2962: a form no longer has to guess what the server will reject in order to
398
+ warn about it beforehand, and mirrored predicates drift.
399
+
400
+ Non-field failures (403 / permission denials / anything without `fields[]`) take
401
+ exactly the path they took before.
402
+
403
+ ### Patch Changes
404
+
405
+ - fc0272a: fix(actions): apply the ADR-0066 D4 capability gate on every action surface (framework#3923)
406
+
407
+ An action declaring `requiredPermissions` is supposed to be one declaration with
408
+ two enforcement surfaces: 403 on the server, hidden button in the UI. The UI half
409
+ only ever ran inside `ActionEngine.getActionsForLocation` — and the surfaces
410
+ `record_header`, `record_more`, `list_item` and `list_toolbar` actually render on
411
+ do not go through the engine. They filter their own action lists. So a button
412
+ declaring a capability nobody holds rendered, live and clickable, on the record
413
+ header, in every grid row menu, and on the list toolbar. For a `type: 'api'`
414
+ action pointed at a self-authored endpoint, nothing else was checking either: the
415
+ platform's action route (which is where the 403 comes from) never sees that
416
+ request.
417
+
418
+ `page:header`, `action:bar` (business _and_ `systemActions`) and the grid's
419
+ `RowActionMenu` now apply the same gate, via a shared `useCapabilityGate()` so
420
+ the surfaces cannot drift apart. The rule is the engine's, unchanged: hide unless
421
+ the caller holds **all** declared capabilities; an empty held set is "holds
422
+ nothing" and gates; **unknown** — no action runtime, no resolved capabilities —
423
+ fails OPEN, because the server is the authority and hiding a permitted user's
424
+ button on missing client data is the worse failure.
425
+
426
+ The record surface was also feeding the gate nothing to work with.
427
+ `RecordDetailView` mounts its own `<ActionProvider>`, which shadows the shell's
428
+ for every action on that page, and seeded it with identity only — no
429
+ `systemPermissions`. Since unknown fails open, that alone un-gated every
430
+ `record_header` / `record_more` / `record_section` action on the one page those
431
+ locations exist on. It now forwards the caller's resolved capabilities (and only
432
+ once they have actually resolved, so a standalone embed without a
433
+ `PermissionProvider` keeps failing open rather than hiding everything).
434
+
435
+ `useRecordEditable`'s record-level explain probe went out on a bare
436
+ `fetch(..., { credentials: 'include' })`. A bearer-token session carries its
437
+ credential in the `Authorization` header, not a cookie, so the probe came back
438
+ 401 on a perfectly valid admin session and the verdict silently failed open —
439
+ the hook was inert in exactly the deployments it was written for. It now rides
440
+ the host's authenticated fetch (`SchemaRendererProvider`'s `apiFetch`), falling
441
+ back to the global one for standalone embeds.
442
+
443
+ - 9eb932b: fix(console): three real-user console failures — 403 blamed on the network, ⌘K search capped at 8 objects, nav gating fields inert
444
+
445
+ 1. **List error panel classifies the failure** (`plugin-list`, `i18n`): a 403/401 from the data source used to render the same "check your connection" copy as a genuine outage, sending users to debug their network while the server was correctly denying access. The panel now classifies by `httpStatus`/`status`/`statusCode`, the `PERMISSION_DENIED`/`UNAUTHORIZED` error codes, or an `HTTP <status>` message prefix, and renders dedicated permission-denied / sign-in-required copy (all nine locales).
446
+
447
+ 2. **⌘K / full-page search scope is no longer truncated** (`react`): `maxObjectsQueried` caps the per-object fanout fallback, not the search scope — it used to slice the candidate pool itself, so the `objects` whitelist sent to the platform's `/api/v1/search` only ever named the first 8 nav objects. Which sidebar group came first decided which records were findable; everything later in the nav was unsearchable no matter what the user typed.
448
+
449
+ 3. **Nav gating fields finally gate** (`app-shell`): `evaluateVisibility` only evaluated `${…}` template strings, so the `{ dialect: 'cel', source }` envelopes the spec normalizes every authored `visible` predicate into fell through to a blanket "visible" — a constant-false predicate still rendered for everyone. It now delegates to `ExpressionEvaluator.evaluateCondition`, which routes CEL envelopes to the canonical `@objectstack/formula` engine. And the sidebars' `requiredPermissions` check treats a bare name as an ADR-0066 system capability (union of the user's permission-set `systemPermissions` from `/me/permissions`) — the same subset rule the server applies to `AppSchema.requiredPermissions` — instead of misreading it as `can(<name>, 'read')`, which had degraded `requiredPermissions` into a hide-from-everyone switch (admins included). The `object:action` form and the legacy object-read fallback keep working.
450
+
451
+ - 4952edf: fix(errors): error-code branches survive the framework's ADR-0112 rename — objectstack#3841
452
+
453
+ Framework ADR-0112 renamed the whole `error.code` vocabulary from lowercase
454
+ `snake_case` to `SCREAMING_SNAKE` (`destructive_change` → `DESTRUCTIVE_CHANGE`).
455
+ Eleven places compared `err.code` against the old spelling with `===`, so against
456
+ a swept server they simply stopped matching — and nothing threw. The affordance
457
+ each branch guards just vanished and the user got the generic error toast instead:
458
+
459
+ - the destructive-change confirm dialog (resource editor, permission matrix)
460
+ - the "create a writable package first" hint
461
+ - field-scoped validation issues on embedded item saves
462
+ - the all-or-nothing publish summary naming the causal item
463
+ - unknown-object tolerance in the app header and in record search
464
+ - the marketplace's local-install messages for conflict / auth / unavailable
465
+ - `isNotFoundError` in the data layer
466
+
467
+ `RECORD_NOT_FOUND` had already been renamed a release earlier, so that branch was
468
+ already dead before this fix.
469
+
470
+ New `errorCodeIs` / `errorCodeIsAnyOf` in `@object-ui/types` compare
471
+ case-insensitively, so the console keeps working against servers on either side
472
+ of the rename — the console ships separately from the server it talks to. Every
473
+ call site now passes the catalog (SCREAMING) spelling, and `error-code.ts` is the
474
+ single file to delete once no supported server emits the old vocabulary.
475
+
476
+ - 2374a49: fix(sdui): a react page no longer loses its state to a memo that never held, and a source that exports nothing fails loudly
477
+
478
+ Writing the regression guard for objectui#2954's "latent hazard" found it was
479
+ already real.
480
+
481
+ **`evaluatedSchema` was memoised on values rebuilt every render.**
482
+ `SchemaRenderer` fell back to a fresh `{}` when no `SchemaRendererProvider` sat
483
+ above it, and `usePageVariables()` returned a brand-new object literal outside a
484
+ `PageVariablesProvider`. Both feed the `evaluatedSchema` memo's dependency list,
485
+ so for any tree without those providers the memo never hit: the schema was
486
+ re-cloned and the ExpressionEvaluator re-run on every render, and children got a
487
+ new schema identity every time. A `kind:'react'` page memoises its compiled
488
+ source on that identity, so the page was recompiled — a new page function, a new
489
+ element type — and React remounted it, silently discarding the user's `useState`.
490
+ Any registry notification (every lazy plugin's first load) triggered it. Both
491
+ fallbacks are now module constants.
492
+
493
+ **A source that exports nothing now throws instead of rendering blank.**
494
+ `generateElement` inserts the implicit `export default` only when the source
495
+ _starts with_ JSX, a `function` declaration, `()` or `class` — so the very
496
+ common `const Page = () => …` exported nothing, and the page rendered blank with
497
+ no error reported anywhere. It now throws with a message naming the fix, which
498
+ `ReactRunner`'s error panel surfaces. `export default null` still means "render
499
+ nothing"; a default export that is not a component throws too.
500
+
501
+ **`PageSchema['kind']` matches `@objectstack/spec`.** It declared
502
+ `'full' | 'slotted'` while the renderer had shipped `'react'` and
503
+ `'html'`/`'jsx'` since ADR-0080 and read the field through a cast. The union now
504
+ spells all five and the cast is gone.
505
+
506
+ Docs: new `content/docs/guide/react-pages.md` (choosing between the executed and
507
+ parsed tiers, the capability gate, the injected scope, flat props, `Block`,
508
+ `useAdapter`, source shapes, error handling) and a `@object-ui/react-runtime`
509
+ README — the package had neither, while being the tier AI-authored pages target.
510
+
511
+ - Updated dependencies [62311b6]
512
+ - Updated dependencies [9e7349e]
513
+ - Updated dependencies [8864971]
514
+ - Updated dependencies [9b773f9]
515
+ - Updated dependencies [1cf0de7]
516
+ - Updated dependencies [752e18f]
517
+ - Updated dependencies [c785740]
518
+ - Updated dependencies [b41f401]
519
+ - Updated dependencies [19e9fa0]
520
+ - Updated dependencies [d61efd1]
521
+ - Updated dependencies [95b7214]
522
+ - Updated dependencies [7d9734d]
523
+ - Updated dependencies [6ae818e]
524
+ - Updated dependencies [9eb932b]
525
+ - Updated dependencies [746dd00]
526
+ - Updated dependencies [aebfa4f]
527
+ - Updated dependencies [38ca8be]
528
+ - Updated dependencies [3cb9646]
529
+ - Updated dependencies [4952edf]
530
+ - Updated dependencies [7f0252e]
531
+ - Updated dependencies [7d35010]
532
+ - Updated dependencies [c4d7b20]
533
+ - Updated dependencies [7639a61]
534
+ - Updated dependencies [94e63ef]
535
+ - Updated dependencies [02aef0c]
536
+ - Updated dependencies [6f29aa5]
537
+ - Updated dependencies [d21794c]
538
+ - Updated dependencies [c4db402]
539
+ - Updated dependencies [5319bf1]
540
+ - Updated dependencies [49e5671]
541
+ - Updated dependencies [b5b97e2]
542
+ - Updated dependencies [f59f2c1]
543
+ - Updated dependencies [4874117]
544
+ - Updated dependencies [ad0183a]
545
+ - Updated dependencies [ce08d55]
546
+ - Updated dependencies [a17ef09]
547
+ - Updated dependencies [aa1240a]
548
+ - Updated dependencies [2374a49]
549
+ - Updated dependencies [390c071]
550
+ - Updated dependencies [d10f526]
551
+ - Updated dependencies [2d5d594]
552
+ - Updated dependencies [ea7f477]
553
+ - Updated dependencies [7f23cd0]
554
+ - Updated dependencies [0ded602]
555
+ - Updated dependencies [24e0e0a]
556
+ - Updated dependencies [f8a95e5]
557
+ - Updated dependencies [3a6cf24]
558
+ - Updated dependencies [aa35561]
559
+ - Updated dependencies [03bd53b]
560
+ - Updated dependencies [3c1f321]
561
+ - Updated dependencies [a045a32]
562
+ - Updated dependencies [912496d]
563
+ - Updated dependencies [9867281]
564
+ - @object-ui/core@17.1.0
565
+ - @object-ui/types@17.1.0
566
+ - @object-ui/data-objectstack@17.1.0
567
+ - @object-ui/i18n@17.1.0
568
+
569
+ ## 17.0.0
570
+
571
+ ### Major Changes
572
+
573
+ - c77108c: refactor(spec-bridge): remove the dead page/dashboard bridges (#1892)
574
+
575
+ `SpecBridge`'s `page` and `dashboard` bridges — `bridgePage`, `bridgeDashboard`,
576
+ and the `SpecBridge#transformPage` / `#transformDashboard` methods — had no
577
+ runtime consumer. Pages render through their own renderer and dashboards
578
+ through `DashboardView → DashboardRenderer → DatasetWidget` (ADR-0021); neither
579
+ path routes through `SpecBridge`. The dashboard bridge's input shape
580
+ (`object` / `categoryField` / `valueField` / `aggregate`) is the pre-ADR-0021
581
+ widget model, which the strict `DashboardWidgetSchema` now rejects — so the
582
+ bridge could not receive a spec-valid dashboard even in principle.
583
+
584
+ Flagged dead by the metadata-liveness audit (framework #1878 / #1892). The
585
+ `list` and `form` bridges are unaffected and remain the live authoring path.
586
+
587
+ BREAKING CHANGE: the public exports `bridgePage`, `bridgeDashboard`, and the
588
+ `SpecBridge#transformPage` / `#transformDashboard` methods are removed. There
589
+ is no replacement — render pages and dashboards through their renderers
590
+ (`DashboardRenderer` / the page renderer) directly.
591
+
592
+ ### Minor Changes
593
+
594
+ - 952b978: fix(detail): the approval band honors the node's `lockRecord` instead of assuming every approval locks (#2902)
595
+
596
+ A record detail page treated "a pending approval request exists" as "this
597
+ record is locked". An approval node declares `lockRecord` (default `true`), and
598
+ on `lockRecord: false` the server keeps accepting writes for the whole time
599
+ that node waits — so the console was asserting a lock the backend did not
600
+ enforce.
601
+
602
+ The label was the smaller half of it. The same conflated signal fed `canEdit`,
603
+ so the record-level inline-edit session was suppressed too: no pencils,
604
+ `enter()` a no-op. On a single-approver step — a department head or plant
605
+ manager, exactly the case `lockRecord: false` exists for, where the approver is
606
+ meant to fill in the missing detail before deciding — the capability was
607
+ unreachable from the UI. And a flow chaining nodes with different policies drew
608
+ one identical band for "edit freely" and "the server will reject your save with
609
+ `RECORD_LOCKED`", so the two states were indistinguishable until Save failed.
610
+
611
+ Approval state is now two signals:
612
+
613
+ - **`approvalPending`** — an approval is running. Drives the band and the recall
614
+ button, both meaningful whether or not the record is editable.
615
+ - **`locked`** — that approval also forbids edits, from the pending node's
616
+ `lock_record` (framework#3814, read off the same `node_config_json` snapshot
617
+ the server's record-lock hook reads).
618
+
619
+ The band renders two states: amber lock + "Locked for approval", or sky clock +
620
+ "In approval · editable", each with its own tooltip. Recall moved out of the
621
+ locked branch — an editable pending approval is just as recallable. Inline
622
+ editing stays live in the editable state.
623
+
624
+ `InlineEditProvider` takes a new optional `approvalPending` prop, defaulting to
625
+ `locked`, so a host that threads only `locked` renders exactly as before. The
626
+ record's `approval_status` field remains the fallback for backends with no
627
+ approvals API; it carries no node granularity, so it still reads as locked — as
628
+ does a pending request from a backend too old to report the policy.
629
+
630
+ New `detail.approvalPendingEditable` / `detail.approvalPendingTooltip` keys are
631
+ translated in all ten locales.
632
+
633
+ - 1767124: feat(grid): compute all eleven spec column summary aggregations (#2890)
634
+
635
+ `ColumnSummarySchema` accepts eleven aggregation names; `useColumnSummary` computed
636
+ five. The other six — `none`, `count_empty`, `count_filled`, `count_unique`,
637
+ `percent_empty`, `percent_filled` — passed validation at authoring time and then
638
+ rendered a blank footer cell, with no error raised on either side.
639
+
640
+ The computation now splits into two families. Count and percent read _raw_ cell
641
+ values, before the numeric parse, so they work on text, select and lookup columns and
642
+ a value that does not parse as a number still counts as a filled row; a cell is empty
643
+ when it is `null`, `undefined`, `""` or an empty array. `sum`/`avg`/`min`/`max` keep
644
+ the existing numeric parse and column formatting.
645
+
646
+ Two behavior changes follow from the enum carrying both `count` and `count_filled`,
647
+ which cannot mean the same thing:
648
+
649
+ - `count` is now every row; `count_filled` is the non-empty variant. Only a column
650
+ whose values are all empty renders differently than before.
651
+ - a zero count renders `Empty: 0` instead of collapsing to a blank cell.
652
+
653
+ Column currency/percent formatting is gated to the numeric family, so `count_unique`
654
+ on a currency column reads `Unique: 3` and not `$3.00`. `none` and unrecognized names
655
+ skip the entry entirely, so a view whose columns all opt out renders no footer row.
656
+
657
+ `ListColumnSchema`'s objectui-local `{ type, field }` arm now takes its vocabulary
658
+ from `SpecColumnSummarySchema` by reference — it was stuck at the same five names,
659
+ which left the per-column `field` override unavailable for the six new aggregations.
660
+
661
+ A parity test asserts the renderer's supported set equals the spec enum in both
662
+ directions: a spec name the renderer omits is the bug above, and a renderer name the
663
+ spec omits would be local dialect (Commandment #0).
664
+
665
+ **Removed:** `useColumnSummary` from `@object-ui/react`. It was a second, unrelated
666
+ hook of the same name with no callers — a different API, a comment claiming it
667
+ implemented spec v2.0.7, and a `distinct` aggregation that is not in the spec
668
+ vocabulary at all (the spec calls it `count_unique`). Use `useColumnSummary` from
669
+ `@object-ui/plugin-grid`, which implements the spec enum.
670
+
671
+ - c6cfdf1: feat(react)!: trim the dead device/preference delegates from
672
+ `useClientNotifications` (objectstack#3612 companion)
673
+
674
+ `registerDevice`, `getPreferences`, and `updatePreferences` delegated to
675
+ `@objectstack/client` methods that were deleted in objectstack#3612 — the
676
+ `/notifications/devices` and `/notifications/preferences` server routes they
677
+ targeted were never built, so every call already surfaced an error at
678
+ runtime. The hook keeps `fetchNotifications` and `markAsRead` (both
679
+ dispatcher-served and route-ledgered). Breaking only for code destructuring
680
+ the removed functions from the hook result; nothing in this repo did.
681
+
682
+ ### Patch Changes
683
+
684
+ - 8ecf5a6: Command palette (⌘K) now surfaces record search hits from the platform's global
685
+ search endpoint (`GET /api/v1/search`).
686
+
687
+ Previously the palette only ran a per-object `find({ $search })` fanout (the
688
+ metadata-driven ADR-0061 search), which misses records that only the global
689
+ search index knows about — so typing a well-known record name returned no
690
+ records even though `/api/v1/search` served them. `ObjectStackAdapter` now
691
+ exposes a `searchAll(query, { limit, objects })` method that calls the unified
692
+ endpoint, `useRecordSearch` prefers it when present (falling back to the fanout
693
+ otherwise), and the palette renders the resulting record hits grouped by object.
694
+
695
+ - 7b35e4b: fix(dashboard,charts): resolve `{current_user_id}` in widget filters (framework #3574)
696
+
697
+ A dashboard widget filtered on `{current_user_id}` rendered `0`. The token
698
+ reached SQL as a literal, matched no row, and nothing was logged on the client
699
+ or the server — a silent zero that reads as "you have no work" rather than
700
+ "this filter did not resolve". The same token in a list-view filter resolved
701
+ correctly, so a user-scoped list and a user-scoped widget over the same data
702
+ disagreed.
703
+
704
+ There was no shared resolver. Three ad-hoc implementations had grown up
705
+ independently — `ObjectView` for list views, `ObjectDataPage` for URL filter
706
+ triples, `NavigationRenderer` for hrefs — and each understood only the filter
707
+ shape its own surface used. `ObjectView`'s opened with
708
+ `if (!Array.isArray(filter)) return filter`, so it could not have been reused
709
+ by dashboard widgets even in principle: widget filters are MongoDB-style
710
+ objects. Widgets therefore got no resolution at all — `DatasetWidget` called
711
+ `resolveDateMacros` and nothing else, which is why `{today}` worked in a widget
712
+ and `{current_user_id}` silently did not.
713
+
714
+ - **`@object-ui/core`** — new `utils/filter-tokens.ts` with
715
+ `resolveContextTokens` and `resolveFilterPlaceholders`. The latter expands
716
+ _every_ placeholder vocabulary in one call and is what surfaces should use;
717
+ resolving only some of them is the whole defect. The walk handles arrays and
718
+ plain objects uniformly, so one resolver covers both platform filter shapes.
719
+ - **`@object-ui/react`** — new `FilterScopeProvider` / `useFilterScope`. The
720
+ renderer packages deliberately do not depend on `@object-ui/auth`, so the
721
+ shell supplies the session values. This is a separate context from
722
+ `PredicateScopeContext`, which is the expression evaluation scope and carries
723
+ no organization.
724
+ - **`@object-ui/plugin-dashboard` / `@object-ui/plugin-charts`** — all six
725
+ widgets that previously resolved date macros only now resolve both
726
+ vocabularies: `DatasetWidget`, `ObjectMetricWidget`, `ObjectDataTable`,
727
+ `ObjectPivotTable`, and `ObjectChart` (dataset-bound and inline paths). The
728
+ chart's `compareTo` comparison filter gets the session pass too — otherwise
729
+ the overlay series silently ignored the owner clause the primary series
730
+ honoured.
731
+ - **`@object-ui/app-shell`** — `ObjectView`'s local `substituteFilterTokens`
732
+ and `ObjectDataPage`'s inline `=== '{current_user_id}'` ternary now delegate
733
+ to the shared resolver, so both also gain `{current_org_id}` and date macros.
734
+ Two of the three ad-hoc implementations are gone rather than joined by a
735
+ fourth.
736
+
737
+ An unresolvable token is left intact rather than dropped: leaving it yields an
738
+ empty result, whereas dropping the clause would _widen_ the result set and show
739
+ a signed-out viewer everyone's data. It is no longer silent — the resolver
740
+ warns, naming the token, and suggests the intended spelling for known
741
+ near-misses (`{current_user}`, `{user_id}`, `{organization_id}`). Authoring-time
742
+ enforcement lands separately as `filter-token-unknown` in `@objectstack/lint`.
743
+
744
+ - Updated dependencies [0b3be01]
745
+ - Updated dependencies [3c4d935]
746
+ - Updated dependencies [4b60d2d]
747
+ - Updated dependencies [952b978]
748
+ - Updated dependencies [de5e40c]
749
+ - Updated dependencies [1a03af6]
750
+ - Updated dependencies [3e886eb]
751
+ - Updated dependencies [cfc675e]
752
+ - Updated dependencies [20df08c]
753
+ - Updated dependencies [1767124]
754
+ - Updated dependencies [8ecf5a6]
755
+ - Updated dependencies [af705b9]
756
+ - Updated dependencies [0502a7c]
757
+ - Updated dependencies [7b35e4b]
758
+ - Updated dependencies [e16ed2d]
759
+ - Updated dependencies [c6fd752]
760
+ - Updated dependencies [f9bbddb]
761
+ - Updated dependencies [dfd3705]
762
+ - Updated dependencies [2735de6]
763
+ - Updated dependencies [6dee2cb]
764
+ - Updated dependencies [e05f052]
765
+ - Updated dependencies [0502a7c]
766
+ - Updated dependencies [faad45e]
767
+ - Updated dependencies [09c6a17]
768
+ - Updated dependencies [c7cff19]
769
+ - Updated dependencies [ba73a02]
770
+ - Updated dependencies [cd09a7b]
771
+ - Updated dependencies [f1abf0e]
772
+ - Updated dependencies [f05b84e]
773
+ - Updated dependencies [9b4b952]
774
+ - Updated dependencies [7d46648]
775
+ - Updated dependencies [6e8fd3c]
776
+ - Updated dependencies [bb4aa25]
777
+ - Updated dependencies [75f1cdf]
778
+ - Updated dependencies [662bdf9]
779
+ - Updated dependencies [059a052]
780
+ - Updated dependencies [53642d4]
781
+ - Updated dependencies [8aae006]
782
+ - Updated dependencies [d62fb1f]
783
+ - Updated dependencies [d147a13]
784
+ - Updated dependencies [c6aaed8]
785
+ - Updated dependencies [263f885]
786
+ - Updated dependencies [dc334da]
787
+ - @object-ui/i18n@17.0.0
788
+ - @object-ui/types@17.0.0
789
+ - @object-ui/data-objectstack@17.0.0
790
+ - @object-ui/core@17.0.0
791
+
3
792
  ## 16.1.0
4
793
 
5
794
  ### Minor Changes
@@ -63,7 +852,7 @@
63
852
 
64
853
  Together with the `^15.1.1` alignment (#2662), a renderer CEL predicate now
65
854
  reaches the identical verdict as the server — including the framework's
66
- `dateField == today()` equality fix (objectstack-ai/framework#3205) once it
855
+ `dateField == today()` equality fix (objectstack-ai/objectstack#3205) once it
67
856
  lands in a published 15.x. The broader home-grown-vs-canonical divergence
68
857
  motivation is #2661.
69
858