@object-ui/core 17.1.0 → 17.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +526 -0
- package/dist/actions/ActionEngine.d.ts +11 -1
- package/dist/actions/ActionEngine.js +14 -3
- package/dist/actions/ActionRunner.d.ts +117 -10
- package/dist/actions/ActionRunner.js +25 -3
- package/dist/actions/actionKeys.d.ts +28 -11
- package/dist/actions/actionKeys.js +50 -10
- package/dist/query/query-ast.d.ts +6 -6
- package/dist/query/query-ast.js +2 -2
- package/dist/registry/PluginSystem.d.ts +15 -5
- package/dist/registry/PluginSystem.js +1 -1
- package/dist/registry/WidgetRegistry.d.ts +7 -7
- package/dist/registry/WidgetRegistry.js +1 -1
- package/dist/styling/scoped-styles.d.ts +14 -8
- package/dist/utils/chart-series.d.ts +14 -2
- package/dist/utils/dashboard-filters.d.ts +9 -0
- package/dist/utils/dashboard-filters.js +99 -4
- package/dist/utils/dataset-format.d.ts +13 -1
- package/dist/utils/dataset-format.js +10 -2
- package/dist/utils/filter-tokens.d.ts +13 -6
- package/dist/utils/filter-tokens.js +13 -6
- package/dist/utils/freeze-schema.d.ts +14 -6
- package/dist/utils/freeze-schema.js +14 -6
- package/dist/utils/managedBy.d.ts +78 -50
- package/dist/utils/managedBy.js +75 -52
- package/dist/utils/normalize-list-view.d.ts +12 -2
- package/dist/validation/index.d.ts +1 -0
- package/dist/validation/index.js +1 -0
- package/dist/validation/required-presence.d.ts +31 -0
- package/dist/validation/required-presence.js +36 -0
- package/dist/validation/schema-validator.d.ts +24 -8
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,531 @@
|
|
|
1
1
|
# @object-ui/core
|
|
2
2
|
|
|
3
|
+
## 17.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bca45cc: Declare the 18 spec-owned action keys `ActionDef` had been absorbing silently.
|
|
8
|
+
|
|
9
|
+
`ActionDef` ends with `[key: string]: any`, so it accepted any key of any type —
|
|
10
|
+
a typo (`targt`) and a retired spec key (`execute`) both type-checked, then the
|
|
11
|
+
runner silently bound no handler (the #2169 "Mark Done does nothing" shape).
|
|
12
|
+
Step 1 (objectstack#4075) made that audible with a dev-mode warning. This is
|
|
13
|
+
step 2: the keys the warning identified as legitimate are now real fields.
|
|
14
|
+
|
|
15
|
+
- **18 keys promoted to explicit optional fields** — `ai`, `aria`, `bodyExtra`,
|
|
16
|
+
`bodyShape`, `bulkEnabled`, `component`, `icon`, `locations`, `mode`,
|
|
17
|
+
`objectName`, `order`, `recordIdField`, `recordIdParam`, `requiredPermissions`,
|
|
18
|
+
`requiresFeature`, `shortcut`, `variant`, `visible`. Every type is **derived**
|
|
19
|
+
from `@objectstack/spec`'s `ActionInput` (`SpecActionInput['locations']`, …),
|
|
20
|
+
never hand-copied: a hand-written duplicate of a spec shape is a second
|
|
21
|
+
contract that drifts, which is the failure this issue is about. Wrong-typed
|
|
22
|
+
values are now compile errors — `order: 'first'`, `variant: 'chartreuse'`,
|
|
23
|
+
`locations: ['nope']` — where before they were absorbed silently.
|
|
24
|
+
- **Derived from `z.input`, not `z.infer`.** `ActionSchema` is a `ZodPipe` whose
|
|
25
|
+
transform narrows `visible` from `string | { dialect, source }` to the
|
|
26
|
+
envelope alone. This runner consumes authored/stored rows, which are
|
|
27
|
+
rehydrated unparsed, so it sees the input shape; deriving from the inferred
|
|
28
|
+
`Action` would have rejected the raw-string predicate `ActionEngine`
|
|
29
|
+
explicitly supports.
|
|
30
|
+
- **Three `as any` casts deleted** in `ActionEngine` — `visible` and
|
|
31
|
+
`requiredPermissions` at the location filter, `locations` at registration.
|
|
32
|
+
They existed only because the fields were undeclared.
|
|
33
|
+
- **Four objectui-dialect keys marked `@deprecated`** with the spec spelling to
|
|
34
|
+
use instead — `actionType` (→ `type`), `api` and `endpoint` (→ `target`;
|
|
35
|
+
`executeAPI` already resolves `api || endpoint || target`), and `navigate`
|
|
36
|
+
(→ flat `target` / `openIn`). Only these four: the remaining dialect keys are
|
|
37
|
+
runner mechanics (chaining, toasts, post-execution reload/close) with no spec
|
|
38
|
+
counterpart, and pointing them at a spelling that does not exist would be
|
|
39
|
+
worse than leaving them declared.
|
|
40
|
+
|
|
41
|
+
**Breaking edge, deliberate.** `shortcut` and `bulkEnabled` were retired by
|
|
42
|
+
`@objectstack/spec` 17 as `retiredKey()` tombstones (`z.never()`), so authoring
|
|
43
|
+
either is already a hard parse rejection. Deriving their types rather than
|
|
44
|
+
hand-writing them turns that runtime rejection into a **compile error**: code
|
|
45
|
+
that assigned `shortcut: 'ctrl+k'` to an `ActionDef` compiled before and does
|
|
46
|
+
not now. Such metadata was already refused by the platform — this only moves the
|
|
47
|
+
failure to where it can be fixed. A host may still pass either explicitly via
|
|
48
|
+
`ActionEngine.registerAction(action, { shortcut, bulkEnabled })`; only authored
|
|
49
|
+
metadata stopped carrying them. `bulkEnabled`'s replacement is the list view's
|
|
50
|
+
`bulkActions` / `bulkActionDefs`; `shortcut` has none.
|
|
51
|
+
|
|
52
|
+
The index signature **stays** — removing it is step 3, and the inverted pin
|
|
53
|
+
asserting it is still present remains the issue's own completion check.
|
|
54
|
+
|
|
55
|
+
- 4bf612c: Aggregate single-call mode for bulk actions: `execution: 'aggregate'` (objectui#3139).
|
|
56
|
+
|
|
57
|
+
A `bulkActionDefs` entry with `operation: 'custom'` used to have exactly one
|
|
58
|
+
dispatch shape: one action-runner call per selected record (`_rowRecord`
|
|
59
|
+
attached). "Select N rows → ONE call that receives every selected id" — the
|
|
60
|
+
zip-of-QR-codes / merged-PDF / batch-print shape — could not be expressed, so
|
|
61
|
+
downstream projects fell back to per-row `window.open` storms or gave up.
|
|
62
|
+
|
|
63
|
+
`BulkActionDef` now carries `execution?: 'perRecord' | 'aggregate'` (default
|
|
64
|
+
`'perRecord'`, existing views untouched). An aggregate def dispatches its
|
|
65
|
+
action exactly once for the whole selection with `params._selectedIds:
|
|
66
|
+
string[]` injected and the full records published as
|
|
67
|
+
`context.selectedRecords`. The authored form usually just names a declared
|
|
68
|
+
object action — `{ name, operation: 'custom', execution: 'aggregate' }` —
|
|
69
|
+
and `resolveBulkActions` attaches the declaration. Results are
|
|
70
|
+
all-or-nothing: a failure is attributed to every id with the real error and
|
|
71
|
+
per-row Retry is hidden (re-running the action is the retry; a total failure
|
|
72
|
+
keeps the selection). `batchSize` does not apply; `maxRecords` still gates.
|
|
73
|
+
|
|
74
|
+
The executor rides the existing `executeBulkBatch` bulk-first decision tree —
|
|
75
|
+
the aggregate call is its `bulkCall`, and the per-row "fallback" only
|
|
76
|
+
re-throws the captured error for attribution, never fans out N dispatches
|
|
77
|
+
against an endpoint written for one `_selectedIds` call.
|
|
78
|
+
|
|
79
|
+
Also: url/api target interpolation now exposes `${ctx.selection.ids}` (comma
|
|
80
|
+
-joined) and `${ctx.selection.count}` from the grid's checkbox selection, so
|
|
81
|
+
a plain `list_toolbar` action can carry the selection without bulk plumbing;
|
|
82
|
+
the console's server-action handler recognizes `_selectedIds` and skips the
|
|
83
|
+
single-record multi-select guard for aggregate dispatches.
|
|
84
|
+
|
|
85
|
+
- 335041c: Stop declaring 13 `@object-ui/core` symbols under names `@objectstack/spec` owns
|
|
86
|
+
(objectui#3158, objectstack#4115 batch 4).
|
|
87
|
+
|
|
88
|
+
**Breaking for importers of `@object-ui/core`** — seven exported names changed,
|
|
89
|
+
because the spec exports the same name for a _different_ thing:
|
|
90
|
+
|
|
91
|
+
| was | now | what the spec's same-named export actually is |
|
|
92
|
+
| :----------------------- | :-------------------------------- | :--------------------------------------------------------------------------- |
|
|
93
|
+
| `ChartSeries` | `ChartSeriesBinding` | the authored dataset-binding descriptor (a measure `name`, no `data`) |
|
|
94
|
+
| `ActionHandler` | `ActionRunnerHandler` | the SERVER-side objectql handler, `(ctx) => unknown` |
|
|
95
|
+
| `PluginDefinition` | `RegistryPluginDefinition` | the platform PACKAGE manifest (`id`/`slug`/`staticPath`/install hooks) |
|
|
96
|
+
| `ValidationError` | `SchemaNodeValidationError` | plugin-manifest validation, keyed by `field`, no severity |
|
|
97
|
+
| `ValidationResult` | `SchemaNodeValidationResult` | ditto, with both arrays optional |
|
|
98
|
+
| `defineView` | `defineSystemView` | the VIEW-DOCUMENT factory: parses a `ViewSchema`, returns a validated `View` |
|
|
99
|
+
| `resolveCrudAffordances` | `resolveEffectiveCrudAffordances` | the object-level affordance matrix, with no notion of server API operations |
|
|
100
|
+
|
|
101
|
+
The other six keep their names and are now **imported from the spec** instead of
|
|
102
|
+
re-declared: `StyleMap`, `ResponsiveStyles` (ADR-0065), `RowHeight`,
|
|
103
|
+
`CONTEXT_TOKENS`, `CrudAffordances`, `RowCrudPredicates`.
|
|
104
|
+
|
|
105
|
+
**The copies were live misdescriptions, not just duplicates.** Three said so in
|
|
106
|
+
their own comments:
|
|
107
|
+
|
|
108
|
+
- `CONTEXT_TOKENS` carried a note that the duplication was "temporary until the
|
|
109
|
+
next coordinated release… because the installed `@objectstack/spec` predates
|
|
110
|
+
that export". The installed spec (17.0.0-rc.0) exports it, and the copy was
|
|
111
|
+
byte-identical — so it passed every value comparison and every behavioural
|
|
112
|
+
test for the whole interval in which its stated reason was false.
|
|
113
|
+
- `RowHeight` advertised itself as "the spec's `RowHeightSchema` vocabulary"
|
|
114
|
+
while being a hand-written union. It happened to be correct; nothing would
|
|
115
|
+
have caught the day it stopped being.
|
|
116
|
+
- `managedBy.ts` described itself as a "UI-side mirror of the framework's
|
|
117
|
+
`resolveCrudAffordances()`" and carried its own `DEFAULTS` table — a
|
|
118
|
+
line-for-line copy of the spec's `CRUD_AFFORDANCE_DEFAULTS`, plus a copy of
|
|
119
|
+
its override parser.
|
|
120
|
+
|
|
121
|
+
`resolveEffectiveCrudAffordances` now **delegates** the bucket/`userActions` half
|
|
122
|
+
to the spec's `resolveCrudAffordances()`, so the bucket table has exactly one
|
|
123
|
+
definition on the platform. What stays objectui's is the part the spec has no
|
|
124
|
+
notion of: intersecting that matrix with the server-resolved effective API
|
|
125
|
+
operation set (#3391), so the UI never offers a button the server would 405 —
|
|
126
|
+
and the name now says that instead of claiming to be the spec's function.
|
|
127
|
+
|
|
128
|
+
Deriving `RowCrudPredicates` also **tightens** it: the local copy typed
|
|
129
|
+
`visibleWhen`/`disabledWhen` as `unknown`, where the spec types them as
|
|
130
|
+
`Expression | ExpressionInput`. That was imprecision, not a deliberate dialect.
|
|
131
|
+
|
|
132
|
+
- b414983: fix(dashboard): a date globalFilter's preset-name default becomes a range, not an equality
|
|
133
|
+
|
|
134
|
+
Setup → System Overview rendered EVERY KPI tile as 0 while its period selector
|
|
135
|
+
read "All time" (objectstack#4475). Every request was `200 OK`, the widgets
|
|
136
|
+
rendered normally, and nothing in the UI signalled a failure — zeros read as
|
|
137
|
+
"nothing has happened yet" rather than as an error, which is why this survived
|
|
138
|
+
to an RC.
|
|
139
|
+
|
|
140
|
+
Both symptoms are one missing normalization. `resolveDashboardFilterDefs` lifts
|
|
141
|
+
the built-in `dateRange` declaration's preset NAME to `{ preset }`, but passed a
|
|
142
|
+
`globalFilters` entry's `defaultValue` through raw. `@objectstack/spec`'s
|
|
143
|
+
`GlobalFilterSchema.defaultValue` is `string | number | boolean`, so a bare
|
|
144
|
+
preset name is the ONLY spelling an author can write — and nothing ever mapped
|
|
145
|
+
it. System Overview declares
|
|
146
|
+
`{ field: 'created_at', type: 'date', defaultValue: 'last_7_days' }`, so:
|
|
147
|
+
|
|
148
|
+
- `buildFilterCondition` fell through to its "a bare string date means equality
|
|
149
|
+
on that day" branch and the widget sent
|
|
150
|
+
`runtimeFilter: { created_at: 'last_7_days' }`. The backend compiled
|
|
151
|
+
`SELECT COUNT(*) AS "user_count" FROM "sys_user" WHERE created_at = $1`
|
|
152
|
+
— verified against a live server, byte-for-byte the SQL in the issue. The
|
|
153
|
+
actual `sys_user` count is 4; that equality matches no row.
|
|
154
|
+
- `DateRangeFilter` derives its selected item from `value.preset` / `.from` /
|
|
155
|
+
`.to`, all `undefined` on a bare string, so the control fell through to its
|
|
156
|
+
ALL sentinel and displayed "All time" while sending that equality. The tiles
|
|
157
|
+
therefore looked deliberately unfiltered and merely empty.
|
|
158
|
+
|
|
159
|
+
`normalizeDateDefault` now applies the same lift the sibling `dateRange`
|
|
160
|
+
declaration already receives, for `date`/`dateRange` filters whose default names
|
|
161
|
+
a preset this module actually knows. This is not consumer-side leniency: it is
|
|
162
|
+
one normalization function completing the same conversion for the sibling
|
|
163
|
+
declaration, and the spec admits no other spelling for an author to fix at the
|
|
164
|
+
producer. A genuine ISO date string still means equality on that day (the
|
|
165
|
+
documented behaviour), and numbers, booleans and unrecognised strings are left
|
|
166
|
+
exactly as declared.
|
|
167
|
+
|
|
168
|
+
No backend change is needed: given a real range the dataset path already lowers
|
|
169
|
+
it correctly (`WHERE (created_at >= $1 AND created_at < $2)` → 4). The
|
|
170
|
+
framework's dashboard metadata needs none either — it is spec-compliant as
|
|
171
|
+
written, and editing it would only hide the defect.
|
|
172
|
+
|
|
173
|
+
Levelled `minor` rather than `patch` because the change is visible in rendered
|
|
174
|
+
dashboards rather than internal: any dashboard declaring a date-typed
|
|
175
|
+
`globalFilters` default now emits a different query shape, its numbers change
|
|
176
|
+
(from 0 to real values), and its filter control's displayed label changes with
|
|
177
|
+
them. Anything asserting on the previously-emitted condition will see it move.
|
|
178
|
+
|
|
179
|
+
Known residual, filed separately rather than widened into here: a `date` filter
|
|
180
|
+
whose value is neither a known preset nor a parseable ISO date still degrades
|
|
181
|
+
silently to an equality that matches nothing, producing the same
|
|
182
|
+
healthy-looking zero. Preset names are covered by this change; a misspelled
|
|
183
|
+
custom value is not.
|
|
184
|
+
|
|
185
|
+
- 256f8cc: fix(dashboard): an unrecognised date filter value is skipped and named, not compared
|
|
186
|
+
|
|
187
|
+
The residual the preset-name fix (objectui#3150 / objectstack#4475) left behind,
|
|
188
|
+
and the more deceptive half of it: a `date`/`dateRange` filter value that is
|
|
189
|
+
neither a known preset name nor a parseable date used to fall through to the
|
|
190
|
+
"a bare string date means equality on that day" branch. A misspelled default —
|
|
191
|
+
`defaultValue: 'last_7_dayz'` — therefore reached the widget query as
|
|
192
|
+
`runtimeFilter: { created_at: 'last_7_dayz' }`, which the backend faithfully
|
|
193
|
+
compiled to `WHERE created_at = $1`. `200 OK`, widget renders, count is 0 —
|
|
194
|
+
indistinguishable from "this range genuinely has no data". No 4xx, no console
|
|
195
|
+
warning, no UI signal. objectstack#4475 took a full RC cycle to catch for
|
|
196
|
+
exactly this reason: **0 looks like a legitimate answer**.
|
|
197
|
+
|
|
198
|
+
`buildFilterCondition` now holds a date value to three spellings, and only
|
|
199
|
+
three:
|
|
200
|
+
|
|
201
|
+
1. a known preset name → range bounds (unchanged, objectui#3150);
|
|
202
|
+
2. an ISO date (`2026-01-15`, `2026-01-15T08:30:00Z`) or a date-macro token
|
|
203
|
+
(`{today}`, `{7_days_ago}`) → equality on that day (the documented
|
|
204
|
+
behaviour, unchanged);
|
|
205
|
+
3. **anything else → the filter is skipped and `console.warn` names the filter,
|
|
206
|
+
the offending value, and the accepted spellings.**
|
|
207
|
+
|
|
208
|
+
The `{ preset: '<unknown>' }` object form gets the same voice. It already
|
|
209
|
+
dropped the filter — silently — because the preset lookup missed and no
|
|
210
|
+
`from`/`to` remained; that drop is now announced. When explicit bounds ride
|
|
211
|
+
along with an unknown preset the bounds are still honoured, and the warning says
|
|
212
|
+
which of the two won.
|
|
213
|
+
|
|
214
|
+
Rule 3 is deliberately the same strictness `buildWidgetScopedFilter` already
|
|
215
|
+
applies to a _default binding on a field the object does not have_ — skip and
|
|
216
|
+
warn, with the same rationale spelled out there: never emit a query the backend
|
|
217
|
+
can only empty-match. Field _names_ had that guard; field _values_ did not.
|
|
218
|
+
|
|
219
|
+
The macro-token check asks `resolveDateMacros` itself whether it recognises the
|
|
220
|
+
string, rather than restating its token grammar in a second place. One
|
|
221
|
+
vocabulary, no dialect to drift — and a token that resolver does not know
|
|
222
|
+
(`{last_7_dayz}`) is precisely the typo this guard exists to catch.
|
|
223
|
+
|
|
224
|
+
Levelled `minor`, matching objectui#3150, because the emitted query shape
|
|
225
|
+
changes: a dashboard carrying a misspelled date value stops sending a
|
|
226
|
+
never-matching equality and instead sends no constraint for that filter (its
|
|
227
|
+
numbers go from 0 to unfiltered) while the console says why. Anything asserting
|
|
228
|
+
on the previously-emitted equality will see it disappear.
|
|
229
|
+
|
|
230
|
+
Note the direction of the relaxation is chosen, not incidental: skipping widens
|
|
231
|
+
the result set, so the number visibly changes and the warning explains it —
|
|
232
|
+
whereas the old behaviour narrowed it to zero, which is the one outcome an
|
|
233
|
+
author cannot tell from a correct answer. Author-time rejection (validating
|
|
234
|
+
`GlobalFilterSchema.defaultValue` at publish, in `@objectstack/spec`) is the
|
|
235
|
+
stricter complement and belongs on the platform side; it is filed separately.
|
|
236
|
+
|
|
237
|
+
- d3584c6: Bring the whole `@objectstack` family to `17.0.0-rc.1`, so the dependency graph resolves a
|
|
238
|
+
single copy of `@objectstack/spec`.
|
|
239
|
+
|
|
240
|
+
#3178 bumped **only** `@objectstack/spec` to `17.0.0-rc.1`. The rest of the family —
|
|
241
|
+
`client`, `core`, `formula`, `lint` (and `sdui-parser`, reached through `lint`) — stayed on
|
|
242
|
+
`17.0.0-rc.0`, and each of them depends on spec at an **exact** version rather than a
|
|
243
|
+
caret:
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
@objectstack/client@17.0.0-rc.0 -> spec "17.0.0-rc.0"
|
|
247
|
+
@objectstack/core@17.0.0-rc.0 -> spec "17.0.0-rc.0"
|
|
248
|
+
@objectstack/formula@17.0.0-rc.0 -> spec "17.0.0-rc.0"
|
|
249
|
+
@objectstack/lint@17.0.0-rc.0 -> spec "17.0.0-rc.0"
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
So `main` carried **two** spec copies: objectui's own code read `17.0.0-rc.1` while every
|
|
253
|
+
`@objectstack/*` package read `17.0.0-rc.0` from its own nested `node_modules`. That breaks
|
|
254
|
+
the single-contract invariant this repo's guards are built on, and it breaks them
|
|
255
|
+
_silently_ — the affected checks depend on identity, not on version strings:
|
|
256
|
+
|
|
257
|
+
- `spec-subschema-parity.test.ts` distinguishes a genuine re-export from a fork by
|
|
258
|
+
**reference identity** of the zod schema object. Two spec copies make every schema a
|
|
259
|
+
distinct object, so a real re-export starts reading as a fork (or a fork slips through,
|
|
260
|
+
depending on which copy each side resolved).
|
|
261
|
+
- `scripts/check-spec-symbol-derivation.mjs` and `spec-symbol-parity.test.ts` use
|
|
262
|
+
`createRequire` to resolve spec's `.d.ts` and run it through the TS checker. With two
|
|
263
|
+
copies installed, _which_ declaration file the checker sees is a function of resolution
|
|
264
|
+
order rather than of intent.
|
|
265
|
+
|
|
266
|
+
The declared ranges were already `^17.0.0-rc.0`, which technically admits rc.1 — the pin
|
|
267
|
+
lived in the lockfile. Raising the remaining ranges to `^17.0.0-rc.1` makes the floor
|
|
268
|
+
explicit and forbids a future install from silently sliding back onto a family member that
|
|
269
|
+
drags rc.0 along with it. The rc.1 family members pin spec at `17.0.0-rc.1` exactly, so the
|
|
270
|
+
graph now converges on one copy by construction, not by luck.
|
|
271
|
+
|
|
272
|
+
No product behaviour changes here. `check:spec-symbols` reconciliation was already
|
|
273
|
+
completed by #3178 and stays green under the unified graph; this changeset is `minor`
|
|
274
|
+
per the repo's fixed-group version policy.
|
|
275
|
+
|
|
276
|
+
- 444457c: feat!: follow the framework's `managedBy: 'system'` → `'system-data'` retirement (objectstack#3355)
|
|
277
|
+
|
|
278
|
+
**FROM → TO: `managedBy: 'system'` → `managedBy: 'system-data'`.** The framework
|
|
279
|
+
retired the residual `system` bucket in protocol 17; this is the UI half of that
|
|
280
|
+
change, landing with it so the closed `ManagedByBucket` union stays a mirror
|
|
281
|
+
rather than a fork.
|
|
282
|
+
|
|
283
|
+
ADR-0103 split the overloaded `system` bucket additively in v16 — the
|
|
284
|
+
engine-owned objects moved to the explicit `engine-owned`, the admin/user-writable
|
|
285
|
+
ones stayed on `system` — which left that value named after the half that had
|
|
286
|
+
already moved out. `system-data` names what it actually holds: the SCHEMA is the
|
|
287
|
+
platform's, the DATA is the admin's or the user's.
|
|
288
|
+
|
|
289
|
+
**The derivation this deletes is the point.** Because v16's `system` doubled as
|
|
290
|
+
both the engine-owned default and the writable set, three UI surfaces had to
|
|
291
|
+
RECOVER the distinction from `userActions` at render time:
|
|
292
|
+
|
|
293
|
+
- `isSystemWritable()` probed `userActions` for any opted-in write. It is now
|
|
294
|
+
`managedBy === 'system-data'` — the bucket answers directly.
|
|
295
|
+
- `ManagedByBadge` derived a synthetic `'system-writable'` variant key. The
|
|
296
|
+
variant map is now 1:1 with the bucket union, so a new bucket is a compile
|
|
297
|
+
error to miss instead of a silent fallthrough. The `systemWritable` /
|
|
298
|
+
`system` i18n keys are **unchanged**, so no locale bundle moves.
|
|
299
|
+
- `resolveManagedByEmptyState()` asked the resolved `create` affordance whether a
|
|
300
|
+
`system` list should read "entries appear automatically" or show the New
|
|
301
|
+
button. `system-data` now falls through to the generic empty state by
|
|
302
|
+
definition; `engine-owned` keeps the automatic-entries copy.
|
|
303
|
+
|
|
304
|
+
**Breaking (UI API):** `ManagedByBadge`'s `userActions` prop and the exported
|
|
305
|
+
`ManagedByUserActions` interface are **removed**. The bucket alone selects the
|
|
306
|
+
variant now, so the prop had become metadata nothing read — the exact defect the
|
|
307
|
+
framework change exists to remove; shipping it as an accepted-but-ignored prop
|
|
308
|
+
would have reproduced it one layer up. Drop the prop from call sites; no other
|
|
309
|
+
change is needed.
|
|
310
|
+
|
|
311
|
+
`MANAGED_BY_BUCKETS` and `ManagedByBucket` no longer contain `'system'`.
|
|
312
|
+
|
|
313
|
+
- 850033c: Stop offering the retired `action.shortcut` / `action.bulkEnabled` keys.
|
|
314
|
+
|
|
315
|
+
`@objectstack/spec` 17 retired both as `retiredKey()` tombstones: authoring
|
|
316
|
+
either one is a hard PARSE REJECTION, so a draft carrying it cannot be saved
|
|
317
|
+
at all. The designer still offered controls for both — a "Bulk — apply to
|
|
318
|
+
multiple selected rows" checkbox and a "Shortcut" text field — which meant the
|
|
319
|
+
Studio action inspector let an author build a draft the platform would then
|
|
320
|
+
refuse, with the rejection arriving later and nowhere near the checkbox.
|
|
321
|
+
|
|
322
|
+
- **Action inspector**: both controls removed. The keys stay hidden from the
|
|
323
|
+
fallback form (the server's live schema still advertises them, so dropping
|
|
324
|
+
them from the hidden list would put the inputs straight back) — now under a
|
|
325
|
+
`RETIRED_FIELDS` list that says why, so nobody "restores the missing
|
|
326
|
+
control". `bulkEnabled`'s replacement is the list view's `bulkActions` /
|
|
327
|
+
`bulkActionDefs`; `shortcut` has none.
|
|
328
|
+
- **Action preview**: the `shortcut` and `bulk` pills are gone — they could
|
|
329
|
+
only ever render for metadata the platform now refuses.
|
|
330
|
+
- **`ActionEngine.registerActions`**: no longer harvests the two retired keys
|
|
331
|
+
from authored metadata, which made two dead registration options look
|
|
332
|
+
load-bearing. Both are still accepted on the single-action
|
|
333
|
+
`registerAction(action, options)` overload, where a HOST passes them
|
|
334
|
+
explicitly.
|
|
335
|
+
|
|
336
|
+
- 009e25d: Report / chart / query symbols stop wearing `@objectstack/spec`'s names
|
|
337
|
+
(objectui#3155, objectstack#4115).
|
|
338
|
+
|
|
339
|
+
**Breaking for TypeScript imports** — six exported names change. Each was a
|
|
340
|
+
different concept than the spec export it collided with, so an author reading
|
|
341
|
+
the objectui declaration as "the spec's" was reading a false claim:
|
|
342
|
+
|
|
343
|
+
| was | now | why they were never the same thing |
|
|
344
|
+
| :------------------ | :----------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
345
|
+
| `ChartSeries` | `ChartDataSeries` | ours is a display name plus literal `data: number[]`; the spec's is a dataset-bound series descriptor (`type`/`stack`/`yAxis`/`variant`) with no data at all |
|
|
346
|
+
| `ChartSeriesSchema` | `ChartDataSeriesSchema` | zod twin of the above |
|
|
347
|
+
| `QueryAST` | `SqlQueryAST` | ours is a compiled SQL syntax tree (`select`/`from`/`join`/`group_by`); the spec's is the ObjectQL request descriptor (`object`/`fields`/`where`/`expand`) |
|
|
348
|
+
| `QuerySchema` | `DriverQueryConfig` | ours is the high-level config `QueryASTBuilder` compiles; the spec exports that name as a zod schema value |
|
|
349
|
+
| `DriverInterface` | `SqlDriverInterface` | ours is objectui's SQL-oriented client abstraction (`query(sql, params)`); the spec's is the platform runtime driver contract |
|
|
350
|
+
| `DatasourceSchema` | `DatasourceRegistration` | ours is the in-memory record `DatasourceManager` holds — its `driver` is a live instance; the spec's is the authored metadata document, where `driver` is a name |
|
|
351
|
+
|
|
352
|
+
Three more are now DERIVED from the spec instead of hand-restated, which fixes
|
|
353
|
+
live silent-stripping defects, since a `z.object()` drops unknown keys:
|
|
354
|
+
|
|
355
|
+
- **`DashboardWidgetSchema`** declared 10 of the spec's 22 keys, so
|
|
356
|
+
`objectui validate` deleted the other 12 without a word — `chartConfig`,
|
|
357
|
+
`colorVariant`, `filter`, `responsive`, `aria`,
|
|
358
|
+
`actionUrl`/`actionType`/`actionIcon`, `compareTo`, `suppressWarnings` and the
|
|
359
|
+
`requiresObject` / `requiresService` capability gates the dashboard renderer
|
|
360
|
+
honours at runtime. The TS interface had declared most of them all along, so a
|
|
361
|
+
widget could type-check and still lose half its configuration on validation.
|
|
362
|
+
Pinned divergences kept: `id` stays optional, `type` stays widened for the
|
|
363
|
+
objectui-only `list` / `custom` families, and the legacy `component` envelope
|
|
364
|
+
stays.
|
|
365
|
+
- **`GlobalFilterSchema`** took `scope` as a free-form string (any typo
|
|
366
|
+
validated); it now uses the spec's `widget | dashboard` vocabulary. The three
|
|
367
|
+
objectui widenings that back a real runtime normalizer are kept and pinned:
|
|
368
|
+
the bare-string `options` shorthand, the normalized `{ preset }` date default,
|
|
369
|
+
and an optional `optionsFrom.labelField`.
|
|
370
|
+
- **`AppContextSelectorSchema`** was a full restatement; spec keys and their
|
|
371
|
+
defaults now flow in by reference, with `label` widened for objectui's i18n
|
|
372
|
+
label envelope — which `AppContextSelectors` already renders.
|
|
373
|
+
|
|
374
|
+
`ListViewSchema`'s zod node now names the spec in its own initializer rather
|
|
375
|
+
than one hop away through a local const, so its long-standing derivation is
|
|
376
|
+
visible where it is declared.
|
|
377
|
+
|
|
378
|
+
Drift guard: `packages/types/src/__tests__/report-chart-query-spec-parity.test.ts`.
|
|
379
|
+
|
|
380
|
+
- 726b89c: `@object-ui/types` stops declaring sixteen symbols under names `@objectstack/spec` owns (objectui#3156, objectstack#4115).
|
|
381
|
+
|
|
382
|
+
Seven are now **derived** from the spec, nine are **renamed** to the local
|
|
383
|
+
dialect they always were. Both halves remove the same hazard: a local
|
|
384
|
+
declaration under a spec export's name reads as the spec's own definition to
|
|
385
|
+
the next reader, so a copy that is merely _correct today_ is a planted premise
|
|
386
|
+
tomorrow.
|
|
387
|
+
|
|
388
|
+
**Derived** — the spec now supplies the keys, by reference:
|
|
389
|
+
|
|
390
|
+
| symbol | derivation |
|
|
391
|
+
| :----------------------- | :-------------------------------------------------------------------------------- |
|
|
392
|
+
| `ActionParam` | `z.input<typeof ActionParamSchema>`, `type` widened to the local legacy spellings |
|
|
393
|
+
| `CreateExportJobRequest` | `Omit<CreateExportJobInput, 'object'>` (`object` is the method argument) |
|
|
394
|
+
| `CreateExportJobResult` | re-export from `@objectstack/spec/contracts` |
|
|
395
|
+
| `ImportRowResult` | re-export from `@objectstack/spec/api` |
|
|
396
|
+
| `NavigationArea` | spec keys, with `navigation` / `visible` pinned locally |
|
|
397
|
+
| `NavigationAreaSchema` | `specFieldsExcept(NavigationAreaSchema.shape, …)` |
|
|
398
|
+
| `Theme` | re-export of the spec's `ThemeInput` (the authoring shape) |
|
|
399
|
+
| `ExportJobFormat` | re-export of the spec's `ExportFormat` |
|
|
400
|
+
|
|
401
|
+
Four of these close real gaps rather than tidy names. `ActionParam` never
|
|
402
|
+
declared `reference` — the key `resolveActionParams()` actually reads for an
|
|
403
|
+
inline lookup target — nor `defaultFromRow`, which the metadata designer's own
|
|
404
|
+
inspector writes; it also narrowed `visible` to a bare string although the
|
|
405
|
+
resolver has always accepted the `{ dialect, source }` envelope too.
|
|
406
|
+
`CreateExportJobResult.createdAt` and `ImportRowResult.action` were optional
|
|
407
|
+
here and required by the server, leaving every consumer a branch that could
|
|
408
|
+
never run. And `NavigationArea`'s `id` now carries the spec's own length rule
|
|
409
|
+
instead of accepting any string.
|
|
410
|
+
|
|
411
|
+
**Renamed** — same word, different concept:
|
|
412
|
+
|
|
413
|
+
| was | now | why |
|
|
414
|
+
| :----------------- | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
|
|
415
|
+
| `FileMetadata` | `UploadedFileMetadata` | field-VALUE payload (`url`, `original_name`), not the storage file record |
|
|
416
|
+
| `GestureType` | `TouchGestureType` | direction-fused (`swipe-left`), not the spec's type+direction pair |
|
|
417
|
+
| `GestureConfig` | `TouchGestureConfig` | gesture→`action` binding, not per-gesture tuning |
|
|
418
|
+
| `OfflineConfig` | `PWAOfflineConfig` | service-worker route caching, not the offline data/sync model |
|
|
419
|
+
| `PageRegion` | `PageNodeRegion` | region of the renderer page NODE, holding `SchemaNode`s |
|
|
420
|
+
| `PageRegionSchema` | `PageNodeRegionSchema` | zod twin of the above |
|
|
421
|
+
| `ResponsiveConfig` | `MobileResponsiveConfig` | mobile box config, not the spec's SDUI grid contract |
|
|
422
|
+
| `WidgetManifest` | `RuntimeWidgetManifest` | SDUI component manifest, not the field-widget plugin manifest |
|
|
423
|
+
| `WidgetSource` | `RuntimeWidgetSource` | `module`/`inline`/`registry` loader union — and its `inline` carries a resolved component where the spec's carries source code |
|
|
424
|
+
|
|
425
|
+
**Migration**: the old names are gone, not deprecated — an alias would preserve
|
|
426
|
+
exactly the ambiguity being removed. Import the new name; nothing about the
|
|
427
|
+
shapes changed. `@object-ui/types` already re-exports the spec's own
|
|
428
|
+
`SpecResponsiveConfig`, and `@object-ui/react`'s `useOffline` config remains the
|
|
429
|
+
spec-shaped `OfflineConfig`, so both concepts stay reachable under
|
|
430
|
+
distinguishable names.
|
|
431
|
+
|
|
432
|
+
Each rename carries a bidirectional tripwire
|
|
433
|
+
(`packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts`): it fails if
|
|
434
|
+
the spec ever claims the new name, and also if the spec retires the old one —
|
|
435
|
+
at which point the natural name can be taken back rather than the workaround
|
|
436
|
+
outliving its reason.
|
|
437
|
+
|
|
438
|
+
### Patch Changes
|
|
439
|
+
|
|
440
|
+
- d9668a7: Honor the server's declared percent scale, so a ratio of exactly 1 renders as 100.0% (#3136)
|
|
441
|
+
|
|
442
|
+
A dataset measure declared `format: '0.0%'` rendered every ratio below 1
|
|
443
|
+
correctly and got the single most consequential one wrong: a rate of exactly
|
|
444
|
+
`1` printed as **`1.0%`**. On an SLA / pass-rate dashboard that turns
|
|
445
|
+
"everything met the SLA" into "1% met the SLA", on both surfaces the issue
|
|
446
|
+
names — the KPI card and the dataset-bound table (they share `formatMeasure`).
|
|
447
|
+
|
|
448
|
+
The cause was never a bad multiplier; it was a missing fact. `formatMeasure`
|
|
449
|
+
scaled by magnitude — `percentDisplayValue` multiplies by 100 only strictly
|
|
450
|
+
inside `(-1, 1)` — because the column arrived with a `%` format string and
|
|
451
|
+
nothing saying what scale its numbers were on. That guess is undecidable at
|
|
452
|
+
exactly 1, which is both a full-compliance ratio ("100%") and one percentage
|
|
453
|
+
point ("1%"), and it resolved to the reading almost nobody means.
|
|
454
|
+
|
|
455
|
+
The server now answers the question instead (framework: `percentScaleOf` +
|
|
456
|
+
`AnalyticsResult.fields[].percentScale`, the sibling of the ADR-0053 currency
|
|
457
|
+
chain): a `derived: { op: 'ratio' }` measure is a `fraction` by definition, and
|
|
458
|
+
a measure over a `percent` field inherits that field's scale. `formatMeasure`
|
|
459
|
+
takes the declared scale as a fourth argument and, when present, scales by it —
|
|
460
|
+
`fraction` ×100, `whole` verbatim — instead of inspecting the value. Every
|
|
461
|
+
dataset-bound call site passes the column's `percentScale`: the dashboard
|
|
462
|
+
metric/table/pivot cells, the report renderer's cells, totals and KPI, and the
|
|
463
|
+
dataset preview.
|
|
464
|
+
|
|
465
|
+
`percentDisplayValue` is untouched and still the fallback for a column that
|
|
466
|
+
arrives without the annotation (an older server, or a non-dataset percent cell
|
|
467
|
+
in a list view), so nothing that renders correctly today changes.
|
|
468
|
+
|
|
469
|
+
- a8ad6c0: A required boolean must be savable in its UNCHECKED state — `false` and `0` are values.
|
|
470
|
+
|
|
471
|
+
Reported against an AI-built task tracker whose 任务 object has a required
|
|
472
|
+
`是否完成` boolean: the create form showed the switch OFF, answered "是否完成不能
|
|
473
|
+
为空", and saved instantly once the switch was turned ON. The app could only ever
|
|
474
|
+
create ALREADY-DONE tasks — the one state the control shows by default was the
|
|
475
|
+
one value it refused to save (cloud#972).
|
|
476
|
+
|
|
477
|
+
Two defects stacked, and either alone is enough to break it:
|
|
478
|
+
|
|
479
|
+
**The `required` verdict read truthiness, not presence.** `@objectstack/spec`
|
|
480
|
+
FieldSchema.required (ADR-0113) is "an insert must provide a NON-NULL value",
|
|
481
|
+
and objectql's record validator implements exactly that. react-hook-form's
|
|
482
|
+
built-in rule instead fails whenever `isBoolean(value) && !value` — its
|
|
483
|
+
accept-the-terms checkbox heritage — silently redefining every required boolean
|
|
484
|
+
as "must be TRUE", including a select whose chosen option value is `false`. It
|
|
485
|
+
also disagreed the other way, letting a whitespace-only string through for the
|
|
486
|
+
server to reject with a 400. The form renderer no longer hands RHF its own
|
|
487
|
+
`required`: the check is now a `validate` entry keyed `required` (so the error
|
|
488
|
+
still surfaces as `type: 'required'`, which the conditional-required cleanup
|
|
489
|
+
keys on) backed by a new shared `isMissingForRequired` in `@object-ui/core`, a
|
|
490
|
+
deliberate mirror of objectql `record-validator.isMissing` — `undefined`,
|
|
491
|
+
`null`, blank-after-trim string, empty array. Deleting the inherited rule also
|
|
492
|
+
stops a `required` that rode in on `validation` from outliving a `requiredWhen`
|
|
493
|
+
that resolved to FALSE.
|
|
494
|
+
|
|
495
|
+
**A boolean field held `undefined` while displaying "off".** A two-state control
|
|
496
|
+
has no third state, but a field with no entry in `defaultValues` rendered an OFF
|
|
497
|
+
switch backed by nothing: the create payload omitted the column (it lands null,
|
|
498
|
+
which reads as unchecked but isn't) and the presence check above would still
|
|
499
|
+
refuse it. The form renderer now folds `false` into `defaultValues` for every
|
|
500
|
+
boolean-widget field the caller left unset — in `defaultValues` itself, not
|
|
501
|
+
per-Controller, because that object is also the dirty-check baseline and what
|
|
502
|
+
the defaults-reset window replays. Every surface gets it, including the
|
|
503
|
+
modal/drawer create dialogs that start from a bare `{}`. An authored default
|
|
504
|
+
(or a loaded record, `null` included) still wins.
|
|
505
|
+
|
|
506
|
+
`WizardForm`'s cross-step gate had its own copy of the empty-value predicate; it
|
|
507
|
+
now imports the shared one so it cannot drift from the per-field verdict. And
|
|
508
|
+
the field-demo renderer read `schema.defaultValue || schema.value`, throwing
|
|
509
|
+
away an authored default of `false` / `0` / `''` — same falsy-as-empty class,
|
|
510
|
+
now `??`.
|
|
511
|
+
|
|
512
|
+
Verified end to end on a local stack against the exact metadata shape
|
|
513
|
+
`apply_blueprint` materializes (`{ type: 'boolean', required: true }`, no
|
|
514
|
+
default): a 是否完成 = 否 task with 工时 = 0 now creates and persists as
|
|
515
|
+
`{ hours: 0, is_done: false }`, turning the switch on still stores `true`, and a
|
|
516
|
+
blank required text is still refused.
|
|
517
|
+
|
|
518
|
+
- Updated dependencies [4ae0ac4]
|
|
519
|
+
- Updated dependencies [696e3c1]
|
|
520
|
+
- Updated dependencies [4bf612c]
|
|
521
|
+
- Updated dependencies [cb82705]
|
|
522
|
+
- Updated dependencies [f572849]
|
|
523
|
+
- Updated dependencies [444457c]
|
|
524
|
+
- Updated dependencies [022e4c3]
|
|
525
|
+
- Updated dependencies [009e25d]
|
|
526
|
+
- Updated dependencies [726b89c]
|
|
527
|
+
- @object-ui/types@17.2.0
|
|
528
|
+
|
|
3
529
|
## 17.1.0
|
|
4
530
|
|
|
5
531
|
### Minor Changes
|
|
@@ -78,7 +78,17 @@ export declare class ActionEngine {
|
|
|
78
78
|
bulkEnabled?: boolean;
|
|
79
79
|
priority?: number;
|
|
80
80
|
}): void;
|
|
81
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Register multiple actions from an ActionSchema array.
|
|
83
|
+
*
|
|
84
|
+
* Only `locations` is harvested from the metadata. `shortcut` and
|
|
85
|
+
* `bulkEnabled` used to be read here too, but spec 17 retired both as
|
|
86
|
+
* `retiredKey()` tombstones — a declaration carrying either one no longer
|
|
87
|
+
* parses, so harvesting them read a key that cannot exist and made two dead
|
|
88
|
+
* options look load-bearing. Both remain accepted on the single-action
|
|
89
|
+
* `registerAction(action, options)` overload, where a HOST passes them
|
|
90
|
+
* explicitly; they are simply no longer sourced from authored metadata.
|
|
91
|
+
*/
|
|
82
92
|
registerActions(actions: ActionDef[]): void;
|
|
83
93
|
/** Unregister an action by name */
|
|
84
94
|
unregisterAction(name: string): void;
|
|
@@ -110,13 +110,24 @@ export class ActionEngine {
|
|
|
110
110
|
this.normalizedShortcutMap.set(normalizeShortcut(options.shortcut), name);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Register multiple actions from an ActionSchema array.
|
|
115
|
+
*
|
|
116
|
+
* Only `locations` is harvested from the metadata. `shortcut` and
|
|
117
|
+
* `bulkEnabled` used to be read here too, but spec 17 retired both as
|
|
118
|
+
* `retiredKey()` tombstones — a declaration carrying either one no longer
|
|
119
|
+
* parses, so harvesting them read a key that cannot exist and made two dead
|
|
120
|
+
* options look load-bearing. Both remain accepted on the single-action
|
|
121
|
+
* `registerAction(action, options)` overload, where a HOST passes them
|
|
122
|
+
* explicitly; they are simply no longer sourced from authored metadata.
|
|
123
|
+
*/
|
|
114
124
|
registerActions(actions) {
|
|
115
125
|
for (const action of actions) {
|
|
116
126
|
this.registerAction(action, {
|
|
127
|
+
// No cast: `locations` is a declared `ActionDef` field as of
|
|
128
|
+
// objectstack#4075 step 2. The `as any` it replaces existed only
|
|
129
|
+
// because the key reached this reader through the index signature.
|
|
117
130
|
locations: action.locations,
|
|
118
|
-
shortcut: action.shortcut,
|
|
119
|
-
bulkEnabled: action.bulkEnabled,
|
|
120
131
|
});
|
|
121
132
|
}
|
|
122
133
|
}
|