@object-ui/plugin-form 16.1.0 → 17.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +831 -1
  2. package/README.md +106 -0
  3. package/dist/DrawerForm.d.ts +1 -0
  4. package/dist/DrawerForm.d.ts.map +1 -0
  5. package/dist/EmbeddableForm.d.ts +1 -0
  6. package/dist/EmbeddableForm.d.ts.map +1 -0
  7. package/dist/FormAnalytics.d.ts +1 -0
  8. package/dist/FormAnalytics.d.ts.map +1 -0
  9. package/dist/FormSection.d.ts +7 -6
  10. package/dist/FormSection.d.ts.map +1 -0
  11. package/dist/LineItemsPanel.d.ts +1 -0
  12. package/dist/LineItemsPanel.d.ts.map +1 -0
  13. package/dist/MasterDetailForm.d.ts +9 -1
  14. package/dist/MasterDetailForm.d.ts.map +1 -0
  15. package/dist/ModalForm.d.ts +7 -2
  16. package/dist/ModalForm.d.ts.map +1 -0
  17. package/dist/ObjectForm.d.ts +1 -0
  18. package/dist/ObjectForm.d.ts.map +1 -0
  19. package/dist/SplitForm.d.ts +22 -2
  20. package/dist/SplitForm.d.ts.map +1 -0
  21. package/dist/TabbedForm.d.ts +13 -1
  22. package/dist/TabbedForm.d.ts.map +1 -0
  23. package/dist/WizardForm.d.ts +14 -1
  24. package/dist/WizardForm.d.ts.map +1 -0
  25. package/dist/autoLayout.d.ts +1 -0
  26. package/dist/autoLayout.d.ts.map +1 -0
  27. package/dist/deriveMasterDetail.d.ts +1 -0
  28. package/dist/deriveMasterDetail.d.ts.map +1 -0
  29. package/dist/fieldGroups.d.ts +1 -0
  30. package/dist/fieldGroups.d.ts.map +1 -0
  31. package/dist/index.d.ts +3 -2
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +1609 -1326
  34. package/dist/index.umd.cjs +1 -1
  35. package/dist/masterDetailTx.d.ts +1 -0
  36. package/dist/masterDetailTx.d.ts.map +1 -0
  37. package/dist/occSave.d.ts +78 -0
  38. package/dist/occSave.d.ts.map +1 -0
  39. package/dist/sanitize.d.ts +1 -0
  40. package/dist/sanitize.d.ts.map +1 -0
  41. package/dist/sectionFields.d.ts +10 -2
  42. package/dist/sectionFields.d.ts.map +1 -0
  43. package/dist/successBehavior.d.ts +1 -0
  44. package/dist/successBehavior.d.ts.map +1 -0
  45. package/package.json +12 -11
package/CHANGELOG.md CHANGED
@@ -1,5 +1,835 @@
1
1
  # @object-ui/plugin-form
2
2
 
3
+ ## 17.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4a51e77: Stop declaring 14 symbols across ten packages under names `@objectstack/spec`
8
+ owns (objectui#3161, objectstack#4115 batch 7 — the long tail, one or two
9
+ entries per package). All ten packages leave the ledger, which drops from 17
10
+ collisions across 11 packages to 3 across 1.
11
+
12
+ **Renamed exports** — in every case the spec exports the same name for a
13
+ _different_ thing, so the old name was a mis-description rather than a dialect:
14
+
15
+ | package | was | now | what the spec's same-named export is |
16
+ | :------------------------- | :--------------------------------- | :--------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
17
+ | `@object-ui/fields` | `FieldWidgetProps` | `FieldWidgetComponentProps` | the DECLARED field-widget plugin props contract (a zod object; `field.type` is the `FieldType` enum, `readonly`/`required` carry defaults) |
18
+ | `@object-ui/layout` | `PageHeaderProps` | `PageHeaderComponentProps` | the authored `page:header` node — a zod schema of `title`, `subtitle`, an icon NAME, `breadcrumb`, `actions: string[]` |
19
+ | `@object-ui/layout` | `Page` | `PageNodeRenderer` | the authored page metadata DOCUMENT (`name`, `label`, `type`, `regions`) |
20
+ | `@object-ui/plugin-detail` | `ObjectFieldLike` | `ObjectDefFieldLike` | the i18n duck type `translateObject` walks (`help`/`description`, plus `[key: string]: any`) |
21
+ | `@object-ui/plugin-grid` | `ColumnSummaryConfig` | `ColumnSummarySetting` | the OBJECT form of `ListColumn.summary` **only** — the local one was the whole union, shorthand included |
22
+ | `@object-ui/plugin-grid` | `isMultiValueField` | `hasMultiValueShape` | the spec's classifier, which requires a def with a `type`; the local one is called with `undefined` |
23
+ | `@object-ui/collaboration` | `RealtimeConfig` | `RealtimeSubscriptionConfig` | the app's realtime DECLARATION (`enabled`, `transport`, `subscriptions[]`) |
24
+ | `@object-ui/plugin-charts` | `ChartConfig` | `ChartContainerConfig` | the authored chart document (`type`, `xAxis`, `series`, `showLegend`, …) |
25
+ | `@object-ui/plugin-form` | `FormSection` / `FormSectionProps` | `FormSectionContainer` / `FormSectionContainerProps` | the authored form-section metadata (`name`, `pane`, `visibleWhen`, `fields`) |
26
+ | `@object-ui/providers` | `Theme` | `ThemePreference` | a whole theme DOCUMENT (`name`, `label`, `colors`, `typography`) |
27
+ | `@object-ui/runner` | `App` (default export) | `RunnerApp` | the authored application metadata type **and** the `App.create()` builder |
28
+ | `@object-ui/sdui-parser` | `ValidationResult` | `ManifestValidationResult` | plugin-manifest validation (`{ valid, errors?, warnings? }`), exported from both `kernel` and `contracts` |
29
+
30
+ `ManifestValidationResult` follows the `<what was validated>Validation<Error|Result>`
31
+ convention registered on objectstack#4115 (`@object-ui/core` took
32
+ `SchemaNodeValidationResult` in batch 4). `PageHeaderComponentProps` deliberately
33
+ reuses the name `@object-ui/app-shell` already chose for its own header props in
34
+ batch 3, so one concept does not acquire two dialect names one package apart.
35
+
36
+ **Now derived from the spec instead of hand-written:**
37
+
38
+ - `@object-ui/fields` — `isFileIdToken` is re-exported from
39
+ `@objectstack/spec/data`. The local copy was character-for-character identical
40
+ to the spec's function while its comment said it "mirrors" it, so every
41
+ behaviour test passed and only reference identity could tell the two apart.
42
+ The regex is a wire decision: widening it server-side while a copy here kept
43
+ the old bound would make every new id read as "not a reference", and the
44
+ widget would submit the legacy inline blob to a backend expecting a reference.
45
+ - `@object-ui/plugin-detail` — `FeedFilterMode` is re-exported from
46
+ `@objectstack/spec/data`, in a file that already imported the sibling
47
+ `FeedItemType` from the spec.
48
+ - `@object-ui/plugin-grid` — the eleven-member aggregation union is now the
49
+ spec's `ColumnSummary` enum, so the total `Record<ColumnSummaryType, string>`
50
+ label map turns a member the spec adds into a compile error instead of a
51
+ blank footer cell. `ColumnSummarySetting` is `NonNullable<ListColumn['summary']>`,
52
+ i.e. whatever forms the spec itself accepts. `hasMultiValueShape` delegates to
53
+ the spec's `isMultiValueField` rather than re-deriving it from
54
+ `MULTI_OPTION_TYPES` / `MULTI_CAPABLE_TYPES`.
55
+ - `@object-ui/providers` — `ThemePreference` is the spec's `ThemeMode` union
56
+ plus the one legacy `'system'` spelling this provider still honours for stored
57
+ preferences, read off the schema's own `_zod` carrier so the package takes no
58
+ zod dependency.
59
+
60
+ `@objectstack/spec` moves from `devDependencies` to `dependencies` in
61
+ `@object-ui/fields` (it re-exports a runtime function) and `@object-ui/providers`
62
+ (its public `.d.ts` now references the spec).
63
+
64
+ Scored `minor`, not `major`, per this repo's fixed-group rule — objectui's major
65
+ tracks `@objectstack`, so breaking changes of our own ship as minor with the
66
+ semantics spelled out above (see AGENTS.md §版本号策略). A `major` here would carry
67
+ all 39 packages of the fixed group to `18.0.0` and off objectstack's 17.x line.
68
+
69
+ ### Patch Changes
70
+
71
+ - 335041c: Stop declaring 13 `@object-ui/core` symbols under names `@objectstack/spec` owns
72
+ (objectui#3158, objectstack#4115 batch 4).
73
+
74
+ **Breaking for importers of `@object-ui/core`** — seven exported names changed,
75
+ because the spec exports the same name for a _different_ thing:
76
+
77
+ | was | now | what the spec's same-named export actually is |
78
+ | :----------------------- | :-------------------------------- | :--------------------------------------------------------------------------- |
79
+ | `ChartSeries` | `ChartSeriesBinding` | the authored dataset-binding descriptor (a measure `name`, no `data`) |
80
+ | `ActionHandler` | `ActionRunnerHandler` | the SERVER-side objectql handler, `(ctx) => unknown` |
81
+ | `PluginDefinition` | `RegistryPluginDefinition` | the platform PACKAGE manifest (`id`/`slug`/`staticPath`/install hooks) |
82
+ | `ValidationError` | `SchemaNodeValidationError` | plugin-manifest validation, keyed by `field`, no severity |
83
+ | `ValidationResult` | `SchemaNodeValidationResult` | ditto, with both arrays optional |
84
+ | `defineView` | `defineSystemView` | the VIEW-DOCUMENT factory: parses a `ViewSchema`, returns a validated `View` |
85
+ | `resolveCrudAffordances` | `resolveEffectiveCrudAffordances` | the object-level affordance matrix, with no notion of server API operations |
86
+
87
+ The other six keep their names and are now **imported from the spec** instead of
88
+ re-declared: `StyleMap`, `ResponsiveStyles` (ADR-0065), `RowHeight`,
89
+ `CONTEXT_TOKENS`, `CrudAffordances`, `RowCrudPredicates`.
90
+
91
+ **The copies were live misdescriptions, not just duplicates.** Three said so in
92
+ their own comments:
93
+
94
+ - `CONTEXT_TOKENS` carried a note that the duplication was "temporary until the
95
+ next coordinated release… because the installed `@objectstack/spec` predates
96
+ that export". The installed spec (17.0.0-rc.0) exports it, and the copy was
97
+ byte-identical — so it passed every value comparison and every behavioural
98
+ test for the whole interval in which its stated reason was false.
99
+ - `RowHeight` advertised itself as "the spec's `RowHeightSchema` vocabulary"
100
+ while being a hand-written union. It happened to be correct; nothing would
101
+ have caught the day it stopped being.
102
+ - `managedBy.ts` described itself as a "UI-side mirror of the framework's
103
+ `resolveCrudAffordances()`" and carried its own `DEFAULTS` table — a
104
+ line-for-line copy of the spec's `CRUD_AFFORDANCE_DEFAULTS`, plus a copy of
105
+ its override parser.
106
+
107
+ `resolveEffectiveCrudAffordances` now **delegates** the bucket/`userActions` half
108
+ to the spec's `resolveCrudAffordances()`, so the bucket table has exactly one
109
+ definition on the platform. What stays objectui's is the part the spec has no
110
+ notion of: intersecting that matrix with the server-resolved effective API
111
+ operation set (#3391), so the UI never offers a button the server would 405 —
112
+ and the name now says that instead of claiming to be the spec's function.
113
+
114
+ Deriving `RowCrudPredicates` also **tightens** it: the local copy typed
115
+ `visibleWhen`/`disabledWhen` as `unknown`, where the spec types them as
116
+ `Expression | ExpressionInput`. That was imprecision, not a deliberate dialect.
117
+
118
+ - 5eaa861: `list-view` and `embeddable-form` get a data source on the registry path — their required `objectName` was binding to nothing (#3144).
119
+
120
+ `SchemaRenderer` puts the data source on `SchemaRendererContext` and **never** injects it into
121
+ component props. A component that reads `props.dataSource` therefore needs its registration to
122
+ bridge the two. `object-form`, `object-kanban` and `object-calendar` each register a small
123
+ renderer that does exactly that. These two did not:
124
+
125
+ - `list-view` (and its `view:list` alias) registered the bare `ListView`, which reads
126
+ `props.dataSource` — so its `getObjectSchema` effect returned immediately, nothing was ever
127
+ fetched, and it rendered the `empty-state` "Nothing here".
128
+ - `embeddable-form`'s renderer was `({ schema }) => <EmbeddableForm config={schema} />`, dropping
129
+ the context entirely — so the read-only source it derives for its inner `ObjectForm` was never
130
+ built, and its submit path (`if (dataSource) await dataSource.create(...)`) had nothing to call.
131
+
132
+ Both declare `objectName` **required** in their registry `inputs`. A binding the protocol obliges
133
+ an author to supply, that nothing on that path can consume, is objectstack#4413's shape one layer
134
+ up — and the reason it went unnoticed is that the console never takes this path: it reaches
135
+ ListView through `ObjectView`'s `renderListView` render-prop, which passes a data source itself.
136
+ Broken on the registry/SDUI path, which is the path `sdui.manifest.json` describes and a
137
+ `kind:'react'` page walks.
138
+
139
+ Found by `apps/console/src/__tests__/public-block-binding-reach.test.tsx` (objectstack#4472), not
140
+ by hand — that suite mounts every public block declaring an `objectName` under a recording
141
+ `dataSource` and asserts the binding arrives. Its ledger carried these two as named debt; with the
142
+ bridge in place the ledger's both-directions assertion **failed until the entries were deleted**,
143
+ which is the mechanism working as designed. Only `record:related_list` remains, and legitimately
144
+ (it needs a parent record id from `RecordContext` before it may fetch).
145
+
146
+ An explicit `dataSource` prop still wins, so hosts passing their own are unaffected, and
147
+ `ListViewRenderer` forwards refs so `ListViewHandle` still works through the registry.
148
+
149
+ - a8ad6c0: A required boolean must be savable in its UNCHECKED state — `false` and `0` are values.
150
+
151
+ Reported against an AI-built task tracker whose 任务 object has a required
152
+ `是否完成` boolean: the create form showed the switch OFF, answered "是否完成不能
153
+ 为空", and saved instantly once the switch was turned ON. The app could only ever
154
+ create ALREADY-DONE tasks — the one state the control shows by default was the
155
+ one value it refused to save (cloud#972).
156
+
157
+ Two defects stacked, and either alone is enough to break it:
158
+
159
+ **The `required` verdict read truthiness, not presence.** `@objectstack/spec`
160
+ FieldSchema.required (ADR-0113) is "an insert must provide a NON-NULL value",
161
+ and objectql's record validator implements exactly that. react-hook-form's
162
+ built-in rule instead fails whenever `isBoolean(value) && !value` — its
163
+ accept-the-terms checkbox heritage — silently redefining every required boolean
164
+ as "must be TRUE", including a select whose chosen option value is `false`. It
165
+ also disagreed the other way, letting a whitespace-only string through for the
166
+ server to reject with a 400. The form renderer no longer hands RHF its own
167
+ `required`: the check is now a `validate` entry keyed `required` (so the error
168
+ still surfaces as `type: 'required'`, which the conditional-required cleanup
169
+ keys on) backed by a new shared `isMissingForRequired` in `@object-ui/core`, a
170
+ deliberate mirror of objectql `record-validator.isMissing` — `undefined`,
171
+ `null`, blank-after-trim string, empty array. Deleting the inherited rule also
172
+ stops a `required` that rode in on `validation` from outliving a `requiredWhen`
173
+ that resolved to FALSE.
174
+
175
+ **A boolean field held `undefined` while displaying "off".** A two-state control
176
+ has no third state, but a field with no entry in `defaultValues` rendered an OFF
177
+ switch backed by nothing: the create payload omitted the column (it lands null,
178
+ which reads as unchecked but isn't) and the presence check above would still
179
+ refuse it. The form renderer now folds `false` into `defaultValues` for every
180
+ boolean-widget field the caller left unset — in `defaultValues` itself, not
181
+ per-Controller, because that object is also the dirty-check baseline and what
182
+ the defaults-reset window replays. Every surface gets it, including the
183
+ modal/drawer create dialogs that start from a bare `{}`. An authored default
184
+ (or a loaded record, `null` included) still wins.
185
+
186
+ `WizardForm`'s cross-step gate had its own copy of the empty-value predicate; it
187
+ now imports the shared one so it cannot drift from the per-field verdict. And
188
+ the field-demo renderer read `schema.defaultValue || schema.value`, throwing
189
+ away an authored default of `false` / `0` / `''` — same falsy-as-empty class,
190
+ now `??`.
191
+
192
+ Verified end to end on a local stack against the exact metadata shape
193
+ `apply_blueprint` materializes (`{ type: 'boolean', required: true }`, no
194
+ default): a 是否完成 = 否 task with 工时 = 0 now creates and persists as
195
+ `{ hours: 0, is_done: false }`, turning the switch on still stores `true`, and a
196
+ blank required text is still refused.
197
+
198
+ - Updated dependencies [4ae0ac4]
199
+ - Updated dependencies [696e3c1]
200
+ - Updated dependencies [bca45cc]
201
+ - Updated dependencies [a889e31]
202
+ - Updated dependencies [09d30a4]
203
+ - Updated dependencies [4bf612c]
204
+ - Updated dependencies [335041c]
205
+ - Updated dependencies [b414983]
206
+ - Updated dependencies [256f8cc]
207
+ - Updated dependencies [d9668a7]
208
+ - Updated dependencies [4b470b9]
209
+ - Updated dependencies [785b8a5]
210
+ - Updated dependencies [cb82705]
211
+ - Updated dependencies [f572849]
212
+ - Updated dependencies [4a51e77]
213
+ - Updated dependencies [f6e8d78]
214
+ - Updated dependencies [ea96284]
215
+ - Updated dependencies [d3584c6]
216
+ - Updated dependencies [a8ad6c0]
217
+ - Updated dependencies [444457c]
218
+ - Updated dependencies [850033c]
219
+ - Updated dependencies [022e4c3]
220
+ - Updated dependencies [009e25d]
221
+ - Updated dependencies [726b89c]
222
+ - @object-ui/types@17.2.0
223
+ - @object-ui/components@17.2.0
224
+ - @object-ui/core@17.2.0
225
+ - @object-ui/react@17.2.0
226
+ - @object-ui/i18n@17.2.0
227
+ - @object-ui/fields@17.2.0
228
+ - @object-ui/permissions@17.2.0
229
+
230
+ ## 17.1.0
231
+
232
+ ### Minor Changes
233
+
234
+ - 38ca8be: refactor(fields): `requiredWhen` is the only required-predicate slot — drop the retired `conditionalRequired` alias
235
+
236
+ `@objectstack/spec` 17 (objectstack#3855) **retired** `Field.conditionalRequired`,
237
+ the long-deprecated alias of `requiredWhen`. ObjectUI carried a back-compat read
238
+ for it in seven places; all of them are removed.
239
+
240
+ The removal is safe because the spec did not merely _stop emitting_ the key — it
241
+ made authoring it **fail loudly**. `retiredKey()` declares the key as
242
+ `z.never()`, so:
243
+
244
+ - `z.input` types it as `never` — writing it is a `tsc` error at the authoring site;
245
+ - the parse **rejects** it (verified against `17.0.0-rc.0`), at both `FieldSchema`
246
+ and `ObjectSchema`, with the prescription as the message:
247
+
248
+ > `conditionalRequired` was removed in @objectstack/spec 17 (#3855) — use
249
+ > `requiredWhen`. Rename the key; the value (a CEL predicate) is unchanged.
250
+ > Run `os migrate meta --from 16` to rewrite it automatically.
251
+
252
+ So spec-parsed metadata cannot carry the key — an object declaring it fails to
253
+ load rather than loading with the rule silently dropped. Keeping a renderer-side
254
+ `requiredWhen ?? conditionalRequired` would have re-created exactly the second
255
+ de-facto contract the tombstone exists to prevent: the key would have kept
256
+ working in the UI while being rejected everywhere else, hiding the producer's bug
257
+ (AGENTS.md #0.1). "Backend-agnostic" (#1) does not argue for keeping it either —
258
+ `conditionalRequired` is an ObjectStack-spec-ism, so the only producers that ever
259
+ emit it are ObjectStack producers on ≤16, and the spec ships them a converter.
260
+
261
+ Removed from:
262
+
263
+ | package | site |
264
+ | :----------------------- | :-------------------------------------------------------------------------------------------------------- |
265
+ | `@object-ui/types` | the `conditionalRequired?:` member on `FormField` |
266
+ | `@object-ui/core` | the `??` fallback + rules-param member in `resolveFieldRuleState` |
267
+ | `@object-ui/components` | three pass-throughs in the form renderer |
268
+ | `@object-ui/plugin-form` | `ObjectForm`, `ModalForm`, `sectionFields`, `deriveMasterDetail` (×2) |
269
+ | `@object-ui/app-shell` | the field inspector's legacy read/auto-migrate, and the key's entry in `clientValidation`'s CEL lint list |
270
+
271
+ **Studio authors lose nothing.** The object designer's draft validation parses
272
+ against the spec's own `ObjectSchema`, so a draft carrying the key now surfaces
273
+ the tombstone's rename prescription under the same `fields.<name>.conditionalRequired`
274
+ path the CEL lint used to report — a better message than the inspector's silent
275
+ auto-migration, and one the server agrees with. That behavior is pinned by a test.
276
+
277
+ **Migrating:** rename the key to `requiredWhen` (the CEL value is unchanged), or
278
+ run `os migrate meta --from 16`.
279
+
280
+ - 03bd53b: feat(form): `SplitForm` honours the spec's new `FormSection.pane`
281
+
282
+ A split form's panel assignment was a hardcoded positional rule — first section
283
+ left, everything else right. The rule was invisible in the metadata, so
284
+ reordering sections silently moved them across the divider, and an author could
285
+ not place two sections in the left pane at all.
286
+
287
+ Sections now declare their panel: `pane: 'primary' | 'secondary'`
288
+ (@objectstack/spec `FormSection.pane`, objectstack#4160). Placement follows the
289
+ key, not the array position — reordering paned sections never changes the
290
+ layout. Omitted keys keep the exact legacy rule (first section `primary`, rest
291
+ `secondary`), so existing metadata renders unchanged.
292
+
293
+ `ObjectForm`'s split dispatch copies the key through its per-key section mapping
294
+ (the path that once silently dropped `visibleOn`), and `ObjectFormSection`
295
+ declares it. The spec side rejects `pane` on non-split form types at parse, so
296
+ the key can never be an accepted-but-ignored no-op.
297
+
298
+ ### Patch Changes
299
+
300
+ - 7639a61: fix(form): the spec↔runtime form-field chokepoint stops dropping spec 17 vocabulary, and the validator stops contradicting the renderer — #3090
301
+
302
+ `normalizeSectionField` — the one translation point between the spec's authored
303
+ form-field shape (`field` = object-field reference) and the runtime shape
304
+ (`name` = data path) — silently dropped four spec keys, worst of all the
305
+ ADR-0089 **canonical** `visibleWhen` spelling while the deprecated `visibleOn`
306
+ worked. Now:
307
+
308
+ - view-level `visibleWhen` routes into the view-level slot (`visibleOn`) so it
309
+ ANDs with the object-level rule instead of clobbering it, and the wizard's
310
+ final-submit gate folds the same slot into its verdict (before, a required
311
+ field the view itself hides could block submission from off-screen);
312
+ - `dependsOn`, `keyField`, and `disclosure` carry through;
313
+ - a behavioral parity gate walks the spec `FormFieldSchema` key set — a key the
314
+ spec adds fails as unmapped, a key it retires fails as stale.
315
+
316
+ `SelectOptionSchema` is now derived from `@objectstack/spec/data` by reference
317
+ (it used to strip `color` — which `@object-ui/fields` renders — plus `default`
318
+ and the per-option `visibleWhen` gate), with pinned divergences (`value`
319
+ widened for UI forms, `visibleWhen` on the #2212 wire contract) and documented
320
+ UI-only extensions (`disabled`, `icon`). `SelectOption` (TS) gains `color` and
321
+ `default`.
322
+
323
+ `FormFieldSchema` (the runtime vocabulary `objectui validate` enforces) now
324
+ covers every key the `FormField` interface declares — `widget`, `dependsOn`,
325
+ `hidden`, `readonly`, `visibleOn`/`visibleWhen`/`readonlyWhen`/`requiredWhen`,
326
+ `span` — and `type` is optional, matching the interface. A typo'd predicate now
327
+ fails loudly instead of being stripped; spec-shape fields (`{ field: … }`) are
328
+ still rejected, pinning the two-layer boundary.
329
+
330
+ - 94e63ef: fix(form): the runtime `field` metadata slot is declared instead of smuggled, and importing the spec's FormField is a lint error — #3090
331
+
332
+ `FormField.field` — the slot where object-bound form paths stash the resolved
333
+ field-metadata **object** for widgets — rode through the index signature,
334
+ undeclared, readable only via `as any`. Same key, different layer: in the spec
335
+ form-view vocabulary `field` is a _string_ (the referenced object-field name),
336
+ and the undeclared slot kept that pun latent. The slot is now declared
337
+ (`field?: Record<string, any>`) with the invariant in its JSDoc: on a runtime
338
+ FormField it is never a string — the authored string form ends at the
339
+ `normalizeSectionField` chokepoint, and a tripwire test pins that across all
340
+ three input shapes. Assigning a string is now a compile error; the `as any`
341
+ casts at the read sites are gone.
342
+
343
+ A `no-restricted-imports` tripwire bans importing `FormField`/
344
+ `FormFieldSchema` from `@objectstack/spec/ui` inside this repo: the spec's
345
+ FormField TYPE erases to `any` in its dist (objectstack#4171), so the
346
+ misimport silently deletes type safety — tsc says nothing. The lint message
347
+ names the two layers and the correct import. The drift-guard parity test is
348
+ the one legitimate importer, exempted inline with its reason.
349
+
350
+ Ledger: `FormField` and `FormFieldSchema` move from untriaged DEBT to ALLOW
351
+ with the two-layer rationale written down (122 → 120).
352
+
353
+ - aeb0bd2: fix(form): a tabbed/split form honours the form view's own `columns`
354
+
355
+ `FormView.columns` is a spec key, but only `ObjectForm`'s simple path and
356
+ `ModalForm` read it. `TabbedForm` and `SplitForm` derived the grid width from the
357
+ per-section `columns` alone, so a view declaring `columns: 3` rendered 3 columns
358
+ in a modal and **single-column** as a tab or split — the same metadata laying out
359
+ differently depending on which host picked it up.
360
+
361
+ Both now resolve the grid the way the other hosts already did:
362
+
363
+ explicit form `columns` ?? widest section's `columns` ?? 1
364
+
365
+ The two keys answer different questions and the precedence reflects that: the
366
+ view's `columns` is how wide the grid is, a section's `columns` is how densely
367
+ that section fills it (via per-field `colSpan`). `columns` is declared on
368
+ `TabbedFormSchema` / `SplitFormSchema` accordingly — `ObjectForm` already spread
369
+ it through, it was simply being dropped on arrival.
370
+
371
+ - c735bf7: fix(form): a spec-vocabulary field no longer crashes the standalone form, and every surface now says which vocabulary you meant — #3090
372
+
373
+ Writing the regression test against the unfixed renderer proved the failure
374
+ was worse than the assumed silent drop: a `{ field: 'x' }` entry (spec
375
+ form-VIEW vocabulary) slipped past the `f?.name` guards into a
376
+ react-hook-form Controller with `name === undefined` and crashed the whole
377
+ standalone form on `name.split('.')`, with nothing naming the culprit entry.
378
+ The renderer now partitions such entries out — the rest of the form renders —
379
+ and surfaces them with an inline alert plus a console.error whose text is the
380
+ fix instruction (rename to `name`, or use an object-bound form whose sections
381
+ accept the spec shape).
382
+
383
+ `objectui validate` grows the same boundary awareness: on failure, a
384
+ `{ field: … }` entry in a standalone form gets a "likely cause" hint naming
385
+ the real fix instead of the bare `invalid_union` — the previous message read
386
+ as "bolt a `name` on", which converts spec metadata wrongly. On success,
387
+ mixed-vocabulary entries (`name` + string `field`) get a warning: they
388
+ validate, but the spec key is dead weight the renderer ignores.
389
+
390
+ `normalizeSectionField` warns (once per site) when an authored section field
391
+ mixes both identity keys — the spec branch derives the runtime name from
392
+ `field`, so an authored `name` was silently overwritten.
393
+
394
+ - e339d60: fix(plugin-form): swapping `recordId` no longer leaves the previous record on screen
395
+
396
+ `loading` in `ModalForm` / `DrawerForm` / `TabbedForm` / `SplitForm` was only ever
397
+ set `true` once, by `useState(true)`, and thereafter only ever set `false`. A
398
+ `recordId` change therefore re-entered the fetch effect **without** going back
399
+ through the loading branch: the form stayed mounted showing — and accepting edits
400
+ to — record A's values, with nothing indicating a different record had been asked
401
+ for, until B's response landed and replaced them in place. Anything typed in that
402
+ window read as A's on screen and would have been submitted against B.
403
+
404
+ The same effect had no staleness guard either, so two overlapping reads landed in
405
+ **completion** order rather than request order: ask for B then C, and a slow B
406
+ arriving last left the form showing B while the caller had asked for C.
407
+
408
+ Both are the same defect from the user's side — the form displays a record nobody
409
+ asked for — so both are fixed:
410
+
411
+ - a change of record re-enters the loading state before the read, so the previous
412
+ record is off screen while the next one is in flight. Gated on the record
413
+ actually changing: the effect also re-runs on `initialData`/`initialValues`
414
+ identity churn (callers rebuild those objects every render), and flashing the
415
+ loading state for that would thrash;
416
+ - the effect's cleanup marks its read stale, so a response that is no longer the
417
+ one being awaited is dropped instead of overwriting a newer record.
418
+
419
+ `ObjectForm` already re-entered loading before its fetch, which is why this only
420
+ ever reproduced on the four sectioned variants.
421
+
422
+ **Also fixed, a consequence of the above:** hiding the form unmounts the inner
423
+ renderer, and that renderer is the only thing that reports dirtiness via
424
+ `onDirtyChange` — it gets no chance to report `false` on the way out. Without
425
+ clearing the flag, the overlay's unsaved-input guards would stay armed for input
426
+ belonging to a record no longer on screen: a plain refresh would prompt, and
427
+ closing would offer to discard nothing.
428
+
429
+ - aa35561: fix(form): a split create/edit form no longer loses the panel you are not submitting from (#2153)
430
+
431
+ `SplitForm` rendered one `SchemaRenderer` — one react-hook-form instance and one
432
+ `<form>` element — **per section**, and its two groups of sections live in
433
+ separate resizable panels. So each panel owned isolated form state: submitting
434
+ from one panel's action bar sent only that section's fields and silently dropped
435
+ everything the user had typed on the other side of the divider. Filling both
436
+ panels and clicking Create persisted `{ subject }` alone.
437
+
438
+ The same isolation killed cross-panel field rules: a `visibleWhen` in the right
439
+ panel referencing a left-panel field never saw that field in its record, so the
440
+ predicate faulted and failed **open** — the field the author meant to hide was
441
+ always shown.
442
+
443
+ Both panels are now ONE form. The panel group became a layout the form renderer
444
+ owns, via a new `FormSchema.fieldPanes` (+ `fieldPanesOrientation`,
445
+ `fieldPanesResizable`) that mirrors `fieldTabs` (#2959): the `<form>` wraps the
446
+ whole `ResizablePanelGroup` and each pane holds only fields, which is what lets a
447
+ single react-hook-form instance span the divider. Sections inside a pane render
448
+ behind the inline `section-divider` header, each at its own declared column
449
+ density within the form's shared grid.
450
+
451
+ One more fix falls out of moving the panels into the renderer: `splitResizable:
452
+ false` now actually pins the divider. It previously only hid the grip — the
453
+ separator stayed draggable, because nothing passed the panel library's
454
+ `disabled`.
455
+
456
+ Each pane is its own `@container`, so a multi-column section collapses to fewer
457
+ columns as its panel is dragged narrower instead of overflowing.
458
+
459
+ - 3c1f321: fix(form): a tabbed/sectioned create-edit form no longer loses the tabs you are not looking at (#2959, #2153)
460
+
461
+ The explicit-`sections` path rendered one `SchemaRenderer` — one react-hook-form
462
+ instance and one `<form>` element — **per section**, all sharing the same
463
+ `formId`. Two failures compounded:
464
+
465
+ 1. the footer submit button (`form={formId}`) can only be associated with the
466
+ **first** of those forms, so section 2+ never reached the payload; and
467
+ 2. in the `tabbed` variant Radix unmounted the inactive panel, destroying that
468
+ tab's form state outright.
469
+
470
+ Reported flow (HotCRM, 3 tabs, required `description` on tab 3): fill tab 1 →
471
+ submit → server 400 `description is required` → switch to tab 3, fill it →
472
+ submit → the server now reports `subject; description; status; priority` **all**
473
+ missing, because the second submit's body had lost every earlier value.
474
+
475
+ `ModalForm` (stacked and `contentLayout: 'tabbed'`) and `TabbedForm` now render
476
+ ONE form for all sections, matching `ObjectForm` / `DrawerForm`. Stacked sections
477
+ use the existing inline `section-divider` header (which now also renders the
478
+ section's `description`); tabbed sections go through a new
479
+ `FormSchema.fieldTabs` (+ `defaultFieldTab`, `fieldTabsPosition`) that the form
480
+ renderer distributes into **force-mounted** Radix panels — CSS-hidden rather
481
+ than unmounted, since react-hook-form skips validation for unmounted fields,
482
+ which is how a required field on a tab nobody opened used to sail past the
483
+ client and come back as a server 400.
484
+
485
+ Validation feedback now points at the tab: a rejected field activates its tab and
486
+ every tab holding one is marked on its trigger, for client-side rules and server
487
+ `fields[]` rejections alike.
488
+
489
+ - c0d0bc8: fix(form): a wizard with `allowSkip` no longer submits past the required fields you skipped
490
+
491
+ `allowSkip` let the user jump to any step from the indicator, and
492
+ `handleStepClick` did so without validating anything on the way. Since a wizard
493
+ mounts ONE step at a time and react-hook-form only validates the fields currently
494
+ **mounted**, a required field on a step nobody opened was never registered, never
495
+ validated, and simply absent from the payload.
496
+
497
+ Measured against the unfixed component — 3 steps, required `owner` on step 2,
498
+ `allowSkip: true`, click step 3's indicator, fill it, hit Create:
499
+
500
+ createCalls: 1
501
+ payload: { subject: 'S1', notes: 'S3' } // `owner` missing entirely
502
+ UI mentions "required": false // nothing said so
503
+
504
+ So an invalid create went out and the client said nothing about why — #2959's
505
+ validation half, wearing a wizard's clothes.
506
+
507
+ The final submit now checks the WHOLE declared field set, and when something is
508
+ outstanding it returns the user to the first step that has one, marks that step's
509
+ indicator (`data-error="true"`, destructive circle + icon), names the fields in a
510
+ toast, and sends nothing. Conditional rules are honoured: the check runs on the
511
+ canonical `resolveFieldRuleState`, the same engine the form renderer and the
512
+ server's rule-validator use, so a field hidden by `visibleWhen` or not yet
513
+ required by `requiredWhen` is not demanded. The sequential path is unaffected —
514
+ a forward jump is refused without `allowSkip`, so Next already validated each step.
515
+
516
+ Also in `WizardForm`:
517
+
518
+ - `FormView.columns` is now honoured (spec key, previously dropped): the grid
519
+ width is the view's `columns`, else the step's own. Unlike the tabbed/split
520
+ hosts there is no widest-section fallback — wizard steps never share a viewport,
521
+ so each keeps its authored width.
522
+ - the root gained `@container`. The step grid is sized with container queries, and
523
+ without a container ancestor every `@md:`/`@2xl:` variant was inert — a step
524
+ declaring 2 columns rendered single-column. Found by running it in a browser;
525
+ the class was present all along, which is why asserting the class alone had
526
+ missed it.
527
+
528
+ - Updated dependencies [62311b6]
529
+ - Updated dependencies [fc0272a]
530
+ - Updated dependencies [9e7349e]
531
+ - Updated dependencies [8864971]
532
+ - Updated dependencies [1cf0de7]
533
+ - Updated dependencies [752e18f]
534
+ - Updated dependencies [c785740]
535
+ - Updated dependencies [b41f401]
536
+ - Updated dependencies [19e9fa0]
537
+ - Updated dependencies [d61efd1]
538
+ - Updated dependencies [95b7214]
539
+ - Updated dependencies [7d9734d]
540
+ - Updated dependencies [6ae818e]
541
+ - Updated dependencies [9eb932b]
542
+ - Updated dependencies [746dd00]
543
+ - Updated dependencies [aebfa4f]
544
+ - Updated dependencies [38ca8be]
545
+ - Updated dependencies [3cb9646]
546
+ - Updated dependencies [68ef584]
547
+ - Updated dependencies [4952edf]
548
+ - Updated dependencies [7f0252e]
549
+ - Updated dependencies [c4d7b20]
550
+ - Updated dependencies [c769d3d]
551
+ - Updated dependencies [7639a61]
552
+ - Updated dependencies [94e63ef]
553
+ - Updated dependencies [c735bf7]
554
+ - Updated dependencies [02aef0c]
555
+ - Updated dependencies [6f29aa5]
556
+ - Updated dependencies [d21794c]
557
+ - Updated dependencies [c4db402]
558
+ - Updated dependencies [5319bf1]
559
+ - Updated dependencies [49e5671]
560
+ - Updated dependencies [2307b52]
561
+ - Updated dependencies [9a04d25]
562
+ - Updated dependencies [b5b97e2]
563
+ - Updated dependencies [f59f2c1]
564
+ - Updated dependencies [07de839]
565
+ - Updated dependencies [2a40b5e]
566
+ - Updated dependencies [df613fa]
567
+ - Updated dependencies [4874117]
568
+ - Updated dependencies [ad0183a]
569
+ - Updated dependencies [ce08d55]
570
+ - Updated dependencies [eb4b740]
571
+ - Updated dependencies [aecc934]
572
+ - Updated dependencies [5b084eb]
573
+ - Updated dependencies [aa1240a]
574
+ - Updated dependencies [2374a49]
575
+ - Updated dependencies [390c071]
576
+ - Updated dependencies [d10f526]
577
+ - Updated dependencies [2d5d594]
578
+ - Updated dependencies [ea7f477]
579
+ - Updated dependencies [379728f]
580
+ - Updated dependencies [7f23cd0]
581
+ - Updated dependencies [0ded602]
582
+ - Updated dependencies [24e0e0a]
583
+ - Updated dependencies [f8a95e5]
584
+ - Updated dependencies [3a6cf24]
585
+ - Updated dependencies [aa35561]
586
+ - Updated dependencies [03bd53b]
587
+ - Updated dependencies [3c1f321]
588
+ - Updated dependencies [a045a32]
589
+ - Updated dependencies [912496d]
590
+ - Updated dependencies [80edbd4]
591
+ - Updated dependencies [9867281]
592
+ - @object-ui/core@17.1.0
593
+ - @object-ui/components@17.1.0
594
+ - @object-ui/react@17.1.0
595
+ - @object-ui/types@17.1.0
596
+ - @object-ui/i18n@17.1.0
597
+ - @object-ui/permissions@17.1.0
598
+ - @object-ui/fields@17.1.0
599
+
600
+ ## 17.0.0
601
+
602
+ ### Minor Changes
603
+
604
+ - f9bbddb: feat: gate detail/form edit & delete on the server's effective operation set (#3546)
605
+
606
+ PR-4 (#3391) wired the **list/toolbar** surface (ObjectView Import, ListView /
607
+ ObjectGrid Export) to the server-resolved effective API operation set
608
+ (`/me/permissions` `apiOperations`, intersected via
609
+ `resolveCrudAffordances(obj, effectiveApiOperations?)`). The **detail / form**
610
+ surfaces still gated edit/delete on the bucket + `userActions` alone. This
611
+ extends the same intersection to them, so the record page and its forms never
612
+ offer an operation the server would 405.
613
+
614
+ - **core** `isObjectInlineEditable(obj, effectiveApiOperations?)` gains the same
615
+ optional second argument as `resolveCrudAffordances` — inline-edit is now
616
+ additionally ANDed with the server allowing `update`.
617
+ - **app-shell** `RecordDetailView` threads the object's effective operations into
618
+ the synthesized Edit/Delete header actions and the record-body inline-edit
619
+ gate (`canEdit`); `RelatedRecordActionsBridge` intersects each **child**
620
+ object's Create/Edit/Delete handlers with that child's own effective set.
621
+ - **plugin-detail** `record:details` ANDs its inline-edit affordance with the
622
+ object's effective `update`.
623
+ - **plugin-form** `ObjectForm`'s blanket managed-object field lock also engages
624
+ when the server denies `update` (edit mode) / `create` (create mode).
625
+
626
+ Backward-compatible: a missing effective set (unrestricted object, older
627
+ backend, or no `PermissionProvider`) leaves the resolved affordance untouched —
628
+ the bucket/`userActions` decision wins, exactly as today. Layers on top of the
629
+ existing per-object `check('edit')` / `check('delete')` permission gates
630
+ (intersection, never union).
631
+
632
+ ### Patch Changes
633
+
634
+ - 6dee2cb: feat(form): consume spec-aligned FormView buttons/defaults in ObjectForm
635
+
636
+ The authored `@objectstack/spec` FormViewSchema carries structured
637
+ `buttons.{submit,cancel,reset}.{show,label}` and `defaults`, but the form
638
+ renderer only read the flat renderer-invented `showSubmit`/`submitText`/
639
+ `showCancel`/`cancelText`/`showReset`/`initialValues`. That left the two spec
640
+ keys parsed-but-inert (ADR-0078) and stuck at `experimental` in the spec
641
+ liveness ledger.
642
+
643
+ `ObjectForm` now folds the structured shape down onto those flat props inside
644
+ its existing normalization pass, so every entry path (ObjectView
645
+ drawer/modal/page, RecordFormPage) honors it. An explicitly-set flat key still
646
+ wins, so metadata authored against the deprecated flat keys is unchanged.
647
+ `ObjectView` and `RecordFormPage` forward `buttons`/`defaults` from the spec
648
+ form view. `ObjectFormSchema` gains the optional `buttons`/`defaults` fields.
649
+
650
+ Refs objectstack-ai/objectstack#1894, objectstack-ai/objectstack#2998.
651
+
652
+ - 2f947e4: fix(page,field): consume the spec's `type`/`label`/`maxLength` keys (framework#1878 §3 naming-drift recheck)
653
+
654
+ Three forward-drifts where objectui read a different key than the spec
655
+ declares, so authoring the documented key silently no-oped:
656
+
657
+ - **page `type` → `pageType`** (app-shell + components): `PageSchema` declares
658
+ the page KIND as `type`, but `PageRenderer` reads `schema.pageType` and fell
659
+ back to `'record'` — and nothing mapped between them. Every non-record page
660
+ (`home`/`app`/`list`/`utility`) rendered with the record max-width, a wrong
661
+ `data-page-type` attribute, and a suppressed header. `PageView` now passes
662
+ `pageType` alongside the SchemaNode discriminator `type`.
663
+ - **page `label` → `title`** (components): `PageSchema.label` is required but the
664
+ region renderer read only `title`. Now dual-reads `title ?? label`, mirroring
665
+ the fallback `DashboardRenderer` already uses. Coupled with the above — the
666
+ header is gated on `pageType !== 'record'`, so both were needed for a title to
667
+ appear.
668
+ - **field `maxLength`/`minLength`** (plugin-form + fields): validation already
669
+ dual-read these, but `ObjectForm`'s HTML-attribute pass and `TextAreaField`
670
+ read `max_length` only, so a spec-authored `maxLength` gave no browser cap and
671
+ no character counter. Both now dual-read, matching `buildValidationRules`.
672
+
673
+ Verified in the browser against the showcase: `capability_map` (`type: 'home'`)
674
+ now renders `data-page-type="home"`, the `home` max-width and its page title;
675
+ record pages are unchanged.
676
+
677
+ - 662bdf9: fix(fls): wire the real per-caller FLS channel into import targets and grid
678
+ columns; remove the never-populated `field.permissions` shape (objectstack#3661)
679
+
680
+ The `permissions?: { read?, write?, edit? }` key on `@object-ui/types` field
681
+ definitions (Phase 3.2.6) was declared-but-never-enforced: no producer in the
682
+ stack ever populated it, so every guard reading it short-circuited to "allow".
683
+ Per ADR-0049 enforce-or-remove, the shape is deleted and the three consumers
684
+ now use the server-resolved `/auth/me/permissions` channel
685
+ (`usePermissions().checkField`) — the same channel ObjectForm/ModalForm/ListView
686
+ already enforce:
687
+
688
+ - **ImportWizard target fields (app-shell `ObjectView`)**: the importable
689
+ field set (and thus the downloadable CSV template's columns) now drops
690
+ fields the caller cannot edit, instead of offering columns the server's
691
+ FLS write gate would 403.
692
+ - **ObjectGrid auto-derived columns**: columns the caller cannot read are
693
+ dropped (same gate ListView applies), instead of a dead schema-shape check.
694
+ - **ObjectForm**: the redundant dead guard in field generation is removed;
695
+ the existing `applyFieldPerms` gate remains the real enforcement point.
696
+
697
+ BREAKING CHANGE: `@object-ui/types` field definitions no longer accept a
698
+ `permissions` key. It never carried data at runtime; consumers needing
699
+ per-caller field-level permissions must use `@object-ui/permissions`
700
+ (`MePermissionsProvider` + `useFieldPermissions`/`checkField`).
701
+
702
+ - dc7a798: fix(plugin-grid,plugin-form,plugin-designer,cli,vscode-extension): type-check the last five unchecked packages, and fix the two runtime bugs that hid there (#2919)
703
+
704
+ Closes the remaining `DEBT` entries from the #2911 sweep. Each package gains
705
+ `"type-check": "tsc --noEmit"` and loses its entry in
706
+ `scripts/check-type-check-coverage.mjs`; coverage goes 36 -> 41 of 45 and
707
+ outstanding errors 25 -> 5 (only #2916 `plugin-view` and #2918 `layout` remain).
708
+
709
+ **Two of these were real bugs, not just type noise.**
710
+
711
+ `@object-ui/cli` — `objectui validate` could never report a validation failure.
712
+ `ZodError.errors` was removed in Zod 4 (the repo is on 4.4.3), so `.errors` read
713
+ `undefined` and `.forEach` threw a `TypeError` that the enclosing `catch`
714
+ reported as `✗ Error reading or parsing schema file: Cannot read properties of
715
+ undefined` — swallowing the very errors the command exists to print. Now reads
716
+ `.issues`. Verified against the built CLI: an invalid schema now prints
717
+ `1. Invalid input / Code: invalid_union` and exits 1.
718
+
719
+ `@object-ui/plugin-grid` — grouping a grid by a boolean column showed the raw
720
+ i18n key. `t('grid.booleanTrue', 'Yes')` asked for a key present in neither
721
+ `GRID_DEFAULT_TRANSLATIONS` nor any locale bundle, and passed the English
722
+ fallback as a bare second argument — which `createSafeTranslation`'s no-provider
723
+ translator reads as an _options object_, so the fallback never applied and the
724
+ header rendered the literal `grid.booleanTrue`. Switched to the `grid.yes` /
725
+ `grid.no` keys the boolean cell renderer (`ObjectGrid.tsx`) and
726
+ `BulkActionDialog` already use, with the fallback passed as `defaultValue`.
727
+ Covered by a new regression test, confirmed to fail against the old code.
728
+
729
+ The rest are type-only corrections that preserve runtime behaviour exactly:
730
+
731
+ - **plugin-grid** `importParsers.ts` — `scorePair`'s `score`/`reason` moved into
732
+ one `best` record. They were captured `let`s mutated only inside the `bump`
733
+ closure, which TypeScript's control-flow analysis does not track, so it still
734
+ believed `reason` was `'none'` at the type gate and flagged the comparisons as
735
+ non-overlapping (TS2367). The gate — which stops a text column being mapped
736
+ onto a number field — is unchanged; its two dedicated tests still pass.
737
+ - **plugin-form** — `SectionFieldsContext.fieldLabel` now requires `fallback`,
738
+ matching the `useSafeFieldLabel` producer in `@object-ui/i18n` (an omitted
739
+ fallback could not satisfy the `=> string` return, and all four call sites
740
+ already pass one). This one signature cleared six errors.
741
+ `MasterDetailFormSchema.recordId` widens to `string | number`, matching
742
+ `ObjectFormSchema` and the five envelopes that forward straight into it;
743
+ it is narrowed with `String()` only at the batch-transaction boundary, whose
744
+ `BatchTransactionOperation.id` is a string by protocol (the `isEdit` guard
745
+ already proves it non-null there). `deriveMasterDetail`'s column sort gets an
746
+ explicit `fillPriority` helper — `GridColumn.type` is optional, and a column
747
+ without one keeps sorting at priority 5 exactly as the old
748
+ `TYPE_FILL_PRIORITY[undefined] ?? 5` lookup put it.
749
+ - **plugin-designer** — unused `index` parameter prefixed `_`, matching the
750
+ `_entry` beside it.
751
+ - **cli** — a stale `@ts-expect-error` removed; `viteConfig` is typed `any`, so
752
+ the line it guarded had stopped erroring.
753
+ - **vscode-extension** (`object-ui`) — migrated off `moduleResolution: "node"`,
754
+ which is deprecated and stops working in TypeScript 7, to `node16` paired with
755
+ `module: "node16"` (the package has no `"type": "module"`, so node16 resolves
756
+ it as the CommonJS that tsup emits, and it gains the `exports`-map awareness
757
+ node10 lacks). Its error count was under-reported as 1: that TS5107 config
758
+ error masked four more. The package uses `console`/`Buffer` but sets
759
+ `lib: ["ES2020"]` with no DOM and never declared `@types/node` — added, with an
760
+ explicit `types: ["node", "vscode"]`.
761
+
762
+ Also: `plugin-grid`, `plugin-form` and `plugin-designer` gain the `baseUrl` +
763
+ `paths` override their type-checked plugin peers already carry, and `cli` an
764
+ empty `paths`. Without it the inherited root `paths` point `@object-ui/*` at
765
+ sibling `src/`, which is outside each project's `rootDir` and produces the ~104
766
+ spurious TS6059 errors noted in #2915; workspace deps instead resolve through
767
+ node_modules to built `.d.ts`, which `type-check`'s `dependsOn: ["^build"]`
768
+ guarantees exist.
769
+
770
+ Verified the gate genuinely covers all five rather than trusting the green:
771
+ injecting a type error into each package makes `pnpm type-check --filter <pkg>`
772
+ fail, which was impossible before this change.
773
+
774
+ - Updated dependencies [7b21891]
775
+ - Updated dependencies [0b3be01]
776
+ - Updated dependencies [3c4d935]
777
+ - Updated dependencies [4b1ed7d]
778
+ - Updated dependencies [4b60d2d]
779
+ - Updated dependencies [952b978]
780
+ - Updated dependencies [de5e40c]
781
+ - Updated dependencies [1a03af6]
782
+ - Updated dependencies [3e886eb]
783
+ - Updated dependencies [cfc675e]
784
+ - Updated dependencies [20df08c]
785
+ - Updated dependencies [1767124]
786
+ - Updated dependencies [8ecf5a6]
787
+ - Updated dependencies [af705b9]
788
+ - Updated dependencies [0502a7c]
789
+ - Updated dependencies [7b35e4b]
790
+ - Updated dependencies [8fb1295]
791
+ - Updated dependencies [e16ed2d]
792
+ - Updated dependencies [c6fd752]
793
+ - Updated dependencies [f9bbddb]
794
+ - Updated dependencies [dfd3705]
795
+ - Updated dependencies [c77108c]
796
+ - Updated dependencies [2735de6]
797
+ - Updated dependencies [697cda4]
798
+ - Updated dependencies [c19ac11]
799
+ - Updated dependencies [6dee2cb]
800
+ - Updated dependencies [e05f052]
801
+ - Updated dependencies [0502a7c]
802
+ - Updated dependencies [faad45e]
803
+ - Updated dependencies [09c6a17]
804
+ - Updated dependencies [c7cff19]
805
+ - Updated dependencies [ba73a02]
806
+ - Updated dependencies [cd09a7b]
807
+ - Updated dependencies [f1abf0e]
808
+ - Updated dependencies [f05b84e]
809
+ - Updated dependencies [9b4b952]
810
+ - Updated dependencies [341bfb5]
811
+ - Updated dependencies [2f947e4]
812
+ - Updated dependencies [7d46648]
813
+ - Updated dependencies [9b53d72]
814
+ - Updated dependencies [bb4aa25]
815
+ - Updated dependencies [75f1cdf]
816
+ - Updated dependencies [662bdf9]
817
+ - Updated dependencies [059a052]
818
+ - Updated dependencies [53642d4]
819
+ - Updated dependencies [8aae006]
820
+ - Updated dependencies [c6cfdf1]
821
+ - Updated dependencies [d147a13]
822
+ - Updated dependencies [c6aaed8]
823
+ - Updated dependencies [263f885]
824
+ - Updated dependencies [dc334da]
825
+ - @object-ui/components@17.0.0
826
+ - @object-ui/i18n@17.0.0
827
+ - @object-ui/fields@17.0.0
828
+ - @object-ui/react@17.0.0
829
+ - @object-ui/types@17.0.0
830
+ - @object-ui/core@17.0.0
831
+ - @object-ui/permissions@17.0.0
832
+
3
833
  ## 16.1.0
4
834
 
5
835
  ### Minor Changes
@@ -901,7 +1731,7 @@ parentData, details)` — emits a parent `update` op (index 0) then diffs each
901
1731
  `type:'tabbed'` form view now renders tabbed in the modal too — not just on the
902
1732
  full-page route (#1762). Non-breaking; `FormView.type` enum unchanged.
903
1733
 
904
- Refs objectstack-ai/framework#1890, ADR-0050
1734
+ Refs objectstack-ai/objectstack#1890, ADR-0050
905
1735
 
906
1736
  - 650bd1f: fix(forms/dashboard/related-list): four business-facing rendering fixes found while QA-ing a showcase workspace
907
1737