@object-ui/react 17.2.0 → 17.5.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 +956 -0
- package/README.md +39 -13
- package/dist/SchemaRenderer.d.ts +78 -5
- package/dist/SchemaRenderer.js +139 -13
- package/dist/context/AppShellContext.d.ts +21 -2
- package/dist/context/RecordContext.d.ts +0 -4
- package/dist/context/RecordContext.js +0 -2
- package/dist/context/RelatedRecordActionsContext.d.ts +29 -0
- package/dist/context/RelatedRecordActionsContext.js +6 -0
- package/dist/element-data-source/ElementDataSourceGate.d.ts +199 -0
- package/dist/element-data-source/ElementDataSourceGate.js +195 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +10 -0
- package/dist/hooks/useActionTextLocalizer.d.ts +69 -0
- package/dist/hooks/useActionTextLocalizer.js +91 -0
- package/dist/hooks/useDatasetDimensionLabels.d.ts +104 -0
- package/dist/hooks/useDatasetDimensionLabels.js +154 -0
- package/dist/hooks/useElementDataSource.d.ts +68 -0
- package/dist/hooks/useElementDataSource.js +138 -0
- package/dist/hooks/useExpression.d.ts +94 -17
- package/dist/hooks/useExpression.js +96 -34
- package/dist/hooks/useNavigationOverlay.d.ts +58 -10
- package/dist/hooks/useOffline.d.ts +62 -36
- package/dist/hooks/useOffline.js +2 -2
- package/dist/hooks/usePageAssignment.js +4 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -1
- package/dist/schema-input.d.ts +31 -0
- package/dist/schema-input.js +31 -0
- package/dist/spec-bridge/SpecBridge.d.ts +11 -5
- package/dist/spec-bridge/SpecBridge.js +7 -1
- package/dist/spec-bridge/bridges/list-view.js +106 -14
- package/dist/spec-bridge/types.d.ts +24 -3
- package/dist/utils/i18n.d.ts +43 -8
- package/dist/utils/i18n.js +42 -4
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,946 @@
|
|
|
1
1
|
# @object-ui/react
|
|
2
2
|
|
|
3
|
+
## 17.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d9d3463: Retire four zero-consumer declared surfaces (dead-surface sweep batch 3, #4328). Each was
|
|
8
|
+
measured as declared-but-never-read at the branch point, and each is removed rather than
|
|
9
|
+
left as an authoring surface whose values nothing acts on.
|
|
10
|
+
|
|
11
|
+
Breaking for anyone who typed against the removed declarations, marked `minor` per this
|
|
12
|
+
repository's version-alignment convention (the major tracks `@objectstack`, never an
|
|
13
|
+
API-break count):
|
|
14
|
+
|
|
15
|
+
- `@object-ui/core` no longer exports `mergeViewsIntoObjects`. It was a second copy left
|
|
16
|
+
behind by the move of that step to the provider layer, and it had drifted: it ignored a
|
|
17
|
+
view container's default `list` and keyed views by the authored bare key instead of the
|
|
18
|
+
composer's `<object>.<key>` identity. The live implementation — `MetadataProvider`'s, in
|
|
19
|
+
`@object-ui/app-shell` — is unchanged and remains the only one. (#3775)
|
|
20
|
+
- `@object-ui/types`' `RoleDefinition` no longer declares `permissions`. A role's grants
|
|
21
|
+
live in `ObjectPermissionConfig.roles`, keyed by object; that is the only home any
|
|
22
|
+
consumer reads (`resolveRoles` walks `inherits` and matches on `name`). The removed
|
|
23
|
+
field was _required_, so five fixtures across three packages had been declaring an empty
|
|
24
|
+
array for a value nothing would ever look at. Role-attached grants are now a compile
|
|
25
|
+
error rather than silently ignored data. (#4288)
|
|
26
|
+
- `@object-ui/react`'s `RecordContextValue` no longer declares `loading` / `error`. Both
|
|
27
|
+
had zero producers and zero consumers — no host passed them, no `record:*` renderer read
|
|
28
|
+
them — and only the provider's memo dependency list still named them. Record-level
|
|
29
|
+
loading and error state stays where it is actually expressed: each renderer's own data
|
|
30
|
+
source. (#3773)
|
|
31
|
+
|
|
32
|
+
No behaviour change, no request-count change:
|
|
33
|
+
|
|
34
|
+
- `@object-ui/data-objectstack` drops five `metadataCache.invalidate('views:<object>')`
|
|
35
|
+
calls across `updateViewConfig` / `createView` / `updateView` / `deleteView`. No read
|
|
36
|
+
path has ever populated that key — `listViews` fetches directly, uncached — so all five
|
|
37
|
+
were permanent no-ops. The invalidations of the keys that do have readers
|
|
38
|
+
(`view:<object>:<viewId>` for `getView`, `view-overrides:<object>` for
|
|
39
|
+
`listViewOverrides`) are untouched and now pinned. (#3778)
|
|
40
|
+
|
|
41
|
+
- b953a97: fix(detail): lookup field values link to the referenced record
|
|
42
|
+
|
|
43
|
+
A valued lookup on a record detail page rendered as plain text plus a copy
|
|
44
|
+
button — the referenced document's name was visible but unreachable, so users
|
|
45
|
+
copied the number and searched for it from the list page instead. Lookup cells
|
|
46
|
+
inside a related list that pointed at a third object were dead the same way.
|
|
47
|
+
|
|
48
|
+
`LookupCellRenderer` — the one cell renderer both surfaces resolve through —
|
|
49
|
+
now renders the display value as a link to the referenced record. The display
|
|
50
|
+
name resolution, the copy affordance and every non-lookup field are unchanged,
|
|
51
|
+
and a lookup with no value still renders its placeholder rather than an empty
|
|
52
|
+
link.
|
|
53
|
+
|
|
54
|
+
The URL is not assembled in the renderer. `RelatedRecordActionsContext` gains
|
|
55
|
+
an optional `recordHref` / `openRecord` pair, published by the console's
|
|
56
|
+
`RelatedRecordActionsBridge` from the SAME builder its related-list row
|
|
57
|
+
navigation already used, so there is one record-route shape rather than a
|
|
58
|
+
second one. A host that does not provide it (Studio designer, embedded
|
|
59
|
+
renderers, standalone grids) renders exactly what it rendered before.
|
|
60
|
+
|
|
61
|
+
- d7f3e30: `bridgeListView` maps the five row heights the spec admits, and only those — the four dead spellings are gone
|
|
62
|
+
|
|
63
|
+
`mapDensity` carried a nine-key table: `compact`, `short`, `comfortable`,
|
|
64
|
+
`spacious`, `small`, `medium`, `large`, `tall`, `extra_tall`. `RowHeightSchema`
|
|
65
|
+
in `@objectstack/spec` admits five — `short | compact | medium | tall |
|
|
66
|
+
extra_tall` — so `comfortable`, `spacious`, `small` and `large` were unreachable
|
|
67
|
+
from any spec-valid list view. The bridge's own parameter type said as much
|
|
68
|
+
(`Partial< ListView >`), and `mapDensity` widened it back to `rowHeight?: string`
|
|
69
|
+
to let them in. They survived because the fixture asserting three of them
|
|
70
|
+
compiled against nothing: the package's build tsconfig excludes tests and no
|
|
71
|
+
other `tsc` read them, so four branches of renderer-side dialect read as live
|
|
72
|
+
capability (objectui#4352, surfaced by objectui#4040 / PR #4351).
|
|
73
|
+
|
|
74
|
+
They are deleted. The parameter takes the spec type honestly, and the table is
|
|
75
|
+
now `Record< RowHeight, … >`, so a row height added upstream fails the build here
|
|
76
|
+
instead of arriving with no density. This is AGENTS.md #0.1: a lenient reading
|
|
77
|
+
for off-spec metadata is a second de-facto contract, and one strict contract
|
|
78
|
+
beats N dialects — a bad `rowHeight` gets fixed at the producer, where the schema
|
|
79
|
+
already rejects it.
|
|
80
|
+
|
|
81
|
+
**Breaking semantics, deliberately graded `minor`** (this repo never publishes
|
|
82
|
+
`major` — its major tracks `@objectstack`). Nothing narrows in the published type
|
|
83
|
+
surface: `mapDensity` is module-local and never appeared in the emitted `.d.ts`,
|
|
84
|
+
and `bridgeListView`'s declaration is unchanged. What changes is runtime output,
|
|
85
|
+
and only for input the spec already rejects: a host handing the bridge
|
|
86
|
+
`rowHeight: 'comfortable'` (or `'spacious'` / `'small'` / `'large'`) used to get
|
|
87
|
+
`density: 'comfortable'` / `'spacious'` / `'compact'` back, and now gets no
|
|
88
|
+
`density` key at all, so the renderer's own default applies. A sweep of this repo,
|
|
89
|
+
the `objectstack` example apps and the console's view metadata found zero authored
|
|
90
|
+
uses of any of the four; the legacy `densityMode` alias cannot produce one either,
|
|
91
|
+
since `DENSITY_MODE_TO_ROW_HEIGHT` is typed `Record< DensityMode, RowHeight >` and
|
|
92
|
+
folds onto `compact` / `medium` / `tall`.
|
|
93
|
+
|
|
94
|
+
- a84385b: `NavigationConfig.mode` is optional — the type now says what the hook does
|
|
95
|
+
|
|
96
|
+
`@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.
|
|
97
|
+
|
|
98
|
+
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.
|
|
99
|
+
|
|
100
|
+
The alias is now the spec's authored config verbatim, with no divergence of its own:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
export type NavigationConfig = SpecAuthoredInput<
|
|
104
|
+
typeof NavigationConfigSchema
|
|
105
|
+
>;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
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.
|
|
109
|
+
|
|
110
|
+
**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.
|
|
111
|
+
|
|
112
|
+
**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.
|
|
113
|
+
|
|
114
|
+
- c1d939f: One `SchemaNode`, and one label vocabulary — the union wins, and labels resolve where the locale lives
|
|
115
|
+
|
|
116
|
+
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.
|
|
117
|
+
|
|
118
|
+
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.
|
|
119
|
+
|
|
120
|
+
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.
|
|
121
|
+
|
|
122
|
+
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.
|
|
123
|
+
|
|
124
|
+
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.
|
|
125
|
+
|
|
126
|
+
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.
|
|
127
|
+
|
|
128
|
+
- 3f5f87c: `SchemaRenderer` states its real contract — a typed, required `schema` and a deliberate forwarding surface
|
|
129
|
+
|
|
130
|
+
`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.
|
|
131
|
+
|
|
132
|
+
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.
|
|
133
|
+
|
|
134
|
+
**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>>`.
|
|
135
|
+
|
|
136
|
+
`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.
|
|
137
|
+
|
|
138
|
+
**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.
|
|
139
|
+
|
|
140
|
+
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.
|
|
141
|
+
|
|
142
|
+
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.
|
|
143
|
+
|
|
144
|
+
- bb68488: Stop declaring 14 symbols under names `@objectstack/spec` owns at `17.0.0-rc.6`
|
|
145
|
+
(objectui#4167, objectstack#4115).
|
|
146
|
+
|
|
147
|
+
The rc.6 bump published nine names this repo already declared locally, on top of
|
|
148
|
+
four that predate it — `check:spec-symbols` reported all thirteen at once, and a
|
|
149
|
+
fourteenth (`GlobalFilterSchema`) appeared during the bump itself. Each was
|
|
150
|
+
triaged on its own rather than blanket-renamed, because the right answer differs
|
|
151
|
+
per symbol: five bind to the spec, three are renamed because the spec's
|
|
152
|
+
same-named export means something else, five arrive by derivation, and one is a
|
|
153
|
+
declared dialect with a written reason.
|
|
154
|
+
|
|
155
|
+
**Breaking for importers of `@object-ui/react`, `@object-ui/app-shell` and
|
|
156
|
+
`@object-ui/types`** — three exported names changed, because the spec exports the
|
|
157
|
+
same name for a _different_ thing:
|
|
158
|
+
|
|
159
|
+
| package | was | now | what the spec's same-named export actually is |
|
|
160
|
+
| :-------------------- | :----------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
161
|
+
| `react` / `app-shell` | `MetadataState` | `MetadataCacheState` | a metadata item's LIFECYCLE state — `'draft' \| 'active' \| 'deprecated' \| 'archived'` (`MetadataStateSchema`, `@objectstack/spec/system`) |
|
|
162
|
+
| `react` / `app-shell` | `resolveI18nLabel` | `resolveKeyedI18nLabel` | a resolver for the INLINE per-locale map (`{ en: 'Owner', 'zh-CN': '负责人' }`) against a BCP-47 locale |
|
|
163
|
+
| `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 |
|
|
164
|
+
|
|
165
|
+
`resolveI18nLabel` is the one where the collision had already started costing
|
|
166
|
+
something. rc.6 widened `I18nLabel` from `string` to
|
|
167
|
+
`string | Record< string, string >`, so the same authored value now reaches
|
|
168
|
+
either resolver — and each answers wrongly, silently, for the other's input: the
|
|
169
|
+
keyed one returns `undefined` for `{ en: 'Owner' }` (no `key`, no
|
|
170
|
+
`defaultValue`), and the spec's reads `key` / `defaultValue` / `params` as locale
|
|
171
|
+
tags. The rc.6 bump PR met this and aliased the spec's import as
|
|
172
|
+
`resolveInlineI18nLabel` in five files, with hand-written comments at two of
|
|
173
|
+
them. That is a review convention, which is what objectstack#4115 exists to
|
|
174
|
+
replace with a rule — so `Keyed` is now the counterpart of that `Inline`, and the
|
|
175
|
+
name says which vocabulary it resolves at every call site.
|
|
176
|
+
|
|
177
|
+
**Eleven keep their names and are now imported or derived from the spec** instead
|
|
178
|
+
of re-declared: `DATE_RANGE_PRESETS`, `NavigationMode`, `AddressValue`,
|
|
179
|
+
`BreakpointColumnMap`, `BreakpointOrderMap`, `KanbanConfig`, `CalendarConfig`,
|
|
180
|
+
`GanttConfig`, plus the three renamed above at their new names.
|
|
181
|
+
|
|
182
|
+
**Four of the copies were losing information, not just duplicating it.**
|
|
183
|
+
|
|
184
|
+
- **`GanttConfig` declared six keys and called itself canonical; rc.6's
|
|
185
|
+
`GanttConfigSchema` declares seventeen.** The eleven it never mentioned —
|
|
186
|
+
`parentField`, `typeField`, `baselineStartField`, `baselineEndField`,
|
|
187
|
+
`groupByField`, `resourceView`, `assigneeField`, `effortField`, `capacity`,
|
|
188
|
+
`quickFilters`, `autoZoomToFilter` — are all read by
|
|
189
|
+
`plugin-gantt/src/ObjectGantt.tsx`, through a local `GanttConfigEx`
|
|
190
|
+
intersection that existed only because this type did not carry them. It now
|
|
191
|
+
derives from the spec, with `timeSegments` (shift segmentation) as the one
|
|
192
|
+
genuinely local extension; the schema is `$loose` upstream, so that key is
|
|
193
|
+
legal metadata rather than a second dialect.
|
|
194
|
+
- **`GanttConfig.tooltipFields` carried the comment "not part of the upstream
|
|
195
|
+
GanttConfigSchema".** It is, as of rc.6, so the key now arrives from the spec.
|
|
196
|
+
- **`AddressValue` declared five of the spec's seven parts** — `countryCode` and
|
|
197
|
+
`formatted` were missing, under a comment already claiming to be "the part
|
|
198
|
+
names of `AddressSchema`". The widget still renders five inputs; binding the
|
|
199
|
+
type stops it from asserting the platform cannot store the other two, and makes
|
|
200
|
+
the `{ ...address }` write-through say so.
|
|
201
|
+
- **`DATE_RANGE_PRESETS` was `Object.keys(PRESET_RANGES)`,** a third copy of a
|
|
202
|
+
vocabulary the spec extracted in objectstack#4614 precisely to collapse — its
|
|
203
|
+
own doc comment names this module as one of the three. It is now the spec's
|
|
204
|
+
array by reference, and the local date-macro bounds table is pinned complete
|
|
205
|
+
against it with `satisfies`, so a preset the schema gains without bounds here
|
|
206
|
+
is a compile error rather than a filter that validates clean and then selects
|
|
207
|
+
nothing.
|
|
208
|
+
|
|
209
|
+
`NavigationMode` was one hop from the spec already (`NavigationConfig['mode']`);
|
|
210
|
+
it is bound directly, with a both-directions type pin that it stays the same type
|
|
211
|
+
as the config's own `mode`. `KanbanConfig` / `CalendarConfig` /
|
|
212
|
+
`BreakpointColumnMap` / `BreakpointOrderMap` were exact hand copies of `$strict`
|
|
213
|
+
schemas and are now re-exports — "still exact" is the argument for binding them,
|
|
214
|
+
since a copy with nothing to protect can only drift.
|
|
215
|
+
|
|
216
|
+
`GlobalFilterSchema` is the one ALLOW entry. It is the same spread-composition
|
|
217
|
+
dialect as `SelectOptionSchema` next to it, and it collided only because rc.6's
|
|
218
|
+
new refinement forced `.extend()` to be respelled as a `.shape` spread — which
|
|
219
|
+
moved a derivation the guard could see into an object literal it deliberately
|
|
220
|
+
does not descend into. The dialect is unchanged and its three divergences are
|
|
221
|
+
pinned; which side moves on the refinement itself is objectui#4165.
|
|
222
|
+
|
|
223
|
+
`@objectstack/spec` moves from `devDependencies` to `dependencies` in
|
|
224
|
+
`@object-ui/layout`: its public type surface now references the spec.
|
|
225
|
+
|
|
226
|
+
### Patch Changes
|
|
227
|
+
|
|
228
|
+
- ceccdcf: Action confirm dialogs and success toasts now honour the bundle's translated
|
|
229
|
+
`confirmText` / `successMessage`, not just `label` (objectui#4265).
|
|
230
|
+
|
|
231
|
+
A TranslationBundle entry for an action carries three keys under one
|
|
232
|
+
`_actions.<name>` node — `label`, `confirmText`, `successMessage` — and
|
|
233
|
+
`useObjectLabel()` has always exposed a resolver for each. What had drifted was
|
|
234
|
+
the call sites: `page:header` (authored record pages), `record:quick_actions`
|
|
235
|
+
and the related-list row menu resolved the button `label` only and dispatched
|
|
236
|
+
the authored `confirmText` / `successMessage` untouched. One bundle entry met
|
|
237
|
+
two fates: the button rendered the translation, the confirm dialog rendered the
|
|
238
|
+
authored English.
|
|
239
|
+
|
|
240
|
+
All action-rendering surfaces now go through one resolver,
|
|
241
|
+
`useActionTextLocalizer()` (new, exported from `@object-ui/react`), which
|
|
242
|
+
applies the existing `actionLabel` / `actionConfirm` / `actionSuccess`
|
|
243
|
+
resolvers over the three keys together. Fallback is unchanged: with no bundle
|
|
244
|
+
entry — or an entry lacking a key — the authored text renders. A bundle cannot
|
|
245
|
+
introduce a `confirmText` or `successMessage` the metadata never declared.
|
|
246
|
+
|
|
247
|
+
- ee26e65: Analytics: the dimension label net's fetch-and-memo glue is written once, not once per surface
|
|
248
|
+
|
|
249
|
+
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.
|
|
250
|
+
|
|
251
|
+
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.
|
|
252
|
+
|
|
253
|
+
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).
|
|
254
|
+
|
|
255
|
+
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.
|
|
256
|
+
|
|
257
|
+
- f650253: `BaseSchema.ariaLabel` declares the keyed i18n vocabulary the renderer actually
|
|
258
|
+
resolves, `.disabled` accepts the predicate string it actually evaluates, and the
|
|
259
|
+
keyed shape finally has a name (objectui#4581)
|
|
260
|
+
|
|
261
|
+
Three slots on one base type had drifted from what the renderer does with them.
|
|
262
|
+
PR #4593 fixed `visible` and measured the rest; these are the rest.
|
|
263
|
+
|
|
264
|
+
`ariaLabel` was `string`, but `SchemaRenderer.tsx:111` resolves it with
|
|
265
|
+
`resolveKeyedI18nLabel`, whose input is the KEYED form
|
|
266
|
+
`{ key, defaultValue?, params? }` — a reference into a translation bundle. It is
|
|
267
|
+
now `string | KeyedI18nLabel`, and `KeyedI18nLabel` is a new exported type in
|
|
268
|
+
`@object-ui/types` rather than a fourth inline copy of one object literal: the
|
|
269
|
+
three that existed (`@object-ui/react`'s resolver, `@object-ui/layout`'s
|
|
270
|
+
`resolveLabel`, `@object-ui/app-shell`'s `t`-taking twin) were verified identical
|
|
271
|
+
in their object half first, and two of them now import the name.
|
|
272
|
+
|
|
273
|
+
The vocabulary matters more than the widening. `#4581` originally asked for
|
|
274
|
+
`string | I18nLabel`, and that spelling was withdrawn as measured-wrong: the
|
|
275
|
+
spec's `I18nLabel` is the INLINE LOCALE MAP (`string | Record<string, string>`),
|
|
276
|
+
a different vocabulary resolved against a BCP-47 locale by a different function
|
|
277
|
+
of a confusingly similar name. Under it the shipped keyed fixture type-checked
|
|
278
|
+
only vacuously — as a locale map whose "locales" are named `key` and
|
|
279
|
+
`defaultValue` — the same label carrying `params` was rejected outright, and a
|
|
280
|
+
genuine `{ en: 'Owner' }` compiled while rendering an EMPTY `aria-label`. Naming
|
|
281
|
+
the keyed shape is the declaration half of the fix objectui#4167 started on the
|
|
282
|
+
naming side; `@object-ui/app-shell`'s copy keeps its inline spelling for now
|
|
283
|
+
because an open PR has a pending change to that file, and the comment there says
|
|
284
|
+
so.
|
|
285
|
+
|
|
286
|
+
`disabled` was `boolean` on a key the renderer never reads as one:
|
|
287
|
+
`SchemaRenderer.tsx:466` evaluates it through the same `evaluateCondition` as
|
|
288
|
+
`visible`, and a `disabledOn?: string` sibling exists for the same reason. It is
|
|
289
|
+
now `boolean | string`. The asymmetry with `visible` was accidental rather than
|
|
290
|
+
deliberate.
|
|
291
|
+
|
|
292
|
+
Both are widenings on authored-input-dominant properties: authors gain a
|
|
293
|
+
spelling, nothing that type-checked before stops doing so, and readers already
|
|
294
|
+
coped with `any` through `BaseSchema`'s index signature. Three test fixtures that
|
|
295
|
+
had been casting past these declarations with `as unknown as BaseSchema` state
|
|
296
|
+
their values directly now, and the declared unions are pinned invariantly so
|
|
297
|
+
neither a missing widening nor an overshoot to `any` can pass unnoticed.
|
|
298
|
+
|
|
299
|
+
Declaring the vocabulary honestly also surfaced a real one: the `toggle`
|
|
300
|
+
renderer writes `aria-label` itself instead of going through SchemaRenderer's
|
|
301
|
+
resolver, and it forwarded the raw value. Invoked directly it emitted
|
|
302
|
+
`aria-label="[object Object]"` for a keyed label — announced verbatim by a
|
|
303
|
+
screen reader. It resolves now. Through `SchemaRenderer` the bug was invisible,
|
|
304
|
+
because SchemaRenderer injects its own resolved `aria-label` afterwards; a
|
|
305
|
+
downstream type-check sweep found it, not a test.
|
|
306
|
+
|
|
307
|
+
`BaseSchema.label` and `.description` are deliberately unchanged and pinned that
|
|
308
|
+
way. They receive the spec's inline `I18nLabel` from the view bridges, which is a
|
|
309
|
+
real defect, but resolving it belongs at the spec-to-schema boundary rather than
|
|
310
|
+
in this declaration — and that work is still blocked on a design question about
|
|
311
|
+
where the display locale enters, so it is not in this release.
|
|
312
|
+
|
|
313
|
+
- 8f85f8b: The spec bridge abstains on prototype-member `rowHeight` spellings instead of leaking a
|
|
314
|
+
function into `density`.
|
|
315
|
+
|
|
316
|
+
`bridgeListView`'s `mapDensity` indexed a plain object literal with an unchecked key, so
|
|
317
|
+
the lookup reached `Object.prototype`. The parameter is typed `RowHeight`, but the
|
|
318
|
+
boundary a host's stored view definition actually crosses is `SpecBridge.transformListView`,
|
|
319
|
+
whose parameter is `any` — so `rowHeight: 'toString'` came back as `Object.prototype.toString`,
|
|
320
|
+
a **function**, out of a read whose return type is three strings or nothing. `bridgeListView`
|
|
321
|
+
then writes the key under `if (density)`, and a function is truthy, so the bad value was not
|
|
322
|
+
merely returned: it was stored on a `SchemaNode` whose renderer expects
|
|
323
|
+
`'compact' | 'comfortable' | 'spacious'`. Same for `constructor`, `valueOf`,
|
|
324
|
+
`hasOwnProperty`, `isPrototypeOf`, `propertyIsEnumerable` and `toLocaleString`.
|
|
325
|
+
|
|
326
|
+
The lookup is now guarded with `Object.prototype.hasOwnProperty.call(...)` — the same guard
|
|
327
|
+
`@object-ui/core`'s `rowHeightToDensityMode` grew in objectui#4440, and the repo's existing
|
|
328
|
+
convention at eight other sites. Both `rowHeight` surfaces now abstain identically on every
|
|
329
|
+
off-spec **string** spelling, and objectui#4440's agreement pin covers the prototype-member
|
|
330
|
+
family instead of excluding it (objectui#4442).
|
|
331
|
+
|
|
332
|
+
Runtime-only: no public type moved, and no spec-valid `rowHeight` changes its answer.
|
|
333
|
+
|
|
334
|
+
- d0c3b26: Every plain `<button>` now declares its `type`. HTML defaults an untyped button to
|
|
335
|
+
`type="submit"`, so any of these buttons would submit the form it was composed into
|
|
336
|
+
instead of running its own handler — a real risk for renderers (`drawer`, `tree-view`,
|
|
337
|
+
`navigation-overlay`) whose placement inside a form is a JSON metadata decision. 114
|
|
338
|
+
sites were converted to `type="button"`; no site was a genuine submit button, and the
|
|
339
|
+
DOM is otherwise unchanged.
|
|
340
|
+
|
|
341
|
+
The defect class is now closed mechanically by a new `object-ui/button-has-type` ESLint
|
|
342
|
+
rule (error), so the next untyped button fails CI at write time rather than being found
|
|
343
|
+
by a fourth audit round (objectui#4045, closing the objectui#3344 family).
|
|
344
|
+
|
|
345
|
+
- f148a64: SpecBridge lifts a legacy bare `exportOptions` array to the spec's object form, so a
|
|
346
|
+
spec-authored view's declared export formats reach the grid (objectui#4585).
|
|
347
|
+
|
|
348
|
+
A spec `ListView` may spell `exportOptions` either way, and `@objectstack/spec` lifts the
|
|
349
|
+
legacy bare format array to `{ formats: [...] }` when it parses one (objectstack#8010).
|
|
350
|
+
That lift never ran on the bridge path: the bridge's input is a TypeScript type, not a
|
|
351
|
+
parsed value — there is no `parse`/`safeParse` anywhere under `spec-bridge/` — so a host
|
|
352
|
+
forwarding raw stored metadata handed the array straight through, and the bridge copied it
|
|
353
|
+
onto the `object-grid` node verbatim. ObjectGrid reads the object form and only that, and
|
|
354
|
+
`.formats` on an array is `undefined`, so the renderer's `['csv', 'json']` default won.
|
|
355
|
+
|
|
356
|
+
A view declaring `['csv', 'xlsx']` therefore rendered an export menu offering CSV and
|
|
357
|
+
JSON: the declared xlsx never appeared, an undeclared json did, and nothing said so — the
|
|
358
|
+
export button still showed, because a non-empty array is truthy. The bridge now applies
|
|
359
|
+
the spec's own transform at the assignment site, so both spellings leave it as one shape.
|
|
360
|
+
|
|
361
|
+
Deliberately narrow: this mirrors the spec's lift and nothing else. The object form passes
|
|
362
|
+
through by reference, unread and unrewritten; a view with no `exportOptions` is untouched;
|
|
363
|
+
and a `'pdf'` stored before its retirement is carried rather than filtered, because the
|
|
364
|
+
spec refuses that value at parse with a migration prescription instead of silently
|
|
365
|
+
dropping it — such a format still dies downstream in ObjectGrid's format-agnostic menu
|
|
366
|
+
filter (objectui#4535). The fix is at the producer for the same reason: a tolerant
|
|
367
|
+
`Array.isArray` fallback in the renderer would make a second de-facto contract out of one
|
|
368
|
+
spec key.
|
|
369
|
+
|
|
370
|
+
One behavior follows from reading the lift literally: `exportOptions: []` now lifts to
|
|
371
|
+
`{ formats: [] }` and the export button is hidden, where before the unreadable `[]` was
|
|
372
|
+
merely truthy and produced a menu built entirely from the `['csv', 'json']` default. A
|
|
373
|
+
view that declares zero formats now offers zero.
|
|
374
|
+
|
|
375
|
+
- 47f551b: fix(react): the spec bridge abstains on a non-string `rowHeight` instead of coercing it to a density, matching core
|
|
376
|
+
|
|
377
|
+
`mapDensity` opened with a truthiness guard, so any **truthy non-string** survived it and was
|
|
378
|
+
then coerced into a lookup key — both `Object.prototype.hasOwnProperty.call` and the table index
|
|
379
|
+
run `String(...)`. `rowHeight: ['compact']`, a boxed `String('compact')` or
|
|
380
|
+
`{ toString: () => 'compact' }` therefore each selected a real density, while
|
|
381
|
+
`@object-ui/core`'s `rowHeightToDensityMode` — which opens with `typeof rowHeight !== 'string'` —
|
|
382
|
+
abstained for the same input. Two published surfaces, two answers for one input.
|
|
383
|
+
|
|
384
|
+
The bridge now opens with core's type guard. An off-spec non-string `rowHeight` renders exactly
|
|
385
|
+
like an absent one, and the producer is where it gets fixed (AGENTS.md #0.1). Behaviour for the
|
|
386
|
+
five spec row heights and for off-spec **strings** is unchanged; `''` keeps its answer by a
|
|
387
|
+
different route (a string now, refused one line later because it is not one of the five keys).
|
|
388
|
+
|
|
389
|
+
Note the direction against the previous fix in this function: that leak returned a _function_,
|
|
390
|
+
visibly wrong to everything downstream. This one returned a legitimate-looking `'compact'` that
|
|
391
|
+
nothing downstream could tell apart from an authored density.
|
|
392
|
+
|
|
393
|
+
- Updated dependencies [0e67b53]
|
|
394
|
+
- Updated dependencies [ee66e2e]
|
|
395
|
+
- Updated dependencies [ee26e65]
|
|
396
|
+
- Updated dependencies [5900ac5]
|
|
397
|
+
- Updated dependencies [932cbcd]
|
|
398
|
+
- Updated dependencies [734d186]
|
|
399
|
+
- Updated dependencies [f650253]
|
|
400
|
+
- Updated dependencies [3d9769a]
|
|
401
|
+
- Updated dependencies [3fc2971]
|
|
402
|
+
- Updated dependencies [aca27fa]
|
|
403
|
+
- Updated dependencies [dde7283]
|
|
404
|
+
- Updated dependencies [f7c6430]
|
|
405
|
+
- Updated dependencies [ae10a01]
|
|
406
|
+
- Updated dependencies [92876f0]
|
|
407
|
+
- Updated dependencies [f279deb]
|
|
408
|
+
- Updated dependencies [eb7f586]
|
|
409
|
+
- Updated dependencies [e901131]
|
|
410
|
+
- Updated dependencies [d9d3463]
|
|
411
|
+
- Updated dependencies [2a40f69]
|
|
412
|
+
- Updated dependencies [537a0d1]
|
|
413
|
+
- Updated dependencies [bec3e14]
|
|
414
|
+
- Updated dependencies [613b167]
|
|
415
|
+
- Updated dependencies [1f9b905]
|
|
416
|
+
- Updated dependencies [828549a]
|
|
417
|
+
- Updated dependencies [e1ade8f]
|
|
418
|
+
- Updated dependencies [abb0f81]
|
|
419
|
+
- Updated dependencies [38ab505]
|
|
420
|
+
- Updated dependencies [3e19fe7]
|
|
421
|
+
- Updated dependencies [bb58d1d]
|
|
422
|
+
- Updated dependencies [5cc847c]
|
|
423
|
+
- Updated dependencies [fa21254]
|
|
424
|
+
- Updated dependencies [33c32bf]
|
|
425
|
+
- Updated dependencies [66fb4fa]
|
|
426
|
+
- Updated dependencies [6d641c9]
|
|
427
|
+
- Updated dependencies [479cc7b]
|
|
428
|
+
- Updated dependencies [7e4f0e5]
|
|
429
|
+
- Updated dependencies [45e1949]
|
|
430
|
+
- Updated dependencies [92250d6]
|
|
431
|
+
- Updated dependencies [c1d939f]
|
|
432
|
+
- Updated dependencies [58bebf6]
|
|
433
|
+
- Updated dependencies [405e808]
|
|
434
|
+
- Updated dependencies [49ae9f4]
|
|
435
|
+
- Updated dependencies [c0f9a4b]
|
|
436
|
+
- Updated dependencies [2459a3e]
|
|
437
|
+
- Updated dependencies [2776b11]
|
|
438
|
+
- Updated dependencies [ac853ce]
|
|
439
|
+
- Updated dependencies [fa51109]
|
|
440
|
+
- Updated dependencies [d6aa172]
|
|
441
|
+
- Updated dependencies [fe52a04]
|
|
442
|
+
- Updated dependencies [d46f9b8]
|
|
443
|
+
- Updated dependencies [605b747]
|
|
444
|
+
- Updated dependencies [2fea4d2]
|
|
445
|
+
- Updated dependencies [7f1cb33]
|
|
446
|
+
- Updated dependencies [bb68488]
|
|
447
|
+
- Updated dependencies [2e3b0c0]
|
|
448
|
+
- Updated dependencies [9461dd3]
|
|
449
|
+
- Updated dependencies [78fa331]
|
|
450
|
+
- Updated dependencies [31ab1ac]
|
|
451
|
+
- Updated dependencies [0082db8]
|
|
452
|
+
- Updated dependencies [b42558a]
|
|
453
|
+
- Updated dependencies [d2f6e6b]
|
|
454
|
+
- Updated dependencies [ab04728]
|
|
455
|
+
- Updated dependencies [85a3082]
|
|
456
|
+
- Updated dependencies [06915b0]
|
|
457
|
+
- Updated dependencies [ff84b05]
|
|
458
|
+
- @object-ui/i18n@17.5.0
|
|
459
|
+
- @object-ui/core@17.5.0
|
|
460
|
+
- @object-ui/data-objectstack@17.5.0
|
|
461
|
+
- @object-ui/types@17.5.0
|
|
462
|
+
|
|
463
|
+
## 17.4.0
|
|
464
|
+
|
|
465
|
+
### Minor Changes
|
|
466
|
+
|
|
467
|
+
- 5bfaabd: `PageComponentSchema.dataSource` now reaches every object-bound block, not just
|
|
468
|
+
`list-view` — and `element:record_picker` stops discarding `view`
|
|
469
|
+
(objectstack#6953).
|
|
470
|
+
|
|
471
|
+
objectstack#5576 wired the spec's per-element data binding
|
|
472
|
+
(`dataSource: { object, view?, filter?, sort?, limit? }`) to `list-view` and left
|
|
473
|
+
the same declaration inert on every other page component. Two gaps remained, and
|
|
474
|
+
both were silent:
|
|
475
|
+
|
|
476
|
+
- **`element:record_picker` read four of the five keys and dropped `view`.** So
|
|
477
|
+
`dataSource: { object: 'account', view: 'hot' }` — the spec's own example —
|
|
478
|
+
built a picker over EVERY account instead of the rows the saved view selects.
|
|
479
|
+
Nothing threw and nothing rendered an error; the option list was simply wider
|
|
480
|
+
than what was authored, which also means a user could select a record the page
|
|
481
|
+
said was out of scope.
|
|
482
|
+
- **`object-grid` / `object-form` / `object-kanban` / `object-calendar` /
|
|
483
|
+
`object-chart` / `object-metric` / `record:related_list` read none of it.**
|
|
484
|
+
Each gates its fetch on its own `objectName`, and nothing mapped
|
|
485
|
+
`dataSource.object` onto it, so a page written the way the spec documents
|
|
486
|
+
rendered an empty grid / a field-less form / a board with no cards / an empty
|
|
487
|
+
month / an empty chart / a static metric number — with no request and no
|
|
488
|
+
diagnostic anywhere. Spec-valid metadata rendering nothing is the
|
|
489
|
+
objectstack#4413 shape.
|
|
490
|
+
|
|
491
|
+
Composition follows objectstack#5576's landed semantics unchanged on every block:
|
|
492
|
+
a named saved view supplies the baseline, a key written on the component itself
|
|
493
|
+
overrides it, an explicit binding key overrides both, `filter` AND-combines
|
|
494
|
+
("additional filter criteria" — a binding can narrow a view, never widen it), and
|
|
495
|
+
a `view` name that does not resolve renders a configuration error instead of
|
|
496
|
+
degrading to the object's full scope.
|
|
497
|
+
|
|
498
|
+
- `@object-ui/react` — new `useElementDataSourceSchema(schema, mapping, dataSource?)`
|
|
499
|
+
and `ElementDataSourceGate` apply a resolved binding to the schema keys a given
|
|
500
|
+
block reads, plus `ElementDataSourceErrorPanel` / `ElementDataSourceLoadingPanel`
|
|
501
|
+
for the two non-final states. One precedence table for all blocks rather than
|
|
502
|
+
one copy per block — that copy is how "additional filter criteria" would have
|
|
503
|
+
become two dialects.
|
|
504
|
+
- A mapping names **only** keys its block genuinely reads. A composed value
|
|
505
|
+
written onto a key the block ignores would be accepted and dropped, which is
|
|
506
|
+
the defect being removed, one layer deeper — so a kanban's swimlane `columns`
|
|
507
|
+
never receive a view's field list, and a block with no row cap leaves `limit`
|
|
508
|
+
unmapped. The per-block coverage table, including two residual gaps that are
|
|
509
|
+
named rather than papered over, is in `content/docs/guide/data-source.md`.
|
|
510
|
+
|
|
511
|
+
No behaviour changes for a block that carries no `dataSource`: the binding-free
|
|
512
|
+
path returns the schema by reference, so nothing remounts and nothing refetches.
|
|
513
|
+
|
|
514
|
+
- 48132f7: Track the `@objectstack` family at `17.0.0-rc.5` (objectui#3560).
|
|
515
|
+
|
|
516
|
+
The pin moves from `^17.0.0-rc.2` to `^17.0.0-rc.5` across all 37 declarations in
|
|
517
|
+
30 `package.json` files, and the sibling `@objectstack/*` packages (`client` /
|
|
518
|
+
`formula` / `lint`) move with it — they pin `@objectstack/spec` **exactly**, so
|
|
519
|
+
leaving them behind would keep a second copy of the spec in the tree and have
|
|
520
|
+
`@objectstack/lint` validating against schemas that still accept the keys rc.3–rc.5
|
|
521
|
+
retire. `pnpm-lock.yaml` now resolves one copy of each of the six family packages
|
|
522
|
+
(`spec` / `client` / `core` / `formula` / `lint` / `sdui-parser`), all at rc.5.
|
|
523
|
+
|
|
524
|
+
Bumping the pin and repairing the fallout cannot be split: the pin alone reddens
|
|
525
|
+
CI, and the code alone targets a shape that is not in effect yet.
|
|
526
|
+
|
|
527
|
+
## A live bug this upgrade fixes
|
|
528
|
+
|
|
529
|
+
**`ObjectStackDataSource.delete()` never emitted its mutation event, and resolved
|
|
530
|
+
`undefined` instead of a boolean.** `@objectstack/client`'s `DeleteDataResult`
|
|
531
|
+
declared a key called `deleted` — a key no schema has ever declared and no server
|
|
532
|
+
path has ever returned on `DELETE /data/:object/:id`. So `result.deleted`
|
|
533
|
+
compiled and read `undefined` at runtime: the guard never fired, a successful
|
|
534
|
+
delete notified no subscriber, and every consumer's cache stayed stale.
|
|
535
|
+
objectstack#5638 corrected the interface to the schema's `success`; following the
|
|
536
|
+
rename is what restores both behaviours. Nothing in this repo had to change shape
|
|
537
|
+
for it — the code was already asking the right question of the wrong key.
|
|
538
|
+
|
|
539
|
+
## Breaking, in FROM → TO form
|
|
540
|
+
|
|
541
|
+
- **The five `@objectstack/spec/ui` interaction-config modules are gone** —
|
|
542
|
+
touch / dnd / keyboard / animation / offline, 32 defs and 64 exports
|
|
543
|
+
(objectstack#4988, PR objectstack#5321). None of them had an authoring door: no
|
|
544
|
+
metadata document could ever carry one of these blocks, so a stack that parsed
|
|
545
|
+
before the retirement parses byte-for-byte the same after it. `@object-ui/types`
|
|
546
|
+
drops the 32 `export type` re-exports. The vocabulary each one's only real
|
|
547
|
+
consumer needs is now declared by that consumer, which is the remedy the spec's
|
|
548
|
+
own retirement ledger prescribes ("declare that union locally — it is your
|
|
549
|
+
client's policy, not the platform's"):
|
|
550
|
+
|
|
551
|
+
- `@object-ui/react`'s `useOffline` owns `OfflineStrategy`, `ConflictResolution`,
|
|
552
|
+
`PersistStorageType`, `EvictionPolicyType`, `OfflineConfig`,
|
|
553
|
+
`OfflineCacheConfig`, `OfflineSyncConfig`;
|
|
554
|
+
- `@object-ui/core`'s `DndProtocol` / `KeyboardProtocol` own `DndConfig`,
|
|
555
|
+
`DragItem`, `DropZone`, `DragConstraint`, `DragHandle`, `DropEffect`,
|
|
556
|
+
`KeyboardNavigationConfig`, `KeyboardShortcut`, `FocusManagement`,
|
|
557
|
+
`FocusTrapConfig`;
|
|
558
|
+
- `@object-ui/types`' `mobile` module owns `SpecGestureConfig`,
|
|
559
|
+
`SwipeGestureConfig`, `PinchGestureConfig`, `LongPressGestureConfig`,
|
|
560
|
+
`TouchTargetConfig`, `TouchInteraction` (plus a new `SPEC_GESTURE_TYPES`
|
|
561
|
+
runtime tuple), so `@object-ui/mobile`'s import paths are unchanged.
|
|
562
|
+
|
|
563
|
+
Every shape is moved verbatim — same keys, same members, same optionality — so
|
|
564
|
+
no hook or bridge changes behaviour. Consumers importing these names from
|
|
565
|
+
`@object-ui/types` must import them from the owning package instead. Note the
|
|
566
|
+
spec's _surviving_ `ConnectorConflictResolution` (`/integration`, connector sync)
|
|
567
|
+
and `ConflictResolutionStrategy` (`/api`, route merge policy) are **different
|
|
568
|
+
concepts** — do not re-point at them.
|
|
569
|
+
|
|
570
|
+
- **`@object-ui/types` no longer re-exports `NotificationAction` or `EmbedConfig`**
|
|
571
|
+
(objectstack#5015, PR objectstack#5300). Both were published `ui` vocabulary with
|
|
572
|
+
no authoring door; no notification action was ever parsed from metadata and no
|
|
573
|
+
iframe route ever read an embed config. The presentation enums
|
|
574
|
+
(`NotificationType` / `NotificationSeverity` / `NotificationPosition`) and
|
|
575
|
+
`SharingConfig` **survive** and are untouched — public form sharing still gates
|
|
576
|
+
the anonymous endpoints on `allowAnonymous` + `publicLink`.
|
|
577
|
+
`@object-ui/core`'s `SharingProtocol` keeps `resolveEmbedConfig` /
|
|
578
|
+
`generateEmbedCode` against a locally declared `EmbedConfig`, so its surface is
|
|
579
|
+
unchanged.
|
|
580
|
+
- **`ThemeEngine` stops emitting nine retired CSS variable groups**
|
|
581
|
+
(objectstack#5021 option 2, PR objectstack#5289). `theme.animation`,
|
|
582
|
+
`theme.zIndex` and five typography groups (`fontSize` / `fontWeight` /
|
|
583
|
+
`lineHeight` / `letterSpacing`, plus `fontFamily.heading` / `fontFamily.mono`)
|
|
584
|
+
are tombstones the schema now rejects by name, so `--duration-*`, `--timing-*`,
|
|
585
|
+
`--z-*`, `--font-size-*`, `--font-weight-*`, `--line-height-*`,
|
|
586
|
+
`--letter-spacing-*`, `--font-heading` and `--font-mono` had become structurally
|
|
587
|
+
dead code — no author could produce the input that reached them.
|
|
588
|
+
`generateAnimationVars` and `generateZIndexVars` are removed from
|
|
589
|
+
`@object-ui/core`, and `@object-ui/types` drops `Animation` / `ZIndex` /
|
|
590
|
+
`AnimationSchema` / `ZIndexSchema`. **`theme.customVars` is the declared — and
|
|
591
|
+
since #5021 the only — door**: each entry is emitted verbatim as
|
|
592
|
+
`--<key>: <value>`, so a `--z-modal` or a `--duration-fast` goes there now.
|
|
593
|
+
LIVE emission is untouched byte for byte: `colors`, `borderRadius`, `shadows`,
|
|
594
|
+
`typography.fontFamily.base` (→ `--font-sans`) and `customVars`.
|
|
595
|
+
- **`@object-ui/types`' `HttpMethodSchema` now binds the spec's
|
|
596
|
+
`HttpMethodSubsetSchema`, and `HttpMethod` binds `HttpMethodSubset`**
|
|
597
|
+
(objectstack#5832, PR objectstack#5976 — objectui#3499). The spec renamed its
|
|
598
|
+
5-value UI subset because `schemaNameFromExportKey` strips the `Schema` suffix,
|
|
599
|
+
so the 5-value and 7-value enums both published as `shared/HttpMethod` and the
|
|
600
|
+
later write won — the emitted JSON Schema and reference page described only one
|
|
601
|
+
of them. **The runtime domain is unchanged and this repo's exported names are
|
|
602
|
+
unchanged**; this follows the rename without touching cross-package semantics.
|
|
603
|
+
Deliberately NOT re-pointed at the spec's bare `HttpMethod`: that is the 7-value
|
|
604
|
+
enum, and widening to it would let `method: 'HEAD'` compile and then throw in
|
|
605
|
+
`HttpRequestSchema.parse()`.
|
|
606
|
+
- **`dashboard.widgets[].actionUrl` / `actionType` / `actionIcon` / `aria` are
|
|
607
|
+
refused, not stripped** (objectstack#5010, ADR-0049 enforce-or-remove). A
|
|
608
|
+
dashboard widget has no action button and never had one — every action the
|
|
609
|
+
dashboard dispatches comes from `header.actions[]` — and no renderer ever applied
|
|
610
|
+
the widget `aria`, so it promised accessibility compliance it did not deliver.
|
|
611
|
+
A stale dashboard now gets a named error telling it where the affordance moved,
|
|
612
|
+
instead of silently losing it. Run `os migrate meta --from 16` to rewrite.
|
|
613
|
+
|
|
614
|
+
### Patch Changes
|
|
615
|
+
|
|
616
|
+
- 8aad9fd: Action-face predicates written against the canonical `record.` root now evaluate
|
|
617
|
+
|
|
618
|
+
`action:button`, `action:icon`, `action:menu` and `action:group` gated their
|
|
619
|
+
actions on `useCondition(pred, context)`, which evaluates on
|
|
620
|
+
`new ExpressionEvaluator({ ...scope, ...context })` — and the context each of
|
|
621
|
+
them passed was the row spread flat, or nothing at all. Only the shorthand
|
|
622
|
+
spelling resolved:
|
|
623
|
+
|
|
624
|
+
| predicate | verdict, before |
|
|
625
|
+
| ---------------------------- | -------------------------------- |
|
|
626
|
+
| `status == "pending"` | evaluates (`action:button` only) |
|
|
627
|
+
| `record.status == "pending"` | throws `record is not defined` |
|
|
628
|
+
| `data.status == "pending"` | throws `data is not defined` |
|
|
629
|
+
|
|
630
|
+
`record.` is not a mistaken spelling — it is the canonical one. It is what
|
|
631
|
+
`ExpressionEvaluator`'s CEL path binds (`bag.record` as the record namespace),
|
|
632
|
+
what `evalRowPredicate` binds on the record header, list rows, the row kebab
|
|
633
|
+
and conditional formatting (`record.status` / bare `status` / `data.status`),
|
|
634
|
+
and what the server enforces with. A `visible` that fails CLOSED turns the throw
|
|
635
|
+
into "hidden", so a correctly-authored predicate deleted its own button —
|
|
636
|
+
indistinguishable from the gate having said no. On the fail-soft legs the same
|
|
637
|
+
throw lands the other way: `disabled` greyed a control out for everyone.
|
|
638
|
+
|
|
639
|
+
Live rather than theoretical: every declared action on framework's
|
|
640
|
+
`sys_approval_request` gates on `record.viewer.*`, so the whole server-declared
|
|
641
|
+
approval decision set was invisible wherever the declared-action bar rendered
|
|
642
|
+
until objectui#4077 fixed that bar. These four generic renderers carried the
|
|
643
|
+
same binding.
|
|
644
|
+
|
|
645
|
+
What changed:
|
|
646
|
+
|
|
647
|
+
- all four bind the row the three canonical ways, through one named helper
|
|
648
|
+
(`usePredicateRecordContext`, exported from `@object-ui/react` beside
|
|
649
|
+
`useCondition`), so the action face and the row surfaces answer an author's
|
|
650
|
+
`visible:` the same way;
|
|
651
|
+
- `action:icon` reads the row at all. It evaluated against an empty bag, so not
|
|
652
|
+
even the bare-field shorthand resolved — and its `data` prop was landing in
|
|
653
|
+
the props spread onto the DOM button;
|
|
654
|
+
- `action:menu`'s items and `action:group`'s two leaves receive the row from
|
|
655
|
+
their host, which they previously never got;
|
|
656
|
+
- `action:bar` forwards the row into the overflow menu it builds, not just to
|
|
657
|
+
its inline members. An action's predicate had been answering a different
|
|
658
|
+
question purely because it spilled past `maxVisible` — which on mobile
|
|
659
|
+
defaults to 1, making the verdict a function of the viewport.
|
|
660
|
+
|
|
661
|
+
Deliberately unchanged: the evaluation entry and each site's error policy. A
|
|
662
|
+
predicate that genuinely faults still fails closed on `action:button` /
|
|
663
|
+
`action:menu` `visible` and still fails soft on the other legs, exactly as
|
|
664
|
+
before; `toPredicateInput`, `hasDeclaredVisibilityGate` and the empty-predicate
|
|
665
|
+
rules keep their pinned semantics. Binding the row is a separate question from
|
|
666
|
+
what to do when the predicate faults.
|
|
667
|
+
|
|
668
|
+
A surface with no row of its own binds nothing rather than an empty record, so
|
|
669
|
+
a host that supplies the row through the ambient predicate scope is not blanked
|
|
670
|
+
out; a row passed explicitly still wins over the scope.
|
|
671
|
+
|
|
672
|
+
- e06810e: `PageComponentSchema.dataSource` is now consumed instead of discarded — a
|
|
673
|
+
`list-view` page component can reference a **saved view by name** for the first
|
|
674
|
+
time, and writing the binding no longer breaks the component
|
|
675
|
+
(objectstack#5576).
|
|
676
|
+
|
|
677
|
+
The spec declares a per-element data binding on every page component —
|
|
678
|
+
`dataSource: { object, view?, filter?, sort?, limit? }` — and objectui read none
|
|
679
|
+
of it. `ViewDataProvider.resolveElementDataSource` forwarded
|
|
680
|
+
`filter`/`sort`/`limit` and dropped `view` entirely, and had no caller outside its
|
|
681
|
+
own test; nothing mapped `object` onto the `objectName` a list actually reads. So
|
|
682
|
+
"reference a saved view by name" was published, validated and inert, and every
|
|
683
|
+
page that wanted a saved view's columns/filter/sort had to inline a second copy of
|
|
684
|
+
them — the drift the binding exists to remove.
|
|
685
|
+
|
|
686
|
+
Writing the binding also **broke** the block, for a reason unrelated to `view`:
|
|
687
|
+
`SchemaRenderer` spread the schema's `dataSource` metadata onto the component as a
|
|
688
|
+
React prop, and that is the prop name the host uses to inject the data-source
|
|
689
|
+
ADAPTER. The plain `{ object, view }` object shadowed the adapter, so the first
|
|
690
|
+
`dataSource.find(…)` threw `dataSource.find is not a function` and `list-view`
|
|
691
|
+
rendered "Couldn't load records" — a spec-compliant component failing next to
|
|
692
|
+
identical ones that omitted the binding.
|
|
693
|
+
|
|
694
|
+
- `@object-ui/react` — `SchemaRenderer` no longer spreads `schema.dataSource` as a
|
|
695
|
+
prop (it is metadata, like `visibleWhen`); renderers read it off `schema`. An
|
|
696
|
+
explicit React `dataSource` prop is unaffected. New
|
|
697
|
+
`useElementDataSource(schema, dataSource?)` hook resolves a binding, fetching
|
|
698
|
+
the named saved view from the object definition's `listViews` and the metadata
|
|
699
|
+
overlay's `listViews()`.
|
|
700
|
+
- `@object-ui/core` — new `isElementDataSourceConfig` / `collectSavedViews` /
|
|
701
|
+
`resolveSavedView` / `composeElementDataSource`, and `resolveElementDataSource`
|
|
702
|
+
now honours `view` through an optional `DataFetcher.fetchViews`, reporting an
|
|
703
|
+
unresolvable view as an error instead of silently returning every record.
|
|
704
|
+
`resolveViewId` moved here from `@object-ui/app-shell` (re-exported there) so
|
|
705
|
+
one matcher serves both the object page and a page component.
|
|
706
|
+
- `@object-ui/plugin-list` — `list-view` maps the binding onto the props
|
|
707
|
+
`ListView` reads. `dataSource.*` keys are authoritative, view-supplied values
|
|
708
|
+
are a baseline the component's own keys override, and `filter` AND-combines at
|
|
709
|
+
every level (the spec calls the binding's filter "additional criteria"), so a
|
|
710
|
+
binding can narrow a saved view but never widen it. A `view` name that does not
|
|
711
|
+
resolve renders a configuration error naming the object's actual views and
|
|
712
|
+
issues no query — it never falls back to the object's default view, because that
|
|
713
|
+
turns a typo into a silently wider answer.
|
|
714
|
+
|
|
715
|
+
- ab3ad4f: An empty predicate is no longer a declared gate anywhere (objectui#3850, objectui#3862)
|
|
716
|
+
|
|
717
|
+
"Is a gate DECLARED on this key — is there a condition to reach a verdict on?" was
|
|
718
|
+
answered three times in this repo, with three different scopes, and the widest
|
|
719
|
+
answers sat on `disabled`, where the mistake is not benign:
|
|
720
|
+
|
|
721
|
+
- `hasDeclaredVisibilityGate` (the action face) asked `!= null && !== ''`, so every
|
|
722
|
+
OBJECT counted — including `{ dialect: 'cel', source: '' }`. That envelope is not
|
|
723
|
+
a hand-written curiosity: `@objectstack/spec`'s `ExpressionInputSchema` normalizes
|
|
724
|
+
every authored predicate into one, so "the author left the predicate empty"
|
|
725
|
+
compiles to exactly it. The verdict path normalized the same value back to
|
|
726
|
+
`undefined`, and `evaluateCondition(undefined)` answers `true` — "no condition, so
|
|
727
|
+
visible/enabled". On `visible` that `true` means SHOW, so the two mistakes
|
|
728
|
+
cancelled; on `disabled` it means GREY, so they compounded: a button disabled
|
|
729
|
+
forever that no author asked to disable (objectui#3850, the residue objectui#3842
|
|
730
|
+
left behind).
|
|
731
|
+
- `SchemaRenderer` asked `disabled !== undefined` inline, one notch wider again, so
|
|
732
|
+
`disabled: null` greyed out too — on the GENERIC rendering path, since that block
|
|
733
|
+
runs for every node type, and not as an internal flag either: `_disabled` is
|
|
734
|
+
forwarded to the component as a real `disabled` prop (objectui#3862).
|
|
735
|
+
- `ActionRunner`'s execution gates asked "does this normalize to something
|
|
736
|
+
evaluable?" — the scope that turned out to be right (objectui#3848 / objectui#3872).
|
|
737
|
+
|
|
738
|
+
There is now ONE definition, `hasDeclaredPredicate`, exported from
|
|
739
|
+
`@object-ui/core` (`evaluator/declaredPredicate.ts`, beside the `toPredicateInput`
|
|
740
|
+
normalizer it is derived from): a gate is declared when normalization still leaves a
|
|
741
|
+
condition to evaluate. `''`, a whitespace-only string, an empty-`source` envelope
|
|
742
|
+
and any non-predicate value (`0`, `{}`) are NOT declared; `false` IS (a verdict is
|
|
743
|
+
not a missing gate — objectui#3812). `hasDeclaredVisibilityGate` keeps its name as a
|
|
744
|
+
re-export of it, so the five member-action renderer call sites, `DeclaredActionsBar`
|
|
745
|
+
and `record-quick-actions` are unchanged and inherit the scope;
|
|
746
|
+
`SchemaRenderer`'s `disabled` / `disabledOn` chain and `ActionRunner`'s two gates
|
|
747
|
+
read the same function. No consumer got a local "and also check for empty" test —
|
|
748
|
+
that fourth dialect is what objectui#3842 / objectui#3849 spent two PRs merging away.
|
|
749
|
+
|
|
750
|
+
Measured behaviour change, `action:button` and the generic path, before → after:
|
|
751
|
+
|
|
752
|
+
| value | `visible` | `disabled` | `enabled` | `SchemaRenderer` `disabled` prop |
|
|
753
|
+
| --------------------------------------------------------- | -------------- | ---------- | --------- | -------------------------------- |
|
|
754
|
+
| `''` | shown → shown | on → on | on → on | forwarded → absent |
|
|
755
|
+
| `null` | shown → shown | on → on | on → on | forwarded → absent |
|
|
756
|
+
| `{ dialect: 'cel', source: '' }` | shown → shown | GREY → on | on → on | forwarded → absent |
|
|
757
|
+
| `{ source: '' }` | shown → shown | GREY → on | on → on | forwarded → absent |
|
|
758
|
+
| `' '` (whitespace) | HIDDEN → shown | on → on | GREY → on | forwarded → absent |
|
|
759
|
+
| `0` / `{}` (not predicates) | shown → shown | GREY → on | on → on | forwarded → absent |
|
|
760
|
+
| `true` / `false` / bare CEL / `${…}` / non-empty envelope | unchanged | unchanged | unchanged | unchanged |
|
|
761
|
+
|
|
762
|
+
Every row moves toward "there is no gate here", never away from it, and no value
|
|
763
|
+
that HAS a verdict changes it — the verdict is still read from the raw value, only
|
|
764
|
+
the gate in front of it narrowed. Two rows are behaviour changes rather than the
|
|
765
|
+
equivalence the ruling expected, and are pinned as such: the whitespace string moves
|
|
766
|
+
on `visible` / `enabled` (it used to normalize to `'${ }'`, which evaluates falsy,
|
|
767
|
+
so a predicate that says nothing HID the action from everyone), and non-predicate
|
|
768
|
+
junk stops greying controls out (fail-open, the posture `ActionRunner` already
|
|
769
|
+
committed to).
|
|
770
|
+
|
|
771
|
+
One blank spelling is knowingly still outside the scope: an envelope whose `source`
|
|
772
|
+
is blank but not EMPTY (`{ dialect: 'cel', source: ' ' }`) — the normalizer folds a
|
|
773
|
+
`source` of `''` and does not trim, so the string spelling of a blank predicate is
|
|
774
|
+
trimmed and the envelope spelling is not, and `disabled` still greys out for that one
|
|
775
|
+
value. The ruling enumerated three empty spellings; this is a fourth, measured and
|
|
776
|
+
filed as objectui#3960 rather than widened in here.
|
|
777
|
+
|
|
778
|
+
One chain is deliberately untouched: `SchemaRenderer`'s `visible` / `visibleWhen` /
|
|
779
|
+
`visibleOn` / `visibility` / `hidden` / `hiddenOn` legs keep `!== undefined`, because
|
|
780
|
+
narrowing them would change ALIAS PRECEDENCE, not just emptiness. The `hidden` legs
|
|
781
|
+
are not negated and therefore carry this same defect with the polarity that makes the
|
|
782
|
+
node vanish — measured, out of this ruling's scope, filed as objectui#3955.
|
|
783
|
+
|
|
784
|
+
- 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)
|
|
785
|
+
|
|
786
|
+
objectui#3850 sank "is a predicate gate DECLARED here?" into one definition,
|
|
787
|
+
`@object-ui/core`'s `hasDeclaredPredicate`. Three places were left out of that
|
|
788
|
+
ruling's placement clause, each with the same shape of defect: the evaluator's
|
|
789
|
+
single default for "there is nothing here to evaluate" is `true`, meaning
|
|
790
|
+
_visible/enabled_, and wherever a too-wide "declared" test hands it an empty
|
|
791
|
+
predicate on an INVERTED key, that `true` turns a control off for a value the
|
|
792
|
+
metadata never used to say anything.
|
|
793
|
+
|
|
794
|
+
**`SchemaRenderer`'s `hidden` / `hiddenOn` legs (objectui#3955)** asked
|
|
795
|
+
`!== undefined` and did NOT negate the verdict, so an empty predicate meant HIDE
|
|
796
|
+
and the node disappeared — on the generic rendering path, since that block runs
|
|
797
|
+
for every schema type. Harder to diagnose than the `disabled` twin objectui#3862
|
|
798
|
+
fixed: a greyed-out control is still on screen, while a node that never rendered
|
|
799
|
+
is indistinguishable from metadata that meant to hide it. Both legs now read the
|
|
800
|
+
shared definition.
|
|
801
|
+
|
|
802
|
+
**The "blank" criterion now covers the envelope spelling (objectui#3960).** The
|
|
803
|
+
definition trimmed a whitespace-only STRING and not an envelope's whitespace-only
|
|
804
|
+
`source`, because `toPredicateInput` folds a `source` of `''` and does not trim.
|
|
805
|
+
So `{ dialect: 'cel', source: ' ' }` was a declared gate whose verdict came from
|
|
806
|
+
core's own CEL entry calling that exact value "no predicate" (`if (!source.trim())
|
|
807
|
+
return true`) — `disabled` greyed out forever and `ActionRunner.execute` answered
|
|
808
|
+
`{ success: false, error: 'Action is disabled' }` with the handler never invoked.
|
|
809
|
+
Blankness is now decided once for both spellings, at the definition. The
|
|
810
|
+
NORMALIZER's contract is deliberately unchanged: "what shape does the evaluator
|
|
811
|
+
accept" is not the same question as "is there a condition", and moving the trim
|
|
812
|
+
there would have flipped verdicts for every
|
|
813
|
+
`useCondition(toPredicateInput(…))` call site, including container-level `visible`
|
|
814
|
+
reads that never asked this question at all.
|
|
815
|
+
|
|
816
|
+
**`ActionEngine.getActionsForLocation`'s `visible` filter (objectui#3957)** was the
|
|
817
|
+
last consumer answering the question with a range of its own — three empty
|
|
818
|
+
spellings folded by hand, everything else coerced with `Boolean(raw)`. It now reads
|
|
819
|
+
the shared definition and the coercion branch is gone, so one value no longer gets
|
|
820
|
+
two answers depending on whether an action was surfaced by the engine or rendered
|
|
821
|
+
standalone (the invariant objectui#3314 established). Its fail-CLOSED posture on a
|
|
822
|
+
predicate that THROWS is untouched (`throwOnError: true` + `warnHiddenPredicate`):
|
|
823
|
+
"the predicate faulted" and "there is no predicate" are different facts.
|
|
824
|
+
|
|
825
|
+
Behaviour changes, before → after. Observation-class: each needs an author to write
|
|
826
|
+
an empty/blank predicate or a non-predicate value, and there is no known user path
|
|
827
|
+
today.
|
|
828
|
+
|
|
829
|
+
| value | `ActionEngine` `visible` | `SchemaRenderer` `hidden` | `disabled` (action face + generic path) | `ActionRunner.execute` `disabled` |
|
|
830
|
+
| ----------------------------------------------------------- | ------------------------ | ------------------------- | --------------------------------------- | --------------------------------- |
|
|
831
|
+
| `''` / `null` | shown → shown | HIDDEN → rendered | unchanged | unchanged |
|
|
832
|
+
| `' '` (blank text) | HIDDEN → shown | HIDDEN → rendered | unchanged | unchanged |
|
|
833
|
+
| `0` / `NaN` | HIDDEN → shown | HIDDEN → rendered | unchanged | unchanged |
|
|
834
|
+
| `{}` / `[]` | shown → shown | HIDDEN → rendered | unchanged | unchanged |
|
|
835
|
+
| `{ dialect: 'cel', source: '' }` | shown → shown | HIDDEN → rendered | unchanged | unchanged |
|
|
836
|
+
| `{ dialect: 'cel', source: ' ' }` | shown → shown | HIDDEN → rendered | GREY → on | refused → runs |
|
|
837
|
+
| `{ source: ' ' }` (no dialect) | HIDDEN → shown | HIDDEN → rendered | GREY → on | refused → runs |
|
|
838
|
+
| `true` / `false` / bare CEL / `${…}` / a non-blank envelope | unchanged | unchanged | unchanged | unchanged |
|
|
839
|
+
|
|
840
|
+
Every row moves toward "there is no gate here", never away from it, and no value
|
|
841
|
+
that HAS a verdict changes it — a declared `false` is still a verdict, not a
|
|
842
|
+
missing gate (objectui#3812), and blankness is `trim()`, not "short": `{ dialect:
|
|
843
|
+
'cel', source: ' x ' }` is a predicate. One alias precedence changes with the
|
|
844
|
+
`hidden` legs and is pinned rather than claimed as an equivalence: an undeclared
|
|
845
|
+
`hidden` no longer short-circuits the chain, so a declared `hiddenOn` is finally
|
|
846
|
+
consulted.
|
|
847
|
+
|
|
848
|
+
`SchemaRenderer`'s four `visible*` legs keep `!== undefined` deliberately, as
|
|
849
|
+
objectui#3850 ruled: their `true` is negated, so an empty predicate already lands
|
|
850
|
+
on "shown", and narrowing them would change alias precedence rather than fix
|
|
851
|
+
anything.
|
|
852
|
+
|
|
853
|
+
- Updated dependencies [6719877]
|
|
854
|
+
- Updated dependencies [56ff091]
|
|
855
|
+
- Updated dependencies [7864f03]
|
|
856
|
+
- Updated dependencies [d229dfa]
|
|
857
|
+
- Updated dependencies [4bc6c23]
|
|
858
|
+
- Updated dependencies [c3b01a7]
|
|
859
|
+
- Updated dependencies [f5f8744]
|
|
860
|
+
- Updated dependencies [3765678]
|
|
861
|
+
- Updated dependencies [d83f6b3]
|
|
862
|
+
- Updated dependencies [5f08c05]
|
|
863
|
+
- Updated dependencies [69becd2]
|
|
864
|
+
- Updated dependencies [5e52495]
|
|
865
|
+
- Updated dependencies [b750823]
|
|
866
|
+
- Updated dependencies [e06810e]
|
|
867
|
+
- Updated dependencies [ab3ad4f]
|
|
868
|
+
- Updated dependencies [c2fd122]
|
|
869
|
+
- Updated dependencies [ac2139c]
|
|
870
|
+
- Updated dependencies [b14ab3a]
|
|
871
|
+
- Updated dependencies [8c60819]
|
|
872
|
+
- Updated dependencies [41d6022]
|
|
873
|
+
- Updated dependencies [e64a52e]
|
|
874
|
+
- Updated dependencies [844d17f]
|
|
875
|
+
- Updated dependencies [48132f7]
|
|
876
|
+
- Updated dependencies [4dcd52a]
|
|
877
|
+
- Updated dependencies [42ae5c6]
|
|
878
|
+
- Updated dependencies [1d723e3]
|
|
879
|
+
- Updated dependencies [0109f54]
|
|
880
|
+
- Updated dependencies [7e5bb5d]
|
|
881
|
+
- Updated dependencies [fbc23e0]
|
|
882
|
+
- Updated dependencies [6d762da]
|
|
883
|
+
- Updated dependencies [e6fdbdc]
|
|
884
|
+
- Updated dependencies [f9faa7d]
|
|
885
|
+
- Updated dependencies [6bb454a]
|
|
886
|
+
- Updated dependencies [523be48]
|
|
887
|
+
- Updated dependencies [7e2b7e9]
|
|
888
|
+
- Updated dependencies [33526fd]
|
|
889
|
+
- Updated dependencies [32413ec]
|
|
890
|
+
- Updated dependencies [c1e1e6b]
|
|
891
|
+
- @object-ui/core@17.4.0
|
|
892
|
+
- @object-ui/i18n@17.4.0
|
|
893
|
+
- @object-ui/types@17.4.0
|
|
894
|
+
- @object-ui/data-objectstack@17.4.0
|
|
895
|
+
|
|
896
|
+
## 17.3.0
|
|
897
|
+
|
|
898
|
+
### Patch Changes
|
|
899
|
+
|
|
900
|
+
- d915c47: Relation fields (`lookup` / `master_detail` / `user` / `tree`) are now usable in action and conditional-formatting predicates: they bind as the stored foreign key on every surface, and the fields a predicate reads are included in the query projection (#3501).
|
|
901
|
+
|
|
902
|
+
Before this, one predicate over one relation field had four different fates, decided by things its author does not control. `$expand` **replaces** the id in place with the whole related record, and a view expands exactly the relations it shows as COLUMNS — so `record.owner == "U1"` was **true** where the column was absent, **false** where it was displayed, and a **fault** where the field was neither displayed nor projected (a list's `$select` was built from its columns alone, and CEL treats an absent key as a fault, not as null). A fault is fail-CLOSED on the row kebab and the selection bar and fail-OPEN on the lenient paths, so the same authoring mistake hid the button from everyone on one surface and showed it to everyone on the next, with nothing on screen to point at either. The server, meanwhile, only ever sees the id — so client and server could not agree, which is the one thing ADR-0036 / ADR-0058 exist to guarantee.
|
|
903
|
+
|
|
904
|
+
Two changes close it. `toPredicateRecord` (new, `@object-ui/core`) collapses expanded relation values back to their ids when a record is bound for evaluation — driven by the object's own field types, not by sniffing for an `id` key, so a `json` field that happens to carry one is untouched. It is threaded through `evalRowPredicate` / `resolveConditionalFormatting` (via a new `fields` option), `useRowPredicate`, `partitionBulkRows`, and both `page:header` evaluators, with the object schema supplied by `ObjectGrid` / `ListView` / `ObjectKanban` / the record context. Kanban card formatting is threaded the same way, so a rule cannot match on the grid view of a list and silently never match on its board. Display is unaffected — a detail-page title still renders the related record's name, and the schema-only `kanban-ui` entry point (which has no object schema to offer) keeps using the payload verbatim. `collectPredicateFieldRefs` / `listViewPredicates` (new) harvest the `record.x` / `data.x` references out of a view's conditional formatting, row-action defs, bulk-action defs, promoted object actions and `userActions` overrides, and add them to `$select` — intersected with the object's declared fields plus the platform columns every object carries (`isProjectableField`), because an unknown key is not ignored by every backend. No `$expand` is added: a predicate wants the foreign key, which is what an unexpanded relation already is.
|
|
905
|
+
|
|
906
|
+
- 175bd79: `toPredicateInput` is now re-exported from `@object-ui/core` instead of being reimplemented in `@object-ui/react`. Behaviour is byte-for-byte identical — the renderer-side copy in `packages/react/src/hooks/useExpression.ts` had item-for-item the same semantics as the canonical `packages/core/src/evaluator/predicateInput.ts` (booleans short-circuit, bare strings and non-`cel` dialects become `${…}`, a `{ dialect: 'cel', source }` envelope survives so `useCondition` routes it to the canonical `@objectstack/formula` engine, empty/absent predicates become `undefined`), and every existing import path (`import { toPredicateInput } from '@object-ui/react'`) keeps working with an unchanged signature. What changes is that there is now ONE implementation rather than two held in step by a parity table: #3314 is the record of what two normalizations do when left alone — they drift, and the same `visible:` predicate reaches different verdicts depending on whether the action was surfaced by `ActionEngine.getActionsForLocation` or rendered standalone. The 14-shape normalization parity table degenerated once both of its columns called the same function, so it is replaced by a single identity assertion (`react`'s export IS `core`'s function object); the engine-path-vs-renderer-path verdict parity suite is untouched and still proves the two call paths agree (#3367).
|
|
907
|
+
- Updated dependencies [18cd432]
|
|
908
|
+
- Updated dependencies [d915c47]
|
|
909
|
+
- Updated dependencies [b71fc92]
|
|
910
|
+
- Updated dependencies [65516ba]
|
|
911
|
+
- Updated dependencies [94c5b7c]
|
|
912
|
+
- Updated dependencies [ca0fa8f]
|
|
913
|
+
- Updated dependencies [3889ffb]
|
|
914
|
+
- Updated dependencies [5781fb1]
|
|
915
|
+
- Updated dependencies [7e2406a]
|
|
916
|
+
- Updated dependencies [9e9e9a9]
|
|
917
|
+
- Updated dependencies [4eeb932]
|
|
918
|
+
- Updated dependencies [5c856ec]
|
|
919
|
+
- Updated dependencies [23018cc]
|
|
920
|
+
- Updated dependencies [68b6a28]
|
|
921
|
+
- Updated dependencies [0554e88]
|
|
922
|
+
- Updated dependencies [d915c47]
|
|
923
|
+
- Updated dependencies [f44d872]
|
|
924
|
+
- Updated dependencies [28b2e65]
|
|
925
|
+
- Updated dependencies [509104a]
|
|
926
|
+
- Updated dependencies [825bbe3]
|
|
927
|
+
- Updated dependencies [6195841]
|
|
928
|
+
- Updated dependencies [5dd0127]
|
|
929
|
+
- Updated dependencies [a415684]
|
|
930
|
+
- Updated dependencies [a4cff5b]
|
|
931
|
+
- Updated dependencies [5af2852]
|
|
932
|
+
- Updated dependencies [f833d3a]
|
|
933
|
+
- Updated dependencies [a6ec93d]
|
|
934
|
+
- Updated dependencies [2a9513d]
|
|
935
|
+
- Updated dependencies [d22ae31]
|
|
936
|
+
- Updated dependencies [c7ed4c3]
|
|
937
|
+
- Updated dependencies [2409e1d]
|
|
938
|
+
- Updated dependencies [789fe3e]
|
|
939
|
+
- @object-ui/core@17.3.0
|
|
940
|
+
- @object-ui/types@17.3.0
|
|
941
|
+
- @object-ui/i18n@17.3.0
|
|
942
|
+
- @object-ui/data-objectstack@17.3.0
|
|
943
|
+
|
|
3
944
|
## 17.2.0
|
|
4
945
|
|
|
5
946
|
### Minor Changes
|
|
@@ -1507,6 +2448,7 @@
|
|
|
1507
2448
|
result and the "warn-once" tracking are now stored separately: a
|
|
1508
2449
|
`WeakMap` caches the validation outcome (so the visual flag is stable),
|
|
1509
2450
|
while a `WeakSet` continues to dedupe `console.warn` output.
|
|
2451
|
+
|
|
1510
2452
|
- @object-ui/types@6.2.2
|
|
1511
2453
|
- @object-ui/core@6.2.2
|
|
1512
2454
|
- @object-ui/i18n@6.2.2
|
|
@@ -1656,6 +2598,7 @@
|
|
|
1656
2598
|
### Minor Changes
|
|
1657
2599
|
|
|
1658
2600
|
- b2d1704: feat(cmdk): record search across objects in the Command Palette
|
|
2601
|
+
|
|
1659
2602
|
- New `useRecordSearch` hook in `@object-ui/react` debounces a query, fans out
|
|
1660
2603
|
to `dataSource.find(name, { $search, $top })` across candidate objects, and
|
|
1661
2604
|
aggregates hits. Race-safe via a monotonic runId; per-object 404s are
|
|
@@ -1670,6 +2613,7 @@
|
|
|
1670
2613
|
|
|
1671
2614
|
Three related fixes that all addressed the same UX: a user follows a URL
|
|
1672
2615
|
shaped `/{object}/{recordId}` and sees a completely blank content area.
|
|
2616
|
+
|
|
1673
2617
|
1. **`useNavigationOverlay` produced the broken URL itself.** When
|
|
1674
2618
|
middle-click / Cmd-click opened a gallery card in a new tab and no
|
|
1675
2619
|
`onNavigate` was provided, the hook built `/{object}/{id}` — a URL
|
|
@@ -1689,6 +2633,7 @@
|
|
|
1689
2633
|
|
|
1690
2634
|
- aa063db: `useRecordSearch` now orders hits by relevance instead of object-fanout
|
|
1691
2635
|
order. Tiers (higher wins):
|
|
2636
|
+
|
|
1692
2637
|
- 110: exact recordId paste
|
|
1693
2638
|
- 100: display exactly equals the query
|
|
1694
2639
|
- 80: display starts with the query
|
|
@@ -1748,17 +2693,20 @@
|
|
|
1748
2693
|
- bd8447d: Three platform-wide detail polish items.
|
|
1749
2694
|
|
|
1750
2695
|
**Tighter page rhythm**
|
|
2696
|
+
|
|
1751
2697
|
- Outer `PageRenderer` padding `p-4 md:p-6 lg:p-8` → `p-3 md:p-4 lg:p-6`
|
|
1752
2698
|
and outer body wrap `space-y-8` → `space-y-6` so list / detail / home
|
|
1753
2699
|
pages share the same edge rhythm. Cuts ~16px of edge slack on lg.
|
|
1754
2700
|
|
|
1755
2701
|
**Highlights KPI treatment**
|
|
2702
|
+
|
|
1756
2703
|
- `HeaderHighlight` now renders numeric / currency / percent / decimal
|
|
1757
2704
|
values as KPI numbers (`text-xl md:text-2xl font-semibold tabular-nums`)
|
|
1758
2705
|
instead of the uniform `text-sm font-semibold`, so amount / probability
|
|
1759
2706
|
/ count fields read as headline stats — Salesforce-style key facts.
|
|
1760
2707
|
|
|
1761
2708
|
**Discussion footer upgrade**
|
|
2709
|
+
|
|
1762
2710
|
- `RecordActivityTimeline` now uses `RichTextCommentInput` (bold / italic /
|
|
1763
2711
|
list / code, `@`-mention autocomplete, preview toggle, Send) instead of
|
|
1764
2712
|
a bare `<textarea>`.
|
|
@@ -1769,6 +2717,7 @@
|
|
|
1769
2717
|
and sidebar positions.
|
|
1770
2718
|
|
|
1771
2719
|
- d51a577: feat(platform): Discussion attachments + @mention directory + Reference Rail aside
|
|
2720
|
+
|
|
1772
2721
|
- **Discussion attachments** — `RichTextCommentInput` now accepts an `extraSlot`
|
|
1773
2722
|
and a `canSubmitEmpty` flag so hosts can mount the existing
|
|
1774
2723
|
`CommentAttachment` composer beneath the editor without forking the toolbar.
|
|
@@ -1858,6 +2807,7 @@
|
|
|
1858
2807
|
page so the two renderers share state.
|
|
1859
2808
|
|
|
1860
2809
|
- 74962b0: feat(detail): record:discussion schema component + flush accordion variant
|
|
2810
|
+
|
|
1861
2811
|
- New `record:discussion` schema type lets authors place the record
|
|
1862
2812
|
chatter feed anywhere in a custom Page schema. Wired through a
|
|
1863
2813
|
shared `DiscussionContext` provider on the `assignedPage` branch
|
|
@@ -1878,6 +2828,7 @@
|
|
|
1878
2828
|
customize the header or one tab.
|
|
1879
2829
|
|
|
1880
2830
|
**Slot menu (v1):**
|
|
2831
|
+
|
|
1881
2832
|
- `header` — replaces `page:header`
|
|
1882
2833
|
- `actions` — replaces the `record:quick_actions` action bar
|
|
1883
2834
|
- `highlights` — replaces the chips + chevron path strip
|
|
@@ -1905,6 +2856,7 @@
|
|
|
1905
2856
|
```
|
|
1906
2857
|
|
|
1907
2858
|
**API changes:**
|
|
2859
|
+
|
|
1908
2860
|
- `PageSchema` (in `@object-ui/types`): adds `kind?: 'full' | 'slotted'`
|
|
1909
2861
|
(default `'full'`) and `slots?: PageSlotMap`.
|
|
1910
2862
|
- `usePageAssignment` (in `@object-ui/react`): result now exposes a
|
|
@@ -2084,6 +3036,7 @@
|
|
|
2084
3036
|
detail view (`TypeError: titleFormat.replace is not a function`) and printed
|
|
2085
3037
|
`Failed to evaluate expression: ${[object Object]}` for every action visibility
|
|
2086
3038
|
predicate.
|
|
3039
|
+
|
|
2087
3040
|
- `@object-ui/core`: `ExpressionEvaluator.evaluate` / `evaluateCondition` now
|
|
2088
3041
|
unwrap Expression envelopes transparently.
|
|
2089
3042
|
- `@object-ui/react`: new `toPredicateInput()` helper to safely normalize
|
|
@@ -2335,6 +3288,7 @@
|
|
|
2335
3288
|
- New plugin-object and ObjectQL SDK updates
|
|
2336
3289
|
|
|
2337
3290
|
**Added:**
|
|
3291
|
+
|
|
2338
3292
|
- New Plugin: @object-ui/plugin-object - ObjectQL plugin for automatic table and form generation
|
|
2339
3293
|
- ObjectTable: Auto-generates tables from ObjectQL object schemas
|
|
2340
3294
|
- ObjectForm: Auto-generates forms from ObjectQL object schemas with create/edit/view modes
|
|
@@ -2343,6 +3297,7 @@
|
|
|
2343
3297
|
- ObjectQL Integration: Enhanced ObjectQLDataSource with getObjectSchema() method using MetadataApiClient
|
|
2344
3298
|
|
|
2345
3299
|
**Changed:**
|
|
3300
|
+
|
|
2346
3301
|
- Updated @objectql/sdk from ^1.8.3 to ^1.9.1
|
|
2347
3302
|
- Updated @objectql/types from ^1.8.3 to ^1.9.1
|
|
2348
3303
|
|
|
@@ -2356,6 +3311,7 @@
|
|
|
2356
3311
|
- Patch release: Add automated changeset workflow and CI/CD improvements
|
|
2357
3312
|
|
|
2358
3313
|
This release includes infrastructure improvements:
|
|
3314
|
+
|
|
2359
3315
|
- Added changeset-based version management
|
|
2360
3316
|
- Enhanced CI/CD workflows with GitHub Actions
|
|
2361
3317
|
- Improved documentation for contributing and releasing
|