@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.
- package/CHANGELOG.md +831 -1
- package/README.md +106 -0
- package/dist/DrawerForm.d.ts +1 -0
- package/dist/DrawerForm.d.ts.map +1 -0
- package/dist/EmbeddableForm.d.ts +1 -0
- package/dist/EmbeddableForm.d.ts.map +1 -0
- package/dist/FormAnalytics.d.ts +1 -0
- package/dist/FormAnalytics.d.ts.map +1 -0
- package/dist/FormSection.d.ts +7 -6
- package/dist/FormSection.d.ts.map +1 -0
- package/dist/LineItemsPanel.d.ts +1 -0
- package/dist/LineItemsPanel.d.ts.map +1 -0
- package/dist/MasterDetailForm.d.ts +9 -1
- package/dist/MasterDetailForm.d.ts.map +1 -0
- package/dist/ModalForm.d.ts +7 -2
- package/dist/ModalForm.d.ts.map +1 -0
- package/dist/ObjectForm.d.ts +1 -0
- package/dist/ObjectForm.d.ts.map +1 -0
- package/dist/SplitForm.d.ts +22 -2
- package/dist/SplitForm.d.ts.map +1 -0
- package/dist/TabbedForm.d.ts +13 -1
- package/dist/TabbedForm.d.ts.map +1 -0
- package/dist/WizardForm.d.ts +14 -1
- package/dist/WizardForm.d.ts.map +1 -0
- package/dist/autoLayout.d.ts +1 -0
- package/dist/autoLayout.d.ts.map +1 -0
- package/dist/deriveMasterDetail.d.ts +1 -0
- package/dist/deriveMasterDetail.d.ts.map +1 -0
- package/dist/fieldGroups.d.ts +1 -0
- package/dist/fieldGroups.d.ts.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1609 -1326
- package/dist/index.umd.cjs +1 -1
- package/dist/masterDetailTx.d.ts +1 -0
- package/dist/masterDetailTx.d.ts.map +1 -0
- package/dist/occSave.d.ts +78 -0
- package/dist/occSave.d.ts.map +1 -0
- package/dist/sanitize.d.ts +1 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sectionFields.d.ts +10 -2
- package/dist/sectionFields.d.ts.map +1 -0
- package/dist/successBehavior.d.ts +1 -0
- package/dist/successBehavior.d.ts.map +1 -0
- 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/
|
|
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
|
|