@object-ui/react 17.6.0 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4263 -0
- package/README.md +383 -42
- package/dist/SchemaRenderer.d.ts +64 -7
- package/dist/SchemaRenderer.js +1326 -106
- package/dist/context/ActionContext.js +40 -1
- package/dist/context/AppShellContext.d.ts +27 -0
- package/dist/context/DiscussionContext.d.ts +27 -0
- package/dist/context/DiscussionContext.js +4 -0
- package/dist/context/DrillNavigationContext.d.ts +13 -0
- package/dist/context/InlineEditContext.d.ts +151 -1
- package/dist/context/InlineEditContext.js +36 -2
- package/dist/context/PrintPageContext.d.ts +60 -0
- package/dist/context/PrintPageContext.js +32 -0
- package/dist/context/RecordContext.d.ts +48 -5
- package/dist/context/RecordContext.js +10 -1
- package/dist/context/RelatedRecordActionsContext.d.ts +72 -2
- package/dist/context/RelatedRecordActionsContext.js +4 -0
- package/dist/context/SchemaRendererContext.d.ts +61 -6
- package/dist/context/SchemaRendererContext.js +39 -6
- package/dist/context/ThemeContext.js +1 -1
- package/dist/context/actionRunnerTranslator.d.ts +27 -0
- package/dist/context/actionRunnerTranslator.js +37 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +1 -0
- package/dist/element-data-source/ElementApiDataSourcePanel.d.ts +28 -0
- package/dist/element-data-source/ElementApiDataSourcePanel.js +10 -0
- package/dist/element-data-source/ElementDataSourceGate.d.ts +118 -3
- package/dist/element-data-source/ElementDataSourceGate.js +401 -19
- package/dist/element-data-source/useElementApiDataSource.d.ts +119 -0
- package/dist/element-data-source/useElementApiDataSource.js +198 -0
- package/dist/hooks/index.d.ts +3 -1
- package/dist/hooks/index.js +10 -1
- package/dist/hooks/useActionEngine.js +46 -3
- package/dist/hooks/useActionRunner.js +4 -0
- package/dist/hooks/useActionTextLocalizer.d.ts +35 -9
- package/dist/hooks/useActionTextLocalizer.js +70 -14
- package/dist/hooks/useConfigBagEvaluator.d.ts +31 -0
- package/dist/hooks/useConfigBagEvaluator.js +40 -0
- package/dist/hooks/useDatasetDimensionLabels.js +25 -10
- package/dist/hooks/useDensityMode.d.ts +8 -2
- package/dist/hooks/useDensityMode.js +8 -2
- package/dist/hooks/useDiscovery.d.ts +1 -10
- package/dist/hooks/useETagCache.js +25 -4
- package/dist/hooks/useElementDataSource.d.ts +15 -2
- package/dist/hooks/useElementDataSource.js +24 -7
- package/dist/hooks/useExpression.d.ts +52 -59
- package/dist/hooks/useExpression.js +58 -90
- package/dist/hooks/useFilterScope.d.ts +22 -2
- package/dist/hooks/useFilterScope.js +29 -4
- package/dist/hooks/useGlobalUndo.d.ts +10 -0
- package/dist/hooks/useGlobalUndo.js +112 -46
- package/dist/hooks/useNavigationOverlay.d.ts +76 -11
- package/dist/hooks/useNavigationOverlay.js +119 -9
- package/dist/hooks/useOffline.js +65 -11
- package/dist/hooks/usePageAssignment.d.ts +33 -7
- package/dist/hooks/usePageAssignment.js +85 -21
- package/dist/hooks/useRecordSearch.d.ts +34 -1
- package/dist/hooks/useRecordSearch.js +42 -7
- package/dist/hooks/useResolvedFilter.d.ts +46 -0
- package/dist/hooks/useResolvedFilter.js +130 -0
- package/dist/hooks/useSchemaPersistence.d.ts +7 -0
- package/dist/hooks/useSchemaPersistence.js +144 -5
- package/dist/hooks/useSettledSchema.d.ts +123 -0
- package/dist/hooks/useSettledSchema.js +186 -0
- package/dist/hooks/useViewData.d.ts +10 -3
- package/dist/hooks/useViewData.js +5 -14
- package/dist/index.d.ts +9 -1
- package/dist/index.js +48 -1
- package/dist/schema-input.d.ts +38 -8
- package/dist/schema-input.js +39 -7
- package/dist/utils/configBag.d.ts +68 -0
- package/dist/utils/configBag.js +70 -0
- package/dist/utils/error-message.d.ts +1 -1
- package/dist/utils/error-message.js +1 -1
- package/dist/utils/inlineAria.d.ts +61 -0
- package/dist/utils/inlineAria.js +70 -0
- package/dist/utils/localeMap.d.ts +27 -0
- package/dist/utils/localeMap.js +106 -0
- package/dist/utils/nonGridRowCeiling.d.ts +44 -0
- package/dist/utils/nonGridRowCeiling.js +78 -0
- package/dist/utils/paramsBag.d.ts +28 -0
- package/dist/utils/paramsBag.js +146 -0
- package/dist/utils/propsBagDiagnostic.d.ts +196 -0
- package/dist/utils/propsBagDiagnostic.js +422 -0
- package/dist/utils/refusedDataPropDiagnostic.d.ts +44 -0
- package/dist/utils/refusedDataPropDiagnostic.js +94 -0
- package/dist/utils/unevaluatedExpression.d.ts +44 -1
- package/dist/utils/unevaluatedExpression.js +92 -11
- package/dist/utils/visibilityDiagnostic.d.ts +282 -21
- package/dist/utils/visibilityDiagnostic.js +321 -46
- package/package.json +10 -10
- package/dist/hooks/useClientNotifications.d.ts +0 -47
- package/dist/hooks/useClientNotifications.js +0 -162
- package/dist/spec-bridge/SpecBridge.d.ts +0 -28
- package/dist/spec-bridge/SpecBridge.js +0 -56
- package/dist/spec-bridge/bridges/form-view.d.ts +0 -158
- package/dist/spec-bridge/bridges/form-view.js +0 -170
- package/dist/spec-bridge/bridges/list-view.d.ts +0 -20
- package/dist/spec-bridge/bridges/list-view.js +0 -268
- package/dist/spec-bridge/index.d.ts +0 -11
- package/dist/spec-bridge/index.js +0 -10
- package/dist/spec-bridge/types.d.ts +0 -55
- package/dist/spec-bridge/types.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,4268 @@
|
|
|
1
1
|
# @object-ui/react
|
|
2
2
|
|
|
3
|
+
## 18.0.0
|
|
4
|
+
|
|
5
|
+
### Dependency floors
|
|
6
|
+
|
|
7
|
+
Computed from `.objectstack-sha` `06cab4849a8a`: the earliest published `@objectstack/spec` that contains it is `18.0.0`.
|
|
8
|
+
|
|
9
|
+
- `@objectstack/spec` (`dependencies`): `^18.0.0-next.1` → `^18.0.0`
|
|
10
|
+
|
|
11
|
+
### Major Changes
|
|
12
|
+
|
|
13
|
+
- 6f3299f: objectui moves to major 18, following `@objectstack/*` from 17 to 18 (objectui#12334). objectui's major is not a count of its own breaking changes: it tracks the major of the `@objectstack/spec` its packages depend on, so that the same major means compatible across the two projects. These packages already declare `@objectstack/*` on the 18 line (objectui#12030), so every package in the fixed release group crosses to 18 together. It is the one `major` that rule admits: objectui's own breaking changes ship as `minor`, described in their entries.
|
|
14
|
+
|
|
15
|
+
The 18 line is released from Changesets pre mode on the `next` tag (`.changeset/pre.json`), as `@objectstack/*` 18 is: these releases are `18.0.0-next.N` on the npm `next` dist-tag, and they do not move `latest`. The first `18.0.0` on `latest` comes when `@objectstack` 18 is generally available and this repository leaves pre mode.
|
|
16
|
+
|
|
17
|
+
What a consumer must do to take it is in the `@objectstack/*` 18 entry of this release (objectui#12030): move its own `@objectstack/*` packages to the 18 line at the same time.
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- fd060f0: `ElementDataSourceGate` takes a `requiresObject` prop: when a placement opts in
|
|
22
|
+
and its node names its object in neither place, the gate renders a short "no
|
|
23
|
+
object named" hint instead of the block (objectui#11605).
|
|
24
|
+
|
|
25
|
+
The object-bound registrations stopped declaring their object key required,
|
|
26
|
+
because the `dataSource` binding can supply it and the page compile has no "this
|
|
27
|
+
key or that binding" form. So the page compile accepts a node that names no
|
|
28
|
+
object at all, and the runtime's answer is the only signal left for it. Several
|
|
29
|
+
blocks answered such a node with an empty list, board, form, chart, dash or pivot,
|
|
30
|
+
which reads as an empty query.
|
|
31
|
+
|
|
32
|
+
**Clause-②: yes (widening)** — `ElementDataSourceGateProps` gains the optional
|
|
33
|
+
`requiresObject` boolean. With it set, the gate reads the mapping's object key
|
|
34
|
+
(`objectName` unless the mapping names another) on the node after the binding
|
|
35
|
+
lands, so a node bound by `dataSource.object` renders as before; a node that names
|
|
36
|
+
no object gets the hint, "No object named: set objectName or dataSource.object."
|
|
37
|
+
(`data-testid` `{testId}-no-object`), and the block is not mounted. Without the
|
|
38
|
+
prop, or with a mapping whose `object` is `false`, nothing changes. The hint is
|
|
39
|
+
drawn after the view states, so a binding that is still resolving or failed to
|
|
40
|
+
resolve keeps its own panel.
|
|
41
|
+
- 2312bd9: A page that declares `print` now prints as a document, and a record page bound to such a page offers a Print action (objectui#11958, the objectui half of the print-page ruling on objectstack#8346, M1, zero server).
|
|
42
|
+
|
|
43
|
+
- **The print stylesheet.** `PageRenderer` mounts a stylesheet built from the page's `print` declaration, only for a page that declares it. Every rule sits inside `@media print`, so the page on screen is drawn as before. `paperSize` and `orientation` set `@page { size }` (A4 and portrait when omitted). `margins` set one `@page` margin per declared side, in millimetres. `pageNumbers` prints "page / pages" in the bottom-right margin. `repeatHeader` and `repeatFooter` repeat the page's own `header` and `footer` regions on every sheet. `repeatTableHeaders` (on when omitted) repeats a table's column headings on every sheet it runs onto. `avoidBreakInside` keeps each block of a region whole on one sheet when it fits. Only the print page reaches paper: the console chrome and the record view's own panels around the page are hidden, and the page runs onto as many sheets as it needs.
|
|
44
|
+
- **The printable blocks print whole.** On a print page, `record:line_items` no longer cuts lines off at its row cap in silence: when the child object has more lines than the cap (500 unless `limit` is set), the panel draws the cap's lines and the shared row-ceiling note under them, naming both numbers when the server reports a total. A collapsible `record:details` section draws open, because a closed section never reaches paper. Off a print page both blocks behave as before.
|
|
45
|
+
- **The record page's Print action.** When the record page bound to a record declares `print`, the record view shows a Print button that opens the browser's print preview of that page.
|
|
46
|
+
- **New exports from `@object-ui/react`:** `PrintPageProvider`, `usePrintPage` and `PrintPageContext`. `usePrintPage()` returns the enclosing page's `print` declaration, or `null` outside a print page.
|
|
47
|
+
- The source comments that described PDF export as declined now say where a printable document lives: a page that declares `print`.
|
|
48
|
+
- c5406c2: Depend on the `@objectstack/*` 18 prerelease line (objectui#12030).
|
|
49
|
+
|
|
50
|
+
Every `@objectstack/*` range these packages declare in `dependencies` or `peerDependencies` moves from `^17.x` to `^18.0.0-next.0`, the `next` prerelease of `@objectstack/*` 18 (raised to `^18.0.0-next.1` by objectui#12370 before release).
|
|
51
|
+
|
|
52
|
+
**Breaking for consumers:** an install that resolves `@objectstack/spec` (or `@objectstack/client`, `@objectstack/formula`, `@objectstack/lint`) 17.x no longer satisfies these ranges. A consumer must move its own `@objectstack/*` packages to the 18 line, starting with `18.0.0-next.1` (objectui#12370), at the same time as it takes this release.
|
|
53
|
+
|
|
54
|
+
**objectui's own surface moves with it:**
|
|
55
|
+
|
|
56
|
+
- `element:record_picker`'s registration stops publishing the `object`, `filter`, `sort` and `limit` inputs, which spec 18 retires; a JSX page that writes one now gets `unknown-prop`.
|
|
57
|
+
- The `CapabilityMultiSelectField` widget's curated capability set (`@object-ui/fields`) follows spec 18: `manage_mailboxes` in, `manage_org_presentation` out.
|
|
58
|
+
- `element:number` and `element:repeater` nodes that bind no object are accepted, as spec 18's row and `PageComponentSchema` accept them; the two `ELEMENT_*_OBJECT_REQUIRED` refusals are gone.
|
|
59
|
+
|
|
60
|
+
**Retired with the spec:** `@objectstack/spec` 18 tombstones `object-grid`'s `defaultFilters` and refuses it by name, and `ObjectGrid` and `ObjectView` stop reading it (`table.defaultFilters` on an `object-view` included). A view that still carries it renders unfiltered: rename the key to `filter`, whose value is the same rule array.
|
|
61
|
+
- c4dd707: feat(react): a page element's API data source is rendered — `dataSource: { provider: 'api', read: { url }, path? }` binds the selected answer as `data` (objectui#12197)
|
|
62
|
+
|
|
63
|
+
Clause-②: yes
|
|
64
|
+
|
|
65
|
+
`SchemaRenderer` now honours `@objectstack/spec`'s `ElementApiDataSourceSchema` on every node. It reads the same-origin `read.url` with `GET` through `SchemaRendererProvider`'s `apiFetch` (the global `fetch` when no host supplies one), selects `path` from the answer, and binds the value as `data` in the node's `${…}` holes and its `visibleWhen`, and in its subtree's. While the read is in flight the node draws a loading line; a failed read, or a `path` that selects nothing, draws an error panel naming why, titled in the page's language from two new keys, `element.component.apiRefused` and `element.component.apiUnreadable` (all ten packs). The binding is parsed with the spec's own schema first: a cross-origin or protocol-relative `url`, a credential key, or a non-`GET` method is refused with the spec's message and no request is sent. Elements binding the same URL at once share one request; nothing is cached.
|
|
66
|
+
|
|
67
|
+
New exports: `useElementApiDataSource`, `judgeElementApiDataSource`, `isElementApiDataSourceShape`, `selectElementApiPath`, `readElementApi`, and the types `ElementApiDataSourceStatus` and `UseElementApiDataSourceResult`. A node without an API binding renders as before and issues no request.
|
|
68
|
+
- 5c2982a: A record whose `approval_status` reads `pending` no longer opens locked when the approvals read answered and found no request (objectui#12220).
|
|
69
|
+
|
|
70
|
+
A composition without the approvals service answers `GET /api/v1/approvals/requests?object=…&recordId=…` with `200 { "data": [] }` and enforces no lock: a `PATCH` to the record succeeds. The record page still showed "Locked for approval", disabled Edit and offered Recall, which had nothing to recall, because the page and the detail body's approval band fell back to the literal `approval_status` column whenever no pending request was found. Records that an app stamped `pending` with no request behind them stayed locked in the console.
|
|
71
|
+
|
|
72
|
+
- **The rule:** when the approvals read answers, the lock follows the open request. No pending request means no approval band, no lock and no Recall. The `approval_status` column stands in only while no read has answered: the route is absent (404 or 501), the read has not landed yet, or the first read failed. A record with an open request is unchanged, including a `lockRecord: false` node, which stays editable.
|
|
73
|
+
- **`@object-ui/react` adds** `resolveApprovalLock`, the one function the record page and the band both call, with its `ApprovalLockInput` and `ApprovalLockState` types. It also adds an optional `approvalReadAnswered` prop on `InlineEditProvider`, read back on `InlineEditContextValue`. A host sets it when its `locked` and `approvalPending` come from an approvals read that answered. Omitted, the band keeps the column fallback exactly as before.
|
|
74
|
+
- **`@object-ui/app-shell`:** the record page sets `approvalReadAnswered` from its approvals read.
|
|
75
|
+
- **`@object-ui/plugin-detail`:** the band decides through `resolveApprovalLock`.
|
|
76
|
+
|
|
77
|
+
Nothing is removed or renamed.
|
|
78
|
+
- 7af0ab3: The author of a comment on a record page can now edit and delete it from the record feed (objectui#12268).
|
|
79
|
+
|
|
80
|
+
`sys_comment` declares the generic Edit and Delete affordances (`managedBy: 'platform'`), and the platform lets an author update and delete their own comment, but the record feed only offered reactions and replies. Now the author's own comment and reply show Edit and Delete. Nobody else sees either control on it.
|
|
81
|
+
|
|
82
|
+
- **Edit** opens the comment's text in place. Save stores the new text and marks the comment edited; the row then shows the new text with the usual "(edited)" mark. If the server refuses, an error appears, the editor keeps the draft, and the comment keeps its previous text. A comment stored as edited reads as edited.
|
|
83
|
+
- **Delete** asks first, through the console's usual destructive confirm dialog. Cancel deletes nothing. If the server refuses, an error appears and the comment stays.
|
|
84
|
+
- **Replies outlive their comment on screen.** A reply whose comment the feed does not hold, because that comment was deleted or lies before the loaded page, now shows as a row of its own instead of disappearing, so deleting a comment no longer hides anyone's replies to it.
|
|
85
|
+
- Each control appears only with the matching permission on `sys_comment` (update for Edit, delete for Delete). The server's own rule still decides every write.
|
|
86
|
+
|
|
87
|
+
Published surface, all additive:
|
|
88
|
+
|
|
89
|
+
- `@object-ui/collaboration`: new export `isCommentAuthor(authorId, viewerId)`, the owner rule `CommentThread` gates Edit and Delete on. Two missing or empty ids no longer count as a match.
|
|
90
|
+
- `@object-ui/types`: `FeedItem.authoredByViewer?: boolean`, set by the host that read the row.
|
|
91
|
+
- `@object-ui/react`: `DiscussionContextValue.onEditComment` and `onDeleteComment`.
|
|
92
|
+
- `@object-ui/plugin-detail`: `onEditComment` and `onDeleteComment` on `RecordActivityTimelineProps`, `RecordChatterPanelProps` and `ThreadedRepliesProps`. A reply now shows the "(edited)" mark too.
|
|
93
|
+
- `@object-ui/core`: two rows in the affordance-to-grant map, `feedCommentEdit` and `feedCommentDelete`.
|
|
94
|
+
- `@object-ui/i18n`: five new `detail.*` keys in all ten language packs (`editComment`, `deleteComment`, `deleteCommentConfirm`, `commentEditFailed`, `commentDeleteFailed`).
|
|
95
|
+
- 524136e: The view-level `provider: 'api'` data source is no longer read anywhere in objectui (objectui#12325). `@objectstack/spec` retires it (objectstack#23082, ADR-0049): a view binds its object (`provider: 'object'`) or static rows (`provider: 'value'`), and a metadata-declared API call is the page element's `dataSource` binding.
|
|
96
|
+
|
|
97
|
+
Breaking, declared as `minor` per the version-alignment rule:
|
|
98
|
+
|
|
99
|
+
- **`resolveDataSource`** (`@object-ui/core`) has no `api` arm. A block that still says `provider: 'api'` is an unknown provider and gets an unknown provider's answer (the fallback DataSource, else `null`); no `ApiDataSource` is built from its `read` / `write`. `ResolveDataSourceOptions` loses `fetch` and `defaultHeaders`, which only that arm read.
|
|
100
|
+
- **`ViewDataProvider`** (`@object-ui/core`) has no `api` arm: such a config resolves with `Unknown data provider: api`. `ViewDataConfig` and `ResolvedData.provider` drop `'api'`, and `DataFetcher.fetchUrl`, which only that arm called, is removed.
|
|
101
|
+
- **`useViewData`** (`@object-ui/react`) no longer keys on or threads anything for an `api` block.
|
|
102
|
+
- **`ObjectGantt`** reads no `api` block: its `read` URL is never requested, and the chart answers it like any unknown provider. `ObjectCalendar` and `ObjectMap` drop their `api` branches, which only warned "not yet implemented".
|
|
103
|
+
- **`ListView`** no longer stands down for a gantt view with an `api` block: it fetches the bound object like any other list.
|
|
104
|
+
|
|
105
|
+
Unchanged: `ApiDataSource` stays a class for code that constructs it from an `ApiDataSourceConfig`, so the console's approvals inbox reads exactly as before.
|
|
106
|
+
- ff9df88: Depend on `@objectstack/*` `18.0.0-next.1` (objectui#12370).
|
|
107
|
+
|
|
108
|
+
Every `@objectstack/*` range these packages declare moves from `^18.0.0-next.0` to `^18.0.0-next.1`; the console and the runner bundle `18.0.0-next.1`. A consumer moves its own `@objectstack/*` packages to `18.0.0-next.1` with this release.
|
|
109
|
+
|
|
110
|
+
**Accept sets that narrow with it.** `@object-ui/types` validates page action rows and view data bindings through the spec's own rows, so its faces (`safeValidateSchema` and `StrictAnyComponentSchema`) now refuse what `18.0.0-next.1` refuses. Each was accepted on `18.0.0-next.0`:
|
|
111
|
+
|
|
112
|
+
- `onSuccess` on an `action:button` / `action:icon` row, or an `action:group` / `action:menu` member, whose executor is the `url`, `navigation`, `modal`, `flow` or `form` built-in, or that has no executor at all. Write the hop on an `api` or `script` action (or a host handler name), or drop it.
|
|
113
|
+
- `onSuccess` beside `operation: 'update'`, and an `onSuccess` block carrying a key outside `{ navigate, openIn }`.
|
|
114
|
+
- `actionType: 'navigation'` on a row (and `type: 'navigation'` on a container member): write `'url'`.
|
|
115
|
+
- A `url` executor's `target` that is not openable, judged on the executor the runner runs, including a row whose `name` selects it.
|
|
116
|
+
- A view `data` binding with `provider: 'api'`: bind the object or static rows; an API call is the page element's binding.
|
|
117
|
+
- 611566b: `SchemaRenderer` no longer reads a page component's `visibility` key (objectui#12654). `@objectstack/spec` 18 retires it as a `retiredKey` tombstone naming `visibleWhen` (objectstack#23543 F4), so `visibleWhen` is the one conditional-visibility key the renderer reads for it.
|
|
118
|
+
|
|
119
|
+
What a raw schema loses: a node handed straight to `SchemaRenderer`, without passing through the spec's parse, that carries only `visibility: 'PREDICATE'` is no longer hidden when the predicate is false; it now draws, and the `visibility` value is no longer stripped before the component's props. Write `visibleWhen` instead. The `SchemaRenderer` working-copy type no longer declares `visibility`.
|
|
120
|
+
|
|
121
|
+
What is unaffected: metadata served by an ObjectStack runtime. A served page row already reaches the renderer with `visibleWhen`: the spec's parse folds the old key through the 18 pre-releases, and spec 18's stored-row rehydration rewrites a row stored with it. `BaseSchema`'s own deprecated `visibleOn` is a different key and is still read.
|
|
122
|
+
- 71243db: A navigation entry whose `visible` predicate cannot be evaluated is no longer drawn for everyone (objectui#12700).
|
|
123
|
+
|
|
124
|
+
The shell's navigation surfaces (the sidebar, the app root's landing, the activity roster read from app navigation, the `nav:menu` block and the command palette's navigation list) answer a FAULTING `visible` predicate by withholding the entry, where they used to draw it. The case that made this matter is a `current_user.can("OBJECT", "read")` gate asked where no `/auth/me/permissions` payload is bound: the expression engine refuses it, and the shell used to read that refusal as "shown", so permission-gated navigation hid nothing there. Where the payload is bound, the gate answers from it as before: a member without the grant is not shown the entry and one with it is. The fault is still reported once per predicate, now with a consequence line saying the entry was withheld. A blank gate is still "no gate" and is drawn. This is UI gating, not a security boundary: the server enforces what a member may read.
|
|
125
|
+
|
|
126
|
+
`@object-ui/react`: `PredicateGateKind` gains a `'withholding'` member, the consequence line for a visibility gate whose fault is answered by withholding the entry. `@object-ui/app-shell`'s published `evaluateVisibility` keeps its fail-open answer; the object-field gate uses it unchanged.
|
|
127
|
+
- 33ee9de: A `list-view` block drawn as a compact list in a narrow page region now shows a "View all" link under its rows (objectui#12703).
|
|
128
|
+
|
|
129
|
+
- `@object-ui/react`: `RelatedRecordActionsValue` gains two optional members, `listHref(objectName, viewName?)` and `openList(objectName, viewName?)`. They address an object's list page, or one of its saved views, the way `recordHref` / `openRecord` address a record. A host that omits them is unchanged.
|
|
130
|
+
- `@object-ui/app-shell`: a custom page supplies them. The routes are `/apps/APP/OBJECT/view/VIEW` for a block bound to a saved view (`dataSource.view`) and `/apps/APP/OBJECT` otherwise. The block's inline filter, columns and sort are never put in the URL.
|
|
131
|
+
- `@object-ui/plugin-list`: the compact list draws the link only when the host answers a destination. It is a real anchor with in-app navigation on a plain click.
|
|
132
|
+
- `@object-ui/i18n`: one new key, `list.viewAll`, in all ten packs.
|
|
133
|
+
- c7462e7: New export `isRecordPageOf(page, objectName)` (objectui#12715): true when `page` is a record page bound to `objectName` (`type: 'record'` and `object` names it). It is the one reading of "a record page of object O": `usePageAssignment`'s object-based lookup now filters its candidates with it, unchanged in behaviour, and the console's named record page route asks it before it renders a named page for a record.
|
|
134
|
+
|
|
135
|
+
`usePageAssignment`'s `pageName` option is unchanged: it still returns the page with that name whatever its type or object, so a caller that renders a record on the page it gets back asks `isRecordPageOf` first.
|
|
136
|
+
- 8df2608: `RelatedRecordHandlers` gains an optional `onViewAll` (objectui#12741). A host returns it from `resolve` to open the child object's full list page, scoped to the parent on the relationship it was handed. `record:related_list` calls it from its "View all" link. A host that omits it draws no link, as for every other handler on this interface.
|
|
137
|
+
|
|
138
|
+
Clause-② line, the same as the pull request's:
|
|
139
|
+
Clause-②: yes
|
|
140
|
+
One optional member is added to the exported `RelatedRecordHandlers` interface. Nothing else on the package entry changes.
|
|
141
|
+
- 5ff64ef: feat(react): `MetadataContextValue.readObjectScope`, the object definitions one app reads (objectui#12811)
|
|
142
|
+
|
|
143
|
+
A new optional member of the metadata context. `readObjectScope({ packageId, names })` answers the
|
|
144
|
+
objects of one package, read with `GET /api/v1/meta/object?package=ID`, plus each of `names` that the
|
|
145
|
+
package does not hold, read by name. Each definition is shaped exactly as the same object in
|
|
146
|
+
`objects`. The answer carries `objects`, `served` (the same definitions as the server served them,
|
|
147
|
+
before views are merged, as `getItemsByType('object')` holds the whole list), `missing` (the names the
|
|
148
|
+
server answered without a definition) and `settled` (the package list and every name have answered). With no `packageId` it is
|
|
149
|
+
the whole registry, as `objects` is.
|
|
150
|
+
|
|
151
|
+
`objects` is unchanged: it is the whole registry. A context value without the member (a hand-rolled
|
|
152
|
+
one in a test, an older host) has nothing narrower to offer, and its readers read `objects`.
|
|
153
|
+
- a7ae252: A `${…}` hole inside an inline locale map is evaluated (objectui#12936). In a node's `properties` bag (or its legacy `props` alias, or a block member evaluated with `useConfigBagEvaluator`), each member of a map such as `title: { en: 'Published ${data.at}', 'zh-CN': '…${data.at}' }` is evaluated like a plain string on the same key: against `record`, `current_user` / `user`, `page`, the host's roots, and `data` on a node with an API binding. A block that picks the viewer's language with `pickLocalized` off its evaluated props, such as `hero`, now draws the bound value instead of the template text, with no change to the block. A member's result is always a string, so the map stays a map.
|
|
154
|
+
|
|
155
|
+
A value counts as a locale map exactly when `@objectstack/spec`'s `InlineLocaleMapSchema` accepts it: every key a locale tag or `default`, every value a string. Any other object keeps the data-object rule and is handed over as authored, with nothing inside it evaluated. A map without a hole arrives as the same object. In development builds, a member whose expression throws keeps its source text and is now reported by the unevaluated-expression diagnostic by its locale (`properties.title.zh-CN`), as a plain string with the same hole is.
|
|
156
|
+
|
|
157
|
+
**Clause-②: yes (widening)** — the renderer honours a shape it used to hand over raw: a hole inside an inline locale map's member. No export is added or removed and no type changes; the set of authored values `SchemaRenderer` evaluates widens by that one shape.
|
|
158
|
+
- 024b7b6: A page route reads the objects its page binds, not the whole object registry (objectui#12980, part 1).
|
|
159
|
+
|
|
160
|
+
Opening a page (`/apps/APP/page/NAME`) used to load every object definition (`GET /api/v1/meta/object`, 751 KB on the control plane), because the route, the page view and three page blocks read `useMetadata().objects`. Now:
|
|
161
|
+
|
|
162
|
+
- **`readObjectScope({ names })` with no `packageId` is a names-only scope** (`@object-ui/react`, `MetadataContextValue.readObjectScope`). Each name is read by name, with no package list. Only a scope with neither `packageId` nor `names` is the whole registry. This widens what the published contract accepts, which is why `@object-ui/react` is a minor bump.
|
|
163
|
+
- **A page route reads the objects its page definition binds**: the page's `object`, its interface source, and every `objectName` and `dataSource.object` on its blocks. They are read by name, beside the app's package list when the app names a package, or through the names-only scope when it does not. A page that binds no object, such as one whose data sources are all `api`, reads none. The chrome around the page gets the same scope.
|
|
164
|
+
- **An object a block asks about outside that set is read by name when it asks.** A link to it is drawn once the object arrives, and opening it navigates once the object is known.
|
|
165
|
+
- **The page blocks:** the interface list page reads its source object by name, `nav:menu`'s capability gate reads the objects its app's entries require by name, and `global:search` reads the registry only once the user types. A record overlay still reads the whole registry, because a record's related lists come from every object that points at it.
|
|
166
|
+
|
|
167
|
+
No export, prop or type member is added.
|
|
168
|
+
- 9e7e1a5: `@object-ui/react` now exports `buildNodeEvaluationScope(predicateScope, boundRecord, pageVariables)`, the one builder of the scope a page node's `visibleWhen` and other values are evaluated against (objectui#12992). It returns four roots: the ambient predicate scope, `current_user` aliased to `user`, the bound row as `record` (only when there is one), and page variables as `page`. `SchemaRenderer` and `evaluateConfigBagInScope` call it as before. `page:tabs` in `@object-ui/components` now calls it for its derived-badge gates and its item-level `visibleWhen` instead of spelling the scope again, so a root added to the builder reaches the tab strip too. No root changes for any caller.
|
|
169
|
+
|
|
170
|
+
### Patch Changes
|
|
171
|
+
|
|
172
|
+
- 92f4e2b: An action's translated copy is now read from ONE bundle node, chosen by the object the action belongs to, the same way `@objectstack/spec` 17.7.0 reads it on the server (objectui#11439). That object is the action's declared `objectName`, else the object whose `actions` embed it.
|
|
173
|
+
|
|
174
|
+
- An action that belongs to an object reads `objects.OBJECT._actions.ACTION.*` only. Copy for it filed under `globalActions.ACTION` no longer applies: its label, confirm text, success message, outcome messages, description, parameters and result dialog show the authored text instead, as they already did everywhere the server translates. Move that copy to `objects.OBJECT._actions.ACTION`, which is where `os validate` asks for it.
|
|
175
|
+
- An action with no object (no `objectName`, not embedded in an object) still reads `globalActions.ACTION.*`.
|
|
176
|
+
- `useActionTextLocalizer` keys on the action's declared `objectName` before the object its caller passes, so an action declared on one object reads that object's copy wherever it is drawn.
|
|
177
|
+
|
|
178
|
+
`useObjectLabel()` and `useActionTextLocalizer()` keep their signatures; no input, export or translation key is added.
|
|
179
|
+
- 92f979d: `usePageAssignment` opens an object's default record page: among the record pages served for the object, the one declared `isDefault: true` is returned, and declaration order decides only when none is (objectui#12195).
|
|
180
|
+
|
|
181
|
+
The hook used to return the first matching record page in served order and never read `PageSchema.isDefault`, so the flag on a shipped page (the platform's `sys_user_detail`, `sys_organization_detail` and `sys_approval_request_detail`, and the showcase's record pages) did nothing. `@objectstack/spec` 18.0.0-next.1 states the rule this follows (maintainer ruling A on objectstack#23015): at most one default record page may exist for an object in a deployment, and a second is refused at `os validate`, at package registration and at metadata save, naming both pages. So the hook adds no tie-break of its own. It reads "the default record page of an object" through the spec's `defaultRecordPageObject`, the same reading those refusals use.
|
|
182
|
+
|
|
183
|
+
**Who sees a different page.** An app or environment that authors its own record page for an object without `isDefault: true`, for an object that also has a served default record page, and whose page came first in the served list. That page used to open; the default opens now. Ruling A keeps two ways to replace a platform record page: an ADR-0005 overlay saved under that page's own name, and host composition (composing the stack without the platform's page). Shipping a second default is not one: the platform refuses it. With no default for an object, nothing changes: the first matching page still opens, and an explicit `pageName` still wins over the default.
|
|
184
|
+
|
|
185
|
+
No type, option or export changes (`Clause-②: no`).
|
|
186
|
+
- 97e7d4e: Lookup links on a record page and on a custom page now carry the console's base path, so the same link works from a left click, a new tab, a middle-click and a copied link (objectui#12233).
|
|
187
|
+
|
|
188
|
+
On a console mounted under a router basename, such as `/_console`, a lookup value rendered as an anchor whose href was the bare route `/apps/APP/OBJECT/record/ID`. On a record page this hit, for example, a lead's converted account or contact and its owner. The same held for a lookup, or a grid's link column, on a custom page. A plain left click worked, because the anchor hands it to the console router and the router adds the basename. Opening the link in a new tab, middle-clicking it or pasting a copied link sent the browser to the bare route on the server, which answered `ENDPOINT_NOT_FOUND`.
|
|
189
|
+
|
|
190
|
+
- `@object-ui/app-shell`: both record-link builders, the record page's and the custom page's, now give lookups an href that includes the router's basename. They read the basename from the router, so a console mounted at the root keeps the bare href. A left click still navigates inside the app, to the same record the href names.
|
|
191
|
+
- `@object-ui/react`: the documentation of `RelatedRecordActionsValue.recordHref` now says the value is a document href that carries the host router's basename, while `openRecord` navigates by the router path. The type is unchanged.
|
|
192
|
+
|
|
193
|
+
Nothing is added to or removed from any package entry.
|
|
194
|
+
- d75f212: Published text no longer teaches the retired view-level `provider: 'api'` data source (objectui#12364). A view binds `provider: 'object'` or `provider: 'value'`; a metadata-declared API call is the page element's `dataSource` binding. Text only: no schema accepts or refuses anything new, and no renderer behaves differently.
|
|
195
|
+
|
|
196
|
+
- **`@object-ui/types`:** the `object-tree` `data` description drops the `api` provider from the providers it names.
|
|
197
|
+
- **`@object-ui/plugin-grid` / `@object-ui/plugin-map`:** the `data` input descriptions drop the `api` provider.
|
|
198
|
+
- **`@object-ui/plugin-calendar` / `@object-ui/plugin-gantt` / `@object-ui/plugin-map`:** the error thrown when no `DataSource` is available now reads `DataSource required for the object provider` (was `DataSource required for object/api providers`). It fires in exactly the same conditions as before.
|
|
199
|
+
- **`@object-ui/react` / `@object-ui/auth`:** the README and docblocks describe `SchemaRendererProvider`'s `apiFetch` as what it is now: the host's authenticated fetch the renderers use for their own reads of the host's API (chart metadata, dataset dimension labels, record-permission probes). It is not a channel for a view's data source.
|
|
200
|
+
- **`@object-ui/app-shell`:** comment wording only in `ConsoleShell`, `useConsoleActionRuntime` and `ObjectView` (these comments ship in the built JS): no behaviour change.
|
|
201
|
+
- **`@object-ui/core`:** docblock wording only on `normalizeListViewSchema` and `resolveRecordSourceObjectName` (these ship in `dist/*.d.ts` and the built JS): no behaviour change.
|
|
202
|
+
- 3eb3ec1: The console's list toolbar no longer has a write into the shared view, and the data-source method that write used is retired (objectui#12504).
|
|
203
|
+
|
|
204
|
+
**BREAKING:** `DataSource.updateViewConfig` is removed from `@object-ui/types`, and `ObjectStackAdapter.updateViewConfig` is removed from `@object-ui/data-objectstack`. The capability gate that only that method read goes with it: `ObjectStackAdapter.setSystemCapabilities`, the `systemCapabilities` option of the adapter constructor and of `createObjectStackAdapter`, `VIEW_CONFIG_CAPABILITY`, `ViewConfigPermissionDeniedError` and `isViewConfigPermissionDeniedError`. A class that implements `DataSource` and keeps an `updateViewConfig` method still compiles, but nothing calls the method any more; an object literal annotated as `DataSource` that carries it is refused as an unknown property. Code that calls `updateViewConfig` through a `DataSource` or an `ObjectStackAdapter`, or imports one of the gate names, no longer compiles. To change a view for everyone, save the view: `createView` / `updateView` on the data source, or the console's view editor, which writes through the runtime metadata seam.
|
|
205
|
+
|
|
206
|
+
Since objectui#12418 and objectui#12474, the toolbar's sort, column layout, density and Hide Fields are each user's own list state, so the toolbar write had no caller. The view editor's explicit save is now the one write path to the shared view, and it is unchanged.
|
|
207
|
+
|
|
208
|
+
Personalization rows that earlier consoles wrote into `/meta/view` stay readable: the overlay marker, `narrowPersonalizationOverlay` and `listViews()` exclusion still apply to them. Nothing new writes such a row.
|
|
209
|
+
|
|
210
|
+
`@object-ui/i18n` drops `console.objectView.viewConfigPermissionDenied` from all ten packs. It was the toast the retired write raised when the gate refused it. The other packages change only in comments and tests that named the retired path.
|
|
211
|
+
- 655e897: `useActionTextLocalizer` resolves every `I18nLabel` copy an action declares (objectui#12757). It used to collapse a per-locale map only for `label` and `successMessage`, and only when the action had a `name`. A `confirmText` or `errorMessage` map, or any map on a nameless action, such as an inline `element:button` action, reached the action runner as an object, so the confirm prompt received an object and the toasts fell back to the runner's generic copy.
|
|
212
|
+
|
|
213
|
+
The localizer now collapses `label`, `confirmText`, `successMessage` and `errorMessage` to the active language on every action, named or not. A translation-bundle entry for a named action still wins over the collapsed copy. `errorMessage` has no bundle key, so the collapse is all it gets. An action whose copy is already plain text comes back unchanged.
|
|
214
|
+
|
|
215
|
+
Clause-②: no. The hook's signature and the package entry are unchanged; no export, prop or i18n key is added or removed.
|
|
216
|
+
- 9753811: Page element blocks draw their loading, empty and error defaults in the page's language (objectui#12889).
|
|
217
|
+
|
|
218
|
+
- **`element:repeater`.** While its query runs it shows the `common.loading` text ("Loading…", "加载中…") instead of an English literal. With no rows it shows `common.noData` ("No data"; it read "No records" before), and when the query fails with no message it shows `list.loadErrorTitle` ("Couldn’t load records"). An authored `emptyText` still wins, and a failure's own message is still shown as thrown.
|
|
219
|
+
- **`element:number`.** While its aggregate runs it draws a skeleton bar the height of the number's line instead of a bare `…` glyph. The bar is a status region named by `common.loading`.
|
|
220
|
+
- **The saved-view placeholder** (`ElementDataSourceLoadingPanel`, drawn by every block bound through a named `view` while it resolves) reads `common.loading` instead of an English "Loading view…".
|
|
221
|
+
|
|
222
|
+
No language-pack key, prop or export is added: all four keys already exist in every pack.
|
|
223
|
+
- 1e6d83b: Data-bound blocks title their "data source could not be resolved" panel in the page's language (objectui#12920, the family of objectui#12910). The panel shows when a block's `dataSource.view` names a view its object does not have. Each block's title now reads `@object-ui/i18n` through `useObjectTranslation`, with the `en` pack's value as the inline default, so a page with no `I18nProvider` draws the same English it drew before.
|
|
224
|
+
|
|
225
|
+
- **Plugin blocks.** `object-calendar`, `object-chart`, `object-metric`, `object-pivot`, `detail-view`, `record:related_list`, `object-form`, `embeddable-form`, `object-master-detail-form`, `record:line_items`, `object-gantt`, `object-grid`, `object-kanban`, `list-view`, `object-map` and `object-timeline` read one `element.KIND.sourceUnresolved` key per block kind. `object-metric` reads `element.number.sourceUnresolved`, the key `element:number` already reads for the same sentence. The three forms share `element.form.sourceUnresolved`. `record:related_list`'s panel for a failed `parentDataSource` read reads `element.relatedList.parentUnreadable` ("This related list’s parent record could not be read").
|
|
226
|
+
- **`@object-ui/react` panel defaults.** `ElementDataSourceErrorPanel` with no `title` reads `element.component.sourceUnresolved`, and `NoDataSourcePanel` with no `title` reads `element.component.noDataSource`. A passed `title` still wins, an empty string included. Props and markup do not change.
|
|
227
|
+
|
|
228
|
+
Clause-②: yes
|
|
229
|
+
|
|
230
|
+
The new keys in `@object-ui/i18n` widen its exported `TranslationKeys`. No prop, input or export of these packages changes.
|
|
231
|
+
- 988aa9f: Charts and dataset widgets read their object's metadata through the data source, so the widgets of one dashboard ask for one object's metadata once (objectui#13039).
|
|
232
|
+
|
|
233
|
+
The chart's category colours and dimension labels come from the bound object's field options. It read them with its own request to `/api/v1/meta/object/NAME`, outside the data source's metadata cache, so every chart over one object sent one more request for the same document. The chart now reads it through the data source's `getObjectSchema` when one is in scope: the read that its own group-by labels, the metric tile and the data table already use, and that the ObjectStack adapter joins while it is in flight. With no data source, or one that cannot answer `getObjectSchema`, the chart reads the document directly as before.
|
|
234
|
+
|
|
235
|
+
The dataset label hook in `@object-ui/react` (`useDatasetDimensionMeta`, behind `useDatasetDimensionLabels`) made the same per-widget request, one for every non-metric dataset widget on a dashboard and for the dataset report block. It now reads through the `SchemaRendererProvider`'s data source in the same way when that source answers `getObjectSchema`, and reads the document directly otherwise.
|
|
236
|
+
|
|
237
|
+
Colours, labels and dotted dimension paths resolve as they did. Nothing is added to the package entry.
|
|
238
|
+
- d11174a: A list's `addRecord` Add button opens the create form on a console page and on the console's object list (objectui#13089).
|
|
239
|
+
|
|
240
|
+
- **Page `list-view` nodes.** The console's page host now serves the record-form create to a list that names no `relationshipField` and has no parent: a page `list-view` node. So `addRecord: { enabled: true }` draws its Add button, and a click opens the create form for the listed object. A related list whose parent did not resolve is still served nothing, so it draws no "+ New" that would open a child form linked to no parent. No Edit, Delete or row click is served to a list without a parent.
|
|
241
|
+
- **`list-view.addRecord` is a published input** of the `list-view` and `list` registrations. `enabled` and `position` are read; `mode` and `formView` are not.
|
|
242
|
+
- **The console's object list.** A saved view that declares `addRecord.enabled` used to draw an Add button whose click did nothing, also for a viewer who may not create. The button now opens the header New's create, and is withheld wherever that New is not offered or is greyed.
|
|
243
|
+
- **`editMode: 'page'`.** A create linked to no parent, from the record-actions bridge, now opens an `editMode: 'page'` object's full-page create route, as the object list does, instead of the overlay.
|
|
244
|
+
- **`ResolveRelatedRecordActionsInput`** (`@object-ui/react`) documents what an input without `relationshipField` asks for: an object-scoped list's handlers. No type or member changes.
|
|
245
|
+
- Updated dependencies [18d7b48]
|
|
246
|
+
- Updated dependencies [172acc3]
|
|
247
|
+
- Updated dependencies [f0496bd]
|
|
248
|
+
- Updated dependencies [c4c506b]
|
|
249
|
+
- Updated dependencies [9db9ff3]
|
|
250
|
+
- Updated dependencies [d92b2a1]
|
|
251
|
+
- Updated dependencies [92f4e2b]
|
|
252
|
+
- Updated dependencies [b10c68e]
|
|
253
|
+
- Updated dependencies [bdc9049]
|
|
254
|
+
- Updated dependencies [e8c0b96]
|
|
255
|
+
- Updated dependencies [b92329c]
|
|
256
|
+
- Updated dependencies [2e818d0]
|
|
257
|
+
- Updated dependencies [fd060f0]
|
|
258
|
+
- Updated dependencies [8b14aec]
|
|
259
|
+
- Updated dependencies [2abec3a]
|
|
260
|
+
- Updated dependencies [9dfaca6]
|
|
261
|
+
- Updated dependencies [7b17705]
|
|
262
|
+
- Updated dependencies [fc3c2cc]
|
|
263
|
+
- Updated dependencies [f9f4a62]
|
|
264
|
+
- Updated dependencies [848ba0e]
|
|
265
|
+
- Updated dependencies [57d82cb]
|
|
266
|
+
- Updated dependencies [de96f3d]
|
|
267
|
+
- Updated dependencies [9ca3cac]
|
|
268
|
+
- Updated dependencies [c910630]
|
|
269
|
+
- Updated dependencies [ce464d9]
|
|
270
|
+
- Updated dependencies [e6dcd85]
|
|
271
|
+
- Updated dependencies [96fcf30]
|
|
272
|
+
- Updated dependencies [834c559]
|
|
273
|
+
- Updated dependencies [22b503c]
|
|
274
|
+
- Updated dependencies [da35453]
|
|
275
|
+
- Updated dependencies [d50f724]
|
|
276
|
+
- Updated dependencies [17acfbb]
|
|
277
|
+
- Updated dependencies [6be0f7a]
|
|
278
|
+
- Updated dependencies [4590363]
|
|
279
|
+
- Updated dependencies [89cc738]
|
|
280
|
+
- Updated dependencies [c0862c1]
|
|
281
|
+
- Updated dependencies [c0862c1]
|
|
282
|
+
- Updated dependencies [c0862c1]
|
|
283
|
+
- Updated dependencies [c0862c1]
|
|
284
|
+
- Updated dependencies [c0862c1]
|
|
285
|
+
- Updated dependencies [ded4494]
|
|
286
|
+
- Updated dependencies [d172f63]
|
|
287
|
+
- Updated dependencies [4c0de52]
|
|
288
|
+
- Updated dependencies [a80fef7]
|
|
289
|
+
- Updated dependencies [3bf8894]
|
|
290
|
+
- Updated dependencies [b9814b3]
|
|
291
|
+
- Updated dependencies [4a9fe31]
|
|
292
|
+
- Updated dependencies [6d8bf0c]
|
|
293
|
+
- Updated dependencies [5dff027]
|
|
294
|
+
- Updated dependencies [1e1f09e]
|
|
295
|
+
- Updated dependencies [455c646]
|
|
296
|
+
- Updated dependencies [f34e877]
|
|
297
|
+
- Updated dependencies [f34e877]
|
|
298
|
+
- Updated dependencies [7ebff39]
|
|
299
|
+
- Updated dependencies [054fd84]
|
|
300
|
+
- Updated dependencies [5275d1f]
|
|
301
|
+
- Updated dependencies [74add0c]
|
|
302
|
+
- Updated dependencies [aaba865]
|
|
303
|
+
- Updated dependencies [f1781be]
|
|
304
|
+
- Updated dependencies [fbad078]
|
|
305
|
+
- Updated dependencies [45d5853]
|
|
306
|
+
- Updated dependencies [2312bd9]
|
|
307
|
+
- Updated dependencies [6d5eb34]
|
|
308
|
+
- Updated dependencies [c5406c2]
|
|
309
|
+
- Updated dependencies [c5406c2]
|
|
310
|
+
- Updated dependencies [d99b731]
|
|
311
|
+
- Updated dependencies [2571a3e]
|
|
312
|
+
- Updated dependencies [2a48bd4]
|
|
313
|
+
- Updated dependencies [8a55f0c]
|
|
314
|
+
- Updated dependencies [c7b30bd]
|
|
315
|
+
- Updated dependencies [20c6d35]
|
|
316
|
+
- Updated dependencies [417e245]
|
|
317
|
+
- Updated dependencies [69bca0a]
|
|
318
|
+
- Updated dependencies [023f00d]
|
|
319
|
+
- Updated dependencies [158c75d]
|
|
320
|
+
- Updated dependencies [7525045]
|
|
321
|
+
- Updated dependencies [5af42db]
|
|
322
|
+
- Updated dependencies [3c0f806]
|
|
323
|
+
- Updated dependencies [1071393]
|
|
324
|
+
- Updated dependencies [5f75cfa]
|
|
325
|
+
- Updated dependencies [6757a93]
|
|
326
|
+
- Updated dependencies [b7cd5c8]
|
|
327
|
+
- Updated dependencies [43e1505]
|
|
328
|
+
- Updated dependencies [dc6d28e]
|
|
329
|
+
- Updated dependencies [c0c2f79]
|
|
330
|
+
- Updated dependencies [0665c3b]
|
|
331
|
+
- Updated dependencies [d2500d8]
|
|
332
|
+
- Updated dependencies [e723f58]
|
|
333
|
+
- Updated dependencies [82bb1d5]
|
|
334
|
+
- Updated dependencies [b231607]
|
|
335
|
+
- Updated dependencies [b231607]
|
|
336
|
+
- Updated dependencies [466a219]
|
|
337
|
+
- Updated dependencies [6fb281d]
|
|
338
|
+
- Updated dependencies [54159d3]
|
|
339
|
+
- Updated dependencies [d48071e]
|
|
340
|
+
- Updated dependencies [fa778e6]
|
|
341
|
+
- Updated dependencies [334f0cf]
|
|
342
|
+
- Updated dependencies [c08c933]
|
|
343
|
+
- Updated dependencies [c4dd707]
|
|
344
|
+
- Updated dependencies [c4dd707]
|
|
345
|
+
- Updated dependencies [7578323]
|
|
346
|
+
- Updated dependencies [38bb373]
|
|
347
|
+
- Updated dependencies [923504f]
|
|
348
|
+
- Updated dependencies [10aba37]
|
|
349
|
+
- Updated dependencies [5d3f437]
|
|
350
|
+
- Updated dependencies [51c661e]
|
|
351
|
+
- Updated dependencies [7af0ab3]
|
|
352
|
+
- Updated dependencies [9f282ef]
|
|
353
|
+
- Updated dependencies [fd4872f]
|
|
354
|
+
- Updated dependencies [f0532d8]
|
|
355
|
+
- Updated dependencies [cea429b]
|
|
356
|
+
- Updated dependencies [3092aca]
|
|
357
|
+
- Updated dependencies [82e5f53]
|
|
358
|
+
- Updated dependencies [d81e460]
|
|
359
|
+
- Updated dependencies [40cb13b]
|
|
360
|
+
- Updated dependencies [8106db1]
|
|
361
|
+
- Updated dependencies [068a540]
|
|
362
|
+
- Updated dependencies [ca45c6d]
|
|
363
|
+
- Updated dependencies [093d55b]
|
|
364
|
+
- Updated dependencies [b64fda3]
|
|
365
|
+
- Updated dependencies [a8051d6]
|
|
366
|
+
- Updated dependencies [866763b]
|
|
367
|
+
- Updated dependencies [524136e]
|
|
368
|
+
- Updated dependencies [2b06a91]
|
|
369
|
+
- Updated dependencies [6f3299f]
|
|
370
|
+
- Updated dependencies [d12bc5f]
|
|
371
|
+
- Updated dependencies [065a1cf]
|
|
372
|
+
- Updated dependencies [f2b17c6]
|
|
373
|
+
- Updated dependencies [52d6194]
|
|
374
|
+
- Updated dependencies [23bf859]
|
|
375
|
+
- Updated dependencies [1c72d77]
|
|
376
|
+
- Updated dependencies [dbc8c11]
|
|
377
|
+
- Updated dependencies [2b4d3f1]
|
|
378
|
+
- Updated dependencies [d75f212]
|
|
379
|
+
- Updated dependencies [37db09f]
|
|
380
|
+
- Updated dependencies [ff9df88]
|
|
381
|
+
- Updated dependencies [919d5cd]
|
|
382
|
+
- Updated dependencies [431cece]
|
|
383
|
+
- Updated dependencies [cfbca7f]
|
|
384
|
+
- Updated dependencies [caac3b2]
|
|
385
|
+
- Updated dependencies [a2225f9]
|
|
386
|
+
- Updated dependencies [a8e5b73]
|
|
387
|
+
- Updated dependencies [d9d9d98]
|
|
388
|
+
- Updated dependencies [21f9577]
|
|
389
|
+
- Updated dependencies [9b82ced]
|
|
390
|
+
- Updated dependencies [3821a3a]
|
|
391
|
+
- Updated dependencies [3eb3ec1]
|
|
392
|
+
- Updated dependencies [92c2a69]
|
|
393
|
+
- Updated dependencies [646caea]
|
|
394
|
+
- Updated dependencies [8ed7e5a]
|
|
395
|
+
- Updated dependencies [6c4d00d]
|
|
396
|
+
- Updated dependencies [db8ce2f]
|
|
397
|
+
- Updated dependencies [f0e760a]
|
|
398
|
+
- Updated dependencies [a5bb7ac]
|
|
399
|
+
- Updated dependencies [f78efdd]
|
|
400
|
+
- Updated dependencies [8a8bcf3]
|
|
401
|
+
- Updated dependencies [f74638c]
|
|
402
|
+
- Updated dependencies [8614705]
|
|
403
|
+
- Updated dependencies [619e3f7]
|
|
404
|
+
- Updated dependencies [ca7a4c3]
|
|
405
|
+
- Updated dependencies [f62b6ff]
|
|
406
|
+
- Updated dependencies [efa9cb4]
|
|
407
|
+
- Updated dependencies [d66d7e8]
|
|
408
|
+
- Updated dependencies [15d8ca6]
|
|
409
|
+
- Updated dependencies [378896d]
|
|
410
|
+
- Updated dependencies [9d857c9]
|
|
411
|
+
- Updated dependencies [670ea10]
|
|
412
|
+
- Updated dependencies [ff215d9]
|
|
413
|
+
- Updated dependencies [33ee9de]
|
|
414
|
+
- Updated dependencies [4839f5c]
|
|
415
|
+
- Updated dependencies [a23e804]
|
|
416
|
+
- Updated dependencies [36e2f7b]
|
|
417
|
+
- Updated dependencies [ac59073]
|
|
418
|
+
- Updated dependencies [44cb469]
|
|
419
|
+
- Updated dependencies [3bcf314]
|
|
420
|
+
- Updated dependencies [c999d4c]
|
|
421
|
+
- Updated dependencies [260ef71]
|
|
422
|
+
- Updated dependencies [8d80845]
|
|
423
|
+
- Updated dependencies [35ef750]
|
|
424
|
+
- Updated dependencies [64acb7e]
|
|
425
|
+
- Updated dependencies [4ff5b6b]
|
|
426
|
+
- Updated dependencies [473e5a0]
|
|
427
|
+
- Updated dependencies [b279016]
|
|
428
|
+
- Updated dependencies [bb6c38c]
|
|
429
|
+
- Updated dependencies [26323c4]
|
|
430
|
+
- Updated dependencies [2727527]
|
|
431
|
+
- Updated dependencies [3da39ae]
|
|
432
|
+
- Updated dependencies [2cfdc99]
|
|
433
|
+
- Updated dependencies [1e6d83b]
|
|
434
|
+
- Updated dependencies [6058bf8]
|
|
435
|
+
- Updated dependencies [c219165]
|
|
436
|
+
- Updated dependencies [fb501bc]
|
|
437
|
+
- Updated dependencies [30083da]
|
|
438
|
+
- Updated dependencies [7f9cc37]
|
|
439
|
+
- Updated dependencies [025130c]
|
|
440
|
+
- Updated dependencies [76f91f8]
|
|
441
|
+
- Updated dependencies [ea34d9b]
|
|
442
|
+
- Updated dependencies [8aa8e3e]
|
|
443
|
+
- Updated dependencies [cb80001]
|
|
444
|
+
- Updated dependencies [2493e85]
|
|
445
|
+
- Updated dependencies [a928aaa]
|
|
446
|
+
- Updated dependencies [47508f3]
|
|
447
|
+
- Updated dependencies [61ed5ad]
|
|
448
|
+
- Updated dependencies [1b94d50]
|
|
449
|
+
- Updated dependencies [6f877f1]
|
|
450
|
+
- Updated dependencies [65fe4c2]
|
|
451
|
+
- Updated dependencies [1ce0711]
|
|
452
|
+
- Updated dependencies [fb85637]
|
|
453
|
+
- Updated dependencies [cd4623d]
|
|
454
|
+
- Updated dependencies [9f1dd79]
|
|
455
|
+
- Updated dependencies [df71568]
|
|
456
|
+
- Updated dependencies [cbac9d7]
|
|
457
|
+
- Updated dependencies [9eb1479]
|
|
458
|
+
- Updated dependencies [74e37aa]
|
|
459
|
+
- Updated dependencies [961b69f]
|
|
460
|
+
- Updated dependencies [52907d7]
|
|
461
|
+
- Updated dependencies [f466b0a]
|
|
462
|
+
- Updated dependencies [673a71c]
|
|
463
|
+
- Updated dependencies [616df65]
|
|
464
|
+
- Updated dependencies [58798ec]
|
|
465
|
+
- Updated dependencies [8803ca5]
|
|
466
|
+
- Updated dependencies [e70c21d]
|
|
467
|
+
- Updated dependencies [06be396]
|
|
468
|
+
- Updated dependencies [7c05f21]
|
|
469
|
+
- Updated dependencies [daeb77f]
|
|
470
|
+
- Updated dependencies [eb4552e]
|
|
471
|
+
- Updated dependencies [3c3115e]
|
|
472
|
+
- Updated dependencies [55e90fd]
|
|
473
|
+
- Updated dependencies [9b75509]
|
|
474
|
+
- Updated dependencies [b13ea3c]
|
|
475
|
+
- Updated dependencies [d7e9e9a]
|
|
476
|
+
- Updated dependencies [cf62edf]
|
|
477
|
+
- Updated dependencies [0253416]
|
|
478
|
+
- Updated dependencies [5d77c09]
|
|
479
|
+
- Updated dependencies [3fd8625]
|
|
480
|
+
- Updated dependencies [1473757]
|
|
481
|
+
- Updated dependencies [d73d987]
|
|
482
|
+
- Updated dependencies [12ff256]
|
|
483
|
+
- Updated dependencies [12ff256]
|
|
484
|
+
- Updated dependencies [26b0bd2]
|
|
485
|
+
- Updated dependencies [d328698]
|
|
486
|
+
- Updated dependencies [d328698]
|
|
487
|
+
- Updated dependencies [b4e0787]
|
|
488
|
+
- Updated dependencies [1b2d016]
|
|
489
|
+
- Updated dependencies [088e0f7]
|
|
490
|
+
- Updated dependencies [78b07f4]
|
|
491
|
+
- Updated dependencies [f962cdc]
|
|
492
|
+
- Updated dependencies [b403bb3]
|
|
493
|
+
- Updated dependencies [8a25fee]
|
|
494
|
+
- Updated dependencies [12dc7b4]
|
|
495
|
+
- @object-ui/types@18.0.0
|
|
496
|
+
- @object-ui/core@18.0.0
|
|
497
|
+
- @object-ui/i18n@18.0.0
|
|
498
|
+
- @object-ui/data-objectstack@18.0.0
|
|
499
|
+
|
|
500
|
+
## 17.7.0
|
|
501
|
+
|
|
502
|
+
### Minor Changes
|
|
503
|
+
|
|
504
|
+
- 97abedc: A `dataSource` binding's own `limit` that the contract refuses is now treated as **not
|
|
505
|
+
authored**: the row cap falls through to the named saved view's usable cap, and only when
|
|
506
|
+
that is absent too to the consumer's own default (objectui#10016).
|
|
507
|
+
|
|
508
|
+
**Behaviour change.** `composeElementDataSource` resolved the cap as
|
|
509
|
+
`config.limit ?? savedViewLimit(view)`. objectui#9928 put a positivity check on the view's
|
|
510
|
+
operand only, so a binding `limit` of `0`, `-10`, `25.5` or a non-number went through
|
|
511
|
+
unchecked:
|
|
512
|
+
|
|
513
|
+
- through `ViewDataProvider.resolveElementDataSource` it reached `DataFetcher.fetchRecords`
|
|
514
|
+
verbatim, and nothing said so;
|
|
515
|
+
- through `ElementDataSourceGate` it was written over everything, a usable component cap
|
|
516
|
+
included, and the consuming block then dropped it and drew its own default, so the read
|
|
517
|
+
went wider than either the view or the component asked for.
|
|
518
|
+
|
|
519
|
+
Both operands now pass the same check, and a refused cap from either is not authored. The
|
|
520
|
+
chain is: a usable binding cap, else a usable view cap, else none. This is the rule
|
|
521
|
+
objectui#10009 set one layer up (a value the contract refuses is not authored, so the other
|
|
522
|
+
source wins), applied to the two operands of one resolver. On the renderer path the view's
|
|
523
|
+
cap that takes the binding's place is a baseline like any other view-sourced value, so a
|
|
524
|
+
usable component cap still wins over it. A usable binding `limit` still beats both, exactly
|
|
525
|
+
as before.
|
|
526
|
+
|
|
527
|
+
**Diagnostics.** `elementDataSourceRefusedLimitMessage` takes two optional trailing
|
|
528
|
+
parameters, the binding and the operand (`'view'`, the default, or `'binding'`). The binding
|
|
529
|
+
operand has its own sentence, naming the binding, so a binding refusal and a view refusal
|
|
530
|
+
are told apart when both fire. `ViewDataProvider` and `ElementDataSourceGate` report it once
|
|
531
|
+
per declaration on the existing `console.warn` channel; the gate reports it only for a block
|
|
532
|
+
that reads a row cap. Called with three arguments the builder answers exactly as before.
|
|
533
|
+
|
|
534
|
+
**Fixed with it.** `ViewDataProvider` reported a saved view's refused cap even when the
|
|
535
|
+
binding's own usable `limit` was the cap actually used, and that warning's claim that the
|
|
536
|
+
fetch falls back to a default was false. The view's refusal is now reported only when the
|
|
537
|
+
binding's `limit` is absent or refused. The builder applies that condition itself, so both
|
|
538
|
+
callers share one copy of it.
|
|
539
|
+
- 2b5f509: fix(react): a data object in a node's `properties` / `props` bag reaches the renderer whole, even when it carries a `source` field
|
|
540
|
+
|
|
541
|
+
`SchemaRenderer` evaluates each value of a node's config bag (`properties`, and its legacy alias `props`) one by one, and handed every value to `ExpressionEvaluator.evaluate`. That call unwraps ANY object with a string `source` to that bare string before it does anything else. So an authored data object that merely had a field called `source` was silently replaced by that one string: an `action:button` with `properties.bodyExtra: { source: 'web', campaign: 'spring' }` sent `"web"` to the action runner instead of the object, with no error and no warning. The same object written at node level, which no loop visits, always arrived intact.
|
|
542
|
+
|
|
543
|
+
This was never one key's problem. Every object-valued input on those two bags took the same path, including values keyed by field name: form `defaultValues` / `initialValues`, a declarative `patch`, filter `values` and a detail view's `data`, where a field named `source` is ordinary.
|
|
544
|
+
|
|
545
|
+
On a key outside the predicate chain, an object now reaches `evaluate` only when it is an Expression envelope in `@objectstack/spec`'s sense, meaning it carries a string `dialect` (the spec's `ExpressionSchema` requires one). Every other object is handed to the renderer exactly as authored.
|
|
546
|
+
|
|
547
|
+
**Behaviour change, stated plainly.** The only value whose meaning changes is an object on a non-predicate config-bag key that has a string `source` and no string `dialect`. It used to collapse to its evaluated `source`; it now arrives as the object. Everything else evaluates as before:
|
|
548
|
+
|
|
549
|
+
- strings are still interpolated;
|
|
550
|
+
- a `{ dialect: 'template', source: '${…}' }` envelope still resolves to its value;
|
|
551
|
+
- the predicate keys (`visible`, `visibleWhen`, `visibleOn`, `visibility`, `hidden`, `hiddenOn`, `disabled`, `disabledOn`, `enabled`) are unchanged: a CEL envelope is still preserved for the canonical engine (objectui#9100 / #9107), and a dialect-less `{ source }` on them is still evaluated, the form `ExpressionWire` declares for `visible` / `hidden` / `disabled`;
|
|
552
|
+
- `params` is still walked leaf by leaf (objectui#7867).
|
|
553
|
+
|
|
554
|
+
Declared as a minor (Clause-②): nothing in the published API is added or removed, but what an authored value evaluates to changes for one shape. `ExpressionEvaluator.evaluate`'s signature admits a dialect-less `{ source }`, and these loops handed it every value except a `params` bag and a CEL envelope on a predicate key. So `{ source: '${…}' }` on a config-bag key outside the predicate chain and `params` now arrives as the object, uninterpolated, and the unevaluated-expression diagnostic does not report the `${…}` inside it. To keep such a value evaluated, write the bare string `'${…}'` or `{ dialect: 'template', source: '${…}' }`; both still interpolate on that path. The spec's envelope always carries `dialect`, and `@object-ui/core`'s `isRuntimeDefault` already reads a dialect-less `{ source }` as a literal value.
|
|
555
|
+
- 6cf5999: fix(components): an action container's members evaluate `properties.params` the way a top-level `action:button` does
|
|
556
|
+
|
|
557
|
+
A member of `action:bar`, `action:group` or `action:menu` never passes through
|
|
558
|
+
`SchemaRenderer`, so the evaluation that resolves `"recordId": "${record.id}"`
|
|
559
|
+
in a node's `properties.params` did not run for it. On a record page an
|
|
560
|
+
`action:bar` member drawn inline handed its `navigate_edit` handler the raw
|
|
561
|
+
`${record.id}` text, and a member that the bar placed in its overflow menu, or
|
|
562
|
+
an `action:group` / `action:menu` item, did not forward its `properties.params`
|
|
563
|
+
at all.
|
|
564
|
+
|
|
565
|
+
Each container now evaluates its members' `properties` with the evaluator and
|
|
566
|
+
scope `SchemaRenderer` uses: `action:bar` where it composes an inline member,
|
|
567
|
+
and `action:group` / `action:menu` when they run an item. A container member's
|
|
568
|
+
static values therefore reach the handler as a top-level `action:button`'s do,
|
|
569
|
+
with one difference: the development-build unevaluated-expression diagnostic
|
|
570
|
+
runs in the `SchemaRenderer` memo, so it does not report an unresolvable
|
|
571
|
+
template on a container member. An item that carries an `ActionParam[]` input
|
|
572
|
+
list forwards both, as `action:button` does. A node-level `params` object is
|
|
573
|
+
still not read as values (objectui#10289). The one exception is unchanged from
|
|
574
|
+
objectui#10462: an `action:group` / `action:menu` item of `type: 'api'` still
|
|
575
|
+
forwards it as the request payload (objectstack#5777 window), unless the item
|
|
576
|
+
also carries a `properties.params` bag, which now takes precedence.
|
|
577
|
+
|
|
578
|
+
`@object-ui/react` exports `useConfigBagEvaluator()` for this. It returns a
|
|
579
|
+
function that applies the `SchemaRenderer` memo's `properties` evaluation (the
|
|
580
|
+
same per-key rule and the same `record` / `current_user` / `page` scope) to a
|
|
581
|
+
config bag rendered without `SchemaRenderer`. It is not a second template
|
|
582
|
+
engine. Its plain half, `evaluateConfigBagInScope()`, is exported beside
|
|
583
|
+
`SchemaRenderer`. Both are built from the module-scope scope builder and
|
|
584
|
+
per-key rule that the memo itself calls.
|
|
585
|
+
|
|
586
|
+
Declared as a minor (Clause-②, widening): `@object-ui/react` gains two public
|
|
587
|
+
exports, `useConfigBagEvaluator` and `evaluateConfigBagInScope`. Nothing is
|
|
588
|
+
removed or renamed.
|
|
589
|
+
- f9c06ef: Every data node that sends its own authored `filter` into a query now resolves the spec's context tokens first (objectui#10666). With `filter: [['owner', '=', '{current_user_id}']]` these nodes used to send the literal token; they now send the signed-in user's id, and `{current_org_id}` resolves to the active organization:
|
|
590
|
+
|
|
591
|
+
- `object-calendar`, `object-map` and `object-tree`, on both the object query and the inline (`provider: 'value'`) query;
|
|
592
|
+
- `object-gantt`, `object-kanban` and `object-timeline`;
|
|
593
|
+
- `record:related_list` and `record:line_items`, where the node's own filter is combined with the parent-record condition;
|
|
594
|
+
- the `element:repeater`, `element:number` (both the `aggregate` filter and the `find` fallback) and `element:record_picker` page elements.
|
|
595
|
+
|
|
596
|
+
`list-view`, `object-grid` and `object-gallery` already did this, and are unchanged.
|
|
597
|
+
|
|
598
|
+
**New in `@object-ui/react`: `useResolvedFilter(filter, scope)`.** Pass it a node's authored filter and `useFilterScope()`. It resolves the filter through `@object-ui/core`'s shared `resolveFilterPlaceholders` and holds the result: re-rendering with an equal filter (even one rebuilt inline on every render) hands back the same value, so a fetch effect keyed on it does not run again, while a structurally different filter or a change of signed-in user or organization resolves again. A filter with no token to resolve is handed back as the value you passed in. The hold is the one `list-view` and `object-gallery` used privately; `plugin-list` now imports it from here, and no second copy remains in that package.
|
|
599
|
+
|
|
600
|
+
What changes for an app:
|
|
601
|
+
|
|
602
|
+
- A filter with no placeholders reaches the query as before, unchanged.
|
|
603
|
+
- Nodes that re-queried whenever the host rebuilt an equal filter inline (`object-calendar`, `object-gantt`, `object-kanban`, `object-map`, `object-tree`) no longer issue those redundant queries.
|
|
604
|
+
- Date macros such as `{today}` in these nodes' own filters are now resolved in the browser's local time, as they already are for `list-view`, `object-grid` and `object-view`. Before, they reached the server as literals, and the server resolved them in the tenant's time zone.
|
|
605
|
+
|
|
606
|
+
`@object-ui/types`: the `filter` docblocks on `ObjectMapSchema`, `ObjectTreeSchema`, `ObjectGanttSchema`, `ObjectCalendarSchema`, `ObjectKanbanSchema`, `ObjectChartSchema` and `ObjectGallerySchema` (and the matching zod descriptions) no longer say the filter is forwarded verbatim; they say its context tokens are resolved first. No type changes.
|
|
607
|
+
- a4b017e: `element:text`, `element:button`, `element:image` and `element:number` now render the accessible name an author declares in their `aria` prop (objectui#11051).
|
|
608
|
+
|
|
609
|
+
**What an author sees change.** These four elements declare the spec's `AriaPropsSchema` as their `aria` prop. The renderer used to put `aria-` in front of each key as written, so `aria: { ariaLabel: 'Order total' }` reached the DOM as `aria-arialabel="Order total"`. No assistive technology reads that attribute, so the element had no accessible name. A locale map (`{ en: 'Order total', 'zh-CN': '订单合计' }`) was written as `[object Object]`. Now:
|
|
610
|
+
|
|
611
|
+
- `ariaLabel` renders `aria-label`. A locale map gives the entry for the display locale (`useDisplayLocale()`), resolved by the spec's own `resolveI18nLabel`.
|
|
612
|
+
- `ariaDescribedBy` renders `aria-describedby` (it rendered `aria-ariadescribedby`).
|
|
613
|
+
- `role` renders `role`, as before.
|
|
614
|
+
|
|
615
|
+
**New export: `resolveInlineAriaProps(aria, locale)` from `@object-ui/react`.** A pure function, not a hook: it maps the spec's nested `aria` bag to those three attributes, and returns only the ones that have a value. It is exported so the renderers in `@object-ui/components` share one mapping. `SchemaRenderer`'s own reader for the FLAT node keys (`ariaLabel` in the keyed `{ key, defaultValue }` form) is unchanged: objectui#4580 Q2-B keeps the two vocabularies apart, and each resolver returns nothing useful for the other's shape.
|
|
616
|
+
|
|
617
|
+
**What is no longer read.** The old helper also passed any raw `aria-*` key straight through, and turned any other key into `aria-KEY`. So an `aria: { label: 'x' }` rendered `aria-label="x"` by accident. The spec refuses both shapes when it parses the document (`unrecognized_keys`), and these four elements now ignore them. When this change was made, a `git grep` found no example app, doc or skill in this repository or in the ObjectStack framework that puts an `aria` bag on these four elements, so no shipped document depended on those shapes. If you wrote one, use `aria: { ariaLabel: 'x' }`.
|
|
618
|
+
- 1263e40: fix(app-shell,plugin-detail): the record feed says "no permission" when its read is refused, instead of showing an empty list
|
|
619
|
+
|
|
620
|
+
This widens two published surfaces: `@object-ui/plugin-detail` gains the export `isRefusedFeedRead`, and `@object-ui/react`'s `DiscussionContextValue` gains the optional members `activityDenied` and `commentsDenied` (as do `RecordActivityTimelineProps` and `RecordChatterPanelProps`).
|
|
621
|
+
|
|
622
|
+
When the server refused a record's activity read (401, 403, or a permission
|
|
623
|
+
envelope), the record page's discussion panel and the `record:activity` block
|
|
624
|
+
rendered the empty state: "No comments yet" or "No activity recorded". A member
|
|
625
|
+
who was not allowed to see the activity could not tell that apart from a record
|
|
626
|
+
with no history. Every failed feed read ended in a catch that treated it as an
|
|
627
|
+
empty answer.
|
|
628
|
+
|
|
629
|
+
Each feed read now keeps its verdict. A refused read shows a localized
|
|
630
|
+
no-permission state in place of the empty state: a lock and "You don't have
|
|
631
|
+
permission to view activity on this record." When the other read did answer, its
|
|
632
|
+
rows stay on screen and the notice above them names the half that was withheld.
|
|
633
|
+
A refused `sys_comment` read shows "You don't have permission to view comments on
|
|
634
|
+
this record." in the same way.
|
|
635
|
+
|
|
636
|
+
- **What counts as refused** is one verdict, `isRefusedFeedRead`, exported from
|
|
637
|
+
`@object-ui/plugin-detail`. Both surfaces use it. It is the `forbidden` and
|
|
638
|
+
`unauthorized` kinds of the existing `classifyLoadError` read classifier, and
|
|
639
|
+
it reads no status of its own.
|
|
640
|
+
- **Unchanged:** a read that answers with zero rows, a 404 (`sys_activity` on a
|
|
641
|
+
deployment without the audit plugin), and any other failure such as a 500 keep
|
|
642
|
+
the empty state. The panel has no error state of its own.
|
|
643
|
+
- **Not retried:** a refusal is recorded and shown; the read is not re-issued.
|
|
644
|
+
- `DiscussionContext` (`@object-ui/react`) gains two optional members,
|
|
645
|
+
`activityDenied` and `commentsDenied`, which the host that owns the fetch
|
|
646
|
+
sets. `RecordActivityTimeline` and `RecordChatterPanel` take the same two
|
|
647
|
+
optional props.
|
|
648
|
+
- Two new `detail.*` strings, `activityAccessDenied` and `commentsAccessDenied`,
|
|
649
|
+
are translated in all ten locale packs.
|
|
650
|
+
- 8bab157: fix: an action gated on `current_user.can(object, verb)` stays hidden until the permissions payload has loaded on every action `visible` surface, a `page:header` action gated through `disabled` stays disabled, and `record:quick_actions` can answer `current_user` at all
|
|
651
|
+
|
|
652
|
+
An action whose `visible` asks `current_user.can(...)` has no answer until the signed-in
|
|
653
|
+
user's permissions have loaded. On `action:group` (both display modes, and the group's own
|
|
654
|
+
`visible`), `action:icon` and a related list's toolbar, that unanswerable gate used to
|
|
655
|
+
SHOW the action, so a button meant for permission holders flashed up for everyone while
|
|
656
|
+
the page loaded, and stayed up where the permissions never load (the `/forms/:name` route,
|
|
657
|
+
a standalone embed). These renderers now treat a `visible` predicate that cannot be
|
|
658
|
+
evaluated the way `action:button` and `action:menu` already did: the action is hidden and
|
|
659
|
+
the console names it once. This is the rule for the key, not a special case for `can`: a
|
|
660
|
+
`visible` that faults for any other reason (a misspelled root such as `nope.x == 1`) is
|
|
661
|
+
hidden too, where it used to be shown.
|
|
662
|
+
|
|
663
|
+
On `page:header`, a `disabled` predicate that cannot be evaluated now renders the button
|
|
664
|
+
DISABLED instead of enabled, so `disabled: "!current_user.can('account', 'delete')"`
|
|
665
|
+
shows a greyed-out button until the answer arrives. This also applies to a `disabled`
|
|
666
|
+
predicate that faults for another reason. A header action's `visible` and `hidden`
|
|
667
|
+
predicates keep their fail directions.
|
|
668
|
+
|
|
669
|
+
`record:quick_actions` filters its actions against the action runner's context, which
|
|
670
|
+
held the host's `user` but never `current_user`, so `current_user.can(...)` and every
|
|
671
|
+
other `current_user.*` gate there hid the action for everyone, the users who hold the
|
|
672
|
+
permission included. `useActionEngine` (`@object-ui/react`) now binds the signed-in user
|
|
673
|
+
from the surrounding `ExpressionProvider` as `current_user`, so a quick action is shown
|
|
674
|
+
to users who hold the permission and hidden from those who don't, and while the
|
|
675
|
+
permissions are loading.
|
|
676
|
+
- 18d1a0a: fix: a row-menu action gated through `disabled` stays disabled until the permissions payload has loaded, in a grid and in a related list's table, and an `<ActionProvider>` answers `current_user` in the runner's own gates
|
|
677
|
+
|
|
678
|
+
A custom row action whose `disabled` asks `current_user.can(...)` has no answer until the
|
|
679
|
+
signed-in user's permissions have loaded. In the row menu of a grid (`RowActionMenu`) and
|
|
680
|
+
of a data table (the related list's rows), that unanswerable gate used to leave the item
|
|
681
|
+
ENABLED, so `disabled: "!current_user.can('account', 'delete')"` offered the action to
|
|
682
|
+
everyone while the page loaded, and kept offering it where the permissions never load (the
|
|
683
|
+
`/forms/:name` route, a standalone embed). Both menus now render a `disabled` predicate that
|
|
684
|
+
cannot be evaluated as DISABLED, the way `page:header` already does, and the console names
|
|
685
|
+
it once. This is the rule for the key, not a special case for `can`: a `disabled` that
|
|
686
|
+
faults for any other reason (a misspelled root such as `nope.x == 1`) is disabled too,
|
|
687
|
+
where it used to be enabled. An absent, empty or blank `disabled` is still not disabled.
|
|
688
|
+
The built-in Edit and Delete keep their fail-soft `disabledWhen`.
|
|
689
|
+
|
|
690
|
+
`<ActionProvider>` (`@object-ui/react`) now binds the signed-in user from the surrounding
|
|
691
|
+
`ExpressionProvider` as `current_user` on its runner. The runner checks an action's
|
|
692
|
+
`disabled` again when the action runs, and that check used to fault on `current_user` unless
|
|
693
|
+
the page also mounted `record:quick_actions`, `record:alert` or a dashboard, so a user who
|
|
694
|
+
held the permission clicked an enabled header action and was told "Action is disabled".
|
|
695
|
+
The check now answers `current_user.can(...)` under any provider mounted inside the
|
|
696
|
+
predicate scope. On the runner's gates `user.can(...)`, `ctx.user.can(...)` and
|
|
697
|
+
`os.user.can(...)` still fault, because there `user` is the host's own user object; write
|
|
698
|
+
`current_user.can(...)`.
|
|
699
|
+
- 2124d04: `useNavigationOverlay` hands an authored `page` click with no `onNavigate` to the record navigator the host publishes (objectui#11293).
|
|
700
|
+
|
|
701
|
+
`page` is the spec's default navigation `mode`, so `navigation: { mode: 'page' }` and every block written without `mode` (for example `{ size: 'lg' }`) resolve to it. The hook's `page` branch called `onNavigate` and nothing else, so a block a host rendered without wiring one — a standalone `object-kanban`, `object-calendar` or `object-grid` placed on a page — opened nothing on click. With no `onNavigate` supplied, the branch now calls `openRecord(objectName, recordId)` from `RelatedRecordActionsContext`, the seam the grid's link column and the lookup cells already read; the hook still builds no record URL for `page`.
|
|
702
|
+
|
|
703
|
+
What does not change: an `onRowClick` still takes the click first, a supplied `onNavigate` still wins (the grid's explicit wiring and the console's list views behave as before), an absent `navigation` stays the host's call, and modifier clicks and `new_window` are untouched. Under a host that publishes no record navigator the click still opens nothing, because there is no record page to open.
|
|
704
|
+
- c476be0: The action success toast is composed from the action's `outcomeMessages`, then its `successMessage`, then the runner's default text. A `message` in the server's answer is no longer shown (objectui#11344).
|
|
705
|
+
|
|
706
|
+
`@objectstack/spec` 17.6.0 declares `ActionSchema.outcomeMessages`, the success copy for each handler outcome. Its keys are the snake_case `outcome` values that a `type: 'api'` or `type: 'script'` handler returns in its success payload. This follows ruling A on objectstack-ai/cloud#2315: the server returns facts, and the console writes the sentence in the user's locale.
|
|
707
|
+
|
|
708
|
+
- `@object-ui/core`: the `ActionRunner` success toast now picks its text in this order:
|
|
709
|
+
1. the `outcomeMessages` entry named by the top-level `outcome` of the handler's answer;
|
|
710
|
+
2. `successMessage`;
|
|
711
|
+
3. the runner's default text, in the language of the translator set with `setTranslator`.
|
|
712
|
+
|
|
713
|
+
Rungs 1 and 2 fill in `${result.*}` tokens from the handler's answer. That is the same scope `onSuccess.navigate` reads, and the values go in as text, not percent-encoded. **Behaviour change:** `result.data.message` used to take precedence over both. The runner no longer reads it at all. To show the server's sentence, write `${result.message}` as the copy. `ActionDef` now declares `outcomeMessages`.
|
|
714
|
+
- `@object-ui/react`: `useActionTextLocalizer` resolves each `outcomeMessages` entry that a named action declares. It looks in `_actions.NAME.outcomeMessages.OUTCOME`, then `globalActions.NAME.outcomeMessages.OUTCOME`, then falls back to the authored text. On a named action, an inline `I18nLabel` map on an entry, or on `successMessage`, is collapsed to the active language first. `${result.*}` tokens pass through unchanged, and the runner fills them in after the action runs.
|
|
715
|
+
- `@object-ui/i18n`: `useObjectLabel()` adds `actionOutcome(objectName, actionName, outcome, fallback)`, the resolver for that address.
|
|
716
|
+
- `@object-ui/components`: `action:button`, `action:icon`, `action:group` and `action:menu` now forward `outcomeMessages` to the runner. Before, a registered action rendered through `action:bar` lost the map one hop before the toast. The `action:button` and `action:icon` registrations do not publish it as an input, because their own spec rows do not declare it at 17.6.0.
|
|
717
|
+
- `@object-ui/types`: `UIActionSchema` declares `outcomeMessages`, derived from the spec.
|
|
718
|
+
|
|
719
|
+
`@object-ui/core` and `@object-ui/types` raise their `@objectstack/spec` floor from `^17.5.0` to `^17.6.0`, because their published types now read `ActionSchema.outcomeMessages`, a member the spec first declares in 17.6.0.
|
|
720
|
+
- 304f611: The spec's page blocks, the `element:text_input` / `element:record_picker`
|
|
721
|
+
rows and a stored page document under its page kind have a TypeScript
|
|
722
|
+
authoring type, and `SchemaRenderer`'s `schema` prop accepts them
|
|
723
|
+
(objectui#11364).
|
|
724
|
+
|
|
725
|
+
`@object-ui/types` exports, each derived by reference and none restated:
|
|
726
|
+
|
|
727
|
+
- `PublicBlockNode`: one member per arm of the zod face's
|
|
728
|
+
`PublicBlockComponentSchema` (`element:text`, `page:tabs`, `action:button`
|
|
729
|
+
and the rest), each the arm's own input. Its `properties` bag is the block's
|
|
730
|
+
`ComponentPropsMap` row in `@objectstack/spec`. `PublicBlockNodeOf<'…'>`
|
|
731
|
+
picks one member.
|
|
732
|
+
- `ElementTextInputNode` and `ElementRecordPickerNode`: the spec rows of the
|
|
733
|
+
two blocks the zod face does not arm yet, each built the way an arm is
|
|
734
|
+
built: the zod `BaseSchema` shape extended with the `type` literal, the node
|
|
735
|
+
envelope (`responsiveStyles`, the spec's `ResponsiveStylesSchema`),
|
|
736
|
+
`properties` as the row, and the `body` / `children` refusals every arm
|
|
737
|
+
carries. The record picker also declares the node-level `dataSource` its
|
|
738
|
+
renderer reads.
|
|
739
|
+
- `PageDocumentNode`: the spec's `PageSchema` input with `type` (the page
|
|
740
|
+
kind) required, the shape `PageView` hands to `SchemaRenderer`.
|
|
741
|
+
- `AuthoringNode`: the union of all of them.
|
|
742
|
+
|
|
743
|
+
`@object-ui/react`'s `SchemaRendererProps.schema` is now
|
|
744
|
+
`BaseSchema | AuthoringNode | string | null | undefined`, and
|
|
745
|
+
`toRenderableSchema`, which returns `SchemaRendererProps['schema']` by
|
|
746
|
+
reference, widens its return type with it. Every value the old union accepted
|
|
747
|
+
is still accepted. Reading is a different matter: on a union, a key that one
|
|
748
|
+
closed member does not declare is TS2339, whatever `BaseSchema`'s index
|
|
749
|
+
signature allows. No caller in this repository reads a key off the prop's type
|
|
750
|
+
or off the bridge's return (the `Type Check` job re-derives that); code that
|
|
751
|
+
does needs to narrow first. Once objectui#8347 removes the index signature,
|
|
752
|
+
a key misspelled inside one of these nodes' bags is refused, and the spec's
|
|
753
|
+
spelling compiles. None of these types carries an index signature, and the
|
|
754
|
+
prop gains none.
|
|
755
|
+
|
|
756
|
+
⚠️ **Dated note, 2026-10-02 — the prop takes the declared-node union — objectui#11466.** At this change `SchemaRendererProps.schema` was `BaseSchema | AuthoringNode | string | null | undefined`, and every value the old union accepted was still accepted; now, later in this same release, it is `DeclaredNode | string | null | undefined`. `DeclaredNode` (`@object-ui/types`) is the union, keyed by `type`, of the component schemas `AnySchema` declares (without its `BaseSchema` arm and without the app-level document `AppComponentSchema`), `AuthoringNode`, and the types an application declares in `CustomNodeRegistry`, so a value typed `BaseSchema` or a `type` nothing declares is refused. `PageDocumentNode` admits every page kind but the interface-mode `list`. The rest of this entry is kept as the reading of this change.
|
|
757
|
+
- 83e3f83: A node slot and `SchemaRenderer`'s `schema` prop take the union of the declared node types, `DeclaredNode` (objectui#11466).
|
|
758
|
+
|
|
759
|
+
**BREAKING (TypeScript authoring face only), shipped as `minor` per this repository's version policy.** `SchemaNode` was `BaseSchema | string | number | boolean | null | undefined`, and the prop was `BaseSchema | AuthoringNode | string | null | undefined`. Both object members are now `DeclaredNode`, a new export of `@object-ui/types`: the discriminated union, keyed by the literal `type`, of
|
|
760
|
+
|
|
761
|
+
- every component schema `AnySchema` declares, without its `BaseSchema` arm (whose `type` is `string`) and without `AppComponentSchema` (the app-level document, which `AppSchemaRenderer` reads structurally and `ComponentRegistry` never dispatches: as a node, `type: 'app'` is the stored page of that kind);
|
|
762
|
+
- every spec-declared `AuthoringNode`;
|
|
763
|
+
- every type an application declares in the new `CustomNodeRegistry` interface.
|
|
764
|
+
|
|
765
|
+
There is no `type: string` arm and no index signature. What moves:
|
|
766
|
+
|
|
767
|
+
- A node whose `type` no declaration names is refused, nested or at the prop: `{ type: 'card', children: [{ type: 'txt' }] }` no longer compiles. So is a value typed `BaseSchema`, which names no declared type.
|
|
768
|
+
- An inline child is checked against its own type's arm wherever it is nested, so a misspelled key on a node type without an index signature (the `AuthoringNode`s, a closed `CustomNodeRegistry` entry) is refused at the slot. Node types that extend `BaseSchema` keep its index signature until objectui#8347 removes it.
|
|
769
|
+
- A node's REQUIRED keys are required (a stored `home` page document needs its `label`, a `data-table` its `columns`), because no `BaseSchema` arm accepts the same literal structurally any more.
|
|
770
|
+
- `app` and `list` are one arm each: `PageDocumentNode` admits every page kind but the interface-mode `list`, which `PageView` renders through `InterfaceListPage` and never hands to `SchemaRenderer`. Narrowing a `DeclaredNode` on `type === 'app'` gives `PageDocumentNode`, and on `'list'` gives `ListSchema`. `SchemaByType` reads `AnySchema` and does not move.
|
|
771
|
+
- `@object-ui/core`'s schema builder takes `DeclaredNode` where it took `BaseSchema`: `.child()` / `.children()` on the grid and flex builders and the card builder's `.content()`.
|
|
772
|
+
- `toRenderableSchema` (`@object-ui/react`) follows `SchemaNode` and the prop by reference.
|
|
773
|
+
|
|
774
|
+
What does NOT move: every zod face (`AnyComponentSchema`, `SchemaNodeSchema`, the strict authoring face) and every runtime path. `zod/base.zod.ts` writes the node slot's zod type out through type aliases so the declared-node union can name the zod-derived `AuthoringNode`s without a circular reference; the schema objects are unchanged. `FlexBlockNode` is now an interface for the same reason, with the same members. `@object-ui/components`' `kind: 'html'` page crosses its runtime-parsed tree into `DeclaredNode` at one documented boundary, after `validateTree` reports no error; it renders exactly what it rendered.
|
|
775
|
+
|
|
776
|
+
**Migration.** Annotate a node with its declared type, or with `DeclaredNode` where any node goes. Declare each type you register with `ComponentRegistry` in `CustomNodeRegistry`:
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
import type { BaseSchema } from '@object-ui/types';
|
|
780
|
+
|
|
781
|
+
interface MyWidgetSchema extends BaseSchema {
|
|
782
|
+
type: 'my-widget';
|
|
783
|
+
customProp?: string;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
declare module '@object-ui/types' {
|
|
787
|
+
interface CustomNodeRegistry {
|
|
788
|
+
'my-widget': MyWidgetSchema;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
An entry joins the union under its KEY (the arm is the entry intersected with `{ type: KEY }`), so it never adds a `type: string` arm. A value built at runtime whose `type` the compiler cannot know is narrowed to a declared type, or crosses at one validated boundary, as the html-tier page does.
|
|
794
|
+
- 1b1d772: Expression-bindable text keys: `statistic.value`, `card.title`, `button.label`
|
|
795
|
+
and their siblings now evaluate `${...}` on the node (objectui#4795 Direction 1,
|
|
796
|
+
maintainer ruling 2026-08-25).
|
|
797
|
+
|
|
798
|
+
**What changes for you.** Four text keys — `title`, `label`, `value`,
|
|
799
|
+
`description` — can now carry an expression written directly on the component
|
|
800
|
+
node, on the component types that declare them:
|
|
801
|
+
|
|
802
|
+
| Component | Bindable node keys |
|
|
803
|
+
|---|---|
|
|
804
|
+
| `statistic` | `label`, `value`, `description` |
|
|
805
|
+
| `card` | `title`, `description` |
|
|
806
|
+
| `button` | `label` |
|
|
807
|
+
|
|
808
|
+
```json
|
|
809
|
+
{ "type": "statistic", "label": "Active users", "value": "${data.metrics.active}" }
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
That node used to render the literal text `${data.metrics.active}`. A dashboard
|
|
813
|
+
`statistic` previously had no way at all to bind a dynamic number — the
|
|
814
|
+
documented workaround (moving the key under `props`) evaluated the value and
|
|
815
|
+
then discarded it, painting a blank card instead. Both shapes are fixed by the
|
|
816
|
+
same change: the value is evaluated once, at the single place that produces
|
|
817
|
+
evaluated schema, and lands where the renderers already read.
|
|
818
|
+
|
|
819
|
+
**No component behaviour changed.** `statistic.tsx`, `card.tsx` and `button.tsx`
|
|
820
|
+
are untouched — they always read these keys off the node; nothing was writing an
|
|
821
|
+
evaluated value there.
|
|
822
|
+
|
|
823
|
+
**Scope, and how it grows.** The list is closed and lives in
|
|
824
|
+
`@objectstack/spec` (`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT`); the renderer
|
|
825
|
+
reads that declaration rather than keeping a copy. On any other component type
|
|
826
|
+
these four keys are still read raw, so an expression reaches the screen as
|
|
827
|
+
literal text — notably `text`, whose `value` is read but has no declaration.
|
|
828
|
+
Adding a type or a key is a change to the spec, never something the renderer
|
|
829
|
+
infers.
|
|
830
|
+
|
|
831
|
+
**Nothing is newly rejected.** This release only widens what evaluates; no
|
|
832
|
+
metadata that used to render now fails to. The build-time rejection of `${...}`
|
|
833
|
+
in undeclared keys — the second half of the same ruling — is not in this release
|
|
834
|
+
and is still open.
|
|
835
|
+
|
|
836
|
+
Published authoring guidance updated to match: `skills/objectui/rules/protocol.md`
|
|
837
|
+
(new "Bindable Text Keys" rule), plus the `page-builder`, `schema-expressions`
|
|
838
|
+
and `data-integration` guides, which taught the now-retired "never evaluated"
|
|
839
|
+
statement and its host-pre-resolution workaround.
|
|
840
|
+
- 30c73cd: A `disabled` / `disabledOn` predicate that cannot be evaluated is now reported on the console, in development **and** in production — and the message says what this gate's fail-soft default actually did.
|
|
841
|
+
|
|
842
|
+
`SchemaRenderer` routes six visibility legs (`visibleWhen`, `visible`, `visibleOn`, `visibility`, `hidden`, `hiddenOn`) through one reporter, and called `evaluateCondition` **bare** on the two enablement legs — the only uninstrumented predicate pair in the file. A faulting `disabled` predicate had therefore never been reported in any build, in any dialect that does not report on its own.
|
|
843
|
+
|
|
844
|
+
It is also the pair whose fail-soft answer **bites**. `evaluateCondition` answers an unevaluable predicate with `true`; on the negated visibility legs that means SHOWN, here it means GREYED OUT. So the user got a control they could see and could not use, and the author got nothing to grep for.
|
|
845
|
+
|
|
846
|
+
- **Wiring only, one engine call.** Both legs pass `EvaluationOptions.onFault` (objectui#6038's seam), which hands back the fault the evaluator has already caught. No `throwOnError`, no second evaluation, no `__DEV__` split — dev and production print identical bytes.
|
|
847
|
+
- **No verdict moves.** The fail-soft `true` is preserved deliberately: a faulting `disabled` predicate still disables, exactly as before. Flipping that is a shipped-behaviour change and is not part of this.
|
|
848
|
+
- **Its own copy, not the visibility reporter's.** The shipped line says the safe default meant the gate "did NOT bite", which is written about a gate that shows the node. This gate's line says the opposite, because the opposite is true: `[ObjectUI] An enablement predicate could not be evaluated`, then the node, the key, the source, the engine's reason, and that the node renders disabled — on screen, greyed out — with nothing else on screen to say a predicate failed. One reporter, one dedupe, one severity; a second message.
|
|
849
|
+
- **Rate-limited exactly as the visibility gate is**, per `(node type, key, predicate source)`: two hundred rows of one broken predicate print one line, a second distinct source still prints, and the same source authored on `disabled` and on `visibleWhen` prints two — the gates cannot silence each other.
|
|
850
|
+
- 830ed58: The record page's approval band offers its **Recall** button to the approval's submitter
|
|
851
|
+
only.
|
|
852
|
+
|
|
853
|
+
Field report on `@objectstack/*@17.2.0`: user A submits a record into a 4-level approval;
|
|
854
|
+
user B — not the submitter, read access, not an admin — opens the record and the band still
|
|
855
|
+
lights a clickable recall button. The click cannot succeed. The recall endpoint authorizes
|
|
856
|
+
on submitter identity and refuses everyone else, so the only outcome available to that
|
|
857
|
+
button was a failure toast. Record state was never at risk; this was purely a
|
|
858
|
+
writability-feedback mismatch, the same family as objectui#3794.
|
|
859
|
+
|
|
860
|
+
The button's only gate was `dataSource.cancelPendingApproval` — "can this adapter recall at
|
|
861
|
+
all" — which is a question about the DataSource, not about the viewer. Identity now joins
|
|
862
|
+
it, threaded the way every other signal on that band already travels: the HOST resolves it
|
|
863
|
+
and passes it through `InlineEditProvider`, so the renderer stays DataSource-agnostic and
|
|
864
|
+
never re-derives who submitted what.
|
|
865
|
+
|
|
866
|
+
- `@object-ui/react` — `InlineEditProvider` accepts `approvalIsSubmitter`, surfaced on
|
|
867
|
+
`InlineEditContextValue`. Additive and optional; no existing prop changes.
|
|
868
|
+
- `@object-ui/plugin-detail` — the band's recall button is withdrawn when that signal is a
|
|
869
|
+
resolved `false`.
|
|
870
|
+
- `@object-ui/app-shell` — `RecordDetailView` resolves the verdict from its existing
|
|
871
|
+
approvals read and threads it.
|
|
872
|
+
|
|
873
|
+
**The signal is tri-state, and the third state is the load-bearing one.** `true` offers
|
|
874
|
+
recall, `false` withdraws it, and **`undefined` — a host that resolves no approval identity
|
|
875
|
+
— renders exactly as it did before this release.** Omission preserving prior behaviour
|
|
876
|
+
mirrors how `approvalPending` falls back to `locked`. Defaulting the unknown case to "hide"
|
|
877
|
+
would have traded a cosmetic defect for a functional loss: every host whose band runs off
|
|
878
|
+
the record's `approval_status` mirror alone would silently lose its submitter's only way to
|
|
879
|
+
unlock their own record.
|
|
880
|
+
|
|
881
|
+
**Withdrawn rather than disabled-with-reason.** The card offered either. For a
|
|
882
|
+
non-submitter this control is never actionable on any pending record, so a permanently
|
|
883
|
+
disabled button is standing clutter rather than a lesson; and the two sibling submitter
|
|
884
|
+
levers already hide — the approvals panel's Remind button, and the declared
|
|
885
|
+
`approval_recall` action's `visible` predicate. The band, its quorum tally and the
|
|
886
|
+
approvals timeline still tell a non-submitter exactly what state the record is in. Only the
|
|
887
|
+
lever they can never pull is gone.
|
|
888
|
+
|
|
889
|
+
**This changes no permission.** Nothing about what the server allows moves, `canEdit` and
|
|
890
|
+
the approval lock are untouched, and nothing downstream reads `approvalIsSubmitter` as an
|
|
891
|
+
authorization verdict — the recall endpoint remains the sole authority, and it refused
|
|
892
|
+
these callers before this change and refuses them after. There is deliberately **no admin
|
|
893
|
+
carve-out** (the reporter ruled that case out, cf. objectstack#9464).
|
|
894
|
+
|
|
895
|
+
The derivation itself is now one function, `isSubmitterOf` — server-resolved
|
|
896
|
+
`viewer.is_submitter` first (framework#3310), an id comparison as the fallback for backends
|
|
897
|
+
that predate it, joined with `??` so a server that resolved `false` is believed rather than
|
|
898
|
+
re-litigated client-side. The approvals panel's Remind gate, which already carried that
|
|
899
|
+
expression inline and whose behaviour is unchanged, now reads the same answer: two copies
|
|
900
|
+
would have been two definitions of who submitted.
|
|
901
|
+
|
|
902
|
+
The **untranslated refusal text** the reporter also saw ("No pending approval request found
|
|
903
|
+
for this record", concatenated after a localized prefix) is a separate defect and is not
|
|
904
|
+
addressed here; it is tracked on objectstack#11993.
|
|
905
|
+
- c71e14d: **The unresolvable-visibility-predicate report now names the roots of the tier the
|
|
906
|
+
predicate was actually evaluated against** (objectui#6487). An app-shell author
|
|
907
|
+
whose nav, area or field `visible` faulted was told to check `record` and
|
|
908
|
+
`page.<var>` — two roots that tier does not bind at all.
|
|
909
|
+
|
|
910
|
+
`formatUnresolvableVisibilityMessage` and `reportUnresolvableVisibilityPredicate`
|
|
911
|
+
(both exported from `@object-ui/react`) take a new **optional sixth argument**, a
|
|
912
|
+
`PredicateScopeTier` — also exported — selecting the closing advice paragraph.
|
|
913
|
+
Everything above that paragraph is unchanged on every surface, and so is every
|
|
914
|
+
verdict: this is diagnostics copy only.
|
|
915
|
+
|
|
916
|
+
**The published signature grew; nothing existing breaks.** The argument defaults
|
|
917
|
+
to `'page-component'`, so a five-argument call keeps printing the bytes it
|
|
918
|
+
printed before. All three in-repo call sites pass their tier explicitly rather
|
|
919
|
+
than lean on that default.
|
|
920
|
+
|
|
921
|
+
⚠️ **Dated note, 2026-09-29 — the call-site count above has since moved —
|
|
922
|
+
objectui#6445.** "All three in-repo call sites" above held when this change landed
|
|
923
|
+
(`c71e14d1c`), counting gates: `reportUnresolvableVisibilityPredicate` was called from
|
|
924
|
+
`SchemaRenderer`'s node visibility gate (twice), the `page:tabs` item gate and
|
|
925
|
+
`ExpressionProvider`'s chrome gate, four calls in three files. Later in this same release
|
|
926
|
+
objectui#6445 (PR objectui#6510) added a fourth gate, `SchemaRenderer`'s `disabled` /
|
|
927
|
+
`disabledOn` enablement gate. Re-measured for objectui#10979 on `main` at `2eaf5be27`,
|
|
928
|
+
the reporter was called five times outside its own module, from four gates in the same
|
|
929
|
+
three files, and all five calls passed their tier explicitly, so none leaned on the
|
|
930
|
+
default. `.changeset/6445-disabled-gate-fault-diagnostic.md` (PR objectui#6510) states
|
|
931
|
+
what ships; the text above is kept as the reading of this change.
|
|
932
|
+
|
|
933
|
+
Each tier's root set was derived from the code that builds the bag, not from the
|
|
934
|
+
prose that described it:
|
|
935
|
+
|
|
936
|
+
- **`'page-component'`** — `SchemaRenderer`'s node gate and `page:tabs` item
|
|
937
|
+
predicates. Both bind `record`, `current_user` and `page.<var>` (the roots
|
|
938
|
+
`@objectstack/spec`'s `ui/page.zod.ts` declares for the tier). Its paragraph is
|
|
939
|
+
byte-for-byte what it was.
|
|
940
|
+
- **`'app-shell'`** — the chrome gate `ExpressionProvider.evaluateVisibility`
|
|
941
|
+
runs, wired onto this reporter by objectui#6443. Its evaluator is built from
|
|
942
|
+
`{ current_user, user, ctx: { user }, os: { user }, data, features }`, so
|
|
943
|
+
the line now names `current_user` with its three ADR-0068 alias spellings and
|
|
944
|
+
`features` — the deployment-flag root that provider documents for exactly
|
|
945
|
+
this kind of predicate — and states outright that `record` and `page.<var>`
|
|
946
|
+
do not exist there. (That bag and that paragraph also carried an `app` root
|
|
947
|
+
when this entry was first written; objectui#8155 removed it from both before
|
|
948
|
+
either shipped, so the released message names five roots, not six.)
|
|
949
|
+
|
|
950
|
+
**Why not generalise the copy instead.** Dropping the concrete root names would
|
|
951
|
+
have made one paragraph true everywhere at the cost of making it useful nowhere:
|
|
952
|
+
an author who mistyped a root needs to know which roots exist *at their tier*,
|
|
953
|
+
which is the whole reason the paragraph is read.
|
|
954
|
+
|
|
955
|
+
`data` is bound at the app-shell tier but is deliberately not advertised there —
|
|
956
|
+
every mount of `ExpressionProvider` in this repo passes `data={{}}` or omits it,
|
|
957
|
+
so naming it would point an author at a root that answers nothing.
|
|
958
|
+
- 748494b: The unresolvable-predicate diagnostic tells `hidden` / `hiddenOn` authors what actually
|
|
959
|
+
happened to their node (objectui#6503). Those two legs are the only ones in
|
|
960
|
+
`SchemaRenderer`'s visibility chain whose verdict is NOT negated: `evaluateCondition`
|
|
961
|
+
answers an unevaluable predicate with `true` on every path, the four negated legs turn that
|
|
962
|
+
into SHOWN, and these two return it as-is, so the same `true` sets `_hidden` and the
|
|
963
|
+
component returns `null`. Both were handed the consequence paragraph written for the negated
|
|
964
|
+
legs — "the gate did NOT bite - a predicate that cannot be evaluated reads on screen exactly
|
|
965
|
+
like one that said yes" — which is the opposite of what an author whose block VANISHED is
|
|
966
|
+
looking at. That line exists to name their predicate, and instead it sent them to hunt a
|
|
967
|
+
rendering bug that does not exist.
|
|
968
|
+
|
|
969
|
+
The two legs now print their own paragraph: the safe default is the one that BITES here, the
|
|
970
|
+
node was REMOVED and is not on the page at all, an absent node is indistinguishable from
|
|
971
|
+
metadata that meant to hide it, and nothing is wrong with the renderer.
|
|
972
|
+
|
|
973
|
+
Copy only — no verdict moved. The node still vanishes, which is the shipped fail-soft the
|
|
974
|
+
neighbouring family (objectui#3862 / #3955 / #6443 / #6487 / #6445) preserved deliberately,
|
|
975
|
+
and every case in the new suite pins the verdict beside the sentence.
|
|
976
|
+
|
|
977
|
+
`PredicateGateKind` — re-exported from `@object-ui/react`'s entry — gains a third member,
|
|
978
|
+
`'concealment'`, joining `'visibility'` and `'enablement'`. The opening line is deliberately
|
|
979
|
+
UNCHANGED (`UNRESOLVABLE_VISIBILITY_PREFIX`): these are visibility predicates, and an app or
|
|
980
|
+
a test filtering the console by that constant must go on catching them. Consumers that
|
|
981
|
+
switch EXHAUSTIVELY over the union, or key a `Record` by it, gain a third case to answer;
|
|
982
|
+
no runtime signature moved and every value accepted before still is.
|
|
983
|
+
- dc4365c: Retire the discovery-wire preview mode — the console no longer turns
|
|
984
|
+
authentication off because a server said `mode: 'preview'` (objectui#6654).
|
|
985
|
+
|
|
986
|
+
`@objectstack/spec` retired the `RuntimeMode` value `'preview'` and the whole
|
|
987
|
+
`PreviewModeConfig` block (objectstack `0c2334f6c`). This console still read that
|
|
988
|
+
surface back off the runtime discovery payload, which is a different layer from
|
|
989
|
+
the retired compile-time type — so the consumption could not simply be assumed
|
|
990
|
+
dead, and its removal was ruled deliberately (2026-08-29).
|
|
991
|
+
|
|
992
|
+
- `ConditionalAuthWrapper` (`@object-ui/app-shell`) drops the branch gated on
|
|
993
|
+
`discovery.mode === 'preview'`. That branch called `setAuthEnabled(false)` and
|
|
994
|
+
simulated an identity out of `discovery.previewMode`, every field behind a
|
|
995
|
+
default. Auth availability is now decided **only** by the ADR-0076 D12 service
|
|
996
|
+
reading (`isServiceUsable(discovery.services.auth)`), exactly as for any other
|
|
997
|
+
mode.
|
|
998
|
+
- `DiscoveryInfo` (`@object-ui/react`) drops the `previewMode` block and stops
|
|
999
|
+
documenting `'preview'` as a runtime mode; the package README's discovery
|
|
1000
|
+
section is updated to match.
|
|
1001
|
+
|
|
1002
|
+
**Accepted failure direction:** a deployment that still emits `mode: 'preview'`
|
|
1003
|
+
or a `previewMode` block now falls back to the ordinary auth reading — it
|
|
1004
|
+
requires login. That is loud, diagnosable and more secure than keeping a dormant
|
|
1005
|
+
auth-off path keyed on a spelling the platform no longer produces.
|
|
1006
|
+
|
|
1007
|
+
**Not affected:** `AuthProvider`'s `previewMode` prop, `useAuth().previewMode`
|
|
1008
|
+
and `PreviewBanner` in `@object-ui/auth` are a separate published capability
|
|
1009
|
+
with a different producer (a host passing the prop). Only the discovery-wire
|
|
1010
|
+
producer of that prop is retired; hosts that pass it explicitly are unchanged.
|
|
1011
|
+
- e321d52: `useSchemaPersistence` refuses to save a schema carrying function-valued keys
|
|
1012
|
+
(objectui#6658).
|
|
1013
|
+
|
|
1014
|
+
The designer save door handed the schema straight to `JSON.stringify`, which
|
|
1015
|
+
does not preserve a function value and says nothing about it: an object key is
|
|
1016
|
+
**dropped**, an array element is **coerced to `null`**. No throw, no warning.
|
|
1017
|
+
`save()`
|
|
1018
|
+
resolved with the id, `error` stayed null and `lastSavedAt` advanced, so every
|
|
1019
|
+
observable signal reported success while the stored schema had quietly lost the
|
|
1020
|
+
handler. The failure ordering was the bad one: loss at save, symptom at render
|
|
1021
|
+
or click, arbitrarily later, in a different component, with no link back.
|
|
1022
|
+
|
|
1023
|
+
Per the 2026-08-29 maintainer ruling on objectui#6658, the door now refuses such
|
|
1024
|
+
a save instead of performing a lossy one:
|
|
1025
|
+
|
|
1026
|
+
- `save()` walks the schema for function-valued keys **anywhere** in it —
|
|
1027
|
+
nested objects and array elements included — before serialization.
|
|
1028
|
+
- On a hit it sets `error`, returns `null`, leaves `lastSavedAt` and `isDirty`
|
|
1029
|
+
untouched, and never reaches the adapter. The message names the exact
|
|
1030
|
+
offending key paths (`columns[2].cell`, `toolbar.actions.onExport`) and both
|
|
1031
|
+
escapes: strip the callables before saving, or use the declarative form.
|
|
1032
|
+
- The guard sits at the **hook layer**, not inside the default localStorage
|
|
1033
|
+
adapter, so a host-injected or REST adapter is covered too — the documented
|
|
1034
|
+
REST adapter has the identical `JSON.stringify` shape, so a host following
|
|
1035
|
+
the docs inherited the behaviour rather than escaping it.
|
|
1036
|
+
|
|
1037
|
+
True lowering (objectstack's server-side `lowerCallables`) is recorded as
|
|
1038
|
+
unavailable at a browser door: functions cannot round-trip storage without a
|
|
1039
|
+
code registry, so refusal is the honest shape.
|
|
1040
|
+
|
|
1041
|
+
Behaviour change for hosts that previously saved callable-bearing schemas —
|
|
1042
|
+
those saves were already losing the callables and now fail loudly instead.
|
|
1043
|
+
Fully declarative schemas are unaffected and store byte-identically to before.
|
|
1044
|
+
No published type or signature changed.
|
|
1045
|
+
- 3beef6d: The spec's `dataSource` element binding is now DECLARED by the blocks that read
|
|
1046
|
+
it, so the html tier stops reporting the one working saved-view spelling as
|
|
1047
|
+
`unknown-prop` (objectui#6678).
|
|
1048
|
+
|
|
1049
|
+
`PageComponentSchema.dataSource` — `{ object, view, filter, sort, limit }` — is
|
|
1050
|
+
the one spelling that resolves a saved view for an object-bound block. It works,
|
|
1051
|
+
and it drew the identical `unknown-prop` warning as the two spellings that do
|
|
1052
|
+
nothing (`viewName`, `view`), because `validateTree` looks a prop up in the
|
|
1053
|
+
block's declared `inputs` and no registration declared this key. On the tier
|
|
1054
|
+
built to accept AI-authored pages, where the diagnostic IS the contract, the
|
|
1055
|
+
only signal pointed away from the key that works.
|
|
1056
|
+
|
|
1057
|
+
Adopting the maintainer ruling of 2026-08-29 — option B **in the injection
|
|
1058
|
+
form**:
|
|
1059
|
+
|
|
1060
|
+
- `ELEMENT_DATA_SOURCE_INPUT` is the single declaration, in `@object-ui/core`
|
|
1061
|
+
beside the binding's own semantics; `Registry.register` emits it for any
|
|
1062
|
+
registration whose renderer passed through the new `elementDataSourceBlock()`
|
|
1063
|
+
seam. One mechanism, one copy — not a hand-kept declaration per block, which is
|
|
1064
|
+
the shape that drifts and that a new block forgets. The seam lives in
|
|
1065
|
+
`@object-ui/core` and is re-exported by `@object-ui/react` beside
|
|
1066
|
+
`ElementDataSourceGate` for discoverability; call sites take the core import,
|
|
1067
|
+
because a registration runs at module scope and this repo's suites partially
|
|
1068
|
+
mock `@object-ui/react`.
|
|
1069
|
+
- Seventeen renderers, in thirteen files across twelve packages, reach the seam
|
|
1070
|
+
and now publish the key to the save gate, the parser whitelist, the generated
|
|
1071
|
+
JSX authoring types and the block list. The card named nine blocks; the tree
|
|
1072
|
+
also has `plugin-grid`, `plugin-timeline`, two further `plugin-form` blocks and
|
|
1073
|
+
`element:record_picker` — nothing was hand-listed, so the mechanism covered
|
|
1074
|
+
them. `element:record_picker` consumes the gate's HOOK and status panels rather
|
|
1075
|
+
than the wrapper tag (its object lives under `properties`), and was found by a
|
|
1076
|
+
render probe rather than by reading sources.
|
|
1077
|
+
- `dataSource` on a block that does NOT read it (`flex`, `card`) still reports
|
|
1078
|
+
`unknown-prop`. Adding the key to `sdui-parser`'s `BASE_PROPS` was refused for
|
|
1079
|
+
exactly this reason — that set mirrors `BaseSchema`, and silencing the key
|
|
1080
|
+
everywhere would make the diagnostic lie in the other direction.
|
|
1081
|
+
- New `check:element-data-source-declaration` fails any source that consumes the
|
|
1082
|
+
gate without reaching the seam, so a block added tomorrow cannot forget.
|
|
1083
|
+
|
|
1084
|
+
Behaviour of the binding itself is unchanged — this is a declaration, not a
|
|
1085
|
+
resolution change. The saved view still resolves its columns, and an
|
|
1086
|
+
unresolvable `view` still fails loudly rather than widening to the object's full
|
|
1087
|
+
scope.
|
|
1088
|
+
|
|
1089
|
+
The spec/registry parity gates (repo-wide and the `record:related_list` per-block
|
|
1090
|
+
pin) now derive their accepted set from the WHOLE node contract rather than from
|
|
1091
|
+
`ComponentPropsMap[type]` alone. `PageComponentSchema` accepts and keeps
|
|
1092
|
+
`dataSource` on a page-component node — it is a node-level key, a sibling of
|
|
1093
|
+
`type` and `className`, not a per-block prop — so the gates' previous complaint
|
|
1094
|
+
was measurably wrong. Derived from the spec, not exempted, and both still
|
|
1095
|
+
discriminate against an invented key.
|
|
1096
|
+
- 26896c6: `element:*` renderers stop re-reading a degenerate config bag as its own character indices — the third and last channel of the objectui#6752 / objectui#6760 hazard (objectui#6783).
|
|
1097
|
+
|
|
1098
|
+
Five modules under `packages/components/src/renderers/basic/` — `elements.tsx`, `data-list.tsx`, `text-input.tsx`, `record-picker.tsx`, `metadata-viewer.tsx` — each carried a copy of the same reader, `{ ...(schema?.props ?? {}), ...(schema?.properties ?? {}) }`. `??` only replaces `null`/`undefined`, so a non-object bag went into the object spread and came back out as indexed keys: for `properties: 'not-a-bag'`, the config bag a renderer received was `{ '0': 'n', '1': 'o', … '8': 'g' }` — nine keys nobody authored. The five copies are now one `readProps` (`renderers/basic/readProps.ts`) that asks `isConfigBag`, and a degenerate bag on either side contributes no keys.
|
|
1099
|
+
|
|
1100
|
+
`@object-ui/react` exports `isConfigBag` from its package entry. That is the API addition here, and it is the reason the fix is not a sixth spelling of the predicate: objectui#6761 converged six occurrences of "is this a real config bag?" behind one definition in `packages/react/src/utils/configBag.ts` and pinned it, but the pin scans `packages/react/src` only — a copy written one package over would be invisible to it. `@object-ui/components` already depends on `@object-ui/react` (all five modules import from it today), so the reachable answer was to publish the definition rather than retell it. Same reason the node-gate predicate reporter is exported at that entry (objectui#6038): one definition, read by every package that asks.
|
|
1101
|
+
|
|
1102
|
+
**What this does not change, measured rather than predicted.** No rendered output moves on today's tree. All five renderers read named keys off this bag, and the single onward spread — `metadata-viewer`'s `<StateMachineView {...props} />` — hands it to components that destructure named fields, so the indexed keys were computed and then dropped. The census behind objectui#6708 found zero authored nodes carrying a degenerate config bag, so this was a latent shape, not a live failure. What the guard buys is what objectui#6752 measured its own guard buys, one channel further down: the authored value's shape is not reinterpreted. objectui#5123's precedence is untouched — `properties` still wins a contested key, and a degenerate bag declares no key for either side to win.
|
|
1103
|
+
- b87f15b: `useOffline` auto-syncs mutations queued while already online (objectui#6818).
|
|
1104
|
+
|
|
1105
|
+
The auto-sync effect was keyed `[isOnline, enabled]` with
|
|
1106
|
+
`react-hooks/exhaustive-deps` suppressed, so its `queue.length === 0` guard was
|
|
1107
|
+
evaluated against the queue as it stood when `isOnline` or `enabled` last
|
|
1108
|
+
changed. `queueMutation` has never been conditional on being offline — it
|
|
1109
|
+
accepts entries whenever the hook is enabled — so anything queued while ALREADY
|
|
1110
|
+
online found the effect asleep, and nothing re-ran it. Only an explicit `sync()`
|
|
1111
|
+
drained those mutations; the hook whose job is auto-sync did nothing for them.
|
|
1112
|
+
|
|
1113
|
+
The suppression's stated reason ("only trigger on `isOnline` changes, not on
|
|
1114
|
+
every queue change") was about TIMER RESTARTS, and it is kept: the effect is
|
|
1115
|
+
keyed on the **boolean** `queue.length > 0`, never on `queue` or `queue.length`,
|
|
1116
|
+
so queueing a second mutation while the 100ms stabilization timer is already
|
|
1117
|
+
armed still does not re-run the effect or restart the timer. What the
|
|
1118
|
+
suppression never justified — the early return against a stale snapshot — is
|
|
1119
|
+
what changed.
|
|
1120
|
+
|
|
1121
|
+
`sync` also read `batchSize` through a ref (newest) while reading `queue` from
|
|
1122
|
+
its own closure (a snapshot), so the two halves of one call disagreed about how
|
|
1123
|
+
current they were, and the auto-sync effect retains such a closure by design.
|
|
1124
|
+
The queue now reaches `sync` through the same commit-phase mirror the sync
|
|
1125
|
+
config uses, so both halves are the newest committed values. That also takes
|
|
1126
|
+
`queue` out of `sync`'s dependency list: `sync` is keyed `[enabled]` and is
|
|
1127
|
+
stable across queued mutations, which is what lets the effect name every value
|
|
1128
|
+
it reads and drop the `eslint-disable` entirely rather than reword it.
|
|
1129
|
+
|
|
1130
|
+
**Behaviour change, graded `minor` deliberately.** `useOffline` is published and
|
|
1131
|
+
its out-of-repo population is unmeasured; the single in-repo caller
|
|
1132
|
+
(`AppHeader`) destructures `isOnline` only and is unaffected. A consumer that
|
|
1133
|
+
called `queueMutation` while online and relied on nothing being sent until it
|
|
1134
|
+
called `sync()` itself will now see that mutation flushed ~100ms later.
|
|
1135
|
+
`sync`'s identity is also more stable than before — it no longer changes on
|
|
1136
|
+
every queued mutation — which is safe for effects keyed on it but is a visible
|
|
1137
|
+
difference.
|
|
1138
|
+
|
|
1139
|
+
Not changed here: a `batchSize` smaller than the queue still drains one batch
|
|
1140
|
+
and leaves the remainder for the next transition, because whether one auto-sync
|
|
1141
|
+
should chain batches until the queue is empty is a separate question about what
|
|
1142
|
+
`batchSize` means, not about this guard. Filed as objectui#6857.
|
|
1143
|
+
- 03380aa: Inline edit: a rejected save now says WHICH field the server refused, and why.
|
|
1144
|
+
|
|
1145
|
+
Editing a record in place on a detail page and hitting Save used to surface the
|
|
1146
|
+
backend's own string when the write was refused — `VALIDATION_FAILED:
|
|
1147
|
+
Validation failed for crm_opportunity` — leaving the user to guess which of the
|
|
1148
|
+
fields they had just edited was the problem. The refusal has always been
|
|
1149
|
+
field-scoped (`@objectstack/objectql`'s validators throw `VALIDATION_FAILED`
|
|
1150
|
+
with `fields[]`, and both the REST layer and the runtime dispatcher pass those
|
|
1151
|
+
entries through intact); the inline surface was the last one still dropping
|
|
1152
|
+
them. `<InlineEditSaveBar>` now renders one reason per rejected field, named by
|
|
1153
|
+
that field's own label — the same treatment record forms have had since #3222.
|
|
1154
|
+
|
|
1155
|
+
Attribution never guesses. It reads the envelope through
|
|
1156
|
+
`@object-ui/react`'s `extractFieldErrors`, the single in-repo normaliser the
|
|
1157
|
+
form surface already uses, and an entry with no usable `field` is dropped
|
|
1158
|
+
rather than pinned on whichever input is nearby. In the drawer's callback mode,
|
|
1159
|
+
where persistence loops `onFieldSave(field, value)` one key at a time, a
|
|
1160
|
+
rejection is attributed to the key that was in flight — a fact about the write,
|
|
1161
|
+
not an inference. Anything that is not field-scoped (a network failure, a
|
|
1162
|
+
permission denial) keeps the cleaned single-line message it had before.
|
|
1163
|
+
|
|
1164
|
+
`@object-ui/react` gains one additive public API member to carry this, and it
|
|
1165
|
+
is the reason that package's entry is `minor` rather than `patch`:
|
|
1166
|
+
`InlineEditContextValue` now has **`fieldErrors`** — a nullable map of field
|
|
1167
|
+
machine name to the server's reason — alongside a **`setFieldErrors`** setter,
|
|
1168
|
+
the exact companions of the `error` / `setError` pair that interface already
|
|
1169
|
+
carried. Nothing is removed and nothing changes shape, so every existing host
|
|
1170
|
+
and consumer compiles and behaves as before; a host that never reads the new
|
|
1171
|
+
member sees no difference. It exists because the save bar and the field rows
|
|
1172
|
+
are SIBLINGS under `InlineEditProvider` in both persistence modes, so before
|
|
1173
|
+
this there was no channel between the component that receives a refusal and the
|
|
1174
|
+
components that render the fields it is about.
|
|
1175
|
+
|
|
1176
|
+
Also recorded in code, per the maintainer's ruling on objectui#6868: **the
|
|
1177
|
+
server is the validation authority on the inline-edit surface.** That was
|
|
1178
|
+
previously an absence — `InlineFieldInput` runs no rules and takes no `error`
|
|
1179
|
+
prop — and it is now a decision, written into both modules' headers with a
|
|
1180
|
+
pointer to the ruling. No client-side rule evaluator was added, and none should
|
|
1181
|
+
be: the server is the only rule source, and this surface only presents it.
|
|
1182
|
+
- 7c3df8f: A non-grid view's fetch now carries a platform row ceiling, and crossing it is
|
|
1183
|
+
never silent (objectui#7210, maintainer ruling a′, 2026-09-02).
|
|
1184
|
+
|
|
1185
|
+
Before this, `ObjectGantt`, `ObjectCalendar`, `ObjectMap` and `ObjectTree` each
|
|
1186
|
+
issued a `find` with **no `$top` at all**, so the request returned the entire
|
|
1187
|
+
filtered result set. At the 186 rows the card was filed from that is invisible;
|
|
1188
|
+
on an object with 100k scheduled rows it is the whole table into the browser,
|
|
1189
|
+
and nothing an author could write — `pagination.pageSize` included — could
|
|
1190
|
+
bound a request that never carried a cap to begin with.
|
|
1191
|
+
|
|
1192
|
+
**What changed.** Those four fetches now ask for `NON_GRID_ROW_CEILING_TOP`
|
|
1193
|
+
rows, draw at most `NON_GRID_ROW_CEILING` of them, and when the result set was
|
|
1194
|
+
larger they render a footnote naming both numbers, verbatim as it renders:
|
|
1195
|
+
*"Showing the first 2000 of 41234 records. Narrow the filter."* Below the
|
|
1196
|
+
ceiling nothing changes: the full set draws and no footnote appears.
|
|
1197
|
+
|
|
1198
|
+
The four view packages take a **minor**, not a patch: a result set above the
|
|
1199
|
+
ceiling is no longer drawn in full, which is a behaviour break whatever the
|
|
1200
|
+
fixed group does to the released version number.
|
|
1201
|
+
|
|
1202
|
+
**The ceiling is a platform constant, not an authorable key** — `2000`, exported
|
|
1203
|
+
from `@object-ui/react` as `NON_GRID_ROW_CEILING`. An authored `limit` or
|
|
1204
|
+
`dataSource: { limit }` still does not reach these queries, by the same ruling;
|
|
1205
|
+
three alternatives were rejected with it (a documentation note only — still the
|
|
1206
|
+
whole table; truncating at `pageSize` — silent, and a complete schedule capped
|
|
1207
|
+
at one page; an authorable `maxRows` — a new permanent key every author sets).
|
|
1208
|
+
|
|
1209
|
+
**Why 2,000.** One constant for all four, so the binding view sets it. Measured
|
|
1210
|
+
in this repo's jsdom lane: gantt, calendar and map hold their DOM flat as rows
|
|
1211
|
+
grow (virtualised task list; four events per day cell; auto-clustering above
|
|
1212
|
+
100 markers), while `ObjectTree` flattens every expanded node into the document
|
|
1213
|
+
at a linear **5.2 DOM elements per record** with no virtualisation. 2,000 rows
|
|
1214
|
+
is where the worst of the four lands at ~10,400 elements — an order of
|
|
1215
|
+
magnitude above Lighthouse's "excessive DOM size" warning, and still ~10x the
|
|
1216
|
+
real application result set this card came from.
|
|
1217
|
+
|
|
1218
|
+
New exports on `@object-ui/react`: `NON_GRID_ROW_CEILING`,
|
|
1219
|
+
`NON_GRID_ROW_CEILING_TOP`, `applyNonGridRowCeiling`, `NonGridRowCeilingNote`
|
|
1220
|
+
and the type `NonGridCeilingResult`. Two new `common.*` i18n keys carry the
|
|
1221
|
+
footnote copy in all ten packs.
|
|
1222
|
+
- cfc9b6d: A filter on a record page can now be scoped to the record the page shows (objectui#7297). Write `{record_id}` as a filter value, for example `{ "assignee": "{record_id}" }` on an `element:number` counting tasks, and on a `type: 'record'` page it resolves to the id of the record in view: on one person's page the number is that person's count, and on the next person's page it is theirs. It works in a component's own `filter` and in its component-level `dataSource.filter`, on every data component that resolves its filter through `@object-ui/react`'s `useFilterScope()`. The token is declared by `@objectstack/spec` 17.5.0 (`RECORD_CONTEXT_TOKENS`, objectstack-ai/objectstack#20003).
|
|
1223
|
+
|
|
1224
|
+
The id comes from the record page's mounted record context, never from a URL parameter or a page variable. `@object-ui/react`'s `useFilterScope()` adds it to the scope it returns (as the new optional `recordId` member of `@object-ui/core`'s `FilterTokenScope`) whenever a `RecordContextProvider` is mounted above the component; `FilterScopeProvider` is unchanged and still carries only the session values. The held filters in `useResolvedFilter`, `object-grid` and `object-view` resolve again when the record changes, so moving to another record queries again without a remount.
|
|
1225
|
+
|
|
1226
|
+
Anywhere with no record in context (a list view, a dashboard, a report, or a page that is not a record page) `{record_id}` is refused by name: `resolveContextTokens` reports it through `onUnresolved` (a console warning by default), the way an unresolved `{current_user_id}` is reported, and leaves it as written. It never becomes `null` and its condition is never dropped, so a number can never silently count every record; the ObjectStack server refuses the leftover token by name (`FILTER_TOKEN_UNRESOLVED`). Like the session tokens, `{record_id}` only scopes what a component shows. It is not access control: which rows a user may read is still decided by the server's row-level security.
|
|
1227
|
+
|
|
1228
|
+
**Correction, 2026-09-30 (objectui#8945).** The example in the first paragraph, `{ "assignee": "{record_id}" }`, is the retired MongoDB-style record form. `@objectstack/spec` 17.5.0 refuses it at an `element:number` `filter`, because that key takes an array and a record is refused by kind. Write the same condition as a `ViewFilterRule` array, which the spec accepts: `[{ "field": "assignee", "operator": "equals", "value": "{record_id}" }]`. A component-level `dataSource.filter` takes the same array. The `{record_id}` token itself, and the rest of this entry, are unaffected.
|
|
1229
|
+
- 71a4a53: **BREAKING: `useClientNotifications` is removed from `@object-ui/react`**
|
|
1230
|
+
|
|
1231
|
+
What left: the `useClientNotifications` hook and its two types,
|
|
1232
|
+
`UseClientNotificationsOptions` and `UseClientNotificationsResult`. Code that
|
|
1233
|
+
imports any of the three from `@object-ui/react` no longer compiles.
|
|
1234
|
+
|
|
1235
|
+
Migration: use the shell's shared inbox feed. `@object-ui/app-shell` reads the
|
|
1236
|
+
ADR-0030 inbox rows (`sys_inbox_message`, with read-state in
|
|
1237
|
+
`sys_notification_receipt`) through one shared feed, and each of its inbox
|
|
1238
|
+
surfaces reads that feed: the header bell in `AppHeader`, and a
|
|
1239
|
+
`global:notifications` node placed on a page. Mount one of those instead of
|
|
1240
|
+
polling `client.notifications.*` yourself. The "Server-side notifications"
|
|
1241
|
+
section of the notifications guide describes both.
|
|
1242
|
+
|
|
1243
|
+
Why: the hook ran a poller of its own (a `setInterval` over
|
|
1244
|
+
`client.notifications.list`) and copied the rows into `NotificationProvider`,
|
|
1245
|
+
beside the shell's feed rather than through it. Mounted next to the console
|
|
1246
|
+
shell, it would have read the same rows on a second schedule and kept a second
|
|
1247
|
+
copy of their read-state: the duplicate-scheduler shape that objectui#4197,
|
|
1248
|
+
objectui#4225 and objectui#4316 removed. It had no consumers. Before the
|
|
1249
|
+
removal, a census found no call site in this repository (objectui `e6203d756`)
|
|
1250
|
+
beyond its own file, the package barrel, the notifications guide and CHANGELOG
|
|
1251
|
+
entries. It also found none in the sibling `objectstack` (`338feda6`) and
|
|
1252
|
+
`hotcrm` (`087b7c5`) repositories. Importers outside those repositories are
|
|
1253
|
+
NOT MEASURED. Under the ruling on objectui#7389, a published capability with
|
|
1254
|
+
zero consumers retires immediately, with no deprecation window.
|
|
1255
|
+
|
|
1256
|
+
Unchanged: `NotificationProvider`, `useNotifications` and the rest of the
|
|
1257
|
+
notification system in `@object-ui/react`.
|
|
1258
|
+
|
|
1259
|
+
Marked `minor`, not `major`, per the version-alignment convention in
|
|
1260
|
+
AGENTS.md; the break is real and is stated here.
|
|
1261
|
+
- 1237ae4: The non-grid row ceiling's mechanism moves to `@object-ui/core`, its probe row
|
|
1262
|
+
lives in one query helper, and its footnote takes the result and nothing else
|
|
1263
|
+
(objectui#7508, maintainer ruling A′ — follow-up to objectui#7210).
|
|
1264
|
+
|
|
1265
|
+
**`@object-ui/core` — new exports**, beside the `extractRecords` they wrap:
|
|
1266
|
+
|
|
1267
|
+
- `NON_GRID_ROW_CEILING` (moved from `@object-ui/react`, value unchanged);
|
|
1268
|
+
- `applyNonGridRowCeiling` and the type `NonGridCeilingResult` (moved from
|
|
1269
|
+
`@object-ui/react`, behaviour unchanged);
|
|
1270
|
+
- `nonGridRowCeilingQuery()` (new) — returns `{ $top }`, the ceiling plus ONE
|
|
1271
|
+
probe row. It is the only place that `+ 1` is written; spread it into the
|
|
1272
|
+
`find()` query.
|
|
1273
|
+
|
|
1274
|
+
**`@object-ui/react`**:
|
|
1275
|
+
|
|
1276
|
+
- still exports `NON_GRID_ROW_CEILING`, `applyNonGridRowCeiling` and the type
|
|
1277
|
+
`NonGridCeilingResult`, now re-exported from `@object-ui/core` — the same
|
|
1278
|
+
bindings, so no import of these three changes;
|
|
1279
|
+
- **removed**: `NON_GRID_ROW_CEILING_TOP`. Replace `$top: NON_GRID_ROW_CEILING_TOP`
|
|
1280
|
+
with `...nonGridRowCeilingQuery()` from `@object-ui/core`. This supersedes the
|
|
1281
|
+
export list in objectui#7210's entry, which names it;
|
|
1282
|
+
- **breaking prop change**: `NonGridRowCeilingNote` takes `result`
|
|
1283
|
+
(a `NonGridCeilingResult`) and no other prop. `drawn`, `total`, `truncated`
|
|
1284
|
+
and `className` are gone, with no deprecated path. Pass the object
|
|
1285
|
+
`applyNonGridRowCeiling` returned: the note prints `result.rows.length` and
|
|
1286
|
+
`result.total`, so a footnote whose numbers disagree with the rows drawn can
|
|
1287
|
+
no longer be written. It now carries `shrink-0` itself, which the gantt used
|
|
1288
|
+
to pass in through `className`.
|
|
1289
|
+
|
|
1290
|
+
`ObjectCalendar`, `ObjectGantt`, `ObjectMap` and `ObjectTree` take the helpers
|
|
1291
|
+
from `@object-ui/core` and hand the note their result; what they fetch, draw and
|
|
1292
|
+
print is unchanged.
|
|
1293
|
+
- b041b9c: `@object-ui/core` publishes `resolveRecordSourceObjectName`, the ONE reader for "which
|
|
1294
|
+
object is this block bound to".
|
|
1295
|
+
|
|
1296
|
+
Six view plugins each spelled that resolution locally — `ObjectCalendar` twice,
|
|
1297
|
+
`ObjectGantt`, `ObjectTree` twice, `ObjectMap`, `ObjectGrid` — and had drifted: three
|
|
1298
|
+
wrote `?? schema.objectName`, one `|| ''`, one `: undefined`, one an `'object' in
|
|
1299
|
+
dataConfig` test. They now delegate to one function that states the published
|
|
1300
|
+
record-source ladder `77cb489b4` declared (`data`, then `staticData`, then `objectName`) once.
|
|
1301
|
+
|
|
1302
|
+
**No behaviour changes.** Each site's pre-collapse expression is transcribed verbatim
|
|
1303
|
+
into `record-source.behaviourNeutrality-7627.test.ts` and asserted equal to its
|
|
1304
|
+
post-collapse spelling across the whole contract-valid input matrix — both bindings
|
|
1305
|
+
present, data only, `objectName` only, empty `objectName`, empty `data.object`, the
|
|
1306
|
+
`api` / `value` / `staticData` / array-shorthand providers, and nothing bound.
|
|
1307
|
+
|
|
1308
|
+
**Two questions stay two questions.** `normalizeListViewSchema`'s gap-fill (#7477,
|
|
1309
|
+
ruling B, landed as `00d2fa682`) is untouched and is NOT re-pointed at the new reader: it answers
|
|
1310
|
+
how `objectName` gets POPULATED when absent, where an already-present `objectName` wins.
|
|
1311
|
+
The new reader answers which object a block RESOLVES, where the `data` block wins — the
|
|
1312
|
+
order declared on both published faces in `@object-ui/types` and pinned by
|
|
1313
|
+
`objectql-record-source-refinement-6939.test.ts`. Merging them would silently override
|
|
1314
|
+
one standing ruling or the other.
|
|
1315
|
+
|
|
1316
|
+
**`ObjectGantt`'s `persistLayoutKey` is deliberately excluded** and keeps its inverted
|
|
1317
|
+
order, with an in-place comment saying why: its receiver is a localStorage key
|
|
1318
|
+
(`gantt-layout:KEY:filters`), not a record source, so re-pointing it would orphan every
|
|
1319
|
+
saved layout and filter-chip set of a view carrying both bindings. Two more sites the
|
|
1320
|
+
finding listed are not object-name readers at all and were struck: `ObjectGantt`'s
|
|
1321
|
+
refresh-handler predicate (`object` OR `api`) and `plugin-dashboard`'s `isObjectProvider`
|
|
1322
|
+
type-guard over a widget's `data`.
|
|
1323
|
+
|
|
1324
|
+
`useSettledSchema`'s doc comment stops prescribing the hand-written ladder at all four
|
|
1325
|
+
lines that taught it, so the copies cannot re-seed from the hook that replaced them.
|
|
1326
|
+
- 95bf128: feat(react): an action's `params` values are templates, evaluated where `properties` are
|
|
1327
|
+
|
|
1328
|
+
Every string leaf of a node's `params` bag - the node-level `params` and
|
|
1329
|
+
`properties.params` alike (and the legacy `props.params`, which follows its
|
|
1330
|
+
canonical bag), at any depth, inside nested objects and arrays - is now
|
|
1331
|
+
template-evaluated by `SchemaRenderer`, with the same evaluator and the same
|
|
1332
|
+
scope that already evaluates `properties`. A metadata-authored button on a
|
|
1333
|
+
record page can therefore name the record it sits on:
|
|
1334
|
+
|
|
1335
|
+
```json
|
|
1336
|
+
{ "type": "action:button", "label": "Edit", "actionType": "navigate_edit",
|
|
1337
|
+
"params": { "objectName": "account", "recordId": "${record.id}" } }
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
Before this, both spellings of `params.recordId` reached the action handler as
|
|
1341
|
+
the raw `${record.id}` text: the node-level bag was never evaluated, and
|
|
1342
|
+
`properties.params` was one value of a shallow loop, so the template inside it
|
|
1343
|
+
was never visited.
|
|
1344
|
+
|
|
1345
|
+
Behaviour change, stated plainly: a `params` string containing `${...}` is now
|
|
1346
|
+
evaluated at render time instead of being passed through verbatim. It reaches
|
|
1347
|
+
only values whose templates never worked - no authored `params` bag in this
|
|
1348
|
+
repository carries one. A template that still cannot be evaluated (an unbound
|
|
1349
|
+
root such as `${nope.id}`) keeps its source text exactly as before, and the
|
|
1350
|
+
development-build unevaluated-expression diagnostic now reports it by its path
|
|
1351
|
+
(`params.recordId`, `properties.params.target.id`), so a wrong template stays
|
|
1352
|
+
loud.
|
|
1353
|
+
|
|
1354
|
+
What is not walked: an ARRAY `params` (the `ActionParam[]` definition list the
|
|
1355
|
+
params dialog renders) is left as authored; keys are never evaluated, only
|
|
1356
|
+
values; non-plain objects (`Date`, `Map`, class instances) and functions pass
|
|
1357
|
+
through by identity; a cycle in a host-built bag ends the walk. Every other
|
|
1358
|
+
nested config value keeps the shallow reading it had.
|
|
1359
|
+
|
|
1360
|
+
⚠️ **Dated note, 2026-10-01 — props written flat on the node — objectui#10872 batch 10.** Later in this same release a flat `actionType` on an `action:button` node is refused by name, by `objectui validate` and the strict authoring face, with a message naming `properties.actionType`. The node-level `params` object the example above shows is refused the same way, toward `properties.params`, where objectui#10289's entry moved its values: the runtime no longer reads a node-level `params` object as values. So the example is kept as the reading of this change, and the node is written `{ "type": "action:button", "properties": { "label": "Edit", "actionType": "navigate_edit", "params": { "objectName": "account", "recordId": "${record.id}" } } }` in this release. The template evaluation this entry describes applies to `properties.params` unchanged. The rest of this entry is kept as the reading of this change.
|
|
1361
|
+
- edea22a: **BREAKING** — `SchemaRendererProvider`'s `dataSource` prop, and the context
|
|
1362
|
+
type every `useSchemaContext()` consumer reads back, are the published
|
|
1363
|
+
`DataSource` contract instead of `any`.
|
|
1364
|
+
|
|
1365
|
+
**FROM** `dataSource={anything}` **TO** `dataSource={adapter}` — a `DataSource`
|
|
1366
|
+
from `@object-ui/types`, or `null` / `undefined` when the host has no adapter
|
|
1367
|
+
bound.
|
|
1368
|
+
|
|
1369
|
+
```ts
|
|
1370
|
+
// before — compiled, and failed at runtime on the first find()
|
|
1371
|
+
<SchemaRendererProvider dataSource={'not-an-adapter'}>
|
|
1372
|
+
// before — compiled, and no reader can do anything with it
|
|
1373
|
+
<SchemaRendererProvider dataSource={{}}>
|
|
1374
|
+
// after
|
|
1375
|
+
<SchemaRendererProvider dataSource={adapter}> // a DataSource
|
|
1376
|
+
<SchemaRendererProvider dataSource={undefined}> // "I have no adapter"
|
|
1377
|
+
```
|
|
1378
|
+
|
|
1379
|
+
Both sites are typed `DataSource | null | undefined` — the spelling
|
|
1380
|
+
`useSettledSchema` in this same package already used. The two absences are part
|
|
1381
|
+
of the contract, not a weakening of it: a Studio preview, a `kind:'react'` page
|
|
1382
|
+
rendered before the host's adapter connects, and a widget probe driving
|
|
1383
|
+
`apiFetch` alone all render with nothing bound, and every reader in the tree
|
|
1384
|
+
already guards for it. What the union refuses is everything that is not an
|
|
1385
|
+
adapter: a string, an empty object, a plain data bag, a partial adapter missing
|
|
1386
|
+
a required member.
|
|
1387
|
+
|
|
1388
|
+
The measured cost, both halves, because the two `any`s have different blast
|
|
1389
|
+
radii (measured separately on `origin/main`, whole-repo type-check over the 33
|
|
1390
|
+
packages that depend on `@object-ui/react`):
|
|
1391
|
+
|
|
1392
|
+
- the **context type** — the `any` that reaches every `useSchemaContext()`
|
|
1393
|
+
reader — reds **7 diagnostics at 7 sites in 4 packages**, all of them
|
|
1394
|
+
production code or a mocked module factory.
|
|
1395
|
+
- the **provider prop** — the injection points — reds **52 diagnostics at 27
|
|
1396
|
+
sites in 11 packages**, all but one of them test doubles.
|
|
1397
|
+
|
|
1398
|
+
That ordering is the reverse of the prediction on the card: the context `any`
|
|
1399
|
+
was expected to be the expensive one because it infects the whole tree, and it
|
|
1400
|
+
is the cheap one, because every reader in the tree already guarded and none of
|
|
1401
|
+
them ever reached past `find` / `getObjectSchema`. The prop is the expensive
|
|
1402
|
+
one, because the injection points are overwhelmingly test doubles that were
|
|
1403
|
+
never complete adapters. The full accounting is on objectui#7912.
|
|
1404
|
+
|
|
1405
|
+
Two runtime behaviours change, both in the "no adapter" direction and both
|
|
1406
|
+
strictly closer to what the surrounding code already intended:
|
|
1407
|
+
|
|
1408
|
+
- `@object-ui/components`' `kind:'react'` page passed an empty object as its
|
|
1409
|
+
"no adapter yet" stand-in. An empty object is TRUTHY, so it walked past every
|
|
1410
|
+
`if (!dataSource)` guard written to catch exactly that state and failed later,
|
|
1411
|
+
at the call. It now passes the absent adapter itself, so the guard fires where
|
|
1412
|
+
it was meant to. The module-constant identity that stand-in existed for is
|
|
1413
|
+
preserved: `null` is a primitive, so the provider's memo is unaffected.
|
|
1414
|
+
- `@object-ui/plugin-calendar`, `@object-ui/plugin-gantt` and
|
|
1415
|
+
`@object-ui/plugin-kanban` collapse a `null` adapter from the context to
|
|
1416
|
+
`undefined` before handing it to their widget, whose prop declares the single
|
|
1417
|
+
spelling `dataSource?: DataSource`.
|
|
1418
|
+
|
|
1419
|
+
Nothing else moves at runtime: no value flowing through this key changes, and
|
|
1420
|
+
no data path is touched. A TypeScript consumer outside this repo that handed
|
|
1421
|
+
this prop something other than an adapter now gets a compile error naming the
|
|
1422
|
+
key (TS2322 / TS2739 / TS2740), which is why the FROM/TO is spelled out above.
|
|
1423
|
+
|
|
1424
|
+
Five `as any` reads of this context in `@object-ui/fields` are gone — they were
|
|
1425
|
+
redundant the moment the seam became honest — and `LookupField`'s local
|
|
1426
|
+
re-declaration of the imported context as a `Context` of `any`, which laundered
|
|
1427
|
+
its `dataSource` read while looking typed, is gone with them. Both directions of
|
|
1428
|
+
the contract are pinned against the real compiler in
|
|
1429
|
+
`SchemaRendererContext.dataSourceType.pin.test.ts`, and the card's planted
|
|
1430
|
+
documentation probe (a bare string in `packages/react/README.md`'s provider
|
|
1431
|
+
example) now fails `pnpm check:doc-snippets`, where it used to exit 0 with zero
|
|
1432
|
+
diagnostics.
|
|
1433
|
+
|
|
1434
|
+
objectui#7912.
|
|
1435
|
+
- 0ea7054: Remove 37 runtime dependencies that no file in the declaring package consumes, and gate
|
|
1436
|
+
the direction so the next one cannot land (objectui#8198).
|
|
1437
|
+
|
|
1438
|
+
`check:phantom-deps` judges imports that are not declared; nothing judged the reverse,
|
|
1439
|
+
so a declaration could outlive its last consumer indefinitely. That is what happened to
|
|
1440
|
+
`recharts` in `@object-ui/components` after objectui#7397 deleted its only importer — it
|
|
1441
|
+
was removed by hand on objectui#7625, and nothing would have reported the next one. The
|
|
1442
|
+
new `pnpm check:unused-deps` asks the reverse question over `dependencies` and
|
|
1443
|
+
`optionalDependencies` of every released package.
|
|
1444
|
+
|
|
1445
|
+
**Potentially breaking, for consumers relying on hoisting.** Nothing these packages ship
|
|
1446
|
+
changes: their Vite `external` predicates are path-based and never read `dependencies`,
|
|
1447
|
+
so no built artifact moves. What changes is the install graph — a project that imports
|
|
1448
|
+
one of the removed packages while depending only on the ObjectUI package that used to
|
|
1449
|
+
drag it in will no longer resolve it. Declare it directly; that is the correct
|
|
1450
|
+
dependency edge in either case. The removals, by package:
|
|
1451
|
+
|
|
1452
|
+
- `@object-ui/plugin-designer`: `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities`, `@object-ui/fields`
|
|
1453
|
+
- `@object-ui/plugin-chatbot`: `react-markdown`, `react-syntax-highlighter`, `remark-gfm` (and the orphaned `@types/react-syntax-highlighter`)
|
|
1454
|
+
- `@object-ui/plugin-report`: `@object-ui/plugin-grid`, `clsx`, `react-i18next`, `tailwind-merge`
|
|
1455
|
+
- `@object-ui/plugin-map`: `@objectstack/spec`, `lucide-react`, `zod`
|
|
1456
|
+
- `@object-ui/runner`: `class-variance-authority`, `clsx`, `tailwind-merge`
|
|
1457
|
+
- `@object-ui/core`: `lodash`, `zod`
|
|
1458
|
+
- `@object-ui/layout`: `clsx`, `tailwind-merge`, and `react-dom` — which it pinned at an exact version in `dependencies` while also declaring it as a peer range, i.e. a library hard-depending on the renderer it asks its host to supply
|
|
1459
|
+
- `@object-ui/plugin-dashboard`: `clsx`, `tailwind-merge`, and the same `react-dom` defect
|
|
1460
|
+
- `@object-ui/plugin-ai`: `@object-ui/react`, `clsx`, `tailwind-merge`
|
|
1461
|
+
- `@object-ui/fields`: `clsx`, `tailwind-merge`
|
|
1462
|
+
- `@object-ui/console`: `@object-ui/react-runtime`, `sucrase`
|
|
1463
|
+
- `@object-ui/auth`: `@object-ui/types`
|
|
1464
|
+
- `@object-ui/plugin-calendar`: `@object-ui/fields`
|
|
1465
|
+
- `@object-ui/plugin-editor`, `@object-ui/plugin-markdown`: `@object-ui/react`
|
|
1466
|
+
- `@object-ui/react`: `react-hook-form`
|
|
1467
|
+
|
|
1468
|
+
Every one was verified by a whole-package grep before removal — the name appeared nowhere
|
|
1469
|
+
under the package but its own manifest and CHANGELOG — and the whole workspace builds,
|
|
1470
|
+
type-checks and tests green afterwards.
|
|
1471
|
+
- 4eaa835: `BaseSchema.testId` is now emitted as `data-testid` (objectui#8268, ADR-0049 enforce arm).
|
|
1472
|
+
|
|
1473
|
+
The declaration promised "Rendered as data-testid attribute" and nothing rendered it. `testId`
|
|
1474
|
+
was absent from `SchemaRenderer`'s metadata destructure, so it fell through into the spread and
|
|
1475
|
+
reached the DOM under React's fallback spelling for an unrecognised prop — a non-standard
|
|
1476
|
+
lowercase `testid` that `screen.getByTestId(...)` and `[data-testid=...]` both miss. The
|
|
1477
|
+
validator accepted the key, so an author who followed the documented row got a failing test with
|
|
1478
|
+
no diagnostic near the cause.
|
|
1479
|
+
|
|
1480
|
+
Measured on `93127bd6f` through the real `ui:scroll-area` renderer, node
|
|
1481
|
+
`{ id: 'my-scroll', testId: 'my-tid' }`:
|
|
1482
|
+
|
|
1483
|
+
```
|
|
1484
|
+
before id="my-scroll" testid="my-tid" data-obj-id="my-scroll"
|
|
1485
|
+
[data-testid="my-tid"] false · [testid="my-tid"] true
|
|
1486
|
+
after id="my-scroll" data-testid="my-tid" data-obj-id="my-scroll"
|
|
1487
|
+
[data-testid="my-tid"] true · [testid="my-tid"] false
|
|
1488
|
+
```
|
|
1489
|
+
|
|
1490
|
+
`id` reaching the DOM as `id` + `data-obj-id` is the contrast leg, green on both runs — the
|
|
1491
|
+
strip list and the spread were working as designed; only this key had no mapping.
|
|
1492
|
+
|
|
1493
|
+
The other direction — retiring the promise — was considered and declined. It is what
|
|
1494
|
+
`c1fe272ad` did for `BaseSchema.hidden`, but that key had a working behaviour to describe and
|
|
1495
|
+
zero named consumers, and the ruling's decline turned on exactly that. This promise already has
|
|
1496
|
+
carriers outside the type declaration: `content/docs/api/schema-reference.md` states it as a
|
|
1497
|
+
table row and authors `testId` in that page's own base-schema example, `@object-ui/cli`'s
|
|
1498
|
+
`OBJECTUI_STRUCTURAL_KEYS` identifies a file as an ObjectUI schema node by this key,
|
|
1499
|
+
at this change `ObjectGridSlotKey` / `ObjectFormSlotKey` pin it, `SchemaBuilder.testId()` writes
|
|
1500
|
+
it, and ADR-0054 C4 — shipped — reads "the renderer emits `data-testid` … derived from metadata".
|
|
1501
|
+
|
|
1502
|
+
⚠️ **Dated note, 2026-09-29 — `ObjectGridSlotKey` withholds `testId` — objectui#10976.** Later
|
|
1503
|
+
in this same release `ObjectGridSlotKey` stops carrying `testId`: `ObjectView` draws its grid as
|
|
1504
|
+
a component, not as a schema node, so nothing emits the attribute there, and `table.testId` is
|
|
1505
|
+
refused. `ObjectFormSlotKey` still carries it. The rest of this entry is kept as the reading of
|
|
1506
|
+
this change; the objectui#10976 entry states what ships.
|
|
1507
|
+
|
|
1508
|
+
`minor`, not `patch`: nodes that author `testId` change what they emit in both directions, so a
|
|
1509
|
+
selector written against the accidental `[testid=…]` stops matching.
|
|
1510
|
+
|
|
1511
|
+
Nodes that author no `testId` are byte-identical to before. The attribute is spread
|
|
1512
|
+
conditionally rather than passed as `'data-testid': undefined`, because the unconditional form
|
|
1513
|
+
puts the key in every node's props bag and a component that sets its own `data-testid` before
|
|
1514
|
+
spreading what it is handed loses its locator — measured, 9 tests red across 5 files, three of
|
|
1515
|
+
them the byte-for-byte props-bag pins (objectui#6708 / #6752 / #6760).
|
|
1516
|
+
|
|
1517
|
+
`testId` is stripped from the spread but kept in objectui#4795's unevaluated-expression scan
|
|
1518
|
+
set: the strip list is that diagnostic's exclusion list because every other member holds raw
|
|
1519
|
+
predicate source, and this key holds a literal.
|
|
1520
|
+
- 152f0a7: `toRenderableSchema` is behaviour-preserving for the falsy primitives too, so `empty`
|
|
1521
|
+
stops printing a stray `"0"` (objectui#8908).
|
|
1522
|
+
|
|
1523
|
+
`toRenderableSchema` is the repo's permanent bridge from `@object-ui/types`' `SchemaNode`
|
|
1524
|
+
union onto `SchemaRendererProps['schema']`, which deliberately declares no `number` /
|
|
1525
|
+
`boolean` (objectui#4548 ruling Q2). Its docblock promised — and its callers were entitled
|
|
1526
|
+
to rely on — that "the value a caller forwards renders identically whether or not it
|
|
1527
|
+
passes through here". That was false for exactly two of the six `SchemaNode` members.
|
|
1528
|
+
|
|
1529
|
+
The bridge mapped every `number` / `boolean` onto `String(node)`. `String(0)` and
|
|
1530
|
+
`String(false)` are the NON-EMPTY strings `'0'` and `'false'`, which are truthy, so they
|
|
1531
|
+
sailed past `SchemaRenderer`'s own `!evaluatedSchema` leg and rendered as their own text —
|
|
1532
|
+
while the same two values handed to `SchemaRenderer` directly render nothing, pinned since
|
|
1533
|
+
objectui#4548. The mapping is now two-legged and mirrors the renderer's own narrowing
|
|
1534
|
+
order: a truthy `number` / `boolean` still becomes its text form, and a falsy one
|
|
1535
|
+
(`0`, `-0`, `NaN`, `false`) becomes nothing.
|
|
1536
|
+
|
|
1537
|
+
**Behaviour change on a published surface, deliberately.** The reachable, shipped instance
|
|
1538
|
+
is `empty`: its `action` slot gates on nullish rather than truthiness (objectui#7105, so a
|
|
1539
|
+
bare string node renders instead of being dropped), so `0` and `false` went through the
|
|
1540
|
+
bridge. `{ type: 'empty', title: 'T', action: 0 }` rendered `"T0"` and now renders `"T"`,
|
|
1541
|
+
which is what the same node handed straight to `SchemaRenderer` has always rendered.
|
|
1542
|
+
Every other slot routed through the bridge — page regions, header-bar actions, detail tabs
|
|
1543
|
+
and footers, report sections, view configs — gets the same correction for the same two
|
|
1544
|
+
values.
|
|
1545
|
+
|
|
1546
|
+
**No type change, and that is not an accident.** The return type is unchanged:
|
|
1547
|
+
`BaseSchema | string | null | undefined`. Letting `0` / `false` through UNCHANGED would
|
|
1548
|
+
have widened it to admit `number` / `boolean`, which `SchemaRenderer`'s prop refuses by
|
|
1549
|
+
ruling — so it would have been a TS2322 at every call site that forwards the result
|
|
1550
|
+
straight into `<SchemaRenderer schema={…} />`, which is all nineteen of them. The falsy
|
|
1551
|
+
leg returns nothing instead, which keeps the guarantee and the type both. The exact
|
|
1552
|
+
equality of the two is pinned in
|
|
1553
|
+
`packages/react/src/__tests__/SchemaRenderer.propsResolution.test.ts`.
|
|
1554
|
+
|
|
1555
|
+
**Migration.** Nothing has to change, and nothing needs a code edit. A slot that authored
|
|
1556
|
+
`0` or `false` was painting a literal `"0"` / `"false"` where the platform's answer for
|
|
1557
|
+
those values is nothing; it now renders nothing. If some surface was relying on that stray
|
|
1558
|
+
text — authoring `action: 0` to make the digit appear — author the string `'0'` instead,
|
|
1559
|
+
which is a `SchemaNode` and renders as its own text on both paths.
|
|
1560
|
+
|
|
1561
|
+
**Supersedes one sentence in the objectui#8331 entry.** That note said the `data-table`
|
|
1562
|
+
`emptyAction` truthiness leg is kept "precisely so those two never reach the bridge, whose
|
|
1563
|
+
`String` mapping would otherwise turn them into the text `"0"` and `"false"`". The bridge
|
|
1564
|
+
no longer does that. The leg is kept anyway, and objectui#8908 kept it deliberately rather
|
|
1565
|
+
than removing it as newly redundant: it is what makes that slot's answer independent of
|
|
1566
|
+
the bridge, and it is why `emptyAction` was never affected by this defect in the first
|
|
1567
|
+
place.
|
|
1568
|
+
|
|
1569
|
+
⚠️ **Dated note, 2026-10-01 — the bridge's return type widens with the prop — objectui#11364.** Later in this same release `SchemaRendererProps.schema` becomes `BaseSchema | AuthoringNode | string | null | undefined`. `toRenderableSchema` returns `SchemaRendererProps['schema']` by reference, so its return type widens with it. So "The return type is unchanged: `BaseSchema | string | null | undefined`" no longer describes the release as a whole: the falsy leg still adds no `number` / `boolean`, and the pinned equality with the prop still holds. The rest of this entry is kept as the reading of this change.
|
|
1570
|
+
|
|
1571
|
+
⚠️ **Dated note, 2026-10-02 — the prop takes the declared-node union — objectui#11466.** At this change, and at objectui#11364's note above, the bridge's return type spelled its object member `BaseSchema` (with `AuthoringNode` beside it after objectui#11364); now, later in this same release, `SchemaRendererProps.schema` is `DeclaredNode | string | null | undefined`, and the return follows it by reference. The falsy leg still adds no `number` / `boolean`, and the pinned equality with the prop still holds. The rest of this entry is kept as the reading of this change.
|
|
1572
|
+
- 689953a: Keep the `{ dialect: 'cel', source }` predicate envelope intact through a node's
|
|
1573
|
+
config bag, so a CEL visibility gate authored the canonical way reaches the CEL
|
|
1574
|
+
engine instead of the legacy JS one (objectui#9100).
|
|
1575
|
+
|
|
1576
|
+
**The defect.** `SchemaRenderer`'s per-value `properties` / `props` evaluation loops
|
|
1577
|
+
handed every bag value to `ExpressionEvaluator.evaluate`, whose first action is to
|
|
1578
|
+
unwrap any `{ source: string }` object down to its bare `source` — the unwrap that
|
|
1579
|
+
makes a `template` envelope interpolate. Applied to a `cel` predicate it was
|
|
1580
|
+
destructive: the envelope is the only thing that routes `evaluateCondition` to the
|
|
1581
|
+
canonical `@objectstack/formula` engine, and those loops run before the hoist, so
|
|
1582
|
+
both consumers — `SchemaRenderer`'s own visibility chain and the renderer's
|
|
1583
|
+
`toPredicateInput` → `useCondition` call one layer down — received a bare string and
|
|
1584
|
+
took the legacy JS path, where a CEL stdlib call such as `has()` is not a function.
|
|
1585
|
+
|
|
1586
|
+
Both polarities then failed silently, in opposite directions. On the four SHOW legs
|
|
1587
|
+
(`visibleWhen` / `visible` / `visibleOn` / `visibility`) the fail-soft `true` is
|
|
1588
|
+
negated, so **a gate authored to hide something rendered it on every row** — measured
|
|
1589
|
+
in a real browser, where a duplicate-lead banner showed on 6 of 6 records whose field
|
|
1590
|
+
was `null`. On the two HIDE legs (`hidden` / `hiddenOn`) the same `true` is returned
|
|
1591
|
+
un-negated, so **the node vanished entirely**. Neither is distinguishable on screen
|
|
1592
|
+
from a predicate that said so on purpose.
|
|
1593
|
+
|
|
1594
|
+
**Behaviour change, deliberately.** A `cel`-envelope predicate on any of those six
|
|
1595
|
+
keys, authored inside `properties` or `props`, now actually decides. Metadata that
|
|
1596
|
+
was relying — knowingly or not — on such a gate never biting will start seeing nodes
|
|
1597
|
+
hidden (SHOW legs) or shown (HIDE legs) as authored. Nothing else moves: a `template`
|
|
1598
|
+
or dialect-less envelope keeps the legacy `${…}` behaviour byte-for-byte, a
|
|
1599
|
+
non-predicate bag key keeps the flattening it has always had, and node-level
|
|
1600
|
+
predicate keys were never affected (they do not pass through these loops, which is
|
|
1601
|
+
why objectui#7530's pin stayed green throughout).
|
|
1602
|
+
|
|
1603
|
+
**`record:alert` declaration.** Its `visible` input was registered as a bare
|
|
1604
|
+
`'string'` while `@objectstack/spec` 17.4.0 declares
|
|
1605
|
+
`boolean | string | { dialect, source }` for the same key and the renderer resolves
|
|
1606
|
+
all three. It is now declared `['boolean', 'string', 'object']`, in the same change
|
|
1607
|
+
that made the envelope arm actually reachable.
|
|
1608
|
+
- 8d3dbb2: fix(react): keep the CEL predicate envelope intact on the ENABLEMENT keys too
|
|
1609
|
+
|
|
1610
|
+
`SchemaRenderer`'s two per-value config-bag loops (`properties` — the spec
|
|
1611
|
+
spelling — and its legacy `props` alias) flattened a `{ dialect: 'cel', source }`
|
|
1612
|
+
predicate envelope on `disabled` / `disabledOn` / `enabled` down to its bare
|
|
1613
|
+
`source` string, exactly as objectui#9100 measured for the visibility chain. The
|
|
1614
|
+
envelope is the only thing that routes `evaluateCondition` to the canonical
|
|
1615
|
+
`@objectstack/formula` engine, so the flattened predicate took the legacy JS
|
|
1616
|
+
path, where a CEL stdlib call such as `has()` is not a function.
|
|
1617
|
+
|
|
1618
|
+
objectui#9104 repaired this for the six visibility legs only. The enablement
|
|
1619
|
+
keys are not visibility legs — they route through the node's enablement gate and
|
|
1620
|
+
through the action renderers' own predicate call — so they were left flattened,
|
|
1621
|
+
and the resulting fail-soft `true` landed in OPPOSITE directions on the two
|
|
1622
|
+
halves of the chain:
|
|
1623
|
+
|
|
1624
|
+
- `disabled` / `disabledOn` are not negated, so a CEL-authored gate greyed its
|
|
1625
|
+
control out on every row and no predicate the author could write re-enabled
|
|
1626
|
+
it — measured through `action:button` and `action:icon`;
|
|
1627
|
+
- the legacy `enabled` alias IS negated by those renderers, so the same `true`
|
|
1628
|
+
arrived as "not disabled" and a control the author had disabled stayed
|
|
1629
|
+
pressable.
|
|
1630
|
+
|
|
1631
|
+
The guard now consults the union of the file's two closed predicate-chain
|
|
1632
|
+
declarations instead of the visibility one alone. `template` and dialect-less
|
|
1633
|
+
envelopes, non-predicate bag keys and predicates nested inside arrays keep their
|
|
1634
|
+
behaviour byte for byte.
|
|
1635
|
+
- 345e24a: **BREAKING** — `RecordContextValue.dataSource` is the DataSource **adapter** it
|
|
1636
|
+
has always held, not a datasource id string.
|
|
1637
|
+
|
|
1638
|
+
**FROM** `dataSource: 'ds_primary'` **TO** `dataSource: myAdapter` (any
|
|
1639
|
+
`DataSource` from `@object-ui/types`).
|
|
1640
|
+
|
|
1641
|
+
```ts
|
|
1642
|
+
// before — compiled, but no producer ever did this and no reader could use it
|
|
1643
|
+
const ctx: RecordContextValue = { objectName: 'account', recordId: 'r1', dataSource: 'ds_primary' };
|
|
1644
|
+
// after
|
|
1645
|
+
const ctx: RecordContextValue = { objectName: 'account', recordId: 'r1', dataSource: myAdapter };
|
|
1646
|
+
```
|
|
1647
|
+
|
|
1648
|
+
The member was declared `dataSource?: string` — "an optional datasource id;
|
|
1649
|
+
mirrors the page-level datasource override" — while the one production host
|
|
1650
|
+
that writes it (`@object-ui/app-shell`'s `RecordDetailView`) forwards an adapter
|
|
1651
|
+
OBJECT and every reader calls adapter methods on it. Measured on the base tree:
|
|
1652
|
+
of the provider sites in this repository that write the member, exactly one is
|
|
1653
|
+
non-test and it passes the adapter; the only in-repo writer of a string was a
|
|
1654
|
+
test fixture, which existed because the declaration invited it. So the id was
|
|
1655
|
+
never a contract anyone kept — it was a statement the code contradicted at every
|
|
1656
|
+
site, and the override it described has zero producers. Contract-first
|
|
1657
|
+
(Commandment #0.1): the declaration moves to what the code holds, and no second
|
|
1658
|
+
`dataSourceId` member is minted for a requirement nobody implements.
|
|
1659
|
+
|
|
1660
|
+
What it bought: each reader paid for the wrong declaration with a cast at the
|
|
1661
|
+
point of use, and a cast deletes the compiler's answer to "does this adapter
|
|
1662
|
+
have the method I am about to call". All 11 record-context read sites are now
|
|
1663
|
+
cast-free, across `@object-ui/app-shell`, `@object-ui/components` and
|
|
1664
|
+
`@object-ui/plugin-detail`. The worked instance is objectui#8883's reference
|
|
1665
|
+
rail: it reaches `dataSource.getObjectSchema` — a REQUIRED member of
|
|
1666
|
+
`DataSource` — and now gets that guarantee from the compiler instead of from
|
|
1667
|
+
`(ctx as any)`.
|
|
1668
|
+
|
|
1669
|
+
⚠️ Nothing to migrate at runtime and no data change: every producer already
|
|
1670
|
+
passed the adapter, so no value moving through this member changes. A
|
|
1671
|
+
TypeScript consumer outside this repo that read the member as `string` is not
|
|
1672
|
+
observable from here and gets a compile error (TS2322) naming the key — which
|
|
1673
|
+
is why the FROM/TO is spelled out above. Re-widening to `string | DataSource`
|
|
1674
|
+
would not be a kindness: a union member without `getObjectSchema` cannot be read
|
|
1675
|
+
without narrowing, so it re-breaks every reader this change repaired. Both
|
|
1676
|
+
directions are pinned against the real compiler in
|
|
1677
|
+
`RecordContext.dataSourceType.pin.test.ts`.
|
|
1678
|
+
|
|
1679
|
+
⚠️ `SchemaRendererContextValue.dataSource` one context over is a **different**
|
|
1680
|
+
declaration (`any`) and is **not** touched here. Five `as any` reads in
|
|
1681
|
+
`@object-ui/fields` key off that context, not this one; they are unaffected by
|
|
1682
|
+
this change and are reported separately.
|
|
1683
|
+
|
|
1684
|
+
objectui#9197.
|
|
1685
|
+
- 8524372: Unbind the data-source adapter from the expression scope, and point `bind` at the scope
|
|
1686
|
+
channel (objectui#9308, maintainer ruling 2026-09-13, option B).
|
|
1687
|
+
|
|
1688
|
+
**Breaking, deliberately — and `minor` only because this repo's `fixed` group of 40
|
|
1689
|
+
packages may not carry a `major` (AGENTS.md 版本号策略).** Read the migration note below
|
|
1690
|
+
before upgrading if any of your metadata reads `data.*` at the page/component tier.
|
|
1691
|
+
|
|
1692
|
+
**What changed.**
|
|
1693
|
+
|
|
1694
|
+
1. `SchemaRenderer` no longer writes `data: dataSource` into the evaluator scope. `data`
|
|
1695
|
+
is not a root this tier binds. The roots it supplies are `record` (the row, when a
|
|
1696
|
+
record surface bound one), `page` (page-local variables) and `current_user` — plus
|
|
1697
|
+
every name the host published through `PredicateScopeProvider`.
|
|
1698
|
+
2. `useDataScope(path)` — what a node's `bind` resolves through — reads the ambient
|
|
1699
|
+
predicate scope (`usePredicateScope()`) instead of walking the injected adapter.
|
|
1700
|
+
|
|
1701
|
+
**Who this breaks, concretely.**
|
|
1702
|
+
|
|
1703
|
+
* **A `${data.…}` expression on a page/component node.** It used to resolve against the
|
|
1704
|
+
host's injected `DataSource` adapter. An adapter answers no `data.*` path, so for every
|
|
1705
|
+
host that injected a real adapter the read was already `undefined` and the predicate was
|
|
1706
|
+
a silent constant — but the constant MOVES: `data` used to be a present key holding an
|
|
1707
|
+
object, so `data.status == 'draft'` evaluated cleanly to `false` and the node was hidden
|
|
1708
|
+
on every row. `data` is now ABSENT, the expression cannot be evaluated at all, and this
|
|
1709
|
+
surface is fail-soft, so the same gate now answers `true` and the node is SHOWN on every
|
|
1710
|
+
row. It is no longer silent: the objectui#5454 reporter names it in the console, in
|
|
1711
|
+
production as well as development. Re-root such a predicate on `record.*` — the row —
|
|
1712
|
+
or publish a `data` of your own through `PredicateScopeProvider`.
|
|
1713
|
+
* **A host that injected a plain data bag as `dataSource`.** That was meaning 2 of the
|
|
1714
|
+
key, and it stops working entirely: `${data.…}` no longer reads it and `bind` no longer
|
|
1715
|
+
walks it. It has been a compile error since objectui#7912 (`dataSource` is typed
|
|
1716
|
+
`DataSource | null | undefined`). Publish those values through `PredicateScopeProvider`
|
|
1717
|
+
instead — the provider, the hook and the app-shell wiring already exist, and this change
|
|
1718
|
+
adds no new published key.
|
|
1719
|
+
* **The nine `bind` readers** (`list`, `tree-view`, `ObjectChart`, `ObjectDataTable`,
|
|
1720
|
+
`ObjectPivotTable`, `ObjectGrid`, `ObjectKanban`, `ObjectGallery`, `ObjectTimeline`) now
|
|
1721
|
+
resolve `bind` against the scope. All nine already carry a fallback
|
|
1722
|
+
(`boundData || schema.items`, `|| schema.nodes || []`, or a fall-through to their own
|
|
1723
|
+
fetch chain), and against a conformant adapter `useDataScope` returned `undefined`
|
|
1724
|
+
before this change — so the fallback is what was running, and a `bind` that resolved
|
|
1725
|
+
nothing before still resolves nothing. What is new is that a `bind` CAN now resolve:
|
|
1726
|
+
a host that publishes rows on its scope will see them used where the fetch used to run.
|
|
1727
|
+
|
|
1728
|
+
**Why.** `@object-ui/app-shell`'s `ExpressionProvider` states the rule this applies: every
|
|
1729
|
+
root bound at a tier must be one the engine accepts AND one that tier can actually answer.
|
|
1730
|
+
objectui#8155 unbound `app` under it and objectui#8166 unbound `data`; ADR-0089 D3 puts
|
|
1731
|
+
`data` at the metadata layer and `record` at the runtime layer, and the engine's
|
|
1732
|
+
per-surface `FIELD_RULE_BOUND_ROOTS` is `['record','previous','parent']`. The renderer tier
|
|
1733
|
+
was the last one still binding a root it could not answer.
|
|
1734
|
+
|
|
1735
|
+
Two side effects worth naming. A host that legitimately published `data` through the
|
|
1736
|
+
documented scope channel used to be silently overwritten by the adapter (the adapter was
|
|
1737
|
+
spread last); it is not any more. And `reportAdapterOnlyDataPredicate` now resolves against
|
|
1738
|
+
the `data` the evaluator actually bound rather than against the adapter — left pointing at
|
|
1739
|
+
the adapter it would have gone silent for precisely the hosts this change breaks.
|
|
1740
|
+
|
|
1741
|
+
The Data Context passages of `content/docs/guide/schema-rendering.md` and
|
|
1742
|
+
`packages/react/README.md` teach the scope channel accordingly.
|
|
1743
|
+
- 72d6587: A record id is a `string` everywhere in the published types, as
|
|
1744
|
+
`@objectstack/spec` has always declared it. Three published declarations that
|
|
1745
|
+
admitted `number` no longer do.
|
|
1746
|
+
|
|
1747
|
+
⚠️ **BREAKING if your code hands a numeric primary key to any of these three:**
|
|
1748
|
+
|
|
1749
|
+
- **`RecordContextValue.recordId`** (`@object-ui/react`) — the value
|
|
1750
|
+
`useRecordContext()` gives you is now a `string`, never a `number`.
|
|
1751
|
+
- **`DataSource.update`'s `id` parameter** (`@object-ui/types`) — a call that
|
|
1752
|
+
passes a `string | number` is now a type error. Adapters that *implement*
|
|
1753
|
+
`DataSource` are unaffected (see Migration).
|
|
1754
|
+
- **`TransactionOperation.id`** (`@object-ui/core`) — the operation record you
|
|
1755
|
+
hand to `TransactionManager.recordOperation()` must carry a `string` id. If
|
|
1756
|
+
you build that object from a numeric key, convert it where you build it. This
|
|
1757
|
+
type is exported from the package root, so this is a breaking change for
|
|
1758
|
+
`@object-ui/core` consumers in its own right, not just a knock-on.
|
|
1759
|
+
|
|
1760
|
+
Ships as `minor` per the launch-window convention: objectui's
|
|
1761
|
+
`major` is a cross-repo pin to `@objectstack`'s so that "same major means
|
|
1762
|
+
compatible" holds across the two repos
|
|
1763
|
+
(`scripts/check-changeset-no-major.mjs`), and objectui's own breaking changes
|
|
1764
|
+
ship as `minor` with the break named where it lands — this entry is the channel
|
|
1765
|
+
that carries it.
|
|
1766
|
+
|
|
1767
|
+
## What changed
|
|
1768
|
+
|
|
1769
|
+
- `RecordContextValue.recordId` (`@object-ui/react`) was
|
|
1770
|
+
`string | number | null | undefined`; it is now `string | null | undefined`.
|
|
1771
|
+
- `DataSource.update`'s `id` parameter (`@object-ui/types`) was
|
|
1772
|
+
`string | number`; it is now `string`.
|
|
1773
|
+
- `TransactionOperation.id` (`@object-ui/core`) was `string | number`; it is now
|
|
1774
|
+
`string`. Its sibling `BatchTransactionOperation.id` was already a `string`,
|
|
1775
|
+
so the two operation records finally agree.
|
|
1776
|
+
- `LineItemsPanel` (`@object-ui/plugin-form`) drops the type assertion
|
|
1777
|
+
objectui#9304 left on its parent id. That assertion was the only thing making
|
|
1778
|
+
the context declaration and `buildMasterDetailEditBatch(parentId: string)`
|
|
1779
|
+
meet; the declaration now does it, so the evidence is discharged.
|
|
1780
|
+
|
|
1781
|
+
## Why the protocol, and not a wider consumer type
|
|
1782
|
+
|
|
1783
|
+
`@objectstack/spec` declares a record id as `z.string()` on every record door —
|
|
1784
|
+
get, update, delete and the batch operation. A consumer type may not be wider
|
|
1785
|
+
than the protocol: a declaration that admits `number` promises callers something
|
|
1786
|
+
the wire never carries, and the promise is kept only by an assertion at the far
|
|
1787
|
+
end, which is what this card was filed about.
|
|
1788
|
+
|
|
1789
|
+
## Internal consumers repaired at the same time (no public contract moves)
|
|
1790
|
+
|
|
1791
|
+
Narrowing an interface **parameter** never reaches implementors — TypeScript
|
|
1792
|
+
compares method parameters bivariantly, so an adapter that still declares
|
|
1793
|
+
`id: string | number` keeps satisfying `DataSource`. It reaches **callers**. A
|
|
1794
|
+
full local type-check of every workspace type-check program found exactly six,
|
|
1795
|
+
in three packages, and each was red because a further declaration one layer in
|
|
1796
|
+
was itself wider than the protocol. All three are narrowed here, types only, with
|
|
1797
|
+
no runtime change and no coercion added at any call site:
|
|
1798
|
+
|
|
1799
|
+
- `UserPreferenceRecord.id` and the `cachedRowId` it feeds
|
|
1800
|
+
(`@object-ui/data-objectstack`) are `string`. Module-local, not published —
|
|
1801
|
+
these rows are read back off the protocol, so the union was a claim the wire
|
|
1802
|
+
never makes.
|
|
1803
|
+
- `resolveRecordId`'s return type (`@object-ui/plugin-grid`) is
|
|
1804
|
+
`string | undefined`. Module-local, not published — it annotates `any`-typed
|
|
1805
|
+
row data, so the union was an assertion rather than a measurement.
|
|
1806
|
+
|
|
1807
|
+
## Migration — no `String(...)` at your call sites
|
|
1808
|
+
|
|
1809
|
+
`RecordContextProvider` still **accepts** `string | number | null | undefined`
|
|
1810
|
+
and narrows it once, itself. A host that mounts a record with a numeric primary
|
|
1811
|
+
key therefore changes nothing: the conversion is paid at that injection
|
|
1812
|
+
boundary, typed, in one place. Consumers of `useRecordContext()` read a
|
|
1813
|
+
`string`.
|
|
1814
|
+
|
|
1815
|
+
`DataSource` implementors are unaffected — TypeScript compares method parameters
|
|
1816
|
+
bivariantly, so an adapter that still declares `id: string | number` continues
|
|
1817
|
+
to satisfy the interface. What changes is the **caller** side: a call that passes
|
|
1818
|
+
a `string | number` to `dataSource.update` is now a type error. A backend whose
|
|
1819
|
+
primary keys are numeric maps them at its own adapter boundary rather than
|
|
1820
|
+
pushing the union through every caller.
|
|
1821
|
+
|
|
1822
|
+
For `TransactionOperation`, the same rule applies one level up: build the
|
|
1823
|
+
operation record with a `string` id. If the id arrives from a numeric-keyed
|
|
1824
|
+
backend, convert it in your adapter — the one place that knows the backend's key
|
|
1825
|
+
type — rather than at each `recordOperation()` call. Nothing about this change
|
|
1826
|
+
alters what is sent over the wire; only the declarations moved.
|
|
1827
|
+
- 0ce32d5: `UseNavigationOverlayOptions.onRowClick` now declares the modifier payload it
|
|
1828
|
+
has always been called with (objectui#9357).
|
|
1829
|
+
|
|
1830
|
+
`useNavigationOverlay`'s `handleClick` invokes the caller-supplied `onRowClick`
|
|
1831
|
+
with two arguments — the record, and the optional `HandleClickModifiers`
|
|
1832
|
+
payload (`metaKey` / `ctrlKey` / `button`) a host needs to implement
|
|
1833
|
+
Cmd/Ctrl/middle-click. The option declared only the record, and `handleClick`
|
|
1834
|
+
carried a type assertion that widened the value at the call site so the code
|
|
1835
|
+
would compile. The second argument was therefore invisible on the one line a
|
|
1836
|
+
host reads, and a host that wanted it had to discover it from the
|
|
1837
|
+
implementation and then spell its own second parameter optional to stay
|
|
1838
|
+
assignable.
|
|
1839
|
+
|
|
1840
|
+
The declaration now names both parameters and the assertion is gone.
|
|
1841
|
+
|
|
1842
|
+
**Source-compatible in both directions, with one measured exception.** A
|
|
1843
|
+
one-parameter handler stays assignable to the widened signature (its extra
|
|
1844
|
+
parameter is optional), and a handler written against the widened signature was
|
|
1845
|
+
already assignable to the old one — measured on this change, both directions.
|
|
1846
|
+
|
|
1847
|
+
**The exception, and the one class that has to change.** A handler passed
|
|
1848
|
+
*directly* to `useNavigationOverlay` whose second parameter is annotated
|
|
1849
|
+
*narrower* than `HandleClickModifiers` no longer type-checks. React's
|
|
1850
|
+
`MouseEvent` is the shape this hits in practice, because until now the payload
|
|
1851
|
+
was only discoverable from the implementation, so a host that wanted it wrote
|
|
1852
|
+
the annotation it saw arrive:
|
|
1853
|
+
|
|
1854
|
+
```ts
|
|
1855
|
+
useNavigationOverlay({
|
|
1856
|
+
objectName: 'account',
|
|
1857
|
+
// was accepted; now TS2322 — `HandleClickModifiers` is not assignable to
|
|
1858
|
+
// `React.MouseEvent`
|
|
1859
|
+
onRowClick: (record, ev?: React.MouseEvent) => { /* ... */ },
|
|
1860
|
+
});
|
|
1861
|
+
```
|
|
1862
|
+
|
|
1863
|
+
It compiled before only because the old declaration had no second parameter to
|
|
1864
|
+
check the annotation against. The parameter is checked contravariantly, so the
|
|
1865
|
+
annotation now has to *admit* `HandleClickModifiers`. **The fix is one line at
|
|
1866
|
+
the call site:** annotate the parameter `HandleClickModifiers` (exported from
|
|
1867
|
+
`@object-ui/react`), or drop the annotation and let it be inferred. Either way
|
|
1868
|
+
the handler keeps receiving exactly what it received before — this is a
|
|
1869
|
+
type-level change only, with no runtime behaviour attached.
|
|
1870
|
+
|
|
1871
|
+
No caller *inside this repository* is in that class, and the repository's own
|
|
1872
|
+
type-check re-derives that on every run rather than this sentence asserting it;
|
|
1873
|
+
a host that reaches the hook through a view component's `onRowClick` prop is
|
|
1874
|
+
unaffected either way, because the prop's own declared type is what gets
|
|
1875
|
+
assigned to the option. The boundary is pinned as a `@ts-expect-error` row in
|
|
1876
|
+
this package's `useNavigationOverlay.onRowClickArity-9357` test, so it cannot
|
|
1877
|
+
move without a red check.
|
|
1878
|
+
|
|
1879
|
+
What the widening buys everyone else: a caller who wants the modifier payload
|
|
1880
|
+
can now see, from the published type, that it is there.
|
|
1881
|
+
|
|
1882
|
+
Scope note: this repairs the hook's own option. The pass-through props on the
|
|
1883
|
+
view components that feed it — `ObjectKanban`, `ObjectGallery`, the
|
|
1884
|
+
`plugin-kanban` renderer's `onCardClick`, and the `onRowClick` prop on the other
|
|
1885
|
+
view plugins — each still declare one parameter on their own published face;
|
|
1886
|
+
which spelling that family converges on is objectui#9357's open question and is
|
|
1887
|
+
not decided here.
|
|
1888
|
+
- bbba098: **BREAKING if your code hands a numeric primary key to one of these four** — a
|
|
1889
|
+
record id at the `DataSource` boundary is a `string`, as `@objectstack/spec`
|
|
1890
|
+
declares every record door (objectui#9511, director batch #136 item 5 letter B).
|
|
1891
|
+
objectui#9333 narrowed `DataSource.update`; this entry closes three more doors,
|
|
1892
|
+
and narrows one consumer declaration — `UseViewDataResult.fetchOne` — that feeds
|
|
1893
|
+
`findOne`, the one door this entry leaves open.
|
|
1894
|
+
|
|
1895
|
+
## What changed
|
|
1896
|
+
|
|
1897
|
+
- **`DataSource.delete`'s `id` parameter** (`@object-ui/types`) — was
|
|
1898
|
+
`string | number`, now `string`.
|
|
1899
|
+
- **`DataSource.bulkUpdate`'s `ids`** (`@object-ui/types`) — was
|
|
1900
|
+
`ReadonlyArray<string | number>`, now `ReadonlyArray<string>`.
|
|
1901
|
+
- **`DataSource.bulkDelete`'s `ids`** (`@object-ui/types`) — same narrowing.
|
|
1902
|
+
- **`UseViewDataResult.fetchOne`** (`@object-ui/react`) — was
|
|
1903
|
+
`(id: string | number) => Promise<T | null>`, now `(id: string) =>
|
|
1904
|
+
Promise<T | null>`. This hook hands its argument straight to
|
|
1905
|
+
`DataSource.findOne`, so the union it admitted was a claim the protocol never
|
|
1906
|
+
made. Exported from the package root, so it is a break for
|
|
1907
|
+
`@object-ui/react` consumers in its own right, not a knock-on.
|
|
1908
|
+
|
|
1909
|
+
The `DataSource` docblock now states the rule once, for the whole interface,
|
|
1910
|
+
so a reader who meets one method does not have to re-derive it from another.
|
|
1911
|
+
|
|
1912
|
+
## Migration
|
|
1913
|
+
|
|
1914
|
+
Convert where you build the id, not at each call:
|
|
1915
|
+
|
|
1916
|
+
```ts
|
|
1917
|
+
// before
|
|
1918
|
+
await dataSource.delete('accounts', row.id); // row.id: number
|
|
1919
|
+
await dataSource.bulkDelete?.('accounts', selectedIds); // number[]
|
|
1920
|
+
|
|
1921
|
+
// after
|
|
1922
|
+
await dataSource.delete('accounts', String(row.id));
|
|
1923
|
+
await dataSource.bulkDelete?.('accounts', selectedIds.map(String));
|
|
1924
|
+
```
|
|
1925
|
+
|
|
1926
|
+
Adapters that **implement** `DataSource` are unaffected: TypeScript compares
|
|
1927
|
+
method parameters bivariantly, so an adapter still declaring `string | number`
|
|
1928
|
+
continues to satisfy the interface. The narrowing binds callers.
|
|
1929
|
+
|
|
1930
|
+
## Disposition
|
|
1931
|
+
|
|
1932
|
+
**Disposition: no ADR-0087 conversion — nothing authored moves.** The change is
|
|
1933
|
+
on the TypeScript call face only. No zod accept-set narrows, so every metadata
|
|
1934
|
+
document that validates today still validates, and there is no old shape for a
|
|
1935
|
+
conversion layer (ADR-0087 D2) to accept and rewrite, nor a metadata migration
|
|
1936
|
+
step (D3) to replay. What moves is what a numeric-primary-key host sends on the
|
|
1937
|
+
wire for these three methods: `'42'` where it sent `42`, consistently with
|
|
1938
|
+
`update`. The conversion lives in one typed place — the host's own adapter
|
|
1939
|
+
boundary — rather than in a union carried by every caller.
|
|
1940
|
+
|
|
1941
|
+
Ships as `minor` per the launch-window convention: objectui's `major` is a
|
|
1942
|
+
cross-repo pin to `@objectstack`'s so that "same major means compatible" holds
|
|
1943
|
+
across the two repos (`scripts/check-changeset-no-major.mjs`), and objectui's
|
|
1944
|
+
own breaking changes ship as `minor` with the break named where it lands.
|
|
1945
|
+
|
|
1946
|
+
## One door is deliberately still open
|
|
1947
|
+
|
|
1948
|
+
`DataSource.findOne` keeps `string | number` in this release. Narrowing it is
|
|
1949
|
+
ruled, but its remaining call sites split two ways. `ObjectForm` reads
|
|
1950
|
+
`ObjectFormSchema.recordId` and `DetailView` reads `DetailViewSchema.resourceId`
|
|
1951
|
+
— **authorable** metadata keys whose zod mirrors accept a number today, so
|
|
1952
|
+
narrowing either refuses author JSON that validates now. `DrawerForm`,
|
|
1953
|
+
`ModalForm`, `SplitForm`, `TabbedForm` and `WizardForm` read a `recordId`
|
|
1954
|
+
declared on their own exported schema face; those faces have no zod mirror and
|
|
1955
|
+
no registered node type, so they are TypeScript-only, like
|
|
1956
|
+
`UseViewDataResult.fetchOne` above — but they still cannot narrow first,
|
|
1957
|
+
because `ObjectForm` builds all five of them from its own authorable
|
|
1958
|
+
`ObjectFormSchema` and the refusal simply moves to those hand-off sites.
|
|
1959
|
+
|
|
1960
|
+
So the choice is either an authoring-face narrowing or a reader-side
|
|
1961
|
+
conversion; neither is named by the ruling, and it is carried on objectui#9511.
|
|
1962
|
+
The reason is recorded in the `DataSource` docblock so it is not rediscovered.
|
|
1963
|
+
- c3df43a: `ObjectGrid` takes `onNavigate` as a component prop, and the list channel's navigation callback takes one closed mode token, `'view' | 'new_window'`.
|
|
1964
|
+
|
|
1965
|
+
`ObjectGridSchema.onNavigate`'s documentation told programmatic callers to pass it on `ObjectGridComponentProps`, where the grid's nine other callbacks live — but that interface did not declare it, so a host that followed the advice passed a function the grid never called, with no diagnostic. `ObjectGridComponentProps` now declares `onNavigate`, and the grid reads the prop first and `schema.onNavigate` only when no prop is supplied: when both are given, the prop wins. `onRowClick`, when supplied, still takes the row click before either.
|
|
1966
|
+
|
|
1967
|
+
The callback's second argument is now `RecordNavigateAction`, a new type exported by `@object-ui/types`: the closed union `'view' | 'new_window'`, where `'view'` opens the record page and `'new_window'` opens it in a new browser tab. It is exactly what `useNavigationOverlay` emits, and the vocabulary `@objectstack/spec` 17.5.0 gives the react-tier `ListView` `onNavigate` callback. The same type now sits on all four faces of the channel — `ObjectGridComponentProps.onNavigate`, `ObjectGridSchema.onNavigate`, `ListViewSchema.onNavigate`, and `UseNavigationOverlayOptions.onNavigate` in `@object-ui/react` — where the three existing faces declared `action?: string` and two of them described it as `'view' | 'edit'`. No branch of the hook passes `'edit'`.
|
|
1968
|
+
|
|
1969
|
+
**Clause-②: yes** — one prop added (`ObjectGridComponentProps.onNavigate`), one type exported (`RecordNavigateAction`), and three published callback types narrowed from `(recordId, action?: string) => void` to `(recordId, action: RecordNavigateAction) => void`. What a host has to change:
|
|
1970
|
+
|
|
1971
|
+
- A handler you supply that is written `(id, action?: string) => …`, or with no second parameter at all, still compiles unchanged and receives exactly what it received before.
|
|
1972
|
+
- A handler whose second parameter is annotated with a union that leaves out `'new_window'` — the old documentation's `'view' | 'edit'` is the likely one — is now refused with TS2322. Annotate it `RecordNavigateAction` (or drop the annotation) and handle `'new_window'`, which a Cmd/Ctrl/middle-click and `navigation.mode: 'new_window'` already deliver.
|
|
1973
|
+
- Code that CALLS one of these callbacks itself must now pass the second argument, and it must be one of the two members.
|
|
1974
|
+
- f0f4d6c: `SchemaRenderer` no longer spreads an authored `data` key as a React prop for blocks
|
|
1975
|
+
whose published `data` row is the `ViewData` OBJECT arm (objectui#9571, ruling
|
|
1976
|
+
objectui#8348 Q2-C, decision batch #136 item 3, maintainer 「同意」).
|
|
1977
|
+
|
|
1978
|
+
**Behavioural, deliberately.** 「8348 以协议为准」 (batch #83) retired the bare-array
|
|
1979
|
+
`data` shorthand at the shared record-source ladder, but the key had a second carrier
|
|
1980
|
+
that the ruling did not reach and that outranked the first: `SchemaRenderer` spreads
|
|
1981
|
+
every non-metadata node key as a React prop, and `ObjectGrid` (`passedData`),
|
|
1982
|
+
`ObjectMap` (`dataProp`) and `ObjectGantt` each lift a `data` PROP with an
|
|
1983
|
+
unconditional `Array.isArray` ahead of the ladder. So an authored
|
|
1984
|
+
`data: [ …rows… ]` on `object-grid` / `object-map` / `object-gantt` still drew,
|
|
1985
|
+
end-to-end, after the ladder had refused it. AGENTS.md #0.1: one key, one honoured
|
|
1986
|
+
spelling — the tolerated second carrier is the defect.
|
|
1987
|
+
|
|
1988
|
+
An authored `data` now reaches such a block only as `schema.data`, judged by the
|
|
1989
|
+
block's own row. In development the renderer says so, once per node: the ladder emits
|
|
1990
|
+
no runtime signal of its own, so without that warning the retirement would land as a
|
|
1991
|
+
page that quietly stopped drawing.
|
|
1992
|
+
|
|
1993
|
+
**Unchanged, and refused on purpose.** The `data` PROP itself. Gating the prop on the
|
|
1994
|
+
arm (option B) was refused because that prop is how a host — `plugin-list`'s
|
|
1995
|
+
`ListView`, `ObjectView` — hands down rows it already fetched. A host rendering
|
|
1996
|
+
`<ObjectGrid data={rows} …/>` directly, or passing `data` through `SchemaRenderer`'s
|
|
1997
|
+
own props, still delivers those rows, and still outranks the authored key.
|
|
1998
|
+
|
|
1999
|
+
Blocks on the ARRAY arm are untouched: `object-calendar`'s published row is
|
|
2000
|
+
`z.array(...)`, so its authored bare array is on-contract and keeps the prop seat.
|
|
2001
|
+
Every type the new reading does not list keeps today's behaviour verbatim.
|
|
2002
|
+
|
|
2003
|
+
**Migration.** Author inline rows the declared way —
|
|
2004
|
+
`data: { provider: 'value', items: [...] }` — or keep using the deprecated
|
|
2005
|
+
`staticData: [...]` array, which is unchanged. Both were already the only spellings
|
|
2006
|
+
`os validate` and the save gate accepted.
|
|
2007
|
+
|
|
2008
|
+
`@object-ui/core` gains one export, `recordSourceDataArmForType(type)`: which `data`
|
|
2009
|
+
arm a registered block type declares. It exists for the one consumer that cannot be
|
|
2010
|
+
handed the arm as a parameter — `SchemaRenderer`, which is generic over every
|
|
2011
|
+
registered type.
|
|
2012
|
+
- 3939545: fix(react,app-shell): `usePageAssignment` picks a record page by `type` alone — `pageType` is a key `PageSchema` refuses
|
|
2013
|
+
|
|
2014
|
+
`usePageAssignment` decided whether a page is a record page with
|
|
2015
|
+
`p.pageType ?? (p.type === 'record' ? 'record' : undefined)`. `PageSchema` is a
|
|
2016
|
+
`strictObject` that refuses `pageType` by name: it is one of the schema's
|
|
2017
|
+
declared aliases of `type`, so a page carrying it is a hard parse error that
|
|
2018
|
+
names `type` as the key to write. The first operand read a spelling no parsing
|
|
2019
|
+
page carries, and taught it onward as a live discriminator. The filter now reads
|
|
2020
|
+
`type` alone, and the comment beside it — which said the designer-side
|
|
2021
|
+
`record_detail` spelling was "still expressible in raw metadata" — is corrected:
|
|
2022
|
+
`PageSchema` refuses `pageType`, and `record_detail` left `PageTypeSchema`.
|
|
2023
|
+
|
|
2024
|
+
**Behaviour change, for pages that do not parse.** The metadata cache the hook
|
|
2025
|
+
walks holds `/meta/page` bodies as served — the client runs no schema over
|
|
2026
|
+
them — so the hook can still be handed a page that carries `pageType`:
|
|
2027
|
+
|
|
2028
|
+
- a page with `pageType: 'record'` and no `type: 'record'` is no longer picked;
|
|
2029
|
+
the object renders its synthesized default detail page instead;
|
|
2030
|
+
- a page with `type: 'record'` and some other `pageType` value is now picked.
|
|
2031
|
+
|
|
2032
|
+
Every page that parses behaves as before, because none can carry `pageType`.
|
|
2033
|
+
|
|
2034
|
+
The mirrors of the same discriminator move with it:
|
|
2035
|
+
|
|
2036
|
+
- The navigation page picker (`isStaticPageOption`) reads `type` alone, as the
|
|
2037
|
+
hook does. A row carrying `pageType: 'record'` without `type: 'record'` is now
|
|
2038
|
+
offered as a static page, and a `type: 'record'` row is excluded whatever its
|
|
2039
|
+
`pageType` says.
|
|
2040
|
+
- `recordPageEnvelope` writes `type: 'record'` and no `pageType`. It used to
|
|
2041
|
+
write `type: 'page'` plus `pageType: 'record'`, a body `PageSchema` refuses on
|
|
2042
|
+
both keys. The helper has no caller in this repository.
|
|
2043
|
+
- The record-page preview in the page designer still reads `pageType` on an
|
|
2044
|
+
unsaved draft; its comment now says why, and that the runtime does not match
|
|
2045
|
+
on it.
|
|
2046
|
+
- 7649f43: `SchemaRenderer` now applies the objectui#9571 authored-`data` strip to the legacy
|
|
2047
|
+
`props` alias bag as well, so all three authoring spellings of that key lose the React
|
|
2048
|
+
prop seat on the object arm (objectui#9758, decision batch #167 item 2, letter 剥,
|
|
2049
|
+
maintainer 「其他同意」).
|
|
2050
|
+
|
|
2051
|
+
**The carrier this closes.** objectui#9571 removed an authored `data` from
|
|
2052
|
+
`componentProps` — the node's own non-metadata keys — for blocks whose published `data`
|
|
2053
|
+
row is the `ViewData` OBJECT arm. But `createElement` spreads the legacy `props` alias
|
|
2054
|
+
bag AFTER that strip, so an author who spelled the identical key
|
|
2055
|
+
`props: { data: [ …rows… ] }` kept the seat the ruling had just taken away, and
|
|
2056
|
+
`ObjectGrid` (`passedData`), `ObjectMap` (`dataProp`) and `ObjectGantt` still lifted it
|
|
2057
|
+
with an unconditional `Array.isArray` ahead of the shared ladder. The arm predicate was
|
|
2058
|
+
right; the corpus was one bag short. AGENTS.md #0.1: one key, one honoured spelling.
|
|
2059
|
+
|
|
2060
|
+
Both bags now go through one helper with ONE arm reading, so the question is asked once
|
|
2061
|
+
instead of at two call sites that can drift apart.
|
|
2062
|
+
|
|
2063
|
+
**Unchanged, and refused on purpose.** The `data` PROP itself — `SchemaRenderer`'s own
|
|
2064
|
+
React props are spread LAST and still win, which is how `plugin-list`'s `ListView` and
|
|
2065
|
+
`ObjectView` hand down rows they already fetched. Nothing else the alias carries moves:
|
|
2066
|
+
every other key under `props` is spread exactly as before, the objectui#5123 precedence
|
|
2067
|
+
between `props` and `properties` is untouched, a degenerate non-object `props` still
|
|
2068
|
+
contributes no keys, and blocks on the array arm or on no declared arm keep their
|
|
2069
|
+
authored `data` prop verbatim. There is no validator refusal here: whether the `props`
|
|
2070
|
+
alias exists at all is objectui#4795's pending question ②, which is the maintainer's.
|
|
2071
|
+
|
|
2072
|
+
**Diagnostics.** The development warning objectui#9571 added now fires for the alias
|
|
2073
|
+
carrier too, once per node, naming the key, the arm and the spelling that works. Its
|
|
2074
|
+
objectui#6708 neighbour — the notice that a `props` bag was not read — stops naming
|
|
2075
|
+
`data` on this arm, because after this change both of its sentences are false for that
|
|
2076
|
+
key: it is no longer spread as a React prop, and the remedy it recommends
|
|
2077
|
+
(`properties: { data }`) loses the seat as well. It still names every other key the
|
|
2078
|
+
alias drops on the same node. The objectui#4795 unevaluated-expression scan is
|
|
2079
|
+
deliberately unchanged and still reports an authored `props: { data: '${…}' }`, because
|
|
2080
|
+
that value is still in front of a user.
|
|
2081
|
+
|
|
2082
|
+
**Migration.** Author inline rows the declared way —
|
|
2083
|
+
`data: { provider: 'value', items: [...] }` — or keep the deprecated `staticData: [...]`
|
|
2084
|
+
array, which is unchanged. Both were already the only spellings `os validate` and the
|
|
2085
|
+
save gate accepted.
|
|
2086
|
+
- 0c789a4: `useNavigationOverlay` stops reading the retired `navigation.view` key, and stops substituting an authored name for the navigation-MODE token
|
|
2087
|
+
|
|
2088
|
+
`view.list.navigation.view` was removed in `@objectstack/spec` 17.5.0 under ADR-0049
|
|
2089
|
+
(enforce-or-remove). `useNavigationOverlay` was its one shipped consumer, and the spec's
|
|
2090
|
+
tombstone describes that hook by name: the authored value "was passed straight into the
|
|
2091
|
+
navigation-MODE argument of the console's `onNavigate`, where anything other than `edit`
|
|
2092
|
+
or `view` matched no branch, so the key selected nothing and could silently deaden the
|
|
2093
|
+
row click."
|
|
2094
|
+
|
|
2095
|
+
The second argument of `onNavigate` is the navigation MODE token, not a view name. The
|
|
2096
|
+
hook read `const view = navigation?.view` and spent it at two call sites as
|
|
2097
|
+
`view ?? 'view'` — the no-config early return and the `page` branch — so an authored
|
|
2098
|
+
`{ mode: 'page', view: 'summary_view' }` dispatched `'summary_view'` into a slot hosts
|
|
2099
|
+
read against a closed `edit`/`view` vocabulary. An authored name did not SELECT a view,
|
|
2100
|
+
it SUBSTITUTED for the mode.
|
|
2101
|
+
|
|
2102
|
+
Both call sites now pass the literal `'view'`, which is what every config WITHOUT the key
|
|
2103
|
+
already dispatched. No replacement route is wired into this hook, and no fallback belongs
|
|
2104
|
+
here: per the same tombstone, choosing what opens for a record is page assignment — assign
|
|
2105
|
+
a `record` page to the object and let `isDefault` pick the one that opens — while a list
|
|
2106
|
+
view's `navigation` block only decides HOW the detail is surfaced (`mode`, `size`).
|
|
2107
|
+
|
|
2108
|
+
**Breaking for anyone who authored the key, and for anyone reading the hook's `view`
|
|
2109
|
+
member.** `NavigationOverlayState` no longer publishes `view`. It could only ever carry
|
|
2110
|
+
the retired key, so keeping it would publish a field that is permanently `undefined` —
|
|
2111
|
+
the same "declared, consumed, and wrong" state the retirement exists to end. Measured
|
|
2112
|
+
before removing it: zero readers of that member anywhere in this repository, against a
|
|
2113
|
+
lit control on its sibling members in the same command. Hosts outside this repository
|
|
2114
|
+
were not measured. Per this repo's version policy a breaking change ships as `minor`; the
|
|
2115
|
+
semantics are stated here rather than in the bump.
|
|
2116
|
+
|
|
2117
|
+
Behaviour on the Console is unchanged, and that is the point rather than a caveat:
|
|
2118
|
+
`ObjectView`'s `onNavigate` already routed every non-`new_window` action to the same
|
|
2119
|
+
record-detail route, so the substitution was inaudible exactly where it was loudest in
|
|
2120
|
+
the metadata. Its comment claimed a custom name was "resolved by RecordDetailView from
|
|
2121
|
+
its own config"; no layer ever resolved a view by that name, and the comment is corrected
|
|
2122
|
+
with the read it described.
|
|
2123
|
+
|
|
2124
|
+
Two existing pins asserted the substitution as intended behaviour and are INVERTED rather
|
|
2125
|
+
than deleted — `useNavigationOverlay.modeDefault` and `gridNavigationMembers-8071`, both
|
|
2126
|
+
keeping their authored `view` fixture because it is the one input the old and new
|
|
2127
|
+
implementations disagree about.
|
|
2128
|
+
- ae61ad4: The console form now renders a refusal message the producer explicitly marked
|
|
2129
|
+
as user-facing, instead of always substituting a generic string
|
|
2130
|
+
(objectui#5210).
|
|
2131
|
+
|
|
2132
|
+
An application's hook guards could not talk to their users. When a hook refused
|
|
2133
|
+
a write with 403, the form replaced the server's text with
|
|
2134
|
+
`form.noPermissionToSave` unconditionally — the recorded objectstack#3821 fix,
|
|
2135
|
+
which exists because a raw refusal body puts untranslated platform diagnostics
|
|
2136
|
+
(`FORBIDDEN: insufficient privileges to update showcase_private_note
|
|
2137
|
+
pi-TgoJ4_DM55Fqz`) in front of end users. The external report behind this change
|
|
2138
|
+
had 11 guards whose deliberate, localized guidance — which role owns the action,
|
|
2139
|
+
whom to ask — never reached anyone, and named the incentive that creates:
|
|
2140
|
+
returning 400 instead of 403 for permission failures, degrading the status
|
|
2141
|
+
semantics logs, monitoring and API consumers depend on.
|
|
2142
|
+
|
|
2143
|
+
The maintainer ruling (2026-08-19) was a producer-side opt-in rather than a
|
|
2144
|
+
chattier 403 branch, and the platform half shipped as objectstack `79c46da90`: a hook
|
|
2145
|
+
marks its refusal text with `userMessage` at throw time. This is the consumer
|
|
2146
|
+
half.
|
|
2147
|
+
|
|
2148
|
+
- `@object-ui/react` gains `declaredUserMessage(err)` — the one "is this
|
|
2149
|
+
marked?" read. It answers the marking verbatim, from the two places the
|
|
2150
|
+
adapter boundary parks the envelope (the error itself, where
|
|
2151
|
+
`@objectstack/client` lifts it, and `details`), and `null` for everything
|
|
2152
|
+
else.
|
|
2153
|
+
- The form prefers a marked message over both its generic strings, on ANY
|
|
2154
|
+
status — the marking is status-agnostic; 403 is where this was reported, not
|
|
2155
|
+
a fence the contract draws.
|
|
2156
|
+
|
|
2157
|
+
**Unmarked refusals are unchanged**: a 403 with no marking still shows the
|
|
2158
|
+
generic `form.noPermissionToSave`, and the raw text still goes to the browser
|
|
2159
|
+
console only. objectstack#3821's protection is preserved by construction, not
|
|
2160
|
+
by re-guessing which 403 bodies are presentable — the mark and the marked text
|
|
2161
|
+
are one field, so no boundary that rewraps or substitutes `message` can promote
|
|
2162
|
+
platform prose into the user-facing channel, and platform code never sets it.
|
|
2163
|
+
- ebce5a3: `object-grid` / `object-form` / `detail-view` resolve their data source the same way, and a block that resolves none says so
|
|
2164
|
+
|
|
2165
|
+
The three object-bound blocks disagreed about how the data-source adapter reached
|
|
2166
|
+
them. `object-grid` and `object-form` were registered through wrappers that read
|
|
2167
|
+
it from `SchemaRendererProvider` context; `detail-view` was registered as the raw
|
|
2168
|
+
component, which reads a React `dataSource` prop. `SchemaRenderer` itself reads
|
|
2169
|
+
only context, so the two wirings were mutually exclusive: measured with correct
|
|
2170
|
+
keys in every cell, provider wiring gave the grid `find` 1 and the detail view
|
|
2171
|
+
`findOne` 0, and prop wiring gave exactly the reverse. Neither reported anything.
|
|
2172
|
+
|
|
2173
|
+
All three now resolve the adapter through one rule — an explicit `dataSource`
|
|
2174
|
+
prop first, the provider context second. This is additive: `detail-view` keeps
|
|
2175
|
+
its prop form (and direct `<DetailView dataSource={…} />` callers are untouched),
|
|
2176
|
+
`object-form` gains a prop form it did not have, and `object-grid` no longer
|
|
2177
|
+
throws `useSchemaContext must be used within a SchemaRendererProvider` when a
|
|
2178
|
+
page has no provider.
|
|
2179
|
+
|
|
2180
|
+
And the silence is over. A block in this family that resolves no adapter renders
|
|
2181
|
+
a **No data source resolved** panel naming the block, the object it was about to
|
|
2182
|
+
read, and the ancestor that injects the adapter — instead of a header-only grid,
|
|
2183
|
+
a field-less form card, or nothing at all. The check is opt-in per block, so a
|
|
2184
|
+
placement with inline rows, inline `customFields`, an inline record or an `api`
|
|
2185
|
+
endpoint is untouched.
|
|
2186
|
+
|
|
2187
|
+
New from `@object-ui/react`: `useResolvedDataSource`, `NoDataSourcePanel`,
|
|
2188
|
+
`noDataSourceMessage`, and a `requiresDataSource` prop on `ElementDataSourceGate`.
|
|
2189
|
+
- cac64b3: The spec-bridge's form-view input now accepts everything `@objectstack/spec`'s
|
|
2190
|
+
FormViewSchema accepts — three key types had drifted narrower, and one of them
|
|
2191
|
+
had inverted (objectui#5652).
|
|
2192
|
+
|
|
2193
|
+
`bridges/form-view.ts` held a third hand-written description of the FormViewSchema
|
|
2194
|
+
contract, after the leaf (objectui#5542) and the two containers (objectui#5596)
|
|
2195
|
+
were converged elsewhere. A description nothing compares is one spec release from
|
|
2196
|
+
being a fork, and this one had already drifted on the keys that decide whether a
|
|
2197
|
+
legal document renders:
|
|
2198
|
+
|
|
2199
|
+
- `FormSection.columns` refused the string spelling of a column count, which the
|
|
2200
|
+
contract admits and folds to a number in its own pipe. It is now the contract's
|
|
2201
|
+
type, and the bridge performs the fold — the `object-form` node's section
|
|
2202
|
+
declares `columns` as a number and its container indexes a grid-class map by
|
|
2203
|
+
it, so forwarding `'2'` handed every downstream renderer a value outside the
|
|
2204
|
+
type it declares.
|
|
2205
|
+
- `FormField.dependsOn` was declared `string[]`, which is the exact inverse of the
|
|
2206
|
+
contract: it admitted only the array arm the contract rejects, and refused the
|
|
2207
|
+
bare parent-field name that is the one configuration making `field-selector`
|
|
2208
|
+
and `dynamic-config` work (objectui#5040).
|
|
2209
|
+
- `visibleWhen`, on both fields and sections, was declared `string`, so the
|
|
2210
|
+
ADR-0089 expression object — the arm `evalFieldPredicate` reads — could not be
|
|
2211
|
+
described at all. Both arms now travel whole onto the node.
|
|
2212
|
+
|
|
2213
|
+
A section's `fields` may also be a bare object-field name, the spec shorthand the
|
|
2214
|
+
list bridge already honours. The form bridge ran it through the object mapper
|
|
2215
|
+
instead, producing a field with no identity (`{ name: undefined }`) for the most
|
|
2216
|
+
ordinary section a form can declare; it is now forwarded verbatim, which is what
|
|
2217
|
+
the node's own `fields` slot admits.
|
|
2218
|
+
|
|
2219
|
+
Each drift-prone key's type is now bound to the `@objectstack/spec` symbol that
|
|
2220
|
+
owns it rather than restated, so it cannot drift again, and the compile-time pins
|
|
2221
|
+
in `FormViewWidenedArms.test.ts` fail if a future edit restates any of them by
|
|
2222
|
+
hand. The documented subset itself is unchanged: the keys this bridge does not
|
|
2223
|
+
declare, including the retirement ledger it keeps, stay exactly as they were.
|
|
2224
|
+
- bf28341: refactor(spec-bridge): retire the spec-bridge — `SpecBridge`, `bridgeListView`, `bridgeFormView` (#6366)
|
|
2225
|
+
|
|
2226
|
+
Removed under the 2026-08-27 maintainer ruling on #6366 (Option A — remove, and
|
|
2227
|
+
for `SpecBridge` as a WHOLE, not just the form half), on the same criteria the
|
|
2228
|
+
17.0.0 entry used to remove `bridgePage` / `bridgeDashboard`: no runtime
|
|
2229
|
+
consumer, and a route that could not deliver a working result even in
|
|
2230
|
+
principle. The stage-1 measurement (2026-08-26, PM-verified) the ruling rests
|
|
2231
|
+
on:
|
|
2232
|
+
|
|
2233
|
+
- **Zero consumers at every reachable endpoint** — no non-test caller in this
|
|
2234
|
+
repo, none in the sibling `objectstack` repo (which does not depend on
|
|
2235
|
+
`@object-ui/react` at all), and a GitHub-wide public code search returning
|
|
2236
|
+
hits only inside this repository. External npm-private hosts are
|
|
2237
|
+
unmeasurable from here; the ruling records fallback C (keep + document the
|
|
2238
|
+
boundary) should evidence of one ever surface.
|
|
2239
|
+
- **The bridged form route was structurally unable to work**: a bridged node
|
|
2240
|
+
carries neither `objectName` nor `customFields`, so `ObjectForm` takes the
|
|
2241
|
+
branch its own code labels "cannot proceed" and renders, in the registry
|
|
2242
|
+
wrapper's words, "a field-less card in silence". The only working host
|
|
2243
|
+
recipe (`{...node, objectName: '...'} as any`) existed solely inside the
|
|
2244
|
+
list bridge's integration tests and was documented nowhere.
|
|
2245
|
+
|
|
2246
|
+
This knowingly reverses the 17.0.0 changelog line "The `list` and `form`
|
|
2247
|
+
bridges are unaffected and remain the live authoring path" — by maintainer
|
|
2248
|
+
ruling, on the same measurement criteria that entry itself applied.
|
|
2249
|
+
|
|
2250
|
+
#5898's restored-key work on the form-view bridge (the
|
|
2251
|
+
`FormViewSpecConformance` suite and the spec keys it carried onto the bridged
|
|
2252
|
+
node) is **superseded by this removal, not fixed** — the route it repaired is
|
|
2253
|
+
gone. #6366's measured type-vocabulary asymmetry (a bridged field carrying
|
|
2254
|
+
`text` where the normalizer produces `field:text`) is likewise mooted rather
|
|
2255
|
+
than repaired.
|
|
2256
|
+
|
|
2257
|
+
The suites pinning the removed route go with it — retirement of a route
|
|
2258
|
+
nothing travels, not quarantine: the seven suites under
|
|
2259
|
+
`react/src/spec-bridge/__tests__/`, and plugin-grid's
|
|
2260
|
+
`specBridgeColumnSpelling` / `specBridgeExportFormats` render-integration
|
|
2261
|
+
suites. `ObjectGrid`'s own column-spelling, export-gate and density behavior
|
|
2262
|
+
keep their non-bridge pins (`columnDeclaredSpellingOnly`, `exportGate`,
|
|
2263
|
+
`exportServer`, `rowHeightOffSpecBoundary`, and core's
|
|
2264
|
+
`normalize-list-view` suite).
|
|
2265
|
+
|
|
2266
|
+
BREAKING CHANGE: the public exports `SpecBridge`, `bridgeListView`,
|
|
2267
|
+
`bridgeFormView` and the types `BridgeContext` / `BridgeFn` / `ObjectDefLite`
|
|
2268
|
+
are removed from `@object-ui/react`. There is no replacement translation
|
|
2269
|
+
layer — author `object-grid` / `object-form` nodes directly (the live path is
|
|
2270
|
+
`app-shell`'s `ObjectView`, which builds them from the object's own metadata),
|
|
2271
|
+
exactly as the 17.0.0 entry already directed for pages and dashboards.
|
|
2272
|
+
- 83fe6e7: row predicates on runtime record surfaces resolve `record.*` only; the bare-field and `data.*` spellings are no longer bound
|
|
2273
|
+
|
|
2274
|
+
Phase 2 of the row-predicate canon (objectui#5330, ruled 2026-08-20, option B;
|
|
2275
|
+
Phase 2 ruled 2026-09-02 and amended 2026-09-05). Until now a
|
|
2276
|
+
row predicate — `visible` / `disabled` / `enabled` on an action renderer, a row
|
|
2277
|
+
action, a `record:alert`, a `page:header` action, a conditional-formatting
|
|
2278
|
+
`condition` — bound the row three ways: canonical `record.status`, bare
|
|
2279
|
+
`status`, and `data.status`. The two non-canonical spellings are retired on
|
|
2280
|
+
every runtime record surface, in both evaluation tiers (`evalRowPredicate` /
|
|
2281
|
+
`partitionRowsByPredicate` in `@object-ui/core`; `usePredicateRecordContext` +
|
|
2282
|
+
`useCondition` in `@object-ui/react`) and for both dialects: a legacy
|
|
2283
|
+
`${data.x}` / `${x}` string on a row surface retires with the CEL spellings.
|
|
2284
|
+
|
|
2285
|
+
**What a retired spelling does now: it faults, exactly as it already did on the
|
|
2286
|
+
server** (`buildScope({ record })` mounts exactly `['record']`, so `status` and
|
|
2287
|
+
`data` are unknown variables there), and each surface applies its EXISTING
|
|
2288
|
+
fault policy — no runtime detector, no "treat as absent" special case, no
|
|
2289
|
+
uniform override:
|
|
2290
|
+
|
|
2291
|
+
- `evalRowPredicate` / `partitionRowsByPredicate` (row kebab, selection bar,
|
|
2292
|
+
`page:header` actions, conditional formatting): the caller's `fallback` —
|
|
2293
|
+
hidden / every row excluded / no style — reported once by the existing fault
|
|
2294
|
+
warning, which names the unknown variable (`Unknown variable: status`) and,
|
|
2295
|
+
on the fast route, carries the `record.` hint.
|
|
2296
|
+
- `useCondition` legs that opt into `throwOnError` (`action:button` and
|
|
2297
|
+
`action:menu` `visible`, `DeclaredActionsBar` `visible`): fail-closed —
|
|
2298
|
+
hidden on every row, reported once as `was hidden/disabled: its predicate
|
|
2299
|
+
threw — status is not defined`.
|
|
2300
|
+
- the non-throwing `useCondition` legs (`action:icon` / `action:group`
|
|
2301
|
+
`visible`, every `disabled` / `enabled`, `record:alert`): fail-soft — shown /
|
|
2302
|
+
greyed / enabled on every row, with the evaluator's own console line.
|
|
2303
|
+
- a host scope that carries its OWN `data` (app-shell's ambient `data: {}`) is
|
|
2304
|
+
left standing: `data.*` on a record surface then reads the host's object — a
|
|
2305
|
+
constant, silent `false` — which is what "no longer bound to the row" means.
|
|
2306
|
+
|
|
2307
|
+
The Phase-1 deprecation warning is removed with the bindings:
|
|
2308
|
+
`warnNonCanonicalRowSpelling` and `resetRowPredicateCanonWarnings` are no
|
|
2309
|
+
longer exported from `@object-ui/core`. `detectNonCanonicalRowSpelling`,
|
|
2310
|
+
`ROW_PREDICATE_CANONICAL_ROOT` and the `NonCanonicalRowSpelling` type stay
|
|
2311
|
+
exported — the offline instrument for sweeping authored metadata.
|
|
2312
|
+
|
|
2313
|
+
The layer rule is unchanged: `data` remains the canonical root on
|
|
2314
|
+
metadata-editing surfaces (ADR-0089 D3, `CANONICAL_ROOT_BY_LAYER`), and
|
|
2315
|
+
app-shell's metadata-admin `SchemaForm` / `predicate.ts` keep binding
|
|
2316
|
+
`{ data: row }` through their own evaluator.
|
|
2317
|
+
|
|
2318
|
+
No stored-metadata survey, export or migration rewrite was run (the maintainer
|
|
2319
|
+
ruled the stored population out of scope, 「不考虑存量」); the Phase-1 warning
|
|
2320
|
+
period was the notice.
|
|
2321
|
+
|
|
2322
|
+
Release note: Phase 1 (`d1ab06f0f` — the canon statement plus the warning) shipped
|
|
2323
|
+
in `@object-ui/core@17.6.0` (npm, 2026-08-24) although its changeset
|
|
2324
|
+
`.changeset/row-predicate-record-canon-5330.md` is still pending on `main`, so
|
|
2325
|
+
the next CHANGELOG section lists Phase 1 and this Phase 2 together: the warning
|
|
2326
|
+
it describes was live from 17.6.0 and is gone from this release on.
|
|
2327
|
+
- d1ab06f: Row predicates declare a canon: `record.*`. The bare shorthand and `data.*` now
|
|
2328
|
+
warn once, and are unchanged otherwise.
|
|
2329
|
+
|
|
2330
|
+
A row predicate (`visible` / `disabled` / `enabled` on an action renderer, a row
|
|
2331
|
+
scope, a `record:alert`) has bound the row three ways since objectui#4075 —
|
|
2332
|
+
`record.status`, bare `status`, and `data.status` — without any of them being
|
|
2333
|
+
declared the contract. The maintainer ruled that question on 2026-08-20
|
|
2334
|
+
(objectui#5330, option B), mirroring the objectstack#7917 option-② precedent for
|
|
2335
|
+
the identical renderer-tolerance shape: **the canon is `record.*`**, and the
|
|
2336
|
+
other two enter a deprecation window.
|
|
2337
|
+
|
|
2338
|
+
The canon states the **server's** accept set, which was this card's first
|
|
2339
|
+
measurement and turns out to be strictly narrower than the renderer's. Measured
|
|
2340
|
+
against `@objectstack/formula@17.1.0`, the engine the server evaluates with:
|
|
2341
|
+
|
|
2342
|
+
| spelling | server runtime | server authoring oracle |
|
|
2343
|
+
|---|---|---|
|
|
2344
|
+
| `record.status` | `{ ok: true, value: true }` | accepted |
|
|
2345
|
+
| bare `status` | `Unknown variable: status` | refused |
|
|
2346
|
+
| `data.status` | `Unknown variable: data` | **silently accepted** |
|
|
2347
|
+
|
|
2348
|
+
`buildScope({ record })` mounts exactly `['record']` — `data` is never bound and
|
|
2349
|
+
the row's fields are never flattened to top level. The three-way binding is a
|
|
2350
|
+
client tolerance with no server counterpart, which is why the warning belongs on
|
|
2351
|
+
this side.
|
|
2352
|
+
|
|
2353
|
+
`data.*` is the dangerous one, and the reason the warning exists. `data` is in
|
|
2354
|
+
`@objectstack/formula`'s `SCOPE_ROOTS`, so the server's bare-identifier oracle
|
|
2355
|
+
waves it through — that list is a deliberately generous "never faults" lint
|
|
2356
|
+
baseline, not the runtime accept set. A `data.*` row predicate therefore passes
|
|
2357
|
+
every authoring gate the platform has and then binds nothing at runtime: not an
|
|
2358
|
+
error, a constant `false`. A `visible` that is constantly false is a button that
|
|
2359
|
+
silently never appears — the objectui#4075 fail-closed signature.
|
|
2360
|
+
|
|
2361
|
+
What ships:
|
|
2362
|
+
|
|
2363
|
+
- `@object-ui/core` exports `detectNonCanonicalRowSpelling`,
|
|
2364
|
+
`warnNonCanonicalRowSpelling`, `resetRowPredicateCanonWarnings` and
|
|
2365
|
+
`ROW_PREDICATE_CANONICAL_ROOT` from a new `evaluator/rowPredicateCanon.ts`,
|
|
2366
|
+
which carries the canon statement and the measurement.
|
|
2367
|
+
- Both evaluation tiers report once, in dev: `evalRowPredicate` (core) and
|
|
2368
|
+
`useCondition` (react, for bags bound by `usePredicateRecordContext`).
|
|
2369
|
+
- Detection reuses the server's own oracles (`collectCelRootIdentifiers`,
|
|
2370
|
+
`firstUndeclaredReference`) rather than a regex, so no second dialect
|
|
2371
|
+
judgement is invented client-side.
|
|
2372
|
+
|
|
2373
|
+
**No spelling is removed and no behaviour changes.** Every predicate that
|
|
2374
|
+
resolved before resolves now — the ruling defers removal behind a stored-metadata
|
|
2375
|
+
survey, and the warning is what makes that survey possible (ADR-0078: a
|
|
2376
|
+
tolerance nothing ever reports can never be retired).
|
|
2377
|
+
|
|
2378
|
+
The deprecation is scoped to the **runtime record layer**. `data` remains the
|
|
2379
|
+
canonical root one layer over, in a metadata-editing form (ADR-0089 D3
|
|
2380
|
+
`CANONICAL_ROOT_BY_LAYER`), and the detector stands down there.
|
|
2381
|
+
- dba7d84: New export: `useSettledSchema` — the settled-schema RESOLUTION half shared by
|
|
2382
|
+
`ObjectKanban` / `ObjectView` / `ObjectCalendar`'s fetch-gate hand copies
|
|
2383
|
+
(objectui#6482, maintainer ruling Option A). It tracks whether an object's
|
|
2384
|
+
definition has finished resolving FOR THE KEY THE CURRENT RENDER IS ASKING
|
|
2385
|
+
ABOUT, returning `{ ready, def }` from one piece of internal state so `ready`
|
|
2386
|
+
and `def` can never be observed inconsistently and a stale key can never read
|
|
2387
|
+
as ready — the structural fix for the `ObjectTree` defect (objectui#6481)
|
|
2388
|
+
where a definition and a separate, one-way-latched "settled" boolean could
|
|
2389
|
+
disagree for a render after the object changed.
|
|
2390
|
+
|
|
2391
|
+
Gate PLACEMENT — which effect branch actually waits on `ready` — stays a
|
|
2392
|
+
per-component decision and is not part of this hook; see the hook's own doc
|
|
2393
|
+
comment. Existing hand copies are migrated on their own subsequent cards, not
|
|
2394
|
+
by this change.
|
|
2395
|
+
- c86185e: Bind `record` into the node-level visibility evaluator, and stop a hoisted
|
|
2396
|
+
`properties.visible` swallowing a declared `visibleWhen`.
|
|
2397
|
+
|
|
2398
|
+
`@objectstack/spec` has declared since ADR-0089 that a page component's
|
|
2399
|
+
`visibleWhen` binds the row — `ui/page.zod.ts`: *"Binds `record`,
|
|
2400
|
+
`current_user`, `page.<var>`"*. `SchemaRenderer` bound no `record` at all. Its
|
|
2401
|
+
evaluator was built from the ambient predicate scope, `data: dataSource` (the
|
|
2402
|
+
connector **adapter**, not the row) and `page: pageVariables`; the row lives in
|
|
2403
|
+
`RecordContext`, which that evaluator never read.
|
|
2404
|
+
|
|
2405
|
+
Because the surface is fail-soft, a `record.*` predicate did not misfire — it
|
|
2406
|
+
resolved to **shown**. Both polarities of the same predicate returned the same
|
|
2407
|
+
verdict, so a visibility gate silently did not gate, on every block on every
|
|
2408
|
+
record page. Measured on `record:alert`, `record:path`, `page:card` and
|
|
2409
|
+
`element:text`.
|
|
2410
|
+
|
|
2411
|
+
Three changes, all in `SchemaRenderer`'s evaluation memo:
|
|
2412
|
+
|
|
2413
|
+
- **`record` is bound**, as the `record` root only — the three roots the
|
|
2414
|
+
describe promises and nothing more. Not as bare fields, and never over
|
|
2415
|
+
`data`, which is what `${data.*}` in a props bag resolves against. Bound
|
|
2416
|
+
conditionally, so "no row" binds nothing rather than shadowing a `record` a
|
|
2417
|
+
host supplied through the ambient scope.
|
|
2418
|
+
- **`visibleWhen` is tested before `visible`.** The memo hoists `properties.*`
|
|
2419
|
+
onto the node, so a node carrying `properties.visible` short-circuited the
|
|
2420
|
+
declared node predicate — the one key the spec tells authors to write was the
|
|
2421
|
+
one key that could be silently ignored. The two deprecated aliases
|
|
2422
|
+
(`visibleOn` / `visibility`) deliberately keep their rank: they normalize into
|
|
2423
|
+
`visibleWhen` at parse, so a spec-parsed page never reaches them.
|
|
2424
|
+
- **An unresolvable predicate is loud** (dev builds). Fail-soft answered "this
|
|
2425
|
+
predicate is broken" and "this predicate said yes" with the same word. The
|
|
2426
|
+
verdict is unchanged on every path — `evaluateCondition` already returned
|
|
2427
|
+
`true` for every unevaluable predicate, including the non-negated `hidden` /
|
|
2428
|
+
`hiddenOn` legs where that `true` means HIDE — so only the silence moved.
|
|
2429
|
+
|
|
2430
|
+
**Behaviour change, stated plainly:** a shipped page whose node-level
|
|
2431
|
+
`record.*` predicate was previously inert now evaluates. A block that was
|
|
2432
|
+
permanently visible may begin to hide — which is the point, but it is a verdict
|
|
2433
|
+
change, not a no-op. `properties.visible` is unaffected in verdict: an
|
|
2434
|
+
in-tree census found **zero** node-level `record.*` predicates on page
|
|
2435
|
+
components, so nothing in this repository changes verdict.
|
|
2436
|
+
|
|
2437
|
+
### Patch Changes
|
|
2438
|
+
|
|
2439
|
+
- 7b10bef: `ElementDataSourceGate` now reports a saved view's refused row cap on the renderer path
|
|
2440
|
+
(objectui#10015).
|
|
2441
|
+
|
|
2442
|
+
objectui#9928 made `@object-ui/core` drop a saved view's `pagination.pageSize` (or legacy flat
|
|
2443
|
+
`limit`) when the contract refuses it: `0`, a negative, or a fraction. The drop is correct, but on
|
|
2444
|
+
the renderer path it removed the author's only diagnostic. The node's cap went from `0` to absent,
|
|
2445
|
+
and the consuming renderer treats an absent key as "not a mistake". The gate had never reported a
|
|
2446
|
+
VIEW cap, so nothing reported it at all.
|
|
2447
|
+
|
|
2448
|
+
The gate now emits `elementDataSourceRefusedLimitMessage`, the same sentence `ViewDataProvider`
|
|
2449
|
+
already uses on the path with no renderer. It is emitted once per declaration, from an effect and
|
|
2450
|
+
never from render, on the same `console.warn` channel. It fires only when the refusal changed what
|
|
2451
|
+
the block receives: the block reads a row cap, the binding declares no `limit` of its own, and the
|
|
2452
|
+
component's own cap is not a usable one. A view with no cap, a usable view cap, a usable component
|
|
2453
|
+
cap, a binding `limit`, or a block with no row cap stays silent. It never fires together with the
|
|
2454
|
+
gate's existing displaced-cap message (objectui#9899), because that message needs a usable cap to
|
|
2455
|
+
have been written and, with the view's cap refused, the only one left is a binding `limit`. No
|
|
2456
|
+
wire value changes.
|
|
2457
|
+
- 0aacecc: `useRecordSearch` no longer keys its search effect on the identity of the
|
|
2458
|
+
caller-supplied `getDisplayName` option (objectui#10044).
|
|
2459
|
+
|
|
2460
|
+
The resolver is read only inside a run, to label hits; it never decides whether a
|
|
2461
|
+
run happens. It was nevertheless the last entry of the search effect's dependency
|
|
2462
|
+
list, and that effect's cleanup clears the pending debounce timer. A caller passing
|
|
2463
|
+
the obvious spelling for an optional callback, an inline arrow, handed the hook a new
|
|
2464
|
+
function on every render, with two opposite results: a parent re-rendering faster
|
|
2465
|
+
than `debounceMs` (250 ms by default) never searched at all, and a parent re-rendering
|
|
2466
|
+
sparsely issued a second, identical request after the first had gone out. This is
|
|
2467
|
+
the dependency AGENTS.md §5 commandment #10 bans.
|
|
2468
|
+
|
|
2469
|
+
Each run now reads the resolver of the latest render through a ref, so an inline
|
|
2470
|
+
resolver is safe. A swapped resolver labels the hits of the next run; results already
|
|
2471
|
+
shown keep their labels. The option's name, type and default are unchanged.
|
|
2472
|
+
- 162621b: `useSettledSchema` no longer republishes an EQUAL definition as a new object,
|
|
2473
|
+
so swapping the adapter on a record-bound view costs ONE record query instead
|
|
2474
|
+
of two.
|
|
2475
|
+
|
|
2476
|
+
The hook refetches `getObjectSchema` whenever the adapter identity changes, and
|
|
2477
|
+
every consumer its doc comment instructs — `ObjectGantt`, `ObjectCalendar`,
|
|
2478
|
+
`ObjectView`, `ObjectTimeline`, `ObjectGallery` — keys its record fetch on the
|
|
2479
|
+
returned `def`, which is what `AGENTS.md` §5 commandment #10 tells a consumer to
|
|
2480
|
+
do (key on the payload, never on a memo identity). The producer half of that
|
|
2481
|
+
same sentence is what this fixes: a byte-identical answer arriving as a fresh
|
|
2482
|
+
object re-fired every one of those fetches, and the duplicate query carried the
|
|
2483
|
+
same `$expand` set as the one before it. Measured first-hand on `ObjectGantt`
|
|
2484
|
+
with an instrumented adapter, with a control swap in the same command; the
|
|
2485
|
+
counts, and the control that makes them a discrimination rather than a counting
|
|
2486
|
+
artefact, are asserted by
|
|
2487
|
+
`packages/react/src/hooks/__tests__/useSettledSchema.equalPayload-10106.test.tsx`
|
|
2488
|
+
(objectui#10106).
|
|
2489
|
+
|
|
2490
|
+
A definition that genuinely CHANGED across the swap still publishes a new
|
|
2491
|
+
object and still re-queries — that case is pinned in the same file, because
|
|
2492
|
+
"fetch less" would have been the wrong repair.
|
|
2493
|
+
- 2ad3671: Correct the read rule in the `InlineEditContextValue.draft` doc comment (objectui#10466).
|
|
2494
|
+
|
|
2495
|
+
It told hosts to read a field's live value as `draft[name] ?? data[name]`. That read
|
|
2496
|
+
falls back to the saved value when the draft value is `null` or `undefined`. A field the
|
|
2497
|
+
user emptied is an own draft key with an empty value, and so is a single select or radio
|
|
2498
|
+
emptied by a cascade clear, which stages `null` since objectui#10291. A host that followed
|
|
2499
|
+
the comment showed the value the user had just removed, and handed it back to an option
|
|
2500
|
+
widget that pruned it, which pruned it again on every render (the loop objectui#7190 fixed
|
|
2501
|
+
on the highlights strip).
|
|
2502
|
+
|
|
2503
|
+
The comment now teaches the own-key read: take the value from the staged record
|
|
2504
|
+
`{ ...data, ...draft }`. An own draft key wins even when its value is empty
|
|
2505
|
+
(`null`, `undefined`, `''`), and the saved value is read only when the key is absent. The
|
|
2506
|
+
comment ships in `dist` typings and is what a host author sees on hover. No runtime
|
|
2507
|
+
behaviour changes. A source-text pin keeps the comment from teaching a fallback read again.
|
|
2508
|
+
- 3d614ea: fix(react,app-shell): a search hit's label no longer prints a field the viewer may not read (objectui#10500)
|
|
2509
|
+
|
|
2510
|
+
The ⌘K command palette, the full-page search results and the `global:search`
|
|
2511
|
+
block label each record hit with `getRecordDisplayName`, and `useRecordSearch`
|
|
2512
|
+
handed that resolver the record as served. On a backend that does not strip
|
|
2513
|
+
denied fields, a `titleFormat` token or a declared `nameField` that the loaded
|
|
2514
|
+
permission policy denies therefore printed as the hit's label.
|
|
2515
|
+
|
|
2516
|
+
`useRecordSearch` takes a new optional `fieldReadPolicy` option: pass
|
|
2517
|
+
`usePermissions()` from `@object-ui/permissions`. Once that policy is loaded,
|
|
2518
|
+
each hit is labelled from its record with the denied fields removed, before any
|
|
2519
|
+
`getDisplayName` resolver reads it. The record id is always kept, so the
|
|
2520
|
+
`Record #` id floor still applies. A denied field is read exactly as an absent
|
|
2521
|
+
one, and the label falls through to its next source, the label ObjectStack's
|
|
2522
|
+
`FieldMasker` row already yields. When the policy finishes loading, a search
|
|
2523
|
+
that is already showing runs again, so hits labelled before the policy loaded
|
|
2524
|
+
are relabelled. The three app-shell search surfaces now pass the policy. The
|
|
2525
|
+
record title (objectui#10434) and the lookup option label (objectui#10411)
|
|
2526
|
+
follow the same rule.
|
|
2527
|
+
|
|
2528
|
+
Without the option, before a policy loads, and with no permission provider
|
|
2529
|
+
mounted, nothing is removed and every label is unchanged. A title the server
|
|
2530
|
+
computed for a `searchAll` hit is still shown as the server sent it, and a
|
|
2531
|
+
hit's `raw` payload is still the record as served.
|
|
2532
|
+
|
|
2533
|
+
This is defence in depth: ObjectStack's `FieldMasker` already removes the
|
|
2534
|
+
fields a user may not read from the rows it serves, so on that backend no
|
|
2535
|
+
label changes.
|
|
2536
|
+
- 9a5f998: `@object-ui/core` exports `withoutDeniedFields(record, policy, objectName, extraKeep?)`, the field-read rule for one record: the record as the viewer may read it on `objectName` (objectui#10594).
|
|
2537
|
+
|
|
2538
|
+
Every field the loaded `policy` denies is removed, which leaves the row ObjectStack's `FieldMasker` already serves. `id`, `_id` and any key in `extraKeep` (a declared id field) are never judged. Before a policy loads, with no policy, with no object name, or for a value that is not an object, the record comes back as it is; when nothing is withheld the SAME object comes back. `policy` is structural (`isLoaded` and `checkField`), so `usePermissions()` from `@object-ui/permissions` satisfies it and `@object-ui/core` gains no dependency.
|
|
2539
|
+
|
|
2540
|
+
The renderer's display surfaces that each carried a module-private copy of this rule now call the export, with no change in what they draw:
|
|
2541
|
+
|
|
2542
|
+
- `@object-ui/fields`: the lookup cell's name and the user cell's name and avatar (the package entry), and the lookup editor's option label and search chip avatar (`LookupField`).
|
|
2543
|
+
- `@object-ui/react`: `useRecordSearch`'s hit label under `fieldReadPolicy`.
|
|
2544
|
+
- `@object-ui/app-shell`: the record page's title and the row its blocks read (`RecordDetailView`).
|
|
2545
|
+
- `@object-ui/plugin-detail`: `DetailView`'s header title and `record:details`' title dedupe. The package-internal `withoutDeniedFields` module is removed; it was never a package export.
|
|
2546
|
+
- 256b4c9: A filter refused as malformed (`INVALID_FILTER`) no longer throws out of render or out of a click
|
|
2547
|
+
handler at the sites that merge filters there. That holds whether this layer's filter converter
|
|
2548
|
+
refused it or, at the drill seam, the spec's own `parseFilterAST`. Each site now surfaces the
|
|
2549
|
+
refusal the way its siblings already do (objectui#10789). The filter stays **refused**: nothing
|
|
2550
|
+
that was refused is accepted, and no site falls back to "no filter".
|
|
2551
|
+
|
|
2552
|
+
- **`ElementDataSourceGate` / `useElementDataSource`** (`@object-ui/react`): the gate's merge of
|
|
2553
|
+
the component's `filter` with the composed binding, and the merge of a saved view's filter
|
|
2554
|
+
with the binding's own, lowered through the throwing converter inside render. A refused
|
|
2555
|
+
filter from either now makes the binding `missing` with a new optional `filterRefusal` on the
|
|
2556
|
+
result (`error` carries its message). The gate then draws, in place of the block, the
|
|
2557
|
+
malformed-filter notice the wrapped blocks draw: `view.malformedFilter`, naming the refused
|
|
2558
|
+
operator or field. `element:record_picker`, which reads the same hook, shows its
|
|
2559
|
+
configuration-error panel with the refusal.
|
|
2560
|
+
- **`PeoplePicker` / `RecordPickerDialog`** (`@object-ui/fields`): the recents merge and the
|
|
2561
|
+
dialog's `mergedFilter` lower through `toFilterNodeSafely`; a refusal is shown in the picker's
|
|
2562
|
+
existing error state and no query runs without the filter.
|
|
2563
|
+
- **Drill-downs** (`ObjectChart`, `ObjectPivotTable`, `DatasetWidget`, `DatasetReportRenderer`):
|
|
2564
|
+
a widget or report filter refused at the drill seam made the drill click throw. The converter
|
|
2565
|
+
refuses some (a spec `$not`, say). The spec's `parseFilterAST` refuses others, such as a scalar
|
|
2566
|
+
on a list operator in either dialect: `[['stage', 'in', 'won']]` or `{ stage: { $in: 'won' } }`.
|
|
2567
|
+
The drill is now not opened or emitted (never with the scope dropped), and the refusal is logged
|
|
2568
|
+
with `console.warn`, naming the operator. `composeDrillFilter` (`@object-ui/core`) now throws
|
|
2569
|
+
one refusal type: the spec's `INVALID_FILTER` error is re-raised as a `FilterOperatorError` with
|
|
2570
|
+
its message and the same `INVALID_FILTER` / 400 envelope, and any other error passes through
|
|
2571
|
+
unchanged.
|
|
2572
|
+
- **`ListView` export** (`@object-ui/plugin-list`): the server export builds its filter inside
|
|
2573
|
+
its `try`, so a refusal is named in the export menu instead of escaping the click.
|
|
2574
|
+
- **`convertFiltersToAST`** (`@object-ui/core`): an `$or` member that is the TRUE identity no
|
|
2575
|
+
longer stops the members after it from being read. `{ $or: [{}, { a: {} }] }` is now refused
|
|
2576
|
+
like `{ $or: [{ a: {} }, {}] }` already was, instead of answering TRUE (every row); a TRUE
|
|
2577
|
+
disjunct beside members that lower still absorbs its `$or`.
|
|
2578
|
+
- 328abeb: Four Console surfaces that read English under a zh-CN session now read the session's
|
|
2579
|
+
language (objectui#10900). English stays the default.
|
|
2580
|
+
|
|
2581
|
+
- **The generic action success toast.** When an action declares no `successMessage`, the
|
|
2582
|
+
runner falls back to "Action completed successfully".
|
|
2583
|
+
That fallback now goes through a translator: `ActionRunner.setTranslator(translate)` is
|
|
2584
|
+
new in `@object-ui/core`, and `<ActionProvider>` and `useActionRunner` in
|
|
2585
|
+
`@object-ui/react` install the session's `t` on the runner they build. The pack key is
|
|
2586
|
+
`actions.completedSuccessfully`. An author's `successMessage` still reaches the toast
|
|
2587
|
+
untranslated, and a runner with no translator installed still toasts the English sentence.
|
|
2588
|
+
- **The social sign-in buttons on the login and sign-up pages.** `SocialSignInButtons`
|
|
2589
|
+
takes a new `buttonText` prop, a template whose `{provider}` is replaced with the
|
|
2590
|
+
provider's display name; unset, the buttons keep "Continue with {provider}" and
|
|
2591
|
+
"Sign up with {provider}". `LoginForm` and `RegisterForm` take a new `socialButton`
|
|
2592
|
+
label and pass it, with their existing `orText` label, to the buttons. `orText` was
|
|
2593
|
+
documented as the divider label but rendered nowhere; it now sets the divider under the
|
|
2594
|
+
buttons, and its documented default is corrected from "or" to "or continue with email",
|
|
2595
|
+
the text that divider has always shown. The console's login and sign-up pages and
|
|
2596
|
+
`@object-ui/app-shell`'s `DefaultLoginPage` / `DefaultRegisterPage` pass the new
|
|
2597
|
+
`auth.login.*` and `auth.register.*` `socialButton` / `orText` keys. The provider's display
|
|
2598
|
+
name is inserted as-is: the component's own label for the branded providers it knows,
|
|
2599
|
+
otherwise the name the server reports.
|
|
2600
|
+
- **Build Doctor.** The build conversation's Build Doctor button (its accessible name and
|
|
2601
|
+
both tooltips) and the title of the drawer it opens read `console.ai.buildDoctor`,
|
|
2602
|
+
`console.ai.buildDoctorTitle` and `console.ai.buildDoctorDisabledTitle`.
|
|
2603
|
+
- **Setup → marketplace.** On `system/marketplace` and the pages under it, the breadcrumb
|
|
2604
|
+
segment after System reads `console.breadcrumb.marketplace` instead of the humanized URL
|
|
2605
|
+
slug; other `system/*` segments are unchanged. The zh marketplace search placeholder
|
|
2606
|
+
reads 「按名称或标识搜索应用…」 instead of 「按名称或 manifest ID 搜索应用…」. The search
|
|
2607
|
+
itself is unchanged: it matches the display name, the identifier and the description.
|
|
2608
|
+
|
|
2609
|
+
All ten locale packs carry the nine new keys; no existing `en` value changes.
|
|
2610
|
+
|
|
2611
|
+
**Clause-②: yes** — besides the nine pack keys, the public surface widens by four optional
|
|
2612
|
+
members: `ActionRunner.setTranslator`, `SocialSignInButtonsProps.buttonText`,
|
|
2613
|
+
`LoginFormLabels.socialButton` and `RegisterFormLabels.socialButton`. Nothing is removed,
|
|
2614
|
+
renamed or narrowed, no accept set changes, and `@object-ui/react` exports nothing new.
|
|
2615
|
+
- 0645133: fix(react): one Ctrl+Z undoes one record write, and Ctrl+Z inside a text field is the field's own undo (objectui#11081)
|
|
2616
|
+
|
|
2617
|
+
What a user saw: with two or more undoable actions behind them, one Ctrl+Z in the
|
|
2618
|
+
console reverted SEVERAL saved record writes, one per mounted `useGlobalUndo`
|
|
2619
|
+
(every console route mounts at least two), with a toast for each. And pressing
|
|
2620
|
+
Ctrl+Z while typing in a field did not undo the typing: the keypress was
|
|
2621
|
+
`preventDefault`ed and the last saved record write was reverted instead.
|
|
2622
|
+
|
|
2623
|
+
What changes: however many `useGlobalUndo` instances are mounted, one `window`
|
|
2624
|
+
`keydown` listener serves them, so one Ctrl+Z (or Ctrl+Shift+Z) pops at most one
|
|
2625
|
+
operation, and exactly one instance answers it: the longest-mounted live one,
|
|
2626
|
+
whose data source runs the write and whose toast shows. That is the instance that
|
|
2627
|
+
already answered whenever a single operation was stacked, so a single Ctrl+Z
|
|
2628
|
+
behaves as before. A keypress whose target is an `input`, `textarea`, `select` or
|
|
2629
|
+
contenteditable region is left alone: nothing is popped, the default is not
|
|
2630
|
+
prevented, and the browser's own undo runs in the field. The listener is removed
|
|
2631
|
+
when the last instance unmounts.
|
|
2632
|
+
|
|
2633
|
+
Unchanged: the hook's signature and return shape, and a toast's Undo button, which
|
|
2634
|
+
still runs through the instance that raised the toast.
|
|
2635
|
+
- a8b9889: A related list inside a record now places its child object's `record_related` actions on each row (objectui#11270; the renderer half of the enforce answer on objectstack-ai/objectstack#20937).
|
|
2636
|
+
|
|
2637
|
+
The spec declares the action location `record_related` ("actions on a related list section inside a record"), and nothing in objectui read it, so an action placed only there rendered nowhere. Triage read the location as row placement, scoped to a parent record: `list_item` shows on the child's rows wherever the child is listed, and `record_related` shows on them only in a related list inside a parent record.
|
|
2638
|
+
|
|
2639
|
+
- **The host bridge** (`RelatedRecordActionsBridge`, `@object-ui/app-shell`) puts the child object's `record_related` actions in each row's action menu beside its `list_item` actions, in the child's declared order, when a parent record is in scope. An action that declares both renders once. The child object's own list view still reads `list_item` only, so a `record_related`-only action does not appear there.
|
|
2640
|
+
- **The authored `actions` channel** on `record:related_list` (`@object-ui/plugin-detail`) accepts an id whose action declares `record_related` and places it in each row's menu. The refusal notice for an id that cannot be placed now names the three related-list locations: `list_toolbar`, `list_item` and `record_related`.
|
|
2641
|
+
- **The action designer's preview** (`@object-ui/app-shell`) draws a `record_related` action on the rows of a list, with the frame `list_item` uses. It used to draw it as a button in a section header, which is not where the console places it. `record_section` keeps its section-header frame.
|
|
2642
|
+
- `@object-ui/react`: the `RelatedRecordHandlers.rowActions` doc comment names both row locations. No code change.
|
|
2643
|
+
|
|
2644
|
+
⚠️ Behaviour change: a child action that declares `record_related` now shows on its related-list rows inside a record, where it used to show nowhere.
|
|
2645
|
+
- b654d4e: `toRenderableSchema` declares its parameter as `SchemaNode` (objectui#11479).
|
|
2646
|
+
|
|
2647
|
+
The parameter used to spell out `SchemaNode`'s members one by one. It now names the union itself, so it follows `SchemaNode` instead of keeping a hand-written copy of it. Nothing it accepts changes today: the two spellings are the same type, and the function's return type and behaviour are untouched.
|
|
2648
|
+
|
|
2649
|
+
⚠️ **Dated note, 2026-10-02 — the parameter narrows with `SchemaNode` — objectui#11466.** At this change the parameter's two spellings were the same type and nothing it accepted changed; now, later in this same release, `SchemaNode`'s object arm is `DeclaredNode`, the union of the declared node types, so the parameter follows it and no longer accepts a value typed `BaseSchema`. That is the point of naming the union instead of copying it. The rest of this entry is kept as the reading of this change.
|
|
2650
|
+
- 546ddf7: A node-gate visibility predicate that FAULTS now says so in a production build, once per
|
|
2651
|
+
distinct predicate source (objectui#6038, maintainer ruling 2026-08-25, option B: "the
|
|
2652
|
+
silence is no longer an accepted property"). Observability only — no verdict moves.
|
|
2653
|
+
|
|
2654
|
+
`SchemaRenderer`'s visibility chain is fail-open: a predicate that cannot be evaluated
|
|
2655
|
+
resolves to the same answer as one that said yes, so a gate that stops biting looks
|
|
2656
|
+
exactly like a gate the author got right. The diagnostic that names it (objectui#5454 /
|
|
2657
|
+
objectui#5687) sat behind a `__DEV__` short-circuit, because the only fault-detection
|
|
2658
|
+
channel available was `throwOnError`, and on the CEL branch `evaluateCelCondition`
|
|
2659
|
+
implements that by evaluating **twice** — too expensive to ship for every predicate of
|
|
2660
|
+
every node.
|
|
2661
|
+
|
|
2662
|
+
**What production actually printed before, measured per dialect on the built evaluator**
|
|
2663
|
+
— the card's premise held for one dialect of three, and the other two failed in opposite
|
|
2664
|
+
directions:
|
|
2665
|
+
|
|
2666
|
+
| dialect | production console, before |
|
|
2667
|
+
|---|---|
|
|
2668
|
+
| bare string | **nothing** |
|
|
2669
|
+
| `{ dialect: 'cel' }` envelope | one generic line, deduped per source |
|
|
2670
|
+
| `${…}` template | one generic line **per evaluation**, never deduped |
|
|
2671
|
+
|
|
2672
|
+
So the dialect objectstack#11254 measured a live gate breaking on was the silent one,
|
|
2673
|
+
while the template dialect was the console flood the ruling's rate-limit clause exists to
|
|
2674
|
+
prevent.
|
|
2675
|
+
|
|
2676
|
+
**The fix reports the fault the evaluator already detected, at the same number of engine
|
|
2677
|
+
calls.** `EvaluationOptions.onFault` is a new passback on `@object-ui/core`'s
|
|
2678
|
+
`ExpressionEvaluator`: every fault site is already inside a `catch`, or already holds the
|
|
2679
|
+
canonical engine's failure reason, so nothing is evaluated twice. It mirrors, one layer
|
|
2680
|
+
up, the seam `FieldPredicateDiagnostic` already documents (`warn: false` plus a reason
|
|
2681
|
+
passback), and supplying it transfers reporting to the caller so one fault stays one
|
|
2682
|
+
line. Pinned: the CEL branch performs the same number of record reads with the passback
|
|
2683
|
+
as without it, and strictly fewer than the `throwOnError` probe.
|
|
2684
|
+
|
|
2685
|
+
`SchemaRenderer` passes it in production and reports through the **same** reporter the dev
|
|
2686
|
+
branch uses — same message, same severity, same dedupe `Set`, same key. Development and
|
|
2687
|
+
production now print the identical line for the identical fault; the `__DEV__` gate no
|
|
2688
|
+
longer decides *whether* a fault is reported, only *how* it is detected.
|
|
2689
|
+
|
|
2690
|
+
`page:tabs` item-level `visibleWhen` (`@object-ui/components`) is covered by the same
|
|
2691
|
+
reporter and the same rate limit. It swallowed the identical fault under a different
|
|
2692
|
+
helper, and it was the worse of the two: the node gate at least reported in development,
|
|
2693
|
+
while a faulting item predicate was silent in *both* builds on a gate whose false verdict
|
|
2694
|
+
removes an entire tab, header and panel.
|
|
2695
|
+
|
|
2696
|
+
**Rate limit:** deduped per (node type, gate key, predicate source) — never per render and
|
|
2697
|
+
never per node instance. A two-hundred-row list of one broken predicate is one line; a
|
|
2698
|
+
second distinct predicate source still gets its own line. Both halves are pinned, because
|
|
2699
|
+
a test that asserts only "a warning was emitted" is equally green on an implementation
|
|
2700
|
+
that emitted fifty, and one that asserts only "exactly one" is equally green on an
|
|
2701
|
+
implementation that suppresses everything.
|
|
2702
|
+
|
|
2703
|
+
**Not changed by this card, deliberately:** the fail-open semantics themselves; the
|
|
2704
|
+
objectui#5687 adapter-only `data.*` report, which stays development-only under its own
|
|
2705
|
+
2026-08-22 ruling (that path is not a fault — the predicate evaluated perfectly, against
|
|
2706
|
+
the wrong object); and the `features` root on `/forms/:name`, which objectui#6262 settled
|
|
2707
|
+
by refusing it in form-view predicates rather than wiring it.
|
|
2708
|
+
|
|
2709
|
+
`reportUnresolvableVisibilityPredicate`, `formatUnresolvableVisibilityMessage`,
|
|
2710
|
+
`UNRESOLVABLE_VISIBILITY_PREFIX` and `__resetVisibilityPredicateWarnings` are now exported
|
|
2711
|
+
from `@object-ui/react` so every surface that evaluates a node `visibleWhen` shares one
|
|
2712
|
+
reporter and one rate limit — a second copy would mean a second dedupe `Set`, and one
|
|
2713
|
+
authored predicate would be entitled to one line per package instead of one line.
|
|
2714
|
+
- 22ba927: `SchemaRenderer` now hands a stable `schema` object identity to a node carrying
|
|
2715
|
+
`responsiveStyles` (objectui#6270).
|
|
2716
|
+
|
|
2717
|
+
ADR-0065 scoped styles make a styled node take a branch that rebuilds the schema
|
|
2718
|
+
object to merge the generated scope class into `className`. That rebuild was not
|
|
2719
|
+
memoised, so it allocated a new object on **every** `SchemaRenderer` render — even
|
|
2720
|
+
when the `evaluatedSchema` memo directly above it held. Every downstream renderer
|
|
2721
|
+
that memoises on `[schema]` therefore saw a fresh identity and re-ran: concretely
|
|
2722
|
+
`ObjectMap`'s `dataConfig` and `mapConfig`, and the whole marker cascade below them
|
|
2723
|
+
(`markers` → `filteredMarkers` → `clusteredData` / `markerBounds` → `initialViewState`).
|
|
2724
|
+
Only nodes on that branch were affected — a plain node was already handed the
|
|
2725
|
+
memoised `evaluatedSchema` itself.
|
|
2726
|
+
|
|
2727
|
+
The trigger is narrower than "has `responsiveStyles`" reads: it needs one of the four
|
|
2728
|
+
sized breakpoint keys (`large` / `medium` / `small` / `xsmall`). A `{ base: … }` shape
|
|
2729
|
+
never took the branch and was never affected.
|
|
2730
|
+
|
|
2731
|
+
The scoped-style computation now lives in a `useMemo` keyed on
|
|
2732
|
+
`[evaluatedSchema, autoStyleId]`, hoisted above the renderer's early returns — its old
|
|
2733
|
+
use site sits after them, so a memo written there would have been a conditional hook.
|
|
2734
|
+
A genuinely changed `className`, interpolated value or breakpoint still produces a new
|
|
2735
|
+
identity, so nothing goes stale.
|
|
2736
|
+
- 5967be0: Extends objectui#5687's adapter-only `data.*` constant-predicate diagnostic — a node-gate
|
|
2737
|
+
predicate that evaluates perfectly, against the wrong object, because at the node tier
|
|
2738
|
+
`data` is the data-source adapter, not the row — to the `disabled` / `disabledOn` gate
|
|
2739
|
+
(objectui#6504, maintainer ruling 2026-08-27 option A).
|
|
2740
|
+
|
|
2741
|
+
A node written `{ "type": "button", "disabled": "data.status == 'locked'" }` evaluates
|
|
2742
|
+
cleanly (no fault, so objectui#6445's fault reporter correctly stays silent), and on the
|
|
2743
|
+
constant's other polarity (`data.locked == null`, `!data.assignee`, or an adapter that
|
|
2744
|
+
answers nothing) hands the gate a constant `true` that greys the control out on every row,
|
|
2745
|
+
in every build, with nothing on the console. This leg now names it, in development only —
|
|
2746
|
+
option C (always-on) was excluded, outside the #5687 precedent.
|
|
2747
|
+
|
|
2748
|
+
The copy is new, not reused: the visibility leg's sentence ("a constant `false` hides the
|
|
2749
|
+
node on every row") is written about the opposite polarity and would be false on this gate.
|
|
2750
|
+
The enablement leg's own sentence names the constant-`true` direction — the control renders
|
|
2751
|
+
DISABLED, greyed out, indistinguishable from a gate the author meant to close.
|
|
2752
|
+
|
|
2753
|
+
Both legs carry the same dissolution pointer: this diagnostic — visibility AND enablement
|
|
2754
|
+
together — dissolves when objectui#5330's `data.*` deprecation window closes.
|
|
2755
|
+
|
|
2756
|
+
Dev-only, no verdict change, no interpolation change, no published type widened (the new
|
|
2757
|
+
`AdapterOnlyPredicateGateKind` type and the new prefix constant are module-internal, not
|
|
2758
|
+
re-exported from the package entry — matching objectui#5687's own symbols, which never were
|
|
2759
|
+
either).
|
|
2760
|
+
- 12402a9: Drop the undeclared object-level `titleField` from the object page's five
|
|
2761
|
+
remaining view-config seams, and from the record-search memo key
|
|
2762
|
+
|
|
2763
|
+
`ObjectView` resolves a title field for seven view kinds. Two of them —
|
|
2764
|
+
calendar and gantt — already read `viewDef.<kind>?.titleField || 'name'`. The
|
|
2765
|
+
other five (timeline, kanban, map, gallery, tree) carried a three-rung chain
|
|
2766
|
+
with `objectDef.titleField` in the middle, so one file answered the same
|
|
2767
|
+
question two different ways. This converges the five on the shape the two
|
|
2768
|
+
siblings already had; it is a convergence, not a removal.
|
|
2769
|
+
|
|
2770
|
+
The middle rung could never fire for legal metadata. `@objectstack/spec`'s
|
|
2771
|
+
object schema is a `strictObject`, so
|
|
2772
|
+
`ObjectSchema.safeParse({ …, titleField: 'x' })` is rejected with
|
|
2773
|
+
`unrecognized_keys` — the same issue code a nonsense key gets — while
|
|
2774
|
+
`nameField`, `displayNameField` and `titleFormat` all parse (measured against
|
|
2775
|
+
`@objectstack/spec@17.2.0`, the dist this repo installs). objectui#6531
|
|
2776
|
+
established that measurement and dropped the twin read inside
|
|
2777
|
+
`getRecordDisplayName`. Reading a key no producer can ship is the
|
|
2778
|
+
consumer-side alias AGENTS.md Commandment #0.1 bans.
|
|
2779
|
+
|
|
2780
|
+
Behaviour for every legal config is unchanged, and both directions are pinned:
|
|
2781
|
+
a view that declares its own `titleField` still wins on every kind, a view that
|
|
2782
|
+
declares none still floors at `'name'`, and an object carrying the
|
|
2783
|
+
contract-rejected key is now honoured by no kind. Re-pointing the middle rung
|
|
2784
|
+
at the declared `nameField` was considered and rejected: it would have added a
|
|
2785
|
+
rung calendar and gantt do not have — increasing the divergence — and, unlike
|
|
2786
|
+
this change, it would have altered behaviour for legal configs.
|
|
2787
|
+
|
|
2788
|
+
`useRecordSearch`'s candidate signature — the memo key that decides when the
|
|
2789
|
+
cross-object fanout re-runs — appended `o?.titleField ?? ''` to every entry.
|
|
2790
|
+
Because no legal object definition can carry the key, that half was permanently
|
|
2791
|
+
`''`: a constant suffix in a cache signature, and the last thing in the repo
|
|
2792
|
+
that read as evidence some producer supplies it. The signature is now the object
|
|
2793
|
+
name alone, which is the only field of an object definition the effect actually
|
|
2794
|
+
consumes. Change detection is unaffected — a changed candidate name still
|
|
2795
|
+
re-runs the fanout, and a new array with identical content still does not.
|
|
2796
|
+
- 46b9bc9: A `props` config bag on a component-renderer node is now named at render
|
|
2797
|
+
instead of dropped in silence (objectui#6708).
|
|
2798
|
+
|
|
2799
|
+
`SchemaRenderer` HOISTS every `properties.*` value onto the node, so a key
|
|
2800
|
+
written under `properties` is a real value on `schema.<key>` by the time a
|
|
2801
|
+
renderer destructures it. `props` — the annotated legacy alias of the same bag
|
|
2802
|
+
— is NOT hoisted: it is evaluated and then spread as React props on the created
|
|
2803
|
+
element. A renderer declared as `({ schema })`, which is the normal shape for
|
|
2804
|
+
the component renderers, therefore never sees it. The `element:*` family is the
|
|
2805
|
+
exception: its `readProps()` merges `{ ...schema.props, ...schema.properties }`,
|
|
2806
|
+
so the same spelling is honoured there.
|
|
2807
|
+
|
|
2808
|
+
Every gate accepts the `props` spelling — `BaseSchema` is `.passthrough()` with
|
|
2809
|
+
`[key: string]: any` — and the docs call it a supported alias, so nothing
|
|
2810
|
+
between the author and the screen said a word. Re-measured on `faac0d935`
|
|
2811
|
+
through the real `SchemaRenderer` with a probe that records both channels:
|
|
2812
|
+
|
|
2813
|
+
| node | React prop `data` | `schema.data` |
|
|
2814
|
+
|---|---|---|
|
|
2815
|
+
| `props: { data: "${data.customers}" }` | the evaluated array | absent |
|
|
2816
|
+
| `properties: { data: "${data.customers}" }` | the evaluated array | the array |
|
|
2817
|
+
|
|
2818
|
+
Same key, same value, one envelope apart. The expression is evaluated on both
|
|
2819
|
+
legs, so this is a dropped value rather than an unevaluated one. Read through a
|
|
2820
|
+
real `data-table` (objectui#6665's four-leg pin) the same pair renders
|
|
2821
|
+
`No results found` against the two rows.
|
|
2822
|
+
|
|
2823
|
+
The diagnostic's level and dedupe were chosen from a census, which the ruling
|
|
2824
|
+
made a precondition. Every JSON document, every `json` fence in every
|
|
2825
|
+
`.md`/`.mdx`, and every TypeScript object literal in the repo was walked for
|
|
2826
|
+
nodes carrying both `type` and `props`: 39 such nodes, 22 of them on
|
|
2827
|
+
component-renderer types, and 19 of those 22 are test fixtures exercising this
|
|
2828
|
+
shape on purpose. The authored, non-test corpus holds 5 — three of which are
|
|
2829
|
+
deliberate counter-examples in the skills guides. Nothing floods, so the level
|
|
2830
|
+
is not softened for volume; the dedupe is keyed on the MESSAGE rather than on
|
|
2831
|
+
the schema object, so a metadata generator emitting one wrong envelope across
|
|
2832
|
+
many nodes still gets one line while two genuinely different nodes get two.
|
|
2833
|
+
|
|
2834
|
+
`console.warn`, matching objectui#6575 and objectui#6665 — the two prior
|
|
2835
|
+
instances of this exact "you declared something and the renderer dropped it"
|
|
2836
|
+
shape — rather than the `console.error` its neighbour at this tier uses for a
|
|
2837
|
+
raw `${...}` placed verbatim in front of a user. Nothing is placed here; a
|
|
2838
|
+
value is dropped.
|
|
2839
|
+
|
|
2840
|
+
No behaviour change, which is the entire reason this arm was chosen. Hoisting
|
|
2841
|
+
`props` to parity with `properties` was refused at ruling: it would weld the
|
|
2842
|
+
legacy alias in as a permanent second spelling, against this repo's
|
|
2843
|
+
alias-retirement direction. Refusing the key at parse stays blocked on the
|
|
2844
|
+
`.passthrough()` ceiling (objectui#5155 / objectui#6269). What every renderer
|
|
2845
|
+
receives is pinned byte-for-byte against a reading captured on the tree before
|
|
2846
|
+
the diagnostic existed. Nothing is added to the published surface either — the
|
|
2847
|
+
predicate, message builder, prefix constant and test-only reset are
|
|
2848
|
+
module-internal and are not re-exported from the package entry, matching
|
|
2849
|
+
objectui#6575's own symbols. The trap stops being silent; it does not stop
|
|
2850
|
+
being a trap.
|
|
2851
|
+
- d47de51: `useSchemaPersistence` no longer writes its adapter ref during render
|
|
2852
|
+
(objectui#6745).
|
|
2853
|
+
|
|
2854
|
+
The hook keeps the live adapter in a ref so `save`/`load`/`list`/`remove` can be
|
|
2855
|
+
created once and still reach the newest adapter at call time. That ref was
|
|
2856
|
+
written in the render body:
|
|
2857
|
+
|
|
2858
|
+
```
|
|
2859
|
+
const defaultAdapter = useRef(createLocalStorageAdapter());
|
|
2860
|
+
const adapterRef = useRef(adapter ?? defaultAdapter.current);
|
|
2861
|
+
adapterRef.current = adapter ?? defaultAdapter.current; // during render
|
|
2862
|
+
```
|
|
2863
|
+
|
|
2864
|
+
which `react-hooks/refs` flags on three counts. A render React discards or
|
|
2865
|
+
replays — StrictMode, a Suspense retry, a concurrent interruption — still
|
|
2866
|
+
performed that write, so a save could be routed through an adapter belonging to
|
|
2867
|
+
a render that never committed.
|
|
2868
|
+
|
|
2869
|
+
The write now happens in `useInsertionEffect`, and the default adapter comes
|
|
2870
|
+
from `useMemo` instead of a ref read during render.
|
|
2871
|
+
|
|
2872
|
+
**Timing is preserved for every legal call site.** Insertion effects run in the
|
|
2873
|
+
mutation phase — before every layout effect in the tree, before paint, and
|
|
2874
|
+
before any event handler can fire — so a changed `adapter` prop is in place
|
|
2875
|
+
before anything that may legally invoke these methods can observe it. This is
|
|
2876
|
+
deliberately *not* `useEffect` (which lands after paint) or `useLayoutEffect`
|
|
2877
|
+
(a child's layout effects run before its parent's); either would route a call
|
|
2878
|
+
made earlier in the same commit to the previous adapter. The only window that
|
|
2879
|
+
changed is a read during the render phase itself, which no legal consumer has:
|
|
2880
|
+
`save`/`load`/`list`/`remove` are side effects and are never callable during
|
|
2881
|
+
render.
|
|
2882
|
+
|
|
2883
|
+
Also fixed in passing, on the same lines: `useRef(createLocalStorageAdapter())`
|
|
2884
|
+
invoked the factory on **every** render and discarded all but the first result.
|
|
2885
|
+
The `useMemo` runs it once. The adapter is a stateless facade over
|
|
2886
|
+
`localStorage` and its identity is never exposed, so this is unobservable
|
|
2887
|
+
beyond the saved work.
|
|
2888
|
+
|
|
2889
|
+
No API, signature or observable behaviour change for any supported call site.
|
|
2890
|
+
- 3fe6463: `SchemaRenderer`: a non-object `props` bag is no longer object-spread into
|
|
2891
|
+
indexed React props (objectui#6752).
|
|
2892
|
+
|
|
2893
|
+
A node written `{ type: 'card', props: 'not-a-bag' }` reached `createElement`
|
|
2894
|
+
carrying nine React props named `0` through `8`, one per character, because
|
|
2895
|
+
`{ ...'not-a-bag' }` enumerates a string's character indices. Nothing threw and
|
|
2896
|
+
nothing was logged, so the symptom — a component handed nine props it never
|
|
2897
|
+
declared — sat a long way from the `props` value that caused it. Measured
|
|
2898
|
+
through the real `SchemaRenderer`; the in-repo corpus has no such node today, so
|
|
2899
|
+
this is a latent shape rather than a live failure.
|
|
2900
|
+
|
|
2901
|
+
The canonical `properties` branch already carried the wider guard. Its comment
|
|
2902
|
+
claimed the guard was hoist-specific, and that turned out not to survive
|
|
2903
|
+
measurement: ablating it leaves the indexed keys the hoist puts on the node
|
|
2904
|
+
completely unchanged, and moves only whether `schema.properties` still holds the
|
|
2905
|
+
value the author wrote. The reason is channel-independent — a degenerate value
|
|
2906
|
+
must not have its shape reinterpreted by an object spread — so `props` now
|
|
2907
|
+
carries the same guard, both bags share one `isConfigBag` predicate, and the
|
|
2908
|
+
`properties` comment states the measured reason instead of the old one.
|
|
2909
|
+
|
|
2910
|
+
Both sites that spread the bag are covered: the evaluation memo (so
|
|
2911
|
+
`schema.props` keeps the authored value) and `propsWithoutCanonicalKeys` (so the
|
|
2912
|
+
`createElement` spread does not re-enumerate it). A degenerate bag now
|
|
2913
|
+
contributes no keys, and the authored value is passed through unmangled on the
|
|
2914
|
+
React prop named `props`.
|
|
2915
|
+
|
|
2916
|
+
Unchanged: a normal object `props` (still evaluated, still spread per key),
|
|
2917
|
+
objectui#5123's two-bag precedence, the `properties` hoist, and objectui#6708's
|
|
2918
|
+
dropped-`props` diagnostic.
|
|
2919
|
+
- 31ab372: A degenerate `properties` bag no longer reaches the element as indexed React
|
|
2920
|
+
props (objectui#6760).
|
|
2921
|
+
|
|
2922
|
+
`properties` is the spec spelling of a node's config bag. The hoist that copies
|
|
2923
|
+
`properties.*` onto the node's top level walked it with `Object.entries`
|
|
2924
|
+
unconditionally, so a non-object value was enumerated rather than skipped:
|
|
2925
|
+
measured on `c6732825d`, `{ type, properties: 'not-a-bag' }` reached the element
|
|
2926
|
+
as nine React props named `0` … `8`, and `properties: ['x', 'y']` as `0`, `1`.
|
|
2927
|
+
Nobody authored those keys — they are the walk's reading of a string's character
|
|
2928
|
+
indices.
|
|
2929
|
+
|
|
2930
|
+
The hoist now asks the same `isConfigBag` question the evaluation memo and the
|
|
2931
|
+
`props` bag already ask (objectui#6752, objectui#6761). Of the two arms the card
|
|
2932
|
+
left open, this is "guard the hoist" rather than "declare that the hoist may
|
|
2933
|
+
enumerate anything", because objectui#5123 ruled that a key gets one answer
|
|
2934
|
+
whichever channel reads it — and the alternative would have answered one
|
|
2935
|
+
authored mistake two ways, with the reinterpreting half falling on the canonical
|
|
2936
|
+
spelling while the quiet half fell on its legacy alias.
|
|
2937
|
+
|
|
2938
|
+
Nothing else moves: a real object bag hoists exactly as before (including the
|
|
2939
|
+
`type`/`id` keys the hoist has always refused to copy), the authored
|
|
2940
|
+
`properties` value still reaches renderers on both channels unchanged, and
|
|
2941
|
+
`properties: 42` / `properties: true` were already contributing no keys.
|
|
2942
|
+
- 67fc3b0: `useETagCache`, `useGlobalUndo` and `useOffline` stop writing their config refs
|
|
2943
|
+
during render (objectui#6797).
|
|
2944
|
+
|
|
2945
|
+
Each of the three kept a "latest value" ref that was assigned in the render
|
|
2946
|
+
body, one `react-hooks/refs` warning apiece on this base
|
|
2947
|
+
(`useETagCache.ts:204`, `useGlobalUndo.ts:57`, `useOffline.ts:262` — all three
|
|
2948
|
+
`Cannot update ref during render`, all three the WRITE only; unlike
|
|
2949
|
+
`useSchemaPersistence` none of them also READ a ref during render). A ref
|
|
2950
|
+
written in the render body is also written by renders React discards or
|
|
2951
|
+
replays — StrictMode's double render, a Suspense retry, a concurrent
|
|
2952
|
+
interruption — so a tree that never committed could publish its config to
|
|
2953
|
+
callbacks that outlive it.
|
|
2954
|
+
|
|
2955
|
+
The write moved to `useInsertionEffect` in all three, but that shape was chosen
|
|
2956
|
+
per hook rather than carried over, because what each ref protects differs:
|
|
2957
|
+
|
|
2958
|
+
- **`useETagCache`** — five resolved config scalars read by five `useCallback`s
|
|
2959
|
+
with `[]` deps whose identity is part of the published result. Re-keying them
|
|
2960
|
+
on the config values would have changed `fetchWithETag`'s identity whenever a
|
|
2961
|
+
caller's `ttl` moved, re-firing consumer effects keyed on it, so the ref
|
|
2962
|
+
stays.
|
|
2963
|
+
- **`useGlobalUndo`** — the whole options bag. Every caller passes a fresh
|
|
2964
|
+
inline literal with inline `onUndo` / `onRedo` closures, and the keydown
|
|
2965
|
+
effect is keyed on `undo` / `redo`, so the ref is the only thing keeping
|
|
2966
|
+
those two stable while still reaching the newest callbacks.
|
|
2967
|
+
- **`useOffline`** — `config.sync`, read by one caller (`sync`) that is
|
|
2968
|
+
*already* unstable (deps `[enabled, queue]`). Here the ref protects RETAINED
|
|
2969
|
+
closures rather than an identity: the auto-sync effect deliberately captures
|
|
2970
|
+
a `sync` and fires it 100ms later, and that closure must still see the newest
|
|
2971
|
+
`batchSize`. Dropping the ref for a `syncConfig?.batchSize` dep would have
|
|
2972
|
+
changed what that retained closure reads, so it was rejected.
|
|
2973
|
+
|
|
2974
|
+
`useInsertionEffect` runs in the mutation phase — ahead of every layout effect,
|
|
2975
|
+
ref attachment and paint — so the only window any of the three defers is the
|
|
2976
|
+
render phase itself, where none of the affected callbacks is legally callable.
|
|
2977
|
+
`useEffectEvent` would be the idiomatic answer but is React 19.2+, and this
|
|
2978
|
+
package's peer range starts at React 18.
|
|
2979
|
+
|
|
2980
|
+
**No behavioural change is claimed for callers that exist today**: reverting any
|
|
2981
|
+
of the three implementations leaves the whole suite green, and the new pins pass
|
|
2982
|
+
against the old code and the new code alike. They guard the next edit — each
|
|
2983
|
+
file's discriminating pin fails under both `useEffect` and `useLayoutEffect`.
|
|
2984
|
+
- 33a3b3c: `useETagCache` builds its config object once per hook instance instead of once
|
|
2985
|
+
per render (objectui#6817).
|
|
2986
|
+
|
|
2987
|
+
`useRef({ enabled, storage, storagePrefix, maxEntries, ttl })` evaluated that
|
|
2988
|
+
literal on **every** render and kept only the first result, so every later
|
|
2989
|
+
render allocated a five-key object that was discarded. It now comes from a
|
|
2990
|
+
`useMemo` keyed on the five values, which is also what the ref's
|
|
2991
|
+
`useInsertionEffect` write publishes.
|
|
2992
|
+
|
|
2993
|
+
`patch`, not `minor`: nothing a published consumer can observe changes. The
|
|
2994
|
+
public shape, the returned callbacks' identities and the values the stable
|
|
2995
|
+
`[]`-deps callbacks read off the ref are all unchanged — the object's identity
|
|
2996
|
+
is private to the hook, so the only difference is the allocation that no longer
|
|
2997
|
+
happens. Same pattern PR objectui#6796 repaired in `useSchemaPersistence`; this
|
|
2998
|
+
is the half of that class the `react-hooks/refs` rule structurally cannot see,
|
|
2999
|
+
which is why it needed a test rather than a lint fix.
|
|
3000
|
+
- 3ed3eec: fix(plugin-form,plugin-list,plugin-view,react): read `SchemaRendererContext` as declared, not through a cast to `any` (objectui#7209)
|
|
3001
|
+
|
|
3002
|
+
Six reads of the renderer context erased its type. Five cast at the read —
|
|
3003
|
+
`useContext(SchemaRendererContext as React.Context<any>)` in the
|
|
3004
|
+
`embeddable-form` and `object-master-detail-form` bridges, in `ListViewBlock`
|
|
3005
|
+
and in `useResolvedDataSource`, and the same cast spelled with a bare
|
|
3006
|
+
`Context<any>` in `useElementDataSource` — and the `object-view` renderer's
|
|
3007
|
+
module re-declared the imported context as a `React.Context<any>`. Through
|
|
3008
|
+
that cast a read of a member the context does not declare compiled clean,
|
|
3009
|
+
which is how the phantom `ctx.formValues ?? ctx.data` channel retired by
|
|
3010
|
+
objectui#7206 went unnoticed. Each of these reads now sees `SchemaRendererContextType` as
|
|
3011
|
+
`@object-ui/react` declares it, so such a read is a compile error on the day it
|
|
3012
|
+
is written.
|
|
3013
|
+
|
|
3014
|
+
No runtime behaviour changes. Removing the casts surfaced no read of an
|
|
3015
|
+
undeclared member; it surfaced two places where the declared `null` ("no
|
|
3016
|
+
adapter bound") met a prop that does not admit it:
|
|
3017
|
+
|
|
3018
|
+
- `embeddable-form` handed that `null` to `EmbeddableForm`'s optional
|
|
3019
|
+
`dataSource`. It now collapses it to `undefined`, as
|
|
3020
|
+
`object-master-detail-form` already did; `EmbeddableForm` only ever tests the
|
|
3021
|
+
adapter for truthiness, so the two absences behave the same.
|
|
3022
|
+
- `object-view` / `view` hand it to `ObjectViewProps.dataSource`, which is
|
|
3023
|
+
declared required and stays so (objectui#7842). That one value now carries a
|
|
3024
|
+
narrow, commented assertion instead of the whole context being erased; the
|
|
3025
|
+
value passed is the same as before.
|
|
3026
|
+
|
|
3027
|
+
The published signatures of `useResolvedDataSource` and `useElementDataSource`
|
|
3028
|
+
do not move.
|
|
3029
|
+
- 2ce2612: A record-page URL now names the object the clicked rows actually came from, in
|
|
3030
|
+
`ObjectTree` and `ObjectCalendar`.
|
|
3031
|
+
|
|
3032
|
+
`useNavigationOverlay` builds `/{objectName}/record/{id}` out of whatever it is handed,
|
|
3033
|
+
and both components handed it the bare top-level `schema.objectName` while resolving
|
|
3034
|
+
their own rows through the ruled record-source ladder (`data`, then
|
|
3035
|
+
`staticData`, then `objectName`). `77cb489b4` published `objectName` as that ladder's
|
|
3036
|
+
THIRD RUNG and not as a parallel "page object" concept, so a block has exactly one
|
|
3037
|
+
record source — and a row fetched through `data.object` whose click built
|
|
3038
|
+
`/{schema.objectName}/record/{id}` named a record that the URL's own object does not
|
|
3039
|
+
contain.
|
|
3040
|
+
|
|
3041
|
+
Two shapes change, both toward the object the rows came from:
|
|
3042
|
+
|
|
3043
|
+
- a block carrying **both** bindings navigated to the top-level key and now navigates to
|
|
3044
|
+
`data.object`;
|
|
3045
|
+
- a **data-only** block had no name to build a URL from at all, so the hook took its
|
|
3046
|
+
`/{id}` leg — an unrouted path that paints a blank page — and now builds the routed
|
|
3047
|
+
record URL.
|
|
3048
|
+
|
|
3049
|
+
`ObjectCalendar` is where the divergence was plainest: on one click it resolved the
|
|
3050
|
+
detail drawer through the ladder and the navigation URL through the top-level key. The
|
|
3051
|
+
URL now reuses the very `schemaObjectName` that already keys the calendar's record query
|
|
3052
|
+
and its `$expand` derivation, so query, drawer and URL agree by construction.
|
|
3053
|
+
|
|
3054
|
+
**Nothing else moves.** Both converted sites keep a site-local `?? schema.objectName`
|
|
3055
|
+
tail for the off-contract `data: { provider: 'object' }` that carries no `object`
|
|
3056
|
+
(`ViewDataSchema` declares it required) — the same tail `ObjectTree`'s `headerObjectName`
|
|
3057
|
+
already carries, and the same conservatism `b041b9c0c` applied when it published the
|
|
3058
|
+
shared reader. `useNavigationOverlay`'s own signature is unchanged: it still takes an
|
|
3059
|
+
`objectName`, and only what callers hand it has changed.
|
|
3060
|
+
|
|
3061
|
+
The hook's `@example` stops prescribing `objectName: schema.objectName`. That prose is
|
|
3062
|
+
why there were copies to convert at all — component authors copied the divergence out of
|
|
3063
|
+
the documentation, correctly, as written — so it now points at
|
|
3064
|
+
`resolveRecordSourceObjectName` and says explicitly that a caller with no data config
|
|
3065
|
+
has nothing above rung three and should keep passing `schema.objectName`.
|
|
3066
|
+
|
|
3067
|
+
`ObjectKanban` is deliberately **not** converted: it has no data config, no
|
|
3068
|
+
`getDataConfig`, and its `data` is a raw row array rather than a `ViewData` binding, so
|
|
3069
|
+
`schema.objectName` already IS its record source and its board, drawer and URL already
|
|
3070
|
+
agree.
|
|
3071
|
+
- d1bebb0: The unevaluated-expression diagnostic now lists `properties` among the channels
|
|
3072
|
+
that evaluate and read back, matching the sibling `props`-bag diagnostic that
|
|
3073
|
+
tells authors to write their keys there.
|
|
3074
|
+
|
|
3075
|
+
Both messages are dev-build diagnostics in `SchemaRenderer`, and one node can
|
|
3076
|
+
trip both. They disagreed: `propsBagDiagnostic` said *"`props` is NOT hoisted
|
|
3077
|
+
onto the node — only `properties.*` is … Write them under `properties`
|
|
3078
|
+
instead"*, while `unevaluatedExpression` enumerated *"channels that do evaluate
|
|
3079
|
+
and read back today"* as `content` or host-side resolution — omitting
|
|
3080
|
+
`properties`. An author who hit both was told to use a channel the other message
|
|
3081
|
+
said did not work.
|
|
3082
|
+
|
|
3083
|
+
The enumeration was the wrong half, established by measurement rather than by
|
|
3084
|
+
reading the `COMPAT` label on the hoist. `properties` has no retirement on
|
|
3085
|
+
record: `@objectstack/spec@17.2.0` calls the vocabulary carried there *"ALIVE —
|
|
3086
|
+
this is not dead surface to retire under ADR-0049"*, keeps
|
|
3087
|
+
`PageComponentSchema.properties` as the open carrier on purpose, gates it at the
|
|
3088
|
+
authoring door, and tombstones no part of it via `retiredKey()`. In this repo
|
|
3089
|
+
`props`, not `properties`, is the spelling annotated as the legacy alias.
|
|
3090
|
+
|
|
3091
|
+
Diagnostic text only — no evaluation, hoist or schema behaviour changed.
|
|
3092
|
+
- 7fb22a1: The published visibility diagnostic no longer tells an author that `app` is a bound
|
|
3093
|
+
expression-scope root (objectui#8155 follow-up).
|
|
3094
|
+
|
|
3095
|
+
`@object-ui/react`'s `SCOPE_TIER_ADVICE['app-shell']` — the paragraph
|
|
3096
|
+
`reportUnresolvableVisibilityPredicate` prints in production when a predicate cannot be
|
|
3097
|
+
evaluated — read "App-shell predicates bind `current_user` … plus `app` and `features`".
|
|
3098
|
+
objectui#8155 removed `app` from `buildExpressionScope`, so that sentence was printed at
|
|
3099
|
+
exactly the moment a saved `app.*` predicate faulted, and it answered "why did my
|
|
3100
|
+
predicate not resolve?" by naming the root that is the reason. The line now names the
|
|
3101
|
+
five roots the provider really binds: `current_user`, its `user` / `ctx.user` / `os.user`
|
|
3102
|
+
aliases, and `features`.
|
|
3103
|
+
|
|
3104
|
+
**Swept as a class, not as two coordinates.** Every other place in this tree that stated
|
|
3105
|
+
`app` was a bound expression-scope root is corrected in the same change — the diagnostic
|
|
3106
|
+
copy and its byte-pin, the ambient-scope docblocks in `@object-ui/react`
|
|
3107
|
+
(`SchemaRenderer`, `useExpression`), `@object-ui/core` (`ActionRunner.ParamDef.visible`,
|
|
3108
|
+
`RowPredicateOptions.scope`), `@object-ui/components` (`form.tsx`, `containers.tsx`),
|
|
3109
|
+
`@object-ui/plugin-detail`, `@object-ui/plugin-form` (docblock and README),
|
|
3110
|
+
`@object-ui/app-shell` and the console app, plus fourteen test fixtures that transcribed
|
|
3111
|
+
the old bag with an `app` key. The fixtures in `@object-ui/app-shell` now call
|
|
3112
|
+
`buildExpressionScope` instead of transcribing it, so that pair cannot drift again.
|
|
3113
|
+
|
|
3114
|
+
Nothing that was true before objectui#8155 changed: the `app` prop on
|
|
3115
|
+
`ExpressionProvider` and the `app` field on its React context value are untouched (they
|
|
3116
|
+
were never CEL roots), and no root other than `app` was added to or removed from any
|
|
3117
|
+
message, bag or fixture.
|
|
3118
|
+
|
|
3119
|
+
Two release-bound corrections travel with it. The producer-side card objectstack#16420
|
|
3120
|
+
was closed `not_planned` on 2026-09-07 by the same ruling that removed the root, not left
|
|
3121
|
+
open; and the consequence of a stale `app.*` predicate is **not** uniformly "fails open" —
|
|
3122
|
+
it is per surface, and the changeset that carries the removal now states the seven
|
|
3123
|
+
measured directions instead of one.
|
|
3124
|
+
- ad66d79: Stop binding an ambient `data` root on record surfaces, so a `data.*` predicate faults
|
|
3125
|
+
loudly instead of resolving against the host's bag (objectui#8166, ruled 2026-09-10).
|
|
3126
|
+
|
|
3127
|
+
**User-visible behaviour change, at RUNTIME and at DEBUG time — not at authoring time.**
|
|
3128
|
+
Nothing an author sees while writing a predicate moves: the authoring lint's accept set
|
|
3129
|
+
lives in `@objectstack/formula`'s `SCOPE_ROOTS`, which still contains `data`, and
|
|
3130
|
+
`data.status == 'x'` still lints clean at `scope: 'record'`. Splitting that list per
|
|
3131
|
+
scope is the producer-side half and is not this repo's to make. What changes is what
|
|
3132
|
+
the runtime does with such a predicate once it is saved.
|
|
3133
|
+
|
|
3134
|
+
**What was wrong.** `83fe6e741` (Phase 2 of the objectui#5330 canon) retired `data.*`
|
|
3135
|
+
on runtime record surfaces — the row is bound as `record.*` and nothing else. But
|
|
3136
|
+
`@object-ui/app-shell`'s `buildExpressionScope` kept binding an ambient `data`, so a
|
|
3137
|
+
predicate the linter had waved through also *resolved* at runtime, against that bag
|
|
3138
|
+
rather than against the row. Measured on `main` before this change, one authored
|
|
3139
|
+
`visibleWhen: "data.status == 'x'"` meant three different things:
|
|
3140
|
+
|
|
3141
|
+
- on every `ExpressionProvider` mount and on `RecordFormPage`'s own evaluator, the
|
|
3142
|
+
ambient `data` was `{}`, so the engine answered `[runtime] No such key: status` — a
|
|
3143
|
+
fault, warned once, and the field-rule fallback applied (fail-open for `visibleWhen`);
|
|
3144
|
+
- on `AppContent`'s field-list evaluator for the global record-form modal in EDIT mode
|
|
3145
|
+
the ambient `data` was **the record being edited**, so the predicate resolved, with no
|
|
3146
|
+
diagnostic at all, off the wrong layer's object — an author testing there would have
|
|
3147
|
+
seen it "work";
|
|
3148
|
+
- in CREATE mode on that same modal it fell back to the first case.
|
|
3149
|
+
|
|
3150
|
+
**What changes.** `buildExpressionScope` no longer accepts or binds `data`, and the two
|
|
3151
|
+
imperative call sites (`AppContent`, `RecordFormPage`) stop passing one. A `data.*`
|
|
3152
|
+
predicate on any app-shell surface now produces the engine's own verdict — `[type]
|
|
3153
|
+
Unknown variable: data` — on both diagnostic channels (the one-time `console.warn` and
|
|
3154
|
+
the `onFault` passback), which is the same verdict the server gives the same string and
|
|
3155
|
+
the same shape `app` has produced since objectui#8155.
|
|
3156
|
+
|
|
3157
|
+
**The fault is loud, not fatal.** The verdict a faulting predicate resolves to is
|
|
3158
|
+
unchanged: `visibleWhen` still fails OPEN, `readonlyWhen` / `requiredWhen` still fail
|
|
3159
|
+
permissive (`@object-ui/core`'s `fieldRules.ts`; the direction is objectui#8069's open
|
|
3160
|
+
question, not this change's). So a record form renders exactly as before except that the
|
|
3161
|
+
console now names the root. Nothing throws.
|
|
3162
|
+
|
|
3163
|
+
**Migration.** Rewrite `data.foo` as `record.foo` on any runtime record surface — the
|
|
3164
|
+
canonical spelling since `83fe6e741`, and the only one that reaches the row. The
|
|
3165
|
+
metadata-admin designer is unaffected: its `data` is the DRAFT under edit (ADR-0089 D3,
|
|
3166
|
+
`CANONICAL_ROOT_BY_LAYER` = `{ runtime: 'record', metadata: 'data' }`), bound by
|
|
3167
|
+
`views/metadata-admin/predicate.ts` through its own builder, which takes only the
|
|
3168
|
+
identity roots from this bag and assigns its own `data` last.
|
|
3169
|
+
|
|
3170
|
+
`@object-ui/react` carries a docblock correction only: the `app-shell` tier paragraph in
|
|
3171
|
+
`utils/visibilityDiagnostic.ts` described the bag this change edits. `@object-ui/components`
|
|
3172
|
+
carries no runtime change at all — three form/predicate tests hand-transcribe the app-shell
|
|
3173
|
+
bag (importing it would invert the package dependency) and each literal carried the removed
|
|
3174
|
+
`data: {}`; the transcriptions are corrected so they cannot go on describing a scope that no
|
|
3175
|
+
longer exists.
|
|
3176
|
+
- e36acd4: `SchemaRenderer` no longer hands an authored `objectFields` to any component as
|
|
3177
|
+
a React prop (objectui#8818, completing maintainer decision batch #70,
|
|
3178
|
+
objectui#7742).
|
|
3179
|
+
|
|
3180
|
+
`objectFields` is the object's field catalogue. The predicate layer reads it to
|
|
3181
|
+
decide how a conditional-formatting rule compares a relation field. Batch #70
|
|
3182
|
+
ruled it a runtime prop that a host injects, never authorable metadata, but the
|
|
3183
|
+
renderer's fixed metadata strip list did not name it. So a node that authored
|
|
3184
|
+
the key had it spread onto whatever component its type resolved to. It now
|
|
3185
|
+
joins that strip list, and all three authoring spellings are dropped: a
|
|
3186
|
+
top-level `objectFields`, `properties: { objectFields }`, and the legacy
|
|
3187
|
+
`props: { objectFields }` alias, which is spread separately and is stripped
|
|
3188
|
+
too.
|
|
3189
|
+
|
|
3190
|
+
Runtime hosts are unaffected. Pass the catalogue as a React prop, either on
|
|
3191
|
+
the component directly (as `ObjectKanban` does for the board it mounts) or as
|
|
3192
|
+
an `objectFields` prop on `SchemaRenderer` itself. `SchemaRenderer` spreads its
|
|
3193
|
+
own props last, so a host prop still arrives. No schema face ever declared the
|
|
3194
|
+
key, so no published type or validator changes.
|
|
3195
|
+
- b9d47ec: Resolve filter placeholders on the drill "escape hatch", so one drill has one scope
|
|
3196
|
+
whatever `drillDown.target` says (objectui#9022).
|
|
3197
|
+
|
|
3198
|
+
Every widget composes its drill filter from the RAW authored filter, so
|
|
3199
|
+
`{current_user_id}`, `{current_org_id}` and the relative-date macros were still
|
|
3200
|
+
literals when they reached the host's `openRecordList`. The in-place drawer arm never
|
|
3201
|
+
had the defect — the `object-data-table` it renders resolves the filter in its own
|
|
3202
|
+
fetch — so only the navigate arm, and the "Open in list →" button in the drawer header,
|
|
3203
|
+
wrote the placeholder into the URL. `filter[close_date][gte]={current_quarter_start}`
|
|
3204
|
+
parses back on the read side as an ordinary string comparand, so the destination list
|
|
3205
|
+
matched nothing, silently, and in disagreement with the chart bar the user had just
|
|
3206
|
+
clicked (that chart scopes its own bars by the RESOLVED value).
|
|
3207
|
+
|
|
3208
|
+
`useOpenRecordList` now expands both placeholder vocabularies against the session scope
|
|
3209
|
+
it already sits inside, immediately before serialization. It is the single host handler
|
|
3210
|
+
every escape hatch funnels through — the chart's navigate arm and its own header
|
|
3211
|
+
button, `DrillDownDrawer`'s navigate arm, and `OpenInListButton` — so both widget
|
|
3212
|
+
families are covered by one resolution rather than N widget seams. A drill carrying no
|
|
3213
|
+
placeholder produces a byte-identical URL: both vocabularies substitute whole-string
|
|
3214
|
+
`{token}` values only.
|
|
3215
|
+
|
|
3216
|
+
The `DrillNavigationValue.openRecordList` contract now states that resolving is the
|
|
3217
|
+
host implementation's job, since an out-of-repo host wiring the same context would
|
|
3218
|
+
otherwise reproduce the split.
|
|
3219
|
+
- efc1c9c: **A node-gate predicate parked under `props` is now REFUSED BY NAME on the console, instead of gating nothing in silence (objectui#9108).**
|
|
3220
|
+
|
|
3221
|
+
A node may spell its config bag `properties` (the spec spelling) or `props` (the annotated legacy alias). `SchemaRenderer` hoists `properties.*` onto the node; nothing copies `props.*`, and both node gates read the post-hoist node — so a predicate that arrived under the alias was never one of the keys either gate could see. Measured at node level, four rows: `props: { visible: false }` and `props: { hidden: true }` both RENDERED, while `properties: { visible: false }` and `properties: { hidden: true }` each hid correctly. Fail-**open** and silent by construction: a gate that never bit renders exactly like a gate that said yes, so no user and no screenshot can find it.
|
|
3222
|
+
|
|
3223
|
+
**No verdict moves, and that is the ruled outcome rather than a limitation.** The alias is refused, ⛔ not honoured: nothing is hoisted, `schema.<KEY>` stays undefined for a renderer declared as `({ schema })`, and every element receives the byte-identical props bag it received before. What changes is that the eight node-gate predicate keys (`visibleWhen` / `visible` / `visibleOn` / `visibility` / `hidden` / `hiddenOn` / `disabled` / `disabledOn`) are named on the console when they are parked there, with the migration that fixes them. The maintainer ruling of 2026-09-13 closed the honour arm (PR objectui#9144) on the ground that honouring the alias would have made *"8 predicate keys work while the rest stayed silently dropped — and partly working is harder to learn from than not working"*.
|
|
3224
|
+
|
|
3225
|
+
The line is `console.error`, in development **and in production** — the same posture objectui#6038 gave the unresolvable-predicate report, because a node gate that has stopped biting in production is exactly the defect that must not be able to sit live and undiscovered. It is rate-limited to one line per distinct authoring bug for the lifetime of the page. One measured carve-out: `disabled` on a bag-reading `element:*` node is honoured by that renderer itself (`disabled={props.disabled || running}`), so it is not refused there.
|
|
3226
|
+
- b234a84: `ElementDataSourceGate`: a row cap the contract refuses is no longer counted as
|
|
3227
|
+
"the component authored one" (objectui#9899).
|
|
3228
|
+
|
|
3229
|
+
The shared gate decides, per key, whether a component's own value should suppress
|
|
3230
|
+
the one a bound saved view supplies. For `columns` it has always decided that by
|
|
3231
|
+
CONTENT — an authored-but-empty list yields, because "supplying the columns is the
|
|
3232
|
+
reason a view was named". For the row cap it decided by PRESENCE, so a value
|
|
3233
|
+
`@objectstack/spec` refuses — `pagination.pageSize: 0`, a negative, a fraction —
|
|
3234
|
+
counted as authorship and suppressed the view's legitimate cap.
|
|
3235
|
+
|
|
3236
|
+
**What an author saw.** Measured end to end on `list-view` rather than inferred: a
|
|
3237
|
+
component carrying `pagination.pageSize: 0` under a view supplying `7` put
|
|
3238
|
+
`$top: 100` on the wire — the renderer's own default, because that renderer already
|
|
3239
|
+
refuses a non-positive page size. So the view's cap never reached the query and the
|
|
3240
|
+
read was WIDER than the view asked for, in the one direction a named view exists to
|
|
3241
|
+
prevent. The same branch feeds the flat `limit` key that `object-kanban`,
|
|
3242
|
+
`object-timeline`, `record:related_list` and the form panels read.
|
|
3243
|
+
|
|
3244
|
+
After this change the view's cap is written, and the same fixture puts `$top: 7` on
|
|
3245
|
+
the wire.
|
|
3246
|
+
|
|
3247
|
+
**The diagnostic moved with the decision.** Before this change the consuming
|
|
3248
|
+
renderer was the one telling the author their `0` had been dropped. Now that the
|
|
3249
|
+
gate substitutes a usable cap, that renderer correctly sees nothing to report — so
|
|
3250
|
+
the gate states it instead, once per declaration, on the same `console.warn` channel
|
|
3251
|
+
the renderers use. It names the refused value, the key it was written on, and the
|
|
3252
|
+
cap used in its place. Exactly one message is emitted before and after; this is not
|
|
3253
|
+
a silent substitution, and it is not a clamp — the author's number is refused, not
|
|
3254
|
+
repaired.
|
|
3255
|
+
|
|
3256
|
+
**Unchanged:** a legitimate authored cap still wins over the view's, and an explicit
|
|
3257
|
+
cap on the `dataSource` binding still wins over both.
|
|
3258
|
+
- befd40c: fix(react): the refused-`data` dev warning can be reset between tests
|
|
3259
|
+
|
|
3260
|
+
The dedupe `Set` behind the objectui#9571 / objectui#9758 diagnostic — "the
|
|
3261
|
+
authored `data` key was NOT passed to the component as a React prop" — lived in
|
|
3262
|
+
`SchemaRenderer.tsx` with nothing able to clear it, while every other dev
|
|
3263
|
+
diagnostic in this package's `utils/` directory pairs its `Set` with an exported
|
|
3264
|
+
test-only reset. `propsBagDiagnostic.ts` states the consequence in its own
|
|
3265
|
+
reset's docblock: the second test to assert the same warning reads the first
|
|
3266
|
+
test's dedupe entry and sees silence, "a green run that checked nothing". The
|
|
3267
|
+
absence direction is the dangerous one — a test asserting this warning did NOT
|
|
3268
|
+
fire for a `type#id` an earlier test already warned for is green whatever the
|
|
3269
|
+
renderer does, and it reports that green as coverage.
|
|
3270
|
+
|
|
3271
|
+
The `Set`, the emit and a new `__resetRefusedDataPropWarnings` now live in
|
|
3272
|
+
`utils/refusedDataPropDiagnostic.ts`, beside the siblings whose idiom they
|
|
3273
|
+
follow. `index.ts` re-exports `SchemaRenderer.js` with a star, so a reset
|
|
3274
|
+
exported from where the `Set` used to sit would have become a member of this
|
|
3275
|
+
package's published entry for the sake of a test affordance; the entry is
|
|
3276
|
+
unchanged by this change, and so are the warning's text, its dedupe key and both
|
|
3277
|
+
`__DEV__`-gated call sites.
|
|
3278
|
+
|
|
3279
|
+
The pin renders one `type#id` across three `it()` blocks — the warning fires,
|
|
3280
|
+
fires again in the next block, then draws silence for host-supplied rows beside
|
|
3281
|
+
a control that proves the channel could still speak. Ablating the reset's body
|
|
3282
|
+
turns the last two red; the absence assertion alone stays green under it, which
|
|
3283
|
+
is the defect it exists to refuse.
|
|
3284
|
+
- 98c3a74: Fix `createLazyPlugin`'s JSDoc example, which taught a call that does not compile.
|
|
3285
|
+
|
|
3286
|
+
The `@example` block passed `() => import('@object-ui/plugin-grid')` as the
|
|
3287
|
+
`importFn`. ObjectUI plugin packages export their components by name and have no
|
|
3288
|
+
`default`, so that call supplies the module namespace object — rejected by the
|
|
3289
|
+
compiler (`Property 'default' is missing`) and, at runtime, handed to
|
|
3290
|
+
`React.lazy` as the component. The examples now unwrap the named export via the
|
|
3291
|
+
`async` spelling, which is the form that actually type-checks. This ships in the
|
|
3292
|
+
published `.d.ts`, so it is what editors show on hover.
|
|
3293
|
+
- 9850c6e: `SchemaRenderer`'s node visibility gate now emits a dev-only warning when a
|
|
3294
|
+
predicate reads `data.*` and the data-source adapter cannot answer that read, so
|
|
3295
|
+
an author who wrote `properties: { visible: "data.status == 'draft'" }` sees the
|
|
3296
|
+
constant they authored instead of shipping it (objectui#5687).
|
|
3297
|
+
|
|
3298
|
+
At the node tier `data` is the data-source **adapter** — the object `${data.total}`
|
|
3299
|
+
in a props bag resolves against — and it has never been the row. A predicate
|
|
3300
|
+
written with the deprecated `data.*` spelling therefore resolves `undefined ==
|
|
3301
|
+
'draft'`, which is a perfectly good `false`: the block is hidden on every row, and
|
|
3302
|
+
because it does not throw, the unresolvable-predicate reporter added for
|
|
3303
|
+
objectui#5454 never fired. Measured on this base, the same predicate written as a
|
|
3304
|
+
`{ dialect: 'cel' }` envelope *does* throw and *was* already reported — so whether
|
|
3305
|
+
an author heard about the identical mistake depended on which dialect they happened
|
|
3306
|
+
to write it in, which is the arbitrariness objectui#5454 existed to remove.
|
|
3307
|
+
|
|
3308
|
+
**No verdict changes and no interpolation changes** (maintainer ruling,
|
|
3309
|
+
2026-08-22, option A). The node tier keeps its documented `data` = adapter
|
|
3310
|
+
semantics; objectui#5330's row binding does not extend here. The evaluator's answer
|
|
3311
|
+
is returned exactly as computed and `${data.*}` interpolation is untouched — only
|
|
3312
|
+
the silence moved.
|
|
3313
|
+
|
|
3314
|
+
The report fires on a `data.*` read the bound adapter answers with `undefined`, not
|
|
3315
|
+
on the spelling. A genuine adapter read stays silent (`data.total > 0` against an
|
|
3316
|
+
adapter carrying `total`), a canonical `record.*` predicate stays silent, and a
|
|
3317
|
+
correctly-hiding gate stays silent. Dev builds only, `console.warn`, deduped per
|
|
3318
|
+
node type + key + predicate source — the same module, Set and lifecycle as the
|
|
3319
|
+
objectui#5454 reporter.
|
|
3320
|
+
|
|
3321
|
+
This loudness is temporary by design: it dissolves when objectui#5330's deprecation
|
|
3322
|
+
window for the `data.*` row spelling closes.
|
|
3323
|
+
- b2ea297: `ObjectSchemaMetadata` is now derived from `@objectstack/spec`'s `ServiceObject`
|
|
3324
|
+
instead of being a hand-written copy (objectui#5362; maintainer ruling
|
|
3325
|
+
2026-08-20: the object document type belongs to the spec).
|
|
3326
|
+
|
|
3327
|
+
What changes on the published type surface:
|
|
3328
|
+
|
|
3329
|
+
- **Gained:** the full spec object-document surface, including the three keys
|
|
3330
|
+
the runtime already reads but the old interface rejected as excess
|
|
3331
|
+
properties: `icon`, `titleFormat`, `listViews` (plus `pluralLabel`,
|
|
3332
|
+
`nameField`, `displayNameField`, `managedBy` as a spec key, and the rest of
|
|
3333
|
+
the spec document).
|
|
3334
|
+
- **Removed:** nine members the old interface declared that no objectui
|
|
3335
|
+
runtime code reads and the spec document does not know: `extends`,
|
|
3336
|
+
`triggers`, `primary_key`, `relationships`, `name_field` (the spec spelling
|
|
3337
|
+
is `nameField`), `soft_delete`, `audit_trail`, `version`, `cache`.
|
|
3338
|
+
`ObjectTrigger` and `ObjectRelationship` remain exported unchanged.
|
|
3339
|
+
- **Kept:** `editMode` — the one measured client-side member the runtime reads
|
|
3340
|
+
(`recordFormNavigation` / `AppContent`) — now declared on the new
|
|
3341
|
+
`ObjectSchemaClientExtensions` interface, which the derivation intersects.
|
|
3342
|
+
Note the spec's strict parse rejects `editMode` on published documents
|
|
3343
|
+
(`unrecognized_keys`); it is a client-type member only.
|
|
3344
|
+
|
|
3345
|
+
Spelling settlement: `listViews` (camelCase) is canonical — `list_views`
|
|
3346
|
+
appears nowhere in `@objectstack/spec` 17.2.0. Runtime read sites in
|
|
3347
|
+
`@object-ui/app-shell` and `@object-ui/react` keep a documented snake-spelling
|
|
3348
|
+
READ fallback for stored pre-settlement documents (that stock has never been
|
|
3349
|
+
censused — objectstack#7917); the CRUD guide and its pinned transcription now
|
|
3350
|
+
author the canonical spelling.
|
|
3351
|
+
- 4f14ad7: `SchemaRenderer`'s node visibility gate now also catches the `${…}`-templated
|
|
3352
|
+
spelling of the objectui#5454/#5687 diagnostics when it is written inside
|
|
3353
|
+
`properties`, e.g. `properties: { visible: "${data.status == 'draft'}" }`
|
|
3354
|
+
(objectui#5756).
|
|
3355
|
+
|
|
3356
|
+
The `properties.*` evaluation loop runs, and interpolates every `${…}` template it
|
|
3357
|
+
finds, **before** the visibility gate ever sees the value — so by the time the
|
|
3358
|
+
existing diagnostics ran, a template-spelled predicate had already collapsed into a
|
|
3359
|
+
plain boolean and there was no predicate text left to inspect. The bare-string
|
|
3360
|
+
spelling of the exact same gate (`properties: { visible: "data.status == 'draft'"
|
|
3361
|
+
}`) was unaffected — nothing interpolates a string with no `${` in it — and was
|
|
3362
|
+
already reported; only the template spelling was structurally invisible.
|
|
3363
|
+
|
|
3364
|
+
Reached by moving the diagnostic check to inside the `properties` evaluation loop,
|
|
3365
|
+
on the predicate's raw (pre-interpolation) text, gated on the key being one of the
|
|
3366
|
+
six visibility keys the render chain consults (`visibleWhen` / `visible` /
|
|
3367
|
+
`visibleOn` / `visibility` / `hidden` / `hiddenOn`) — a `properties.content`
|
|
3368
|
+
interpolation, or any other non-visibility key, is untouched and stays silent.
|
|
3369
|
+
|
|
3370
|
+
Reports only the key that actually **decides** the node's visibility, mirroring
|
|
3371
|
+
objectui#5454's own leg semantics (its reporter is likewise only ever invoked on
|
|
3372
|
+
the leg the chain's early-return sequence actually reaches): a `properties.visible`
|
|
3373
|
+
template that a co-declared `visibleWhen` outranks is not reported for deciding
|
|
3374
|
+
nothing.
|
|
3375
|
+
|
|
3376
|
+
**No verdict changes and no interpolation changes.** The diagnostic call's return
|
|
3377
|
+
value is discarded; the real verdict is still computed afterward, off the
|
|
3378
|
+
post-evaluation, post-hoist schema, by the same code path as before this change.
|
|
3379
|
+
Same two reporters as objectui#5454/#5687 (unresolvable-predicate / adapter-only-data
|
|
3380
|
+
predicate), same dedupe `Set`, same `console.warn` severity, same dev-only gate —
|
|
3381
|
+
only the silence moved, one render-step earlier.
|
|
3382
|
+
- f90b8fb: `toRenderableSchema`'s header now says the bridge is permanent, instead of instructing
|
|
3383
|
+
callers to remove it (objectui#4622).
|
|
3384
|
+
|
|
3385
|
+
No executable line changes — but the artifact is **not** unchanged, and that is worth
|
|
3386
|
+
stating plainly rather than rounding to "comment-only". This package builds with plain
|
|
3387
|
+
`tsc`, and nothing in its config chain sets `"removeComments"`, so the default `false` holds and the JSDoc
|
|
3388
|
+
is emitted into `dist/schema-input.js` as well as `dist/schema-input.d.ts` — it is both
|
|
3389
|
+
what an editor shows on hover at every call site and bytes that ship.
|
|
3390
|
+
|
|
3391
|
+
Measured by building the package the way the repo builds it, at both revisions:
|
|
3392
|
+
`dist/schema-input.js` grows from 1,486 to 2,377 bytes (1.45 KB to 2.32 KB), and from 850
|
|
3393
|
+
to 1,266 bytes gzipped (0.83 KB to 1.24 KB) — **+891 bytes raw, +416 gzipped**. All 19
|
|
3394
|
+
differing lines in the emitted file are JSDoc continuations and the three executable lines
|
|
3395
|
+
are byte-identical, so the growth is the paragraph and nothing else. The trade is
|
|
3396
|
+
deliberate: roughly 0.4 KB gzipped, against the five-hour `Build Docs` outage the old
|
|
3397
|
+
paragraph's instruction produced once already.
|
|
3398
|
+
|
|
3399
|
+
The old closing paragraph said the two competing repo-wide `SchemaNode` spellings "have
|
|
3400
|
+
not been reconciled" and that "when it lands, the call sites using this can go back to
|
|
3401
|
+
forwarding directly". Both halves went false when PR #4608 merged, and the second half is
|
|
3402
|
+
the harmful one: it is an instruction whose trigger condition has now fired, sitting
|
|
3403
|
+
directly above the function a future author is about to call.
|
|
3404
|
+
|
|
3405
|
+
The reconciliation (objectui#4580 / PR #4608) resolved the collision in favour of
|
|
3406
|
+
`@object-ui/types`' union — `@object-ui/core` now re-exports it rather than hand-declaring
|
|
3407
|
+
an interface — while `SchemaRenderer`'s prop stays deliberately narrow per objectui#4548
|
|
3408
|
+
ruling Q2 (`schema: BaseSchema | string | null | undefined`, no `number` / `boolean`). So
|
|
3409
|
+
a `SchemaNode` became *less* assignable to that prop, not more, and the bridge is a
|
|
3410
|
+
permanent crossing between two intentionally different types rather than scaffolding
|
|
3411
|
+
awaiting a merge.
|
|
3412
|
+
|
|
3413
|
+
Following the old instruction has a measured cost: five `apps/site` call sites were
|
|
3414
|
+
forwarding directly when PR #4608 landed, and `Build Docs` was red on `main` for roughly
|
|
3415
|
+
five hours until PR #4621 routed all five through this function (objectui#4617).
|
|
3416
|
+
|
|
3417
|
+
⚠️ **Dated note, 2026-10-01 — the prop's union widens — objectui#11364.** Later in this same release `SchemaRendererProps.schema` becomes `BaseSchema | AuthoringNode | string | null | undefined`, and `toRenderableSchema`'s return widens with it by reference. So the union spelled above (`schema: BaseSchema | string | null | undefined`) no longer describes the release as a whole. What it was quoted for still holds: the prop declares no `number` / `boolean`, so the bridge stays the crossing from `SchemaNode`. The rest of this entry is kept as the reading of this change.
|
|
3418
|
+
|
|
3419
|
+
⚠️ **Dated note, 2026-10-02 — the prop takes the declared-node union — objectui#11466.** At this change the prop was `BaseSchema | string | null | undefined` (and `BaseSchema | AuthoringNode | string | null | undefined` after objectui#11364's note above); now, later in this same release, it is `DeclaredNode | string | null | undefined`, and `SchemaNode`'s object arm is `DeclaredNode` too. The prop still declares no `number` / `boolean`, so the bridge stays the crossing from `SchemaNode`. The rest of this entry is kept as the reading of this change.
|
|
3420
|
+
- f53a8d0: fix(react): stop the form-view bridge silently dropping 18 spec keys
|
|
3421
|
+
|
|
3422
|
+
`spec-bridge/bridges/form-view.ts` promised (#2545) that "every serializable
|
|
3423
|
+
spec key is either mapped onto the `object-form` node or listed here with an
|
|
3424
|
+
explicit reason for being ignored". Measured against `@objectstack/spec` 17.2.0
|
|
3425
|
+
the promise was false for 18 keys, because the conformance test enforcing it ran
|
|
3426
|
+
its completeness loop over its own hand-written fixture rather than the
|
|
3427
|
+
contract's key set.
|
|
3428
|
+
|
|
3429
|
+
Seventeen of them now reach the node, at the destinations the receiving layer
|
|
3430
|
+
already reads:
|
|
3431
|
+
|
|
3432
|
+
- `FormViewSchema.buttons` / `.defaults` — `ObjectFormSchema` declares both and
|
|
3433
|
+
`ObjectForm` folds them at render (action-button visibility/labels, and
|
|
3434
|
+
create-mode initial values).
|
|
3435
|
+
- `FormSection.pane` — explicit split-pane placement; without it a spec-authored
|
|
3436
|
+
split form fell back to the positional rule, so reordering sections moved them
|
|
3437
|
+
across the divider.
|
|
3438
|
+
- `FormSection.visibleOn` — the deprecated spelling now folds onto `visibleWhen`,
|
|
3439
|
+
matching the contract's own parse-time normalisation and the field path.
|
|
3440
|
+
- Thirteen `FormFieldSchema` keys — `maxLength`, `minLength`, `min`, `max`,
|
|
3441
|
+
`precision`, `scale`, `multiple`, `immutable`, `span`, `language`, `keyField`,
|
|
3442
|
+
`disclosure`, `fields` — so authored constraints, composite config and field
|
|
3443
|
+
width survive the bridge instead of ending there.
|
|
3444
|
+
|
|
3445
|
+
`publicPicker` is deliberately not carried and now says so: it is a server-side
|
|
3446
|
+
public-lookup authorization opt-in with no client destination.
|
|
3447
|
+
|
|
3448
|
+
The conformance test's key set is now derived from the contract's own shape at
|
|
3449
|
+
all three levels, so a spec key that is neither mapped nor explained fails the
|
|
3450
|
+
suite by construction.
|
|
3451
|
+
- d91aed9: Name the case-only spelling when a component type misses the registry.
|
|
3452
|
+
|
|
3453
|
+
Registry lookup is exactly case-sensitive, so a node typed `Page` misses a registered `page` and falls through to the OBJUI-001 "Unknown component type" panel. Because the mistake is usually uniform across a document, the symptom is not one broken widget — it is the whole page rendering as error panels, with nothing in the message pointing at the cause.
|
|
3454
|
+
|
|
3455
|
+
Both surfaces that report the miss now name the spelling that would have resolved. `SchemaRenderer`'s panel reads `Unknown component type: Page — did you mean 'page'?`, and `objectui check` reports `Unknown schema type "Page" in <file> — did you mean "page"?`. When no known type differs by case alone, neither says anything extra — `zzz` gains no bogus suggestion, and this is case matching, not an edit distance, so `pge` suggests nothing either.
|
|
3456
|
+
|
|
3457
|
+
**Lookup itself does not change.** `Page` still misses, still fails, and still renders the panel; only the message teaches. Normalising the lookup was considered and rejected (objectui#5247, maintainer ruling 2026-08-19): it would make two spellings valid everywhere, permanently, and legalise the typo class (`PAGE`, `pAge`) along with the PascalCase convention.
|
|
3458
|
+
|
|
3459
|
+
Each surface reads its candidates from the set it can actually trust — the renderer from the live `ComponentRegistry` (including pending lazy stubs), the CLI from the registration-derived `KNOWN_SCHEMA_TYPES` snapshot — so neither can suggest a type nothing registers.
|
|
3460
|
+
- Updated dependencies [97abedc]
|
|
3461
|
+
- Updated dependencies [b46c58f]
|
|
3462
|
+
- Updated dependencies [a507334]
|
|
3463
|
+
- Updated dependencies [ad694ac]
|
|
3464
|
+
- Updated dependencies [6f96fca]
|
|
3465
|
+
- Updated dependencies [afb2284]
|
|
3466
|
+
- Updated dependencies [3ac2de8]
|
|
3467
|
+
- Updated dependencies [63f4f92]
|
|
3468
|
+
- Updated dependencies [c131d9e]
|
|
3469
|
+
- Updated dependencies [5f00ff4]
|
|
3470
|
+
- Updated dependencies [c9e073a]
|
|
3471
|
+
- Updated dependencies [7b395d8]
|
|
3472
|
+
- Updated dependencies [0879812]
|
|
3473
|
+
- Updated dependencies [8cedb0d]
|
|
3474
|
+
- Updated dependencies [6cc910b]
|
|
3475
|
+
- Updated dependencies [061f5e8]
|
|
3476
|
+
- Updated dependencies [2dd4d3f]
|
|
3477
|
+
- Updated dependencies [e686f4d]
|
|
3478
|
+
- Updated dependencies [4ab4f1b]
|
|
3479
|
+
- Updated dependencies [b57107d]
|
|
3480
|
+
- Updated dependencies [e3ea4f9]
|
|
3481
|
+
- Updated dependencies [9ec8ceb]
|
|
3482
|
+
- Updated dependencies [bb5d4ee]
|
|
3483
|
+
- Updated dependencies [dc666f7]
|
|
3484
|
+
- Updated dependencies [3335767]
|
|
3485
|
+
- Updated dependencies [8b1f066]
|
|
3486
|
+
- Updated dependencies [af243c1]
|
|
3487
|
+
- Updated dependencies [961ceaa]
|
|
3488
|
+
- Updated dependencies [f3f4e4c]
|
|
3489
|
+
- Updated dependencies [a05c350]
|
|
3490
|
+
- Updated dependencies [1dbb993]
|
|
3491
|
+
- Updated dependencies [8c10f4f]
|
|
3492
|
+
- Updated dependencies [90dac98]
|
|
3493
|
+
- Updated dependencies [6096f20]
|
|
3494
|
+
- Updated dependencies [544aca2]
|
|
3495
|
+
- Updated dependencies [ea02938]
|
|
3496
|
+
- Updated dependencies [a14fb23]
|
|
3497
|
+
- Updated dependencies [ae98f1d]
|
|
3498
|
+
- Updated dependencies [f98eddf]
|
|
3499
|
+
- Updated dependencies [ce6bd99]
|
|
3500
|
+
- Updated dependencies [a5b08c9]
|
|
3501
|
+
- Updated dependencies [86982ac]
|
|
3502
|
+
- Updated dependencies [5e67837]
|
|
3503
|
+
- Updated dependencies [ff14e29]
|
|
3504
|
+
- Updated dependencies [9b28151]
|
|
3505
|
+
- Updated dependencies [64563a9]
|
|
3506
|
+
- Updated dependencies [1a5003f]
|
|
3507
|
+
- Updated dependencies [8acc51b]
|
|
3508
|
+
- Updated dependencies [ea9d17f]
|
|
3509
|
+
- Updated dependencies [45362a3]
|
|
3510
|
+
- Updated dependencies [2a943bf]
|
|
3511
|
+
- Updated dependencies [4357a27]
|
|
3512
|
+
- Updated dependencies [3261e64]
|
|
3513
|
+
- Updated dependencies [8740e86]
|
|
3514
|
+
- Updated dependencies [d22b37b]
|
|
3515
|
+
- Updated dependencies [1daf477]
|
|
3516
|
+
- Updated dependencies [6c2f3c5]
|
|
3517
|
+
- Updated dependencies [fb13e85]
|
|
3518
|
+
- Updated dependencies [c2d8659]
|
|
3519
|
+
- Updated dependencies [e0f8202]
|
|
3520
|
+
- Updated dependencies [ac2d6f1]
|
|
3521
|
+
- Updated dependencies [9fbbb17]
|
|
3522
|
+
- Updated dependencies [c3a26cc]
|
|
3523
|
+
- Updated dependencies [a66e58e]
|
|
3524
|
+
- Updated dependencies [d89492c]
|
|
3525
|
+
- Updated dependencies [9a5f998]
|
|
3526
|
+
- Updated dependencies [9327397]
|
|
3527
|
+
- Updated dependencies [17cc3a3]
|
|
3528
|
+
- Updated dependencies [4758b33]
|
|
3529
|
+
- Updated dependencies [f905090]
|
|
3530
|
+
- Updated dependencies [9c78ebe]
|
|
3531
|
+
- Updated dependencies [12809a5]
|
|
3532
|
+
- Updated dependencies [f9c06ef]
|
|
3533
|
+
- Updated dependencies [5ad3b88]
|
|
3534
|
+
- Updated dependencies [f9d772b]
|
|
3535
|
+
- Updated dependencies [97b6c21]
|
|
3536
|
+
- Updated dependencies [baac95a]
|
|
3537
|
+
- Updated dependencies [29b45f6]
|
|
3538
|
+
- Updated dependencies [39b8d51]
|
|
3539
|
+
- Updated dependencies [17b323e]
|
|
3540
|
+
- Updated dependencies [b956e69]
|
|
3541
|
+
- Updated dependencies [f812f37]
|
|
3542
|
+
- Updated dependencies [33e58d8]
|
|
3543
|
+
- Updated dependencies [256b4c9]
|
|
3544
|
+
- Updated dependencies [fec3b1a]
|
|
3545
|
+
- Updated dependencies [b8e0941]
|
|
3546
|
+
- Updated dependencies [0c50f18]
|
|
3547
|
+
- Updated dependencies [1dae95a]
|
|
3548
|
+
- Updated dependencies [e32dae1]
|
|
3549
|
+
- Updated dependencies [30b11ad]
|
|
3550
|
+
- Updated dependencies [4aebea0]
|
|
3551
|
+
- Updated dependencies [f976774]
|
|
3552
|
+
- Updated dependencies [3d6badf]
|
|
3553
|
+
- Updated dependencies [25cb364]
|
|
3554
|
+
- Updated dependencies [c6678b1]
|
|
3555
|
+
- Updated dependencies [0638322]
|
|
3556
|
+
- Updated dependencies [e3782d2]
|
|
3557
|
+
- Updated dependencies [db0beb2]
|
|
3558
|
+
- Updated dependencies [997ce38]
|
|
3559
|
+
- Updated dependencies [ae0b9d3]
|
|
3560
|
+
- Updated dependencies [3b469c8]
|
|
3561
|
+
- Updated dependencies [990a2d6]
|
|
3562
|
+
- Updated dependencies [6650259]
|
|
3563
|
+
- Updated dependencies [4f8b7f8]
|
|
3564
|
+
- Updated dependencies [9e6619f]
|
|
3565
|
+
- Updated dependencies [f6ae5e2]
|
|
3566
|
+
- Updated dependencies [eb97ce6]
|
|
3567
|
+
- Updated dependencies [7343376]
|
|
3568
|
+
- Updated dependencies [b2683a2]
|
|
3569
|
+
- Updated dependencies [dded788]
|
|
3570
|
+
- Updated dependencies [b45d463]
|
|
3571
|
+
- Updated dependencies [54a7830]
|
|
3572
|
+
- Updated dependencies [f3135a4]
|
|
3573
|
+
- Updated dependencies [b5696d3]
|
|
3574
|
+
- Updated dependencies [3f9d926]
|
|
3575
|
+
- Updated dependencies [e978ed5]
|
|
3576
|
+
- Updated dependencies [6a7f24e]
|
|
3577
|
+
- Updated dependencies [b3c96d6]
|
|
3578
|
+
- Updated dependencies [8d0ca91]
|
|
3579
|
+
- Updated dependencies [c30c8dd]
|
|
3580
|
+
- Updated dependencies [244d516]
|
|
3581
|
+
- Updated dependencies [ac15833]
|
|
3582
|
+
- Updated dependencies [ac15833]
|
|
3583
|
+
- Updated dependencies [e2dffc9]
|
|
3584
|
+
- Updated dependencies [328abeb]
|
|
3585
|
+
- Updated dependencies [24d3e65]
|
|
3586
|
+
- Updated dependencies [95a7c8d]
|
|
3587
|
+
- Updated dependencies [e227156]
|
|
3588
|
+
- Updated dependencies [cc4e476]
|
|
3589
|
+
- Updated dependencies [92970c4]
|
|
3590
|
+
- Updated dependencies [d570eaa]
|
|
3591
|
+
- Updated dependencies [4b742f4]
|
|
3592
|
+
- Updated dependencies [42687ba]
|
|
3593
|
+
- Updated dependencies [6cd8f66]
|
|
3594
|
+
- Updated dependencies [24a0f14]
|
|
3595
|
+
- Updated dependencies [797a30f]
|
|
3596
|
+
- Updated dependencies [b4075c0]
|
|
3597
|
+
- Updated dependencies [9b85600]
|
|
3598
|
+
- Updated dependencies [e327c89]
|
|
3599
|
+
- Updated dependencies [2eaf5be]
|
|
3600
|
+
- Updated dependencies [bf7ab35]
|
|
3601
|
+
- Updated dependencies [99878d8]
|
|
3602
|
+
- Updated dependencies [3c13675]
|
|
3603
|
+
- Updated dependencies [63ab761]
|
|
3604
|
+
- Updated dependencies [0eb9f36]
|
|
3605
|
+
- Updated dependencies [ae582b7]
|
|
3606
|
+
- Updated dependencies [51c2949]
|
|
3607
|
+
- Updated dependencies [8732846]
|
|
3608
|
+
- Updated dependencies [db11afd]
|
|
3609
|
+
- Updated dependencies [154075a]
|
|
3610
|
+
- Updated dependencies [582edef]
|
|
3611
|
+
- Updated dependencies [19f484f]
|
|
3612
|
+
- Updated dependencies [0a78a20]
|
|
3613
|
+
- Updated dependencies [615346d]
|
|
3614
|
+
- Updated dependencies [75dcc81]
|
|
3615
|
+
- Updated dependencies [55a12a8]
|
|
3616
|
+
- Updated dependencies [edfcf5a]
|
|
3617
|
+
- Updated dependencies [0a3e540]
|
|
3618
|
+
- Updated dependencies [f61dab1]
|
|
3619
|
+
- Updated dependencies [b0a05dd]
|
|
3620
|
+
- Updated dependencies [dd5ff19]
|
|
3621
|
+
- Updated dependencies [54997ff]
|
|
3622
|
+
- Updated dependencies [81f8498]
|
|
3623
|
+
- Updated dependencies [a782fa7]
|
|
3624
|
+
- Updated dependencies [1d6a23d]
|
|
3625
|
+
- Updated dependencies [76e9df0]
|
|
3626
|
+
- Updated dependencies [6158e4c]
|
|
3627
|
+
- Updated dependencies [cff8641]
|
|
3628
|
+
- Updated dependencies [c27b575]
|
|
3629
|
+
- Updated dependencies [0e6e76b]
|
|
3630
|
+
- Updated dependencies [bf43afa]
|
|
3631
|
+
- Updated dependencies [385ebc5]
|
|
3632
|
+
- Updated dependencies [858eafb]
|
|
3633
|
+
- Updated dependencies [a8c5509]
|
|
3634
|
+
- Updated dependencies [c2a8d23]
|
|
3635
|
+
- Updated dependencies [cd5b19a]
|
|
3636
|
+
- Updated dependencies [17dc167]
|
|
3637
|
+
- Updated dependencies [20d23be]
|
|
3638
|
+
- Updated dependencies [20d23be]
|
|
3639
|
+
- Updated dependencies [1263e40]
|
|
3640
|
+
- Updated dependencies [e6bc087]
|
|
3641
|
+
- Updated dependencies [9419df1]
|
|
3642
|
+
- Updated dependencies [a7557a7]
|
|
3643
|
+
- Updated dependencies [7d074ba]
|
|
3644
|
+
- Updated dependencies [6158e4c]
|
|
3645
|
+
- Updated dependencies [6158e4c]
|
|
3646
|
+
- Updated dependencies [52aad5c]
|
|
3647
|
+
- Updated dependencies [1563d3e]
|
|
3648
|
+
- Updated dependencies [58da8ae]
|
|
3649
|
+
- Updated dependencies [138ad45]
|
|
3650
|
+
- Updated dependencies [138ad45]
|
|
3651
|
+
- Updated dependencies [5262f7d]
|
|
3652
|
+
- Updated dependencies [6aa029b]
|
|
3653
|
+
- Updated dependencies [770cc5b]
|
|
3654
|
+
- Updated dependencies [d893290]
|
|
3655
|
+
- Updated dependencies [1a88ce2]
|
|
3656
|
+
- Updated dependencies [a1a44d6]
|
|
3657
|
+
- Updated dependencies [e0a9c67]
|
|
3658
|
+
- Updated dependencies [5638529]
|
|
3659
|
+
- Updated dependencies [5638529]
|
|
3660
|
+
- Updated dependencies [d0fba91]
|
|
3661
|
+
- Updated dependencies [c476be0]
|
|
3662
|
+
- Updated dependencies [c82ff39]
|
|
3663
|
+
- Updated dependencies [6c3da53]
|
|
3664
|
+
- Updated dependencies [31987bd]
|
|
3665
|
+
- Updated dependencies [3c3ce15]
|
|
3666
|
+
- Updated dependencies [063119f]
|
|
3667
|
+
- Updated dependencies [e100589]
|
|
3668
|
+
- Updated dependencies [304f611]
|
|
3669
|
+
- Updated dependencies [e46ee77]
|
|
3670
|
+
- Updated dependencies [f9c8c4e]
|
|
3671
|
+
- Updated dependencies [6e9c8d2]
|
|
3672
|
+
- Updated dependencies [9547063]
|
|
3673
|
+
- Updated dependencies [3f6efd6]
|
|
3674
|
+
- Updated dependencies [55d18c6]
|
|
3675
|
+
- Updated dependencies [c4ab6d0]
|
|
3676
|
+
- Updated dependencies [0e9058b]
|
|
3677
|
+
- Updated dependencies [c4ab6d0]
|
|
3678
|
+
- Updated dependencies [5988b6b]
|
|
3679
|
+
- Updated dependencies [6158e4c]
|
|
3680
|
+
- Updated dependencies [00ccdf7]
|
|
3681
|
+
- Updated dependencies [9d9ed54]
|
|
3682
|
+
- Updated dependencies [4a1adb7]
|
|
3683
|
+
- Updated dependencies [ca3de72]
|
|
3684
|
+
- Updated dependencies [83e3f83]
|
|
3685
|
+
- Updated dependencies [401611b]
|
|
3686
|
+
- Updated dependencies [2c0ddf2]
|
|
3687
|
+
- Updated dependencies [4abc0aa]
|
|
3688
|
+
- Updated dependencies [f560ded]
|
|
3689
|
+
- Updated dependencies [2b188fa]
|
|
3690
|
+
- Updated dependencies [f68e0a0]
|
|
3691
|
+
- Updated dependencies [aea682a]
|
|
3692
|
+
- Updated dependencies [fcdc8ec]
|
|
3693
|
+
- Updated dependencies [2d576e4]
|
|
3694
|
+
- Updated dependencies [8366acc]
|
|
3695
|
+
- Updated dependencies [95e58a3]
|
|
3696
|
+
- Updated dependencies [9d7419b]
|
|
3697
|
+
- Updated dependencies [fc7db05]
|
|
3698
|
+
- Updated dependencies [9ed8d0f]
|
|
3699
|
+
- Updated dependencies [c73cdb5]
|
|
3700
|
+
- Updated dependencies [6f5719e]
|
|
3701
|
+
- Updated dependencies [d0097af]
|
|
3702
|
+
- Updated dependencies [432882b]
|
|
3703
|
+
- Updated dependencies [64dae8e]
|
|
3704
|
+
- Updated dependencies [b06e374]
|
|
3705
|
+
- Updated dependencies [06a8af5]
|
|
3706
|
+
- Updated dependencies [6a91586]
|
|
3707
|
+
- Updated dependencies [a04d7c6]
|
|
3708
|
+
- Updated dependencies [f3c2bb0]
|
|
3709
|
+
- Updated dependencies [978507b]
|
|
3710
|
+
- Updated dependencies [9801765]
|
|
3711
|
+
- Updated dependencies [9cebfca]
|
|
3712
|
+
- Updated dependencies [460575f]
|
|
3713
|
+
- Updated dependencies [594704f]
|
|
3714
|
+
- Updated dependencies [d3995fe]
|
|
3715
|
+
- Updated dependencies [d88e20f]
|
|
3716
|
+
- Updated dependencies [2d7304d]
|
|
3717
|
+
- Updated dependencies [993f312]
|
|
3718
|
+
- Updated dependencies [636b236]
|
|
3719
|
+
- Updated dependencies [d6d8fb9]
|
|
3720
|
+
- Updated dependencies [64d624d]
|
|
3721
|
+
- Updated dependencies [053fdc8]
|
|
3722
|
+
- Updated dependencies [41b7ce3]
|
|
3723
|
+
- Updated dependencies [ae476b8]
|
|
3724
|
+
- Updated dependencies [95bad12]
|
|
3725
|
+
- Updated dependencies [d2fb6ef]
|
|
3726
|
+
- Updated dependencies [fda49e5]
|
|
3727
|
+
- Updated dependencies [94e2fa7]
|
|
3728
|
+
- Updated dependencies [490d9a9]
|
|
3729
|
+
- Updated dependencies [fc62bb4]
|
|
3730
|
+
- Updated dependencies [41df893]
|
|
3731
|
+
- Updated dependencies [0cba1b7]
|
|
3732
|
+
- Updated dependencies [00f3eb5]
|
|
3733
|
+
- Updated dependencies [1ec291c]
|
|
3734
|
+
- Updated dependencies [453dbaa]
|
|
3735
|
+
- Updated dependencies [95f8704]
|
|
3736
|
+
- Updated dependencies [69a2163]
|
|
3737
|
+
- Updated dependencies [24e027e]
|
|
3738
|
+
- Updated dependencies [2c3cd1b]
|
|
3739
|
+
- Updated dependencies [90665e0]
|
|
3740
|
+
- Updated dependencies [8d3a529]
|
|
3741
|
+
- Updated dependencies [5ac2e2c]
|
|
3742
|
+
- Updated dependencies [7e19d03]
|
|
3743
|
+
- Updated dependencies [b08b7eb]
|
|
3744
|
+
- Updated dependencies [1e946c9]
|
|
3745
|
+
- Updated dependencies [546ddf7]
|
|
3746
|
+
- Updated dependencies [864154e]
|
|
3747
|
+
- Updated dependencies [b023625]
|
|
3748
|
+
- Updated dependencies [75bd83d]
|
|
3749
|
+
- Updated dependencies [44d075b]
|
|
3750
|
+
- Updated dependencies [40c479a]
|
|
3751
|
+
- Updated dependencies [971d387]
|
|
3752
|
+
- Updated dependencies [ee851c3]
|
|
3753
|
+
- Updated dependencies [6414dfd]
|
|
3754
|
+
- Updated dependencies [a8d5c71]
|
|
3755
|
+
- Updated dependencies [905b21f]
|
|
3756
|
+
- Updated dependencies [88e9109]
|
|
3757
|
+
- Updated dependencies [2c45966]
|
|
3758
|
+
- Updated dependencies [db3a600]
|
|
3759
|
+
- Updated dependencies [3a3db76]
|
|
3760
|
+
- Updated dependencies [0d723a3]
|
|
3761
|
+
- Updated dependencies [0c95d3d]
|
|
3762
|
+
- Updated dependencies [3e4fa2c]
|
|
3763
|
+
- Updated dependencies [b5b928a]
|
|
3764
|
+
- Updated dependencies [5fa06c4]
|
|
3765
|
+
- Updated dependencies [52a43de]
|
|
3766
|
+
- Updated dependencies [195052f]
|
|
3767
|
+
- Updated dependencies [e4559d1]
|
|
3768
|
+
- Updated dependencies [2c71482]
|
|
3769
|
+
- Updated dependencies [a26b9e4]
|
|
3770
|
+
- Updated dependencies [5ef9c4f]
|
|
3771
|
+
- Updated dependencies [46f0bb4]
|
|
3772
|
+
- Updated dependencies [06b82b8]
|
|
3773
|
+
- Updated dependencies [6f81384]
|
|
3774
|
+
- Updated dependencies [5d3a2d1]
|
|
3775
|
+
- Updated dependencies [8f1d995]
|
|
3776
|
+
- Updated dependencies [b362c1b]
|
|
3777
|
+
- Updated dependencies [5127378]
|
|
3778
|
+
- Updated dependencies [dddb942]
|
|
3779
|
+
- Updated dependencies [00c665e]
|
|
3780
|
+
- Updated dependencies [29754cf]
|
|
3781
|
+
- Updated dependencies [3c2b6f7]
|
|
3782
|
+
- Updated dependencies [b84dc18]
|
|
3783
|
+
- Updated dependencies [ac8abb0]
|
|
3784
|
+
- Updated dependencies [9d86e1d]
|
|
3785
|
+
- Updated dependencies [3a5817f]
|
|
3786
|
+
- Updated dependencies [99a3c2d]
|
|
3787
|
+
- Updated dependencies [5961030]
|
|
3788
|
+
- Updated dependencies [c8ea8af]
|
|
3789
|
+
- Updated dependencies [9602dc8]
|
|
3790
|
+
- Updated dependencies [3190414]
|
|
3791
|
+
- Updated dependencies [4e480f5]
|
|
3792
|
+
- Updated dependencies [38a123c]
|
|
3793
|
+
- Updated dependencies [299102e]
|
|
3794
|
+
- Updated dependencies [d7acad6]
|
|
3795
|
+
- Updated dependencies [45a9aeb]
|
|
3796
|
+
- Updated dependencies [713db46]
|
|
3797
|
+
- Updated dependencies [bf3a03c]
|
|
3798
|
+
- Updated dependencies [cb55718]
|
|
3799
|
+
- Updated dependencies [831be72]
|
|
3800
|
+
- Updated dependencies [29cb85b]
|
|
3801
|
+
- Updated dependencies [3e028c8]
|
|
3802
|
+
- Updated dependencies [d0889e2]
|
|
3803
|
+
- Updated dependencies [ce503e5]
|
|
3804
|
+
- Updated dependencies [f20dcf0]
|
|
3805
|
+
- Updated dependencies [4ca30d0]
|
|
3806
|
+
- Updated dependencies [7a5da14]
|
|
3807
|
+
- Updated dependencies [fff9645]
|
|
3808
|
+
- Updated dependencies [9c3b7ce]
|
|
3809
|
+
- Updated dependencies [2c1c967]
|
|
3810
|
+
- Updated dependencies [4d5f9b4]
|
|
3811
|
+
- Updated dependencies [d6ceb8d]
|
|
3812
|
+
- Updated dependencies [969ba84]
|
|
3813
|
+
- Updated dependencies [7977ff9]
|
|
3814
|
+
- Updated dependencies [3beef6d]
|
|
3815
|
+
- Updated dependencies [dbd5194]
|
|
3816
|
+
- Updated dependencies [b392674]
|
|
3817
|
+
- Updated dependencies [4f3a1e2]
|
|
3818
|
+
- Updated dependencies [2acd8e1]
|
|
3819
|
+
- Updated dependencies [f07b976]
|
|
3820
|
+
- Updated dependencies [045d20b]
|
|
3821
|
+
- Updated dependencies [a2d2515]
|
|
3822
|
+
- Updated dependencies [adb2a86]
|
|
3823
|
+
- Updated dependencies [503cd8b]
|
|
3824
|
+
- Updated dependencies [3619792]
|
|
3825
|
+
- Updated dependencies [3561bd2]
|
|
3826
|
+
- Updated dependencies [bf97b98]
|
|
3827
|
+
- Updated dependencies [320374d]
|
|
3828
|
+
- Updated dependencies [b0d308d]
|
|
3829
|
+
- Updated dependencies [40f34b4]
|
|
3830
|
+
- Updated dependencies [8063bcb]
|
|
3831
|
+
- Updated dependencies [b74a859]
|
|
3832
|
+
- Updated dependencies [d4493fd]
|
|
3833
|
+
- Updated dependencies [240b80f]
|
|
3834
|
+
- Updated dependencies [77cb489]
|
|
3835
|
+
- Updated dependencies [bfaa158]
|
|
3836
|
+
- Updated dependencies [777e5c6]
|
|
3837
|
+
- Updated dependencies [0c386dd]
|
|
3838
|
+
- Updated dependencies [9e37d9b]
|
|
3839
|
+
- Updated dependencies [5ad86dd]
|
|
3840
|
+
- Updated dependencies [16a725f]
|
|
3841
|
+
- Updated dependencies [4dfdcc3]
|
|
3842
|
+
- Updated dependencies [ce986aa]
|
|
3843
|
+
- Updated dependencies [446d93d]
|
|
3844
|
+
- Updated dependencies [ecd9cb2]
|
|
3845
|
+
- Updated dependencies [98d4108]
|
|
3846
|
+
- Updated dependencies [0e3b3be]
|
|
3847
|
+
- Updated dependencies [512bc90]
|
|
3848
|
+
- Updated dependencies [a29ae2d]
|
|
3849
|
+
- Updated dependencies [220c18d]
|
|
3850
|
+
- Updated dependencies [4388f71]
|
|
3851
|
+
- Updated dependencies [0b1ac58]
|
|
3852
|
+
- Updated dependencies [c93b4d5]
|
|
3853
|
+
- Updated dependencies [c1fe272]
|
|
3854
|
+
- Updated dependencies [8ad218d]
|
|
3855
|
+
- Updated dependencies [3e41187]
|
|
3856
|
+
- Updated dependencies [5f78953]
|
|
3857
|
+
- Updated dependencies [639114c]
|
|
3858
|
+
- Updated dependencies [639114c]
|
|
3859
|
+
- Updated dependencies [639114c]
|
|
3860
|
+
- Updated dependencies [1490691]
|
|
3861
|
+
- Updated dependencies [1f31d3a]
|
|
3862
|
+
- Updated dependencies [d8ec8d6]
|
|
3863
|
+
- Updated dependencies [351eb31]
|
|
3864
|
+
- Updated dependencies [866cd1d]
|
|
3865
|
+
- Updated dependencies [20c04b2]
|
|
3866
|
+
- Updated dependencies [01c9023]
|
|
3867
|
+
- Updated dependencies [48c19bd]
|
|
3868
|
+
- Updated dependencies [a6d8b8d]
|
|
3869
|
+
- Updated dependencies [b652514]
|
|
3870
|
+
- Updated dependencies [adbda1b]
|
|
3871
|
+
- Updated dependencies [8952395]
|
|
3872
|
+
- Updated dependencies [e2b3826]
|
|
3873
|
+
- Updated dependencies [0348bc9]
|
|
3874
|
+
- Updated dependencies [e8c553b]
|
|
3875
|
+
- Updated dependencies [2e32ed4]
|
|
3876
|
+
- Updated dependencies [7c3df8f]
|
|
3877
|
+
- Updated dependencies [a4514e8]
|
|
3878
|
+
- Updated dependencies [db3896c]
|
|
3879
|
+
- Updated dependencies [e75f4c9]
|
|
3880
|
+
- Updated dependencies [19f1639]
|
|
3881
|
+
- Updated dependencies [47547d0]
|
|
3882
|
+
- Updated dependencies [1bee5d0]
|
|
3883
|
+
- Updated dependencies [858cd72]
|
|
3884
|
+
- Updated dependencies [cfc9b6d]
|
|
3885
|
+
- Updated dependencies [554f2b6]
|
|
3886
|
+
- Updated dependencies [669d71b]
|
|
3887
|
+
- Updated dependencies [6a99bb2]
|
|
3888
|
+
- Updated dependencies [ed27d7c]
|
|
3889
|
+
- Updated dependencies [52c8cf7]
|
|
3890
|
+
- Updated dependencies [2ceb43a]
|
|
3891
|
+
- Updated dependencies [7cdd2b9]
|
|
3892
|
+
- Updated dependencies [52c8cf7]
|
|
3893
|
+
- Updated dependencies [3399704]
|
|
3894
|
+
- Updated dependencies [81a2eb1]
|
|
3895
|
+
- Updated dependencies [caa0cd3]
|
|
3896
|
+
- Updated dependencies [20cb8db]
|
|
3897
|
+
- Updated dependencies [25c7d58]
|
|
3898
|
+
- Updated dependencies [00d2fa6]
|
|
3899
|
+
- Updated dependencies [77b2a18]
|
|
3900
|
+
- Updated dependencies [c6198c2]
|
|
3901
|
+
- Updated dependencies [721d1e0]
|
|
3902
|
+
- Updated dependencies [c2f0f48]
|
|
3903
|
+
- Updated dependencies [1237ae4]
|
|
3904
|
+
- Updated dependencies [51eb515]
|
|
3905
|
+
- Updated dependencies [c354ce5]
|
|
3906
|
+
- Updated dependencies [8fe8e5c]
|
|
3907
|
+
- Updated dependencies [feac439]
|
|
3908
|
+
- Updated dependencies [efbd566]
|
|
3909
|
+
- Updated dependencies [2a5bf45]
|
|
3910
|
+
- Updated dependencies [9587fc9]
|
|
3911
|
+
- Updated dependencies [e62c44e]
|
|
3912
|
+
- Updated dependencies [23b9958]
|
|
3913
|
+
- Updated dependencies [5d0876c]
|
|
3914
|
+
- Updated dependencies [b041b9c]
|
|
3915
|
+
- Updated dependencies [ce2aaef]
|
|
3916
|
+
- Updated dependencies [544ecba]
|
|
3917
|
+
- Updated dependencies [bc640ec]
|
|
3918
|
+
- Updated dependencies [1e215c4]
|
|
3919
|
+
- Updated dependencies [da6e191]
|
|
3920
|
+
- Updated dependencies [3e377c9]
|
|
3921
|
+
- Updated dependencies [a3eb5d0]
|
|
3922
|
+
- Updated dependencies [4ce14f1]
|
|
3923
|
+
- Updated dependencies [aef97e5]
|
|
3924
|
+
- Updated dependencies [2af1fa7]
|
|
3925
|
+
- Updated dependencies [a137d0c]
|
|
3926
|
+
- Updated dependencies [caf477f]
|
|
3927
|
+
- Updated dependencies [f6375da]
|
|
3928
|
+
- Updated dependencies [967e5d8]
|
|
3929
|
+
- Updated dependencies [c907a9c]
|
|
3930
|
+
- Updated dependencies [a4611b3]
|
|
3931
|
+
- Updated dependencies [20316ba]
|
|
3932
|
+
- Updated dependencies [d3499b3]
|
|
3933
|
+
- Updated dependencies [91f9276]
|
|
3934
|
+
- Updated dependencies [309c75e]
|
|
3935
|
+
- Updated dependencies [c9f9bae]
|
|
3936
|
+
- Updated dependencies [18897a4]
|
|
3937
|
+
- Updated dependencies [8b7ea39]
|
|
3938
|
+
- Updated dependencies [a915064]
|
|
3939
|
+
- Updated dependencies [dcbf0b2]
|
|
3940
|
+
- Updated dependencies [52cac38]
|
|
3941
|
+
- Updated dependencies [93fea2e]
|
|
3942
|
+
- Updated dependencies [53ded82]
|
|
3943
|
+
- Updated dependencies [1422a92]
|
|
3944
|
+
- Updated dependencies [d05fe17]
|
|
3945
|
+
- Updated dependencies [a480f79]
|
|
3946
|
+
- Updated dependencies [f08d1a8]
|
|
3947
|
+
- Updated dependencies [64a252d]
|
|
3948
|
+
- Updated dependencies [786bc91]
|
|
3949
|
+
- Updated dependencies [75fca96]
|
|
3950
|
+
- Updated dependencies [7ca6ddd]
|
|
3951
|
+
- Updated dependencies [f1cd290]
|
|
3952
|
+
- Updated dependencies [5a41ce7]
|
|
3953
|
+
- Updated dependencies [8d50bc2]
|
|
3954
|
+
- Updated dependencies [604476d]
|
|
3955
|
+
- Updated dependencies [335abea]
|
|
3956
|
+
- Updated dependencies [0f5cadf]
|
|
3957
|
+
- Updated dependencies [4f9f1ee]
|
|
3958
|
+
- Updated dependencies [66e8b2a]
|
|
3959
|
+
- Updated dependencies [aa083cd]
|
|
3960
|
+
- Updated dependencies [12b5992]
|
|
3961
|
+
- Updated dependencies [b93e245]
|
|
3962
|
+
- Updated dependencies [c842594]
|
|
3963
|
+
- Updated dependencies [290de37]
|
|
3964
|
+
- Updated dependencies [e1c27e4]
|
|
3965
|
+
- Updated dependencies [8c8da45]
|
|
3966
|
+
- Updated dependencies [8cd8eb5]
|
|
3967
|
+
- Updated dependencies [cf1d29e]
|
|
3968
|
+
- Updated dependencies [1bd79c8]
|
|
3969
|
+
- Updated dependencies [af9e957]
|
|
3970
|
+
- Updated dependencies [b1030c7]
|
|
3971
|
+
- Updated dependencies [c974edf]
|
|
3972
|
+
- Updated dependencies [ad852b6]
|
|
3973
|
+
- Updated dependencies [6778809]
|
|
3974
|
+
- Updated dependencies [0758bd8]
|
|
3975
|
+
- Updated dependencies [ee4d19f]
|
|
3976
|
+
- Updated dependencies [496d31d]
|
|
3977
|
+
- Updated dependencies [f57ca75]
|
|
3978
|
+
- Updated dependencies [0ea7054]
|
|
3979
|
+
- Updated dependencies [9a853f2]
|
|
3980
|
+
- Updated dependencies [cb847fd]
|
|
3981
|
+
- Updated dependencies [ee70287]
|
|
3982
|
+
- Updated dependencies [3e98e13]
|
|
3983
|
+
- Updated dependencies [a695f50]
|
|
3984
|
+
- Updated dependencies [fc32921]
|
|
3985
|
+
- Updated dependencies [4eaa835]
|
|
3986
|
+
- Updated dependencies [8f9d87a]
|
|
3987
|
+
- Updated dependencies [b1777ae]
|
|
3988
|
+
- Updated dependencies [24845c4]
|
|
3989
|
+
- Updated dependencies [6f864cf]
|
|
3990
|
+
- Updated dependencies [24d1edd]
|
|
3991
|
+
- Updated dependencies [645087c]
|
|
3992
|
+
- Updated dependencies [33f4a19]
|
|
3993
|
+
- Updated dependencies [6e9a3d4]
|
|
3994
|
+
- Updated dependencies [5323168]
|
|
3995
|
+
- Updated dependencies [841dd2b]
|
|
3996
|
+
- Updated dependencies [3014fc0]
|
|
3997
|
+
- Updated dependencies [dacb402]
|
|
3998
|
+
- Updated dependencies [846cec0]
|
|
3999
|
+
- Updated dependencies [91facae]
|
|
4000
|
+
- Updated dependencies [b38014e]
|
|
4001
|
+
- Updated dependencies [474797d]
|
|
4002
|
+
- Updated dependencies [704e695]
|
|
4003
|
+
- Updated dependencies [a407bd6]
|
|
4004
|
+
- Updated dependencies [317dbce]
|
|
4005
|
+
- Updated dependencies [309728c]
|
|
4006
|
+
- Updated dependencies [aa08d7e]
|
|
4007
|
+
- Updated dependencies [3a43a15]
|
|
4008
|
+
- Updated dependencies [f76f436]
|
|
4009
|
+
- Updated dependencies [ce45a03]
|
|
4010
|
+
- Updated dependencies [421544b]
|
|
4011
|
+
- Updated dependencies [fb01022]
|
|
4012
|
+
- Updated dependencies [e9d9212]
|
|
4013
|
+
- Updated dependencies [ecfb693]
|
|
4014
|
+
- Updated dependencies [abc1b18]
|
|
4015
|
+
- Updated dependencies [81a51db]
|
|
4016
|
+
- Updated dependencies [67749c7]
|
|
4017
|
+
- Updated dependencies [507b61b]
|
|
4018
|
+
- Updated dependencies [512c84b]
|
|
4019
|
+
- Updated dependencies [fb3a101]
|
|
4020
|
+
- Updated dependencies [d4733f2]
|
|
4021
|
+
- Updated dependencies [7c9145f]
|
|
4022
|
+
- Updated dependencies [f391ede]
|
|
4023
|
+
- Updated dependencies [f5cfbbd]
|
|
4024
|
+
- Updated dependencies [f5cfbbd]
|
|
4025
|
+
- Updated dependencies [8b532cb]
|
|
4026
|
+
- Updated dependencies [64c3cdd]
|
|
4027
|
+
- Updated dependencies [4d65991]
|
|
4028
|
+
- Updated dependencies [c42554e]
|
|
4029
|
+
- Updated dependencies [555b4ec]
|
|
4030
|
+
- Updated dependencies [1ccfc23]
|
|
4031
|
+
- Updated dependencies [542718f]
|
|
4032
|
+
- Updated dependencies [7f27bc5]
|
|
4033
|
+
- Updated dependencies [0a174f3]
|
|
4034
|
+
- Updated dependencies [676f677]
|
|
4035
|
+
- Updated dependencies [f95b140]
|
|
4036
|
+
- Updated dependencies [541ce4e]
|
|
4037
|
+
- Updated dependencies [6479086]
|
|
4038
|
+
- Updated dependencies [d79f525]
|
|
4039
|
+
- Updated dependencies [d1865d2]
|
|
4040
|
+
- Updated dependencies [9662aca]
|
|
4041
|
+
- Updated dependencies [55ba3ff]
|
|
4042
|
+
- Updated dependencies [f1190b0]
|
|
4043
|
+
- Updated dependencies [561abef]
|
|
4044
|
+
- Updated dependencies [ef52001]
|
|
4045
|
+
- Updated dependencies [6a4680b]
|
|
4046
|
+
- Updated dependencies [c3a4273]
|
|
4047
|
+
- Updated dependencies [abf710d]
|
|
4048
|
+
- Updated dependencies [093af32]
|
|
4049
|
+
- Updated dependencies [1bd1be7]
|
|
4050
|
+
- Updated dependencies [d234fa9]
|
|
4051
|
+
- Updated dependencies [adf5812]
|
|
4052
|
+
- Updated dependencies [5058336]
|
|
4053
|
+
- Updated dependencies [2f6b2bf]
|
|
4054
|
+
- Updated dependencies [2028b31]
|
|
4055
|
+
- Updated dependencies [63601ab]
|
|
4056
|
+
- Updated dependencies [c372b29]
|
|
4057
|
+
- Updated dependencies [8693b85]
|
|
4058
|
+
- Updated dependencies [58b7b3d]
|
|
4059
|
+
- Updated dependencies [84defab]
|
|
4060
|
+
- Updated dependencies [681d3f1]
|
|
4061
|
+
- Updated dependencies [969d4f2]
|
|
4062
|
+
- Updated dependencies [f3bc481]
|
|
4063
|
+
- Updated dependencies [b79aac2]
|
|
4064
|
+
- Updated dependencies [93fc0e7]
|
|
4065
|
+
- Updated dependencies [a4b723f]
|
|
4066
|
+
- Updated dependencies [2b10ca0]
|
|
4067
|
+
- Updated dependencies [7db4a81]
|
|
4068
|
+
- Updated dependencies [19a0b0e]
|
|
4069
|
+
- Updated dependencies [526fc11]
|
|
4070
|
+
- Updated dependencies [f8e3e9a]
|
|
4071
|
+
- Updated dependencies [3b6bc69]
|
|
4072
|
+
- Updated dependencies [6732df4]
|
|
4073
|
+
- Updated dependencies [fe9e0d0]
|
|
4074
|
+
- Updated dependencies [63fb72c]
|
|
4075
|
+
- Updated dependencies [0b8c638]
|
|
4076
|
+
- Updated dependencies [804831c]
|
|
4077
|
+
- Updated dependencies [279e48e]
|
|
4078
|
+
- Updated dependencies [8700d6d]
|
|
4079
|
+
- Updated dependencies [8db2a0f]
|
|
4080
|
+
- Updated dependencies [30443fb]
|
|
4081
|
+
- Updated dependencies [da45e6b]
|
|
4082
|
+
- Updated dependencies [835f0f3]
|
|
4083
|
+
- Updated dependencies [ed35b44]
|
|
4084
|
+
- Updated dependencies [729e851]
|
|
4085
|
+
- Updated dependencies [96919a4]
|
|
4086
|
+
- Updated dependencies [20b507a]
|
|
4087
|
+
- Updated dependencies [2e471dc]
|
|
4088
|
+
- Updated dependencies [be50942]
|
|
4089
|
+
- Updated dependencies [775e079]
|
|
4090
|
+
- Updated dependencies [7e8b3c0]
|
|
4091
|
+
- Updated dependencies [53374dc]
|
|
4092
|
+
- Updated dependencies [f6fb83f]
|
|
4093
|
+
- Updated dependencies [2049b03]
|
|
4094
|
+
- Updated dependencies [2bf34f7]
|
|
4095
|
+
- Updated dependencies [15b33ae]
|
|
4096
|
+
- Updated dependencies [7cbc724]
|
|
4097
|
+
- Updated dependencies [fb91ac9]
|
|
4098
|
+
- Updated dependencies [641fb55]
|
|
4099
|
+
- Updated dependencies [8524372]
|
|
4100
|
+
- Updated dependencies [7cbefa5]
|
|
4101
|
+
- Updated dependencies [72d6587]
|
|
4102
|
+
- Updated dependencies [a272a4f]
|
|
4103
|
+
- Updated dependencies [55f39ee]
|
|
4104
|
+
- Updated dependencies [0970a0e]
|
|
4105
|
+
- Updated dependencies [e427e9c]
|
|
4106
|
+
- Updated dependencies [bbc9dc3]
|
|
4107
|
+
- Updated dependencies [02f1813]
|
|
4108
|
+
- Updated dependencies [ba0b61a]
|
|
4109
|
+
- Updated dependencies [ac716ff]
|
|
4110
|
+
- Updated dependencies [f0f3cd5]
|
|
4111
|
+
- Updated dependencies [ab856ed]
|
|
4112
|
+
- Updated dependencies [20f3e65]
|
|
4113
|
+
- Updated dependencies [bbba098]
|
|
4114
|
+
- Updated dependencies [87af769]
|
|
4115
|
+
- Updated dependencies [3be720e]
|
|
4116
|
+
- Updated dependencies [43c0d17]
|
|
4117
|
+
- Updated dependencies [c3df43a]
|
|
4118
|
+
- Updated dependencies [d16d0e9]
|
|
4119
|
+
- Updated dependencies [bbe57fd]
|
|
4120
|
+
- Updated dependencies [272a530]
|
|
4121
|
+
- Updated dependencies [1779e8d]
|
|
4122
|
+
- Updated dependencies [f0f4d6c]
|
|
4123
|
+
- Updated dependencies [4128188]
|
|
4124
|
+
- Updated dependencies [269880a]
|
|
4125
|
+
- Updated dependencies [b253c4e]
|
|
4126
|
+
- Updated dependencies [78a9c67]
|
|
4127
|
+
- Updated dependencies [4a7ef0d]
|
|
4128
|
+
- Updated dependencies [dea17b4]
|
|
4129
|
+
- Updated dependencies [89bb77a]
|
|
4130
|
+
- Updated dependencies [06611e4]
|
|
4131
|
+
- Updated dependencies [44152c4]
|
|
4132
|
+
- Updated dependencies [dc3893d]
|
|
4133
|
+
- Updated dependencies [1bbaa16]
|
|
4134
|
+
- Updated dependencies [6ee259a]
|
|
4135
|
+
- Updated dependencies [e708426]
|
|
4136
|
+
- Updated dependencies [3b6d53b]
|
|
4137
|
+
- Updated dependencies [276d174]
|
|
4138
|
+
- Updated dependencies [2982ed9]
|
|
4139
|
+
- Updated dependencies [a8198de]
|
|
4140
|
+
- Updated dependencies [05a49f2]
|
|
4141
|
+
- Updated dependencies [a78cd37]
|
|
4142
|
+
- Updated dependencies [5ea623e]
|
|
4143
|
+
- Updated dependencies [5eabe86]
|
|
4144
|
+
- Updated dependencies [ca5d671]
|
|
4145
|
+
- Updated dependencies [32bf2d6]
|
|
4146
|
+
- Updated dependencies [af4fb29]
|
|
4147
|
+
- Updated dependencies [c698a81]
|
|
4148
|
+
- Updated dependencies [ff5ef1c]
|
|
4149
|
+
- Updated dependencies [9a97800]
|
|
4150
|
+
- Updated dependencies [6bca0e4]
|
|
4151
|
+
- Updated dependencies [81c0bc4]
|
|
4152
|
+
- Updated dependencies [b2e85a9]
|
|
4153
|
+
- Updated dependencies [60500cb]
|
|
4154
|
+
- Updated dependencies [d5c1f52]
|
|
4155
|
+
- Updated dependencies [c7cd2b6]
|
|
4156
|
+
- Updated dependencies [b2065e7]
|
|
4157
|
+
- Updated dependencies [2fcefb9]
|
|
4158
|
+
- Updated dependencies [77f846a]
|
|
4159
|
+
- Updated dependencies [bc5870c]
|
|
4160
|
+
- Updated dependencies [b55a346]
|
|
4161
|
+
- Updated dependencies [065bba7]
|
|
4162
|
+
- Updated dependencies [f760064]
|
|
4163
|
+
- Updated dependencies [6791717]
|
|
4164
|
+
- Updated dependencies [8ea3bee]
|
|
4165
|
+
- Updated dependencies [100547e]
|
|
4166
|
+
- Updated dependencies [3a58149]
|
|
4167
|
+
- Updated dependencies [6d1c155]
|
|
4168
|
+
- Updated dependencies [d7573b3]
|
|
4169
|
+
- Updated dependencies [bf3edfe]
|
|
4170
|
+
- Updated dependencies [6ce89da]
|
|
4171
|
+
- Updated dependencies [0e05aac]
|
|
4172
|
+
- Updated dependencies [5aed9e4]
|
|
4173
|
+
- Updated dependencies [83c77dc]
|
|
4174
|
+
- Updated dependencies [18a8e7d]
|
|
4175
|
+
- Updated dependencies [e7957ab]
|
|
4176
|
+
- Updated dependencies [f7e34ca]
|
|
4177
|
+
- Updated dependencies [e719ebd]
|
|
4178
|
+
- Updated dependencies [516583b]
|
|
4179
|
+
- Updated dependencies [f9e4f91]
|
|
4180
|
+
- Updated dependencies [6ef48b1]
|
|
4181
|
+
- Updated dependencies [fa429cf]
|
|
4182
|
+
- Updated dependencies [ed8df3e]
|
|
4183
|
+
- Updated dependencies [b470e91]
|
|
4184
|
+
- Updated dependencies [8b446f5]
|
|
4185
|
+
- Updated dependencies [8e74b27]
|
|
4186
|
+
- Updated dependencies [8ebd57f]
|
|
4187
|
+
- Updated dependencies [617707a]
|
|
4188
|
+
- Updated dependencies [c40f3b8]
|
|
4189
|
+
- Updated dependencies [7357447]
|
|
4190
|
+
- Updated dependencies [199d31b]
|
|
4191
|
+
- Updated dependencies [3e01cb5]
|
|
4192
|
+
- Updated dependencies [7138bc1]
|
|
4193
|
+
- Updated dependencies [cef27e2]
|
|
4194
|
+
- Updated dependencies [4e8622b]
|
|
4195
|
+
- Updated dependencies [dffd752]
|
|
4196
|
+
- Updated dependencies [06973aa]
|
|
4197
|
+
- Updated dependencies [50798f3]
|
|
4198
|
+
- Updated dependencies [6a576c9]
|
|
4199
|
+
- Updated dependencies [105f3c5]
|
|
4200
|
+
- Updated dependencies [3ccd9e8]
|
|
4201
|
+
- Updated dependencies [689b979]
|
|
4202
|
+
- Updated dependencies [e546222]
|
|
4203
|
+
- Updated dependencies [fd13f52]
|
|
4204
|
+
- Updated dependencies [fffa30d]
|
|
4205
|
+
- Updated dependencies [fb336df]
|
|
4206
|
+
- Updated dependencies [20e317c]
|
|
4207
|
+
- Updated dependencies [0fce2ef]
|
|
4208
|
+
- Updated dependencies [42df928]
|
|
4209
|
+
- Updated dependencies [8e00bfd]
|
|
4210
|
+
- Updated dependencies [8d37efb]
|
|
4211
|
+
- Updated dependencies [0e2ddd4]
|
|
4212
|
+
- Updated dependencies [b7479ab]
|
|
4213
|
+
- Updated dependencies [9118a31]
|
|
4214
|
+
- Updated dependencies [de570cc]
|
|
4215
|
+
- Updated dependencies [b2ea297]
|
|
4216
|
+
- Updated dependencies [5b5a5c3]
|
|
4217
|
+
- Updated dependencies [14582b8]
|
|
4218
|
+
- Updated dependencies [51e144e]
|
|
4219
|
+
- Updated dependencies [19cbf10]
|
|
4220
|
+
- Updated dependencies [a691c0b]
|
|
4221
|
+
- Updated dependencies [1e66879]
|
|
4222
|
+
- Updated dependencies [c5200f0]
|
|
4223
|
+
- Updated dependencies [af3861f]
|
|
4224
|
+
- Updated dependencies [515f171]
|
|
4225
|
+
- Updated dependencies [1f4e029]
|
|
4226
|
+
- Updated dependencies [7b43319]
|
|
4227
|
+
- Updated dependencies [258d264]
|
|
4228
|
+
- Updated dependencies [8033ad1]
|
|
4229
|
+
- Updated dependencies [c00bf28]
|
|
4230
|
+
- Updated dependencies [93127bd]
|
|
4231
|
+
- Updated dependencies [f2158ec]
|
|
4232
|
+
- Updated dependencies [759606e]
|
|
4233
|
+
- Updated dependencies [fd8dace]
|
|
4234
|
+
- Updated dependencies [a51fa0c]
|
|
4235
|
+
- Updated dependencies [51f3d8d]
|
|
4236
|
+
- Updated dependencies [78cbdb5]
|
|
4237
|
+
- Updated dependencies [b7543a9]
|
|
4238
|
+
- Updated dependencies [6c6cee7]
|
|
4239
|
+
- Updated dependencies [83fe6e7]
|
|
4240
|
+
- Updated dependencies [d1ab06f]
|
|
4241
|
+
- Updated dependencies [38a9568]
|
|
4242
|
+
- Updated dependencies [91783c4]
|
|
4243
|
+
- Updated dependencies [ca39427]
|
|
4244
|
+
- Updated dependencies [2d36552]
|
|
4245
|
+
- Updated dependencies [b2437a7]
|
|
4246
|
+
- Updated dependencies [f157423]
|
|
4247
|
+
- Updated dependencies [7a90afd]
|
|
4248
|
+
- Updated dependencies [eddc1dd]
|
|
4249
|
+
- Updated dependencies [c9327c9]
|
|
4250
|
+
- Updated dependencies [920165d]
|
|
4251
|
+
- Updated dependencies [968dc1e]
|
|
4252
|
+
- Updated dependencies [3c73d99]
|
|
4253
|
+
- Updated dependencies [ed71d9e]
|
|
4254
|
+
- Updated dependencies [7776fc2]
|
|
4255
|
+
- Updated dependencies [e76634c]
|
|
4256
|
+
- Updated dependencies [fb96ecb]
|
|
4257
|
+
- Updated dependencies [1170ed1]
|
|
4258
|
+
- Updated dependencies [f75810e]
|
|
4259
|
+
- Updated dependencies [92814db]
|
|
4260
|
+
- Updated dependencies [4d73b07]
|
|
4261
|
+
- @object-ui/core@17.7.0
|
|
4262
|
+
- @object-ui/types@17.7.0
|
|
4263
|
+
- @object-ui/i18n@17.7.0
|
|
4264
|
+
- @object-ui/data-objectstack@17.7.0
|
|
4265
|
+
|
|
3
4266
|
## 17.6.0
|
|
4
4267
|
|
|
5
4268
|
### Minor Changes
|