@object-ui/react 17.3.0 → 17.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1242 -0
- package/README.md +48 -21
- package/dist/LazyPluginLoader.d.ts +14 -7
- package/dist/LazyPluginLoader.js +14 -7
- package/dist/SchemaRenderer.d.ts +78 -5
- package/dist/SchemaRenderer.js +572 -34
- package/dist/context/AppShellContext.d.ts +21 -2
- package/dist/context/HostNavigationContext.d.ts +82 -0
- package/dist/context/HostNavigationContext.js +65 -0
- package/dist/context/RecordContext.d.ts +0 -4
- package/dist/context/RecordContext.js +0 -2
- package/dist/context/RelatedRecordActionsContext.d.ts +44 -0
- package/dist/context/RelatedRecordActionsContext.js +6 -0
- package/dist/context/index.d.ts +13 -12
- package/dist/context/index.js +13 -12
- package/dist/element-data-source/ElementDataSourceGate.d.ts +272 -0
- package/dist/element-data-source/ElementDataSourceGate.js +274 -0
- package/dist/hooks/index.d.ts +35 -32
- package/dist/hooks/index.js +42 -32
- package/dist/hooks/useActionEngine.js +1 -1
- package/dist/hooks/useActionTextLocalizer.d.ts +69 -0
- package/dist/hooks/useActionTextLocalizer.js +91 -0
- package/dist/hooks/useCapabilityGate.js +6 -5
- package/dist/hooks/useClientNotifications.js +2 -2
- package/dist/hooks/useCrudShortcuts.js +1 -1
- package/dist/hooks/useDatasetDimensionLabels.d.ts +104 -0
- package/dist/hooks/useDatasetDimensionLabels.js +154 -0
- package/dist/hooks/useDiscovery.js +1 -1
- package/dist/hooks/useElementDataSource.d.ts +68 -0
- package/dist/hooks/useElementDataSource.js +141 -0
- package/dist/hooks/useExpression.d.ts +82 -3
- package/dist/hooks/useExpression.js +121 -4
- package/dist/hooks/useNavigationOverlay.d.ts +59 -11
- package/dist/hooks/useOffline.d.ts +62 -36
- package/dist/hooks/useOffline.js +2 -2
- package/dist/hooks/usePageAssignment.js +5 -5
- package/dist/hooks/usePageTransition.js +1 -1
- package/dist/hooks/usePageVariableActionBridge.js +2 -2
- package/dist/hooks/useViewData.js +1 -1
- package/dist/hooks/useViewTransition.js +1 -1
- package/dist/index.d.ts +11 -9
- package/dist/index.js +12 -8
- package/dist/schema-input.d.ts +42 -0
- package/dist/schema-input.js +42 -0
- package/dist/spec-bridge/SpecBridge.d.ts +12 -6
- package/dist/spec-bridge/SpecBridge.js +9 -3
- package/dist/spec-bridge/bridges/form-view.d.ts +99 -23
- package/dist/spec-bridge/bridges/form-view.js +51 -7
- package/dist/spec-bridge/bridges/list-view.d.ts +1 -1
- package/dist/spec-bridge/bridges/list-view.js +143 -24
- package/dist/spec-bridge/index.d.ts +4 -4
- package/dist/spec-bridge/index.js +3 -3
- package/dist/spec-bridge/types.d.ts +24 -3
- package/dist/utils/error-message.d.ts +59 -0
- package/dist/utils/error-message.js +116 -1
- package/dist/utils/i18n.d.ts +43 -8
- package/dist/utils/i18n.js +42 -4
- package/dist/utils/unevaluatedExpression.d.ts +69 -0
- package/dist/utils/unevaluatedExpression.js +194 -0
- package/dist/utils/visibilityDiagnostic.d.ts +119 -0
- package/dist/utils/visibilityDiagnostic.js +284 -0
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,1232 @@
|
|
|
1
1
|
# @object-ui/react
|
|
2
2
|
|
|
3
|
+
## 17.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f1d4748: Remove the retired `striped` / `bordered` / `virtualScroll` list-view surface
|
|
8
|
+
|
|
9
|
+
objectstack#7176 retired `list.striped`, `list.bordered` and `list.virtualScroll`
|
|
10
|
+
from the spec after measuring every objectui reader as pass-through: each one
|
|
11
|
+
copied the key onward and no renderer ever applied it. objectui stops declaring,
|
|
12
|
+
typing and forwarding them.
|
|
13
|
+
|
|
14
|
+
Off the chain: the `@objectstack/spec` list-view bridge in `@object-ui/react`,
|
|
15
|
+
`ListView`'s child-view props in `@object-ui/plugin-list`, both `ObjectView`
|
|
16
|
+
relays (`@object-ui/plugin-view` and `@object-ui/app-shell`), the `ObjectGridSchema`
|
|
17
|
+
and `NamedListView` declarations in `@object-ui/types` (interface and zod),
|
|
18
|
+
`ObjectGrid.component.yml` in `@object-ui/components`, and the page-block
|
|
19
|
+
inspector's `striped` / `bordered` toggles in the metadata-admin designer.
|
|
20
|
+
|
|
21
|
+
Behaviour is unchanged: nothing read these keys, so nothing rendered differently
|
|
22
|
+
for them. Stored view metadata that still carries one keeps validating — the keys
|
|
23
|
+
are simply no longer relayed. `ListViewSchema` continues to take the spec's
|
|
24
|
+
list-view fields by reference, so the protocol's own retirement tombstones
|
|
25
|
+
arrive with the next `@objectstack/spec` bump and reject the keys at the
|
|
26
|
+
authoring boundary. Restoring any of the three as live surface requires an
|
|
27
|
+
implementation card filed first, per the ruling.
|
|
28
|
+
- 8c0d52e: A form's ruled `submitBehavior.url` redirect can now be performed by the HOST, so a destination stays inside a console mounted at a sub-path (objectui#4989 defect 4).
|
|
29
|
+
|
|
30
|
+
`ObjectForm` and `WizardForm` accept a relative in-app path and used to travel to it with `window.location.assign`. A rooted path resolves against the ORIGIN there, so under a mounted host — `<BrowserRouter basename="/_console">`, which the framework CLI configures for every embedded deployment — an authored `/thanks` left the application. The destination was correct and the navigation was wrong, and a published renderer cannot fix that alone: only the host knows its mount.
|
|
31
|
+
|
|
32
|
+
**New: `HostNavigationContext` (`@object-ui/react`).** A host offers its own navigate; a renderer uses it when it is there:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { HostNavigationProvider } from '@object-ui/react';
|
|
36
|
+
import { useNavigate } from 'react-router-dom';
|
|
37
|
+
|
|
38
|
+
function Bridge({ children }) {
|
|
39
|
+
const navigate = useNavigate();
|
|
40
|
+
return (
|
|
41
|
+
<HostNavigationProvider value={{ navigate: (to, o) => navigate(to, { replace: o?.replace ?? false }) }}>
|
|
42
|
+
{children}
|
|
43
|
+
</HostNavigationProvider>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`@object-ui/app-shell`'s `ConsoleShell` now mounts that bridge above every console route, so the console's own routes, and its basename, are what a post-submit redirect resolves against — and the redirect becomes an SPA transition instead of a full page load.
|
|
49
|
+
|
|
50
|
+
**Nothing changes for a host that wires nothing.** With no provider the behaviour is byte-for-byte what it was: one `window.location.assign` of the resolved path after the declared `delayMs`. A host with no router has no basename, so origin-rooted resolution is already right there — the seam changes what a MOUNTED host gets and nothing else. `useHostNavigation()` outside a provider answers `{ navigate: undefined }` and never throws.
|
|
51
|
+
|
|
52
|
+
Two mechanisms were weighed and rejected by the ruling, recorded so they are not re-proposed. **Reading React Router's context when a router happens to be present** is implicit, and unavailable anyway: a React context is a module-instance object, so reading the host's means importing `react-router` into the renderer — which `@object-ui/plugin-form` declares in none of its dependency fields, whose published build externalises every bare specifier, and which two real consumers (`apps/site`, `packages/plugin-view`) do not install. **Requiring `react-router` as a peer** is the honest version of the same thing and costs the package its property of dropping into any React application.
|
|
53
|
+
|
|
54
|
+
What the host takes on by supplying a navigate: the destination becomes a client-side transition, so an in-app path with no matching route renders the host's not-found instead of a full page load. That is the host's routing table to answer for, which is why the choice is the host's. The contract verdict is unchanged either way — a destination `@objectstack/spec` refuses is refused identically with or without a seam, so an injected navigate can never launder a value the authoring door rejects.
|
|
55
|
+
|
|
56
|
+
`navigateOnSuccess` is a different declared key with its own open contract question (objectui#5034) and is deliberately untouched here.
|
|
57
|
+
- dbbd38a: fix(react): `bridgeListView` emits the column spelling the spec declares
|
|
58
|
+
|
|
59
|
+
`mapColumn` took a spec-canonical `ListColumn` — whose columns are **already**
|
|
60
|
+
spelled `field` / `label` — and down-translated every one of them to
|
|
61
|
+
`{ accessorKey, header }` before emitting the `object-grid` node, which
|
|
62
|
+
`ObjectGrid` then translated back. A round trip through a spelling
|
|
63
|
+
`ListColumnSchema` refuses by name, on a value that arrived canonical. The
|
|
64
|
+
bridge now forwards the declared shape, and the tolerance branch on the other
|
|
65
|
+
side retires in the same release (see `@object-ui/plugin-grid`).
|
|
66
|
+
|
|
67
|
+
**Output shape.** `bridgeListView` / `SpecBridge.transformListView` emit
|
|
68
|
+
`columns: [{ field, label?, … }]`. Code reading `node.columns[i].accessorKey`
|
|
69
|
+
off a bridged node reads `field` instead; `header` becomes `label`. The bare
|
|
70
|
+
string shorthand `columns: ['name']` now maps to `{ field: 'name' }`.
|
|
71
|
+
|
|
72
|
+
**No label is invented any more.** `header: col.label ?? col.field` turned "the
|
|
73
|
+
author declared no label" into "the author declared the machine name", and that
|
|
74
|
+
synthesized value pre-empted `ObjectGrid`'s own header chain — the column's
|
|
75
|
+
label, then the **object field's** label, then the prettified machine name —
|
|
76
|
+
whose middle step exists so a localized field label wins on a non-English app.
|
|
77
|
+
A bridged view therefore rendered raw machine names where a directly authored
|
|
78
|
+
`object-grid` rendered the field's real label. A bare `{ field }` column now
|
|
79
|
+
reaches that chain intact.
|
|
80
|
+
|
|
81
|
+
Speaking the declared spelling also routes bridged views through the renderer's
|
|
82
|
+
full ListColumn path rather than its type-inference-only one: object-schema
|
|
83
|
+
field enrichment, `hidden` filtering, primary-field auto-linking, and per-column
|
|
84
|
+
`link` / `action` handling now apply to a bridged `ListView` exactly as they do
|
|
85
|
+
to an authored grid.
|
|
86
|
+
|
|
87
|
+
### Patch Changes
|
|
88
|
+
|
|
89
|
+
- 516663d: RecordAttachmentsPanel no longer offers a Retry for an api-disabled `sys_attachment` read.
|
|
90
|
+
|
|
91
|
+
`OBJECT_API_DISABLED` (404, `enable.apiEnabled: false`) and its sibling
|
|
92
|
+
`OBJECT_API_METHOD_NOT_ALLOWED` (405, the operation is absent from
|
|
93
|
+
`enable.apiMethods`) are pure functions of the object's metadata — no user, no
|
|
94
|
+
session, no request body — so every retry of every persona re-fetches the
|
|
95
|
+
identical refusal. Before this change both landed in `RecordAttachmentsPanel`'s
|
|
96
|
+
`unavailable` state and offered a Retry that was guaranteed to change nothing,
|
|
97
|
+
the same wrong advice `ListView`'s error panel already stops giving for list
|
|
98
|
+
reads.
|
|
99
|
+
|
|
100
|
+
The panel gains a fifth status, `api-unavailable`: no Retry button, and honest
|
|
101
|
+
copy ("The attachments list is not available on this object.", new
|
|
102
|
+
`detail.attachmentsApiUnavailable` key in all ten locale packs) instead of
|
|
103
|
+
"We couldn't load the attachments for this record." The pre-existing `denied`
|
|
104
|
+
(authorization) and `unavailable` (network/5xx/expired-session) states and
|
|
105
|
+
their affordances are unchanged.
|
|
106
|
+
|
|
107
|
+
`ListView.classifyLoadError` — the classifier that already separated this case
|
|
108
|
+
into its own `api-disabled` kind for list views — is lifted out of
|
|
109
|
+
`packages/plugin-list/src/ListView.tsx`'s module scope into
|
|
110
|
+
`@object-ui/react` (`classifyLoadError`, `LoadErrorKind`), so both surfaces
|
|
111
|
+
consume one classification instead of `RecordAttachmentsPanel` re-deriving it.
|
|
112
|
+
`ListView`'s own behavior is unchanged — it now imports the function it
|
|
113
|
+
previously defined locally. The classifier delegates its api-disabled check to
|
|
114
|
+
`isApiAccessDeniedError` (`@object-ui/data-objectstack`), removing a second,
|
|
115
|
+
independently-maintained copy of the same code list.
|
|
116
|
+
- d374caf: SpecBridge's form-view bridge stops reading `defaultSort` and `aria`, two keys spec 17
|
|
117
|
+
retired on the FormView carrier (`retiredKey()` tombstones — authoring either is a parse
|
|
118
|
+
error). Both guards were unreachable for any FormView that passed validation, and both
|
|
119
|
+
ends were measured dead before removal: no form renderer reads either off the node
|
|
120
|
+
(`plugin-form` reads neither, and `SchemaRenderer`'s ARIA injection resolves flat
|
|
121
|
+
`ariaLabel`/`ariaDescribedBy`/`role`, never a nested `aria` object). Behaviour for
|
|
122
|
+
spec-valid metadata is unchanged; a host that fed the exported bridge a raw pre-17
|
|
123
|
+
document no longer gets these two keys copied onto a node slot nothing consumed. The
|
|
124
|
+
LIST view's `aria` pass-through is untouched — that carrier stayed live and is applied by
|
|
125
|
+
`ListView`.
|
|
126
|
+
- af5e292: Emit explicit file extensions on relative import specifiers, so the published
|
|
127
|
+
entries can be imported by Node's own ESM resolver.
|
|
128
|
+
|
|
129
|
+
`@object-ui/react`'s built entry re-exported through extensionless relative
|
|
130
|
+
specifiers (`export * from './SchemaRenderer'`). Node does not extension-search
|
|
131
|
+
relative specifiers, so `import('@object-ui/react')` under plain Node — an SSR
|
|
132
|
+
host, or any consumer without a bundler — failed with `ERR_MODULE_NOT_FOUND`.
|
|
133
|
+
Bundled consumers were never affected and are unchanged by this.
|
|
134
|
+
|
|
135
|
+
`@object-ui/types`, `@object-ui/core` and `@object-ui/i18n` carried the same
|
|
136
|
+
emission; `@object-ui/react`'s entry stayed unloadable until they were fixed
|
|
137
|
+
too, because evaluation crosses into them. No exported API changed.
|
|
138
|
+
- 0046d8f: Evaluate expressions written under a node's `properties`, not just under `props`
|
|
139
|
+
|
|
140
|
+
`properties` is the spec spelling of a node's config bag and `props` is the
|
|
141
|
+
legacy alias, but `SchemaRenderer`'s evaluation memo only ran the expression
|
|
142
|
+
evaluator over `props`. Renderers in the `element:*` namespace read
|
|
143
|
+
`schema.properties` first, so a node written the canonical way handed the
|
|
144
|
+
renderer the raw `${…}` source and rendered it verbatim, while the same node
|
|
145
|
+
written with the alias evaluated correctly — writing the spec-compliant form
|
|
146
|
+
was the way to lose your expressions.
|
|
147
|
+
|
|
148
|
+
`schema.properties` values are now evaluated per value, exactly as `props`
|
|
149
|
+
already was, and the evaluation runs before the existing
|
|
150
|
+
`properties`-to-top-level hoist so a key means the same thing whether it is read
|
|
151
|
+
as `schema.properties.x`, as `schema.x`, or as the spread `x` React prop.
|
|
152
|
+
Evaluation stays shallow on both spellings (nested objects and arrays are passed
|
|
153
|
+
through, not walked), and `properties` keeps its precedence over `props`.
|
|
154
|
+
- 70a774b: A node writing both `properties` and `props` now gets ONE answer per key, and it is the canonical `properties` one — on both read channels.
|
|
155
|
+
|
|
156
|
+
`properties` is the spec spelling of a node's config bag and `props` is the
|
|
157
|
+
annotated legacy alias, but which one actually reached the screen depended on
|
|
158
|
+
how the receiving renderer happened to read it — and the two channels disagreed
|
|
159
|
+
in opposite directions:
|
|
160
|
+
|
|
161
|
+
- **config bag** (`schema.properties.x`) — the `element:*` family's
|
|
162
|
+
`readProps()` merges `{ ...schema.props, ...schema.properties }`, so
|
|
163
|
+
`properties` won.
|
|
164
|
+
- **React prop** (`x` arriving as a prop) — `SchemaRenderer`'s `createElement`
|
|
165
|
+
spread the hoisted `properties.*` values first and then
|
|
166
|
+
`...(evaluatedSchema.props || {})` last, which overwrote them, so `props` won.
|
|
167
|
+
|
|
168
|
+
Measured on one render of one such node: the bag read `FROM_PROPERTIES` while
|
|
169
|
+
the same key read as a React prop gave `FROM_PROPS`. Which value rendered was
|
|
170
|
+
decided by nothing an author can see — only by whether their chosen component
|
|
171
|
+
belonged to the `readProps()` family.
|
|
172
|
+
|
|
173
|
+
The React-prop channel now declines to let the legacy alias override a key the
|
|
174
|
+
canonical bag also declares; the config-bag order was already correct and is
|
|
175
|
+
unchanged. Scope is co-occurrence only: a key that only `props` declares still
|
|
176
|
+
works exactly as before, and a node that writes one spelling is untouched. The
|
|
177
|
+
`props` alias is not retired here — only its precedence against a co-present
|
|
178
|
+
canonical spelling is settled.
|
|
179
|
+
- dfc6975: Related-list "+ New" now honours `userActions.create` predicates, and the grid
|
|
180
|
+
toolbar's inline-edit affordance is gated on `update` permission (objectui#4646,
|
|
181
|
+
objectui#4647).
|
|
182
|
+
|
|
183
|
+
Two declared-but-unenforced gaps on the same toolbar surface.
|
|
184
|
+
|
|
185
|
+
**#4646 — `createPredicates` had a producer and no consumer.**
|
|
186
|
+
`@objectstack/spec@17.0.0` widened `userActions.create` to
|
|
187
|
+
`z.union([z.boolean(), RowCrudActionOverrideSchema])`, so `resolveCrudAffordances`
|
|
188
|
+
emits `createPredicates` — and nothing in objectui read them, against roughly
|
|
189
|
+
fifteen consumption sites apiece for `editPredicates` / `deletePredicates`. The
|
|
190
|
+
symptom: a parent record entering a frozen state correctly greyed its children's
|
|
191
|
+
row Edit/Delete while the related list's "+ New" stayed fully live, so the user
|
|
192
|
+
filled in the whole child form to earn a server 409. The related-list toolbar now
|
|
193
|
+
evaluates `visibleWhen` / `disabledWhen` **once against the host parent record**,
|
|
194
|
+
per the spec docblock's binding for this key, on top of the existing
|
|
195
|
+
`o.create ∧ can(child, 'create')` check. `visibleWhen` hides "+ New" and fails
|
|
196
|
+
CLOSED; `disabledWhen` greys it and fails SOFT — the same evaluator, fail
|
|
197
|
+
directions and hidden-vs-disabled split the record header already uses for
|
|
198
|
+
edit/delete (objectui#4419 / PR #4515). A bare-boolean `userActions.create` is
|
|
199
|
+
untouched: with no predicates there is nothing to evaluate.
|
|
200
|
+
|
|
201
|
+
**#4647 — the inline-edit toggle was the one ungated affordance on its toolbar.**
|
|
202
|
+
It rendered on "grid view ∧ the host wired `onInlineEditChange` ∧ not the compact
|
|
203
|
+
toolbar", and every host wires that callback unconditionally. New and Import are
|
|
204
|
+
hidden for an account without the grant and the bulk-delete entry on the same
|
|
205
|
+
toolbar ANDs `can(obj, 'delete')`, but a read-only principal could flip inline
|
|
206
|
+
edit, modify cells and press "Save all" to earn a server 403. It is now gated on
|
|
207
|
+
the object's resolved edit affordance ∧ `can(object, 'update')`, mirroring that
|
|
208
|
+
bulk-delete gate. The gate is applied at all three sites that carry this
|
|
209
|
+
affordance — the wide toolbar's toggle, the compact toolbar's settings-popover
|
|
210
|
+
entry (which previously had no gate at all, not even the callback), and the
|
|
211
|
+
`editable` mode handed to the grid, so a stored view carrying `inlineEdit: true`
|
|
212
|
+
can no longer drop a read-only principal into editable cells with no toggle to
|
|
213
|
+
press.
|
|
214
|
+
|
|
215
|
+
`ListViewSchema.userActions.editInline` is also consumed now: an explicit `false`
|
|
216
|
+
withholds the affordance wholesale, which authors previously could not do.
|
|
217
|
+
|
|
218
|
+
**Behaviour change for read-only users, stated plainly.** Where the UI used to
|
|
219
|
+
offer inline editing and let the server refuse it, it now declines to offer the
|
|
220
|
+
entry point at all. No data access changes — the server gate was and remains the
|
|
221
|
+
enforcement boundary; this only stops the UI walking users into round-trips
|
|
222
|
+
guaranteed to fail. Accounts *with* the grant see no change, and hosts with no
|
|
223
|
+
`PermissionProvider` mounted (standalone embeds, the Studio designer) keep
|
|
224
|
+
today's behaviour, since `can()` answers `true` there by design.
|
|
225
|
+
|
|
226
|
+
One deliberate non-change: the absent case of `userActions.editInline` defers to
|
|
227
|
+
the host's existing `inlineEdit` channel rather than enforcing the spec's
|
|
228
|
+
`.default(false)`. Enforcing that default would remove the toggle from every
|
|
229
|
+
stored console list view in one release, since nothing folds a legacy key into
|
|
230
|
+
`editInline` and no existing view declares it. This follows the rule the
|
|
231
|
+
surrounding toolbar-flag block already states for itself — defaults chosen to
|
|
232
|
+
match what the flags have always done. `InterfaceListPage`, the key's other
|
|
233
|
+
consumer, reads the absent case as OFF, because the ADR-0047 interface page has
|
|
234
|
+
no such host channel to defer to.
|
|
235
|
+
- 144ef9b: Dev builds now shout when an unevaluated `${…}` expression reaches the DOM.
|
|
236
|
+
|
|
237
|
+
A value only reaches the user if `SchemaRenderer` EVALUATES it and the renderer
|
|
238
|
+
READS IT BACK, and those two sets do not fully overlap. Where they miss, the
|
|
239
|
+
failure was silent. Measured on a real render with `dataSource: { n: 99 }`:
|
|
240
|
+
`{ type: 'ui:statistic', value: '${data.n}' }` puts the literal text `${data.n}`
|
|
241
|
+
on screen, because the evaluation memo covers `content`, the `properties` /
|
|
242
|
+
`props` bags and the predicate keys and passes every other top-level key through
|
|
243
|
+
untouched. An author — increasingly, an AI authoring metadata — got no signal at
|
|
244
|
+
all: a literal `${data.n}` in front of a user reads like a data problem rather
|
|
245
|
+
than a contract violation.
|
|
246
|
+
|
|
247
|
+
`SchemaRenderer` now reports such a value once per node via `console.error`,
|
|
248
|
+
naming the node type and id, the key the raw source survived on (spelled the way
|
|
249
|
+
it was authored — `properties.value`, not the hoisted top-level copy), the
|
|
250
|
+
expression source verbatim, and the channels that do work today. It also catches
|
|
251
|
+
the second, harder shape: an expression that WAS evaluated but THREW, which
|
|
252
|
+
`ExpressionEvaluator` returns as its source text — indistinguishable on screen
|
|
253
|
+
from a key that was never evaluated.
|
|
254
|
+
|
|
255
|
+
Diagnostic only. No evaluation behaviour changes, no DOM attribute is added, and
|
|
256
|
+
the whole module is behind the module-load `NODE_ENV` constant a bundler folds
|
|
257
|
+
away, so production pays nothing.
|
|
258
|
+
|
|
259
|
+
Two boundaries are deliberate. The scan is exactly as deep as evaluation is —
|
|
260
|
+
shallow — because a nested `aria: { label: '${…}' }` keeps its raw source today
|
|
261
|
+
by decision (objectui#4799 pinned that shallowness on both bags), and a deeper
|
|
262
|
+
scan would report a shape the engine has not yet decided to change. And schema
|
|
263
|
+
METADATA is never reported: `visible` / `visibleWhen` / `hidden` / `disabled` /
|
|
264
|
+
… hold raw predicate source by design, and the diagnostic reads the set of
|
|
265
|
+
values that actually leaves for the DOM, after those keys have been stripped.
|
|
266
|
+
|
|
267
|
+
Part of objectui#4795 (Direction 3, per the maintainer's 2026-08-17 ruling).
|
|
268
|
+
Widening the set of evaluated text keys is Direction 1 and stays deferred behind
|
|
269
|
+
its named restart condition; this change deliberately does not pre-empt it.
|
|
270
|
+
- Updated dependencies [88085e3]
|
|
271
|
+
- Updated dependencies [69251bf]
|
|
272
|
+
- Updated dependencies [57e668f]
|
|
273
|
+
- Updated dependencies [516663d]
|
|
274
|
+
- Updated dependencies [41ac1b7]
|
|
275
|
+
- Updated dependencies [1eaf0a1]
|
|
276
|
+
- Updated dependencies [2533ec5]
|
|
277
|
+
- Updated dependencies [bbe8b86]
|
|
278
|
+
- Updated dependencies [8477be5]
|
|
279
|
+
- Updated dependencies [279fb13]
|
|
280
|
+
- Updated dependencies [2e82ab2]
|
|
281
|
+
- Updated dependencies [ad07b65]
|
|
282
|
+
- Updated dependencies [41f498b]
|
|
283
|
+
- Updated dependencies [ef0d150]
|
|
284
|
+
- Updated dependencies [1ef236e]
|
|
285
|
+
- Updated dependencies [f34226e]
|
|
286
|
+
- Updated dependencies [564b605]
|
|
287
|
+
- Updated dependencies [e1d4251]
|
|
288
|
+
- Updated dependencies [1184192]
|
|
289
|
+
- Updated dependencies [a2a9747]
|
|
290
|
+
- Updated dependencies [a1609a6]
|
|
291
|
+
- Updated dependencies [37f6844]
|
|
292
|
+
- Updated dependencies [2b50261]
|
|
293
|
+
- Updated dependencies [ac600e5]
|
|
294
|
+
- Updated dependencies [c1ef923]
|
|
295
|
+
- Updated dependencies [af5e292]
|
|
296
|
+
- Updated dependencies [7f96b10]
|
|
297
|
+
- Updated dependencies [167ec42]
|
|
298
|
+
- Updated dependencies [cf4f8a6]
|
|
299
|
+
- Updated dependencies [f1d4748]
|
|
300
|
+
- Updated dependencies [bea374e]
|
|
301
|
+
- Updated dependencies [b1119ec]
|
|
302
|
+
- Updated dependencies [9f23d2b]
|
|
303
|
+
- Updated dependencies [578e025]
|
|
304
|
+
- Updated dependencies [af025ee]
|
|
305
|
+
- Updated dependencies [d109a4d]
|
|
306
|
+
- Updated dependencies [3d053bb]
|
|
307
|
+
- Updated dependencies [598c89a]
|
|
308
|
+
- Updated dependencies [b8b9af4]
|
|
309
|
+
- Updated dependencies [31676be]
|
|
310
|
+
- Updated dependencies [9ce096f]
|
|
311
|
+
- Updated dependencies [e05db88]
|
|
312
|
+
- Updated dependencies [ad13d63]
|
|
313
|
+
- Updated dependencies [5ffcc14]
|
|
314
|
+
- Updated dependencies [d971e51]
|
|
315
|
+
- Updated dependencies [97abb24]
|
|
316
|
+
- Updated dependencies [deb157a]
|
|
317
|
+
- Updated dependencies [9c60144]
|
|
318
|
+
- Updated dependencies [d2ce342]
|
|
319
|
+
- Updated dependencies [a8411ad]
|
|
320
|
+
- Updated dependencies [9695da7]
|
|
321
|
+
- Updated dependencies [58b8346]
|
|
322
|
+
- Updated dependencies [a9e17b4]
|
|
323
|
+
- Updated dependencies [b8ce7dc]
|
|
324
|
+
- Updated dependencies [8871c14]
|
|
325
|
+
- Updated dependencies [dfc6975]
|
|
326
|
+
- Updated dependencies [3cf4de0]
|
|
327
|
+
- Updated dependencies [c9dc811]
|
|
328
|
+
- Updated dependencies [d871f8e]
|
|
329
|
+
- Updated dependencies [a0b9e91]
|
|
330
|
+
- Updated dependencies [99bd015]
|
|
331
|
+
- Updated dependencies [21e4585]
|
|
332
|
+
- @object-ui/types@17.6.0
|
|
333
|
+
- @object-ui/i18n@17.6.0
|
|
334
|
+
- @object-ui/core@17.6.0
|
|
335
|
+
- @object-ui/data-objectstack@17.6.0
|
|
336
|
+
|
|
337
|
+
## 17.5.0
|
|
338
|
+
|
|
339
|
+
### Minor Changes
|
|
340
|
+
|
|
341
|
+
- d9d3463: Retire four zero-consumer declared surfaces (dead-surface sweep batch 3, #4328). Each was
|
|
342
|
+
measured as declared-but-never-read at the branch point, and each is removed rather than
|
|
343
|
+
left as an authoring surface whose values nothing acts on.
|
|
344
|
+
|
|
345
|
+
Breaking for anyone who typed against the removed declarations, marked `minor` per this
|
|
346
|
+
repository's version-alignment convention (the major tracks `@objectstack`, never an
|
|
347
|
+
API-break count):
|
|
348
|
+
|
|
349
|
+
- `@object-ui/core` no longer exports `mergeViewsIntoObjects`. It was a second copy left
|
|
350
|
+
behind by the move of that step to the provider layer, and it had drifted: it ignored a
|
|
351
|
+
view container's default `list` and keyed views by the authored bare key instead of the
|
|
352
|
+
composer's `<object>.<key>` identity. The live implementation — `MetadataProvider`'s, in
|
|
353
|
+
`@object-ui/app-shell` — is unchanged and remains the only one. (#3775)
|
|
354
|
+
- `@object-ui/types`' `RoleDefinition` no longer declares `permissions`. A role's grants
|
|
355
|
+
live in `ObjectPermissionConfig.roles`, keyed by object; that is the only home any
|
|
356
|
+
consumer reads (`resolveRoles` walks `inherits` and matches on `name`). The removed
|
|
357
|
+
field was _required_, so five fixtures across three packages had been declaring an empty
|
|
358
|
+
array for a value nothing would ever look at. Role-attached grants are now a compile
|
|
359
|
+
error rather than silently ignored data. (#4288)
|
|
360
|
+
- `@object-ui/react`'s `RecordContextValue` no longer declares `loading` / `error`. Both
|
|
361
|
+
had zero producers and zero consumers — no host passed them, no `record:*` renderer read
|
|
362
|
+
them — and only the provider's memo dependency list still named them. Record-level
|
|
363
|
+
loading and error state stays where it is actually expressed: each renderer's own data
|
|
364
|
+
source. (#3773)
|
|
365
|
+
|
|
366
|
+
No behaviour change, no request-count change:
|
|
367
|
+
|
|
368
|
+
- `@object-ui/data-objectstack` drops five `metadataCache.invalidate('views:<object>')`
|
|
369
|
+
calls across `updateViewConfig` / `createView` / `updateView` / `deleteView`. No read
|
|
370
|
+
path has ever populated that key — `listViews` fetches directly, uncached — so all five
|
|
371
|
+
were permanent no-ops. The invalidations of the keys that do have readers
|
|
372
|
+
(`view:<object>:<viewId>` for `getView`, `view-overrides:<object>` for
|
|
373
|
+
`listViewOverrides`) are untouched and now pinned. (#3778)
|
|
374
|
+
|
|
375
|
+
- b953a97: fix(detail): lookup field values link to the referenced record
|
|
376
|
+
|
|
377
|
+
A valued lookup on a record detail page rendered as plain text plus a copy
|
|
378
|
+
button — the referenced document's name was visible but unreachable, so users
|
|
379
|
+
copied the number and searched for it from the list page instead. Lookup cells
|
|
380
|
+
inside a related list that pointed at a third object were dead the same way.
|
|
381
|
+
|
|
382
|
+
`LookupCellRenderer` — the one cell renderer both surfaces resolve through —
|
|
383
|
+
now renders the display value as a link to the referenced record. The display
|
|
384
|
+
name resolution, the copy affordance and every non-lookup field are unchanged,
|
|
385
|
+
and a lookup with no value still renders its placeholder rather than an empty
|
|
386
|
+
link.
|
|
387
|
+
|
|
388
|
+
The URL is not assembled in the renderer. `RelatedRecordActionsContext` gains
|
|
389
|
+
an optional `recordHref` / `openRecord` pair, published by the console's
|
|
390
|
+
`RelatedRecordActionsBridge` from the SAME builder its related-list row
|
|
391
|
+
navigation already used, so there is one record-route shape rather than a
|
|
392
|
+
second one. A host that does not provide it (Studio designer, embedded
|
|
393
|
+
renderers, standalone grids) renders exactly what it rendered before.
|
|
394
|
+
|
|
395
|
+
- d7f3e30: `bridgeListView` maps the five row heights the spec admits, and only those — the four dead spellings are gone
|
|
396
|
+
|
|
397
|
+
`mapDensity` carried a nine-key table: `compact`, `short`, `comfortable`,
|
|
398
|
+
`spacious`, `small`, `medium`, `large`, `tall`, `extra_tall`. `RowHeightSchema`
|
|
399
|
+
in `@objectstack/spec` admits five — `short | compact | medium | tall |
|
|
400
|
+
extra_tall` — so `comfortable`, `spacious`, `small` and `large` were unreachable
|
|
401
|
+
from any spec-valid list view. The bridge's own parameter type said as much
|
|
402
|
+
(`Partial< ListView >`), and `mapDensity` widened it back to `rowHeight?: string`
|
|
403
|
+
to let them in. They survived because the fixture asserting three of them
|
|
404
|
+
compiled against nothing: the package's build tsconfig excludes tests and no
|
|
405
|
+
other `tsc` read them, so four branches of renderer-side dialect read as live
|
|
406
|
+
capability (objectui#4352, surfaced by objectui#4040 / PR #4351).
|
|
407
|
+
|
|
408
|
+
They are deleted. The parameter takes the spec type honestly, and the table is
|
|
409
|
+
now `Record< RowHeight, … >`, so a row height added upstream fails the build here
|
|
410
|
+
instead of arriving with no density. This is AGENTS.md #0.1: a lenient reading
|
|
411
|
+
for off-spec metadata is a second de-facto contract, and one strict contract
|
|
412
|
+
beats N dialects — a bad `rowHeight` gets fixed at the producer, where the schema
|
|
413
|
+
already rejects it.
|
|
414
|
+
|
|
415
|
+
**Breaking semantics, deliberately graded `minor`** (this repo never publishes
|
|
416
|
+
`major` — its major tracks `@objectstack`). Nothing narrows in the published type
|
|
417
|
+
surface: `mapDensity` is module-local and never appeared in the emitted `.d.ts`,
|
|
418
|
+
and `bridgeListView`'s declaration is unchanged. What changes is runtime output,
|
|
419
|
+
and only for input the spec already rejects: a host handing the bridge
|
|
420
|
+
`rowHeight: 'comfortable'` (or `'spacious'` / `'small'` / `'large'`) used to get
|
|
421
|
+
`density: 'comfortable'` / `'spacious'` / `'compact'` back, and now gets no
|
|
422
|
+
`density` key at all, so the renderer's own default applies. A sweep of this repo,
|
|
423
|
+
the `objectstack` example apps and the console's view metadata found zero authored
|
|
424
|
+
uses of any of the four; the legacy `densityMode` alias cannot produce one either,
|
|
425
|
+
since `DENSITY_MODE_TO_ROW_HEIGHT` is typed `Record< DensityMode, RowHeight >` and
|
|
426
|
+
folds onto `compact` / `medium` / `tall`.
|
|
427
|
+
|
|
428
|
+
- a84385b: `NavigationConfig.mode` is optional — the type now says what the hook does
|
|
429
|
+
|
|
430
|
+
`@object-ui/react` published a `NavigationConfig` that required `mode`, in front of a `useNavigationOverlay` that has always defaulted it. The declaration took the spec's authored config, `Omit`ted `mode`, and re-added it as `NonNullable< … >`; 140 lines below, the hook read `navigation?.mode ?? 'page'`. The type was strictly stricter than the implementation it fronted, and `'page'` is meaningful behaviour rather than a placeholder.
|
|
431
|
+
|
|
432
|
+
The spec never asked for that. `NavigationConfigSchema` declares `mode: NavigationModeSchema.default('page')`, and a `.default()` lands on the authoring side as `| undefined` — so `navigation: { view: 'summary_view' }` is legal authored metadata that lets the mode default. `@object-ui/types` already re-exported the spec's own `NavigationConfig` unchanged, which meant one monorepo shipped two published types of the same name that disagreed about whether `mode` could be omitted.
|
|
433
|
+
|
|
434
|
+
The alias is now the spec's authored config verbatim, with no divergence of its own:
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
export type NavigationConfig = SpecAuthoredInput<
|
|
438
|
+
typeof NavigationConfigSchema
|
|
439
|
+
>;
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
The cost of the old spelling was paid by callers. `ListView` carried `schema.navigation as NavigationConfig | undefined` for no reason except to get a valid spec-shaped value past the declaration; that assertion is deleted here, not replaced. A type in front of an implementation must not be stricter than the implementation — when it is, every caller pays in casts, and a cast is exactly the renderer-side workaround that belongs back at the producer.
|
|
443
|
+
|
|
444
|
+
**Nothing changes at runtime.** `navigation?.mode ?? 'page'` is untouched, and the default is now pinned as observable behaviour (`useNavigationOverlay.modeDefault.test.tsx`) rather than only as a comment — the explicit modes, the `preventNavigation` and `none` short-circuits, the `onRowClick` priority, and the Cmd/Ctrl/middle-click and `new_window` branches are all pinned alongside it.
|
|
445
|
+
|
|
446
|
+
**Why minor rather than patch**, from the measured `.d.ts`. Optional-izing a property is looser for writers and narrower for readers, so the grade turns on which role the published surface actually plays. In this package `NavigationConfig` occurs only in input positions — `useNavigationOverlay`'s `navigation?:` option and `resolveOverlayWidth`'s parameter — and never in a return type; the package consumes these values and never hands one back. For consumers the change is therefore purely permissive: every call that compiled before still compiles, and spec-shaped configs that previously needed an assertion now compile without one. That gained input shape is a real capability rather than an internal repair, which is more than a patch describes. The reader-side narrowing is real but secondary: code that imports the bare type, annotates its own value with it and reads `.mode` now sees `NavigationMode | undefined`. The in-repo census found exactly one such importer — `ListView` — and it imported the type only to write the assertion this change removes.
|
|
447
|
+
|
|
448
|
+
- c1d939f: One `SchemaNode`, and one label vocabulary — the union wins, and labels resolve where the locale lives
|
|
449
|
+
|
|
450
|
+
Two packages published a type called `SchemaNode` and they were not the same type. `@object-ui/core` hand-declared `interface SchemaNode { type: string; … [key: string]: any }`; `@object-ui/types` exported `type SchemaNode = BaseSchema | string | number | boolean | null | undefined`, whose own doc comment names `'Plain string'` a valid node. Both were exported under one name from packages the same consumers import together, so which declaration a call site got depended on which package it happened to import from — #4548's canary measured 19 of 35 errors as exactly that collision. Core's declaration is now a re-export of types', so there is one declaration left to disagree with. Core's entry surface is unchanged: `dist/index.d.ts` is byte-identical across the change.
|
|
451
|
+
|
|
452
|
+
Reconciling it exposed a real defect rather than a mechanical narrowing, which is why the first attempt was withdrawn instead of forced. The spec bridges write `spec.label` — the spec's `I18nLabel`, an INLINE locale map like `{ en: 'Owner', 'zh-CN': '负责人' }` — into `node.label`, and `BaseSchema.label` declared `string`. Under core's old index signature that assignment was invisibly `any`; under one honest `SchemaNode` it is a type error. `BaseSchema.label` and `.description` therefore now accept `string | I18nLabel`, and the two bridge assignments compile with their expressions untouched.
|
|
453
|
+
|
|
454
|
+
Resolution happens at READ time, in the renderer, against the display locale — not at the bridge. Resolving at the bridge was measured unimplementable: it is a plain class method that cannot call a hook, `BridgeContext` declares no locale, and `updateContext()` has zero callers, so a bridge-resolved label would freeze one audience's language into the node tree with no re-translation channel. React's own invalidation re-translates for free at the read site.
|
|
455
|
+
|
|
456
|
+
The widening turned every blind `schema.label`-as-string read into a named compiler error, and that inventory is the audit: it named four sites repo-wide, all one class — the label reaching a React child position, where a map does not render as `[object Object]` but THROWS `Objects are not valid as a React child`, failing the whole subtree. Three are `@object-ui/components` renderers (`filter-builder`, `sidebar-group`, `dropdown-menu`), which now resolve with the spec's own `resolveI18nLabel` against `useDisplayLocale()`. The fourth is `plugin-dashboard`'s `DashboardGridLayout` heading, which resolves with `pickLocalized` against the active UI language — matching the widget-title resolution already in that same component rather than putting two resolvers and two disagreeing locale channels in one render; the two resolvers are limb-for-limb twins with a parity test pinning them.
|
|
457
|
+
|
|
458
|
+
One interface now carries both label vocabularies two properties apart — `label`/`description` are the spec's INLINE map, `ariaLabel` is the KEYED bundle reference — and each accepts the other's shape vacuously. That confusability is objectui#4167's known hazard, inherent to the spec's `I18nLabel` design; both shapes are named with cross-referenced doc comments stating which resolver owns which slot, and a pin asserts the two unions do not collapse into each other.
|
|
459
|
+
|
|
460
|
+
Finally, the spec bridges declare their return type as `BaseSchema` instead of the union. Both bridges end in a single `return node` on an object literal, so the union described nothing real while forcing a narrowing at every read — 272 mechanical errors across five suites in the first round. That change is a type annotation only; the emitted JavaScript is byte-identical.
|
|
461
|
+
|
|
462
|
+
- 3f5f87c: `SchemaRenderer` states its real contract — a typed, required `schema` and a deliberate forwarding surface
|
|
463
|
+
|
|
464
|
+
`SchemaRenderer` is the renderer loop: every registered SDUI component is rendered through it. It handed `forwardRef` a props type of `{ schema: SchemaNode } & Record<string, any>`, which puts `string` into `keyof Props`, so `'ref' extends keyof Props` was always true, React's `PropsWithoutRef` took its `Omit` branch, and `Omit` over a type carrying a string index signature keeps only the index signature. Every declared prop was erased. Measured on the pre-fix source: `keyof ComponentProps<typeof SchemaRenderer>` was `string` and `ComponentProps<typeof SchemaRenderer>['schema']` was `any`, while the type argument went on declaring `SchemaNode`. The other half is the same defect seen from the call site — `<SchemaRenderer />` with no schema at all, `<SchemaRenderer schema={12345} />`, and an arbitrary misspelled prop each type-checked in silence. This is objectui#4422 / PR #4438's trap in the most central component in the repo, spelled `Record<string, any>` rather than `[key: string]: any`, which is why every previous sweep's grep and both shipped guards' detector reported the site as clean.
|
|
465
|
+
|
|
466
|
+
Graded **minor, not major**, on objectui#4528's reasoning: the type argument has always DECLARED `schema`; the index signature erased it from the resolved type, and restoring what the declaration documents is a fix to the published contract rather than a contract break.
|
|
467
|
+
|
|
468
|
+
**The forwarding surface is kept, deliberately.** This component forwards every prop it does not read to the component the schema names, resolved at runtime from a plugin-extensible registry — `packages/react/README.md` documents exactly that, and `@object-ui/components`' form renderer consumes the `onSubmit` it shows being forwarded. Closing that surface would state a false contract and would force every leaf plugin's props into this package. So the two halves are separated: the `forwardRef` type argument is the honest `SchemaRendererProps`, with no index signature for `PropsWithoutRef` to collapse, and the open surface is stated once in an explicit export annotation, which nothing routes through `Omit`. The published `.d.ts` shows the erasure disappearing: `ForwardRefExoticComponent<Omit<{ schema: SchemaNode } & Record<string, any>, "ref"> & RefAttributes<any>>` becomes `ForwardRefExoticComponent<SchemaRendererProps & Record<string, any> & RefAttributes<any>>`.
|
|
469
|
+
|
|
470
|
+
`SchemaRendererProps.schema` is declared as `BaseSchema | string | null | undefined` — what this component actually handles. It previously declared `@object-ui/core`'s `SchemaNode` interface, which requires `type: string` and so contradicted the component's own early returns for strings and nullish, while every caller held `@object-ui/types`' wider union. The erasure hid that mismatch completely.
|
|
471
|
+
|
|
472
|
+
**One declared behaviour change.** A non-object, non-string primitive schema now renders as its own text. It previously fell through to the shallow copy `{ ...schema }`, which spreads a primitive to an empty object, lost the `type` the renderer then looked up, and surfaced the red "Unknown component type: undefined" box — an accident of the spread rather than a decision. The declared props type excludes `number` / `boolean` so no author is invited to pass them; the runtime handling is defence-in-depth for untyped callers and stored metadata. Strings, `null`, `undefined`, `0` and `false` render exactly as before, and an object naming an unregistered type still gets the error box; all four are pinned.
|
|
473
|
+
|
|
474
|
+
Latent defects the erasure had been hiding, each surfaced by the repo-wide type-check and fixed at its call site: `DashboardRenderer` cast its widget schema to `Record<string, any>`, dropping the `type` every branch of `getComponentSchema` sets; `DashboardGridLayout`'s equivalent now states its return type instead of inferring a union that admitted a shape with no `type`; and `ReportViewer` handed a section's `content` array to the renderer whole, so a multi-node section rendered the unknown-component box instead of its content — arrays are mapped rather than widened into the renderer's declared input.
|
|
475
|
+
|
|
476
|
+
A repo-wide structural guard replaces the two per-package siblings' blocked direction: it judges every `forwardRef` in `packages/*/src` (219 sites) and its detector resolves `Record<string, …>` and `string`-keyed mapped types in addition to literal index signatures — the spelling the previous detector went blind on. It judges the type argument only, where an index signature is an accidental eraser, and never an export annotation, where one is a stated contract.
|
|
477
|
+
|
|
478
|
+
- bb68488: Stop declaring 14 symbols under names `@objectstack/spec` owns at `17.0.0-rc.6`
|
|
479
|
+
(objectui#4167, objectstack#4115).
|
|
480
|
+
|
|
481
|
+
The rc.6 bump published nine names this repo already declared locally, on top of
|
|
482
|
+
four that predate it — `check:spec-symbols` reported all thirteen at once, and a
|
|
483
|
+
fourteenth (`GlobalFilterSchema`) appeared during the bump itself. Each was
|
|
484
|
+
triaged on its own rather than blanket-renamed, because the right answer differs
|
|
485
|
+
per symbol: five bind to the spec, three are renamed because the spec's
|
|
486
|
+
same-named export means something else, five arrive by derivation, and one is a
|
|
487
|
+
declared dialect with a written reason.
|
|
488
|
+
|
|
489
|
+
**Breaking for importers of `@object-ui/react`, `@object-ui/app-shell` and
|
|
490
|
+
`@object-ui/types`** — three exported names changed, because the spec exports the
|
|
491
|
+
same name for a _different_ thing:
|
|
492
|
+
|
|
493
|
+
| package | was | now | what the spec's same-named export actually is |
|
|
494
|
+
| :-------------------- | :----------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
495
|
+
| `react` / `app-shell` | `MetadataState` | `MetadataCacheState` | a metadata item's LIFECYCLE state — `'draft' \| 'active' \| 'deprecated' \| 'archived'` (`MetadataStateSchema`, `@objectstack/spec/system`) |
|
|
496
|
+
| `react` / `app-shell` | `resolveI18nLabel` | `resolveKeyedI18nLabel` | a resolver for the INLINE per-locale map (`{ en: 'Owner', 'zh-CN': '负责人' }`) against a BCP-47 locale |
|
|
497
|
+
| `types` | `DateRangePreset` | `FilterBuilderDateRangePreset` | the thirteen HISTORICAL dashboard filter-bar presets; this one is the filter-builder set, which adds eight FUTURE windows the dashboard schema rejects |
|
|
498
|
+
|
|
499
|
+
`resolveI18nLabel` is the one where the collision had already started costing
|
|
500
|
+
something. rc.6 widened `I18nLabel` from `string` to
|
|
501
|
+
`string | Record< string, string >`, so the same authored value now reaches
|
|
502
|
+
either resolver — and each answers wrongly, silently, for the other's input: the
|
|
503
|
+
keyed one returns `undefined` for `{ en: 'Owner' }` (no `key`, no
|
|
504
|
+
`defaultValue`), and the spec's reads `key` / `defaultValue` / `params` as locale
|
|
505
|
+
tags. The rc.6 bump PR met this and aliased the spec's import as
|
|
506
|
+
`resolveInlineI18nLabel` in five files, with hand-written comments at two of
|
|
507
|
+
them. That is a review convention, which is what objectstack#4115 exists to
|
|
508
|
+
replace with a rule — so `Keyed` is now the counterpart of that `Inline`, and the
|
|
509
|
+
name says which vocabulary it resolves at every call site.
|
|
510
|
+
|
|
511
|
+
**Eleven keep their names and are now imported or derived from the spec** instead
|
|
512
|
+
of re-declared: `DATE_RANGE_PRESETS`, `NavigationMode`, `AddressValue`,
|
|
513
|
+
`BreakpointColumnMap`, `BreakpointOrderMap`, `KanbanConfig`, `CalendarConfig`,
|
|
514
|
+
`GanttConfig`, plus the three renamed above at their new names.
|
|
515
|
+
|
|
516
|
+
**Four of the copies were losing information, not just duplicating it.**
|
|
517
|
+
|
|
518
|
+
- **`GanttConfig` declared six keys and called itself canonical; rc.6's
|
|
519
|
+
`GanttConfigSchema` declares seventeen.** The eleven it never mentioned —
|
|
520
|
+
`parentField`, `typeField`, `baselineStartField`, `baselineEndField`,
|
|
521
|
+
`groupByField`, `resourceView`, `assigneeField`, `effortField`, `capacity`,
|
|
522
|
+
`quickFilters`, `autoZoomToFilter` — are all read by
|
|
523
|
+
`plugin-gantt/src/ObjectGantt.tsx`, through a local `GanttConfigEx`
|
|
524
|
+
intersection that existed only because this type did not carry them. It now
|
|
525
|
+
derives from the spec, with `timeSegments` (shift segmentation) as the one
|
|
526
|
+
genuinely local extension; the schema is `$loose` upstream, so that key is
|
|
527
|
+
legal metadata rather than a second dialect.
|
|
528
|
+
- **`GanttConfig.tooltipFields` carried the comment "not part of the upstream
|
|
529
|
+
GanttConfigSchema".** It is, as of rc.6, so the key now arrives from the spec.
|
|
530
|
+
- **`AddressValue` declared five of the spec's seven parts** — `countryCode` and
|
|
531
|
+
`formatted` were missing, under a comment already claiming to be "the part
|
|
532
|
+
names of `AddressSchema`". The widget still renders five inputs; binding the
|
|
533
|
+
type stops it from asserting the platform cannot store the other two, and makes
|
|
534
|
+
the `{ ...address }` write-through say so.
|
|
535
|
+
- **`DATE_RANGE_PRESETS` was `Object.keys(PRESET_RANGES)`,** a third copy of a
|
|
536
|
+
vocabulary the spec extracted in objectstack#4614 precisely to collapse — its
|
|
537
|
+
own doc comment names this module as one of the three. It is now the spec's
|
|
538
|
+
array by reference, and the local date-macro bounds table is pinned complete
|
|
539
|
+
against it with `satisfies`, so a preset the schema gains without bounds here
|
|
540
|
+
is a compile error rather than a filter that validates clean and then selects
|
|
541
|
+
nothing.
|
|
542
|
+
|
|
543
|
+
`NavigationMode` was one hop from the spec already (`NavigationConfig['mode']`);
|
|
544
|
+
it is bound directly, with a both-directions type pin that it stays the same type
|
|
545
|
+
as the config's own `mode`. `KanbanConfig` / `CalendarConfig` /
|
|
546
|
+
`BreakpointColumnMap` / `BreakpointOrderMap` were exact hand copies of `$strict`
|
|
547
|
+
schemas and are now re-exports — "still exact" is the argument for binding them,
|
|
548
|
+
since a copy with nothing to protect can only drift.
|
|
549
|
+
|
|
550
|
+
`GlobalFilterSchema` is the one ALLOW entry. It is the same spread-composition
|
|
551
|
+
dialect as `SelectOptionSchema` next to it, and it collided only because rc.6's
|
|
552
|
+
new refinement forced `.extend()` to be respelled as a `.shape` spread — which
|
|
553
|
+
moved a derivation the guard could see into an object literal it deliberately
|
|
554
|
+
does not descend into. The dialect is unchanged and its three divergences are
|
|
555
|
+
pinned; which side moves on the refinement itself is objectui#4165.
|
|
556
|
+
|
|
557
|
+
`@objectstack/spec` moves from `devDependencies` to `dependencies` in
|
|
558
|
+
`@object-ui/layout`: its public type surface now references the spec.
|
|
559
|
+
|
|
560
|
+
### Patch Changes
|
|
561
|
+
|
|
562
|
+
- ceccdcf: Action confirm dialogs and success toasts now honour the bundle's translated
|
|
563
|
+
`confirmText` / `successMessage`, not just `label` (objectui#4265).
|
|
564
|
+
|
|
565
|
+
A TranslationBundle entry for an action carries three keys under one
|
|
566
|
+
`_actions.<name>` node — `label`, `confirmText`, `successMessage` — and
|
|
567
|
+
`useObjectLabel()` has always exposed a resolver for each. What had drifted was
|
|
568
|
+
the call sites: `page:header` (authored record pages), `record:quick_actions`
|
|
569
|
+
and the related-list row menu resolved the button `label` only and dispatched
|
|
570
|
+
the authored `confirmText` / `successMessage` untouched. One bundle entry met
|
|
571
|
+
two fates: the button rendered the translation, the confirm dialog rendered the
|
|
572
|
+
authored English.
|
|
573
|
+
|
|
574
|
+
All action-rendering surfaces now go through one resolver,
|
|
575
|
+
`useActionTextLocalizer()` (new, exported from `@object-ui/react`), which
|
|
576
|
+
applies the existing `actionLabel` / `actionConfirm` / `actionSuccess`
|
|
577
|
+
resolvers over the three keys together. Fallback is unchanged: with no bundle
|
|
578
|
+
entry — or an entry lacking a key — the authored text renders. A bundle cannot
|
|
579
|
+
introduce a `confirmText` or `successMessage` the metadata never declared.
|
|
580
|
+
|
|
581
|
+
- ee26e65: Analytics: the dimension label net's fetch-and-memo glue is written once, not once per surface
|
|
582
|
+
|
|
583
|
+
PR #4388 (objectui#4330) put the same React glue on two surfaces — the dashboard's `DatasetWidget` and plugin-report's dataset block. The resolution RULES were never duplicated (both call the same `@object-ui/core` helpers), but the wiring around them was: read the object schema through the host's authenticated `apiFetch`, keep the fetched metadata locale-free in state, derive the label maps in a render memo. Two copies meant two statements of the same two bug fixes, which is a drift surface rather than a defect — nothing a user could hit today, filed as objectui#4389 so it was retired deliberately.
|
|
584
|
+
|
|
585
|
+
It is now split along the layer that can actually hold each half. `@object-ui/core` gains the React-free parts — `loadDimensionFieldMeta` (the base-object read composed with the dimension walk), `deriveDimensionLabelMaps` (the locale-applying derivation) and `dimensionOptionTranslator` (binding the bundle resolver to the object that OWNS a terminal field, which for a dotted path is the relationship target). `@object-ui/react` gains `useDatasetDimensionLabels` / `useDatasetDimensionMeta`, the React wiring that cannot live in core, beside the `useViewData` / `useElementDataSource` / `useDiscovery` hooks that already read `SchemaRendererContext` the same way. Both plugins consume it; the dashboard keeps its chart-only per-category colour and category-order derivation layered locally, since a table renders no palette.
|
|
586
|
+
|
|
587
|
+
The card originally proposed `@object-ui/core` as the whole glue's home. That home was disproven by measurement and retired in the card's PM RULING #2: `SchemaRendererContext` is defined in `@object-ui/react`, which depends on core, so core importing it back is a cycle — and core is React-free by declaration, by content, and by the topology in AGENTS.md. objectui#3367 had already ruled this direction for the same family (core-canonical logic, react re-exports).
|
|
588
|
+
|
|
589
|
+
Behaviour is unchanged by construction: same read count, same best-effort fallback, same memoization boundary. The two bug fixes are now stated once and pinned at the shared hook — the read rides the host's authenticated `apiFetch` (objectui#4121, pinned by asserting that a new channel re-issues the read, i.e. that it really is in the effect's deps), and the fetched metadata stays locale-free (objectui#4030 / PR #4324, pinned by switching language at runtime and asserting the labels flip with no second metadata read). All 39 assertions PR #4388 landed across both surfaces pass unchanged, and their files are byte-identical to before.
|
|
590
|
+
|
|
591
|
+
- f650253: `BaseSchema.ariaLabel` declares the keyed i18n vocabulary the renderer actually
|
|
592
|
+
resolves, `.disabled` accepts the predicate string it actually evaluates, and the
|
|
593
|
+
keyed shape finally has a name (objectui#4581)
|
|
594
|
+
|
|
595
|
+
Three slots on one base type had drifted from what the renderer does with them.
|
|
596
|
+
PR #4593 fixed `visible` and measured the rest; these are the rest.
|
|
597
|
+
|
|
598
|
+
`ariaLabel` was `string`, but `SchemaRenderer.tsx:111` resolves it with
|
|
599
|
+
`resolveKeyedI18nLabel`, whose input is the KEYED form
|
|
600
|
+
`{ key, defaultValue?, params? }` — a reference into a translation bundle. It is
|
|
601
|
+
now `string | KeyedI18nLabel`, and `KeyedI18nLabel` is a new exported type in
|
|
602
|
+
`@object-ui/types` rather than a fourth inline copy of one object literal: the
|
|
603
|
+
three that existed (`@object-ui/react`'s resolver, `@object-ui/layout`'s
|
|
604
|
+
`resolveLabel`, `@object-ui/app-shell`'s `t`-taking twin) were verified identical
|
|
605
|
+
in their object half first, and two of them now import the name.
|
|
606
|
+
|
|
607
|
+
The vocabulary matters more than the widening. `#4581` originally asked for
|
|
608
|
+
`string | I18nLabel`, and that spelling was withdrawn as measured-wrong: the
|
|
609
|
+
spec's `I18nLabel` is the INLINE LOCALE MAP (`string | Record<string, string>`),
|
|
610
|
+
a different vocabulary resolved against a BCP-47 locale by a different function
|
|
611
|
+
of a confusingly similar name. Under it the shipped keyed fixture type-checked
|
|
612
|
+
only vacuously — as a locale map whose "locales" are named `key` and
|
|
613
|
+
`defaultValue` — the same label carrying `params` was rejected outright, and a
|
|
614
|
+
genuine `{ en: 'Owner' }` compiled while rendering an EMPTY `aria-label`. Naming
|
|
615
|
+
the keyed shape is the declaration half of the fix objectui#4167 started on the
|
|
616
|
+
naming side; `@object-ui/app-shell`'s copy keeps its inline spelling for now
|
|
617
|
+
because an open PR has a pending change to that file, and the comment there says
|
|
618
|
+
so.
|
|
619
|
+
|
|
620
|
+
`disabled` was `boolean` on a key the renderer never reads as one:
|
|
621
|
+
`SchemaRenderer.tsx:466` evaluates it through the same `evaluateCondition` as
|
|
622
|
+
`visible`, and a `disabledOn?: string` sibling exists for the same reason. It is
|
|
623
|
+
now `boolean | string`. The asymmetry with `visible` was accidental rather than
|
|
624
|
+
deliberate.
|
|
625
|
+
|
|
626
|
+
Both are widenings on authored-input-dominant properties: authors gain a
|
|
627
|
+
spelling, nothing that type-checked before stops doing so, and readers already
|
|
628
|
+
coped with `any` through `BaseSchema`'s index signature. Three test fixtures that
|
|
629
|
+
had been casting past these declarations with `as unknown as BaseSchema` state
|
|
630
|
+
their values directly now, and the declared unions are pinned invariantly so
|
|
631
|
+
neither a missing widening nor an overshoot to `any` can pass unnoticed.
|
|
632
|
+
|
|
633
|
+
Declaring the vocabulary honestly also surfaced a real one: the `toggle`
|
|
634
|
+
renderer writes `aria-label` itself instead of going through SchemaRenderer's
|
|
635
|
+
resolver, and it forwarded the raw value. Invoked directly it emitted
|
|
636
|
+
`aria-label="[object Object]"` for a keyed label — announced verbatim by a
|
|
637
|
+
screen reader. It resolves now. Through `SchemaRenderer` the bug was invisible,
|
|
638
|
+
because SchemaRenderer injects its own resolved `aria-label` afterwards; a
|
|
639
|
+
downstream type-check sweep found it, not a test.
|
|
640
|
+
|
|
641
|
+
`BaseSchema.label` and `.description` are deliberately unchanged and pinned that
|
|
642
|
+
way. They receive the spec's inline `I18nLabel` from the view bridges, which is a
|
|
643
|
+
real defect, but resolving it belongs at the spec-to-schema boundary rather than
|
|
644
|
+
in this declaration — and that work is still blocked on a design question about
|
|
645
|
+
where the display locale enters, so it is not in this release.
|
|
646
|
+
|
|
647
|
+
- 8f85f8b: The spec bridge abstains on prototype-member `rowHeight` spellings instead of leaking a
|
|
648
|
+
function into `density`.
|
|
649
|
+
|
|
650
|
+
`bridgeListView`'s `mapDensity` indexed a plain object literal with an unchecked key, so
|
|
651
|
+
the lookup reached `Object.prototype`. The parameter is typed `RowHeight`, but the
|
|
652
|
+
boundary a host's stored view definition actually crosses is `SpecBridge.transformListView`,
|
|
653
|
+
whose parameter is `any` — so `rowHeight: 'toString'` came back as `Object.prototype.toString`,
|
|
654
|
+
a **function**, out of a read whose return type is three strings or nothing. `bridgeListView`
|
|
655
|
+
then writes the key under `if (density)`, and a function is truthy, so the bad value was not
|
|
656
|
+
merely returned: it was stored on a `SchemaNode` whose renderer expects
|
|
657
|
+
`'compact' | 'comfortable' | 'spacious'`. Same for `constructor`, `valueOf`,
|
|
658
|
+
`hasOwnProperty`, `isPrototypeOf`, `propertyIsEnumerable` and `toLocaleString`.
|
|
659
|
+
|
|
660
|
+
The lookup is now guarded with `Object.prototype.hasOwnProperty.call(...)` — the same guard
|
|
661
|
+
`@object-ui/core`'s `rowHeightToDensityMode` grew in objectui#4440, and the repo's existing
|
|
662
|
+
convention at eight other sites. Both `rowHeight` surfaces now abstain identically on every
|
|
663
|
+
off-spec **string** spelling, and objectui#4440's agreement pin covers the prototype-member
|
|
664
|
+
family instead of excluding it (objectui#4442).
|
|
665
|
+
|
|
666
|
+
Runtime-only: no public type moved, and no spec-valid `rowHeight` changes its answer.
|
|
667
|
+
|
|
668
|
+
- d0c3b26: Every plain `<button>` now declares its `type`. HTML defaults an untyped button to
|
|
669
|
+
`type="submit"`, so any of these buttons would submit the form it was composed into
|
|
670
|
+
instead of running its own handler — a real risk for renderers (`drawer`, `tree-view`,
|
|
671
|
+
`navigation-overlay`) whose placement inside a form is a JSON metadata decision. 114
|
|
672
|
+
sites were converted to `type="button"`; no site was a genuine submit button, and the
|
|
673
|
+
DOM is otherwise unchanged.
|
|
674
|
+
|
|
675
|
+
The defect class is now closed mechanically by a new `object-ui/button-has-type` ESLint
|
|
676
|
+
rule (error), so the next untyped button fails CI at write time rather than being found
|
|
677
|
+
by a fourth audit round (objectui#4045, closing the objectui#3344 family).
|
|
678
|
+
|
|
679
|
+
- f148a64: SpecBridge lifts a legacy bare `exportOptions` array to the spec's object form, so a
|
|
680
|
+
spec-authored view's declared export formats reach the grid (objectui#4585).
|
|
681
|
+
|
|
682
|
+
A spec `ListView` may spell `exportOptions` either way, and `@objectstack/spec` lifts the
|
|
683
|
+
legacy bare format array to `{ formats: [...] }` when it parses one (objectstack#8010).
|
|
684
|
+
That lift never ran on the bridge path: the bridge's input is a TypeScript type, not a
|
|
685
|
+
parsed value — there is no `parse`/`safeParse` anywhere under `spec-bridge/` — so a host
|
|
686
|
+
forwarding raw stored metadata handed the array straight through, and the bridge copied it
|
|
687
|
+
onto the `object-grid` node verbatim. ObjectGrid reads the object form and only that, and
|
|
688
|
+
`.formats` on an array is `undefined`, so the renderer's `['csv', 'json']` default won.
|
|
689
|
+
|
|
690
|
+
A view declaring `['csv', 'xlsx']` therefore rendered an export menu offering CSV and
|
|
691
|
+
JSON: the declared xlsx never appeared, an undeclared json did, and nothing said so — the
|
|
692
|
+
export button still showed, because a non-empty array is truthy. The bridge now applies
|
|
693
|
+
the spec's own transform at the assignment site, so both spellings leave it as one shape.
|
|
694
|
+
|
|
695
|
+
Deliberately narrow: this mirrors the spec's lift and nothing else. The object form passes
|
|
696
|
+
through by reference, unread and unrewritten; a view with no `exportOptions` is untouched;
|
|
697
|
+
and a `'pdf'` stored before its retirement is carried rather than filtered, because the
|
|
698
|
+
spec refuses that value at parse with a migration prescription instead of silently
|
|
699
|
+
dropping it — such a format still dies downstream in ObjectGrid's format-agnostic menu
|
|
700
|
+
filter (objectui#4535). The fix is at the producer for the same reason: a tolerant
|
|
701
|
+
`Array.isArray` fallback in the renderer would make a second de-facto contract out of one
|
|
702
|
+
spec key.
|
|
703
|
+
|
|
704
|
+
One behavior follows from reading the lift literally: `exportOptions: []` now lifts to
|
|
705
|
+
`{ formats: [] }` and the export button is hidden, where before the unreadable `[]` was
|
|
706
|
+
merely truthy and produced a menu built entirely from the `['csv', 'json']` default. A
|
|
707
|
+
view that declares zero formats now offers zero.
|
|
708
|
+
|
|
709
|
+
- 47f551b: fix(react): the spec bridge abstains on a non-string `rowHeight` instead of coercing it to a density, matching core
|
|
710
|
+
|
|
711
|
+
`mapDensity` opened with a truthiness guard, so any **truthy non-string** survived it and was
|
|
712
|
+
then coerced into a lookup key — both `Object.prototype.hasOwnProperty.call` and the table index
|
|
713
|
+
run `String(...)`. `rowHeight: ['compact']`, a boxed `String('compact')` or
|
|
714
|
+
`{ toString: () => 'compact' }` therefore each selected a real density, while
|
|
715
|
+
`@object-ui/core`'s `rowHeightToDensityMode` — which opens with `typeof rowHeight !== 'string'` —
|
|
716
|
+
abstained for the same input. Two published surfaces, two answers for one input.
|
|
717
|
+
|
|
718
|
+
The bridge now opens with core's type guard. An off-spec non-string `rowHeight` renders exactly
|
|
719
|
+
like an absent one, and the producer is where it gets fixed (AGENTS.md #0.1). Behaviour for the
|
|
720
|
+
five spec row heights and for off-spec **strings** is unchanged; `''` keeps its answer by a
|
|
721
|
+
different route (a string now, refused one line later because it is not one of the five keys).
|
|
722
|
+
|
|
723
|
+
Note the direction against the previous fix in this function: that leak returned a _function_,
|
|
724
|
+
visibly wrong to everything downstream. This one returned a legitimate-looking `'compact'` that
|
|
725
|
+
nothing downstream could tell apart from an authored density.
|
|
726
|
+
|
|
727
|
+
- Updated dependencies [0e67b53]
|
|
728
|
+
- Updated dependencies [ee66e2e]
|
|
729
|
+
- Updated dependencies [ee26e65]
|
|
730
|
+
- Updated dependencies [5900ac5]
|
|
731
|
+
- Updated dependencies [932cbcd]
|
|
732
|
+
- Updated dependencies [734d186]
|
|
733
|
+
- Updated dependencies [f650253]
|
|
734
|
+
- Updated dependencies [3d9769a]
|
|
735
|
+
- Updated dependencies [3fc2971]
|
|
736
|
+
- Updated dependencies [aca27fa]
|
|
737
|
+
- Updated dependencies [dde7283]
|
|
738
|
+
- Updated dependencies [f7c6430]
|
|
739
|
+
- Updated dependencies [ae10a01]
|
|
740
|
+
- Updated dependencies [92876f0]
|
|
741
|
+
- Updated dependencies [f279deb]
|
|
742
|
+
- Updated dependencies [eb7f586]
|
|
743
|
+
- Updated dependencies [e901131]
|
|
744
|
+
- Updated dependencies [d9d3463]
|
|
745
|
+
- Updated dependencies [2a40f69]
|
|
746
|
+
- Updated dependencies [537a0d1]
|
|
747
|
+
- Updated dependencies [bec3e14]
|
|
748
|
+
- Updated dependencies [613b167]
|
|
749
|
+
- Updated dependencies [1f9b905]
|
|
750
|
+
- Updated dependencies [828549a]
|
|
751
|
+
- Updated dependencies [e1ade8f]
|
|
752
|
+
- Updated dependencies [abb0f81]
|
|
753
|
+
- Updated dependencies [38ab505]
|
|
754
|
+
- Updated dependencies [3e19fe7]
|
|
755
|
+
- Updated dependencies [bb58d1d]
|
|
756
|
+
- Updated dependencies [5cc847c]
|
|
757
|
+
- Updated dependencies [fa21254]
|
|
758
|
+
- Updated dependencies [33c32bf]
|
|
759
|
+
- Updated dependencies [66fb4fa]
|
|
760
|
+
- Updated dependencies [6d641c9]
|
|
761
|
+
- Updated dependencies [479cc7b]
|
|
762
|
+
- Updated dependencies [7e4f0e5]
|
|
763
|
+
- Updated dependencies [45e1949]
|
|
764
|
+
- Updated dependencies [92250d6]
|
|
765
|
+
- Updated dependencies [c1d939f]
|
|
766
|
+
- Updated dependencies [58bebf6]
|
|
767
|
+
- Updated dependencies [405e808]
|
|
768
|
+
- Updated dependencies [49ae9f4]
|
|
769
|
+
- Updated dependencies [c0f9a4b]
|
|
770
|
+
- Updated dependencies [2459a3e]
|
|
771
|
+
- Updated dependencies [2776b11]
|
|
772
|
+
- Updated dependencies [ac853ce]
|
|
773
|
+
- Updated dependencies [fa51109]
|
|
774
|
+
- Updated dependencies [d6aa172]
|
|
775
|
+
- Updated dependencies [fe52a04]
|
|
776
|
+
- Updated dependencies [d46f9b8]
|
|
777
|
+
- Updated dependencies [605b747]
|
|
778
|
+
- Updated dependencies [2fea4d2]
|
|
779
|
+
- Updated dependencies [7f1cb33]
|
|
780
|
+
- Updated dependencies [bb68488]
|
|
781
|
+
- Updated dependencies [2e3b0c0]
|
|
782
|
+
- Updated dependencies [9461dd3]
|
|
783
|
+
- Updated dependencies [78fa331]
|
|
784
|
+
- Updated dependencies [31ab1ac]
|
|
785
|
+
- Updated dependencies [0082db8]
|
|
786
|
+
- Updated dependencies [b42558a]
|
|
787
|
+
- Updated dependencies [d2f6e6b]
|
|
788
|
+
- Updated dependencies [ab04728]
|
|
789
|
+
- Updated dependencies [85a3082]
|
|
790
|
+
- Updated dependencies [06915b0]
|
|
791
|
+
- Updated dependencies [ff84b05]
|
|
792
|
+
- @object-ui/i18n@17.5.0
|
|
793
|
+
- @object-ui/core@17.5.0
|
|
794
|
+
- @object-ui/data-objectstack@17.5.0
|
|
795
|
+
- @object-ui/types@17.5.0
|
|
796
|
+
|
|
797
|
+
## 17.4.0
|
|
798
|
+
|
|
799
|
+
### Minor Changes
|
|
800
|
+
|
|
801
|
+
- 5bfaabd: `PageComponentSchema.dataSource` now reaches every object-bound block, not just
|
|
802
|
+
`list-view` — and `element:record_picker` stops discarding `view`
|
|
803
|
+
(objectstack#6953).
|
|
804
|
+
|
|
805
|
+
objectstack#5576 wired the spec's per-element data binding
|
|
806
|
+
(`dataSource: { object, view?, filter?, sort?, limit? }`) to `list-view` and left
|
|
807
|
+
the same declaration inert on every other page component. Two gaps remained, and
|
|
808
|
+
both were silent:
|
|
809
|
+
|
|
810
|
+
- **`element:record_picker` read four of the five keys and dropped `view`.** So
|
|
811
|
+
`dataSource: { object: 'account', view: 'hot' }` — the spec's own example —
|
|
812
|
+
built a picker over EVERY account instead of the rows the saved view selects.
|
|
813
|
+
Nothing threw and nothing rendered an error; the option list was simply wider
|
|
814
|
+
than what was authored, which also means a user could select a record the page
|
|
815
|
+
said was out of scope.
|
|
816
|
+
- **`object-grid` / `object-form` / `object-kanban` / `object-calendar` /
|
|
817
|
+
`object-chart` / `object-metric` / `record:related_list` read none of it.**
|
|
818
|
+
Each gates its fetch on its own `objectName`, and nothing mapped
|
|
819
|
+
`dataSource.object` onto it, so a page written the way the spec documents
|
|
820
|
+
rendered an empty grid / a field-less form / a board with no cards / an empty
|
|
821
|
+
month / an empty chart / a static metric number — with no request and no
|
|
822
|
+
diagnostic anywhere. Spec-valid metadata rendering nothing is the
|
|
823
|
+
objectstack#4413 shape.
|
|
824
|
+
|
|
825
|
+
Composition follows objectstack#5576's landed semantics unchanged on every block:
|
|
826
|
+
a named saved view supplies the baseline, a key written on the component itself
|
|
827
|
+
overrides it, an explicit binding key overrides both, `filter` AND-combines
|
|
828
|
+
("additional filter criteria" — a binding can narrow a view, never widen it), and
|
|
829
|
+
a `view` name that does not resolve renders a configuration error instead of
|
|
830
|
+
degrading to the object's full scope.
|
|
831
|
+
|
|
832
|
+
- `@object-ui/react` — new `useElementDataSourceSchema(schema, mapping, dataSource?)`
|
|
833
|
+
and `ElementDataSourceGate` apply a resolved binding to the schema keys a given
|
|
834
|
+
block reads, plus `ElementDataSourceErrorPanel` / `ElementDataSourceLoadingPanel`
|
|
835
|
+
for the two non-final states. One precedence table for all blocks rather than
|
|
836
|
+
one copy per block — that copy is how "additional filter criteria" would have
|
|
837
|
+
become two dialects.
|
|
838
|
+
- A mapping names **only** keys its block genuinely reads. A composed value
|
|
839
|
+
written onto a key the block ignores would be accepted and dropped, which is
|
|
840
|
+
the defect being removed, one layer deeper — so a kanban's swimlane `columns`
|
|
841
|
+
never receive a view's field list, and a block with no row cap leaves `limit`
|
|
842
|
+
unmapped. The per-block coverage table, including two residual gaps that are
|
|
843
|
+
named rather than papered over, is in `content/docs/guide/data-source.md`.
|
|
844
|
+
|
|
845
|
+
No behaviour changes for a block that carries no `dataSource`: the binding-free
|
|
846
|
+
path returns the schema by reference, so nothing remounts and nothing refetches.
|
|
847
|
+
|
|
848
|
+
- 48132f7: Track the `@objectstack` family at `17.0.0-rc.5` (objectui#3560).
|
|
849
|
+
|
|
850
|
+
The pin moves from `^17.0.0-rc.2` to `^17.0.0-rc.5` across all 37 declarations in
|
|
851
|
+
30 `package.json` files, and the sibling `@objectstack/*` packages (`client` /
|
|
852
|
+
`formula` / `lint`) move with it — they pin `@objectstack/spec` **exactly**, so
|
|
853
|
+
leaving them behind would keep a second copy of the spec in the tree and have
|
|
854
|
+
`@objectstack/lint` validating against schemas that still accept the keys rc.3–rc.5
|
|
855
|
+
retire. `pnpm-lock.yaml` now resolves one copy of each of the six family packages
|
|
856
|
+
(`spec` / `client` / `core` / `formula` / `lint` / `sdui-parser`), all at rc.5.
|
|
857
|
+
|
|
858
|
+
Bumping the pin and repairing the fallout cannot be split: the pin alone reddens
|
|
859
|
+
CI, and the code alone targets a shape that is not in effect yet.
|
|
860
|
+
|
|
861
|
+
## A live bug this upgrade fixes
|
|
862
|
+
|
|
863
|
+
**`ObjectStackDataSource.delete()` never emitted its mutation event, and resolved
|
|
864
|
+
`undefined` instead of a boolean.** `@objectstack/client`'s `DeleteDataResult`
|
|
865
|
+
declared a key called `deleted` — a key no schema has ever declared and no server
|
|
866
|
+
path has ever returned on `DELETE /data/:object/:id`. So `result.deleted`
|
|
867
|
+
compiled and read `undefined` at runtime: the guard never fired, a successful
|
|
868
|
+
delete notified no subscriber, and every consumer's cache stayed stale.
|
|
869
|
+
objectstack#5638 corrected the interface to the schema's `success`; following the
|
|
870
|
+
rename is what restores both behaviours. Nothing in this repo had to change shape
|
|
871
|
+
for it — the code was already asking the right question of the wrong key.
|
|
872
|
+
|
|
873
|
+
## Breaking, in FROM → TO form
|
|
874
|
+
|
|
875
|
+
- **The five `@objectstack/spec/ui` interaction-config modules are gone** —
|
|
876
|
+
touch / dnd / keyboard / animation / offline, 32 defs and 64 exports
|
|
877
|
+
(objectstack#4988, PR objectstack#5321). None of them had an authoring door: no
|
|
878
|
+
metadata document could ever carry one of these blocks, so a stack that parsed
|
|
879
|
+
before the retirement parses byte-for-byte the same after it. `@object-ui/types`
|
|
880
|
+
drops the 32 `export type` re-exports. The vocabulary each one's only real
|
|
881
|
+
consumer needs is now declared by that consumer, which is the remedy the spec's
|
|
882
|
+
own retirement ledger prescribes ("declare that union locally — it is your
|
|
883
|
+
client's policy, not the platform's"):
|
|
884
|
+
|
|
885
|
+
- `@object-ui/react`'s `useOffline` owns `OfflineStrategy`, `ConflictResolution`,
|
|
886
|
+
`PersistStorageType`, `EvictionPolicyType`, `OfflineConfig`,
|
|
887
|
+
`OfflineCacheConfig`, `OfflineSyncConfig`;
|
|
888
|
+
- `@object-ui/core`'s `DndProtocol` / `KeyboardProtocol` own `DndConfig`,
|
|
889
|
+
`DragItem`, `DropZone`, `DragConstraint`, `DragHandle`, `DropEffect`,
|
|
890
|
+
`KeyboardNavigationConfig`, `KeyboardShortcut`, `FocusManagement`,
|
|
891
|
+
`FocusTrapConfig`;
|
|
892
|
+
- `@object-ui/types`' `mobile` module owns `SpecGestureConfig`,
|
|
893
|
+
`SwipeGestureConfig`, `PinchGestureConfig`, `LongPressGestureConfig`,
|
|
894
|
+
`TouchTargetConfig`, `TouchInteraction` (plus a new `SPEC_GESTURE_TYPES`
|
|
895
|
+
runtime tuple), so `@object-ui/mobile`'s import paths are unchanged.
|
|
896
|
+
|
|
897
|
+
Every shape is moved verbatim — same keys, same members, same optionality — so
|
|
898
|
+
no hook or bridge changes behaviour. Consumers importing these names from
|
|
899
|
+
`@object-ui/types` must import them from the owning package instead. Note the
|
|
900
|
+
spec's _surviving_ `ConnectorConflictResolution` (`/integration`, connector sync)
|
|
901
|
+
and `ConflictResolutionStrategy` (`/api`, route merge policy) are **different
|
|
902
|
+
concepts** — do not re-point at them.
|
|
903
|
+
|
|
904
|
+
- **`@object-ui/types` no longer re-exports `NotificationAction` or `EmbedConfig`**
|
|
905
|
+
(objectstack#5015, PR objectstack#5300). Both were published `ui` vocabulary with
|
|
906
|
+
no authoring door; no notification action was ever parsed from metadata and no
|
|
907
|
+
iframe route ever read an embed config. The presentation enums
|
|
908
|
+
(`NotificationType` / `NotificationSeverity` / `NotificationPosition`) and
|
|
909
|
+
`SharingConfig` **survive** and are untouched — public form sharing still gates
|
|
910
|
+
the anonymous endpoints on `allowAnonymous` + `publicLink`.
|
|
911
|
+
`@object-ui/core`'s `SharingProtocol` keeps `resolveEmbedConfig` /
|
|
912
|
+
`generateEmbedCode` against a locally declared `EmbedConfig`, so its surface is
|
|
913
|
+
unchanged.
|
|
914
|
+
- **`ThemeEngine` stops emitting nine retired CSS variable groups**
|
|
915
|
+
(objectstack#5021 option 2, PR objectstack#5289). `theme.animation`,
|
|
916
|
+
`theme.zIndex` and five typography groups (`fontSize` / `fontWeight` /
|
|
917
|
+
`lineHeight` / `letterSpacing`, plus `fontFamily.heading` / `fontFamily.mono`)
|
|
918
|
+
are tombstones the schema now rejects by name, so `--duration-*`, `--timing-*`,
|
|
919
|
+
`--z-*`, `--font-size-*`, `--font-weight-*`, `--line-height-*`,
|
|
920
|
+
`--letter-spacing-*`, `--font-heading` and `--font-mono` had become structurally
|
|
921
|
+
dead code — no author could produce the input that reached them.
|
|
922
|
+
`generateAnimationVars` and `generateZIndexVars` are removed from
|
|
923
|
+
`@object-ui/core`, and `@object-ui/types` drops `Animation` / `ZIndex` /
|
|
924
|
+
`AnimationSchema` / `ZIndexSchema`. **`theme.customVars` is the declared — and
|
|
925
|
+
since #5021 the only — door**: each entry is emitted verbatim as
|
|
926
|
+
`--<key>: <value>`, so a `--z-modal` or a `--duration-fast` goes there now.
|
|
927
|
+
LIVE emission is untouched byte for byte: `colors`, `borderRadius`, `shadows`,
|
|
928
|
+
`typography.fontFamily.base` (→ `--font-sans`) and `customVars`.
|
|
929
|
+
- **`@object-ui/types`' `HttpMethodSchema` now binds the spec's
|
|
930
|
+
`HttpMethodSubsetSchema`, and `HttpMethod` binds `HttpMethodSubset`**
|
|
931
|
+
(objectstack#5832, PR objectstack#5976 — objectui#3499). The spec renamed its
|
|
932
|
+
5-value UI subset because `schemaNameFromExportKey` strips the `Schema` suffix,
|
|
933
|
+
so the 5-value and 7-value enums both published as `shared/HttpMethod` and the
|
|
934
|
+
later write won — the emitted JSON Schema and reference page described only one
|
|
935
|
+
of them. **The runtime domain is unchanged and this repo's exported names are
|
|
936
|
+
unchanged**; this follows the rename without touching cross-package semantics.
|
|
937
|
+
Deliberately NOT re-pointed at the spec's bare `HttpMethod`: that is the 7-value
|
|
938
|
+
enum, and widening to it would let `method: 'HEAD'` compile and then throw in
|
|
939
|
+
`HttpRequestSchema.parse()`.
|
|
940
|
+
- **`dashboard.widgets[].actionUrl` / `actionType` / `actionIcon` / `aria` are
|
|
941
|
+
refused, not stripped** (objectstack#5010, ADR-0049 enforce-or-remove). A
|
|
942
|
+
dashboard widget has no action button and never had one — every action the
|
|
943
|
+
dashboard dispatches comes from `header.actions[]` — and no renderer ever applied
|
|
944
|
+
the widget `aria`, so it promised accessibility compliance it did not deliver.
|
|
945
|
+
A stale dashboard now gets a named error telling it where the affordance moved,
|
|
946
|
+
instead of silently losing it. Run `os migrate meta --from 16` to rewrite.
|
|
947
|
+
|
|
948
|
+
### Patch Changes
|
|
949
|
+
|
|
950
|
+
- 8aad9fd: Action-face predicates written against the canonical `record.` root now evaluate
|
|
951
|
+
|
|
952
|
+
`action:button`, `action:icon`, `action:menu` and `action:group` gated their
|
|
953
|
+
actions on `useCondition(pred, context)`, which evaluates on
|
|
954
|
+
`new ExpressionEvaluator({ ...scope, ...context })` — and the context each of
|
|
955
|
+
them passed was the row spread flat, or nothing at all. Only the shorthand
|
|
956
|
+
spelling resolved:
|
|
957
|
+
|
|
958
|
+
| predicate | verdict, before |
|
|
959
|
+
| ---------------------------- | -------------------------------- |
|
|
960
|
+
| `status == "pending"` | evaluates (`action:button` only) |
|
|
961
|
+
| `record.status == "pending"` | throws `record is not defined` |
|
|
962
|
+
| `data.status == "pending"` | throws `data is not defined` |
|
|
963
|
+
|
|
964
|
+
`record.` is not a mistaken spelling — it is the canonical one. It is what
|
|
965
|
+
`ExpressionEvaluator`'s CEL path binds (`bag.record` as the record namespace),
|
|
966
|
+
what `evalRowPredicate` binds on the record header, list rows, the row kebab
|
|
967
|
+
and conditional formatting (`record.status` / bare `status` / `data.status`),
|
|
968
|
+
and what the server enforces with. A `visible` that fails CLOSED turns the throw
|
|
969
|
+
into "hidden", so a correctly-authored predicate deleted its own button —
|
|
970
|
+
indistinguishable from the gate having said no. On the fail-soft legs the same
|
|
971
|
+
throw lands the other way: `disabled` greyed a control out for everyone.
|
|
972
|
+
|
|
973
|
+
Live rather than theoretical: every declared action on framework's
|
|
974
|
+
`sys_approval_request` gates on `record.viewer.*`, so the whole server-declared
|
|
975
|
+
approval decision set was invisible wherever the declared-action bar rendered
|
|
976
|
+
until objectui#4077 fixed that bar. These four generic renderers carried the
|
|
977
|
+
same binding.
|
|
978
|
+
|
|
979
|
+
What changed:
|
|
980
|
+
|
|
981
|
+
- all four bind the row the three canonical ways, through one named helper
|
|
982
|
+
(`usePredicateRecordContext`, exported from `@object-ui/react` beside
|
|
983
|
+
`useCondition`), so the action face and the row surfaces answer an author's
|
|
984
|
+
`visible:` the same way;
|
|
985
|
+
- `action:icon` reads the row at all. It evaluated against an empty bag, so not
|
|
986
|
+
even the bare-field shorthand resolved — and its `data` prop was landing in
|
|
987
|
+
the props spread onto the DOM button;
|
|
988
|
+
- `action:menu`'s items and `action:group`'s two leaves receive the row from
|
|
989
|
+
their host, which they previously never got;
|
|
990
|
+
- `action:bar` forwards the row into the overflow menu it builds, not just to
|
|
991
|
+
its inline members. An action's predicate had been answering a different
|
|
992
|
+
question purely because it spilled past `maxVisible` — which on mobile
|
|
993
|
+
defaults to 1, making the verdict a function of the viewport.
|
|
994
|
+
|
|
995
|
+
Deliberately unchanged: the evaluation entry and each site's error policy. A
|
|
996
|
+
predicate that genuinely faults still fails closed on `action:button` /
|
|
997
|
+
`action:menu` `visible` and still fails soft on the other legs, exactly as
|
|
998
|
+
before; `toPredicateInput`, `hasDeclaredVisibilityGate` and the empty-predicate
|
|
999
|
+
rules keep their pinned semantics. Binding the row is a separate question from
|
|
1000
|
+
what to do when the predicate faults.
|
|
1001
|
+
|
|
1002
|
+
A surface with no row of its own binds nothing rather than an empty record, so
|
|
1003
|
+
a host that supplies the row through the ambient predicate scope is not blanked
|
|
1004
|
+
out; a row passed explicitly still wins over the scope.
|
|
1005
|
+
|
|
1006
|
+
- e06810e: `PageComponentSchema.dataSource` is now consumed instead of discarded — a
|
|
1007
|
+
`list-view` page component can reference a **saved view by name** for the first
|
|
1008
|
+
time, and writing the binding no longer breaks the component
|
|
1009
|
+
(objectstack#5576).
|
|
1010
|
+
|
|
1011
|
+
The spec declares a per-element data binding on every page component —
|
|
1012
|
+
`dataSource: { object, view?, filter?, sort?, limit? }` — and objectui read none
|
|
1013
|
+
of it. `ViewDataProvider.resolveElementDataSource` forwarded
|
|
1014
|
+
`filter`/`sort`/`limit` and dropped `view` entirely, and had no caller outside its
|
|
1015
|
+
own test; nothing mapped `object` onto the `objectName` a list actually reads. So
|
|
1016
|
+
"reference a saved view by name" was published, validated and inert, and every
|
|
1017
|
+
page that wanted a saved view's columns/filter/sort had to inline a second copy of
|
|
1018
|
+
them — the drift the binding exists to remove.
|
|
1019
|
+
|
|
1020
|
+
Writing the binding also **broke** the block, for a reason unrelated to `view`:
|
|
1021
|
+
`SchemaRenderer` spread the schema's `dataSource` metadata onto the component as a
|
|
1022
|
+
React prop, and that is the prop name the host uses to inject the data-source
|
|
1023
|
+
ADAPTER. The plain `{ object, view }` object shadowed the adapter, so the first
|
|
1024
|
+
`dataSource.find(…)` threw `dataSource.find is not a function` and `list-view`
|
|
1025
|
+
rendered "Couldn't load records" — a spec-compliant component failing next to
|
|
1026
|
+
identical ones that omitted the binding.
|
|
1027
|
+
|
|
1028
|
+
- `@object-ui/react` — `SchemaRenderer` no longer spreads `schema.dataSource` as a
|
|
1029
|
+
prop (it is metadata, like `visibleWhen`); renderers read it off `schema`. An
|
|
1030
|
+
explicit React `dataSource` prop is unaffected. New
|
|
1031
|
+
`useElementDataSource(schema, dataSource?)` hook resolves a binding, fetching
|
|
1032
|
+
the named saved view from the object definition's `listViews` and the metadata
|
|
1033
|
+
overlay's `listViews()`.
|
|
1034
|
+
- `@object-ui/core` — new `isElementDataSourceConfig` / `collectSavedViews` /
|
|
1035
|
+
`resolveSavedView` / `composeElementDataSource`, and `resolveElementDataSource`
|
|
1036
|
+
now honours `view` through an optional `DataFetcher.fetchViews`, reporting an
|
|
1037
|
+
unresolvable view as an error instead of silently returning every record.
|
|
1038
|
+
`resolveViewId` moved here from `@object-ui/app-shell` (re-exported there) so
|
|
1039
|
+
one matcher serves both the object page and a page component.
|
|
1040
|
+
- `@object-ui/plugin-list` — `list-view` maps the binding onto the props
|
|
1041
|
+
`ListView` reads. `dataSource.*` keys are authoritative, view-supplied values
|
|
1042
|
+
are a baseline the component's own keys override, and `filter` AND-combines at
|
|
1043
|
+
every level (the spec calls the binding's filter "additional criteria"), so a
|
|
1044
|
+
binding can narrow a saved view but never widen it. A `view` name that does not
|
|
1045
|
+
resolve renders a configuration error naming the object's actual views and
|
|
1046
|
+
issues no query — it never falls back to the object's default view, because that
|
|
1047
|
+
turns a typo into a silently wider answer.
|
|
1048
|
+
|
|
1049
|
+
- ab3ad4f: An empty predicate is no longer a declared gate anywhere (objectui#3850, objectui#3862)
|
|
1050
|
+
|
|
1051
|
+
"Is a gate DECLARED on this key — is there a condition to reach a verdict on?" was
|
|
1052
|
+
answered three times in this repo, with three different scopes, and the widest
|
|
1053
|
+
answers sat on `disabled`, where the mistake is not benign:
|
|
1054
|
+
|
|
1055
|
+
- `hasDeclaredVisibilityGate` (the action face) asked `!= null && !== ''`, so every
|
|
1056
|
+
OBJECT counted — including `{ dialect: 'cel', source: '' }`. That envelope is not
|
|
1057
|
+
a hand-written curiosity: `@objectstack/spec`'s `ExpressionInputSchema` normalizes
|
|
1058
|
+
every authored predicate into one, so "the author left the predicate empty"
|
|
1059
|
+
compiles to exactly it. The verdict path normalized the same value back to
|
|
1060
|
+
`undefined`, and `evaluateCondition(undefined)` answers `true` — "no condition, so
|
|
1061
|
+
visible/enabled". On `visible` that `true` means SHOW, so the two mistakes
|
|
1062
|
+
cancelled; on `disabled` it means GREY, so they compounded: a button disabled
|
|
1063
|
+
forever that no author asked to disable (objectui#3850, the residue objectui#3842
|
|
1064
|
+
left behind).
|
|
1065
|
+
- `SchemaRenderer` asked `disabled !== undefined` inline, one notch wider again, so
|
|
1066
|
+
`disabled: null` greyed out too — on the GENERIC rendering path, since that block
|
|
1067
|
+
runs for every node type, and not as an internal flag either: `_disabled` is
|
|
1068
|
+
forwarded to the component as a real `disabled` prop (objectui#3862).
|
|
1069
|
+
- `ActionRunner`'s execution gates asked "does this normalize to something
|
|
1070
|
+
evaluable?" — the scope that turned out to be right (objectui#3848 / objectui#3872).
|
|
1071
|
+
|
|
1072
|
+
There is now ONE definition, `hasDeclaredPredicate`, exported from
|
|
1073
|
+
`@object-ui/core` (`evaluator/declaredPredicate.ts`, beside the `toPredicateInput`
|
|
1074
|
+
normalizer it is derived from): a gate is declared when normalization still leaves a
|
|
1075
|
+
condition to evaluate. `''`, a whitespace-only string, an empty-`source` envelope
|
|
1076
|
+
and any non-predicate value (`0`, `{}`) are NOT declared; `false` IS (a verdict is
|
|
1077
|
+
not a missing gate — objectui#3812). `hasDeclaredVisibilityGate` keeps its name as a
|
|
1078
|
+
re-export of it, so the five member-action renderer call sites, `DeclaredActionsBar`
|
|
1079
|
+
and `record-quick-actions` are unchanged and inherit the scope;
|
|
1080
|
+
`SchemaRenderer`'s `disabled` / `disabledOn` chain and `ActionRunner`'s two gates
|
|
1081
|
+
read the same function. No consumer got a local "and also check for empty" test —
|
|
1082
|
+
that fourth dialect is what objectui#3842 / objectui#3849 spent two PRs merging away.
|
|
1083
|
+
|
|
1084
|
+
Measured behaviour change, `action:button` and the generic path, before → after:
|
|
1085
|
+
|
|
1086
|
+
| value | `visible` | `disabled` | `enabled` | `SchemaRenderer` `disabled` prop |
|
|
1087
|
+
| --------------------------------------------------------- | -------------- | ---------- | --------- | -------------------------------- |
|
|
1088
|
+
| `''` | shown → shown | on → on | on → on | forwarded → absent |
|
|
1089
|
+
| `null` | shown → shown | on → on | on → on | forwarded → absent |
|
|
1090
|
+
| `{ dialect: 'cel', source: '' }` | shown → shown | GREY → on | on → on | forwarded → absent |
|
|
1091
|
+
| `{ source: '' }` | shown → shown | GREY → on | on → on | forwarded → absent |
|
|
1092
|
+
| `' '` (whitespace) | HIDDEN → shown | on → on | GREY → on | forwarded → absent |
|
|
1093
|
+
| `0` / `{}` (not predicates) | shown → shown | GREY → on | on → on | forwarded → absent |
|
|
1094
|
+
| `true` / `false` / bare CEL / `${…}` / non-empty envelope | unchanged | unchanged | unchanged | unchanged |
|
|
1095
|
+
|
|
1096
|
+
Every row moves toward "there is no gate here", never away from it, and no value
|
|
1097
|
+
that HAS a verdict changes it — the verdict is still read from the raw value, only
|
|
1098
|
+
the gate in front of it narrowed. Two rows are behaviour changes rather than the
|
|
1099
|
+
equivalence the ruling expected, and are pinned as such: the whitespace string moves
|
|
1100
|
+
on `visible` / `enabled` (it used to normalize to `'${ }'`, which evaluates falsy,
|
|
1101
|
+
so a predicate that says nothing HID the action from everyone), and non-predicate
|
|
1102
|
+
junk stops greying controls out (fail-open, the posture `ActionRunner` already
|
|
1103
|
+
committed to).
|
|
1104
|
+
|
|
1105
|
+
One blank spelling is knowingly still outside the scope: an envelope whose `source`
|
|
1106
|
+
is blank but not EMPTY (`{ dialect: 'cel', source: ' ' }`) — the normalizer folds a
|
|
1107
|
+
`source` of `''` and does not trim, so the string spelling of a blank predicate is
|
|
1108
|
+
trimmed and the envelope spelling is not, and `disabled` still greys out for that one
|
|
1109
|
+
value. The ruling enumerated three empty spellings; this is a fourth, measured and
|
|
1110
|
+
filed as objectui#3960 rather than widened in here.
|
|
1111
|
+
|
|
1112
|
+
One chain is deliberately untouched: `SchemaRenderer`'s `visible` / `visibleWhen` /
|
|
1113
|
+
`visibleOn` / `visibility` / `hidden` / `hiddenOn` legs keep `!== undefined`, because
|
|
1114
|
+
narrowing them would change ALIAS PRECEDENCE, not just emptiness. The `hidden` legs
|
|
1115
|
+
are not negated and therefore carry this same defect with the polarity that makes the
|
|
1116
|
+
node vanish — measured, out of this ruling's scope, filed as objectui#3955.
|
|
1117
|
+
|
|
1118
|
+
- 0109f54: Blank predicates and non-predicate values are no longer gates, at the last three entries that still judged them (objectui#3955, objectui#3957, objectui#3960)
|
|
1119
|
+
|
|
1120
|
+
objectui#3850 sank "is a predicate gate DECLARED here?" into one definition,
|
|
1121
|
+
`@object-ui/core`'s `hasDeclaredPredicate`. Three places were left out of that
|
|
1122
|
+
ruling's placement clause, each with the same shape of defect: the evaluator's
|
|
1123
|
+
single default for "there is nothing here to evaluate" is `true`, meaning
|
|
1124
|
+
_visible/enabled_, and wherever a too-wide "declared" test hands it an empty
|
|
1125
|
+
predicate on an INVERTED key, that `true` turns a control off for a value the
|
|
1126
|
+
metadata never used to say anything.
|
|
1127
|
+
|
|
1128
|
+
**`SchemaRenderer`'s `hidden` / `hiddenOn` legs (objectui#3955)** asked
|
|
1129
|
+
`!== undefined` and did NOT negate the verdict, so an empty predicate meant HIDE
|
|
1130
|
+
and the node disappeared — on the generic rendering path, since that block runs
|
|
1131
|
+
for every schema type. Harder to diagnose than the `disabled` twin objectui#3862
|
|
1132
|
+
fixed: a greyed-out control is still on screen, while a node that never rendered
|
|
1133
|
+
is indistinguishable from metadata that meant to hide it. Both legs now read the
|
|
1134
|
+
shared definition.
|
|
1135
|
+
|
|
1136
|
+
**The "blank" criterion now covers the envelope spelling (objectui#3960).** The
|
|
1137
|
+
definition trimmed a whitespace-only STRING and not an envelope's whitespace-only
|
|
1138
|
+
`source`, because `toPredicateInput` folds a `source` of `''` and does not trim.
|
|
1139
|
+
So `{ dialect: 'cel', source: ' ' }` was a declared gate whose verdict came from
|
|
1140
|
+
core's own CEL entry calling that exact value "no predicate" (`if (!source.trim())
|
|
1141
|
+
return true`) — `disabled` greyed out forever and `ActionRunner.execute` answered
|
|
1142
|
+
`{ success: false, error: 'Action is disabled' }` with the handler never invoked.
|
|
1143
|
+
Blankness is now decided once for both spellings, at the definition. The
|
|
1144
|
+
NORMALIZER's contract is deliberately unchanged: "what shape does the evaluator
|
|
1145
|
+
accept" is not the same question as "is there a condition", and moving the trim
|
|
1146
|
+
there would have flipped verdicts for every
|
|
1147
|
+
`useCondition(toPredicateInput(…))` call site, including container-level `visible`
|
|
1148
|
+
reads that never asked this question at all.
|
|
1149
|
+
|
|
1150
|
+
**`ActionEngine.getActionsForLocation`'s `visible` filter (objectui#3957)** was the
|
|
1151
|
+
last consumer answering the question with a range of its own — three empty
|
|
1152
|
+
spellings folded by hand, everything else coerced with `Boolean(raw)`. It now reads
|
|
1153
|
+
the shared definition and the coercion branch is gone, so one value no longer gets
|
|
1154
|
+
two answers depending on whether an action was surfaced by the engine or rendered
|
|
1155
|
+
standalone (the invariant objectui#3314 established). Its fail-CLOSED posture on a
|
|
1156
|
+
predicate that THROWS is untouched (`throwOnError: true` + `warnHiddenPredicate`):
|
|
1157
|
+
"the predicate faulted" and "there is no predicate" are different facts.
|
|
1158
|
+
|
|
1159
|
+
Behaviour changes, before → after. Observation-class: each needs an author to write
|
|
1160
|
+
an empty/blank predicate or a non-predicate value, and there is no known user path
|
|
1161
|
+
today.
|
|
1162
|
+
|
|
1163
|
+
| value | `ActionEngine` `visible` | `SchemaRenderer` `hidden` | `disabled` (action face + generic path) | `ActionRunner.execute` `disabled` |
|
|
1164
|
+
| ----------------------------------------------------------- | ------------------------ | ------------------------- | --------------------------------------- | --------------------------------- |
|
|
1165
|
+
| `''` / `null` | shown → shown | HIDDEN → rendered | unchanged | unchanged |
|
|
1166
|
+
| `' '` (blank text) | HIDDEN → shown | HIDDEN → rendered | unchanged | unchanged |
|
|
1167
|
+
| `0` / `NaN` | HIDDEN → shown | HIDDEN → rendered | unchanged | unchanged |
|
|
1168
|
+
| `{}` / `[]` | shown → shown | HIDDEN → rendered | unchanged | unchanged |
|
|
1169
|
+
| `{ dialect: 'cel', source: '' }` | shown → shown | HIDDEN → rendered | unchanged | unchanged |
|
|
1170
|
+
| `{ dialect: 'cel', source: ' ' }` | shown → shown | HIDDEN → rendered | GREY → on | refused → runs |
|
|
1171
|
+
| `{ source: ' ' }` (no dialect) | HIDDEN → shown | HIDDEN → rendered | GREY → on | refused → runs |
|
|
1172
|
+
| `true` / `false` / bare CEL / `${…}` / a non-blank envelope | unchanged | unchanged | unchanged | unchanged |
|
|
1173
|
+
|
|
1174
|
+
Every row moves toward "there is no gate here", never away from it, and no value
|
|
1175
|
+
that HAS a verdict changes it — a declared `false` is still a verdict, not a
|
|
1176
|
+
missing gate (objectui#3812), and blankness is `trim()`, not "short": `{ dialect:
|
|
1177
|
+
'cel', source: ' x ' }` is a predicate. One alias precedence changes with the
|
|
1178
|
+
`hidden` legs and is pinned rather than claimed as an equivalence: an undeclared
|
|
1179
|
+
`hidden` no longer short-circuits the chain, so a declared `hiddenOn` is finally
|
|
1180
|
+
consulted.
|
|
1181
|
+
|
|
1182
|
+
`SchemaRenderer`'s four `visible*` legs keep `!== undefined` deliberately, as
|
|
1183
|
+
objectui#3850 ruled: their `true` is negated, so an empty predicate already lands
|
|
1184
|
+
on "shown", and narrowing them would change alias precedence rather than fix
|
|
1185
|
+
anything.
|
|
1186
|
+
|
|
1187
|
+
- Updated dependencies [6719877]
|
|
1188
|
+
- Updated dependencies [56ff091]
|
|
1189
|
+
- Updated dependencies [7864f03]
|
|
1190
|
+
- Updated dependencies [d229dfa]
|
|
1191
|
+
- Updated dependencies [4bc6c23]
|
|
1192
|
+
- Updated dependencies [c3b01a7]
|
|
1193
|
+
- Updated dependencies [f5f8744]
|
|
1194
|
+
- Updated dependencies [3765678]
|
|
1195
|
+
- Updated dependencies [d83f6b3]
|
|
1196
|
+
- Updated dependencies [5f08c05]
|
|
1197
|
+
- Updated dependencies [69becd2]
|
|
1198
|
+
- Updated dependencies [5e52495]
|
|
1199
|
+
- Updated dependencies [b750823]
|
|
1200
|
+
- Updated dependencies [e06810e]
|
|
1201
|
+
- Updated dependencies [ab3ad4f]
|
|
1202
|
+
- Updated dependencies [c2fd122]
|
|
1203
|
+
- Updated dependencies [ac2139c]
|
|
1204
|
+
- Updated dependencies [b14ab3a]
|
|
1205
|
+
- Updated dependencies [8c60819]
|
|
1206
|
+
- Updated dependencies [41d6022]
|
|
1207
|
+
- Updated dependencies [e64a52e]
|
|
1208
|
+
- Updated dependencies [844d17f]
|
|
1209
|
+
- Updated dependencies [48132f7]
|
|
1210
|
+
- Updated dependencies [4dcd52a]
|
|
1211
|
+
- Updated dependencies [42ae5c6]
|
|
1212
|
+
- Updated dependencies [1d723e3]
|
|
1213
|
+
- Updated dependencies [0109f54]
|
|
1214
|
+
- Updated dependencies [7e5bb5d]
|
|
1215
|
+
- Updated dependencies [fbc23e0]
|
|
1216
|
+
- Updated dependencies [6d762da]
|
|
1217
|
+
- Updated dependencies [e6fdbdc]
|
|
1218
|
+
- Updated dependencies [f9faa7d]
|
|
1219
|
+
- Updated dependencies [6bb454a]
|
|
1220
|
+
- Updated dependencies [523be48]
|
|
1221
|
+
- Updated dependencies [7e2b7e9]
|
|
1222
|
+
- Updated dependencies [33526fd]
|
|
1223
|
+
- Updated dependencies [32413ec]
|
|
1224
|
+
- Updated dependencies [c1e1e6b]
|
|
1225
|
+
- @object-ui/core@17.4.0
|
|
1226
|
+
- @object-ui/i18n@17.4.0
|
|
1227
|
+
- @object-ui/types@17.4.0
|
|
1228
|
+
- @object-ui/data-objectstack@17.4.0
|
|
1229
|
+
|
|
3
1230
|
## 17.3.0
|
|
4
1231
|
|
|
5
1232
|
### Patch Changes
|
|
@@ -1555,6 +2782,7 @@
|
|
|
1555
2782
|
result and the "warn-once" tracking are now stored separately: a
|
|
1556
2783
|
`WeakMap` caches the validation outcome (so the visual flag is stable),
|
|
1557
2784
|
while a `WeakSet` continues to dedupe `console.warn` output.
|
|
2785
|
+
|
|
1558
2786
|
- @object-ui/types@6.2.2
|
|
1559
2787
|
- @object-ui/core@6.2.2
|
|
1560
2788
|
- @object-ui/i18n@6.2.2
|
|
@@ -1704,6 +2932,7 @@
|
|
|
1704
2932
|
### Minor Changes
|
|
1705
2933
|
|
|
1706
2934
|
- b2d1704: feat(cmdk): record search across objects in the Command Palette
|
|
2935
|
+
|
|
1707
2936
|
- New `useRecordSearch` hook in `@object-ui/react` debounces a query, fans out
|
|
1708
2937
|
to `dataSource.find(name, { $search, $top })` across candidate objects, and
|
|
1709
2938
|
aggregates hits. Race-safe via a monotonic runId; per-object 404s are
|
|
@@ -1718,6 +2947,7 @@
|
|
|
1718
2947
|
|
|
1719
2948
|
Three related fixes that all addressed the same UX: a user follows a URL
|
|
1720
2949
|
shaped `/{object}/{recordId}` and sees a completely blank content area.
|
|
2950
|
+
|
|
1721
2951
|
1. **`useNavigationOverlay` produced the broken URL itself.** When
|
|
1722
2952
|
middle-click / Cmd-click opened a gallery card in a new tab and no
|
|
1723
2953
|
`onNavigate` was provided, the hook built `/{object}/{id}` — a URL
|
|
@@ -1737,6 +2967,7 @@
|
|
|
1737
2967
|
|
|
1738
2968
|
- aa063db: `useRecordSearch` now orders hits by relevance instead of object-fanout
|
|
1739
2969
|
order. Tiers (higher wins):
|
|
2970
|
+
|
|
1740
2971
|
- 110: exact recordId paste
|
|
1741
2972
|
- 100: display exactly equals the query
|
|
1742
2973
|
- 80: display starts with the query
|
|
@@ -1796,17 +3027,20 @@
|
|
|
1796
3027
|
- bd8447d: Three platform-wide detail polish items.
|
|
1797
3028
|
|
|
1798
3029
|
**Tighter page rhythm**
|
|
3030
|
+
|
|
1799
3031
|
- Outer `PageRenderer` padding `p-4 md:p-6 lg:p-8` → `p-3 md:p-4 lg:p-6`
|
|
1800
3032
|
and outer body wrap `space-y-8` → `space-y-6` so list / detail / home
|
|
1801
3033
|
pages share the same edge rhythm. Cuts ~16px of edge slack on lg.
|
|
1802
3034
|
|
|
1803
3035
|
**Highlights KPI treatment**
|
|
3036
|
+
|
|
1804
3037
|
- `HeaderHighlight` now renders numeric / currency / percent / decimal
|
|
1805
3038
|
values as KPI numbers (`text-xl md:text-2xl font-semibold tabular-nums`)
|
|
1806
3039
|
instead of the uniform `text-sm font-semibold`, so amount / probability
|
|
1807
3040
|
/ count fields read as headline stats — Salesforce-style key facts.
|
|
1808
3041
|
|
|
1809
3042
|
**Discussion footer upgrade**
|
|
3043
|
+
|
|
1810
3044
|
- `RecordActivityTimeline` now uses `RichTextCommentInput` (bold / italic /
|
|
1811
3045
|
list / code, `@`-mention autocomplete, preview toggle, Send) instead of
|
|
1812
3046
|
a bare `<textarea>`.
|
|
@@ -1817,6 +3051,7 @@
|
|
|
1817
3051
|
and sidebar positions.
|
|
1818
3052
|
|
|
1819
3053
|
- d51a577: feat(platform): Discussion attachments + @mention directory + Reference Rail aside
|
|
3054
|
+
|
|
1820
3055
|
- **Discussion attachments** — `RichTextCommentInput` now accepts an `extraSlot`
|
|
1821
3056
|
and a `canSubmitEmpty` flag so hosts can mount the existing
|
|
1822
3057
|
`CommentAttachment` composer beneath the editor without forking the toolbar.
|
|
@@ -1906,6 +3141,7 @@
|
|
|
1906
3141
|
page so the two renderers share state.
|
|
1907
3142
|
|
|
1908
3143
|
- 74962b0: feat(detail): record:discussion schema component + flush accordion variant
|
|
3144
|
+
|
|
1909
3145
|
- New `record:discussion` schema type lets authors place the record
|
|
1910
3146
|
chatter feed anywhere in a custom Page schema. Wired through a
|
|
1911
3147
|
shared `DiscussionContext` provider on the `assignedPage` branch
|
|
@@ -1926,6 +3162,7 @@
|
|
|
1926
3162
|
customize the header or one tab.
|
|
1927
3163
|
|
|
1928
3164
|
**Slot menu (v1):**
|
|
3165
|
+
|
|
1929
3166
|
- `header` — replaces `page:header`
|
|
1930
3167
|
- `actions` — replaces the `record:quick_actions` action bar
|
|
1931
3168
|
- `highlights` — replaces the chips + chevron path strip
|
|
@@ -1953,6 +3190,7 @@
|
|
|
1953
3190
|
```
|
|
1954
3191
|
|
|
1955
3192
|
**API changes:**
|
|
3193
|
+
|
|
1956
3194
|
- `PageSchema` (in `@object-ui/types`): adds `kind?: 'full' | 'slotted'`
|
|
1957
3195
|
(default `'full'`) and `slots?: PageSlotMap`.
|
|
1958
3196
|
- `usePageAssignment` (in `@object-ui/react`): result now exposes a
|
|
@@ -2132,6 +3370,7 @@
|
|
|
2132
3370
|
detail view (`TypeError: titleFormat.replace is not a function`) and printed
|
|
2133
3371
|
`Failed to evaluate expression: ${[object Object]}` for every action visibility
|
|
2134
3372
|
predicate.
|
|
3373
|
+
|
|
2135
3374
|
- `@object-ui/core`: `ExpressionEvaluator.evaluate` / `evaluateCondition` now
|
|
2136
3375
|
unwrap Expression envelopes transparently.
|
|
2137
3376
|
- `@object-ui/react`: new `toPredicateInput()` helper to safely normalize
|
|
@@ -2383,6 +3622,7 @@
|
|
|
2383
3622
|
- New plugin-object and ObjectQL SDK updates
|
|
2384
3623
|
|
|
2385
3624
|
**Added:**
|
|
3625
|
+
|
|
2386
3626
|
- New Plugin: @object-ui/plugin-object - ObjectQL plugin for automatic table and form generation
|
|
2387
3627
|
- ObjectTable: Auto-generates tables from ObjectQL object schemas
|
|
2388
3628
|
- ObjectForm: Auto-generates forms from ObjectQL object schemas with create/edit/view modes
|
|
@@ -2391,6 +3631,7 @@
|
|
|
2391
3631
|
- ObjectQL Integration: Enhanced ObjectQLDataSource with getObjectSchema() method using MetadataApiClient
|
|
2392
3632
|
|
|
2393
3633
|
**Changed:**
|
|
3634
|
+
|
|
2394
3635
|
- Updated @objectql/sdk from ^1.8.3 to ^1.9.1
|
|
2395
3636
|
- Updated @objectql/types from ^1.8.3 to ^1.9.1
|
|
2396
3637
|
|
|
@@ -2404,6 +3645,7 @@
|
|
|
2404
3645
|
- Patch release: Add automated changeset workflow and CI/CD improvements
|
|
2405
3646
|
|
|
2406
3647
|
This release includes infrastructure improvements:
|
|
3648
|
+
|
|
2407
3649
|
- Added changeset-based version management
|
|
2408
3650
|
- Enhanced CI/CD workflows with GitHub Actions
|
|
2409
3651
|
- Improved documentation for contributing and releasing
|