@object-ui/plugin-form 17.6.0 → 17.7.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 +4187 -0
- package/README.md +376 -44
- package/dist/DrawerForm.d.ts +30 -2
- package/dist/DrawerForm.d.ts.map +1 -1
- package/dist/EmbeddableForm.d.ts.map +1 -1
- package/dist/FormAnalytics.d.ts.map +1 -1
- package/dist/LineItemsPanel.d.ts +6 -3
- package/dist/LineItemsPanel.d.ts.map +1 -1
- package/dist/MasterDetailForm.d.ts +31 -10
- package/dist/MasterDetailForm.d.ts.map +1 -1
- package/dist/ModalForm.d.ts +48 -4
- package/dist/ModalForm.d.ts.map +1 -1
- package/dist/ObjectForm.d.ts.map +1 -1
- package/dist/SplitForm.d.ts +37 -2
- package/dist/SplitForm.d.ts.map +1 -1
- package/dist/TabbedForm.d.ts +65 -3
- package/dist/TabbedForm.d.ts.map +1 -1
- package/dist/WizardForm.d.ts +143 -38
- package/dist/WizardForm.d.ts.map +1 -1
- package/dist/autoLayout.d.ts +12 -3
- package/dist/autoLayout.d.ts.map +1 -1
- package/dist/closedAffordanceNotice.d.ts +14 -0
- package/dist/closedAffordanceNotice.d.ts.map +1 -0
- package/dist/customFieldsMerge.d.ts +29 -0
- package/dist/customFieldsMerge.d.ts.map +1 -0
- package/dist/deriveMasterDetail.d.ts +32 -8
- package/dist/deriveMasterDetail.d.ts.map +1 -1
- package/dist/fieldGroups.d.ts +176 -0
- package/dist/fieldGroups.d.ts.map +1 -1
- package/dist/fieldWriteGate.d.ts +116 -0
- package/dist/fieldWriteGate.d.ts.map +1 -0
- package/dist/flatFields.d.ts +11 -2
- package/dist/flatFields.d.ts.map +1 -1
- package/dist/formChrome.d.ts +55 -0
- package/dist/formChrome.d.ts.map +1 -0
- package/dist/index.d.ts +124 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3435 -2093
- package/dist/index.umd.cjs +1 -1
- package/dist/initialRecord.d.ts +72 -0
- package/dist/initialRecord.d.ts.map +1 -0
- package/dist/loadFailure.d.ts +28 -0
- package/dist/loadFailure.d.ts.map +1 -0
- package/dist/masterDetailTx.d.ts.map +1 -1
- package/dist/occSave.d.ts +6 -1
- package/dist/occSave.d.ts.map +1 -1
- package/dist/recordInvalidation.d.ts +33 -0
- package/dist/recordInvalidation.d.ts.map +1 -0
- package/dist/recordReadGate.d.ts +34 -0
- package/dist/recordReadGate.d.ts.map +1 -0
- package/dist/sanitize.d.ts +187 -5
- package/dist/sanitize.d.ts.map +1 -1
- package/dist/schemaDefaults.d.ts +27 -6
- package/dist/schemaDefaults.d.ts.map +1 -1
- package/dist/sectionFields.d.ts +56 -2
- package/dist/sectionFields.d.ts.map +1 -1
- package/dist/sectionGroups.d.ts +62 -0
- package/dist/sectionGroups.d.ts.map +1 -0
- package/dist/sectionPredicateDiagnostic.d.ts +38 -0
- package/dist/sectionPredicateDiagnostic.d.ts.map +1 -0
- package/dist/submitTarget.d.ts +119 -0
- package/dist/submitTarget.d.ts.map +1 -0
- package/dist/successBehavior.d.ts +44 -25
- package/dist/successBehavior.d.ts.map +1 -1
- package/dist/thankYouRedirectCountdown.d.ts +1 -1
- package/dist/uploadGate.d.ts +35 -0
- package/dist/uploadGate.d.ts.map +1 -0
- package/dist/writePayload.d.ts +68 -0
- package/dist/writePayload.d.ts.map +1 -0
- package/package.json +12 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,4192 @@
|
|
|
1
1
|
# @object-ui/plugin-form
|
|
2
2
|
|
|
3
|
+
## 17.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e686f4d: A record form can no longer be saved while an upload is still in flight (objectui#10166).
|
|
8
|
+
|
|
9
|
+
A `file` / `image` value only becomes its fileId once the presigned upload settles. Until
|
|
10
|
+
now a record form had no notion of upload state at all, so a Save pressed during that
|
|
11
|
+
window wrote the record WITHOUT the attachment — and reported success. The user picked the
|
|
12
|
+
file, saw it listed and saved; there was no error, no warning, and the record looked saved.
|
|
13
|
+
Whoever noticed did so later, looking at a record that should have a file and does not.
|
|
14
|
+
|
|
15
|
+
`onUploadingChange` (ADR-0059) already carried the signal, and had exactly one consumer,
|
|
16
|
+
`ActionParamDialog`. It could not have a second: that prop is per-widget, and a record form
|
|
17
|
+
hands a `fields` array to the `form` node renderer and never touches a widget, so there is
|
|
18
|
+
no point in the chain where it can attach a callback — and its upload controls can sit
|
|
19
|
+
inside a section, a tab or a line-items subform.
|
|
20
|
+
|
|
21
|
+
`@object-ui/fields` therefore publishes the AGGREGATION beside the prop: `useUploadingScope`
|
|
22
|
+
(the host's "is anything below me uploading") and `UploadingScopeProvider`.
|
|
23
|
+
`useUploadingSignal` — also exported now, for widgets authored outside this repo — feeds
|
|
24
|
+
both sinks from the one call it already made, so the per-widget prop and the scope cannot
|
|
25
|
+
disagree, and a host that mounts no provider is unaffected. A widget that unmounts
|
|
26
|
+
mid-upload releases its slot, so a collapsing section cannot wedge Save shut.
|
|
27
|
+
|
|
28
|
+
Nesting CHAINS rather than shadows: an inner scope gates its own Save AND reports itself to
|
|
29
|
+
the scope above. The direction is forced by `MasterDetailForm`, whose Save persists parent
|
|
30
|
+
and children in one batch while its rows are edited by nested `ObjectForm`s — a gate that
|
|
31
|
+
saw only the parent's uploads would refuse nothing while a child's attachment was in flight
|
|
32
|
+
and would still read as coverage.
|
|
33
|
+
|
|
34
|
+
Every submit owner in `@object-ui/plugin-form` is gated: `ObjectForm`, `ModalForm`,
|
|
35
|
+
`DrawerForm`, `SplitForm`, `TabbedForm`, `WizardForm`, `MasterDetailForm`, and
|
|
36
|
+
`EmbeddableForm` through the `ObjectForm` it hosts. While an upload is in flight each
|
|
37
|
+
refuses the submit (which is also the keyboard-submit guard), labels Save "Uploading…", and
|
|
38
|
+
renders the reason as a sentence — `form.uploadInFlight`, new in all ten locale packs. The
|
|
39
|
+
hosts that own their Save button — `ModalForm`, `DrawerForm`, `MasterDetailForm`, and
|
|
40
|
+
`WizardForm`'s final step — disable it as well; the flat `ObjectForm`, `SplitForm` and
|
|
41
|
+
`TabbedForm` paths submit through the `form` node renderer in `@object-ui/components`, which
|
|
42
|
+
exposes no per-button disable, so there the refusal plus the label and the notice are what
|
|
43
|
+
the user meets.
|
|
44
|
+
|
|
45
|
+
`WizardForm` is gated on its FINAL commit only. Moving between steps writes nothing, so
|
|
46
|
+
`Next` is deliberately untouched — but note that leaving a step unmounts its widgets, so an
|
|
47
|
+
upload in flight is released by the unmount and its value never reaches the record. That
|
|
48
|
+
loss predates this change and is not addressed by it.
|
|
49
|
+
- d5cb261: fix(plugin-form): `customFields` members render inside explicit `sections` on the drawer, modal, tabbed, wizard and split arms
|
|
50
|
+
|
|
51
|
+
The registered description of `object-form.customFields` is one sentence for
|
|
52
|
+
every `formType`: "Field definitions merged over the set generated from object
|
|
53
|
+
metadata." With explicit `sections`, an `object-form` with `formType: 'drawer'`,
|
|
54
|
+
`'modal'`, `'tabbed'`, `'wizard'` or `'split'` (and a `DrawerForm`, `ModalForm`,
|
|
55
|
+
`TabbedForm`, `WizardForm` or `SplitForm` mounted directly) built every field a
|
|
56
|
+
section names from the object schema alone, so a member naming that field was
|
|
57
|
+
dropped: its label, its `required` and the rest of its definition never reached
|
|
58
|
+
the form. The default arm already drew the member.
|
|
59
|
+
|
|
60
|
+
Those five arms now take a section field's definition from the member naming
|
|
61
|
+
it, through the same lookup the default arm's merge uses; a field no member
|
|
62
|
+
names is built from the object schema as before. This holds for a bare field
|
|
63
|
+
name and for a spec `{ field }` entry, whose own overrides still apply on top
|
|
64
|
+
of the member. Over a member, the widget changes only when the entry restates
|
|
65
|
+
`type`. A section naming a field that only a member supplies now renders that
|
|
66
|
+
member, where it used to render a plain text input labelled with the field
|
|
67
|
+
name. A member that no section lists is still not drawn when `sections` are
|
|
68
|
+
given, as on the default arm. `TabbedFormSchema`, `WizardFormSchema` and
|
|
69
|
+
`SplitFormSchema` now declare `customFields`, as `DrawerFormSchema` and
|
|
70
|
+
`ModalFormSchema` already did.
|
|
71
|
+
|
|
72
|
+
Unchanged: the rules for what a section entry may override, which still differ
|
|
73
|
+
between the default arm and the other five.
|
|
74
|
+
- 37140f4: refactor(plugin-form)!: retire the `form-analytics` node type key (objectui#10859, batch 8 phase 2b)
|
|
75
|
+
|
|
76
|
+
**BREAKING (authoring):** the plugin no longer registers `form-analytics` (and with it `plugin-form:form-analytics`). `objectui validate` refused a `form-analytics` node at `type`, and nothing in this repository, its examples or objectstack authored it. A node authored `type: "form-analytics"` now renders the "Unknown component type" panel. `FormAnalytics` stays a named export.
|
|
77
|
+
|
|
78
|
+
Migration:
|
|
79
|
+
|
|
80
|
+
- `{ "type": "form-analytics", "formId": …, "formTitle": …, "metrics": … }` → mount `FormAnalytics` directly with the same three props.
|
|
81
|
+
|
|
82
|
+
**Clause-②: yes** — a registration leaves the runtime (narrowing), released as `minor` with this banner.
|
|
83
|
+
- 8aa68b1: A master-detail form whose `title`, `submitText` or `cancelText` is a per-locale map now shows the viewer's language instead of crashing or toasting "[object Object] saved" (objectui#10935).
|
|
84
|
+
|
|
85
|
+
**What it was.** `@objectstack/spec` types these three members of `object-master-detail-form` as `I18nLabel`, a plain string or an inline per-locale map such as `{ en: 'Purchase order', 'zh-CN': '采购单' }`, and objectui's validator accepts such a map in the node's `properties` (objectui#10927). `MasterDetailForm` read all three raw. `submitText` and `cancelText` are Button children, so a map threw "Objects are not valid as a React child" and took the form down. A map `title` went into the built-in edit-save toast as "[object Object] saved".
|
|
86
|
+
|
|
87
|
+
**What changed, in observable terms.**
|
|
88
|
+
|
|
89
|
+
- Each of the three is resolved with `pickLocalized` against the active UI language (`useObjectTranslation().language`), the source `ObjectMetricWidget` resolves its own `I18nLabel` members against. A map shows the entry for the viewer's language, then falls back the way `pickLocalized` does.
|
|
90
|
+
- A plain string renders exactly as authored.
|
|
91
|
+
- With nothing authored, this change leaves the defaults as they were: 'Create' or 'Save' on the Save button, 'Cancel' on the Cancel button, and 'Created' or 'Saved' in the built-in save toast. objectui#11039 moves those defaults, and the " saved" the toast puts after an authored `title`, into the locale packs, so they follow the session language.
|
|
92
|
+
- On the two buttons, an authored empty string, or a map with no string entry, now shows the default. An authored empty string used to render an empty button; a map with no string entry used to throw as a React child.
|
|
93
|
+
- The parent `ObjectForm` is handed the resolved `title` string, the `string` its `ObjectFormSchema.title` declares.
|
|
94
|
+
- The block's registration declares both arms for the three keys, `type: ['string', 'object']`, with descriptions that teach the per-locale map. The manifest built from `ComponentRegistry.getPublicConfigs()` therefore no longer makes `validateTree` report `type-mismatch` on a locale map for these keys. A value that matches neither arm, such as a number, is still reported.
|
|
95
|
+
|
|
96
|
+
**Types.** `MasterDetailFormSchema.title`, `.submitText` and `.cancelText` widen from `string` to `I18nLabel` (from `@object-ui/types`), matching the spec row. Code that writes these members compiles unchanged. Code that reads one of them and uses it as a `string` no longer compiles: resolve it first, for example with `pickLocalized` from `@object-ui/i18n`.
|
|
97
|
+
|
|
98
|
+
**Clause-②: yes** — three members of `MasterDetailFormSchema`, which the package entry exports, widen from `string` to `I18nLabel`, and the registration's `inputs` for the same three keys widen from `'string'` to `['string', 'object']`. Nothing that was accepted before is refused now.
|
|
99
|
+
- 19f484f: `FormulaFieldMetadata` declares `@objectstack/spec`'s `expression` in place of `formula`, and three readers of a lookup's display pointer read the spec's `displayField` alone (objectui#11070, round 6). Both retired spellings go at once, with no alias.
|
|
100
|
+
|
|
101
|
+
- **Types.** `FormulaFieldMetadata.formula` is removed. `FormulaFieldMetadata.expression` is `FieldSchema`'s `expression` by reference: a CEL source string, or the spec's `{ dialect, source, … }` envelope. Nothing in ObjectUI read the removed member through the type. `FieldSchema` refuses `formula` by name on every field type, with a rename hint to `expression`.
|
|
102
|
+
- **Form payloads.** `sanitizeFormData` (`@object-ui/plugin-form`) no longer treats a `formula` key as a "computed" flag. Every `type: 'formula'` field is still dropped from the payload by its type, as before. Only a field of some other type that carries `formula` changes: its value is now sent like any writable field's. The spec refuses such a definition at publish, so a served one cannot carry it.
|
|
103
|
+
- **Display pointer.** `deriveColumns` and `hydrateColumns` (`@object-ui/plugin-form`, the master-detail grid columns), `ObjectChart`'s group-by labels (`@object-ui/plugin-charts`) and the action-param resolver (`@object-ui/app-shell`) read `displayField`, then `reference_field`. None of them reads `display_field` any more.
|
|
104
|
+
- **A fix for spec-spelled lookups in master-detail grids.** `deriveColumns` and `hydrateColumns` read `display_field || reference_field` before, with no `displayField` leg. A lookup that declared only `displayField`, which is the spec's spelling, got no display pointer on its grid column. It now gets one.
|
|
105
|
+
|
|
106
|
+
A definition served through `ObjectStackAdapter.getObjectSchema` or `MetadataProvider` loses nothing: the ingestion pass (objectui#7650) stamps a stored `display_field` onto `displayField` before any of these readers sees it. Measured with a lookup carrying `display_field: 'title'` served through `ObjectStackAdapter.getObjectSchema`: the master-detail column (`deriveColumns` and `hydrateColumns`), the chart's axis label and the action param all resolve the `title` column before and after this change.
|
|
107
|
+
|
|
108
|
+
## ⚠️ BREAKING, priced as minor under the fixed group's version policy
|
|
109
|
+
|
|
110
|
+
TypeScript that writes `formula` on a `FormulaFieldMetadata` no longer compiles (an excess-property error naming the key). Rename it to `expression` and write the formula in CEL against the record, for example `record.quantity * record.unit_price`.
|
|
111
|
+
|
|
112
|
+
At runtime, a lookup whose display pointer is spelled only `display_field` loses it wherever the ingestion pass does not run first. Measured before and after this change, on a lookup with `display_field: 'title'` handed to the readers directly:
|
|
113
|
+
|
|
114
|
+
- **`deriveColumns` / `hydrateColumns` with a `childSchema` that did not come through the ingestion pass** (an external caller, or a master-detail form whose `DataSource` is not `ObjectStackAdapter`): the column's `displayField` was `title`. It is now absent.
|
|
115
|
+
- **`ObjectChart` on a `DataSource` other than `ObjectStackAdapter`**, grouped by that lookup: the axis label came from the `title` column. It now comes from the `name` column, the generic fallback.
|
|
116
|
+
- **The action-param resolver, when a host passes its own unfolded `objects`** (for example through `RecordDetailView`'s `objects` prop): the lookup param's `displayField` was `title`. It is now absent.
|
|
117
|
+
|
|
118
|
+
The same lookups spelled `displayField` resolve the `title` column in all three after this change. Before it, the chart and the action param already did, and the master-detail columns did not (the fix above).
|
|
119
|
+
|
|
120
|
+
**Fix:** spell the pointer `displayField`, or serve the definition through `ObjectStackAdapter`.
|
|
121
|
+
- 0a3e540: The grid field's `sort_field` is declared, and a master-detail detail's sort field is derived only (objectui#11070, round 9).
|
|
122
|
+
|
|
123
|
+
`GridField` stamps each row with its index in the field `sort_field` names, on every change, so the order a drag-reorder leaves is saved with the rows. It read that key while no face declared it. Its one producer is `MasterDetailForm`, which hands the grid the sort field `deriveDetail` picks from the child object: the first of its fields named `position`, `sort_order`, `sequence`, `line_no`, `line_number` or `sort`. A detail could also override that pick with an authored `sortField`, which nothing wrote in either repository and no spec key carries.
|
|
124
|
+
|
|
125
|
+
- **`sort_field` is declared (`@object-ui/types`).** `GridFieldMetadata` declares `sort_field?: string`, documented as the CHILD field stamped with each row's index. `GridField` now reads its config as `GridFieldMetadata` alone, so every key it reads is declared there (`@object-ui/fields`; no runtime change).
|
|
126
|
+
- **The authored override is retired (`@object-ui/plugin-form`).** `MasterDetailDetailConfig` no longer has a `sortField` member, and `MasterDetailForm` no longer reads one. The sort field the grid receives is the derived one, carried on the form's internal per-detail state.
|
|
127
|
+
|
|
128
|
+
**Clause-②: yes (narrowing).** The published `GridFieldMetadata` face widens by one optional member. The published `MasterDetailDetailConfig` face narrows by one member, and what `MasterDetailForm` honours narrows with it.
|
|
129
|
+
|
|
130
|
+
## ⚠️ BREAKING, priced as minor under the fixed group's version policy
|
|
131
|
+
|
|
132
|
+
- **TypeScript.** A `MasterDetailDetailConfig` literal that writes `sortField` is a compile error. Fix: delete it. The sort field comes from the child object; name the child's position field `position` (or another of the names above) so the derivation finds it.
|
|
133
|
+
- **Rendering.** A detail written with `sortField` (through a cast, or in a document the compiler never saw) renders as if the key were absent. On a detail the form derives, the grid stamps the child's sort-named field; on a fully configured detail (relationship field set and every column typed), which loads no child schema, the grid stamps none.
|
|
134
|
+
- f61dab1: `reference` is now the only spelling ObjectUI writes or reads for a relational field's target object (objectui#11070, round 4, under the objectui#6837 ruling: 「objectui不是前端的项目吗?后端的元数据只要对,前端按协议执行就行了呀」).
|
|
135
|
+
|
|
136
|
+
- **Types.** `LookupFieldMetadata`, `MasterDetailFieldMetadata` and `DetailViewField` (with its zod mirror `DetailViewFieldSchema`) declare `reference` and no longer declare `reference_to`. On the two field metadata types the member is typed by reference to `@objectstack/spec`'s `FieldSchema.reference`. `@object-ui/plugin-form`'s `FieldDefaultsSchemaLike` drops its `reference_to` member the same way.
|
|
137
|
+
- **Readers.** `LookupField`, `UserField`, `LookupCellRenderer`, `UserCellRenderer`, the inline editor's reference fallback, the form's `current_user` seeding and the inline-subform parent lookup read `reference` alone.
|
|
138
|
+
- **Emitters.** Every in-repo producer that builds a field definition or a widget `field` prop writes `reference`: the action-param dialog (`paramToField`), the bulk-action dialog, the record detail page, drawer, footer, related list and synthesised page, the gallery card, the form's section-field override and the flow designer's reference picker. The grid's and the dashboard's relational copy sets carry `reference` and no longer copy `reference_to`.
|
|
139
|
+
- **Ingestion.** `normalizeFieldReferenceKeys` (behind `ObjectStackAdapter.getObjectSchema` and `MetadataProvider`) still folds a legacy `reference_to` / `referenceTo` onto `reference` when `reference` is absent, and still warns in dev. It no longer stamps `reference_to` onto every relational definition, and it still never drops or overwrites a key.
|
|
140
|
+
|
|
141
|
+
`@objectstack/spec`'s `FieldSchema` refuses `reference_to` by name, and objectstack#13847 rewrites stored ones on the serve path and in `os migrate meta`, so a definition served by an ObjectStack backend is unaffected.
|
|
142
|
+
|
|
143
|
+
## ⚠️ BREAKING for a host that hands `reference_to` to the widgets directly
|
|
144
|
+
|
|
145
|
+
The type change is a compile error for TypeScript that writes `reference_to` on `LookupFieldMetadata`, `MasterDetailFieldMetadata` or `DetailViewField`: rename it to `reference`.
|
|
146
|
+
|
|
147
|
+
At runtime the break reaches exactly one kind of host: one that serves object definitions spelling the target only as `reference_to` through a `DataSource` other than `ObjectStackAdapter`, or that passes such a definition straight into `LookupField`, `UserField` or a cell renderer. Those definitions never pass the ingestion fold. Measured with an object-bound lookup field on `ObjectForm` and a lookup column on `ObjectGrid`, both fed by a hand-written `DataSource` whose `getObjectSchema` returns `{ type: 'lookup', reference_to: 'account' }`:
|
|
148
|
+
|
|
149
|
+
- before this change, opening the picker queried `account` and the cell resolved the record's name;
|
|
150
|
+
- after it, the picker has no object to query (no `find` call is made) and the cell shows the raw id beside the unresolved-reference marker.
|
|
151
|
+
|
|
152
|
+
The same definition served through `ObjectStackAdapter.getObjectSchema` still works, before and after: the fold adds `reference`, the picker queries `account`, the cell resolves the name, and the dev warning names the field. **Fix:** spell the target `reference` in the definition your `DataSource` serves.
|
|
153
|
+
|
|
154
|
+
⚠️ **Dated note, 2026-09-30 — the break reaches more than the paragraph above states — objectui#11070.** Two corrections from the contract review of this change, appended rather than edited in; neither changes what ships.
|
|
155
|
+
|
|
156
|
+
- "The break reaches exactly one kind of host" above is too narrow. Host code that read `reference_to` off a definition after ingestion — from `ObjectStackAdapter.getObjectSchema()` or `useMetadata().objects` — also loses that key, because the ingestion pass no longer stamps it (the **Ingestion** bullet above states the fact, and `reference_to` was never a declared member of those definitions). **Fix:** read `reference`.
|
|
157
|
+
- The measured break above is a `lookup` field's. A `user` field handed a `reference_to`-only definition directly does not end at "no query": `UserField` falls back to `sys_user`, so its picker queries `sys_user` rather than the object `reference_to` named. **Fix:** spell the target `reference` there too.
|
|
158
|
+
|
|
159
|
+
The text above is kept as the reading of this change.
|
|
160
|
+
- dd5ff19: The text-family field types and every length reader use `@objectstack/spec`'s own `minLength` / `maxLength`, and the snake_case `min_length` / `max_length` are retired at once, with no alias (objectui#11070, the text-family round). Two switches nothing read, `auto_compute` and `auto_update`, are retired too.
|
|
161
|
+
|
|
162
|
+
- **Types.** `TextFieldMetadata` and `TextareaFieldMetadata` declare `minLength` and `maxLength`; `MarkdownFieldMetadata`, `HtmlFieldMetadata`, `RichtextFieldMetadata`, `EmailFieldMetadata` and `UrlFieldMetadata` declare `maxLength`. Each is `FieldSchema`'s member by reference, and each replaces the snake_case member the type declared before. `FormulaFieldMetadata.auto_compute` and `SummaryFieldMetadata.auto_update` are removed: nothing in ObjectUI read either.
|
|
163
|
+
- **Readers.** The form renderer's built-in `input`, `textarea` and fallback branches, the form's validation rules (`buildValidationRules`), `TextAreaField`, `RichTextField`, `EmbeddableForm`'s default long-text cap and `ObjectForm`'s length forwarding read `maxLength` / `minLength` alone. The built-in branches no longer strip a `max_length` key off the element: nothing reads it, so it is treated like any other undeclared key.
|
|
164
|
+
- **Dashboard.** `ObjectDataTable` and `RecordDetailDrawer` no longer copy a lookup's `display_field` onto their internal cell meta. The lookup cell has read the display pointer as `displayField` since objectui#7155, so nothing changes on screen.
|
|
165
|
+
|
|
166
|
+
`FieldSchema` refuses `min_length`, `max_length`, `auto_compute` and `auto_update` by name, so no spec-compliant producer writes them. A definition served through `ObjectStackAdapter.getObjectSchema` or `MetadataProvider` is unaffected even if it was stored with a snake_case length: the ingestion pass folds `max_length` / `min_length` onto `maxLength` / `minLength` before any reader sees it. Measured with an object-bound `textarea` field declaring `max_length: 111` on `ObjectForm`, served through `ObjectStackAdapter`: the editor has `maxlength="111"` and a `0/111` counter before and after this change.
|
|
167
|
+
|
|
168
|
+
## ⚠️ BREAKING, priced as minor under the fixed group's version policy
|
|
169
|
+
|
|
170
|
+
The type change is a compile error for TypeScript that writes `min_length`, `max_length`, `auto_compute` or `auto_update` on one of these types (an excess-property error naming the key). Rename the first two to `minLength` / `maxLength`, and delete the other two.
|
|
171
|
+
|
|
172
|
+
At runtime, a length spelled only `max_length` or `min_length` now applies nowhere the ingestion fold does not run first. Measured before and after this change:
|
|
173
|
+
|
|
174
|
+
- **A hand-authored `form` document handed straight to the renderer.** A built-in `input` field with `max_length: 5` and a `textarea` field with `max_length: 7` rendered `maxlength="5"` and `maxlength="7"`. They now render no `maxlength`, and the element carries `max_length` as an inert attribute, as it would any key the renderer does not read. The same fields spelled `maxLength` render identically before and after.
|
|
175
|
+
- **An object definition served to `ObjectForm` by a `DataSource` other than `ObjectStackAdapter`.** A `textarea` field with `max_length: 111` had `maxlength="111"` and a `0/111` counter; it now has neither. The same field spelled `maxLength` is unchanged.
|
|
176
|
+
- **Submit-time validation.** `buildValidationRules` on a field carrying only `max_length: 9` / `min_length: 2` produced `maxLength` and `minLength` rules; it now produces no rules.
|
|
177
|
+
- **`EmbeddableForm`.** A custom `textarea` field carrying `max_length: 40` used to opt out of the default cap and got no `maxLength`; it now gets the 5000-character long-text default.
|
|
178
|
+
|
|
179
|
+
**Fix:** spell the bounds `maxLength` / `minLength`.
|
|
180
|
+
- 17dc167: `object-form.layout` publishes only `vertical` and `horizontal`. This is objectui#11168 slice 3 and delivers objectui#7759 group C. `@objectstack/spec` 17.5.0 retired `inline` and `grid` from the form layout enum (objectstack#20221). The authored `object-form` arm, which reads the spec row by reference, already refused both. The registration still published them, so the page validator passed values `objectui validate` refuses.
|
|
181
|
+
|
|
182
|
+
⚠️ This narrows a published input. Measured through the real `SchemaRenderer` before the change, `inline` and `grid` rendered byte-identical to `vertical` on the simple, tabbed, wizard and split layouts. Nothing that rendered is lost. A stored document carrying either value is refused at authoring; write `vertical` (the default) or `horizontal`. The fixed group ships the change as `minor`.
|
|
183
|
+
|
|
184
|
+
The fold that mapped `inline` / `grid` to `vertical` is retired with them, in `ObjectForm` (simple, drawer and modal routes), `DrawerForm` and `ModalForm`. `layout` now passes through unfolded, and an absent `layout` still draws as `vertical`.
|
|
185
|
+
- 072b7e8: `record:line_items` now publishes every key of its `@objectstack/spec` 17.6.0
|
|
186
|
+
row (objectui#11536). Each key was decided by measuring it through
|
|
187
|
+
`SchemaRenderer` and the block's registration, per the objectui#11111 ruling:
|
|
188
|
+
declare what the panel honours, leave out what it does not. All ten keys the
|
|
189
|
+
row added over the registration move the panel, so all ten are declared.
|
|
190
|
+
|
|
191
|
+
Newly published, so the SDUI manifest, the JSX intrinsics and the page
|
|
192
|
+
validator accept them instead of reporting `unknown-prop`:
|
|
193
|
+
|
|
194
|
+
- `parentId` and `recordId`: the parent record whose lines are loaded and
|
|
195
|
+
saved. `parentId` outranks `recordId`, and both outrank the record the page
|
|
196
|
+
shows.
|
|
197
|
+
- `parentObject`: the parent object the line total is written to on Save,
|
|
198
|
+
together with `totalField`. It outranks the object of the record the page
|
|
199
|
+
shows.
|
|
200
|
+
- `title`: the panel heading, a plain string.
|
|
201
|
+
- `readonly`: lines are shown without editing (no Save, no row actions).
|
|
202
|
+
- `minRows` / `maxRows`: Remove row is disabled at the floor; Add line and
|
|
203
|
+
Duplicate row are disabled at the cap.
|
|
204
|
+
- `filter`, `sort` and `limit`: the additional criteria (AND-combined behind
|
|
205
|
+
the parent relationship, never replacing it), the load order and the row
|
|
206
|
+
cap (default 500), each read as a top-level key as well as through a
|
|
207
|
+
`dataSource` binding.
|
|
208
|
+
|
|
209
|
+
Nothing changes at runtime: the panel already read every one of these keys.
|
|
210
|
+
- 9c74902: Retire the form-view section `className` / `gridClassName` reads (objectstack#13626,
|
|
211
|
+
maintainer ruling 2026-09-01, director decision batch C).
|
|
212
|
+
|
|
213
|
+
**Breaking, deliberately.** A `className` or `gridClassName` authored on a form-view
|
|
214
|
+
section no longer has any effect. Before this change an authored `gridClassName`
|
|
215
|
+
reached the section's field-grid `<div>` and an authored `className` reached the
|
|
216
|
+
section wrapper / divider header; both are now dropped at the renderer.
|
|
217
|
+
|
|
218
|
+
The two keys sit on the SDUI-only side of the authorable boundary: `@objectstack/spec`
|
|
219
|
+
deliberately does not declare either on the form-view/section surface (its
|
|
220
|
+
`component.zod.ts` says so in as many words) and the authorable-surface ledger carries
|
|
221
|
+
no entry for them. The renderer nevertheless reached them off the parsed view through
|
|
222
|
+
`as any` at seven sites — the boundary declared on one side and crossed on the other,
|
|
223
|
+
with the two repos each deliberate and in opposite directions.
|
|
224
|
+
|
|
225
|
+
Declaring the keys instead was weighed and **not** adopted: it would formally invite
|
|
226
|
+
free Tailwind strings into authored metadata, the exact class the boundary exists to
|
|
227
|
+
keep out — and per ADR-0065 / ADR-0080 (rev. 2026-06-30) utility classNames in runtime
|
|
228
|
+
metadata are never scanned by the build-time Tailwind, so they silently produce no CSS
|
|
229
|
+
anyway. Declaring them would have published a styling surface whose most obvious use
|
|
230
|
+
does nothing. If per-view styling becomes a real product need it gets an explicit
|
|
231
|
+
controlled token surface, not two leaked keys.
|
|
232
|
+
|
|
233
|
+
**Migration.** Nothing in the measured corpora has to change. A census across the
|
|
234
|
+
objectstack corpus, this repo's corpus, and the hotcrm application found **zero**
|
|
235
|
+
authored uses of either key on a form-view section (201 authored section nodes reached,
|
|
236
|
+
0 carrying either key). If you author them in your own metadata, move the styling to
|
|
237
|
+
the host application's own CSS, or to the form ROOT `className` — which is a different
|
|
238
|
+
key on a different node and is **unaffected** by this change.
|
|
239
|
+
|
|
240
|
+
Six sites in `ObjectForm` (the tabbed / wizard / split / drawer / modal section maps and
|
|
241
|
+
the stacked section-divider) and one in `DrawerForm` (its own divider) stop copying the
|
|
242
|
+
keys. The omission is pinned behaviourally across all seven arms rather than by a source
|
|
243
|
+
grep, because `ObjectFormSection` still declares both keys — so a later uncast
|
|
244
|
+
`className: s.className` would type-check and silently restore consumption.
|
|
245
|
+
- 636b236: `navigateOnSuccess` is relative-only, escapes the interpolated id, and is deprecated in favour of `submitBehavior`
|
|
246
|
+
|
|
247
|
+
The url contract for this key was undeclared: it was same-origin-guarded (so a same-origin
|
|
248
|
+
ABSOLUTE value was accepted), it interpolated `{id}` / `{recordId}` without escaping the
|
|
249
|
+
substituted value, and nothing said which of those was intended. The maintainer ruled it on
|
|
250
|
+
2026-08-17: `navigateOnSuccess` is the pre-ruling ancestor of the `submitBehavior` family
|
|
251
|
+
rather than a second dialect, so as a compat alias it runs under the semantics
|
|
252
|
+
objectstack#7496 ruled for that family.
|
|
253
|
+
|
|
254
|
+
**Relative paths only.** A same-origin absolute such as `https://own-host/record/{id}` is
|
|
255
|
+
now refused like any other out-of-contract value, rather than accepted and navigated at
|
|
256
|
+
browser level. The destination is authored metadata, which is exactly where an address
|
|
257
|
+
somebody else chose gets copied in. Cross-origin and protocol-relative values were already
|
|
258
|
+
refused and still are; every relative shape that worked before still works.
|
|
259
|
+
|
|
260
|
+
**The interpolated id is URL-escaped.** `/r/{id}` with an id of `a/b c` resolved to
|
|
261
|
+
`/r/a/b c`, silently growing a path segment, and a template of `{id}` let the id become the
|
|
262
|
+
whole destination. The substituted value now goes through `encodeURIComponent`, so a token
|
|
263
|
+
is a value in the path and never a way to add path structure. The template is the author's
|
|
264
|
+
and is untouched — only the id, which is data read off the written record, is escaped.
|
|
265
|
+
|
|
266
|
+
Both halves are needed and neither implies the other: relative-only is a rule about where a
|
|
267
|
+
destination starts, so it cannot see structure injected further along; escaping runs only on
|
|
268
|
+
the substituted value, so it cannot see an absolute the author wrote out.
|
|
269
|
+
|
|
270
|
+
This can only narrow what is reachable. Every destination the key now accepts is a relative
|
|
271
|
+
reference, and a relative reference cannot carry an authority, so it was already accepted by
|
|
272
|
+
the same-origin guard this replaces — no value that was refused is now followed. With every
|
|
273
|
+
accepted destination relative, the browser-level `window.location.assign` fallback at both
|
|
274
|
+
call sites became unreachable and was removed; an accepted destination goes to the injected
|
|
275
|
+
navigation seam, and the absent-seam fallback inside the shared hook is unchanged.
|
|
276
|
+
|
|
277
|
+
**Deprecation.** `navigateOnSuccess` is marked `@deprecated` in favour of `submitBehavior`,
|
|
278
|
+
which already takes precedence over it and carries the richer `{{record.field_name}}`
|
|
279
|
+
interpolation. The `{id}` / `{recordId}` dialect keeps working for forms that already
|
|
280
|
+
declare it — the ruling converges the documentation and the semantics, not the spelling.
|
|
281
|
+
- 7a72422: Publish the create-payload rule from `@object-ui/plugin-form`'s entry, so a
|
|
282
|
+
second form renderer can call it instead of composing it by hand
|
|
283
|
+
(objectui#6059).
|
|
284
|
+
|
|
285
|
+
Newly importable from `@object-ui/plugin-form` — two functions, nothing else:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { omitServerResolvedDefaults, isRequiredInForm } from '@object-ui/plugin-form';
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
- `omitServerResolvedDefaults(values, objectSchema)` — drop the keys a CREATE
|
|
292
|
+
payload must leave to the producer: a field whose declared `defaultValue` is a
|
|
293
|
+
runtime instruction (`NOW()` / `current_user`, or a CEL envelope) and whose
|
|
294
|
+
submitted value is empty. `ObjectQL.applyFieldDefaults` resolves a declaration
|
|
295
|
+
only for a field that arrives absent or null, so submitting a blank stores
|
|
296
|
+
`''` and silently defeats it. **Create-only** — the caller keeps the mode gate.
|
|
297
|
+
- `isRequiredInForm(field, isCreateForm)` — the `required` a form should
|
|
298
|
+
enforce, given the mode. Published as the pair's other half on purpose:
|
|
299
|
+
excusing a server-owned field from `required` and then submitting the key
|
|
300
|
+
anyway is not half a fix, it is no fix.
|
|
301
|
+
|
|
302
|
+
Both are pure functions over plain data (no React, no registry). The rest of
|
|
303
|
+
`schemaDefaults.ts` — `seedCreateValues`, `schemaDefaultValues`,
|
|
304
|
+
`isSeedableDefault`, `isCreateFormMode`, `SeedContext` — stays module-private,
|
|
305
|
+
and `isRuntimeDefault` stays `@object-ui/core`'s to publish.
|
|
306
|
+
|
|
307
|
+
No behaviour change. The console's `FormPage` now calls the published helper
|
|
308
|
+
instead of composing `isRuntimeDefault` + `isMissingForRequired` locally; its
|
|
309
|
+
create payload is decided identically before and after, pinned against the
|
|
310
|
+
deleted implementation over the full matrix of default shapes, value spellings
|
|
311
|
+
and both modes.
|
|
312
|
+
- 5173a5e: ⚠️ **Behaviour change: `current_user` predicates that have been doing nothing on
|
|
313
|
+
the console form routes and in the wizard's submit gate now TAKE EFFECT.** Read
|
|
314
|
+
this before upgrading if any of your form metadata gates on the session user.
|
|
315
|
+
|
|
316
|
+
objectui#6010 bound the host predicate scope on the five authored-predicate call
|
|
317
|
+
sites in the components form renderer, so `current_user` (plus the ADR-0068
|
|
318
|
+
`user` / `ctx.user` / `os.user` aliases) resolves on `visibleWhen` / `visibleOn`
|
|
319
|
+
there. Two other authored-predicate evaluators were still passing `undefined`
|
|
320
|
+
for that argument, so the same authored text meant two different things
|
|
321
|
+
depending on which surface opened the form (objectui#6110):
|
|
322
|
+
|
|
323
|
+
- **`apps/console`'s form renderer**, on the authed internal route
|
|
324
|
+
`/forms/:name`. The internal route is a runtime record surface by ADR-0089
|
|
325
|
+
D1's own words (*"runtime record surfaces bind `record` + `current_user`"*),
|
|
326
|
+
and its `visibleWhen` metadata is the same `*.view.ts` FormView the
|
|
327
|
+
object-view chain renders — so a role gate authored once behaved differently
|
|
328
|
+
depending on which route opened the form.
|
|
329
|
+
- **`WizardForm`'s submit-time required re-check** (`missingRequiredByStep`),
|
|
330
|
+
the gate that re-checks the whole declared field set at final submit because
|
|
331
|
+
`allowSkip` can jump past a step. Its docstring promises *"the same verdict
|
|
332
|
+
from all three rather than a second, divergent dialect"*, and since #6010 it
|
|
333
|
+
was the divergent one.
|
|
334
|
+
|
|
335
|
+
**Why nobody noticed, and why the fix is felt as a change.** `visibleWhen` fails
|
|
336
|
+
OPEN: a field on screen is what you get when the predicate resolves TRUE, when
|
|
337
|
+
the scope was never bound so the predicate faulted, *and* when the predicate is
|
|
338
|
+
broken. Those worlds were indistinguishable, so an app that authored a
|
|
339
|
+
`current_user` gate saw the field render and had no way to tell the rule was
|
|
340
|
+
inert. After this change the predicate is evaluated for real, and fields and
|
|
341
|
+
sections that have always been visible will disappear for the users the rule
|
|
342
|
+
excludes. `requiredWhen` fails the other way (CLOSED), so a `current_user`
|
|
343
|
+
requiredWhen that has been silently not applying will now start holding submits.
|
|
344
|
+
|
|
345
|
+
In the wizard the change is a fix in the user's favour as well: a required field
|
|
346
|
+
the wizard HID from this user was still counted as visible by the submit gate,
|
|
347
|
+
so the submit was refused on a control the submitter could neither see nor fill
|
|
348
|
+
in.
|
|
349
|
+
|
|
350
|
+
**Before upgrading**, audit any `visibleWhen` / `visibleOn` / `requiredWhen` in
|
|
351
|
+
your form-view and object metadata that names `current_user`, and confirm each
|
|
352
|
+
predicate says what you actually want evaluated against `record` +
|
|
353
|
+
`current_user`.
|
|
354
|
+
|
|
355
|
+
**The public anonymous form `/f/:slug` is deliberately unchanged.** It is
|
|
356
|
+
mounted outside `ProtectedRoute` so an anonymous visitor can submit it, there is
|
|
357
|
+
no authenticated principal, and no provider is mounted above it — so its scope
|
|
358
|
+
is empty and a `current_user` predicate authored on a public form still faults
|
|
359
|
+
and still fails open, exactly as before. Nothing new is declared to say so: the
|
|
360
|
+
two routes are told apart by which component mounts them.
|
|
361
|
+
|
|
362
|
+
`@object-ui/app-shell` exports `buildExpressionUser`, the `ExpressionProvider`
|
|
363
|
+
user normalisation, so every console surface that mounts the provider publishes
|
|
364
|
+
the same `current_user` shape rather than re-deriving it.
|
|
365
|
+
- 971d387: ⚠️ **Behaviour change: an authored `FormSection.visibleWhen` that has been doing nothing
|
|
366
|
+
will now START HIDING SECTIONS.** Read this before upgrading if any of your metadata
|
|
367
|
+
authors a section predicate.
|
|
368
|
+
|
|
369
|
+
`@objectstack/spec` declares `FormSection.visibleWhen` and this repo's spec bridge maps it
|
|
370
|
+
through, but every plugin-form layout renders a section header as a virtual
|
|
371
|
+
`section-divider` pseudo-field and none of them copied the predicate onto it. On the
|
|
372
|
+
object-view chain — the create/edit modal, the drawer, the split form, and the full-page
|
|
373
|
+
record form — the key was declared, mapped, carried, and then dropped one hop before
|
|
374
|
+
anything could evaluate it. The section rendered unconditionally, with no diagnostic
|
|
375
|
+
(objectui#6111).
|
|
376
|
+
|
|
377
|
+
**Why nobody noticed, and why the fix is felt as a regression.** `visibleWhen` fails OPEN:
|
|
378
|
+
a section that renders is what you get when the predicate resolves TRUE, when the predicate
|
|
379
|
+
never arrives, *and* when the predicate faults. Those three worlds were indistinguishable,
|
|
380
|
+
so an app that authored a section predicate saw its section render and had no way to tell
|
|
381
|
+
that the rule was inert. Every such app has been running with the rule switched off, and
|
|
382
|
+
some will have been authored — or simply grown used to — that state. After this change the
|
|
383
|
+
predicate is evaluated for real, and sections that have always been visible will disappear
|
|
384
|
+
for the users the rule excludes.
|
|
385
|
+
|
|
386
|
+
This is the intended ADR-0089 contract being delivered, not a new capability: the key was
|
|
387
|
+
already declared, already documented, and already honoured by the console form renderer.
|
|
388
|
+
The object-view chain was the one that silently ignored it.
|
|
389
|
+
|
|
390
|
+
**Before upgrading**, audit any `sections[].visibleWhen` in your form-view metadata and
|
|
391
|
+
confirm each predicate says what you actually want, evaluated against `record` +
|
|
392
|
+
`current_user`. A predicate that was written speculatively, or left behind after a rework,
|
|
393
|
+
now takes effect.
|
|
394
|
+
|
|
395
|
+
**Measured scope of the hide.** The predicate gates the section's HEADER row. The renderer
|
|
396
|
+
treats `section-divider` as presentational and holds no association between it and the
|
|
397
|
+
fields that follow it, so a false predicate removes the heading and the section's fields
|
|
398
|
+
keep rendering. The console renderer (`apps/console`) drops the whole `<section>`, fields
|
|
399
|
+
included. That divergence is real, is pinned honestly by this change's tests rather than
|
|
400
|
+
implied away, and is filed separately — it needs a renderer-side grouping contract, not
|
|
401
|
+
another line in a layout.
|
|
402
|
+
|
|
403
|
+
Two hops were dropping the key and both are repaired: `ObjectForm` rebuilds each section
|
|
404
|
+
key by key when it delegates to Split/Drawer/Modal (and `ModalForm`'s own `groups` map does
|
|
405
|
+
it again), so a key those maps did not copy never reached the layout at all; and the six
|
|
406
|
+
`section-divider` synthesis sites across the four layout files.
|
|
407
|
+
|
|
408
|
+
`@object-ui/types` gains the matching `ObjectFormSection.visibleWhen` declaration.
|
|
409
|
+
- 5ef9c4f: The section grouping contract (objectui#6236, maintainer ruling 2026-08-27): a
|
|
410
|
+
`section-divider` row may now CLAIM its member fields — `FormField.fields: string[]`, the
|
|
411
|
+
same membership shape `FormFieldTab.fields` / `FormFieldPane.fields` already model — and
|
|
412
|
+
the form renderer then gates the WHOLE group on the divider's own visibility verdict
|
|
413
|
+
(`visibleWhen` / `visibleOn` / legacy `condition`).
|
|
414
|
+
|
|
415
|
+
Before this, one authored `FormSection.visibleWhen` meant two different things: the
|
|
416
|
+
console renderer drops the whole `<section>` (heading and fields), while the plugin-form
|
|
417
|
+
chain's renderer treated `section-divider` as a purely presentational row and hid only
|
|
418
|
+
the HEADING, leaving the section's fields rendering (measured in objectui#6111, which
|
|
419
|
+
pinned that honestly rather than implying a guarantee it did not deliver).
|
|
420
|
+
|
|
421
|
+
Ruled semantics, now pinned in `section-grouping-6236.test.tsx`:
|
|
422
|
+
|
|
423
|
+
- **Visibility decides what is DRAWN and nothing else** (console precedent, 2026-08-22
|
|
424
|
+
ruling after #5594) — a hidden section's values still submit.
|
|
425
|
+
- **A hidden section's fields skip client-side validation** — a user is never blocked by
|
|
426
|
+
an error pointing at a control they cannot see (the objectui#6110 defect shape); the
|
|
427
|
+
server-side contract remains the loud floor for genuinely-required data. A section
|
|
428
|
+
hiding mid-session also clears its members' stale errors, the way a field's own false
|
|
429
|
+
predicate already did.
|
|
430
|
+
- **A divider without a claim keeps the old contract** (its predicate gates only the
|
|
431
|
+
heading), so existing schemas are untouched.
|
|
432
|
+
|
|
433
|
+
Both halves ride the mechanism the field-level predicate already uses (return `null`;
|
|
434
|
+
react-hook-form keeps the value and skips the unmounted control), so field-level and
|
|
435
|
+
section-level visibility cannot drift apart. The zod mirror (`FormFieldSchema`) declares
|
|
436
|
+
the key with the same scope note.
|
|
437
|
+
|
|
438
|
+
`@object-ui/plugin-form` wires the producer half: all six `section-divider` synthesis
|
|
439
|
+
sites (ObjectForm's stacked simple path, ModalForm's sectioned and derived-fieldGroup
|
|
440
|
+
paths, DrawerForm's sectioned and derived-fieldGroup paths, SplitForm's panes) now stamp
|
|
441
|
+
the membership claim onto the divider they emit, from the RESOLVED member list — so an
|
|
442
|
+
authored `FormSection.visibleWhen` finally hides the whole section on the object-view
|
|
443
|
+
chain, matching the console renderer. The #6111 honest pin (`measured scope`) flipped
|
|
444
|
+
accordingly: it now pins heading-and-fields hiding together, and every per-layout DENIED
|
|
445
|
+
row asserts the claimed member as well as the heading. The derived-fieldGroup sites carry
|
|
446
|
+
the claim for uniformity but stay fail-open — the spec `fieldGroups` vocabulary has no
|
|
447
|
+
section-predicate slot to author. The tabbed arm's predicate slot (objectui#6237) is
|
|
448
|
+
designed to reuse this same grouping contract.
|
|
449
|
+
- 46f0bb4: The tabbed arm of the grouping contract (objectui#6237, same maintainer ruling as
|
|
450
|
+
objectui#6236): `FormFieldTab` gains the predicate slot the ruling named —
|
|
451
|
+
`visibleWhen?: string | { dialect?: string; source: string }` — so a section rendered as
|
|
452
|
+
a TAB PANEL (`ModalForm` `contentLayout: 'tabbed'`) can finally carry an authored
|
|
453
|
+
`FormSection.visibleWhen`. The tabbed layout synthesises no `section-divider` at all, so
|
|
454
|
+
the #6236 membership-claim mechanism had nothing to stamp the predicate onto and no slot
|
|
455
|
+
to copy it into; the predicate was silently dropped one hop before evaluation (measured
|
|
456
|
+
in objectui#6237's card).
|
|
457
|
+
|
|
458
|
+
The form renderer evaluates the tab's predicate with the same record assembly the
|
|
459
|
+
field-level rules use (`ruleRecord` / `previousRecord` / host predicate scope, #6010),
|
|
460
|
+
fail-open, and when FALSE draws neither the tab's trigger nor its panel. Not drawing the
|
|
461
|
+
panel unmounts the claimed fields through the exact mechanism a field's own false
|
|
462
|
+
predicate uses, so the ruled hidden-group semantics are inherited rather than
|
|
463
|
+
re-implemented, and are pinned in `fieldtab-visiblewhen-6237.test.tsx`:
|
|
464
|
+
|
|
465
|
+
- **Visibility decides what is DRAWN and nothing else** — a hidden tab's values still
|
|
466
|
+
submit.
|
|
467
|
+
- **A hidden tab's fields skip client-side validation** — a user is never blocked by an
|
|
468
|
+
error pointing at a control they cannot see; the server-side contract remains the loud
|
|
469
|
+
floor for genuinely-required data (#2959's trap, answered the same way for tabs as for
|
|
470
|
+
sections). A tab hiding mid-session clears its members' stale errors.
|
|
471
|
+
- **Deterministic re-selection**: a predicate hiding the ACTIVE tab activates the user's
|
|
472
|
+
pick if still visible, else the declared default, else the first visible tab — never an
|
|
473
|
+
empty panel — and the user's pick is restored the moment its tab is re-admitted.
|
|
474
|
+
- **No mid-interaction collapse**: whether the tabbed arm engages stays judged on the
|
|
475
|
+
DECLARED tabs, so a predicate hiding one of two tabs filters the strip (and hides the
|
|
476
|
+
tab's fields) instead of collapsing the modal into the stacked layout under the user's
|
|
477
|
+
cursor. With every tab hidden the strip is omitted; unclaimed fields still render.
|
|
478
|
+
- **A tab without the key keeps the pre-#6237 contract** (always drawn), so existing
|
|
479
|
+
schemas are untouched.
|
|
480
|
+
|
|
481
|
+
`@object-ui/plugin-form` wires the producer half: `ModalForm`'s tabbed synthesis site now
|
|
482
|
+
copies the section's `visibleWhen` onto the tab it emits, and the #6111 layout matrix
|
|
483
|
+
gains the tabbed-modal rows (direct and via `ObjectForm` delegation). `TabbedForm` /
|
|
484
|
+
`WizardForm` still declare no section predicate in their own section configs — those arms
|
|
485
|
+
remain open on objectui#6237.
|
|
486
|
+
- 2da6441: `formType: 'tabbed'` now honours an authored section `visibleWhen` (objectui#6237).
|
|
487
|
+
|
|
488
|
+
The tabbed arm of the one grouping contract ruled 2026-08-29 (option A). Before
|
|
489
|
+
this, an authored `FormSection.visibleWhen` was dropped on the tabbed route
|
|
490
|
+
while `split` / `drawer` / `modal` and the flat layout all honoured it — the key
|
|
491
|
+
never reached a renderer at all, so it did nothing.
|
|
492
|
+
|
|
493
|
+
`TabbedForm` already synthesised the renderer's `fieldTabs`, which is the same
|
|
494
|
+
machinery the `modal` + `contentLayout: 'tabbed'` arm runs on. The predicate was
|
|
495
|
+
simply dropped at three points on the way there, and all three now carry it:
|
|
496
|
+
`ObjectForm`'s tabbed section map, `FormSectionConfig` (which declared no such
|
|
497
|
+
key), and `TabbedForm`'s `fieldTabs` synthesis.
|
|
498
|
+
|
|
499
|
+
Because the arm reaches the existing evaluator, the three ruled semantics are
|
|
500
|
+
inherited rather than re-implemented beside it: a hidden tab's values still
|
|
501
|
+
submit, its fields skip client-side validation (so a required field on a hidden
|
|
502
|
+
tab cannot block a submit invisibly — objectui#2959's defect through a new
|
|
503
|
+
door), a predicate hiding the ACTIVE tab re-selects deterministically instead of
|
|
504
|
+
drawing an empty panel, and arm engagement stays structural on the DECLARED
|
|
505
|
+
tabs so a predicate cannot collapse the strip mid-interaction.
|
|
506
|
+
|
|
507
|
+
Two boundaries are deliberate:
|
|
508
|
+
|
|
509
|
+
- A single-section tabbed form never engages the tab arm, so it degrades to the
|
|
510
|
+
untabbed layout's own predicate mechanism — a chrome-less `section-divider`
|
|
511
|
+
claiming its members by name. Existing single-section forms are unchanged; the
|
|
512
|
+
gate is emitted only where a predicate was actually authored.
|
|
513
|
+
- Wizard STEPS still do not take a predicate, and now say so in the type:
|
|
514
|
+
`WizardStepConfig` omits the key, because a step predicate is a different
|
|
515
|
+
contract (step-boundary reactive against the ruled live-record reactivity, and
|
|
516
|
+
needing navigation and final-gate semantics none of this machinery supplies).
|
|
517
|
+
`ObjectForm` continues to report that gap at runtime for untyped JSON.
|
|
518
|
+
- a29ae2d: A form-view section can reference a declared field group instead of copying its
|
|
519
|
+
members (objectstack#13855, objectui#7051 — the view-level half; the
|
|
520
|
+
`record:details` half shipped as objectui#8497).
|
|
521
|
+
|
|
522
|
+
`@objectstack/spec` 17.3.0 declares two ways for a `form.sections[]` entry to give
|
|
523
|
+
itself members: enumerate `fields`, or point `group` at one of the object's declared
|
|
524
|
+
`fieldGroups` and inherit that group's membership **and** its presentation. Nothing on
|
|
525
|
+
this renderer read `group`, and the omission was not a no-op:
|
|
526
|
+
|
|
527
|
+
- on the default `simple` layout an authored `{ group: 'contact_info' }` threw
|
|
528
|
+
`Cannot read properties of undefined (reading 'map')` out of the section loop in
|
|
529
|
+
`SimpleObjectForm`'s own body — above the JSX it returns, so no per-section error
|
|
530
|
+
boundary could contain it — and **blanked the entire form**, taking every
|
|
531
|
+
well-formed sibling section with it;
|
|
532
|
+
- on `tabbed` / `split` / `drawer` / `modal` the section silently rendered nothing;
|
|
533
|
+
- on `wizard` it rendered an empty step.
|
|
534
|
+
|
|
535
|
+
`ObjectForm` now resolves the reference **once**, above its routing fork, so all six
|
|
536
|
+
layouts inherit it: a `{ group }` section renders the members that group declares, in
|
|
537
|
+
the order and with the label, description and collapse state the object declares them
|
|
538
|
+
with. Resolution goes through `deriveFieldGroupLayout` (ADR-0085 §5) via this
|
|
539
|
+
package's existing single adapter — the same code path the no-sections field-group
|
|
540
|
+
fallback already used, so authoring a group by reference and letting the fallback
|
|
541
|
+
derive it produce the same section by construction. No assembly rule is
|
|
542
|
+
re-implemented here.
|
|
543
|
+
|
|
544
|
+
The object definition is fetched only when a section actually authors `group`, so a
|
|
545
|
+
form that does not use the reference form issues no additional request and takes no
|
|
546
|
+
new path.
|
|
547
|
+
|
|
548
|
+
Diagnostics rather than silence, for the shapes the spec door cannot see (programmatic
|
|
549
|
+
SDUI callers): a `group` naming no declared group renders nothing and is reported once
|
|
550
|
+
on the console (`@objectstack/lint` owns it as `form-section-group-unknown`); a
|
|
551
|
+
group-owned presentation key restated beside `group` is ignored — the spec grants no
|
|
552
|
+
override semantics — and reported; `group` on a wizard step is refused and reported,
|
|
553
|
+
because a step has no slot for the `collapse` / `visibleWhen` a group carries.
|
|
554
|
+
|
|
555
|
+
Also in this change: `@object-ui/types`' `ObjectFormSection` declares `group` and makes
|
|
556
|
+
`fields` optional, so the spec-legal shape finally compiles for a TypeScript author;
|
|
557
|
+
and the shared field-group adapter now carries the group's `description` and
|
|
558
|
+
`visibleWhen` onto the section it derives — both are keys the assembler emits and
|
|
559
|
+
`ObjectFormSection` declares, and a key-by-key rebuild that drops one is how a declared
|
|
560
|
+
group's presentation goes missing.
|
|
561
|
+
- 2d3fe73: Publish the parameter types of the entry's own exported functions, so a consumer can
|
|
562
|
+
name what it must pass (objectui#7324).
|
|
563
|
+
|
|
564
|
+
`ChildObjectSchemaLike` and `FieldDefaultsSchemaLike` are now exported from
|
|
565
|
+
`@object-ui/plugin-form`. They are **type-only** additions — no runtime name is added
|
|
566
|
+
to the entry, which is pinned.
|
|
567
|
+
|
|
568
|
+
**Why `minor`, not `patch`.** Nothing breaks and no behaviour changes, but two names
|
|
569
|
+
join the published surface of a published package. Additions are `minor` in this repo,
|
|
570
|
+
and a new public export is the kind of addition a consumer's lockfile-pinned range
|
|
571
|
+
should be able to see.
|
|
572
|
+
|
|
573
|
+
**What was wrong.** Five exported derive functions (`deriveDetail`, `deriveColumns`,
|
|
574
|
+
`deriveFormFields`, `findRelationshipField`, `resolveInlineMode`) take a `childSchema`,
|
|
575
|
+
and the exported `omitServerResolvedDefaults` takes an `objectSchema` — and neither
|
|
576
|
+
parameter type reached the entry. A host with its own form renderer (the reason
|
|
577
|
+
objectui#6059 published `omitServerResolvedDefaults` in the first place) has to hold
|
|
578
|
+
that schema in a variable or a prop, and could not annotate it. Structural typing means
|
|
579
|
+
such a host still compiled by writing the shape out by hand, so the cost was not a hard
|
|
580
|
+
failure but a producer-owned shape restated in every consumer, invisible to every gate
|
|
581
|
+
until the producer's shape moved. The package README carried exactly that restatement,
|
|
582
|
+
and now imports the real name instead.
|
|
583
|
+
|
|
584
|
+
**Renamed at the declaration site first, deliberately.** Both types were called
|
|
585
|
+
`ObjectSchemaLike`, in two files, and they are **not** the same type: the defaults one
|
|
586
|
+
pins the four field members its rule reads (`defaultValue`, `type`, `reference`,
|
|
587
|
+
`reference_to`), while the child one leaves a field value as `any` because the derive
|
|
588
|
+
functions read much more of it. Measured with `tsc`, they are mutually assignable
|
|
589
|
+
**only** through that `any` — replace it with `unknown` and the child → defaults
|
|
590
|
+
direction fails (TS2322) — so re-exporting either under the shared name would have put
|
|
591
|
+
a name on the public surface that already meant something else two files over, with
|
|
592
|
+
nothing in the name to say which. Neither old name was reachable from outside the
|
|
593
|
+
package (the package `exports` map has a single `.` entry and the entry never re-exported
|
|
594
|
+
them), so the rename is not a break for any consumer.
|
|
595
|
+
|
|
596
|
+
**Not** `@object-ui/types`' `ObjectSchemaMetadata`: measured, it requires `name`,
|
|
597
|
+
requires a `type` on every field, and has no `reference_to` member — while
|
|
598
|
+
`isCurrentUserSeedField` honours both `reference` and `reference_to` on purpose. Adopting
|
|
599
|
+
it would have narrowed what these functions accept and dropped one of the two honoured
|
|
600
|
+
spellings, not widened anything.
|
|
601
|
+
|
|
602
|
+
⚠️ **Dated note, 2026-09-30 — `FieldDefaultsSchemaLike` pins three members now — objectui#11070.** "the four field members its rule reads (`defaultValue`, `type`, `reference`, `reference_to`)" and "honours both `reference` and `reference_to`" above held when this change landed. Later in this same release objectui#11070 (round 4) retired `reference_to` there: `isCurrentUserSeedField` reads the target as `reference` alone, and the published type no longer carries a `reference_to` member. `.changeset/11070-reference-to-round4.md` states what ships; the text above is kept as the reading of this change.
|
|
603
|
+
- 9a853f2: Retire the legacy string `sort` clause: one spelling, the array
|
|
604
|
+
(objectui#8221) — `convertSortToQueryParams` now REFUSES `"name desc"` with a
|
|
605
|
+
diagnostic naming `[{ field: 'name', order: 'desc' }]`, instead of lowering it.
|
|
606
|
+
|
|
607
|
+
**BREAKING for `@object-ui/core` consumers — scored `minor`, not `major`, per
|
|
608
|
+
AGENTS.md 版本号策略** (every package is in one fixed group, so a `major` here
|
|
609
|
+
would carry all 39 off the `@objectstack` major this repo is pinned to). The
|
|
610
|
+
breaking semantics are stated below rather than encoded in the version.
|
|
611
|
+
|
|
612
|
+
Director ruling, decision batch #77 (2026-09-07), option B. Three faces
|
|
613
|
+
disagreed about one key: `@object-ui/core` implemented the string clause
|
|
614
|
+
on purpose (`sort-query.ts`, docblock and all), `content/docs/plugins/plugin-map.mdx`
|
|
615
|
+
taught it as `sort?: string | SortConfig[]`, and the html tier answered
|
|
616
|
+
`type-mismatch` for it because all seven `sort` registrations publish
|
|
617
|
+
`type: 'array'` alone — while `@objectstack/spec` refuses the string outright on
|
|
618
|
+
`element-record-picker`. Option A (per-block string arms) was rejected by name:
|
|
619
|
+
it would make one key mean different things on different blocks.
|
|
620
|
+
|
|
621
|
+
**What moves.** `convertSortToQueryParams(sort)` narrows from
|
|
622
|
+
`string | QuerySortEntry[]` to `QuerySortEntry[]`, and the three declarations
|
|
623
|
+
that published a string arm narrow with it — `ObjectGridSchema.sort`,
|
|
624
|
+
`ObjectMapSchema.sort` and `ObjectGanttSchema.sort`, in the TypeScript face AND
|
|
625
|
+
in the zod mirror, together, because a narrowing that left `z.string()` in the
|
|
626
|
+
mirror is the declared-vs-enforced split this change exists to close. The local
|
|
627
|
+
`sort` declarations on `LineItemsPanel`, `ObjectTimeline` and
|
|
628
|
+
`deriveRelatedLists`'s ListView input narrow the same way.
|
|
629
|
+
|
|
630
|
+
**What a string does now.** Types are erased, so the signature stops a string
|
|
631
|
+
only at compile time; authored JSON and stored `sys_metadata` rows still reach
|
|
632
|
+
the sink carrying `"name desc"`. Such a value is REFUSED — the query carries no
|
|
633
|
+
`$orderby` — and `console.error` names the array form, quotes what arrived and
|
|
634
|
+
states the consequence, once per spelling. A silent `undefined` was the one
|
|
635
|
+
outcome the ruling ruled out.
|
|
636
|
+
|
|
637
|
+
**Measured consequences you may see.** A related list that inherited its child
|
|
638
|
+
object's default list-view sort in the legacy spelling stops inheriting it (the
|
|
639
|
+
console says so). `@objectstack/spec@17.3.0` still ACCEPTS the string on
|
|
640
|
+
`ListViewSchema.sort` and on `RecordRelatedListProps.sort`, so such metadata is
|
|
641
|
+
still spec-legal today; the spec-side pull-back is its own card. Two surfaces
|
|
642
|
+
are deliberately untouched, because they are a DIFFERENT string dialect that
|
|
643
|
+
never reaches this sink: `record:related_list`'s `'field'` / `'-field'` form,
|
|
644
|
+
normalized by `RelatedList.normalizeSortSpec`, and `ListView.parseSortConfig`,
|
|
645
|
+
which reads the platform view record the spec still blesses.
|
|
646
|
+
|
|
647
|
+
Docs teach the array only: `content/docs/plugins/plugin-map.mdx`,
|
|
648
|
+
`content/docs/plugins/plugin-view.mdx` and `packages/plugin-view/README.md`.
|
|
649
|
+
- 6fda1a9: Render a form section that REFERENCES a field group on the console's form page,
|
|
650
|
+
and publish the resolver that does it (objectui#8641).
|
|
651
|
+
|
|
652
|
+
`@objectstack/spec` 17.3.0 lets a `form.sections[]` entry declare its members
|
|
653
|
+
either way — enumerate `fields`, or point `group` at one of the object's declared
|
|
654
|
+
`fieldGroups` (objectstack#13855, ADR-0085 §5). `apps/console`'s `FormPage` has
|
|
655
|
+
its own section builder, on none of `@object-ui/plugin-form`'s code path, and it
|
|
656
|
+
read `sec.fields ?? []` and `sec.label` — neither of which a `{ group }` section
|
|
657
|
+
carries. Measured in the DOM on both routes before the fix: the `<section>` was
|
|
658
|
+
emitted with its border and padding and then stood **empty** — no heading, no
|
|
659
|
+
inputs, no diagnostic — so a submitter saw a blank card where the group's fields
|
|
660
|
+
belong. The same silent-drop class objectui#7051 closed on the `plugin-form`
|
|
661
|
+
chain, at the third consumer.
|
|
662
|
+
|
|
663
|
+
Newly importable from `@object-ui/plugin-form` — one function and the options
|
|
664
|
+
type its signature requires, nothing else:
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
import {
|
|
668
|
+
resolveSectionGroupReferences,
|
|
669
|
+
type ResolveSectionGroupsOptions,
|
|
670
|
+
} from '@object-ui/plugin-form';
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
- `resolveSectionGroupReferences(sections, { objectName, formType, objectDef })`
|
|
674
|
+
— replace every `{ group: 'x' }` section with the section that group declares
|
|
675
|
+
(label, members, description, collapse state), leaving everything else
|
|
676
|
+
untouched. With no reference in the list it returns its input **by identity**,
|
|
677
|
+
so it cannot perturb an existing form and is safe inside a `useMemo`. An
|
|
678
|
+
unresolvable reference yields an empty section, never a dropped one, and is
|
|
679
|
+
reported once naming the object and the key.
|
|
680
|
+
|
|
681
|
+
`hasSectionGroupReference`, `resetSectionGroupReports`, `GROUP_OWNED_SECTION_KEYS`
|
|
682
|
+
and `SECTION_LAYOUT_KEYS` stay module-private, pinned as the withheld set.
|
|
683
|
+
|
|
684
|
+
⛔ No assembly rule is re-implemented on the console side: declared order, the
|
|
685
|
+
empty-group drop, the ungrouped trailing bucket and the collapse / `visibleWhen`
|
|
686
|
+
passthrough all reach it from `deriveFieldGroupLayout` through this package's one
|
|
687
|
+
adapter — the same code path `ObjectForm` resolves through — which is why the
|
|
688
|
+
resolver is exported rather than the derivation being read a second time.
|
|
689
|
+
|
|
690
|
+
`ObjectSchemaPayload` in the console now carries `fieldGroups`, and its internal
|
|
691
|
+
`/meta/object/:name` loader copies the key: that rebuild is key by key, so an
|
|
692
|
+
uncopied key is gone before the builder can see it.
|
|
693
|
+
|
|
694
|
+
No behaviour change for any form that does not author `group`.
|
|
695
|
+
- 804831c: A render-time filter refusal now renders a named "this view's filter is malformed" state instead of throwing out of render (objectui#9050).
|
|
696
|
+
|
|
697
|
+
`convertFiltersToAST` refuses eleven authored shapes with a `FilterOperatorError`, and
|
|
698
|
+
`toFilterNode` delegates to it from four call sites that are not on the wire path:
|
|
699
|
+
`RelatedList`, `LineItemsPanel` and `ObjectGrid` read it from a render-time `useMemo`
|
|
700
|
+
(where a throw is a render error, with no `classifyLoadError` to turn it into "the
|
|
701
|
+
filter is malformed"), and `ObjectGrid`'s deprecated `defaultFilters` leg reads it
|
|
702
|
+
inside the load effect, which caught the refusal but reported it under "Error loading
|
|
703
|
+
grid" over the converter's English paragraph.
|
|
704
|
+
|
|
705
|
+
What changes:
|
|
706
|
+
|
|
707
|
+
- `@object-ui/core` gains `toFilterNodeSafely`, whose result is a UNION — `{ ok: true,
|
|
708
|
+
node }` or `{ ok: false, refusal }`. The refusal is deliberately NOT representable as
|
|
709
|
+
`undefined`: `undefined` means "no filter", i.e. every row, which is the silently
|
|
710
|
+
unconstrained query objectui#9001 closed. A caller must narrow before it can build a
|
|
711
|
+
query.
|
|
712
|
+
- `FilterOperatorError` now carries `operator` and `field` as data, and
|
|
713
|
+
`filterRefusalSubject` picks the token a diagnostic names. The eleven messages
|
|
714
|
+
deliberately share no idiom, so recovering that token by pattern would be a twelfth
|
|
715
|
+
dialect. `operator` is the spelling the AUTHOR wrote, never a canonical form
|
|
716
|
+
substituted for it; it is absent on exactly the two arms that judge a comparand
|
|
717
|
+
written with no operator in it, where `field` is the only handle.
|
|
718
|
+
- The three render-time readers keep the refusal as a value, send nothing, and render a
|
|
719
|
+
state that names the operator. The subtree around them is unaffected — on the
|
|
720
|
+
schema-rendered path a `SchemaErrorBoundary` already contained the throw, so what is
|
|
721
|
+
new there is the DIAGNOSIS in place of a generic "Component failed to render"; mounted
|
|
722
|
+
directly, which every one of these components supports through its package entry,
|
|
723
|
+
there was no boundary at all and the throw reached the host.
|
|
724
|
+
- `view.malformedFilter` is added to all ten locale packs and to the three affected
|
|
725
|
+
`createSafeTranslation` tables.
|
|
726
|
+
|
|
727
|
+
⛔ Not changed: which shapes convert. The converter's accept set is byte-identical —
|
|
728
|
+
this is a delivery change, not an acceptance one. Where the protocol's own parse accepts
|
|
729
|
+
an input this layer refuses, that gap is the protocol's to close and is filed against
|
|
730
|
+
`@objectstack/spec` rather than papered over here.
|
|
731
|
+
|
|
732
|
+
⚠️ **Dated note, 2026-09-27 — one more refusal carries no operator — objectui#9164.**
|
|
733
|
+
Later in this same release `convertFiltersToAST` also refuses an empty operator map that
|
|
734
|
+
is all a filter says (`{ a: {} }`). Its `FilterOperatorError` carries `field` and no
|
|
735
|
+
`operator`, like the two comparand arms, so `filterRefusalSubject` names the field and
|
|
736
|
+
the three render-time readers show the malformed-filter state for it. The counts above
|
|
737
|
+
(eleven shapes, two arms without an operator) are this change's reading. The rest of this
|
|
738
|
+
entry is kept as the reading of this change; the objectui#9164 entry states the new
|
|
739
|
+
refusal.
|
|
740
|
+
- 87af769: **BREAKING** — a record id is a **string** wherever metadata names one, and on the last
|
|
741
|
+
`DataSource` door (objectui#9511). An authored `recordId: 42` / `resourceId: 42` no longer
|
|
742
|
+
validates.
|
|
743
|
+
|
|
744
|
+
**FROM** `string | number` **TO** `string`, on both published faces of three authorable keys
|
|
745
|
+
and on `DataSource.findOne`:
|
|
746
|
+
|
|
747
|
+
```jsonc
|
|
748
|
+
// before — accepted
|
|
749
|
+
{ "type": "detail", "resourceId": 42 }
|
|
750
|
+
// after — refused at parse, with the repair named in the message
|
|
751
|
+
{ "type": "detail", "resourceId": "42" }
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
**What an author sees, in business terms.** If you write a record id as a bare number today,
|
|
755
|
+
your metadata stops validating the moment you upgrade, and the error tells you what to write:
|
|
756
|
+
quote it. `42` becomes `'42'`. ⛔ **Nothing converts for you, in either direction** — that is
|
|
757
|
+
deliberate, not an omission. The platform is not guessing whether your `42` meant the string
|
|
758
|
+
`'42'` or something else; a host whose primary keys are numeric does the conversion once, at
|
|
759
|
+
its own adapter boundary, so every author, every caller and every adapter downstream sees one
|
|
760
|
+
shape. The wire traffic for such a host was already `'42'` on `update` (objectui#9333) and on
|
|
761
|
+
`delete` / `bulkUpdate` / `bulkDelete` (objectui#9712); this change makes `findOne` agree
|
|
762
|
+
instead of being the one door that disagreed.
|
|
763
|
+
|
|
764
|
+
**The three authorable keys, each on BOTH faces.** `ObjectFormSchema.recordId`
|
|
765
|
+
(`objectql.ts` + `zod/objectql.zod.ts`), `DetailViewSchema.resourceId` (`views.ts` +
|
|
766
|
+
`zod/views.zod.ts`) and `DetailSchema.resourceId` (`crud.ts` + `zod/crud.zod.ts`). ⚠️ The
|
|
767
|
+
`crud` pair is **`DetailSchema`**, not `DetailViewSchema`, and it reaches the same renderer —
|
|
768
|
+
not by symbol but by data flow: `plugin-detail` registers the `'detail'` node type onto
|
|
769
|
+
`DetailView`. A read that follows TypeScript symbols alone finds two keys and is incomplete.
|
|
770
|
+
|
|
771
|
+
⭐ **The declaration alone would not have been enough, and this is the reusable part.** A
|
|
772
|
+
TypeScript declaration does not run at parse time. The hand-written zod mirror is the only
|
|
773
|
+
face in this repository that can refuse an authored number, so narrowing the declaration
|
|
774
|
+
without the mirror would have shipped `declared !== enforced` on a published surface — and
|
|
775
|
+
`zod-mirror-parity.test.ts` would have stayed GREEN through it, because that instrument
|
|
776
|
+
asserts a mirror accepts everything its declaration declares and a mirror left WIDER passes.
|
|
777
|
+
Both faces moved together for that reason.
|
|
778
|
+
|
|
779
|
+
**Disposition: an ADR-0087 D2 narrowing that ships `minor` with this banner, ⛔ not a
|
|
780
|
+
tombstone and ⛔ not an npm `major`.** The key stays declared and writable; only its accept
|
|
781
|
+
set narrows, so there is no `never` member and no `retirementTombstone()`. Pre-GA, a
|
|
782
|
+
metadata-facing break ships `minor` carrying the `**BREAKING**` banner and its ADR-0087
|
|
783
|
+
disposition — the level is not the carrier of breaking-ness during the window, the banner and
|
|
784
|
+
this line are (ADR-0087, amended 2026-09-13). `major` is refused outright here anyway: all
|
|
785
|
+
publishable packages sit in one `fixed` group pinned to the `@objectstack` major
|
|
786
|
+
(AGENTS.md §版本号策略, enforced by `scripts/check-changeset-no-major.mjs`).
|
|
787
|
+
|
|
788
|
+
⚠️ **No live conversion entry ships with this.** A D2 conversion table lives in
|
|
789
|
+
`@objectstack/spec`, and these three keys are objectui's own vocabulary rather than spec keys,
|
|
790
|
+
so there is no table here to add a row to; the repair is also lossless and mechanical in a way
|
|
791
|
+
a table would not improve (`42` to `'42'`). An author is given the prescription at the point
|
|
792
|
+
of refusal instead. Recorded rather than left silent, because ADR-0087's post-GA ladder does
|
|
793
|
+
require a live entry and this is the pre-GA spelling of the same obligation.
|
|
794
|
+
|
|
795
|
+
**The accept set moved in exactly ONE direction, measured — 45 documents across the three
|
|
796
|
+
mirrors, parsed on this branch and on the merge base:**
|
|
797
|
+
|
|
798
|
+
| reading | count |
|
|
799
|
+
| --- | --: |
|
|
800
|
+
| GREEN to RED | **9** — the three keys x integer / negative / float, and nothing else |
|
|
801
|
+
| **RED to GREEN** | **0** |
|
|
802
|
+
| verdict unchanged | 36 |
|
|
803
|
+
| refusals carrying the prescription | **9 / 9** |
|
|
804
|
+
|
|
805
|
+
Controls on the same instrument and corpus: **lit** — `title: 42` / `objectName: 42`, keys
|
|
806
|
+
that were string-only before this change, still refuse with `invalid_type`; a wrong `type`
|
|
807
|
+
literal still refuses; **green** — the key absent, `'42'`, `'rec_1'` and `''` all still parse;
|
|
808
|
+
**red-both (21 rows)** — object, array, boolean and `null` at the same key were refused before
|
|
809
|
+
and are refused now, which is the half of the corpus that shows nothing was loosened while the
|
|
810
|
+
number arm was removed.
|
|
811
|
+
|
|
812
|
+
**Delivery surface — where the refusal does and does not arrive.** PARSE-TIME on the mirror
|
|
813
|
+
(`@object-ui/types/zod`, so `objectui validate` and any consumer calling `safeParse`) and
|
|
814
|
+
COMPILE-TIME on the declaration (`tsc`). ⚠️ As with every other key on these mirrors, the
|
|
815
|
+
runtime render path does not parse through them, so a rendered document is not where this
|
|
816
|
+
arrives.
|
|
817
|
+
|
|
818
|
+
⚠️ **Dated note, 2026-09-25 — `objectui check` does not deliver this refusal —
|
|
819
|
+
objectui#10524.** This entry first listed `objectui check` on the parse-time side. `check` is
|
|
820
|
+
an advisory sweep: it never parses against the schema a file whose root carries a structural key (`children`,
|
|
821
|
+
`className`, `body`, …), it lists a file with none of those keys by name when the file does
|
|
822
|
+
not validate, and it exits non-zero on unreadable JSON only. The refusal is
|
|
823
|
+
`objectui validate`'s.
|
|
824
|
+
|
|
825
|
+
**Consequential narrowings, all compiler-forced by the door above.** `DrawerForm`,
|
|
826
|
+
`ModalForm`, `SplitForm`, `TabbedForm` and `WizardForm` each declare their own `recordId` and
|
|
827
|
+
hand it straight to `findOne`; `ObjectForm` builds all five from the authorable
|
|
828
|
+
`ObjectFormSchema`, so they move together or the refusal simply relocates onto the hand-off
|
|
829
|
+
site. Two more fell out of the same narrowing and are named because they are NOT obvious:
|
|
830
|
+
|
|
831
|
+
- `@object-ui/plugin-grid` — `BulkActionDialogProps.dataSource` hand-restates `findOne`. It
|
|
832
|
+
declares it as a **property**, not a method, so it is checked contravariantly where the
|
|
833
|
+
interface's own method declarations are bivariant. ⇒ this is the one shape in the tree where
|
|
834
|
+
narrowing the protocol reddens a consumer rather than passing it by, and that file's own
|
|
835
|
+
comment had already predicted it.
|
|
836
|
+
- `@object-ui/plugin-view` — `ObjectView.buildFormSchema` asserted an untyped record bag's id
|
|
837
|
+
as `string | number | undefined`. The assertion now states the protocol. ⛔ Deliberately not
|
|
838
|
+
`String(...)`: a reader-side coercion is the option the ruling refused.
|
|
839
|
+
|
|
840
|
+
**Ruling.** Director batch #195 item 1, letter **A**, maintainer 「同意」 2026-09-20, standing
|
|
841
|
+
on batch #136 item 5 letter **B**. **B** (leave the fifth door wide and document the asymmetry
|
|
842
|
+
as deliberate) and **C** (convert at each reader) were both refused — C by name, because eight
|
|
843
|
+
conversion sites contradict the rule's own aim that the conversion live in one typed place.
|
|
844
|
+
|
|
845
|
+
**Pins.** `data-source-id-surface-9511.test.ts` gains the `findOne` rows it previously and
|
|
846
|
+
deliberately withheld, plus an absence row so the retired "this door is held open" prose
|
|
847
|
+
cannot return. `authorable-record-id-string-9511.test.ts` is new and pins the parse half —
|
|
848
|
+
the refusal, its prescription, the string spellings, and both control directions.
|
|
849
|
+
|
|
850
|
+
⚠️ **The ruling's execution note asks for the `KnownDrift` / mirror-parity ledger rows to
|
|
851
|
+
shrink in the same change; that is an instruction over an EMPTY SET.** Measured in
|
|
852
|
+
`zod-mirror-parity.test.ts`: `resourceId` appears **0** times and `recordId` **1**, and that
|
|
853
|
+
one hit is prose about an `onNavigate` signature rather than a ledger row. There is nothing to
|
|
854
|
+
shrink, and it is said here so the next reader does not go looking for rows that never existed.
|
|
855
|
+
- 63bf47d: `object-form`'s two seed keys now merge PER MEMBER. `initialData` is registered as
|
|
856
|
+
the "alternate spelling of `initialValues` … read FIRST", and every presentation
|
|
857
|
+
arm implemented that by choosing between the two as WHOLE OBJECTS —
|
|
858
|
+
`schema.initialData || schema.initialValues`. `||` tests the object and never its
|
|
859
|
+
size, and `{}` is truthy, so the two ways an author loses data were:
|
|
860
|
+
|
|
861
|
+
- an EMPTY `initialData` — the shape a `?? {}` producer hands over when it has
|
|
862
|
+
nothing to contribute — blanked a populated `initialValues` completely; and
|
|
863
|
+
- a PARTIAL `initialData` naming one member dropped every other `initialValues`
|
|
864
|
+
member, including the ones it said nothing about.
|
|
865
|
+
|
|
866
|
+
Neither produced a warning, a diagnostic or an empty state: the form simply
|
|
867
|
+
opened blank where it used to open seeded.
|
|
868
|
+
|
|
869
|
+
⚠️ **Behaviour change.** A page that used an empty or partial `initialData` to
|
|
870
|
+
BLANK a form now gets the merge its description promised — `initialData` wins per
|
|
871
|
+
member, `initialValues` supplies the rest. A page relying on the blanking must
|
|
872
|
+
stop authoring `initialValues` on that node, or author the blank members
|
|
873
|
+
explicitly (an explicit `null` member is still a value, not an absence).
|
|
874
|
+
|
|
875
|
+
One shared `resolveInitialRecord(schema)` replaces the expression at every read
|
|
876
|
+
site across every presentation arm — the flat form, Modal, Drawer, Tabbed, Split,
|
|
877
|
+
Wizard and the master-detail parent form, which inherits it. Both read shapes go
|
|
878
|
+
through it, and both keep the object schema's declared `defaultValue`s under the
|
|
879
|
+
authored record — the sectioned arms at seed time through
|
|
880
|
+
`seedCreateValues(objectSchema, …, ctx)`, the flat form one composition later at
|
|
881
|
+
render. Neither layers an inline `customFields` member's own `defaultValue`, which
|
|
882
|
+
is read from object metadata only. Each site is pinned rather than assumed.
|
|
883
|
+
|
|
884
|
+
Registration descriptions are unchanged in substance; the two that quoted the
|
|
885
|
+
deleted `||` expression now state the per-member precedence instead.
|
|
886
|
+
- 5a311a3: `object-form.customFields` now MERGES over the metadata-generated field set, as its
|
|
887
|
+
registered description always promised (objectui#9778, maintainer ruling 2026-09-18,
|
|
888
|
+
director seat batch #161 item 5).
|
|
889
|
+
|
|
890
|
+
**Behaviour change, deliberately.** Hosts that used `customFields` as a full
|
|
891
|
+
replacement now see the metadata-generated members too. Before this change a
|
|
892
|
+
non-empty `customFields` replaced the generated set outright and the object's schema
|
|
893
|
+
was never even fetched; the registration has always described the other thing —
|
|
894
|
+
"Field definitions merged over the set generated from object metadata. With inline
|
|
895
|
+
definitions and no data source, this becomes the only field source." The per-member
|
|
896
|
+
merge the prose describes already existed as code in `ObjectForm` — the
|
|
897
|
+
`customFields?.find((f) => f.name === name)` lookup inside the metadata branch — and
|
|
898
|
+
was unreachable, because that branch ran only when `customFields` was absent or
|
|
899
|
+
empty, i.e. only when the lookup had nothing to find.
|
|
900
|
+
|
|
901
|
+
The merge takes three directions, one pinned case each in
|
|
902
|
+
`objectFormCustomFieldsMembers-8071.test.tsx`:
|
|
903
|
+
|
|
904
|
+
- **override** — a member naming a declared field supplies that field's whole
|
|
905
|
+
definition, in the generated set's position, inheriting nothing from it;
|
|
906
|
+
- **keep** — a declared field no member names still renders, from object metadata;
|
|
907
|
+
- **append** — a member naming a field the metadata never declares is added after
|
|
908
|
+
the generated set, in authored order.
|
|
909
|
+
|
|
910
|
+
**Unchanged where there is nothing to merge over.** With no data source (or no
|
|
911
|
+
`objectName`) there is no generated set, so the members remain the only field
|
|
912
|
+
source — the registration's second sentence, and the shape `EmbeddableForm` uses,
|
|
913
|
+
which deliberately passes no data source once inline members are present. An object
|
|
914
|
+
the adapter cannot describe now falls back to that same members-only source rather
|
|
915
|
+
than replacing a form that used to render with an error panel.
|
|
916
|
+
|
|
917
|
+
**Migration.** ⛔ No "replace mode" option is added: a host that wants a bespoke set
|
|
918
|
+
declares its own form. Two routes exist for a host that wants exactly its member
|
|
919
|
+
list against an object that HAS metadata, both through the existing `fields`
|
|
920
|
+
whitelist, which narrows the generated set the members merge over (measured):
|
|
921
|
+
`fields: []` leaves the members as the whole set, and `fields: ['customer']` renders
|
|
922
|
+
that one generated field plus the members. Authors who intended the documented merge
|
|
923
|
+
all along need to change nothing.
|
|
924
|
+
- 8813335: `object-form`: one rule for section divider rows on the default, modal and drawer layouts,
|
|
925
|
+
and there a section's own settings apply whether or not it has a heading (objectui#9849 step
|
|
926
|
+
two, director ruling letter E). The split, tabbed and wizard layouts are not changed.
|
|
927
|
+
|
|
928
|
+
Before this change, the default, modal and drawer layouts gave four different answers for a
|
|
929
|
+
section with no `label`. Some drew no row, some drew a row carrying only the blurb, and some
|
|
930
|
+
drew a full row. Whether the section's `visibleWhen` predicate and its `collapsed` /
|
|
931
|
+
`collapsible` pair were honoured depended on which of those answers the layout gave.
|
|
932
|
+
There is now one rule:
|
|
933
|
+
|
|
934
|
+
- A section's `visibleWhen` predicate gates its fields on these three layouts, heading or not. A
|
|
935
|
+
section with a `description` and no `label` on the default layout used to show its fields
|
|
936
|
+
even when its predicate was false. That group is now hidden, like a titled one.
|
|
937
|
+
- The divider row is drawn when the section has a `label` or a `description`. With a
|
|
938
|
+
`description` alone it is the blurb-only row (no heading, just the blurb). The drawer's
|
|
939
|
+
explicit `sections` path used to push a row for every section; it follows the same rule now.
|
|
940
|
+
- The collapse control sits on that row. A blurb-only row can now collapse its section: on the
|
|
941
|
+
default layout, `collapsed: true` on a section with a `description` and no `label` used to be
|
|
942
|
+
ignored. It now starts closed, and clicking the blurb opens it.
|
|
943
|
+
- A section that declares `collapsible` or `collapsed` but has neither a `label` nor a
|
|
944
|
+
`description` has no row to hold the control. It renders open, and a console warning says
|
|
945
|
+
so: "collapsible section has no heading or description to carry its control". Its fields
|
|
946
|
+
are never hidden without a control.
|
|
947
|
+
- The modal layout now honours `collapsible` / `collapsed`, both for explicit `sections`
|
|
948
|
+
(`ModalFormSectionConfig` gains the two members) and for sections derived from the
|
|
949
|
+
object's `fieldGroups`. Before, it ignored both and always drew every field. A modal with
|
|
950
|
+
`contentLayout: 'tabbed'` and more than one explicit section draws tabs, which do not
|
|
951
|
+
collapse, so that case is unchanged; with a single section, or with sections derived from
|
|
952
|
+
`fieldGroups`, it renders stacked and honours the pair.
|
|
953
|
+
- 3c9fca3: Create forms pre-fill the `current_user` defaultValue token with the acting user (#5683). `PermissionContextValue` gains `userId` (from `/me/permissions`; `null` = unknown), and the create-form seeding resolves `defaultValue: 'current_user'` on `user` / `lookup→sys_user` fields to that id — the same value the engine stamps at insert, so the pre-fill is a preview of the server's own resolution, not a second default contract. Unknown user (no provider / anonymous / role-based provider) seeds nothing and keeps the omit-and-let-the-engine-resolve behavior. `NOW()` and CEL defaults stay server-owned.
|
|
954
|
+
- ebce5a3: `object-grid` / `object-form` / `detail-view` resolve their data source the same way, and a block that resolves none says so
|
|
955
|
+
|
|
956
|
+
The three object-bound blocks disagreed about how the data-source adapter reached
|
|
957
|
+
them. `object-grid` and `object-form` were registered through wrappers that read
|
|
958
|
+
it from `SchemaRendererProvider` context; `detail-view` was registered as the raw
|
|
959
|
+
component, which reads a React `dataSource` prop. `SchemaRenderer` itself reads
|
|
960
|
+
only context, so the two wirings were mutually exclusive: measured with correct
|
|
961
|
+
keys in every cell, provider wiring gave the grid `find` 1 and the detail view
|
|
962
|
+
`findOne` 0, and prop wiring gave exactly the reverse. Neither reported anything.
|
|
963
|
+
|
|
964
|
+
All three now resolve the adapter through one rule — an explicit `dataSource`
|
|
965
|
+
prop first, the provider context second. This is additive: `detail-view` keeps
|
|
966
|
+
its prop form (and direct `<DetailView dataSource={…} />` callers are untouched),
|
|
967
|
+
`object-form` gains a prop form it did not have, and `object-grid` no longer
|
|
968
|
+
throws `useSchemaContext must be used within a SchemaRendererProvider` when a
|
|
969
|
+
page has no provider.
|
|
970
|
+
|
|
971
|
+
And the silence is over. A block in this family that resolves no adapter renders
|
|
972
|
+
a **No data source resolved** panel naming the block, the object it was about to
|
|
973
|
+
read, and the ancestor that injects the adapter — instead of a header-only grid,
|
|
974
|
+
a field-less form card, or nothing at all. The check is opt-in per block, so a
|
|
975
|
+
placement with inline rows, inline `customFields`, an inline record or an `api`
|
|
976
|
+
endpoint is untouched.
|
|
977
|
+
|
|
978
|
+
New from `@object-ui/react`: `useResolvedDataSource`, `NoDataSourcePanel`,
|
|
979
|
+
`noDataSourceMessage`, and a `requiresDataSource` prop on `ElementDataSourceGate`.
|
|
980
|
+
|
|
981
|
+
### Patch Changes
|
|
982
|
+
|
|
983
|
+
- 142fdfd: fix(plugin-form): `customFields` merges on the drawer and modal arms too
|
|
984
|
+
|
|
985
|
+
The registered description of `object-form.customFields` is one sentence for
|
|
986
|
+
every `formType`: "Field definitions merged over the set generated from object
|
|
987
|
+
metadata." The default arm has merged since objectui#9778, but an `object-form`
|
|
988
|
+
with `formType: 'drawer'` or `'modal'` (and a `DrawerForm` / `ModalForm` mounted
|
|
989
|
+
directly) still REPLACED the generated fields with the members, so the same key
|
|
990
|
+
meant two different things depending on the arm.
|
|
991
|
+
|
|
992
|
+
All three arms now resolve the members through one rule: a member naming a
|
|
993
|
+
declared field is that field's whole definition, in its position; a declared
|
|
994
|
+
field no member names still renders; a member naming nothing declared is
|
|
995
|
+
appended after the generated set, in authored order. With no data source the
|
|
996
|
+
members remain the only field source.
|
|
997
|
+
|
|
998
|
+
`customFields` also no longer switches off the object's `fieldGroups` fallback
|
|
999
|
+
or the modal's auto-layout (and so its auto-sized width), matching the default
|
|
1000
|
+
arm: the groups are derived over the merged field set.
|
|
1001
|
+
|
|
1002
|
+
⚠️ Behaviour change for authors who relied on the drawer or modal replacing the
|
|
1003
|
+
generated set: the object's other fields now appear alongside the members. To
|
|
1004
|
+
narrow the generated set, list the fields to draw in the `fields` whitelist, as
|
|
1005
|
+
on the default arm; the members are drawn either way.
|
|
1006
|
+
- e026e15: A form no longer emits the columns the server owns, and a master-detail batch sends only the cells the user changed (objectui#10108).
|
|
1007
|
+
|
|
1008
|
+
**Clause-②: no** — nothing an author writes changes, and no export moves. `SERVER_OWNED_FIELD_NAMES` is module-scoped inside the package (the package publishes `.` only, from `index.tsx`, which does not re-export `sanitize`), so this is a change to what the client PUTS on the wire, not to the package's face.
|
|
1009
|
+
|
|
1010
|
+
**What it was.** The write-side roster in `sanitize.ts` and the render-side roster in `autoLayout.ts` were two hand-written copies of the same list, and they drifted. The render copy had learned to drop `owner_id`; the write copy never did. A form therefore showed the user business inputs only, and sent back every ownership and audit column it had read — `owner_id`, `owning_business_unit_id`, `created_by`, `updated_by` — while dropping `created_at` and `organization_id` from the same payload, because those two happened to be in the write copy.
|
|
1011
|
+
|
|
1012
|
+
That is not a cosmetic difference. The platform refuses a write to a system-managed ownership column unless the caller holds the transfer grant, and it cannot tell a round-trip of the value it just served from an attempted ownership transfer. Echoing an UNCHANGED `owner_id` back was therefore a 403 for every role without that grant — and a master-detail save commits as one atomic batch, so one echoed column on one row refused the parent and every sibling row with it. The roles this blocked are the line-entry roles the feature exists for, which by definition do not hold the transfer grant.
|
|
1013
|
+
|
|
1014
|
+
**What changed, in observable terms.**
|
|
1015
|
+
|
|
1016
|
+
- One roster now serves both readings, so a form never writes a field it refuses to render. The unified list adds `owning_business_unit_id`, `company_id`, `space`, `_id`, `__v`, `created`, `modified`, `modified_by` and the camelCase spellings of the ownership FKs to BOTH sides. ⚠️ If an auto-laid-out form in your app was rendering one of those as an editable input, it no longer does, and no form writes one.
|
|
1017
|
+
- `sanitizeFormData` also refuses any field the object schema marks `system: true`, whatever it is called — so the next column the platform injects does not need an edit here to be refused.
|
|
1018
|
+
- A master-detail edit batch sends only the cells that differ from the loaded snapshot, and a row where nothing moved produces no operation at all (it used to be rewritten in full on every save). The comparison resolves everything it cannot settle towards SENDING, so a real edit is never dropped: `1000` and `'1000'` read as different, and a snapshot that does not show a row already linked still gets its FK re-asserted.
|
|
1019
|
+
- The lifecycle bookkeeping columns (`locked`, `instance_state`, `deleted`, `is_deleted`) are deliberately NOT in the roster — they are plausibly author-writable, and adding them on the strength of the pattern alone would silently drop a value a form was asked to persist.
|
|
1020
|
+
- 80c5412: A form no longer submits — nor offers — a field the CALLER may read but not edit.
|
|
1021
|
+
|
|
1022
|
+
**Clause-②: no** — no exported symbol is added, removed, renamed or retyped, no key on a published payload moves, and no accept set is relaxed. `sanitizeFormData` gains an optional third argument and `fieldWriteGate` / `applyFieldPermissions` are new, but `@object-ui/plugin-form` publishes `.` only, from `index.tsx`, which re-exports neither module — measured on the built `dist/index.d.ts`, where neither name appears. `LookupField`'s props are unchanged. What moves is what the client PUTS on the wire and which controls it draws.
|
|
1023
|
+
|
|
1024
|
+
**What it was.** The platform refuses a write to a field the caller's permission set marks `editable: false`, and it cannot tell a round-trip of the value it just served from an attempted write. A form echoing an UNCHANGED `score` back is therefore a 403 for the whole save, even when the user touched only a field they *are* allowed to edit — so the standard edit form was unusable for any role with a field-level restriction on it. The list view's inline grid succeeded on the same record for the same user, because it sends only the changed cell.
|
|
1025
|
+
|
|
1026
|
+
The verdict that answers 「may this caller edit this field」 already existed and one path already used it: `checkField(object, field, 'write')` in `@object-ui/permissions`, reading the server's `/me/permissions` grant. What had gone wrong is that each form container spelled its own COPY of the strip and of the render pass, and the family had drifted — the same defect class as objectui#10108, one layer up. Measured on one record with one permission set: the simple form and the modal withheld `score` while the drawer sent it, and the drawer rendered it as a live input while the other two rendered it disabled. One family, three answers; the third container carried neither half.
|
|
1027
|
+
|
|
1028
|
+
**What changed, in observable terms.**
|
|
1029
|
+
|
|
1030
|
+
- The field-level verdict now arrives at the ONE outbound filter as a predicate (`sanitizeFormData`'s `canEdit`), instead of as a strip loop written out after each container's call. ⚠️ `DrawerForm` previously sent every displayed field regardless of the caller's field permissions; it no longer does. `ObjectForm` and `ModalForm` already withheld the refused field, and still do — their loops were correct, they were just copies.
|
|
1031
|
+
- The render pass is likewise one function for all three containers. ⚠️ `DrawerForm` previously drew a field the caller may read but not edit as a live input; it now draws it read-only and disabled, exactly as the other two already did. A field the caller may not READ is dropped, also as before.
|
|
1032
|
+
- Both halves stay fail-open with no `PermissionProvider` / `MePermissionsProvider` mounted, unchanged: a standalone form, a designer preview and a guest surface have no resolvable principal, and the server still enforces.
|
|
1033
|
+
- A lookup's selected chip no longer offers its remove ✕ when the field is disabled. ⚠️ This is how BOTH refusals reach the widget — a field the object declares `readonly` is folded into `disabled` by the form's section builder, and a field the permission set refuses is marked disabled by the pass above — so a reporter could previously clear a master-detail parent the server would then refuse to unset. The trigger and the browse button were already disabled; the chip's ✕ was the one control the gate had missed. The chips themselves stay: the value is readable, only the affordance goes.
|
|
1034
|
+
|
|
1035
|
+
**Deliberately not here.** Submitting only DIRTY fields is objectui#10156, filed with its own risk argument (a false CLEAN silently drops a user's edit and returns 200). Naming the refused field in the 403 instead of the generic console message is owned by the producing side.
|
|
1036
|
+
- 2dd4d3f: An edit form now writes only the fields that changed (objectui#10156).
|
|
1037
|
+
|
|
1038
|
+
**Clause-②: no.** No exported symbol, type or prop changes. `@object-ui/plugin-form` publishes `.` only, from `index.tsx`, and `index.tsx` re-exports neither `sanitize` nor `masterDetailTx`. After a build, `dist/index.d.ts` names none of the new helpers. The change is in what the client sends, and ⚠️ in what a host `submitHandler` receives in edit mode (see below).
|
|
1039
|
+
|
|
1040
|
+
**Before.** A master-detail child row already sent only the cells that differed from its loaded snapshot (objectui#10108). The other two edit payloads did not. The plain record edit `PATCH` and the parent operation of a master-detail batch sent every sanitized field on every save, including fields the user never touched. With the concurrency guard, a `409` followed by **Overwrite** therefore rewrote every field, not only the ones this user changed.
|
|
1041
|
+
|
|
1042
|
+
**What changed, in observable terms.**
|
|
1043
|
+
|
|
1044
|
+
- In edit mode, `ObjectForm`, `ModalForm` and `DrawerForm` compare their save with the record they read through `findOne`, and write only the fields that differ. The parent operation of a master-detail batch follows, because its header is a simple `ObjectForm`.
|
|
1045
|
+
- There is one comparison, shared with the master-detail child rows. It sends anything it cannot prove unchanged. `null` and `undefined` count as the same value. `null` and `''` are different. So are `5` and `'5'`, a lookup id and its expanded object, a `Date` and a date string, and two objects whose keys come in a different order. A field the form changed by itself after the read, such as a cascade clear, is sent.
|
|
1046
|
+
- A save with nothing changed still sends the full sanitized payload. It stays a real request, with the same concurrency guard and a real server record for `onSuccess`.
|
|
1047
|
+
- A form that did not read the record itself still sends every field. That covers a create, a record supplied as `initialData`, and inline `customFields`.
|
|
1048
|
+
- After a successful save, the form counts the fields it just wrote as saved. A form that stays open compares its next save with the record as it is now. Changing a field back to its first-read value is therefore still sent.
|
|
1049
|
+
- The concurrency guard is unchanged by this rule. The update carries `ifMatch` = the `updated_at` the form read (after a save, a form that stays open sends the `updated_at` that save returned instead, objectui#10565), and a `409` still offers **Keep editing** or **Overwrite**. **Overwrite** now resends only the changed fields.
|
|
1050
|
+
- ⚠️ A host `submitHandler` on an edit form receives the payload the form would have written. That is the changed fields, or the full sanitized payload when nothing changed. A host that needs the whole record must read it itself. In this repository, only `MasterDetailForm` passes a `submitHandler` to an edit form. Its header form receives the changed fields. Its row editor has no `recordId`, so it still receives every value.
|
|
1051
|
+
- The JSDoc of `ObjectFormSchema.submitHandler` in `@object-ui/types`, and its copies on `ModalFormSchema` and `DrawerFormSchema`, now say what an edit-mode handler receives.
|
|
1052
|
+
|
|
1053
|
+
**Not covered at this change.** The `tabbed`, `wizard` and `split` variants had save paths of their own and still sent every value they held. That included a master-detail header laid out `tabbed`, and a simple form whose mobile `stepper` option shows it one step at a time through the wizard.
|
|
1054
|
+
|
|
1055
|
+
⚠️ **Dated note, 2026-09-25 — those variants have since been covered — objectui#10563.** Later in this same release the `tabbed`, `wizard` and `split` save paths, the `stepper` route and a master-detail header laid out `tabbed` write through the same sequence as the simple form: the same strip, and on an edit the same comparison against the record they read. The rest of this entry is kept as the reading of this change; the objectui#10563 entry states what those layouts now send.
|
|
1056
|
+
- b809375: A `record:line_items` grid no longer offers a cell the CALLER may read but not edit (objectui#10163).
|
|
1057
|
+
|
|
1058
|
+
**What it was.** `80c54122e` taught the record-form containers to render a field the caller's permission set marks `editable: false` as disabled, through one shared render pass. The line-items panel stayed outside that pass: it read `readonly` and nothing else, so on the same page the same column rendered as a live, editable cell — inviting an edit the server refuses.
|
|
1059
|
+
|
|
1060
|
+
**What changed, in observable terms.**
|
|
1061
|
+
|
|
1062
|
+
- The panel's columns now go through the same render pass the form containers use, adapted to the grid: a column the caller may read but not edit renders its cells locked (on loaded rows and on the entry row alike), and a column the caller may not read is omitted, exactly as the forms omit such a field. Columns the caller may edit are unchanged.
|
|
1063
|
+
- Adding and removing lines are unchanged: they still follow the panel's `readonly`.
|
|
1064
|
+
- With no permission provider mounted the grid is exactly as before — every cell editable. That fail-open posture is objectui#10161's and is not moved here; the server still enforces.
|
|
1065
|
+
|
|
1066
|
+
**Clause-②: no** — no exported symbol is added, removed, renamed or retyped. The new column adapter lives in a module `@object-ui/plugin-form`'s entry does not re-export. No authored key changes meaning and no accept set is relaxed.
|
|
1067
|
+
|
|
1068
|
+
**Not in this entry.** The master-detail form's own child grid (`object-master-detail-form`) is covered by the companion objectui#10163 entry.
|
|
1069
|
+
- 6099dd8: A master-detail form's child grid no longer offers a cell the CALLER may read but not edit (objectui#10163).
|
|
1070
|
+
|
|
1071
|
+
**What it was.** The companion objectui#10163 entry routed the `record:line_items` panel's columns through the render pass the record-form containers share. The master-detail form's own child grid (`object-master-detail-form`) renders through the same line-items widget and read no field-level permission, so a child column the caller's permission set marks `editable: false` rendered as a live, editable cell — inviting an edit the server refuses.
|
|
1072
|
+
|
|
1073
|
+
**What changed, in observable terms.**
|
|
1074
|
+
|
|
1075
|
+
- Each child collection's columns now go through that same pass: a child column the caller may read but not edit renders its cells locked (on loaded rows and on the entry row alike), and a child column the caller may not read is omitted, exactly as the line-items panel does. Columns the caller may edit are unchanged.
|
|
1076
|
+
- With no permission provider mounted the grid is exactly as before — every cell editable. That fail-open posture is objectui#10161's and is not moved here; the server still enforces.
|
|
1077
|
+
- The per-row "expand to full form" editor is unchanged: it is a record form and already rendered through the form containers' pass.
|
|
1078
|
+
|
|
1079
|
+
**Clause-②: no** — no exported symbol is added, removed, renamed or retyped, and no prop changes. No authored key changes meaning and no accept set is relaxed.
|
|
1080
|
+
- a04b06d: A wizard no longer writes a record without a file whose upload was still running when the user
|
|
1081
|
+
pressed Next (objectui#10180).
|
|
1082
|
+
|
|
1083
|
+
To reproduce: pick a file on a wizard step, press Next before the upload finishes, then press
|
|
1084
|
+
Create on the last step before the upload settles. The record was written WITHOUT the attachment
|
|
1085
|
+
and the wizard reported success.
|
|
1086
|
+
|
|
1087
|
+
The upload itself was never cancelled. `FileField` and `ImageField` do not abort an upload
|
|
1088
|
+
when their step unmounts, and when it finishes the value still reaches the form. What went
|
|
1089
|
+
missing was the upload's REPORT. A widget publishes "an upload is in flight" as its own
|
|
1090
|
+
React state, so leaving the step unmounted the widget and ended the report while the
|
|
1091
|
+
upload kept running. The final-commit gate objectui#10166 put on the wizard then read
|
|
1092
|
+
"nothing uploading": Create was enabled and labelled "Create", and pressing it in that
|
|
1093
|
+
window wrote the record before the file arrived.
|
|
1094
|
+
|
|
1095
|
+
`FileField` and `ImageField` in `@object-ui/fields` now also hold the enclosing uploading
|
|
1096
|
+
scope for the upload's own lifetime; `FileCell`, the line-item grid cell, does not. The hold
|
|
1097
|
+
is taken when the upload starts and released once the upload settles, whether or not the
|
|
1098
|
+
widget is still mounted: after the value has been handed to the form when it succeeds, and
|
|
1099
|
+
on a failure too. While a file from an earlier step is still uploading, the wizard gives the
|
|
1100
|
+
same answer objectui#10166's gate gives for any other in-flight upload. Create reads
|
|
1101
|
+
"Uploading…" and is disabled, the wizard shows the reason it already gives, and a submit
|
|
1102
|
+
that reaches it anyway is refused, not queued. A Create pressed once the upload settles
|
|
1103
|
+
writes the record with the file. `Next` is not gated, and no new element or message is
|
|
1104
|
+
introduced: the label and the reason are the ones objectui#10166 added.
|
|
1105
|
+
|
|
1106
|
+
No exported symbol, prop or type changes. `useUploadingScope().anyUploading` now stays
|
|
1107
|
+
true until an upload that `FileField` or `ImageField` started inside the scope has settled,
|
|
1108
|
+
instead of going false when that widget unmounts. A widget that publishes only through the
|
|
1109
|
+
exported `useUploadingSignal` still releases its report when it unmounts, as before, so an
|
|
1110
|
+
upload such a widget leaves running after it unmounts is not visible to the scope. A hold
|
|
1111
|
+
can last only as long as its upload is unsettled, which is the same limit a mounted widget's
|
|
1112
|
+
report already has.
|
|
1113
|
+
- 88a4ef6: fix(plugin-form): `DrawerForm` no longer paints an editable form before the record it edits has loaded
|
|
1114
|
+
|
|
1115
|
+
Opening an edit drawer showed the form empty and editable while the record read
|
|
1116
|
+
was still in flight, and when the record arrived it replaced whatever had been
|
|
1117
|
+
typed there — the value was gone from the screen and from the save, with no
|
|
1118
|
+
warning. The drawer already refused this when switching from one record to
|
|
1119
|
+
another; it now refuses it on the first load too, keeping its loading state
|
|
1120
|
+
until the record read lands.
|
|
1121
|
+
|
|
1122
|
+
`DrawerForm` builds its fields and reads its record in two effects that both run
|
|
1123
|
+
when the object schema arrives. The field-building effect used to end the
|
|
1124
|
+
loading state unconditionally; it now leaves that to the record read whenever
|
|
1125
|
+
one is outstanding.
|
|
1126
|
+
- 6ea68e6: fix(plugin-form): the default `object-form` layout draws a section's members in the order the section lists them, and applies each section entry's overrides
|
|
1127
|
+
|
|
1128
|
+
With explicit `sections` and no `formType`, `ObjectForm` resolved each
|
|
1129
|
+
section's members with a name filter over the form's field list. The members
|
|
1130
|
+
came out in the order of that list (top-level `fields`, or the object's own
|
|
1131
|
+
field order), not in the order the section wrote them, and a section entry
|
|
1132
|
+
written as a spec `FormFieldSchema` object (`{ field: 'note', label: …,
|
|
1133
|
+
required: true }`) kept only its `visibleOn`, `colSpan` and `span`. Every other
|
|
1134
|
+
override was dropped without a warning. An authored `required: true` was
|
|
1135
|
+
therefore not enforced, and `label`, `readonly`, `hidden`, `helpText`,
|
|
1136
|
+
`placeholder`, `options`, `min` / `max`, `widget` and `visibleWhen` did nothing.
|
|
1137
|
+
The `drawer`, `modal`, `tabbed`, `wizard` and `split` layouts already honoured
|
|
1138
|
+
all of them, so the same `sections` block laid out differently depending on
|
|
1139
|
+
`formType`.
|
|
1140
|
+
|
|
1141
|
+
The default layout now builds sections the way the other five do. Members
|
|
1142
|
+
render in the section's order, and every entry override applies: `required`
|
|
1143
|
+
refuses an empty submit, and `visibleWhen` hides the member until the record
|
|
1144
|
+
satisfies it. Two things stay as they were. First, a section member that the
|
|
1145
|
+
top-level `fields` does not list is still dropped, with its console warning,
|
|
1146
|
+
because the two keys intersect. Second, at this change the form's own field
|
|
1147
|
+
definition remains the starting point for an override, so an override does not
|
|
1148
|
+
lift a managed object's field lock, and field-level security still hides or
|
|
1149
|
+
locks each member. The same fix reaches `object-master-detail-form`, whose
|
|
1150
|
+
parent form renders through this layout.
|
|
1151
|
+
|
|
1152
|
+
⚠️ **Dated note, 2026-09-25 — the managed-object field lock no longer rides the form's own field definition — objectui#10612.**
|
|
1153
|
+
Later in this same release the lock moved out of this layout's field generator
|
|
1154
|
+
into the one field-gate step every layout draws its fields through, after the
|
|
1155
|
+
section builder and together with field-level security. An override still does
|
|
1156
|
+
not lift the lock, but the reason is now that the lock is applied after the
|
|
1157
|
+
override, on every layout, rather than that it rides the definition the
|
|
1158
|
+
override starts from. The rest of this entry is kept as the reading of this
|
|
1159
|
+
change.
|
|
1160
|
+
- e0f8202: The `tabbed`, `wizard` and `split` form layouts now write what the simple form writes (objectui#10563).
|
|
1161
|
+
|
|
1162
|
+
**Clause-②: no.** No exported symbol, type or prop changes. The shared sequence lives in an internal module that `index.tsx` does not re-export. The change is in what the client sends, and ⚠️ in what a host `submitHandler` receives from these layouts (see below).
|
|
1163
|
+
|
|
1164
|
+
**Before.** The simple form, `ModalForm` and `DrawerForm` strip a save before sending it, and on an edit they send only the fields that differ from the record they read (objectui#10108, `80c54122e`, objectui#10156). `TabbedForm`, `SplitForm` and `WizardForm` did neither. An edit sent every value the form held: `id`, `owner_id`, `created_by`, `updated_at`, formula columns, and fields the caller's field-level security refuses. The server answered with a `403` or an unknown-field refusal. A create seeded with a whole record, such as a copy of an existing one, posted those columns too. A simple form whose mobile `stepper` option shows it one step at a time renders through `WizardForm`, so it had the same defect.
|
|
1165
|
+
|
|
1166
|
+
**What changed, in observable terms.**
|
|
1167
|
+
|
|
1168
|
+
- In edit mode, the `tabbed`, `wizard` and `split` layouts and the `stepper` route write only the fields that differ from the record they read with `findOne`. They use the same comparison as every other layout. A save with nothing changed still sends the full stripped payload, with the same concurrency guard.
|
|
1169
|
+
- On every save, create or edit, these layouts drop the fields a form never writes. That means server-owned columns, computed, formula and read-only columns, keys the object does not declare, and fields the caller's field-level security refuses. A field whose name is on the server-owned roster, such as `owner`, is not written even when an object declares it as an ordinary field. The other layouts already worked this way.
|
|
1170
|
+
- A field the caller may read but not edit now renders disabled on these layouts, as on the others, also when a section names it by a bare string. Before, it rendered as a live input. With the strip above, a value typed there would have been dropped while the save reported success.
|
|
1171
|
+
- After a save succeeds, a form that stays open compares its next save with the record as it now is.
|
|
1172
|
+
- ⚠️ A host `submitHandler` on one of these layouts now receives, in edit mode, the payload the form would have written. That is the changed fields, or the full stripped payload when nothing changed. A master-detail header laid out `tabbed` therefore sends only its changed fields in the parent operation, as a `simple` header already did.
|
|
1173
|
+
- With inline `customFields`, the object definition is not used for the strip, as on the simple form. An inline member may name a field the object does not declare, and its value is still sent.
|
|
1174
|
+
- An empty field whose runtime default the server resolves is left out of the payload whenever the form has no persisted record: `mode: 'create'`, or no `recordId`. That is the rule the simple form and the seeding of these layouts already use. Before, these layouts left it out only when `mode` was `create`.
|
|
1175
|
+
- The JSDoc of `ObjectFormSchema.submitHandler` in `@object-ui/types` no longer lists these layouts as exceptions. The copies on `TabbedFormSchema`, `SplitFormSchema` and `WizardFormSchema` now say what an edit-mode handler receives.
|
|
1176
|
+
- cc07476: A master-detail edit form that stays open after a save no longer re-creates the line items it just created, re-deletes the ones it just deleted, or drops a line cell changed back to its first-read value (objectui#10564).
|
|
1177
|
+
|
|
1178
|
+
**Clause-②: no.** No exported symbol, type or prop changes. The change is in what the form's next save sends.
|
|
1179
|
+
|
|
1180
|
+
**Before.** The form diffed every save against the child rows it read when it opened. A host that keeps the form mounted after a save (an `onSuccess` that does not navigate away) therefore got:
|
|
1181
|
+
|
|
1182
|
+
- a second copy of a line the first save created, because that row never learned its id and was created again on the next save;
|
|
1183
|
+
- a second `delete` of a line the first save had already deleted;
|
|
1184
|
+
- a lost edit when a line cell was changed, saved, and changed back to its first-read value: the second save sent no line operation and still reported success, and the server kept the first save's value.
|
|
1185
|
+
|
|
1186
|
+
**What changed, in observable terms.**
|
|
1187
|
+
|
|
1188
|
+
- After a successful edit save, the rows that save created take the ids the batch returned for them, and the collection's baseline takes on what the save wrote. The next save compares with the lines as they now stand. This is the same rule the parent record already followed after a save (objectui#10156).
|
|
1189
|
+
- A save that fails advances nothing, neither the parent nor the lines, so a retry still sends every operation.
|
|
1190
|
+
- The lines take no input while a save is in flight (objectui#10631), so nothing typed during a save can separate a line the save created from its id.
|
|
1191
|
+
- Create mode is unchanged: a successful create still clears the lines and the header for the next entry.
|
|
1192
|
+
- f99f9cd: An edit form that stays open no longer meets the conflict dialog over its own earlier save (objectui#10565).
|
|
1193
|
+
|
|
1194
|
+
**Clause-②: no.** No exported symbol, type or prop changes. The change is in which `ifMatch` an edit save sends.
|
|
1195
|
+
|
|
1196
|
+
**Before.** Every edit layout (the simple form, `modal`, `drawer`, `tabbed`, `split` and `wizard`) sent `ifMatch` = the `updated_at` of the record it read with `findOne`, on every save. None of them refreshes that record after a save. The first save moves the stored `updated_at`, so the second save from the same open form sent a version the server no longer held. A server that enforces `ifMatch` answered `409 CONCURRENT_UPDATE`, and the user was offered **Keep editing** or **Overwrite** over a conflict with their own first save.
|
|
1197
|
+
|
|
1198
|
+
**What changed, in observable terms.**
|
|
1199
|
+
|
|
1200
|
+
- After a save lands, the next save of the same record from the same open form sends the `updated_at` that save returned, for every layout. The **Overwrite** retry counts as a save, so the save after it sends the version the overwrite returned.
|
|
1201
|
+
- A genuine conflict still answers `409`: if someone else saves the record between two saves, the dialog still opens.
|
|
1202
|
+
- If the form reads the record again and gets a different `updated_at`, the new read's token is sent.
|
|
1203
|
+
- A record read without an `updated_at` is still saved without `ifMatch`. When a data source's `update` resolves without an `updated_at`, the next save sends the read's token, as before.
|
|
1204
|
+
- What a save writes is unchanged: an edit still sends only the fields that differ from the record the form read (objectui#10156).
|
|
1205
|
+
- 4a3d500: `object-form` in edit mode re-reads its record when the data-invalidation bus (`notifyDataChanged` from `@object-ui/react`) reports a change to that record, its object, or everything (objectui#10572). The re-read is gated on pristine: an untouched form refreshes in place without remounting, while a form holding unsaved input keeps both the typed values and the version token its edit started from, so a real conflict still surfaces at save. One held re-read is replayed once the edit is saved or the form returns to pristine. A change to another record of the object reads nothing.
|
|
1206
|
+
- 41dcbd5: Every `object-form` layout now locks a managed object's fields, as the simple form does (objectui#10612).
|
|
1207
|
+
|
|
1208
|
+
**Clause-②: no.** No exported symbol, type or prop changes. The shared step lives in `fieldWriteGate.ts`, an internal module that `index.tsx` does not re-export. What changes is which inputs the `drawer`, `modal`, `tabbed`, `split` and `wizard` layouts draw enabled.
|
|
1209
|
+
|
|
1210
|
+
**Before.** ADR-0092 D4 locks a form on a managed object: when the object's CRUD affordance for the form's mode is closed, every field is disabled. The affordance comes from the object's `managedBy` bucket, its `userActions` opt-in (`create` on a create form, `edit` on an edit form) and the server's effective API operations. Only the simple form applied this lock, inside its own field generator. The `drawer`, `modal`, `tabbed`, `split` and `wizard` layouts drew live inputs on, for example, a `managedBy: 'better-auth'` object with no `userActions.create`, and the server then refused the save. The layouts disagreed with each other on the same object for the same user.
|
|
1211
|
+
|
|
1212
|
+
**What changed, in observable terms.**
|
|
1213
|
+
|
|
1214
|
+
- The `drawer`, `modal`, `tabbed`, `split` and `wizard` layouts, flat or sectioned, now disable every field of an object whose affordance for the form's mode is closed. This covers a managed bucket that does not open the mode, and any object whose `create` or `update` the server's effective API operations deny. The simple form did this already.
|
|
1215
|
+
- The lock and field-level security now run as one step that every layout calls on the fields it has resolved. A field the caller may not read is dropped, and a field they may read but not edit is locked, on every layout, as before.
|
|
1216
|
+
- ⚠️ On the simple form, an inline `customFields` member, and a section entry already written as a full field definition, are now locked with the rest of a managed object's fields. The lock used to be set only on the fields the simple form generated from the object, so those two escaped it.
|
|
1217
|
+
- The lock sets `disabled` only, as it did on the simple form, and the submit button stays on screen on every layout. The server's write guard is still what refuses the write. The notice that explains the lock, and the wizard's disabled Next, come with objectui#11000.
|
|
1218
|
+
- 5c61e52: A master-detail form no longer takes input into its lines, or sends a second batch, while its save is in flight. A slow save can no longer write the same records twice, or leave the lines it created without their ids (objectui#10631).
|
|
1219
|
+
|
|
1220
|
+
**Clause-②: no.** No exported symbol, type or prop changes. The line-item grids are given the `disabled` prop they already had.
|
|
1221
|
+
|
|
1222
|
+
**Before.**
|
|
1223
|
+
|
|
1224
|
+
- Save re-armed 1.5 s after it was clicked, whatever the batch was doing. With a batch slower than that, a second click sent a second batch built from the same baseline. In edit mode the lines that save created were created twice; in create mode the whole document, parent and lines, was.
|
|
1225
|
+
- The lines stayed editable during the save. A line the save creates takes the id the batch returns for it only while it is still the row object the batch was built from, and a grid with a sort field (a child field named `position`, `sort_order`, `sequence`, `line_no`, `line_number` or `sort`) replaces every row object on any change. One keystroke in such a grid during the save left every line the save created without its id, and the next save deleted and re-created all of them.
|
|
1226
|
+
- A save started by submitting the header form without the Save button (an implicit submission, when the header has a single text input) left Save and the lines enabled, and a second such submit sent a second batch.
|
|
1227
|
+
|
|
1228
|
+
**What changed, in observable terms.**
|
|
1229
|
+
|
|
1230
|
+
- Save stays disabled and reads "Saving…" until the batch succeeds or fails. A failed batch re-enables it, and the refusal is shown as before. The 1.5 s release is kept for a submit that never reaches the batch, such as a header that fails validation, so Save is never left stuck.
|
|
1231
|
+
- While a save is in flight every line-item grid is disabled: no cell takes input, and there is no empty trailing row, add, duplicate, remove or reorder. An open row editor ("expand to full form") is disabled too, "Apply" included. Both take input again once the save has settled.
|
|
1232
|
+
- A header-form submit that arrives while a batch is in flight is refused and sends nothing. The host's `onError` hears of it, and the save that is running keeps Save disabled until its own outcome.
|
|
1233
|
+
- The header fields stay editable during the save. In edit mode an edit made there is kept and sent by the next save; in create mode the header still clears for the next entry once the create lands.
|
|
1234
|
+
- The `record:line_items` panel's grid is disabled while its own save is in flight. That panel reloads its rows once the save lands, so a line edited in the meantime used to be overwritten by the reload, with the panel reading clean.
|
|
1235
|
+
- 1ea21c4: fix(plugin-form): `ModalForm` no longer paints an editable form before the record it edits has loaded
|
|
1236
|
+
|
|
1237
|
+
Opening an edit modal showed the form empty and editable while the record read
|
|
1238
|
+
was still in flight. When the record arrived it replaced whatever had been
|
|
1239
|
+
typed, and the save that followed sent the whole record with its original
|
|
1240
|
+
values. The value was gone from the screen and from the save, with no warning.
|
|
1241
|
+
The modal now keeps its loading state until the record read lands, as the edit
|
|
1242
|
+
drawer has done since objectui#10190.
|
|
1243
|
+
|
|
1244
|
+
`ModalForm` builds its fields and reads its record in two effects that both run
|
|
1245
|
+
when the object schema arrives. The field-building effect used to end the
|
|
1246
|
+
loading state unconditionally. It now leaves that to the record read whenever
|
|
1247
|
+
one is outstanding, through the same check the drawer uses, which both
|
|
1248
|
+
containers now share.
|
|
1249
|
+
- f9c06ef: Every data node that sends its own authored `filter` into a query now resolves the spec's context tokens first (objectui#10666). With `filter: [['owner', '=', '{current_user_id}']]` these nodes used to send the literal token; they now send the signed-in user's id, and `{current_org_id}` resolves to the active organization:
|
|
1250
|
+
|
|
1251
|
+
- `object-calendar`, `object-map` and `object-tree`, on both the object query and the inline (`provider: 'value'`) query;
|
|
1252
|
+
- `object-gantt`, `object-kanban` and `object-timeline`;
|
|
1253
|
+
- `record:related_list` and `record:line_items`, where the node's own filter is combined with the parent-record condition;
|
|
1254
|
+
- the `element:repeater`, `element:number` (both the `aggregate` filter and the `find` fallback) and `element:record_picker` page elements.
|
|
1255
|
+
|
|
1256
|
+
`list-view`, `object-grid` and `object-gallery` already did this, and are unchanged.
|
|
1257
|
+
|
|
1258
|
+
**New in `@object-ui/react`: `useResolvedFilter(filter, scope)`.** Pass it a node's authored filter and `useFilterScope()`. It resolves the filter through `@object-ui/core`'s shared `resolveFilterPlaceholders` and holds the result: re-rendering with an equal filter (even one rebuilt inline on every render) hands back the same value, so a fetch effect keyed on it does not run again, while a structurally different filter or a change of signed-in user or organization resolves again. A filter with no token to resolve is handed back as the value you passed in. The hold is the one `list-view` and `object-gallery` used privately; `plugin-list` now imports it from here, and no second copy remains in that package.
|
|
1259
|
+
|
|
1260
|
+
What changes for an app:
|
|
1261
|
+
|
|
1262
|
+
- A filter with no placeholders reaches the query as before, unchanged.
|
|
1263
|
+
- Nodes that re-queried whenever the host rebuilt an equal filter inline (`object-calendar`, `object-gantt`, `object-kanban`, `object-map`, `object-tree`) no longer issue those redundant queries.
|
|
1264
|
+
- Date macros such as `{today}` in these nodes' own filters are now resolved in the browser's local time, as they already are for `list-view`, `object-grid` and `object-view`. Before, they reached the server as literals, and the server resolved them in the tenant's time zone.
|
|
1265
|
+
|
|
1266
|
+
`@object-ui/types`: the `filter` docblocks on `ObjectMapSchema`, `ObjectTreeSchema`, `ObjectGanttSchema`, `ObjectCalendarSchema`, `ObjectKanbanSchema`, `ObjectChartSchema` and `ObjectGallerySchema` (and the matching zod descriptions) no longer say the filter is forwarded verbatim; they say its context tokens are resolved first. No type changes.
|
|
1267
|
+
- ee6f6c6: fix(plugin-form): the line-items panel clears its load error when a later load commits rows (objectui#10682, objectui#10683)
|
|
1268
|
+
|
|
1269
|
+
The `record:line_items` panel (`LineItemsPanel`) shows one banner, written by a
|
|
1270
|
+
failed load and by a failed save. Only the start of a save cleared it, so after
|
|
1271
|
+
a failed load the banner stayed over the rows a later load drew, and after a
|
|
1272
|
+
failed save it stayed over the rows a later load put in place of the edits it
|
|
1273
|
+
was about. Now only the current load writes that banner. When it commits rows it
|
|
1274
|
+
clears the banner, whether it holds a failed load's message or a failed save's.
|
|
1275
|
+
A load that fails shows its own failure instead. A load that another has
|
|
1276
|
+
superseded (another parent record, say, while it was in flight) neither raises
|
|
1277
|
+
the banner nor clears it. This is the rule objectui#10578 set for `ObjectGantt`:
|
|
1278
|
+
the error is cleared when the current load commits, never when a load starts.
|
|
1279
|
+
- b526480: fix(plugin-form): a failed read no longer keeps a record form on its error screen after a later read of the same kind succeeds (objectui#10682)
|
|
1280
|
+
|
|
1281
|
+
Every layout `ObjectForm` routes to (its default layout, `DrawerForm`,
|
|
1282
|
+
`ModalForm`, `SplitForm`, `TabbedForm` and `WizardForm`) reads the object
|
|
1283
|
+
schema and then the record before it draws, and shows its error screen ahead of
|
|
1284
|
+
the form whenever a load error is set. Both reads wrote that one error and
|
|
1285
|
+
nothing ever cleared it. After one failed read, a later read that succeeded
|
|
1286
|
+
still wrote its values, but the form stayed on the error screen until it
|
|
1287
|
+
remounted. Since objectui#10572 the default layout also re-reads its record in
|
|
1288
|
+
place when the data-invalidation bus reports a change to that record, to its
|
|
1289
|
+
object as a whole, or to everything, and holds that re-read while the form has
|
|
1290
|
+
unsaved input. So one failed background re-read was enough.
|
|
1291
|
+
|
|
1292
|
+
Each form now keeps the failure of each read apart and shows the error screen
|
|
1293
|
+
while either is set. A read's failure is cleared when a later run of the same
|
|
1294
|
+
read commits. A record read that succeeds clears an earlier record failure and
|
|
1295
|
+
never a schema failure: after a failed schema read, a record read can only run
|
|
1296
|
+
over a schema read earlier, for another object or data source. This is the rule
|
|
1297
|
+
objectui#10578 set for `ObjectGantt`, applied per read. Only the current run of each read writes its failure, so a superseded
|
|
1298
|
+
read can neither clear the current failure nor raise its own over the current
|
|
1299
|
+
values. The error is not cleared when a read starts; it stays until that read
|
|
1300
|
+
commits.
|
|
1301
|
+
|
|
1302
|
+
A failed background re-read is still reported: no form has a silent mode, so it
|
|
1303
|
+
shows the error screen rather than keeping the last good values, and the next
|
|
1304
|
+
re-read that succeeds takes the screen back.
|
|
1305
|
+
- bf14b64: fix(plugin-form): the line-items panel commits only the answer to its current load, a save only to the parent it was issued for, and holds a same-parent reload while it has unsaved edits (objectui#10712)
|
|
1306
|
+
|
|
1307
|
+
The `record:line_items` panel (`LineItemsPanel`) loads its rows again whenever
|
|
1308
|
+
the parent record or a load input changes. When a load was superseded by a
|
|
1309
|
+
newer one while it was in flight (the host moved the panel to another parent,
|
|
1310
|
+
say), its answer still landed. Landing last, it replaced the current parent's
|
|
1311
|
+
lines (since objectui#10740 the rows it brought were refused as another
|
|
1312
|
+
parent's, so the current lines were gone either way); landing first, it ended
|
|
1313
|
+
the loading state and drew the grid while the current load was still pending.
|
|
1314
|
+
Now a superseded load commits nothing: not the rows, not the end of the loading
|
|
1315
|
+
state.
|
|
1316
|
+
|
|
1317
|
+
The panel's save reloaded the rows through the `load` it captured when Save was
|
|
1318
|
+
clicked. A save that landed after the panel had moved to another parent
|
|
1319
|
+
re-read the old parent, its lines were then committed into the new parent's
|
|
1320
|
+
panel (and, since objectui#10740, refused there behind the placeholder), and
|
|
1321
|
+
its failure was written over the new parent's panel. Now a save reloads only
|
|
1322
|
+
while the parent it saved is still on screen, reading the panel's current
|
|
1323
|
+
inputs (sort, limit, filter) rather than the ones captured at the click, and a
|
|
1324
|
+
save whose parent has left the screen neither re-reads nor reports.
|
|
1325
|
+
|
|
1326
|
+
A change to a load input other than the parent (sort, limit or filter) re-read
|
|
1327
|
+
the rows while the author had unsaved lines, and the read's commit discarded
|
|
1328
|
+
those lines with no signal. Now, as the default record form already does for
|
|
1329
|
+
its own background re-read, the panel holds such a change while it has unsaved
|
|
1330
|
+
edits for the current parent: no read is issued, the edited lines stay drawn
|
|
1331
|
+
and saveable, and the next read that runs (the post-save reload, a parent move,
|
|
1332
|
+
or the commit of a same-parent read that was in flight) carries the change.
|
|
1333
|
+
While a change is held, the grid shows the rows under the previous sort, filter
|
|
1334
|
+
or limit until the panel saves, with no signal. A change while the panel is
|
|
1335
|
+
clean, a parent move, and a change of adapter or child object still read as
|
|
1336
|
+
before. No prop, export or schema key changes.
|
|
1337
|
+
- 4345558: fix(plugin-form): a record form keeps what its current read committed when an earlier read lands late (objectui#10712)
|
|
1338
|
+
|
|
1339
|
+
Every layout `ObjectForm` routes to (its default layout, `DrawerForm`,
|
|
1340
|
+
`ModalForm`, `SplitForm`, `TabbedForm` and `WizardForm`) reads the object
|
|
1341
|
+
schema and then the record before it draws. When the form is pointed at
|
|
1342
|
+
another record or object while a read is in flight, that earlier read is
|
|
1343
|
+
superseded, but two of its writes still landed:
|
|
1344
|
+
|
|
1345
|
+
- The default layout and `WizardForm` wrote a superseded record read's values
|
|
1346
|
+
over the current record's. If the previous record's answer landed last, the
|
|
1347
|
+
form showed the previous record's values under the new record id. If it
|
|
1348
|
+
landed first, it ended the loading state while the current read was still
|
|
1349
|
+
pending. The other four layouts already ignored such an answer.
|
|
1350
|
+
- All six layouts wrote a superseded object-schema read over the current one.
|
|
1351
|
+
After an `objectName` or data-source change, the previous object's schema
|
|
1352
|
+
could land last, and the current record was then drawn against the previous
|
|
1353
|
+
object's fields. A superseded schema read that failed also ended the loading
|
|
1354
|
+
state while the current one was pending, on the four layouts whose failure
|
|
1355
|
+
branch ends it.
|
|
1356
|
+
|
|
1357
|
+
Now a superseded read commits nothing: not the values, not the schema, and not
|
|
1358
|
+
the end of the loading state. Each read's failure is still reported only by
|
|
1359
|
+
its current run, as objectui#10682 set. No prop, export or schema key changes.
|
|
1360
|
+
- 704e05b: fix(plugin-form): every `object-form` layout re-reads its record on the data-invalidation bus (objectui#10715)
|
|
1361
|
+
|
|
1362
|
+
`object-form` in edit mode re-reads its record when the data-invalidation bus
|
|
1363
|
+
(`notifyDataChanged` from `@object-ui/react`) reports a change to that record,
|
|
1364
|
+
its object, or everything, gated on pristine (objectui#10572). Until now only
|
|
1365
|
+
the default layout did. With `formType` set to `drawer`, `modal`, `split`,
|
|
1366
|
+
`tabbed` or `wizard`, the form kept the record as first read: after a change
|
|
1367
|
+
another writer announced on the bus, `findOne` stayed at one call on each of
|
|
1368
|
+
the five, while the default layout read again.
|
|
1369
|
+
|
|
1370
|
+
The five layouts now read the bus by the default layout's rule, which is stated
|
|
1371
|
+
once in the package: a pristine form re-reads in place, without remounting; a
|
|
1372
|
+
form holding unsaved input keeps the typed values and the version token its
|
|
1373
|
+
edit started from, and replays one held re-read once the edit is saved or the
|
|
1374
|
+
form returns to pristine; a change to another object, or to another record of
|
|
1375
|
+
the object, reads nothing; a create form reads nothing. The re-read runs
|
|
1376
|
+
through each layout's own record read, so a re-read a later one has superseded
|
|
1377
|
+
commits nothing (objectui#10712).
|
|
1378
|
+
|
|
1379
|
+
Two layouts have state of their own. A wizard also holds the re-read while a
|
|
1380
|
+
step submitted with Next carries an answer not yet saved, so a re-read never
|
|
1381
|
+
discards it, and a re-read never moves the wizard off its current step. A
|
|
1382
|
+
closed drawer or modal reads while closed and opens on the fresh values, with
|
|
1383
|
+
no further read on open. No prop, export or schema key changes.
|
|
1384
|
+
- cf00ea8: fix(plugin-form): a wizard goes back to its loading state when it is pointed at another record (objectui#10726)
|
|
1385
|
+
|
|
1386
|
+
When a mounted `WizardForm` in edit mode was given another `recordId`, it kept
|
|
1387
|
+
drawing the previous record while the new record was read. The previous
|
|
1388
|
+
record's values stayed on the current step and could be edited, so anything
|
|
1389
|
+
typed in that window was typed into the previous record's values while the form
|
|
1390
|
+
already stood for the new record.
|
|
1391
|
+
|
|
1392
|
+
The wizard now does what `DrawerForm`, `ModalForm`, `SplitForm` and
|
|
1393
|
+
`TabbedForm` already do: a change of record takes it back to the loading state
|
|
1394
|
+
until the new record's answer lands. Only a change of record does this. A
|
|
1395
|
+
wizard whose record read runs again for the same record, for example because
|
|
1396
|
+
the caller rebuilt `initialValues`, stays on screen. A create wizard never goes
|
|
1397
|
+
back to loading.
|
|
1398
|
+
|
|
1399
|
+
The current step, the completed-step marks and the step error marks carry over
|
|
1400
|
+
a record change, as before. No step draws a value of the previous record once
|
|
1401
|
+
the new record lands, since every step reads its values from the new record's
|
|
1402
|
+
answer. No prop, export or schema key changes.
|
|
1403
|
+
- 0896838: fix(plugin-form): the line-items panel neither draws nor saves the lines it holds for another parent record (objectui#10740)
|
|
1404
|
+
|
|
1405
|
+
The `record:line_items` panel (`LineItemsPanel`) holds one set of rows, replaced
|
|
1406
|
+
when a load commits. When the host moved the panel to another parent record
|
|
1407
|
+
without a remount and that parent's load failed, the previous parent's lines
|
|
1408
|
+
stayed on screen, edits included, drawn editable with Save enabled; Save then
|
|
1409
|
+
wrote them under the new parent's id, moving another record's lines to it. A load
|
|
1410
|
+
that declined for the new parent (a refused filter) drew the decline's own notice
|
|
1411
|
+
but left the same Save enabled over the same held rows.
|
|
1412
|
+
|
|
1413
|
+
Now the panel records which parent its held rows belong to, wherever the rows are
|
|
1414
|
+
written: a load's commit, or an edit made before any load settled. While that
|
|
1415
|
+
parent is not the current one, no held line is drawn (after a failed load a
|
|
1416
|
+
placeholder stands where the grid would be, under the failure's banner; after a
|
|
1417
|
+
refused-filter decline that filter's own notice stands there, as before; after a
|
|
1418
|
+
lost-adapter decline the placeholder stands alone), the Save button is off, and a
|
|
1419
|
+
save sends nothing. A load for the current parent that commits
|
|
1420
|
+
takes the grid back. Unchanged: a load for the new parent that succeeds replaces
|
|
1421
|
+
the rows as before, and a re-load of the same parent that fails keeps the
|
|
1422
|
+
author's unsaved edits drawn, editable and saveable under that parent.
|
|
1423
|
+
- b32e7de: fix(fields): a currency grid column's width is its currency's minor unit, never an authored `scale`; a hydrated currency column's `scale` is reported (objectui#10783)
|
|
1424
|
+
|
|
1425
|
+
The line-item grid (`GridField` / `LineItemsField`) no longer reads `scale` on a
|
|
1426
|
+
`currency` column. The currency's ISO 4217 minor unit decides both the width a
|
|
1427
|
+
computed amount is stored at and the places the cell shows: whole yen for JPY,
|
|
1428
|
+
two places for USD, three for KWD, whatever `scale` the column carries.
|
|
1429
|
+
`@objectstack/spec` 17.5.0 refuses `scale` on an inline grid column that
|
|
1430
|
+
declares `type: 'currency'` (ruling B on objectstack-ai/objectstack#19629,
|
|
1431
|
+
ruling 乙 on objectstack-ai/objectstack#19910), and the grid now matches it.
|
|
1432
|
+
`scale` on a `number` column is unchanged.
|
|
1433
|
+
|
|
1434
|
+
A column that declares no `type` and takes `currency` from its child field, such
|
|
1435
|
+
as `inlineColumns: [{ name: 'amount', scale: 2 }]` over a currency `amount`
|
|
1436
|
+
field, still passes the spec, which cannot see the child field's type. The
|
|
1437
|
+
master-detail form's column hydration now reports such a `scale` on the console,
|
|
1438
|
+
once per column, naming the column and the child object, instead of leaving it
|
|
1439
|
+
unread in silence. It reports a declared currency column that carries `scale`
|
|
1440
|
+
too, because a form view's `subforms[].columns` is not judged by the spec. The
|
|
1441
|
+
column still renders, at its currency's minor unit; delete the key.
|
|
1442
|
+
|
|
1443
|
+
**Behaviour change** for a currency column with an authored `scale`: a computed
|
|
1444
|
+
amount is stored and shown at the currency's minor unit instead of at that
|
|
1445
|
+
`scale`. A JPY tenant's column with `scale: 2` used to show `¥1,234.57` and now
|
|
1446
|
+
shows `¥1,235`.
|
|
1447
|
+
- ed76b1b: fix(plugin-report,plugin-form): a dataset report and an authored-parent `record:line_items` panel re-read on the data-invalidation bus
|
|
1448
|
+
|
|
1449
|
+
The one fetch every dataset report presentation runs through (the tabular and
|
|
1450
|
+
summary table, the matrix, the embedded chart, each joined block) now names the
|
|
1451
|
+
`useDataInvalidation` nonce for the dataset's base object, the object the
|
|
1452
|
+
query's answer names. So a write declared on the bus (`notifyDataChanged`, as a
|
|
1453
|
+
page action over raw HTTP does) re-reads a `report` / `spec-report` block in
|
|
1454
|
+
place: the rows on screen stay drawn until the answer replaces them, where
|
|
1455
|
+
before the block dropped back to "Running report…". The `spec-report` a
|
|
1456
|
+
drill-down drawer opens for `drillDown.report` re-reads the same way. A report
|
|
1457
|
+
whose answer names no object does not subscribe. A re-read that fails shows the
|
|
1458
|
+
error in place of the rows and keeps listening, so the next such write re-reads
|
|
1459
|
+
the report; a first load that fails, or a new selection, listens to nothing
|
|
1460
|
+
until an answer names its object.
|
|
1461
|
+
|
|
1462
|
+
The child-row read of `record:line_items` now names the nonce for its child
|
|
1463
|
+
object, so a panel with an authored `parentId` / `recordId` (one a stored page
|
|
1464
|
+
holds with no record context) re-reads its lines after such a write. A re-read
|
|
1465
|
+
of the lines on screen keeps the grid drawn, taking no input until the answer
|
|
1466
|
+
lands. While the panel holds unsaved edits the re-read is held, and the save's
|
|
1467
|
+
reload carries it, as for any other re-read of this panel.
|
|
1468
|
+
|
|
1469
|
+
Before, both refreshed after such a write only when their host remounted them,
|
|
1470
|
+
and `PageView` is about to stop doing that (objectui#10519).
|
|
1471
|
+
|
|
1472
|
+
⚠️ **Dated note, 2026-10-02 — `spec-report` is retired — objectui#11440.**
|
|
1473
|
+
Later in this same release `@object-ui/plugin-report` stopped registering `spec-report`, an alias of
|
|
1474
|
+
`report`, and the drill-down drawer renders `drillDown.report` as `{ type: 'report', report }`. So
|
|
1475
|
+
"a `report` / `spec-report` block" above now reads a `report` block, and "The `spec-report` a
|
|
1476
|
+
drill-down drawer opens" reads the `report` node it opens; both re-read the same way.
|
|
1477
|
+
`.changeset/11440-retire-spec-report.md` states what ships. The rest of this entry is kept as the
|
|
1478
|
+
reading of this change.
|
|
1479
|
+
- de1b879: fix(plugin-form,components): an edit-mode `object-master-detail-form`'s detail lines and `element:record_picker`'s options re-read on the data-invalidation bus
|
|
1480
|
+
|
|
1481
|
+
An `object-master-detail-form` in edit mode now reads the bus for each detail
|
|
1482
|
+
collection's child object: a write declared there (`notifyDataChanged`, as a
|
|
1483
|
+
page action over raw HTTP does), or an unscoped `'*'`, re-reads that
|
|
1484
|
+
collection's lines in place, and the rows and the baseline the next save diffs
|
|
1485
|
+
against move together. A collection only re-reads for its own child object. The
|
|
1486
|
+
header already re-read through its own form. While a collection holds lines the
|
|
1487
|
+
user has changed since they were last read or saved (compared the way the save
|
|
1488
|
+
compares rows, with the link to the parent set aside), or while the row editor
|
|
1489
|
+
("Open row") is open on it, its re-read is held. It runs once, when the row
|
|
1490
|
+
editor is closed and either the lines have been changed back or this form's
|
|
1491
|
+
save has landed. A line typed while a re-read is in flight, in the grid or in
|
|
1492
|
+
the row editor, is kept, and the re-read is held behind it; an open row editor
|
|
1493
|
+
is never reset by a re-read. A re-read that fails keeps the lines on screen.
|
|
1494
|
+
`object-form` with `subforms`, which renders the same form, re-reads the same
|
|
1495
|
+
way.
|
|
1496
|
+
|
|
1497
|
+
`element:record_picker` now re-reads its options when the bus reports a write to
|
|
1498
|
+
the object it queries. The re-read keeps the control enabled over the options on
|
|
1499
|
+
screen (no "Loading…") and never touches the bound page variable. If the bound
|
|
1500
|
+
record is no longer among the options, the variable keeps its value and the
|
|
1501
|
+
control shows no label until a later read offers that record again.
|
|
1502
|
+
|
|
1503
|
+
Before, both refreshed after such a write only when their host remounted them,
|
|
1504
|
+
and `PageView` is about to stop doing that (objectui#10519).
|
|
1505
|
+
- e3782d2: docs(plugin-form): the README's authored `object-form` examples write their props in the `properties` bag (objectui#10859, batch 4)
|
|
1506
|
+
|
|
1507
|
+
The multi-step and metadata-route examples now author `{ "type": "object-form", "properties": { … } }`, the spelling `@objectstack/spec`'s `ComponentPropsMap['object-form']` row declares and `objectui validate` now requires. The example that mounts `WizardForm` directly keeps the flat `schema` prop: a component mounted without `SchemaRenderer` receives the node as the renderer reads it after the hoist. No runtime change.
|
|
1508
|
+
- 9b85600: An `object-form` whose `title`, `description`, `submitText`, `cancelText`, `nextText`, `prevText` or `successMessage` is a per-locale map now shows the viewer's language instead of failing to render (objectui#10993).
|
|
1509
|
+
|
|
1510
|
+
**What it was.** `@objectstack/spec` types these seven members of `object-form` as `I18nLabel`: a plain string or an inline per-locale map such as `{ en: 'Save order', 'zh-CN': '保存订单' }`. `ObjectForm` handed all seven raw to the presentation its `formType` picked, and every presentation that displays one renders it as a React child. A map therefore threw "Objects are not valid as a React child", and the node rendered `Component "form" failed to render` instead of a form.
|
|
1511
|
+
|
|
1512
|
+
**What changed, in observable terms.**
|
|
1513
|
+
|
|
1514
|
+
- `ObjectForm` resolves the seven with `pickLocalized` against the active UI language (`useObjectTranslation().language`), once, before it picks a presentation. So the simple, tabbed, split and wizard forms, the drawer and modal presentations, and the master-detail route all receive the entry for the viewer's language, with the fallback chain `pickLocalized` applies.
|
|
1515
|
+
- A plain string renders exactly as authored. With nothing authored, every presentation shows the default label it showed before.
|
|
1516
|
+
- A map with no string entry resolves to nothing, so the presentation's default label shows.
|
|
1517
|
+
- `object-view`'s `form` slot takes `ObjectFormSchema`'s keys, so its `form.title` and `form.description` are `I18nLabel` too. `ObjectView` draws its own drawer and modal header around the form, and it now resolves both the same way, against the same UI language, where it used to read them raw.
|
|
1518
|
+
- The `object-form` registration declares both arms for the seven keys, `type: ['string', 'object']`, with descriptions that teach the per-locale map. The manifest built from `ComponentRegistry.getPublicConfigs()` therefore no longer makes `validateTree` report `type-mismatch` on a locale map for these keys. A value that matches neither arm, such as a number, is still reported.
|
|
1519
|
+
|
|
1520
|
+
**Types.** In `@object-ui/types`, `ObjectFormSchema.title`, `.description`, `.submitText`, `.cancelText`, `.nextText`, `.prevText` and `.successMessage` widen from `string` to `I18nLabel`, matching the spec row. The zod mirror's `title`, `description`, `submitText`, `cancelText` and `successMessage` widen from `z.string()` to the spec's `I18nLabelSchema`, by reference, so `safeValidateSchema` accepts a locale map on them; a number is still refused at the member. `nextText` and `prevText` stay unmirrored, as before. Code that writes these members compiles unchanged. Code that reads one of them off an `ObjectFormSchema` and uses it as a `string` no longer compiles: resolve it first, for example with `pickLocalized` from `@object-ui/i18n`.
|
|
1521
|
+
|
|
1522
|
+
**Clause-②: yes** — seven members of the exported `ObjectFormSchema` type, and five members of its zod mirror, widen from a string to `I18nLabel`, and the registration's `inputs` for the seven keys widen from `'string'` to `['string', 'object']`. Nothing that was accepted before is refused now.
|
|
1523
|
+
|
|
1524
|
+
**Correction, 2026-09-30 (objectui#6152).** The **Types** paragraph above says `nextText` and `prevText` stay unmirrored. That was true when this change was written, and it no longer is: objectui#6152 (PR #11125) mirrors both, by the same reference to the spec's `I18nLabelSchema`, so `safeValidateSchema` now judges them as it judges the other five: a locale map is accepted, and a number is refused at the member.
|
|
1525
|
+
- 2eaf5be: `WizardForm`'s own chrome now reads the locale packs (objectui#10999). The
|
|
1526
|
+
default Cancel, Back, Next, Submitting, Create and Update labels, the
|
|
1527
|
+
"Step x of y" counter, the step indicator's label for a step that declares no
|
|
1528
|
+
`label`, the indicator's accessible name, and the notice on a step with no
|
|
1529
|
+
fields were English literals, so a wizard in a zh session showed `Cancel`,
|
|
1530
|
+
`Step 1 of 3` and `Next` beside a Chinese UI. They now resolve through the
|
|
1531
|
+
wizard's existing `createSafeTranslation` hook, the way the rest of
|
|
1532
|
+
plugin-form's chrome does.
|
|
1533
|
+
|
|
1534
|
+
An authored `cancelText`, `prevText`, `nextText` or `submitText` still renders
|
|
1535
|
+
exactly as authored, in every locale: only the defaults are localized, and the
|
|
1536
|
+
four keys stay plain strings.
|
|
1537
|
+
|
|
1538
|
+
Five of the strings reuse keys the packs already carried: `common.cancel`,
|
|
1539
|
+
`common.next`, `form.create`, `form.update`, and `form.stepOf`, a key that
|
|
1540
|
+
`pnpm check:i18n-dead-keys` listed as read by nothing until now. Five are new
|
|
1541
|
+
under `wizard.` in all ten packs: `back`, `submitting`, `stepFallback`,
|
|
1542
|
+
`progressLabel` and `emptyStep`. The wizard gets its own `back` rather than
|
|
1543
|
+
`common.back` because zh words a wizard's step back ("previous step", the term
|
|
1544
|
+
`grid.import.back` and `grid.bulk.back` already use) differently from a page's
|
|
1545
|
+
back ("return").
|
|
1546
|
+
|
|
1547
|
+
The zh value of `form.stepOf` gains the spaces around its numbers that the zh
|
|
1548
|
+
pack's other position counters, such as `table.pageInfo` and
|
|
1549
|
+
`detail.recordOf`, already carry.
|
|
1550
|
+
|
|
1551
|
+
**One rendered English string changes**: the final button's in-flight label was
|
|
1552
|
+
typed with three ASCII full stops and is now `Submitting…` with the typographic
|
|
1553
|
+
ellipsis, because it is a pack value now and `ellipsis-glyph-3878.test.ts`
|
|
1554
|
+
holds every pack value to U+2026. Every other English default renders the same
|
|
1555
|
+
text as before.
|
|
1556
|
+
- bf7ab35: A record form whose fields are all locked because the user may not create (or
|
|
1557
|
+
edit) records of its object now says so (objectui#11000). The ADR-0092 D4 lock
|
|
1558
|
+
disables every field when the object's affordance for the form's mode is
|
|
1559
|
+
closed: the object's `managedBy` bucket keeps it closed, or the server's
|
|
1560
|
+
effective API operation set for the user lacks `create` (or `update` on an edit
|
|
1561
|
+
form). Until now nothing on the form said why, and a wizard's Next stayed
|
|
1562
|
+
enabled.
|
|
1563
|
+
|
|
1564
|
+
- Every form layout that draws the lock (the default form, `drawer`, `modal`,
|
|
1565
|
+
`tabbed`, `split` and `wizard`) renders one notice above the fields, such as
|
|
1566
|
+
"You don't have permission to create Project records. The fields are
|
|
1567
|
+
read-only." It names the object by its label, translated when the app's
|
|
1568
|
+
locale bundle translates it, and names `create` on a create form or `edit` on
|
|
1569
|
+
an edit form. It is announced as a status (`role="status"`).
|
|
1570
|
+
- The notice reads the same verdict the lock does, so it appears exactly when
|
|
1571
|
+
every field is locked for that reason. A field locked on its own (one the
|
|
1572
|
+
user may read but not edit, or one declared `readonly`) shows no notice.
|
|
1573
|
+
- A wizard does not walk a user who cannot submit through its steps: while the
|
|
1574
|
+
lock holds, Next and the final submit button are disabled, and the step
|
|
1575
|
+
indicator does not jump forward even with `allowSkip`. Cancel and Back stay
|
|
1576
|
+
usable, and every step is still shown. A user whose affordance is open sees
|
|
1577
|
+
no change.
|
|
1578
|
+
- Two keys are new in the `form` namespace of all ten packs:
|
|
1579
|
+
`noPermissionToCreate` and `noPermissionToEdit`, each with an `{{object}}`
|
|
1580
|
+
placeholder.
|
|
1581
|
+
|
|
1582
|
+
The lock itself is unchanged, and so is the Save button of the non-wizard
|
|
1583
|
+
layouts.
|
|
1584
|
+
- 51c2949: The form family's own feedback chrome now reads the locale packs (objectui#11039). The default success toast, the thank-you heading, the loading line, the load-failure heading and the default submit and cancel labels were English literals, so a zh session saw `Created`, `Thanks!`, `Loading form...` and `Error loading form` beside a Chinese UI. They now resolve through the i18n catalogue, the way `WizardForm`'s footer has since objectui#10999.
|
|
1585
|
+
|
|
1586
|
+
**What moved.**
|
|
1587
|
+
|
|
1588
|
+
- `@object-ui/plugin-form`, across the presentations `object-form` routes to:
|
|
1589
|
+
- The success toast when no `successMessage` is authored: `form.created` after a create, `form.saved` after an edit (the default arm and `WizardForm`), and `MasterDetailForm`'s built-in save toast, whose "… saved" after an authored `title` is `form.savedNamed`.
|
|
1590
|
+
- The note that rides that toast when a declared `navigateOnSuccess` was refused: `form.navigateRefused`. The submitter reads it, as the toast's description.
|
|
1591
|
+
- The thank-you heading when a `thank-you` submit behaviour declares no `title` (the default arm and `WizardForm`): `publicForm.thankYouTitle`.
|
|
1592
|
+
- The loading line of the default arm, `TabbedForm`, `SplitForm`, `WizardForm` and `DrawerForm`: `publicForm.loading`.
|
|
1593
|
+
- The heading of the load-failure panel in those five and `ModalForm`: `form.errorLoading`.
|
|
1594
|
+
- The default submit label, `form.create` or `form.update`, in the default arm, `TabbedForm`, `SplitForm`, `DrawerForm` and `ModalForm`; the default Cancel of `DrawerForm` and `ModalForm`, `common.cancel`; and `MasterDetailForm`'s default Save, Create and Cancel, `common.save`, `form.create` and `common.cancel`.
|
|
1595
|
+
- `@object-ui/console`: the form page (`/f/:slug` and `/forms/:name`) reads its loading line from `common.loading`, its success toast from `form.submitted`, and its default thank-you heading and message from `publicForm.thankYouTitle` and `publicForm.thankYouMessage`, through the `I18nProvider` that `main.tsx` mounts above every console route.
|
|
1596
|
+
- `@object-ui/i18n`: six keys are new under `form.` in all ten packs: `created`, `saved`, `savedNamed` (`{{title}}`), `submitted`, `errorLoading` and `navigateRefused`. The three `publicForm.` keys were already in all ten packs with nothing reading them (`pnpm check:i18n-dead-keys` listed them as confirmed dead), and say the same thing. The zh value of `publicForm.thankYouTitle` ends with a full-width exclamation mark instead of an ASCII one.
|
|
1597
|
+
|
|
1598
|
+
**Authored values still win.** An authored `successMessage`, `submitText` or `cancelText`, a plain string or a per-locale map, and a `thank-you` behaviour's own `title` and `message`, render as authored in every locale. Only the defaults are localized. No schema key is added, renamed or retyped.
|
|
1599
|
+
|
|
1600
|
+
**Three English strings change their rendered text**, because each is now the value of a key whose English differs from the old literal:
|
|
1601
|
+
|
|
1602
|
+
- the loading line, `Loading form...` with three ASCII full stops, is now `Loading form…` with the typographic ellipsis;
|
|
1603
|
+
- the thank-you heading, `Thanks!`, is now `Thank you!`, in the default arm, `WizardForm` and the form page;
|
|
1604
|
+
- the form page's default thank-you message, `Your submission has been received.`, is now `Your submission has been received successfully.`
|
|
1605
|
+
|
|
1606
|
+
Every other English default renders the same text as before. With no i18n provider mounted, plugin-form's forms still render English from their defaults tables, which `pnpm check:i18n-keys` holds byte-identical to the `en` pack.
|
|
1607
|
+
- 559a2e2: fix(plugin-form): `object-form`'s `modalCloseButton: false` hides the modal's close button
|
|
1608
|
+
|
|
1609
|
+
An author can now hide the close (X) button of a modal form. `modalCloseButton`
|
|
1610
|
+
was declared on `ObjectFormSchema` and on the `object-form` registration, and
|
|
1611
|
+
`ObjectForm`'s modal route forwarded it, but `ModalForm` never read it: `false`
|
|
1612
|
+
still drew the X, with no effect and no error. `ModalForm` now honours it on both
|
|
1613
|
+
of its dialog arms (the flat form and the `subforms` master-detail form). Only an
|
|
1614
|
+
explicit `false` hides the button; unset and `true` keep it. With the X hidden the
|
|
1615
|
+
modal still closes on Escape, and on the Cancel action when that is shown.
|
|
1616
|
+
|
|
1617
|
+
`@object-ui/components`: `MobileDialogContent` gains an optional `showCloseButton`
|
|
1618
|
+
prop, default `true`, named after upstream Shadcn's `DialogContent` prop of the
|
|
1619
|
+
same purpose. `false` leaves the close button out of the DOM. Existing callers are
|
|
1620
|
+
unchanged. The component's exported props type gains this one optional member,
|
|
1621
|
+
which is why this package takes a minor bump.
|
|
1622
|
+
- 55a12a8: The grid field reads each field-level key under the one spelling `GridFieldMetadata` declares (objectui#11070, round 8).
|
|
1623
|
+
|
|
1624
|
+
`GridFieldMetadata` declared `allow_reorder` and the docs taught it, while `GridField` read `reorderable`, so `allow_reorder: false` still drew a drag handle on every row. The widget also read its footer total under three spellings, and read four keys that no face declared. Each key now has one spelling, and that spelling is declared and read:
|
|
1625
|
+
|
|
1626
|
+
- **Reorder (`@object-ui/fields`).** `GridField` reads `allow_reorder`. `allow_reorder: false` removes the drag handles. The undeclared `reorderable` is no longer read; this round's census found no writer of it in either repository.
|
|
1627
|
+
- **Total (`@object-ui/types`, `@object-ui/fields`).** `GridFieldMetadata` declares `total_field`, the one spelling the grid reads. It names the CHILD column summed into the footer, which is the spec's `amountField` (`inlineAmountField` on a `master_detail` field, `subforms[].amountField` on a form view). It is not the spec's `totalField`, the parent field a master-detail save writes the sum to. The `amount_field` and `amountField` reads beside it are retired: the same census found nothing writing either into the grid's config.
|
|
1628
|
+
- **`add_label` (`@object-ui/types`).** Declared. `MasterDetailForm` writes it from a detail's `addLabel`, and it labels the grid's Add button.
|
|
1629
|
+
- **`allow_duplicate` and `show_line_numbers` (`@object-ui/fields`)** are retired under ADR-0049. No face declared either, and the census found no producer of either. The behaviour their defaults gave stays: each row offers a duplicate action whenever rows can be added, and the line-number column always shows.
|
|
1630
|
+
- **`sort_field` is unchanged.** `MasterDetailForm` still writes it from a detail's `sortField`, which is derived from the child object when not authored. The spec declares no inline sort-field key, so it stays read and undeclared, named in one place in `GridField`.
|
|
1631
|
+
- **`record:line_items` total (`@object-ui/plugin-form`).** The panel shows its grid's footer total whenever `amountField` names the column to sum, the way `MasterDetailForm` already did. It used to show it only when `totalField` was also set.
|
|
1632
|
+
|
|
1633
|
+
`GridField` now types its config reads as `GridFieldMetadata`, so a read of a key the type does not declare fails to compile.
|
|
1634
|
+
|
|
1635
|
+
**Clause-②: yes (narrowing).** The published `GridFieldMetadata` face widens by two optional members, `total_field` and `add_label`. What the grid honours narrows: five keys it used to read are no longer read.
|
|
1636
|
+
|
|
1637
|
+
## ⚠️ BREAKING, priced as minor under the fixed group's version policy
|
|
1638
|
+
|
|
1639
|
+
- **Rendering.** A grid field written with `reorderable`, `amount_field`, `amountField`, `allow_duplicate` or `show_line_numbers` renders as if that key were absent. Fix: write `allow_reorder: false` to turn off drag reordering, and `total_field` to name the summed column. To turn off the duplicate action, turn off adding with `allow_add: false`; there is no switch for the line-number column.
|
|
1640
|
+
- **Behaviour.** `allow_reorder: false` now removes the drag handles; before, it was ignored. A `record:line_items` panel with `amountField` and no `totalField` now shows the footer total of that column.
|
|
1641
|
+
- **TypeScript.** A `GridFieldMetadata` literal carrying any of the five retired keys was already a compile error, and still is.
|
|
1642
|
+
|
|
1643
|
+
**Note, 2026-10-01 (objectui#11070 round 9, shipping in this same release).**
|
|
1644
|
+
The `sort_field` bullet above no longer holds. `GridFieldMetadata` now declares
|
|
1645
|
+
`sort_field`, so `GridField` reads no undeclared key, and a detail's `sortField`
|
|
1646
|
+
is no longer authored: `MasterDetailDetailConfig` has no such member, and
|
|
1647
|
+
`MasterDetailForm` hands the grid the sort field it derives from the child
|
|
1648
|
+
object. See `11070-grid-sort-field-round9`.
|
|
1649
|
+
- 54997ff: fix(console,plugin-form,i18n): the public form page and the master-detail form chrome speak the user's language
|
|
1650
|
+
|
|
1651
|
+
objectui#11039 moved the form family's feedback chrome onto the locale packs and
|
|
1652
|
+
left two halves behind, both still English inside a Chinese UI.
|
|
1653
|
+
|
|
1654
|
+
The console form page (`/f/:slug`, `/forms/:name`) rendered its action chrome from
|
|
1655
|
+
literals: the submit button's `Submit`, its in-flight `Submitting…` and
|
|
1656
|
+
`Uploading…`, the `Redirecting…` line of a pending redirect, and the frame of the
|
|
1657
|
+
`Required: …` refusal. They now read `publicForm.submit`, `publicForm.submitting`,
|
|
1658
|
+
`fields.file.uploading` (the key every other form's in-flight Save label already
|
|
1659
|
+
reads), and two new keys, `publicForm.redirectPending` and
|
|
1660
|
+
`publicForm.requiredFields`. The refusal is one key with a `{{fields}}` hole and
|
|
1661
|
+
its labels are joined with the pack's own list separator, so the colon, its
|
|
1662
|
+
spacing and the word order belong to the locale.
|
|
1663
|
+
|
|
1664
|
+
`plugin-form` had the same literals in the master-detail form: `Loading columns…`,
|
|
1665
|
+
the `Subtotal` / `Tax (N%)` / `Total` stack, the row editor's `Line item — row N`
|
|
1666
|
+
title with its `Apply` and `Close`, the in-form collection's `Add`, and the Save
|
|
1667
|
+
button's `Saving…`; in `ModalForm`, the sr-only description of a master-detail
|
|
1668
|
+
dialog; and in `ObjectForm`, the hint under a field the caller may read but not
|
|
1669
|
+
write. They now read the pack (new keys under `form.masterDetail` and
|
|
1670
|
+
`form.deniedDescription`, plus the existing `detail.add`, `detail.saving` and
|
|
1671
|
+
`common.close`), in all ten packs.
|
|
1672
|
+
|
|
1673
|
+
Only defaults move. An authored value still wins everywhere it did: a collection
|
|
1674
|
+
`title` and `addLabel`, a dialog `description`, a field `description`, an authored
|
|
1675
|
+
field label inside the refusal. English output is byte-identical to the literals.
|
|
1676
|
+
- 3fa1938: The `object-master-detail-form` registration's `fields` description no longer calls that key "the submitted set" (objectui#11114).
|
|
1677
|
+
|
|
1678
|
+
The sentence told an author, or an AI authoring metadata, that the parent field pool bounds what Save writes. It does not, and it was never meant to: `fields` bounds what the parent form DRAWS and edits, and on a create the parent leg of the atomic batch is the drawn fields plus any parent value seeded through `initialValues` (or its alternate spelling `initialData`), drawn or not. The description now says exactly that, and adds that a seed for an undeclared, server-owned, computed or read-only field is still stripped, as on any save.
|
|
1679
|
+
|
|
1680
|
+
Text only. The description is a declared input of a public-tier block, so it reaches the published `sdui.manifest.json`; no input, type, default or accepted value moves, and no runtime behaviour changes. The parent leg is deliberately not filtered to `fields`: that would silently drop author-seeded values, which is how a hidden parent key reaches a record.
|
|
1681
|
+
- 385ebc5: fix(plugin-form,fields,i18n): the record page's line-items panel and the line-items grid speak the user's language
|
|
1682
|
+
|
|
1683
|
+
Under a Chinese session the record page's `record:line_items` panel still
|
|
1684
|
+
rendered some of its chrome in English. Its default title read `Line Items` and
|
|
1685
|
+
its button read `Save` / `Saving…`. The line-items grid (`GridField`) read
|
|
1686
|
+
`Add line` on its Add button, `No items yet — click “Add” to begin.` in list
|
|
1687
|
+
mode, and `No items` when read-only.
|
|
1688
|
+
|
|
1689
|
+
They now read the locale packs. The button reuses `common.save` and
|
|
1690
|
+
`detail.saving`, and the empty text names `detail.add`. Four new keys are added
|
|
1691
|
+
to all ten packs: `form.lineItems.title`, `fields.grid.addLine`,
|
|
1692
|
+
`fields.grid.noItems` and `fields.grid.noItemsAddHint`.
|
|
1693
|
+
|
|
1694
|
+
Only the defaults move. An authored `title` or `add_label` still wins, and an
|
|
1695
|
+
authored `add_label` is also the label the empty text names. English output is
|
|
1696
|
+
byte-identical to the literals these replace, with or without an i18n provider.
|
|
1697
|
+
- 1923d35: fix(plugin-form): the master-detail form's Subtotal / Tax / Total show the amount's currency, not a hard-coded yen sign (objectui#11132)
|
|
1698
|
+
|
|
1699
|
+
The document totals stack under a master-detail form's line items printed a
|
|
1700
|
+
literal `¥` in front of every amount, whatever the amount field's currency or the
|
|
1701
|
+
tenant's. A USD tenant's invoice read `¥1,234.50` under lines in dollars.
|
|
1702
|
+
|
|
1703
|
+
The stack now resolves its currency through `resolveFieldCurrency`, the one
|
|
1704
|
+
precedence every currency face shares: the amount field's fixed currency
|
|
1705
|
+
(`currencyConfig` with `currencyMode: 'fixed'`), else the tenant's default
|
|
1706
|
+
currency. The field definition is the one the form already loads from the child
|
|
1707
|
+
object to derive or hydrate its columns. The amount is `Intl`'s currency format in
|
|
1708
|
+
the display locale, so:
|
|
1709
|
+
|
|
1710
|
+
- the sign sits where the locale puts it: `$1,234.50` in English, `1.234,50 $` in
|
|
1711
|
+
German;
|
|
1712
|
+
- the decimal places are the currency's own: two for USD and EUR, none for JPY
|
|
1713
|
+
(a JPY tenant's stack read `¥1,234.50` and now reads `¥1,235`), three for KWD;
|
|
1714
|
+
- the digits still follow the display locale, as objectui#9909 made them.
|
|
1715
|
+
|
|
1716
|
+
With no currency known, or when the stack adds entries whose amounts resolve to
|
|
1717
|
+
different currencies, the lines are plain numbers at two places with no sign.
|
|
1718
|
+
|
|
1719
|
+
A fully configured detail entry (relationship field and every column typed) loads
|
|
1720
|
+
no child schema, so its amount field's own currency is not read and the stack
|
|
1721
|
+
shows the tenant's currency for it.
|
|
1722
|
+
- 263dcd7: fix(plugin-form): a master-detail child with authored inline columns derives its sort field and amount field, so line order persists and the total shows (objectui#11144)
|
|
1723
|
+
|
|
1724
|
+
A master-detail child whose relationship field declares `inlineColumns` lost two
|
|
1725
|
+
things in the parent's form. A line created or dragged into a new order carried no
|
|
1726
|
+
`position`, so the order was gone after save. And unless the relationship also
|
|
1727
|
+
declared `inlineAmountField`, no running total rendered: neither the grid's own
|
|
1728
|
+
total nor the Subtotal / Tax / Total stack.
|
|
1729
|
+
|
|
1730
|
+
Such a detail reaches the form with a relationship field and an authored column
|
|
1731
|
+
set, and that path only filled in the column types from the child object. The sort
|
|
1732
|
+
field (a `position`-, `sort_order`- or similarly named field on the child) and the
|
|
1733
|
+
amount field were picked only for details without authored columns, and nothing
|
|
1734
|
+
else supplies them: the spec has no inline sort-field key.
|
|
1735
|
+
|
|
1736
|
+
A detail with authored columns now takes both from the same derivation as every
|
|
1737
|
+
other detail, while keeping its own columns, order and labels. The amount field is
|
|
1738
|
+
picked from the authored columns by the rule a derived grid already uses. An
|
|
1739
|
+
authored `inlineAmountField` (or a detail's own `amountField` / `sortField`) still
|
|
1740
|
+
wins over the derived one.
|
|
1741
|
+
|
|
1742
|
+
**Note, 2026-10-01 (objectui#11070 round 9, shipping in this same release).**
|
|
1743
|
+
A detail's own `sortField` no longer wins, because it is no longer read:
|
|
1744
|
+
objectui#11070 round 9 retired that member of `MasterDetailDetailConfig`. The
|
|
1745
|
+
derived sort field is the only one. An authored `inlineAmountField` (or a
|
|
1746
|
+
detail's own `amountField`) still wins over the derived amount field.
|
|
1747
|
+
- a8c5509: fix(plugin-form,fields,i18n): the line-items panel, the grid field and the master-detail heading finish speaking the user's language
|
|
1748
|
+
|
|
1749
|
+
Under a Chinese session the rest of the line-items chrome still rendered in
|
|
1750
|
+
English. The record page's `record:line_items` panel read `Loading…`, `Save the
|
|
1751
|
+
record first to add line items.`, `This record’s line items have not been
|
|
1752
|
+
loaded.`, the `Failed to load line items` / `Failed to save line items`
|
|
1753
|
+
fallbacks and its no-`childObject` hint. The line-items grid (`GridField`) read
|
|
1754
|
+
its footer `Total`, its column chooser's `Columns` / `Optional columns`, the
|
|
1755
|
+
computed cell's `Computed` tooltip and its row actions. A master-detail
|
|
1756
|
+
collection with no `title` was headed `Line Items`.
|
|
1757
|
+
|
|
1758
|
+
They now read the locale packs. Reused keys: `common.loading`, `table.columns`,
|
|
1759
|
+
`form.masterDetail.total` (the footer), `view.dragToReorder` (the drag handle)
|
|
1760
|
+
and `form.lineItems.title` (the master-detail heading). New keys, in all ten
|
|
1761
|
+
packs: `form.lineItems.saveRecordFirst`, `form.lineItems.notLoaded`,
|
|
1762
|
+
`form.lineItems.loadFailed`, `form.lineItems.saveFailed`,
|
|
1763
|
+
`form.lineItems.noChildObject`, `fields.grid.optionalColumns`,
|
|
1764
|
+
`fields.grid.computed`, `fields.grid.openRow`, `fields.grid.duplicateRow` and
|
|
1765
|
+
`fields.grid.removeRow`.
|
|
1766
|
+
|
|
1767
|
+
Each row action now has one key, read by both its `aria-label` and its `title`.
|
|
1768
|
+
The English kept for each is the accessible name it already had:
|
|
1769
|
+
|
|
1770
|
+
- open the row in the full form: `Open row` (the tooltip was `Open full form`);
|
|
1771
|
+
- duplicate the row: `Duplicate row` (the tooltip was `Duplicate line`);
|
|
1772
|
+
- remove the row: `Remove row` (no tooltip, as before);
|
|
1773
|
+
- the drag handle: `Drag to reorder`.
|
|
1774
|
+
|
|
1775
|
+
Only the defaults move. An authored collection `title` still wins, and so does
|
|
1776
|
+
a failure message the server sent. Apart from the two tooltips above, English
|
|
1777
|
+
output is byte-identical to the literals these replace, with or without an i18n
|
|
1778
|
+
provider.
|
|
1779
|
+
- c2a8d23: fix(fields,plugin-form,i18n): the line-items grid's required-cell text and the master-detail form's config hints read the locale packs
|
|
1780
|
+
|
|
1781
|
+
Under a Chinese session two parts of the line-items family still rendered in
|
|
1782
|
+
English. The line-items grid (`GridField`) named a required, empty cell as the
|
|
1783
|
+
column label followed by ` is required`. That text is the plain cell's tooltip
|
|
1784
|
+
and the `error` the lookup and file cells take. A master-detail collection that
|
|
1785
|
+
could not be resolved showed one of three English configuration hints: no
|
|
1786
|
+
`childObject`, a child schema that failed to load, or no lookup or
|
|
1787
|
+
master_detail field linking the child to the parent.
|
|
1788
|
+
|
|
1789
|
+
They now read the locale packs. The grid cells reuse `validation.required`
|
|
1790
|
+
(`{{field}} is required`), with the column label in `{{field}}`: the sentence
|
|
1791
|
+
the form renderer already shows for a required field. The three hints read
|
|
1792
|
+
new keys in all ten packs: `form.masterDetail.noChildObject`,
|
|
1793
|
+
`form.masterDetail.schemaUnavailable` and
|
|
1794
|
+
`form.masterDetail.noRelationshipField`. Property names (`childObject`,
|
|
1795
|
+
`relationshipField`) and object names stay code elements and are never
|
|
1796
|
+
translated.
|
|
1797
|
+
|
|
1798
|
+
English output is byte-identical to the literals these replace, with or
|
|
1799
|
+
without an i18n provider.
|
|
1800
|
+
- 58da8ae: A blank gate predicate is diagnosed on the three paths that still drew it in
|
|
1801
|
+
silence, and objectui's two gate mirrors whose protocol key refuses a blank now
|
|
1802
|
+
refuse it too (objectui#11262, ADR-0137 D4 and D1). **No drawn verdict moves:** a
|
|
1803
|
+
blank gate is still "no gate".
|
|
1804
|
+
|
|
1805
|
+
**Diagnosed, verdict unchanged (`@object-ui/core`, `@object-ui/plugin-form`).**
|
|
1806
|
+
ADR-0137 D4 says a blank gate predicate is "diagnosed, never a silent `true`".
|
|
1807
|
+
objectui#8069 diagnosed the `{ dialect: 'cel' }` route; three paths stayed silent,
|
|
1808
|
+
and each now reports through that same guard (`evalFieldPredicate`'s `[blank]`
|
|
1809
|
+
report, deduped per blank text and locator):
|
|
1810
|
+
|
|
1811
|
+
- `ExpressionEvaluator.evaluateCondition`'s legacy path: a bare `''`, a
|
|
1812
|
+
whitespace-only string, and an envelope without `dialect` whose `source` is
|
|
1813
|
+
blank. These are the values `SchemaRenderer`'s visibility legs pass through
|
|
1814
|
+
raw. The answer is still `true` in every mode, `throwOnError` included. A
|
|
1815
|
+
caller that passes `onFault` receives the `[blank]` reason there; every other
|
|
1816
|
+
caller gets the built-in warning. A blank on either route lands on one dedupe
|
|
1817
|
+
key, so `''` and `{ dialect: 'cel', source: '' }` print one line.
|
|
1818
|
+
- `evalRowPredicate`: a bare blank string returned the caller's fallback before
|
|
1819
|
+
anything could say so. It now takes the route its blank-envelope twin already
|
|
1820
|
+
took, with the same fallback and the same report (labelled on the
|
|
1821
|
+
`warnOnError` route).
|
|
1822
|
+
- `sectionFields`' `attachVisibility` (every sectioned `object-form` arm): a
|
|
1823
|
+
blank view-level predicate is still dropped, so the field draws with no gate,
|
|
1824
|
+
and the drop is reported, naming the field. A runtime field that already
|
|
1825
|
+
carries its own blank `visibleOn` keeps it. The form renderer reports that one
|
|
1826
|
+
when it evaluates it.
|
|
1827
|
+
|
|
1828
|
+
**Refused at authoring (`@object-ui/types`, minor, a narrowing).** The protocol
|
|
1829
|
+
declares an option's `visibleWhen` and a form view field's `visibleWhen` /
|
|
1830
|
+
`visibleOn` as `EvaluatedExpressionInputSchema`, which refuses a blank predicate
|
|
1831
|
+
(since `@objectstack/spec` 17.5.0). objectui's `SelectOptionSchema.visibleWhen`
|
|
1832
|
+
and `FormFieldSchema.visibleOn` override those keys to keep objectui's wire, and
|
|
1833
|
+
they still accepted `''`, whitespace and a blank envelope `source`. Both now
|
|
1834
|
+
refuse them at the key, with the protocol's own sentence, through the check the
|
|
1835
|
+
field-rule triad already carries. The accepted shape is unchanged: the same wire
|
|
1836
|
+
options, by reference, and the parsed value is the authored one.
|
|
1837
|
+
|
|
1838
|
+
`BaseSchema`'s `visible` / `hidden` / `disabled` have no protocol counterpart,
|
|
1839
|
+
so a blank there still parses and is only diagnosed.
|
|
1840
|
+
- dbd1081: The top-level `fields` input descriptions of `object-form`, `view:form`,
|
|
1841
|
+
`embeddable-form` and `object-master-detail-form`, and the `console.warn` a
|
|
1842
|
+
top-level `fields` member that resolves to no field name draws, no longer say
|
|
1843
|
+
that a `{ name }` object member "is tolerated" (objectui#11550). Each still says
|
|
1844
|
+
that the members are bare field names, and the warning still names the right
|
|
1845
|
+
spelling: a bare field-name string, or an entry in `sections[].fields`.
|
|
1846
|
+
|
|
1847
|
+
Write top-level `fields` members as bare field-name strings, for example
|
|
1848
|
+
`"fields": ["name", "email"]`. A per-field override (`colSpan`, a label) goes on
|
|
1849
|
+
a `sections[].fields` entry instead.
|
|
1850
|
+
|
|
1851
|
+
Behaviour is unchanged. A `{ name }` member already stored in a form view still
|
|
1852
|
+
reads at render, and the member that resolves to no name is still skipped with
|
|
1853
|
+
the same warning; only the wording that taught `{ name }` as an authoring
|
|
1854
|
+
spelling is gone.
|
|
1855
|
+
- 39f4309: Published typings from every `vite-plugin-dts` package now carry an explicit extension on
|
|
1856
|
+
every relative specifier, and a type error in the declaration build now fails the build
|
|
1857
|
+
instead of being printed and ignored (objectui#5439, objectui#5483).
|
|
1858
|
+
|
|
1859
|
+
**Consumers on `moduleResolution: nodenext` or `node16` may see NEW type errors, and that
|
|
1860
|
+
is the fix working.** These packages re-export mostly through NAMED re-exports —
|
|
1861
|
+
`export { useObjectChat } from './useObjectChat'`. TypeScript could not follow the
|
|
1862
|
+
extensionless hop, but it still DECLARED the name, so the symbol resolved to a silent
|
|
1863
|
+
`any`. Nothing errored; consumers simply got no types. With the extension emitted, the
|
|
1864
|
+
symbol carries its real type, and any call site that was relying on the `any` now type
|
|
1865
|
+
checks for the first time. This is the mode that produced the 21 residual `TS7006` on
|
|
1866
|
+
`@object-ui/app-shell` reported against objectui#5365 — a type hole that opened quietly,
|
|
1867
|
+
unlike objectui#5365's own `export * from './ui'` packages where the same defect surfaced
|
|
1868
|
+
immediately as `TS2305: has no exported member`.
|
|
1869
|
+
|
|
1870
|
+
410 extensionless relative specifiers across 19 packages were emitted before this change;
|
|
1871
|
+
the count is now 0 in all 22 packages that build typings through `vite-plugin-dts`.
|
|
1872
|
+
`@object-ui/fields` was already clean — its sources write explicit `.js` specifiers — and
|
|
1873
|
+
is wired so it stays that way.
|
|
1874
|
+
|
|
1875
|
+
The second half changes no emitted output today: 22/22 packages built green unmodified, so
|
|
1876
|
+
making the declaration step's exit code honest turns nothing red. It changes what a FUTURE
|
|
1877
|
+
regression does — print and exit 0, versus fail the build.
|
|
1878
|
+
- 3e853c9: Let a producer-marked refusal reach the drag-write surfaces (objectui#5902).
|
|
1879
|
+
|
|
1880
|
+
The kanban card-move toast, the calendar drag-to-reschedule toast and the OCC
|
|
1881
|
+
conflict dialog each substituted a generic string for a refusal the producer had
|
|
1882
|
+
marked as user-facing (`userMessage`), so a user was told "Save failed" where the
|
|
1883
|
+
application author had written a sentence addressed to them. All three now read
|
|
1884
|
+
the marking through the shared `declaredUserMessage` reader, which covers both
|
|
1885
|
+
places the adapter boundary parks it — the typed member on
|
|
1886
|
+
`ConcurrentUpdateError` and the details bag on `DataApiValidationError`.
|
|
1887
|
+
|
|
1888
|
+
Nothing unmarked changes: the reader answers `null` for it, so every existing
|
|
1889
|
+
generic substitution — including the localized "not authorized" message that
|
|
1890
|
+
keeps raw server diagnostics away from end users — still governs unmarked
|
|
1891
|
+
refusals exactly as before.
|
|
1892
|
+
|
|
1893
|
+
The two toasts substitute; the conflict dialog augments. Its description also
|
|
1894
|
+
explains what the destructive "Overwrite" button does, which is affordance copy
|
|
1895
|
+
that surface owns rather than a refusal message, so the marking leads and that
|
|
1896
|
+
paragraph stays.
|
|
1897
|
+
- 17ccec9: `object-master-detail-form` declines to fetch a detail collection whose child object it never resolved, instead of calling `getObjectSchema(undefined)`.
|
|
1898
|
+
|
|
1899
|
+
`childObject` is REQUIRED on `MasterDetailDetailConfig` and is what every downstream read is keyed
|
|
1900
|
+
on — `deriveDetail(d.childObject, …)`, the child-schema cache, and the FK scope of each child
|
|
1901
|
+
fetch. But a detail entry reaches the renderer straight off an authored schema, so a malformed one
|
|
1902
|
+
arrives with the key `undefined`, and the resolve effect asked the data layer for it anyway.
|
|
1903
|
+
Measured: mounting the block with a detail entry that carries no `childObject` issued
|
|
1904
|
+
`getObjectSchema(undefined)` — a real backend receives a query for an object literally named
|
|
1905
|
+
`undefined`, and whatever it returns becomes the console's problem.
|
|
1906
|
+
|
|
1907
|
+
The resolve effect now skips such an entry and warns, leaving it in place so the grid card shows
|
|
1908
|
+
its config hint and the row-state array stays index-matched. This is the choice `RelatedList`
|
|
1909
|
+
already makes for the same class of missing key (*"has no referenceField/parentId — refusing to
|
|
1910
|
+
fetch all rows"*), and the sibling child-schema-cache effect in this same component already spelled
|
|
1911
|
+
it `.filter(Boolean)`; the three now agree. A detail collection that names its child object fetches
|
|
1912
|
+
exactly as before.
|
|
1913
|
+
- c9a7252: `record:line_items` declines to fetch the child schema of a panel whose child object it never resolved, instead of calling `getObjectSchema(undefined)`.
|
|
1914
|
+
|
|
1915
|
+
`childObject` is declared `required: true` on the block's registry entry and typed `string` on
|
|
1916
|
+
`LineItemsPanelSchema`, but nothing enforces either — `inputs[].required` is designer metadata, and
|
|
1917
|
+
the block has no spec schema — so a node reaches the renderer straight off an authored schema with
|
|
1918
|
+
the key `undefined`, and the child-schema effect asked the data layer for it anyway. Measured:
|
|
1919
|
+
mounting the block through the registry with `childObject` unset issued
|
|
1920
|
+
`getObjectSchema(undefined)`, and a real backend receives a query for an object literally named
|
|
1921
|
+
`undefined`. The effect's `.catch` then turned the answer into a null child schema, so the visible
|
|
1922
|
+
outcome was a silently unsanitized child grid rather than an error.
|
|
1923
|
+
|
|
1924
|
+
The effect now declines and warns, naming the key and what to set it to, and clears the cached child
|
|
1925
|
+
schema so a later save is never sanitized against a previous object's fields. This is the choice
|
|
1926
|
+
`RelatedList` already makes for the same class of missing key (*"has no referenceField/parentId —
|
|
1927
|
+
refusing to fetch all rows"*), and the one `object-master-detail-form` makes on this exact key. A
|
|
1928
|
+
panel that names its child object fetches exactly as before.
|
|
1929
|
+
- 5f19b92: `record:line_items` declines to LOAD OR WRITE the rows of a panel whose child object it never resolved, instead of calling `find(undefined, …)` — the sibling site of the child-schema decline, in the same component.
|
|
1930
|
+
|
|
1931
|
+
`LineItemsPanel` read `schema.childObject` at two sites. The first now declines; the row load still
|
|
1932
|
+
asked the data layer to `find` an object literally named `undefined`, scoped by
|
|
1933
|
+
`{ [relationshipField]: parentId }`. `load` guarded the *data source* and the *parent id* — the two
|
|
1934
|
+
things `RelatedList` calls "can I scope this query" — but not the *object being queried*.
|
|
1935
|
+
|
|
1936
|
+
Declining that fetch is not enough on its own, and this is the part worth reading: `load` owns
|
|
1937
|
+
`loading`, and the panel branched `loading ? "Loading…" : !parentId ? "Save the record first…" :
|
|
1938
|
+
<grid>`. So the moment the fetch declined, an unresolvable panel with a parent id bound fell to the
|
|
1939
|
+
third branch and showed an **empty editable grid with an Add button, over an object that does not
|
|
1940
|
+
exist** — a worse outcome than the fetch it replaced. Measured on the pre-fix component: one
|
|
1941
|
+
keystroke in the grid's always-present ghost row materialised a row, which enabled Save, which
|
|
1942
|
+
reached `batchTransaction([{ object: undefined, action: 'create', data: { qty: 3, invoice: 'inv-1' } }])`.
|
|
1943
|
+
The bad *read* was one keystroke away from a bad *write*.
|
|
1944
|
+
|
|
1945
|
+
An unresolvable panel therefore gets its own render branch — a config hint naming `childObject` and
|
|
1946
|
+
what to set it to, following the precedent `object-master-detail-form` set for this exact key and
|
|
1947
|
+
`AdvancedChartImpl`'s refusal placeholders. It is checked ahead of `loading`, because nothing is
|
|
1948
|
+
pending: the schema itself already says the panel can never resolve, so there is no honest moment at
|
|
1949
|
+
which "Loading…" is true. `save` takes the same one-line guard, for the one route the render branch
|
|
1950
|
+
cannot close — a schema edited to drop `childObject` while rows are already dirty.
|
|
1951
|
+
|
|
1952
|
+
A panel that names its child object loads, renders and saves exactly as before.
|
|
1953
|
+
- e0b289d: An authored section `visibleWhen` on `formType: 'tabbed'` or `formType: 'wizard'` now
|
|
1954
|
+
**reports** that the layout cannot honour it, instead of being silently dropped
|
|
1955
|
+
(objectui#6237).
|
|
1956
|
+
|
|
1957
|
+
`ObjectForm` rebuilds each section key by key when it delegates to a layout, so a key
|
|
1958
|
+
the map does not copy never reaches a renderer at all. Three of those maps copy
|
|
1959
|
+
`visibleWhen` (`split` / `drawer` / `modal`, objectui#6111) and the flat arm carries it
|
|
1960
|
+
on the `section-divider` pseudo-field — but the `tabbed` and `wizard` maps copy nothing,
|
|
1961
|
+
so an author writing the key on those two arms watched it do exactly nothing, with no
|
|
1962
|
+
signal anywhere. That silence is the defect this ships against.
|
|
1963
|
+
|
|
1964
|
+
The two arms now log a warning naming the layout and the sections whose predicate is
|
|
1965
|
+
being dropped, through one shared message builder so they cannot drift apart.
|
|
1966
|
+
|
|
1967
|
+
**This changes no rendering behaviour** — the predicate is still not evaluated on those
|
|
1968
|
+
arms. It is the interim half of a maintainer ruling (2026-08-29) that the real repair is
|
|
1969
|
+
a **design** task: one renderer-side section/group contract with a predicate slot,
|
|
1970
|
+
designed once for every layout arm (tabbed / TabbedForm / WizardForm / flat) rather than
|
|
1971
|
+
patched arm by arm. The ruling requires the diagnostic to land first, so the gap stops
|
|
1972
|
+
being invisible while that contract is designed.
|
|
1973
|
+
|
|
1974
|
+
Deliberately silent on the arms that work, so the warning stays worth reading:
|
|
1975
|
+
|
|
1976
|
+
- `split` / `drawer` / `modal`, and the flat layout — all honour a section `visibleWhen`.
|
|
1977
|
+
- `ModalForm` with `contentLayout: 'tabbed'` — honours it through the real
|
|
1978
|
+
`FormFieldTab.visibleWhen` slot that landed in objectui#6619. "Tabbed" names two
|
|
1979
|
+
different things on this card; only `formType: 'tabbed'` (`TabbedForm`) is inert.
|
|
1980
|
+
- A master-detail parent, which re-enters `ObjectForm` through its own parent schema —
|
|
1981
|
+
the report is left to that inner pass, where the real layout is decided (a
|
|
1982
|
+
master-detail `wizard` parent renders `simple`, which honours the key). Reporting at
|
|
1983
|
+
both would double-report the tabbed parent and false-report the wizard one.
|
|
1984
|
+
|
|
1985
|
+
No authorable key is added anywhere: declaring `visibleWhen` on a type whose renderer
|
|
1986
|
+
ignores it is the defect this card family exists to close, and the shared
|
|
1987
|
+
`FormSectionConfig` that `WizardForm` uses for its steps makes that trap concrete.
|
|
1988
|
+
- 3b9c774: Split `WizardStepConfig` off `FormSectionConfig`, and correct the section-predicate
|
|
1989
|
+
support table (objectui#6237, maintainer ruling 2026-08-30).
|
|
1990
|
+
|
|
1991
|
+
`WizardForm` typed its steps as `Omit<FormSectionConfig, 'visibleWhen'>` — a
|
|
1992
|
+
subtraction from the TabbedForm section type, which is the predicate-CARRYING
|
|
1993
|
+
type. That defended the one key it named and left the mechanism open: every key
|
|
1994
|
+
added to `FormSectionConfig` reached a wizard step by default, so the next
|
|
1995
|
+
predicate in the same family (`readonlyWhen` / `requiredWhen`, already this
|
|
1996
|
+
package's field-level vocabulary) would have handed the wizard a silent slot its
|
|
1997
|
+
renderer does not read — the declared-but-unenforced shape the ruling split the
|
|
1998
|
+
types to stop.
|
|
1999
|
+
|
|
2000
|
+
`WizardStepConfig` is now declared independently in `WizardForm.tsx`, which is
|
|
2001
|
+
simply what `SplitFormSectionConfig`, `ModalFormSectionConfig` and
|
|
2002
|
+
`DrawerFormSectionConfig` already do: each layout owns its group shape, documents
|
|
2003
|
+
`className` / `gridClassName` in its own terms, and declares `visibleWhen` only
|
|
2004
|
+
where its renderer honours it. The derivation flips from subtractive to additive
|
|
2005
|
+
— a key is authorable on a wizard step only if someone writes it there.
|
|
2006
|
+
|
|
2007
|
+
No behaviour change and no key added or removed: `WizardStepConfig` exports the
|
|
2008
|
+
same key set it already had, and `visibleWhen` on a wizard step literal was, and
|
|
2009
|
+
remains, a compile error. What is new is that it stays one for the whole
|
|
2010
|
+
predicate family, pinned by a type-level assertion that fails the build if any
|
|
2011
|
+
`*When` key ever appears on the step type.
|
|
2012
|
+
|
|
2013
|
+
Documentation repair in the same stroke: the support table in the README and in
|
|
2014
|
+
`content/docs/plugins/plugin-form.mdx` still said `formType: 'tabbed'` sections
|
|
2015
|
+
drop the predicate. That stopped being true when the tabbed arm landed — the row
|
|
2016
|
+
now reads **Yes**, the surrounding prose no longer claims two inert arms or a
|
|
2017
|
+
diagnostic that fires for `tabbed`, and the wizard row stays **No**, which is
|
|
2018
|
+
still exactly true.
|
|
2019
|
+
- 1c19722: `object-master-detail-form` now renders a config hint naming `childObject` for a detail
|
|
2020
|
+
collection whose child object never resolved, instead of `Loading columns…` forever
|
|
2021
|
+
(objectui#6360).
|
|
2022
|
+
|
|
2023
|
+
`MasterDetailForm` already declines to fetch the schema of such a detail (objectui#5940)
|
|
2024
|
+
and returns the entry unresolved, which is correct — asking the data layer for an object
|
|
2025
|
+
literally named `undefined` is what that guard removed. But the decline is precisely the
|
|
2026
|
+
guarantee that the entry's columns can never arrive, and the render branch it fell into
|
|
2027
|
+
read `!d.columns?.length ? <p>Loading columns…</p>`. The author was shown a
|
|
2028
|
+
spinner-shaped message that was permanently, unfixably wrong, and that never named the
|
|
2029
|
+
key they had to set.
|
|
2030
|
+
|
|
2031
|
+
The `!d.childObject` case now takes its own branch, checked **before** the columns arm
|
|
2032
|
+
because nothing is pending — there is no first paint where "loading" is honest. The copy
|
|
2033
|
+
and structure are `LineItemsPanel`'s, which took the same branch for the same key in
|
|
2034
|
+
objectui#6194 / PR #6359; the two components had been disagreeing about what an author
|
|
2035
|
+
sees for the identical authoring mistake, and the weaker of the two was the one that read
|
|
2036
|
+
as the precedent. The hint carries its own `data-testid` (`md-detail-no-child-object`).
|
|
2037
|
+
|
|
2038
|
+
Two source comments — at the decline itself and at the resolver's `catch` — asserted that
|
|
2039
|
+
"the grid card shows a config hint". They were false, and following them cost a reader a
|
|
2040
|
+
run of the component. The first is now true and says so. The second is **corrected rather
|
|
2041
|
+
than made true**: a detail whose schema fetch *threw* does name a child object, so it
|
|
2042
|
+
skips the new branch and still lands on `Loading columns…`. Distinguishing that from
|
|
2043
|
+
"still in flight" needs per-entry error state the resolver does not keep, so it is filed
|
|
2044
|
+
as objectui#6372 and the comment now points at it instead of promising a hint that is not
|
|
2045
|
+
rendered there.
|
|
2046
|
+
|
|
2047
|
+
No spec or schema change: `childObject` is already REQUIRED on `MasterDetailDetailConfig`.
|
|
2048
|
+
This is renderer-side reporting of an authoring error that the type system cannot catch,
|
|
2049
|
+
because a detail entry reaches this renderer straight off an authored JSON schema.
|
|
2050
|
+
- faa863d: `MasterDetailForm` gives every detail collection a per-entry record carrying its own
|
|
2051
|
+
identity and its own resolution status, closing two defects that both came from the same
|
|
2052
|
+
absence (objectui#6372, objectui#6371).
|
|
2053
|
+
|
|
2054
|
+
`resolvedDetails` was a plain `MasterDetailDetailConfig[]` with no per-entry metadata, so
|
|
2055
|
+
both *what happened to this entry* and *which entry is this* were inferred from the
|
|
2056
|
+
entry's position in the array. One record answers both, which is why they land together —
|
|
2057
|
+
either one alone would have reshaped this structure and the second would then have
|
|
2058
|
+
rewritten the first.
|
|
2059
|
+
|
|
2060
|
+
**objectui#6372 — a detail whose schema fetch threw sat on "Loading columns…" forever.**
|
|
2061
|
+
The resolver's `catch` returned the entry unchanged, and an entry with no `columns` is how
|
|
2062
|
+
*still in flight* is represented too, so the two states were indistinguishable and the
|
|
2063
|
+
render branch showed the same spinner-shaped message for both. For the failed one it never
|
|
2064
|
+
ended: the fetch is not retried, so nothing could ever replace it. Entries now carry a
|
|
2065
|
+
resolution status, and a failed one renders a refusal placeholder naming the child object
|
|
2066
|
+
whose schema could not be loaded (shaped on `AdvancedChartImpl`'s refusal placeholders —
|
|
2067
|
+
`role="status"`, because a refusal is a state, not an alert). Measured before the fix
|
|
2068
|
+
rather than read from source: a detail whose `getObjectSchema` rejects rendered
|
|
2069
|
+
`<p>Loading columns…</p>`.
|
|
2070
|
+
|
|
2071
|
+
The thrown error is no longer discarded. The bare `catch` threw away the whole diagnosis,
|
|
2072
|
+
so whoever debugged this had neither a message nor a stack; the decline arm next to it has
|
|
2073
|
+
warned since objectui#5940, and this arm now matches it and passes the error object
|
|
2074
|
+
through.
|
|
2075
|
+
|
|
2076
|
+
⭐ The fetch and the derive are caught **separately**, because they are different failures
|
|
2077
|
+
with different truths to tell. A schema that loads fine and then yields no relationship
|
|
2078
|
+
field is a configuration error, and calling it a load failure would be false. That arm's
|
|
2079
|
+
render is deliberately unchanged; only its error stops being swallowed.
|
|
2080
|
+
|
|
2081
|
+
**objectui#6371 — a declined entry had no identity across a reorder.** There was no
|
|
2082
|
+
duplicate-key collision: the map index is unique among siblings by construction, so two
|
|
2083
|
+
declined details keyed as `undefined-0` and `undefined-1`, distinct. The real defect is
|
|
2084
|
+
that for a declined entry the data half of that key is `undefined`, leaving position as
|
|
2085
|
+
the entry's whole identity — and the row-state store was addressed the same way, seeded
|
|
2086
|
+
once at mount and never re-synced when the authored config changed. Reordering or removing
|
|
2087
|
+
an entry therefore handed a collection a different collection's rows.
|
|
2088
|
+
|
|
2089
|
+
Entries now carry an id synthesized once from the incoming config: the child object for a
|
|
2090
|
+
named collection, and the authored position for a declined one, which has no other
|
|
2091
|
+
identity to offer. Row state is keyed by that id, so a collection can only ever read its
|
|
2092
|
+
own slot. Three reads were affected, not the one the report named:
|
|
2093
|
+
|
|
2094
|
+
- the grid value, which showed the wrong collection's rows;
|
|
2095
|
+
- the document **subtotal** reducer, so a reorder did not merely mis-associate a grid, it
|
|
2096
|
+
mis-computed the total;
|
|
2097
|
+
- the batch payload on save, which read
|
|
2098
|
+
`details.filter(d => d.relationshipField).map((d, i) => state[i])` — after the filter `i`
|
|
2099
|
+
indexed the filtered array while the row state was indexed against the full one, so a
|
|
2100
|
+
declined entry above a real collection shifted every read below it by one and that
|
|
2101
|
+
collection's rows were **silently dropped from the transaction**. Data loss on save, not
|
|
2102
|
+
a display defect.
|
|
2103
|
+
- fd814d6: `MasterDetailForm` shows a config hint naming `relationshipField` for a detail collection
|
|
2104
|
+
whose child schema **loaded fine but could not be derived from**, instead of a permanent
|
|
2105
|
+
`Loading columns…` (objectui#6394).
|
|
2106
|
+
|
|
2107
|
+
This is the third and last arm of the same resolver to be closed. `deriveDetail` throws
|
|
2108
|
+
when no lookup/`master_detail` field on the child object references the parent — a
|
|
2109
|
+
configuration error whose remedy is a key the author writes. The `catch` returned the
|
|
2110
|
+
entry unresolved, so it fell through to `!d.columns?.length ? <p>Loading columns…</p>`,
|
|
2111
|
+
and that message never ended: the derive is not retried, so those columns could never
|
|
2112
|
+
arrive. Same unbounded-wait-shown-as-a-spinner family as objectui#5940 / objectui#6188 /
|
|
2113
|
+
objectui#6194 / objectui#6360 / objectui#6372.
|
|
2114
|
+
|
|
2115
|
+
The entry now carries `status: 'underivable'`, and the renderer gives it a branch of its
|
|
2116
|
+
own that names both ends of the relationship it could not find and the key to set:
|
|
2117
|
+
|
|
2118
|
+
> Could not work out how `po_line` links to `purchase_order`: no lookup or master_detail
|
|
2119
|
+
> field on it references the parent. Set `relationshipField` on this collection to the
|
|
2120
|
+
> field that holds the parent record.
|
|
2121
|
+
|
|
2122
|
+
⛔ Deliberately **not** objectui#6372's refusal placeholder, which states the schema could
|
|
2123
|
+
not be loaded — false for a schema that loaded fine. The two failures keep separate copy
|
|
2124
|
+
because they have different remedies: one is "check the object exists and reload", this one
|
|
2125
|
+
is "set this key". The thrown error is still logged with its stack (objectui#6372), since
|
|
2126
|
+
the placeholder shows the author the key rather than the raw message.
|
|
2127
|
+
|
|
2128
|
+
Behaviour is unchanged for the other two arms and for a detail that is genuinely still
|
|
2129
|
+
fetching — that one keeps `Loading columns…`, where the message is true.
|
|
2130
|
+
- 3beef6d: The spec's `dataSource` element binding is now DECLARED by the blocks that read
|
|
2131
|
+
it, so the html tier stops reporting the one working saved-view spelling as
|
|
2132
|
+
`unknown-prop` (objectui#6678).
|
|
2133
|
+
|
|
2134
|
+
`PageComponentSchema.dataSource` — `{ object, view, filter, sort, limit }` — is
|
|
2135
|
+
the one spelling that resolves a saved view for an object-bound block. It works,
|
|
2136
|
+
and it drew the identical `unknown-prop` warning as the two spellings that do
|
|
2137
|
+
nothing (`viewName`, `view`), because `validateTree` looks a prop up in the
|
|
2138
|
+
block's declared `inputs` and no registration declared this key. On the tier
|
|
2139
|
+
built to accept AI-authored pages, where the diagnostic IS the contract, the
|
|
2140
|
+
only signal pointed away from the key that works.
|
|
2141
|
+
|
|
2142
|
+
Adopting the maintainer ruling of 2026-08-29 — option B **in the injection
|
|
2143
|
+
form**:
|
|
2144
|
+
|
|
2145
|
+
- `ELEMENT_DATA_SOURCE_INPUT` is the single declaration, in `@object-ui/core`
|
|
2146
|
+
beside the binding's own semantics; `Registry.register` emits it for any
|
|
2147
|
+
registration whose renderer passed through the new `elementDataSourceBlock()`
|
|
2148
|
+
seam. One mechanism, one copy — not a hand-kept declaration per block, which is
|
|
2149
|
+
the shape that drifts and that a new block forgets. The seam lives in
|
|
2150
|
+
`@object-ui/core` and is re-exported by `@object-ui/react` beside
|
|
2151
|
+
`ElementDataSourceGate` for discoverability; call sites take the core import,
|
|
2152
|
+
because a registration runs at module scope and this repo's suites partially
|
|
2153
|
+
mock `@object-ui/react`.
|
|
2154
|
+
- Seventeen renderers, in thirteen files across twelve packages, reach the seam
|
|
2155
|
+
and now publish the key to the save gate, the parser whitelist, the generated
|
|
2156
|
+
JSX authoring types and the block list. The card named nine blocks; the tree
|
|
2157
|
+
also has `plugin-grid`, `plugin-timeline`, two further `plugin-form` blocks and
|
|
2158
|
+
`element:record_picker` — nothing was hand-listed, so the mechanism covered
|
|
2159
|
+
them. `element:record_picker` consumes the gate's HOOK and status panels rather
|
|
2160
|
+
than the wrapper tag (its object lives under `properties`), and was found by a
|
|
2161
|
+
render probe rather than by reading sources.
|
|
2162
|
+
- `dataSource` on a block that does NOT read it (`flex`, `card`) still reports
|
|
2163
|
+
`unknown-prop`. Adding the key to `sdui-parser`'s `BASE_PROPS` was refused for
|
|
2164
|
+
exactly this reason — that set mirrors `BaseSchema`, and silencing the key
|
|
2165
|
+
everywhere would make the diagnostic lie in the other direction.
|
|
2166
|
+
- New `check:element-data-source-declaration` fails any source that consumes the
|
|
2167
|
+
gate without reaching the seam, so a block added tomorrow cannot forget.
|
|
2168
|
+
|
|
2169
|
+
Behaviour of the binding itself is unchanged — this is a declaration, not a
|
|
2170
|
+
resolution change. The saved view still resolves its columns, and an
|
|
2171
|
+
unresolvable `view` still fails loudly rather than widening to the object's full
|
|
2172
|
+
scope.
|
|
2173
|
+
|
|
2174
|
+
The spec/registry parity gates (repo-wide and the `record:related_list` per-block
|
|
2175
|
+
pin) now derive their accepted set from the WHOLE node contract rather than from
|
|
2176
|
+
`ComponentPropsMap[type]` alone. `PageComponentSchema` accepts and keeps
|
|
2177
|
+
`dataSource` on a page-component node — it is a node-level key, a sibling of
|
|
2178
|
+
`type` and `className`, not a per-block prop — so the gates' previous complaint
|
|
2179
|
+
was measurably wrong. Derived from the spec, not exempted, and both still
|
|
2180
|
+
discriminate against an invented key.
|
|
2181
|
+
- ecd9cb2: Wizard view v1, the objectui half (Card R, objectui#6985) — alignment + pins for the
|
|
2182
|
+
ruled `type: 'wizard'` tightening (objectstack#13622 D1–D8, maintainer ruling
|
|
2183
|
+
2026-08-31; spec half PR objectstack#13733).
|
|
2184
|
+
|
|
2185
|
+
The renderer was already aligned: `WizardStepConfig` carries no predicate/collapse
|
|
2186
|
+
keys (objectui#6237's ruled split), the wizard route drops-and-reports an authored
|
|
2187
|
+
step `visibleWhen`, and `allowSkip` has been navigation-freedom-not-validation-
|
|
2188
|
+
exemption since #2959. This card lands the residue:
|
|
2189
|
+
|
|
2190
|
+
- **metadata-admin view create seeds one starter step for a wizard** (app-shell
|
|
2191
|
+
`anchors.ts`): the create body used to emit `sections: []` for every form type,
|
|
2192
|
+
which for `type: 'wizard'` is exactly the shape the tightened spec refuses at
|
|
2193
|
+
parse (D7 — a stepless wizard silently rendered as a plain simple form). Same
|
|
2194
|
+
seed-the-required-shape move the flow anchor makes for its `type` enum
|
|
2195
|
+
(objectui#2326). Other form types keep the bare `[]` — only the wizard variant
|
|
2196
|
+
refuses emptiness.
|
|
2197
|
+
- **`@object-ui/types` TSDoc states the ruled wizard boundary** where the shared
|
|
2198
|
+
section/form types restate the form-view family: `ObjectFormSection.visibleWhen`
|
|
2199
|
+
/ `collapsible` / `collapsed` name the wizard drop + spec-door refusal;
|
|
2200
|
+
`ObjectFormSchema.sections` states sections-ARE-steps and array-order-is-step-
|
|
2201
|
+
order; `allowSkip` states the D4 semantics. Type SHAPES are unchanged — the
|
|
2202
|
+
spec's own ruled mechanism is a parse-time refinement over the single shared
|
|
2203
|
+
section schema (D2 option A), which these types mirror at the type level.
|
|
2204
|
+
- **Consumer-side behaviour pins** (`wizardRuledSemantics-6985.test.tsx`): the
|
|
2205
|
+
wizard-inert step keys are dropped, never honoured (a denying `visibleWhen`
|
|
2206
|
+
does not remove a step; `collapsible`/`collapsed: true` produce no collapse
|
|
2207
|
+
affordance, with a positive control on the affordance probe); the empty-steps
|
|
2208
|
+
wizard's measured degradation to a simple form is pinned as the shape the spec
|
|
2209
|
+
door now refuses (one-step wizards stay legal — no arity floor); array order
|
|
2210
|
+
is step order (with a reversed-array control).
|
|
2211
|
+
- **Installed-spec door pins** (`wizardSpecDoor-6985.test.ts`), gated on a
|
|
2212
|
+
capability probe of the installed `FormViewSchema` rather than a version
|
|
2213
|
+
string: the post-Card-S half (refusal messages, prescriptions, the authored-
|
|
2214
|
+
`false` collapse boundary, the wizard-scoped control) activates by itself on
|
|
2215
|
+
the lockfile bump that brings the tightening in; until then the pre-tightening
|
|
2216
|
+
half records the 17.2.x accept-set it measured. `steps:` is pinned refused on
|
|
2217
|
+
every spec line.
|
|
2218
|
+
|
|
2219
|
+
No teaching material — the objectstack#13337 / objectstack#13267 fence lifts only after both halves land;
|
|
2220
|
+
docs changes here are TSDoc/comments only.
|
|
2221
|
+
- 3ed3eec: fix(plugin-form,plugin-list,plugin-view,react): read `SchemaRendererContext` as declared, not through a cast to `any` (objectui#7209)
|
|
2222
|
+
|
|
2223
|
+
Six reads of the renderer context erased its type. Five cast at the read —
|
|
2224
|
+
`useContext(SchemaRendererContext as React.Context<any>)` in the
|
|
2225
|
+
`embeddable-form` and `object-master-detail-form` bridges, in `ListViewBlock`
|
|
2226
|
+
and in `useResolvedDataSource`, and the same cast spelled with a bare
|
|
2227
|
+
`Context<any>` in `useElementDataSource` — and the `object-view` renderer's
|
|
2228
|
+
module re-declared the imported context as a `React.Context<any>`. Through
|
|
2229
|
+
that cast a read of a member the context does not declare compiled clean,
|
|
2230
|
+
which is how the phantom `ctx.formValues ?? ctx.data` channel retired by
|
|
2231
|
+
objectui#7206 went unnoticed. Each of these reads now sees `SchemaRendererContextType` as
|
|
2232
|
+
`@object-ui/react` declares it, so such a read is a compile error on the day it
|
|
2233
|
+
is written.
|
|
2234
|
+
|
|
2235
|
+
No runtime behaviour changes. Removing the casts surfaced no read of an
|
|
2236
|
+
undeclared member; it surfaced two places where the declared `null` ("no
|
|
2237
|
+
adapter bound") met a prop that does not admit it:
|
|
2238
|
+
|
|
2239
|
+
- `embeddable-form` handed that `null` to `EmbeddableForm`'s optional
|
|
2240
|
+
`dataSource`. It now collapses it to `undefined`, as
|
|
2241
|
+
`object-master-detail-form` already did; `EmbeddableForm` only ever tests the
|
|
2242
|
+
adapter for truthiness, so the two absences behave the same.
|
|
2243
|
+
- `object-view` / `view` hand it to `ObjectViewProps.dataSource`, which is
|
|
2244
|
+
declared required and stays so (objectui#7842). That one value now carries a
|
|
2245
|
+
narrow, commented assertion instead of the whole context being erased; the
|
|
2246
|
+
value passed is the same as before.
|
|
2247
|
+
|
|
2248
|
+
The published signatures of `useResolvedDataSource` and `useElementDataSource`
|
|
2249
|
+
do not move.
|
|
2250
|
+
- 7dedec6: A master-detail form no longer ends on a screen asserting both a failure and a success
|
|
2251
|
+
(objectui#7345).
|
|
2252
|
+
|
|
2253
|
+
`MasterDetailForm` raised its two save outcomes — `handleSaved`'s confirmation and
|
|
2254
|
+
`handleError`'s refusal — under sonner's auto-generated ids, so nothing held a handle on
|
|
2255
|
+
the previous attempt's toast. A save the server refused left its error toast on screen,
|
|
2256
|
+
and when the user corrected the input and saved again inside that toast's lifetime the
|
|
2257
|
+
confirmation landed *beside* the refusal, exactly the objectui#7252 defect on a renderer
|
|
2258
|
+
that fix did not touch.
|
|
2259
|
+
|
|
2260
|
+
Both outcomes now travel under one stable per-form id (`React.useId()`-scoped, the same
|
|
2261
|
+
spelling the form renderer and the console's `FormPage` publish under), and each save
|
|
2262
|
+
attempt retires the previous attempt's toast before it starts:
|
|
2263
|
+
|
|
2264
|
+
- with no host `onSuccess` (SDUI / embedded hosts), the confirmation supersedes the
|
|
2265
|
+
refusal instead of stacking beside it;
|
|
2266
|
+
- with a host `onSuccess` (the console), where the built-in confirmation is deliberately
|
|
2267
|
+
skipped, the dismissal is what retires the refusal — otherwise it stood over a save
|
|
2268
|
+
that had succeeded.
|
|
2269
|
+
|
|
2270
|
+
Toast durations are unchanged: this is about supersession, not lifetime.
|
|
2271
|
+
- 0809f8a: Fix a refused master-detail save showing the same refusal twice (objectui#7354).
|
|
2272
|
+
|
|
2273
|
+
`MasterDetailForm`'s `handleError` toasted the write's error message under its own
|
|
2274
|
+
sonner id, and the parent `<ObjectForm>` then re-threw the same error, which surfaced
|
|
2275
|
+
in the form renderer's own catch (`packages/components/.../renderers/form/form.tsx`)
|
|
2276
|
+
and toasted it AGAIN under a different, independently-generated `form-outcome:<id>`.
|
|
2277
|
+
Two raisers reported one refusal.
|
|
2278
|
+
|
|
2279
|
+
`handleError` now only releases the save guard and forwards the error to the host's
|
|
2280
|
+
own `onError` (if supplied) for bookkeeping — it no longer toasts. Display is left to
|
|
2281
|
+
the form renderer's catch, which every `submitHandler` host already shares and which
|
|
2282
|
+
already extracts a better message (permission-aware, honours the author-marked
|
|
2283
|
+
`userMessage`) than the raw `err.message` this callback showed.
|
|
2284
|
+
- c6198c2: **Breaking for authored metadata:** `ComponentInput.label`, `ComponentInput.defaultValue` and
|
|
2285
|
+
`ComponentInput.advanced` are RETIRED on both faces (objectui#7493 item ① and objectui#7781;
|
|
2286
|
+
maintainer ruling A of 2026-09-06, immediate, no deprecation window; ADR-0049 enforce-or-remove).
|
|
2287
|
+
They are the three keys the manifest serializer does not forward, and nothing read them on any
|
|
2288
|
+
publication or consumption path.
|
|
2289
|
+
|
|
2290
|
+
No manifest ever published them, so no consumer could ever have read them. `sdui-parser`'s
|
|
2291
|
+
serializer (`packages/sdui-parser/src/index.ts`) forwards exactly seven keys per input — `name`,
|
|
2292
|
+
`type`, `of`, `required`, `enum`, `binding`, `description` — so a value authored under any of the three
|
|
2293
|
+
never reached `sdui.manifest.json`, the generated JSX `.d.ts`, or a diagnostic; its boundary type
|
|
2294
|
+
has no slot for them; the registry's data-source seam reads `name` only; and neither the designer
|
|
2295
|
+
nor the app-shell inspectors consult registry `inputs` at all. A structural census over every
|
|
2296
|
+
`inputs:` array in the repository (re-measured on this change's merge-base, `name` 951 and `type`
|
|
2297
|
+
951 as the controls) counted the writes: `label` 908, `defaultValue` 245, `advanced` 9 — written on
|
|
2298
|
+
nearly every registration, read by nothing.
|
|
2299
|
+
|
|
2300
|
+
FROM → TO, per key — all three **TOMBSTONED, not removed**, because the route was measured on
|
|
2301
|
+
the built face before it was chosen: `ComponentInputSchema` is a non-strict `z.object`, and an
|
|
2302
|
+
undeclared key parses GREEN and is silently STRIPPED, so a deletion would have swallowed 1,162
|
|
2303
|
+
authored values in silence. The tombstone is what makes the refusal loud and by name.
|
|
2304
|
+
|
|
2305
|
+
- `label?: string` → `label?: never` on the interface, `retirementTombstone()` on the Zod mirror.
|
|
2306
|
+
Migration: delete the key. An input is identified by its `name` on every path that reaches it;
|
|
2307
|
+
nothing ever rendered a label for it.
|
|
2308
|
+
- `defaultValue?: any` → `defaultValue?: never` / `retirementTombstone()`. Migration: delete the
|
|
2309
|
+
key. The renderer's own fallback read IS the default; tell the author about it in `description`,
|
|
2310
|
+
which IS published. (Tightening the type to `unknown` was ruled out: it closes no error class,
|
|
2311
|
+
since nothing reads the value.)
|
|
2312
|
+
- `advanced?: boolean` → `advanced?: never` / `retirementTombstone()`. Migration: delete the key.
|
|
2313
|
+
No designer surface ever hid an "advanced" input; there is nothing to write instead.
|
|
2314
|
+
|
|
2315
|
+
The retirement kit: `?: never` on `ComponentInput` (`packages/types/src/base.ts`), so authoring one
|
|
2316
|
+
is a `tsc` error at the registration site; `retirementTombstone()` on `ComponentInputSchema`
|
|
2317
|
+
(`packages/types/src/zod/base.zod.ts`), so an authored value is REFUSED at parse time with
|
|
2318
|
+
`code: 'invalid_type'`, the key named in the issue `path`, and the migration note as the message
|
|
2319
|
+
(one string, both channels). Pinned in
|
|
2320
|
+
`packages/types/src/__tests__/component-input-retired-keys-7493.test.ts`, which also holds a
|
|
2321
|
+
tree-scoped absence census over every `inputs:` array under `packages/**` and `apps/**`.
|
|
2322
|
+
|
|
2323
|
+
Accept-set change, stated plainly for reviewers: a document that sets any of the three keys on a
|
|
2324
|
+
`ComponentInput` used to parse GREEN (the value was then dropped by the serializer) and now parses
|
|
2325
|
+
RED. Every in-repo authoring site — 1,199 keys across 110 registration files, the three standalone
|
|
2326
|
+
`ComponentInput[]` arrays and the two named input arrays `tsc` found included — is deleted in the same change, as the ruling's split rule
|
|
2327
|
+
requires; the `WidgetRegistry` seam no longer copies the widget-manifest values onto the synthesized
|
|
2328
|
+
`ComponentInput` (they fed nothing), and the data-source declaration `ELEMENT_DATA_SOURCE_INPUT`
|
|
2329
|
+
drops its `label`. The patch entries on the other packages record exactly that: their registrations
|
|
2330
|
+
stop authoring inert keys, with no runtime or published-manifest change.
|
|
2331
|
+
|
|
2332
|
+
The nine test files that read `defaultValue` off a registration were re-pinned against the
|
|
2333
|
+
renderer's ACTUAL default (its own fallback read, or the `defaultProps` it ships) instead of the
|
|
2334
|
+
declaration that went away; two assertions that only restated the shadow default were dropped with
|
|
2335
|
+
the reason on the line.
|
|
2336
|
+
|
|
2337
|
+
The in-repo zero is what was measured. Whether anything OUTSIDE this repository writes these keys
|
|
2338
|
+
is not measurable from here (the objectui#5674 limit); converting such a write from a silent drop
|
|
2339
|
+
into a named refusal is exactly what the tombstones buy. `WidgetInput`'s own `label` /
|
|
2340
|
+
`defaultValue` / `advanced` (the widget-manifest face) stay declared and writable — nothing has
|
|
2341
|
+
ruled on that face; that it now has no reader either is recorded as objectui#7911.
|
|
2342
|
+
- af9e957: fix(types,components,plugin-form,console,core): a faulted `visibleWhen` refuses the submit, naming the field and the rule; a blank field rule is refused; blank gates are diagnosed (objectui#8069)
|
|
2343
|
+
|
|
2344
|
+
⚠️ **User-visible, and a narrowing.** A form whose field `visibleWhen` cannot be
|
|
2345
|
+
evaluated — a typo in a column name, a syntax error, an unbound root — used to
|
|
2346
|
+
render the field (fail-open) and submit as if the rule had said "show". It now
|
|
2347
|
+
still renders the field, and **refuses the submit** with a message that names
|
|
2348
|
+
the field and the rule (`form.visibleWhenFaulted`). This is ADR-0137 D2 as
|
|
2349
|
+
ruled for objectui#8069 (Q1 = B, one judge per rule): no server evaluates a
|
|
2350
|
+
field's `visibleWhen`, so its fail-open render direction (D3) was a silent grant
|
|
2351
|
+
— a field the working rule would have hidden, drawn, edited and written. The
|
|
2352
|
+
refusal applies on the record form renderer (`form.tsx`, every `ObjectForm`
|
|
2353
|
+
layout), the console's `/forms/:name` and `/f/:slug` page, and the wizard's
|
|
2354
|
+
cross-step gate at final submit.
|
|
2355
|
+
|
|
2356
|
+
- `requiredWhen` / `readonlyWhen` are **unchanged on the client**: the server
|
|
2357
|
+
evaluates both and refuses a faulted one itself (ADR-0137 D2), and the form
|
|
2358
|
+
renderer shows that field-attributed refusal beside the input.
|
|
2359
|
+
- **Accepted residuals:** a `visibleWhen` reading `previous` cannot be
|
|
2360
|
+
evaluated on a CREATE form, so such a form is refused on every submit; and
|
|
2361
|
+
the wizard's cross-step gate binds no `previous` in either mode, so the same
|
|
2362
|
+
rule is refused at the final submit of an EDIT wizard too.
|
|
2363
|
+
- A **blank** field rule (`''`, whitespace, an envelope whose `source` is
|
|
2364
|
+
blank) is a fault, not "no rule" (ADR-0137 D2). A STORED blank `visibleWhen`
|
|
2365
|
+
is refused at submit on the same three paths; a stored blank `requiredWhen` /
|
|
2366
|
+
`readonlyWhen` is the server's to refuse.
|
|
2367
|
+
|
|
2368
|
+
⚠️ **Narrowing (`@object-ui/types`): `FormFieldSchema` refuses a blank field
|
|
2369
|
+
rule at parse.** `visibleWhen`, `readonlyWhen` and `requiredWhen` on a form
|
|
2370
|
+
field now refuse a predicate that is blank after trimming, with the spec's own
|
|
2371
|
+
sentence (`EVALUATED_EXPRESSION_SOURCE_REQUIRED`) — ADR-0137 D1, the same
|
|
2372
|
+
refusal `@objectstack/spec` makes on `FieldSchema`. The accepted SHAPE is
|
|
2373
|
+
unchanged (a string or `{ dialect?, source }`, `ExpressionWireSchema`'s own
|
|
2374
|
+
arms): only the blank value is taken out. A blank GATE — `BaseSchema`'s
|
|
2375
|
+
`visible` / `hidden` / `disabled`, a form field's view-level `visibleOn`, an
|
|
2376
|
+
option's `visibleWhen` — still parses and is still read as "no gate".
|
|
2377
|
+
|
|
2378
|
+
**Added (`@object-ui/core`):** `resolveFieldRuleState` returns `faults` beside
|
|
2379
|
+
its three verdicts — the per-rule fault report the submit paths read, filled
|
|
2380
|
+
from the same evaluation — and its type, `FieldRuleFaults`, is exported.
|
|
2381
|
+
**Added (`@object-ui/i18n`):** the `form.visibleWhenFaulted` key in all ten
|
|
2382
|
+
packs.
|
|
2383
|
+
|
|
2384
|
+
**Diagnosed, no verdict changed (ADR-0137 D4):** a blank CEL gate reaching
|
|
2385
|
+
`ExpressionEvaluator.evaluateCondition`, and a blank gate folded to "no gate" by
|
|
2386
|
+
`hasDeclaredPredicate`, now each report once through the same `[blank]` channel
|
|
2387
|
+
field-rule faults use. Both verdicts (objectui#3850 / #3960) are unchanged,
|
|
2388
|
+
`throwOnError` included.
|
|
2389
|
+
- c03d03b: An authored `max_length` on a rich-content field is now VISIBLE, not only enforced at
|
|
2390
|
+
submit (objectui#8438).
|
|
2391
|
+
|
|
2392
|
+
**The defect.** `markdown`, `html` and `richtext` are three registry keys served by ONE
|
|
2393
|
+
widget, `RichTextField`. That widget read `maxLength` / `max_length` nowhere, while
|
|
2394
|
+
`buildValidationRules` — which has no field-type gate — compiled the same key into a
|
|
2395
|
+
react-hook-form rule for every field. So a cap authored on any of the three was enforced
|
|
2396
|
+
when the form was submitted and invisible before then: no native stop, no character
|
|
2397
|
+
counter, nothing named in `aria-describedby`. The person was told the limit only after
|
|
2398
|
+
writing the text, which is the worst of the three possible orderings.
|
|
2399
|
+
|
|
2400
|
+
**The fix, and where it is NOT.** The card was filed as "`richtext` is missing from
|
|
2401
|
+
`ObjectForm`'s maxLength guard and `EmbeddableForm`'s `DEFAULT_MAX_LENGTH`". Re-measured,
|
|
2402
|
+
neither list could have carried the cap:
|
|
2403
|
+
|
|
2404
|
+
- `ObjectForm`'s guard writes `formField.maxLength`, but a registered widget's metadata
|
|
2405
|
+
carrier is `formField.field` — a different object. Ablating that assignment entirely
|
|
2406
|
+
changed no rendered attribute, for any of the four types it names. It is left in place
|
|
2407
|
+
(it is live for the other form-field producer) with the measurement recorded at the site.
|
|
2408
|
+
- `EmbeddableForm`'s `DEFAULT_MAX_LENGTH` did deliver 5000 for `markdown` and `html`, and
|
|
2409
|
+
`RichTextField` then dropped it unread.
|
|
2410
|
+
|
|
2411
|
+
⇒ The cap was lost for **all three** rich-content keys, not for `richtext` alone.
|
|
2412
|
+
`RichTextField` now dual-reads `maxLength ?? max_length` off its metadata carrier — the
|
|
2413
|
+
same read `TextAreaField` has carried since framework#1878 §3 — and forwards it to the
|
|
2414
|
+
native stop, the `CharacterCount` counter and the `aria-describedby` wiring, on both the
|
|
2415
|
+
inline surface and the fullscreen dialog.
|
|
2416
|
+
|
|
2417
|
+
**What changes for you.** A `markdown`, `html` or `richtext` field that already declares
|
|
2418
|
+
`max_length` (or the spec-canonical `maxLength`) now shows a counter and stops typing at
|
|
2419
|
+
the cap, where before it silently accepted the overflow and failed on submit. A field with
|
|
2420
|
+
no authored cap is unchanged. In `EmbeddableForm`, a public form's `richtext` field is now
|
|
2421
|
+
capped at the 5000-character long-text default like its two siblings, instead of accepting
|
|
2422
|
+
unbounded input.
|
|
2423
|
+
|
|
2424
|
+
**New export.** `@object-ui/fields` publishes `RICH_TEXT_FIELD_TYPES` (and the
|
|
2425
|
+
`RichTextFieldType` union), the key set of the widget's display table, so consumers stop
|
|
2426
|
+
hand-writing the list. `EmbeddableForm`'s cap table is derived from it. This answers the
|
|
2427
|
+
list question objectui#4831 raised and its fix declined to remove — the root cause behind
|
|
2428
|
+
objectui#4250, objectui#4831 and this card: a hand-written list that stops at two of one
|
|
2429
|
+
widget's three registry keys can no longer omit the third, because it no longer names one.
|
|
2430
|
+
|
|
2431
|
+
⚠️ **Dated note, 2026-09-30 — the widget reads `maxLength` alone — objectui#11070.** Later in this same release objectui#11070 (its text-family round) retired the snake_case `max_length`: `RichTextField` reads `maxLength` only, not `maxLength ?? max_length`, and `MarkdownFieldMetadata`, `HtmlFieldMetadata` and `RichtextFieldMetadata` declare `maxLength` in its place. So "a field that already declares `max_length` … now shows a counter" above holds for `maxLength` only; a ceiling that reaches the widget spelled `max_length` gets no stop and no counter. `.changeset/11070-text-family-round5.md` states what ships; the text above is kept as the reading of this change.
|
|
2432
|
+
- 8fda009: `object-form`'s top-level `fields` (and its `form` / `view:form` alias and
|
|
2433
|
+
`object-master-detail-form`'s parent `fields`) now emit a named
|
|
2434
|
+
`console.warn` when a member resolves to no field name, instead of silently
|
|
2435
|
+
dropping it (objectui#8738 route 1, ruled after route 2 landed in a prior
|
|
2436
|
+
release).
|
|
2437
|
+
|
|
2438
|
+
Top-level `fields` reads only bare field-name strings (`{ name }` tolerated)
|
|
2439
|
+
— a different vocabulary from `sections[].fields`, which also accepts the
|
|
2440
|
+
spec `FormFieldSchema` object (identity key `field`, e.g.
|
|
2441
|
+
`{ field: 'note', colSpan: 2 }`). Moving one of those objects into a
|
|
2442
|
+
top-level `fields` array resolves to no name and used to vanish without a
|
|
2443
|
+
word; it is now reported once per distinct offender via `console.warn`,
|
|
2444
|
+
naming the skipped shape and the vocabulary difference, modelled on
|
|
2445
|
+
`sectionFields.ts`'s existing `warnOnMixedVocabulary`.
|
|
2446
|
+
|
|
2447
|
+
The render outcome is unchanged — the member is still dropped, not resolved;
|
|
2448
|
+
this is a diagnostic-only addition, not a lenient fallback. `form` /
|
|
2449
|
+
`view:form` (the same `ObjectFormRenderer`) and `object-master-detail-form`'s
|
|
2450
|
+
parent `fields` (routed through the same `SimpleObjectForm` read path via
|
|
2451
|
+
`ObjectForm`) inherit the warning for free; both registrations' `description`
|
|
2452
|
+
now also document the vocabulary (objectui#8847).
|
|
2453
|
+
- fd9bf26: `object-form`'s top-level `fields` input now documents its member vocabulary
|
|
2454
|
+
(objectui#8738, route 2 of 2 — route 1, a diagnostic `console.warn`, was a
|
|
2455
|
+
separate ruling still pending when this was written; it landed 92 minutes
|
|
2456
|
+
later, see the dated note below).
|
|
2457
|
+
|
|
2458
|
+
The registration declared `{ name: 'fields', type: 'array' }` with no
|
|
2459
|
+
description, so an author had nowhere to read that this key's members are
|
|
2460
|
+
**bare field names** — a different vocabulary from `sections[].fields`, which
|
|
2461
|
+
also accepts the spec `FormFieldSchema` object (identity key `field`, e.g.
|
|
2462
|
+
`{ field: 'note', colSpan: 2 }`). Moving one of those objects to the top-level
|
|
2463
|
+
`fields` resolves to no name and is skipped by `SimpleObjectForm`
|
|
2464
|
+
(`ObjectForm.tsx`) and by `buildFlatFields` (`flatFields.ts`, shared by the
|
|
2465
|
+
drawer/modal presentations). Behaviour is unchanged by this entry; it only adds
|
|
2466
|
+
the description text an author would need to avoid the drop before writing it.
|
|
2467
|
+
|
|
2468
|
+
⚠️ Dated correction, so the original reading is not taken for the behaviour of
|
|
2469
|
+
the release this publishes into. As written — `fd9bf26df0`, 2026-09-09T14:28:48Z
|
|
2470
|
+
— that drop was SILENT: no throw, no warning, no empty-state. That reading was
|
|
2471
|
+
true for 92 minutes. `8fda009057` (objectui#8859, route 1 of the same card) put
|
|
2472
|
+
a de-duplicated `console.warn` at both named read sites at 2026-09-09T16:00:31Z,
|
|
2473
|
+
so the drop is no longer silent. What did NOT change: the member is still
|
|
2474
|
+
skipped, and there is still no throw and no empty-state.
|
|
2475
|
+
- 60e1f80: `embeddable-form`'s top-level `fields` input now documents its member
|
|
2476
|
+
vocabulary (objectui#8847, the last of the registrations #8738 opened — the
|
|
2477
|
+
same trap already documented on `object-form`, `form` / `view:form`, and
|
|
2478
|
+
`object-master-detail-form`'s parent `fields`).
|
|
2479
|
+
|
|
2480
|
+
The registration declared `{ name: 'fields', type: 'array' }` with no
|
|
2481
|
+
description, so an author had nowhere to read that this key's members are
|
|
2482
|
+
**bare field names** — a different vocabulary from `sections[].fields`, which
|
|
2483
|
+
also accepts the spec `FormFieldSchema` object (identity key `field`, e.g.
|
|
2484
|
+
`{ field: 'note', colSpan: 2 }`). `EmbeddableForm` passes `config.fields`
|
|
2485
|
+
straight through to `<ObjectForm>` with no `sections`, so it renders through
|
|
2486
|
+
the same `SimpleObjectForm` read path as `object-form`; moving one of those
|
|
2487
|
+
objects into `embeddable-form`'s `fields` resolves to no name and is silently
|
|
2488
|
+
skipped. Behaviour is unchanged — this only adds the description text, and
|
|
2489
|
+
records (measured, not assumed) that this surface already inherits the
|
|
2490
|
+
`console.warn` route 1 (objectui#8738/#8859) added at that same read site.
|
|
2491
|
+
- 72d6587: A record id is a `string` everywhere in the published types, as
|
|
2492
|
+
`@objectstack/spec` has always declared it. Three published declarations that
|
|
2493
|
+
admitted `number` no longer do.
|
|
2494
|
+
|
|
2495
|
+
⚠️ **BREAKING if your code hands a numeric primary key to any of these three:**
|
|
2496
|
+
|
|
2497
|
+
- **`RecordContextValue.recordId`** (`@object-ui/react`) — the value
|
|
2498
|
+
`useRecordContext()` gives you is now a `string`, never a `number`.
|
|
2499
|
+
- **`DataSource.update`'s `id` parameter** (`@object-ui/types`) — a call that
|
|
2500
|
+
passes a `string | number` is now a type error. Adapters that *implement*
|
|
2501
|
+
`DataSource` are unaffected (see Migration).
|
|
2502
|
+
- **`TransactionOperation.id`** (`@object-ui/core`) — the operation record you
|
|
2503
|
+
hand to `TransactionManager.recordOperation()` must carry a `string` id. If
|
|
2504
|
+
you build that object from a numeric key, convert it where you build it. This
|
|
2505
|
+
type is exported from the package root, so this is a breaking change for
|
|
2506
|
+
`@object-ui/core` consumers in its own right, not just a knock-on.
|
|
2507
|
+
|
|
2508
|
+
Ships as `minor` per the launch-window convention: objectui's
|
|
2509
|
+
`major` is a cross-repo pin to `@objectstack`'s so that "same major means
|
|
2510
|
+
compatible" holds across the two repos
|
|
2511
|
+
(`scripts/check-changeset-no-major.mjs`), and objectui's own breaking changes
|
|
2512
|
+
ship as `minor` with the break named where it lands — this entry is the channel
|
|
2513
|
+
that carries it.
|
|
2514
|
+
|
|
2515
|
+
## What changed
|
|
2516
|
+
|
|
2517
|
+
- `RecordContextValue.recordId` (`@object-ui/react`) was
|
|
2518
|
+
`string | number | null | undefined`; it is now `string | null | undefined`.
|
|
2519
|
+
- `DataSource.update`'s `id` parameter (`@object-ui/types`) was
|
|
2520
|
+
`string | number`; it is now `string`.
|
|
2521
|
+
- `TransactionOperation.id` (`@object-ui/core`) was `string | number`; it is now
|
|
2522
|
+
`string`. Its sibling `BatchTransactionOperation.id` was already a `string`,
|
|
2523
|
+
so the two operation records finally agree.
|
|
2524
|
+
- `LineItemsPanel` (`@object-ui/plugin-form`) drops the type assertion
|
|
2525
|
+
objectui#9304 left on its parent id. That assertion was the only thing making
|
|
2526
|
+
the context declaration and `buildMasterDetailEditBatch(parentId: string)`
|
|
2527
|
+
meet; the declaration now does it, so the evidence is discharged.
|
|
2528
|
+
|
|
2529
|
+
## Why the protocol, and not a wider consumer type
|
|
2530
|
+
|
|
2531
|
+
`@objectstack/spec` declares a record id as `z.string()` on every record door —
|
|
2532
|
+
get, update, delete and the batch operation. A consumer type may not be wider
|
|
2533
|
+
than the protocol: a declaration that admits `number` promises callers something
|
|
2534
|
+
the wire never carries, and the promise is kept only by an assertion at the far
|
|
2535
|
+
end, which is what this card was filed about.
|
|
2536
|
+
|
|
2537
|
+
## Internal consumers repaired at the same time (no public contract moves)
|
|
2538
|
+
|
|
2539
|
+
Narrowing an interface **parameter** never reaches implementors — TypeScript
|
|
2540
|
+
compares method parameters bivariantly, so an adapter that still declares
|
|
2541
|
+
`id: string | number` keeps satisfying `DataSource`. It reaches **callers**. A
|
|
2542
|
+
full local type-check of every workspace type-check program found exactly six,
|
|
2543
|
+
in three packages, and each was red because a further declaration one layer in
|
|
2544
|
+
was itself wider than the protocol. All three are narrowed here, types only, with
|
|
2545
|
+
no runtime change and no coercion added at any call site:
|
|
2546
|
+
|
|
2547
|
+
- `UserPreferenceRecord.id` and the `cachedRowId` it feeds
|
|
2548
|
+
(`@object-ui/data-objectstack`) are `string`. Module-local, not published —
|
|
2549
|
+
these rows are read back off the protocol, so the union was a claim the wire
|
|
2550
|
+
never makes.
|
|
2551
|
+
- `resolveRecordId`'s return type (`@object-ui/plugin-grid`) is
|
|
2552
|
+
`string | undefined`. Module-local, not published — it annotates `any`-typed
|
|
2553
|
+
row data, so the union was an assertion rather than a measurement.
|
|
2554
|
+
|
|
2555
|
+
## Migration — no `String(...)` at your call sites
|
|
2556
|
+
|
|
2557
|
+
`RecordContextProvider` still **accepts** `string | number | null | undefined`
|
|
2558
|
+
and narrows it once, itself. A host that mounts a record with a numeric primary
|
|
2559
|
+
key therefore changes nothing: the conversion is paid at that injection
|
|
2560
|
+
boundary, typed, in one place. Consumers of `useRecordContext()` read a
|
|
2561
|
+
`string`.
|
|
2562
|
+
|
|
2563
|
+
`DataSource` implementors are unaffected — TypeScript compares method parameters
|
|
2564
|
+
bivariantly, so an adapter that still declares `id: string | number` continues
|
|
2565
|
+
to satisfy the interface. What changes is the **caller** side: a call that passes
|
|
2566
|
+
a `string | number` to `dataSource.update` is now a type error. A backend whose
|
|
2567
|
+
primary keys are numeric maps them at its own adapter boundary rather than
|
|
2568
|
+
pushing the union through every caller.
|
|
2569
|
+
|
|
2570
|
+
For `TransactionOperation`, the same rule applies one level up: build the
|
|
2571
|
+
operation record with a `string` id. If the id arrives from a numeric-keyed
|
|
2572
|
+
backend, convert it in your adapter — the one place that knows the backend's key
|
|
2573
|
+
type — rather than at each `recordOperation()` call. Nothing about this change
|
|
2574
|
+
alters what is sent over the wire; only the declarations moved.
|
|
2575
|
+
- 6df9141: `ObjectForm`'s numeric `step` now follows `scale` (decimal places), not `precision`
|
|
2576
|
+
(total digit count) — objectui#9574.
|
|
2577
|
+
|
|
2578
|
+
`@objectstack/spec` declares the two members apart: `precision` is "Total digits
|
|
2579
|
+
(non-negative integer)", `scale` is "Decimal places (non-negative integer)". The
|
|
2580
|
+
auto-generated form field derived its step from `precision`, so a `decimal(10, 0)`
|
|
2581
|
+
field — ten total digits, ZERO decimal places — was handed `step` `1e-10` instead of
|
|
2582
|
+
`1`, and a `decimal(10, 2)` got `1e-10` instead of `0.01`. `NumberField` states the
|
|
2583
|
+
same rule verbatim one layer down and already moved; this is the producer one layer up
|
|
2584
|
+
catching up with it.
|
|
2585
|
+
|
|
2586
|
+
Two further consequences of the same expression:
|
|
2587
|
+
|
|
2588
|
+
- **`scale: 0` is honoured.** The test is `typeof field.scale === 'number'`, not
|
|
2589
|
+
truthiness — a declared zero means "steps by 1", and the spec's own example of a
|
|
2590
|
+
`scale: 0` field is an ordinal integer.
|
|
2591
|
+
- **An undeclared `scale` now resolves to `step="any"`, not to no attribute at all.**
|
|
2592
|
+
An absent `step` is HTML's default of 1, which marks every decimal `:invalid` and
|
|
2593
|
+
blocks the submit — a granularity the author never declared. This matches
|
|
2594
|
+
`NumberField`'s tail for the same case.
|
|
2595
|
+
|
|
2596
|
+
**Where the change is observable.** The registered `field:*` widgets do not read this
|
|
2597
|
+
key: their metadata carrier is the raw object-schema field, their DOM whitelist does
|
|
2598
|
+
not forward `step`, and each derives its own — measured by deleting the assignment
|
|
2599
|
+
outright and re-rendering, which left every `<input>` on that route byte-identical. The
|
|
2600
|
+
key IS read on the renderer's unregistered-widget fallback, where a field whose declared
|
|
2601
|
+
`widget` names a component the app never registered has its leftover props spread onto
|
|
2602
|
+
the `<input>`; there the producer's step is the granularity the browser enforces. Both
|
|
2603
|
+
routes are pinned side by side in `objectFormNumericStep-9574.test.tsx`.
|
|
2604
|
+
|
|
2605
|
+
`percent` is covered by the same expression it was always covered by, and moves from
|
|
2606
|
+
`10^-precision` to `10^-scale`. What `scale` means for a percent field that stores a
|
|
2607
|
+
0–1 fraction — stored decimals, which is what the record validator enforces, or
|
|
2608
|
+
displayed percentage points, which is the landed display convention — is open at
|
|
2609
|
+
objectui#9810 and is not decided here.
|
|
2610
|
+
- 99c4eb8: `object-form`: a section that declares `collapsed: true` is now collapsible — the
|
|
2611
|
+
disclosure control is installed whether or not `collapsible` is also written
|
|
2612
|
+
(objectui#9780, maintainer ruling 2026-09-18, letter A).
|
|
2613
|
+
|
|
2614
|
+
The grouped layout read `collapsed` for the section's initial state
|
|
2615
|
+
unconditionally, but installed the toggle only for a section that also declared
|
|
2616
|
+
`collapsible`. The two are independent members and every declaration face accepts
|
|
2617
|
+
either alone, so `collapsed: true` written by itself — the most natural spelling of
|
|
2618
|
+
"collapsed by default" — rendered a permanently closed section: its fields were out
|
|
2619
|
+
of the DOM and nothing on the page could bring them back, with no error, warning or
|
|
2620
|
+
degradation.
|
|
2621
|
+
|
|
2622
|
+
`collapsible: true` on its own is unchanged (open, toggle present), and
|
|
2623
|
+
`collapsible: false` together with `collapsed: true` resolves the same way the
|
|
2624
|
+
ruling states: collapsed wins and the toggle is present. A section declaring
|
|
2625
|
+
neither member is untouched. Nothing is refused that was accepted before — the
|
|
2626
|
+
accept set is unchanged and only behaviour widens, so no author can lose anything
|
|
2627
|
+
they could previously depend on.
|
|
2628
|
+
- 19d1f24: fix(plugin-form): the drawer arm hands a section's `description` to the divider it already draws
|
|
2629
|
+
|
|
2630
|
+
An `object-form` rendered as `formType: 'drawer'` dropped its sections'
|
|
2631
|
+
`description`, and it dropped it one layer LATER than the default layout did.
|
|
2632
|
+
`ObjectForm`'s drawer map copies the key onto `DrawerFormSectionConfig` — which
|
|
2633
|
+
has always declared it — and `DrawerForm`'s own `section-divider` pushes then
|
|
2634
|
+
rebuilt the row key by key without it. So the author wrote the key correctly,
|
|
2635
|
+
the first layer passed it correctly, and the last layer did not take it. The
|
|
2636
|
+
sibling member `label` arrived in the same call, so a titled section with a
|
|
2637
|
+
blurb rendered the title and silently ate the blurb.
|
|
2638
|
+
|
|
2639
|
+
Both of that file's pushes dropped it and both are repaired: the
|
|
2640
|
+
explicit-sections one (the shape a form view authors) and the
|
|
2641
|
+
derived-fieldGroups one (the fallback the drawer takes when the object's own
|
|
2642
|
+
metadata declares `fieldGroups` and the host passes no sections). One key copied
|
|
2643
|
+
onto each push; nothing else in the renderer moved.
|
|
2644
|
+
|
|
2645
|
+
⛔ No gate was widened. The condition that decides whether a divider row is
|
|
2646
|
+
drawn at all also decides the ADR-0089 section predicate and the objectui#6236
|
|
2647
|
+
membership claim that gates the whole group, so it is a ruling about other keys.
|
|
2648
|
+
The derived push keeps its heading gate untouched, and a derived group with no
|
|
2649
|
+
heading still draws no divider and still drops its blurb. ⚠️ The
|
|
2650
|
+
explicit-sections push, by contrast, was ALREADY unconditional, so once the key
|
|
2651
|
+
is copied a drawer section carrying a `description` and no heading renders the
|
|
2652
|
+
blurb alone — where the default layout draws nothing for the same member. That
|
|
2653
|
+
is a consequence of copying the key, not of touching the gate, and it is pinned
|
|
2654
|
+
as a reading.
|
|
2655
|
+
|
|
2656
|
+
The drawer arm's behaviour here was watched by nothing before this change. It
|
|
2657
|
+
now has its own pin, `drawerFormSectionDescription-9834`, covering both routes a
|
|
2658
|
+
host can take into `DrawerForm` — through the real `ObjectForm` with
|
|
2659
|
+
`formType: 'drawer'`, and mounted directly — plus the derived-fieldGroups push,
|
|
2660
|
+
an absence control and the ungated-push reading above.
|
|
2661
|
+
`objectFormSectionMembers-8071` carried a sentence about which arms render a
|
|
2662
|
+
blurb for a headingless member, which this change makes false; it is corrected
|
|
2663
|
+
in the same change, as is the member-pin ledger entry for `object-form.sections`
|
|
2664
|
+
in `registry-inputs-spec-parity`, whose prose describes the behaviour being
|
|
2665
|
+
repaired. No published behaviour of `@object-ui/console` changes.
|
|
2666
|
+
- 8f37c4e: Render a headingless section's `description` in the default (grouped) form layout
|
|
2667
|
+
(objectui#9835, maintainer ruling 2026-09-18, letter B).
|
|
2668
|
+
|
|
2669
|
+
A form section that authored a `description` and neither `name` nor `label` lost its
|
|
2670
|
+
blurb on the default layout — the one a section-carrying `object-form` gets when it
|
|
2671
|
+
declares no `formType`. That layout pushes its `section-divider` row only for a member
|
|
2672
|
+
that yields a heading, so a member with no heading had nothing to carry the blurb on.
|
|
2673
|
+
The `split`, `modal`, `wizard` and `tabbed` arms all rendered it, so one authored
|
|
2674
|
+
section rendered differently depending only on which arm the host chose.
|
|
2675
|
+
|
|
2676
|
+
Such a member now gets a **blurb-only** row: it carries the `description` and nothing
|
|
2677
|
+
else. In particular it does **not** carry the ADR-0089 `visibleWhen` predicate, the
|
|
2678
|
+
objectui#6236 membership claim that gates the whole group, or the
|
|
2679
|
+
`collapsed` / `collapsible` pair. Widening the heading gate instead (the obvious
|
|
2680
|
+
one-line fix) was considered and refused: that same condition implements those three
|
|
2681
|
+
semantics, so widening it would let an untitled section's predicate hide its group and
|
|
2682
|
+
let an untitled `collapsed: true` remove its fields from the DOM with no control to
|
|
2683
|
+
bring them back — a decision about two other keys, taken while fixing a blurb.
|
|
2684
|
+
|
|
2685
|
+
**What changes for authors.** A section with a `description` and no title now shows
|
|
2686
|
+
that text on every layout arm instead of four out of five. Nothing else moves: a titled
|
|
2687
|
+
section renders exactly as before, and an untitled section's `visibleWhen`, `collapsed`
|
|
2688
|
+
and `collapsible` keep doing exactly what they did (nothing) on this layout.
|
|
2689
|
+
- 58d65c5: `object-form`: a drawer section that declares `collapsed: true` can be opened again. The
|
|
2690
|
+
drawer now resolves `collapsed` / `collapsible` the way the default layout does
|
|
2691
|
+
(objectui#9849 step one, which converges the collapse rules onto objectui#9780).
|
|
2692
|
+
|
|
2693
|
+
The drawer's explicit `sections` path read `collapsed` for the section's initial state
|
|
2694
|
+
unconditionally, but installed the disclosure control only for a section that also wrote
|
|
2695
|
+
`collapsible`. So `formType: 'drawer'` with `collapsed: true` written alone (the most
|
|
2696
|
+
natural spelling of "collapsed by default") drew a permanently closed section. Its fields
|
|
2697
|
+
were out of the DOM and nothing on the page could bring them back. The default layout
|
|
2698
|
+
stopped doing this under objectui#9780. The drawer still did, and its derived-`fieldGroups`
|
|
2699
|
+
path spelled the same two keys a third way.
|
|
2700
|
+
|
|
2701
|
+
All three paths now use one resolution:
|
|
2702
|
+
|
|
2703
|
+
- `collapsed: true` implies `collapsible`, so the control is installed.
|
|
2704
|
+
- `collapsible: false` together with `collapsed: true` resolves in favour of `collapsed`,
|
|
2705
|
+
with the control present.
|
|
2706
|
+
- `collapsible: true` alone is unchanged: the section is open and has the control.
|
|
2707
|
+
- A section declaring neither member is untouched.
|
|
2708
|
+
- A section is collapsed only while its row is on the page to carry the control. A drawer
|
|
2709
|
+
section with no heading and no description therefore keeps its fields open instead of
|
|
2710
|
+
hiding them behind nothing.
|
|
2711
|
+
|
|
2712
|
+
Nothing is refused that was accepted before. The accept set is unchanged. The sections
|
|
2713
|
+
that render differently are ones whose fields could not be reached, plus two edge cases:
|
|
2714
|
+
|
|
2715
|
+
- A section that shares its key with a collapsible sibling is no longer hidden by that
|
|
2716
|
+
sibling's toggle.
|
|
2717
|
+
- A drawer section that was declared `collapsed` at mount, and whose declaration is later
|
|
2718
|
+
removed, now follows the declaration until the user toggles it. It used to stay closed.
|
|
2719
|
+
- 509f8ed: The section-configuration → `section-divider` projection is one path, and a modal form
|
|
2720
|
+
built from an object's own `fieldGroups` metadata now renders each group's authored
|
|
2721
|
+
`description` (objectui#9849).
|
|
2722
|
+
|
|
2723
|
+
Six pushes across `ObjectForm`, `ModalForm` and `DrawerForm` each rebuilt the divider row
|
|
2724
|
+
key by key. A key one of them forgot was invisible to the author, because its siblings on
|
|
2725
|
+
the same section arrived in the same call — the same failure carded three times running
|
|
2726
|
+
for one key (objectui#9779 default arm, objectui#9834 drawer arm, and this card's modal
|
|
2727
|
+
derived push, the last site still dropping it). A modal form that passes no `sections` and
|
|
2728
|
+
leans on the object's declared `fieldGroups` drew each group's heading and silently ate
|
|
2729
|
+
the blurb its author wrote.
|
|
2730
|
+
|
|
2731
|
+
All six sites now go through one projection, so the key set is copied once: the blurb, the
|
|
2732
|
+
ADR-0089 `visibleWhen` predicate, the objectui#6236 membership claim, the collapse pair
|
|
2733
|
+
and the row's span. A key added there is added for every arm at once, and a key dropped
|
|
2734
|
+
there is dropped for every arm at once — which is what makes the loss visible instead of
|
|
2735
|
+
silent.
|
|
2736
|
+
|
|
2737
|
+
**What changes for authors.** A `fieldGroups`-derived group renders its `description` in a
|
|
2738
|
+
modal form, as it already did in a drawer and on the default layout. Nothing else moves:
|
|
2739
|
+
every arm's gate, its collapse resolution and its predicate handling are unchanged, and
|
|
2740
|
+
each arm hands its own resolutions to the shared projection rather than inheriting
|
|
2741
|
+
another's.
|
|
2742
|
+
- 76f1543: `object-master-detail-form`'s `fields` registration no longer claims to be
|
|
2743
|
+
"Ignored when `sections` is given", and a section member the top-level `fields`
|
|
2744
|
+
excludes is now reported instead of vanishing (objectui#9884).
|
|
2745
|
+
|
|
2746
|
+
The declaration was the wrong half, ruled from the tree rather than from the
|
|
2747
|
+
principle. One `SimpleObjectForm` renders this block's parent form and
|
|
2748
|
+
`object-form` alike — `MasterDetailForm`'s `parentSchema` memo literally builds
|
|
2749
|
+
a `{ type: 'object-form', ... }` node and renders it through a directly
|
|
2750
|
+
imported `<ObjectForm>` — and the three sibling `fields` registrations
|
|
2751
|
+
(`object-form`, `form`, `embeddable-form`) all declare the key as the field
|
|
2752
|
+
selection with no such exemption, with `objectFormFieldsMembers-8071` pinning
|
|
2753
|
+
it as one. Honouring the exemption would have falsified three declarations to
|
|
2754
|
+
satisfy one, and it would have done so on a pool that is not only the layout:
|
|
2755
|
+
`fields` builds the parent field set that also feeds create defaults, the
|
|
2756
|
+
`initialValues` merge and the values a submit carries.
|
|
2757
|
+
|
|
2758
|
+
So the rendered outcome is unchanged and the intersection stands: the parent
|
|
2759
|
+
field pool is built from `fields` first, and each section resolves its members
|
|
2760
|
+
against that pool. What changed is that the loss is audible.
|
|
2761
|
+
`warnSectionMemberExcludedByFields` (`sectionFields.ts`, beside the two
|
|
2762
|
+
warnings objectui#8738 and objectui#3090 added) names the section, the member
|
|
2763
|
+
and the two keys that collided, once per distinct pair, whenever a member the
|
|
2764
|
+
object really declares is dropped for the sole reason that `fields` omits it —
|
|
2765
|
+
including the expensive case where it was the section's last surviving member
|
|
2766
|
+
and the section disappears with its heading. A member the object never declares
|
|
2767
|
+
at all is deliberately NOT recruited into this warning: it resolves to nothing
|
|
2768
|
+
whether or not `fields` is authored, which is a different silence with a
|
|
2769
|
+
different remedy.
|
|
2770
|
+
|
|
2771
|
+
The `object-master-detail-form.sections` member pin moves in the same change
|
|
2772
|
+
rather than after it: its sharp row keeps the two DOM assertions objectui#8071
|
|
2773
|
+
slice 15 wrote, and gains the warning legs plus a firing control and a leg
|
|
2774
|
+
keeping the warning off the other silence. Nothing in it was relaxed.
|
|
2775
|
+
|
|
2776
|
+
The member-pin ledger moves with it. `apps/console`'s registry-inputs parity
|
|
2777
|
+
suite carried the old reading in two prose passages — the
|
|
2778
|
+
`object-master-detail-form.sections` entry quoting the retired sentence and
|
|
2779
|
+
recording the drop as having no diagnostic and the finding as not acted on, and
|
|
2780
|
+
the slice-15 narrative repeating the quote. Both now state the ruling. That file
|
|
2781
|
+
is a test and releases nothing: `@object-ui/console` ships no source from it, so
|
|
2782
|
+
this declaration covers `@object-ui/plugin-form` alone.
|
|
2783
|
+
|
|
2784
|
+
Refs objectui#9884, objectui#8071.
|
|
2785
|
+
- a78cd37: Dates and numbers across the console and the plugins format in the session's
|
|
2786
|
+
display locale instead of the machine's (objectui#9909).
|
|
2787
|
+
|
|
2788
|
+
Every one of these faces handed `Intl` — directly, or through a formatter called
|
|
2789
|
+
without one — either no locale tag or an explicit `undefined`, which is not "the
|
|
2790
|
+
user's locale": it is the locale of the machine the browser runs on, which is
|
|
2791
|
+
neither of this repository's two locale channels.
|
|
2792
|
+
A German or Spanish session therefore read, beside a translated label, a date or
|
|
2793
|
+
an amount grouped and decimal-marked the machine's way — and an amount with
|
|
2794
|
+
inverted separators does not read as unformatted, it reads as a different
|
|
2795
|
+
number. They now go through `useDisplayLocale()` (tenant regional default, then
|
|
2796
|
+
the active UI language, then `'en'`), and a plain helper takes that tag from its
|
|
2797
|
+
caller. The surfaces:
|
|
2798
|
+
|
|
2799
|
+
- **`@object-ui/fields`** — `GridField`'s numeric and currency cells (list mode
|
|
2800
|
+
and computed columns) and both total cells. The same cell helper's date branch
|
|
2801
|
+
already used the display locale, so a single grid row read two conventions at
|
|
2802
|
+
once.
|
|
2803
|
+
- **`@object-ui/plugin-charts`** — ISO-date x-axis ticks, compact y-axis ticks,
|
|
2804
|
+
the single-value face, spec `format` strings and the tooltip value.
|
|
2805
|
+
- **`@object-ui/app-shell`** — the organization invitations, members and
|
|
2806
|
+
accept-invitation dates; marketplace version dates; the AI conversation row's
|
|
2807
|
+
older-than-a-week date and its tooltip; the build-debug token count; the
|
|
2808
|
+
record approvals timeline; and the metadata-admin audit, history, external
|
|
2809
|
+
datasource snapshot, schema-browser row estimate, flow-run start and job
|
|
2810
|
+
next-fire faces. The audit, history and flow-run panels format these in the
|
|
2811
|
+
DISPLAY locale, not in the `locale` prop they take for their UI strings
|
|
2812
|
+
(`AuditPanel` defaults that prop to `'en-US'`; `HistoryPanel` and
|
|
2813
|
+
`FlowRunsPanel` leave it optional).
|
|
2814
|
+
- **`@object-ui/console`** — the approvals inbox's timestamp tooltips, amounts
|
|
2815
|
+
and payload summary; the audit log's timestamp column; the flow runs table and
|
|
2816
|
+
its run detail.
|
|
2817
|
+
- **`@object-ui/components`** — the export dialog's record counts and the debug
|
|
2818
|
+
panel's event times.
|
|
2819
|
+
- **`@object-ui/plugin-form`** — the analytics submission count, the
|
|
2820
|
+
master-detail subtotal / tax / total stack and the edit-conflict dialog's
|
|
2821
|
+
"their save" time.
|
|
2822
|
+
- **`@object-ui/plugin-chatbot`** — the approvals inbox's past-30-days date and
|
|
2823
|
+
the times stamped on local-mode chat messages (the user's and the
|
|
2824
|
+
auto-response).
|
|
2825
|
+
- **`@object-ui/plugin-dashboard`** — the record-count badge, and the currency
|
|
2826
|
+
and date-format branches of `renderFieldValue`, which was handed the display
|
|
2827
|
+
locale and spent it only on its percent branch.
|
|
2828
|
+
- **`@object-ui/plugin-grid`** — the record-detail panel's inferred currency
|
|
2829
|
+
value and the mobile card's amount line, siblings of date cells already on the
|
|
2830
|
+
display locale.
|
|
2831
|
+
- **`@object-ui/plugin-designer`** — `VersionHistory`'s version times.
|
|
2832
|
+
|
|
2833
|
+
A host that mounts one of the components that newly read the display locale with
|
|
2834
|
+
NO `I18nProvider` now gets react-i18next's once-per-module `NO_I18NEXT_INSTANCE`
|
|
2835
|
+
notice in the console on the first such mount; the faces still render, in the
|
|
2836
|
+
channel's `'en'` last resort, and mounting an `I18nProvider` (as the console
|
|
2837
|
+
does) avoids the notice.
|
|
2838
|
+
|
|
2839
|
+
**Additive API** (nothing that compiled before stops compiling):
|
|
2840
|
+
|
|
2841
|
+
- `@object-ui/types`: `ValidationContext` gains an optional `locale`. At this
|
|
2842
|
+
change, the `@object-ui/core` validation engine prints the `date_min` /
|
|
2843
|
+
`date_max` bound in it; omitted, `Intl` follows the runtime default, as
|
|
2844
|
+
`formatDisplayNumber` already declares for a caller with no locale in hand.
|
|
2845
|
+
|
|
2846
|
+
⚠️ **Dated note, 2026-09-25 — that engine (`ValidationEngine`) is removed from
|
|
2847
|
+
`@object-ui/core` by a later change; `ValidationContext.locale` stays declared in
|
|
2848
|
+
`@object-ui/types`, and at that change nothing in `@object-ui/core` reads it —
|
|
2849
|
+
objectui#7659.**
|
|
2850
|
+
|
|
2851
|
+
⚠️ **Dated note, 2026-09-27 — `ValidationContext` itself is removed from
|
|
2852
|
+
`@object-ui/types` by a later change, with the rest of the Phase 3.5
|
|
2853
|
+
validation types it belonged to, so the `locale` member this entry adds no
|
|
2854
|
+
longer exists — objectui#10719.**
|
|
2855
|
+
- `@object-ui/plugin-report`: `exportReport`, `exportAsHTML` and `exportAsPDF`
|
|
2856
|
+
take a trailing optional `locale` for the exported file's "Generated:" time,
|
|
2857
|
+
and `LiveExportOptions` gains an optional `locale` that `exportWithLiveData`
|
|
2858
|
+
forwards. Omitted, the time uses the display channel's own last resort
|
|
2859
|
+
(`'en'`), never the machine's locale. `ReportViewer` passes the session's
|
|
2860
|
+
display locale. The locale is deliberately NOT a `ReportExportConfig` member:
|
|
2861
|
+
that type is authored report metadata, and a display locale belongs to the
|
|
2862
|
+
session.
|
|
2863
|
+
|
|
2864
|
+
**One census, repository-wide.** `plugin-detail`'s machine-locale census pin
|
|
2865
|
+
(objectui#9786) is now a single test, `machineLocaleCensus-9909.test.ts` in
|
|
2866
|
+
`@object-ui/i18n`, covering every workspace package whose manifest depends on
|
|
2867
|
+
`@object-ui/i18n`. It refuses any call site that passes nothing, `undefined`,
|
|
2868
|
+
the `'default'` pseudo-tag (a subtag no locale data answers, so `Intl` resolves
|
|
2869
|
+
it to the machine's locale) or a hard-coded tag, unless the site is declared
|
|
2870
|
+
with its reason — for example the `catch` fallback for a tag `Intl` itself
|
|
2871
|
+
rejected, or an ISO formatter feeding `<input type="date">`. Each declared site
|
|
2872
|
+
is counted exactly, so deleting one without its entry is refused too. The
|
|
2873
|
+
per-package pin it replaces is deleted. The runtime tripwire that observes the
|
|
2874
|
+
argument each locale-taking call (`Intl` constructors, `Date` and `Number`
|
|
2875
|
+
`toLocale*`) actually receives moved into the private `@object-ui/test-support`
|
|
2876
|
+
package, and every surface above is pinned with it: the same data under two
|
|
2877
|
+
declared locales must render differently, NO locale-taking call may receive the
|
|
2878
|
+
machine's locale (nothing, `undefined` or `'default'`), and the surface's own
|
|
2879
|
+
calls must receive the declared tag. A call carrying another declared tag, such
|
|
2880
|
+
as the `'en'` of the session's UI language, is tolerated by design: only the
|
|
2881
|
+
machine's locale is the defect.
|
|
2882
|
+
- 9f7e846: Refuse a row cap the contract already refuses before it reaches `$top`, at the
|
|
2883
|
+
LAST read point in the repo that still forwarded one — `record:line_items`
|
|
2884
|
+
(objectui#9925).
|
|
2885
|
+
|
|
2886
|
+
This panel spelled its row cap as a bare `schema.limit ?? DEFAULT_LINE_ITEMS_LIMIT`.
|
|
2887
|
+
`??` rejects only `null` and `undefined`, so a value the contract refuses was not
|
|
2888
|
+
nullish and survived as a real fetch window: it reached the adapter as `$top: 0`,
|
|
2889
|
+
the panel asked the server for nothing, and the empty line-items grid named no
|
|
2890
|
+
cause. A negative went out the same way, and a non-integer became a fractional
|
|
2891
|
+
window.
|
|
2892
|
+
|
|
2893
|
+
The read now goes through one resolver, mirroring the shape objectui#9853 landed
|
|
2894
|
+
on `ObjectGrid`, objectui#9897 repeated on `ListView`, and objectui#9925 landed on
|
|
2895
|
+
`object-kanban`, `object-timeline` and `record:reference_rail` — one resolver at
|
|
2896
|
+
every entry is what keeps the answer single. A refused value is dropped, this
|
|
2897
|
+
panel's own default is used, and one `console.warn` names the block, the child
|
|
2898
|
+
object and the value. The warning is conditional and deduped: an absent `limit`
|
|
2899
|
+
and a usable one both stay silent, and one declaration warns once rather than
|
|
2900
|
+
once per render.
|
|
2901
|
+
|
|
2902
|
+
This closes BOTH entrances into the panel, which is why the repair is at the read
|
|
2903
|
+
point. The panel reads one key, and two authoring shapes fill it: a `dataSource`
|
|
2904
|
+
binding lowers a named view's `pagination.pageSize` into `schema.limit`, and a
|
|
2905
|
+
panel with no binding at all carries the authored `limit` straight through. A
|
|
2906
|
+
repair at the lowering layer would close only the first.
|
|
2907
|
+
|
|
2908
|
+
Refusing this is not a renderer choosing a meaning. `@objectstack/spec` declares
|
|
2909
|
+
the element data source `limit` a binding lowers into this key a positive integer
|
|
2910
|
+
(`z.number().int().positive().optional()`), and so is the `pagination.pageSize`
|
|
2911
|
+
of a named view that fills it.
|
|
2912
|
+
|
|
2913
|
+
⛔ No fallback literal changed: the panel keeps the `500` it already documented.
|
|
2914
|
+
⛔ No shared helper was extracted — a shared home would be `@object-ui/core`,
|
|
2915
|
+
which is objectui#9928's package and out of this card's face.
|
|
2916
|
+
- fb336df: Move `lucide-react` from `^1.31.0` to `^1.43.0` in every package that declares it, and
|
|
2917
|
+
repair what the jump breaks, so icons resolved from a STRING keep drawing a glyph.
|
|
2918
|
+
|
|
2919
|
+
Measured once against the installed 1.43.0 artifact when this change was made; nothing in
|
|
2920
|
+
the repository re-derives these readings. Across the jump lucide removes no runtime export,
|
|
2921
|
+
no public type name and no `lucide-react/dynamic.mjs` name, and every name this repository
|
|
2922
|
+
imports from `lucide-react` resolves. Exactly one key leaves the runtime `icons` record:
|
|
2923
|
+
`Trash2`, retired in favour of `trash`.
|
|
2924
|
+
|
|
2925
|
+
What a user sees change:
|
|
2926
|
+
|
|
2927
|
+
- Icons the lazy icon seam draws (`resolveIcon`, objectui#9251) get their path data again.
|
|
2928
|
+
lucide 1.43.0 icon modules export their path data inside `__iconData` and no longer
|
|
2929
|
+
export `__iconNode`; the seam now reads both. Reading only `__iconNode` against 1.43.0
|
|
2930
|
+
leaves every such glyph an empty box, with nothing thrown or logged.
|
|
2931
|
+
- `DetailView`'s delete action and three schema-catalog examples spell their icon
|
|
2932
|
+
`trash`, not `trash-2`, so they keep resolving. The glyph is unchanged: 1.43.0's `trash`
|
|
2933
|
+
path data is byte-identical to the `trash-2` path data of 1.31.0 and 1.35.0. Anything
|
|
2934
|
+
that already authored `trash` now draws that same artwork, because lucide moved it under
|
|
2935
|
+
the `trash` name; a few other glyphs were also redrawn upstream.
|
|
2936
|
+
- Icons imported as COMPONENTS carry lucide 1.43.0's own classes: one class per declared
|
|
2937
|
+
alias (a spinner renders `class="lucide lucide-loader-circle lucide-loader-2 ..."`), and
|
|
2938
|
+
no longer the class lucide used to derive from the PascalCase key where that differs
|
|
2939
|
+
(`ArrowDown01` no longer carries `lucide-arrow-down01`). The canonical
|
|
2940
|
+
`lucide-<icon name>` class is still there, so a `.lucide-loader-circle` selector still
|
|
2941
|
+
matches.
|
|
2942
|
+
- Icons resolved from a STRING through the seam keep the classes they had: `lucide`, the
|
|
2943
|
+
canonical `lucide-<icon name>` class and the key-derived class. They do not carry
|
|
2944
|
+
lucide's per-alias classes. That is a maintainer ruling (objectui#8941, option C): the
|
|
2945
|
+
alias list lives only inside each lazily loaded icon module, and it was not moved into
|
|
2946
|
+
the eager name list. A selector that targets an alias class matches a component-imported
|
|
2947
|
+
icon and not a string-resolved one. This also dates the example in the objectui#9251
|
|
2948
|
+
entry: `trash-2` no longer resolves as a string, so no seam-drawn glyph carries
|
|
2949
|
+
`lucide-trash2 lucide-trash-2`; a digit-bearing name that still resolves, such as
|
|
2950
|
+
`arrow-down-0-1`, carries `lucide-arrow-down01 lucide-arrow-down-0-1`.
|
|
2951
|
+
- 425762e: `object-master-detail-form` declares `formType` as a closed vocabulary instead of a bare `string`.
|
|
2952
|
+
|
|
2953
|
+
The block declared `formType` as `type: 'string'` while the sibling `object-form` declared the
|
|
2954
|
+
same key as an `enum`, and both funnel into the renderer that switches on those variant names. A
|
|
2955
|
+
value outside the vocabulary therefore matched no branch and fell through to the flat field list
|
|
2956
|
+
with no diagnostic — measured, a `formType` of `'wizzard'` renders the parent half with its
|
|
2957
|
+
authored sections silently gone.
|
|
2958
|
+
|
|
2959
|
+
The declared set is `simple | tabbed`, measured against the master-detail composition rather than
|
|
2960
|
+
copied from the sibling's six: `drawer` and `modal` host the parent half in a portal dialog outside
|
|
2961
|
+
the master-detail container, so its single bottom Save bar has no form to submit; `wizard` mounts
|
|
2962
|
+
only the current step's fields and turns that Save bar into a `Next`; `split` renders inline but
|
|
2963
|
+
persists through `dataSource.create` instead of the atomic batch.
|
|
2964
|
+
|
|
2965
|
+
Authoring-surface only. The manifest, the JSX-page compiler and the save gate now report an
|
|
2966
|
+
out-of-vocabulary value as `invalid-enum`; rejection at publish time remains `@objectstack/spec`'s.
|
|
2967
|
+
- 584eeca: The record dialog now draws a `form.sections[].group` section (objectui#11542).
|
|
2968
|
+
|
|
2969
|
+
A form view section can declare its members by pointing `group` at one of the
|
|
2970
|
+
object's `fieldGroups` (the reference form of objectstack#13855). `ObjectForm`
|
|
2971
|
+
resolved that form, but `ModalForm` did not, and the console's More actions ›
|
|
2972
|
+
Edit / New dialog and action-opened modals mount `ModalForm` directly with the
|
|
2973
|
+
form view's sections as authored. A `{ group }` section therefore reached the
|
|
2974
|
+
dialog with no fields and was dropped: a tabbed form view showed no tab for the
|
|
2975
|
+
group, a stacked one showed no header, and the fields only that group carries
|
|
2976
|
+
could not be edited in the dialog.
|
|
2977
|
+
|
|
2978
|
+
`ModalForm` now resolves its sections through `resolveSectionGroupReferences`,
|
|
2979
|
+
the same resolver `ObjectForm` uses, against the object schema it already
|
|
2980
|
+
loads. The group's section is drawn with the group's label and members in both
|
|
2981
|
+
content layouts, its members pass the same field-level security gate as
|
|
2982
|
+
enumerated fields, and an unknown group renders nothing and is reported once,
|
|
2983
|
+
as it is on `ObjectForm`. A section list that uses no `group` reaches the
|
|
2984
|
+
dialog unchanged.
|
|
2985
|
+
- 83ec618: `README.md`'s "Not a `FormField` key" table said a field-level `className` is
|
|
2986
|
+
"read on exactly one pseudo-field, `type: 'section-divider'`". That quantifier
|
|
2987
|
+
holds only for the renderer's *explicit* read — `className={fp.className}` on
|
|
2988
|
+
the `section-divider` branch of
|
|
2989
|
+
`packages/components/src/renderers/form/form.tsx`. The same renderer forwards
|
|
2990
|
+
every key it did not destructure, and `className` is not among the names taken
|
|
2991
|
+
off the field config, not among the ones `stripRendererOnlyProps` removes, and
|
|
2992
|
+
so rides `{...fieldProps}` into `renderFieldComponent`, whose built-in `input`
|
|
2993
|
+
branch spreads it onto `<Input>`. A field-level `className` therefore lands
|
|
2994
|
+
visibly on ordinary built-in controls, and a reader taking "exactly one"
|
|
2995
|
+
literally concludes the opposite of what the code does (objectui#5131).
|
|
2996
|
+
|
|
2997
|
+
The cell now describes the contract rather than the reader count: an undeclared
|
|
2998
|
+
key still rides the props spread down to whichever component the field resolves
|
|
2999
|
+
to, nothing in the contract promises that, and a registered widget honours it
|
|
3000
|
+
only if it happens to spread its leftover props — the wording the docs site
|
|
3001
|
+
already ships, so the two sources agree again. The advice in the row is
|
|
3002
|
+
unchanged and was never wrong (`span` / `colSpan` for width,
|
|
3003
|
+
`FormSchema.fieldContainerClass` for the grid), and the explicit
|
|
3004
|
+
`section-divider` read is kept, now named as explicit.
|
|
3005
|
+
|
|
3006
|
+
This is a documentation fix to a file `plugin-form` publishes to npm, which is
|
|
3007
|
+
why it carries a version: the npm landing page only picks up the correction on a
|
|
3008
|
+
release. No behaviour, export, type, or `dist` byte changes.
|
|
3009
|
+
- 4d963a2: fix(plugin-form): `object-form`'s default layout hands a section's `description` to the divider it already draws
|
|
3010
|
+
|
|
3011
|
+
An `object-form` that declares `sections` and no `formType` renders through
|
|
3012
|
+
`SimpleObjectForm`'s grouped branch, which rebuilds each section key by key into
|
|
3013
|
+
a virtual `section-divider` row. That rebuild copied `label`, the ADR-0089
|
|
3014
|
+
`visibleWhen`, the objectui#6236 membership claim and the collapse pair — and
|
|
3015
|
+
not `description`. The key was therefore dropped on the layout an author reaches
|
|
3016
|
+
by default, while `SectionDivider` (the very component the row renders as) has
|
|
3017
|
+
always drawn a blurb and the `tabbed` / `wizard` / `split` / `modal` rebuilds all
|
|
3018
|
+
copied one. Its sibling `label` on the same member arrived, so a titled section
|
|
3019
|
+
with a blurb rendered the title and silently ate the blurb.
|
|
3020
|
+
|
|
3021
|
+
Measured on this branch, arm by arm, through the real renderer: `tabbed`,
|
|
3022
|
+
`wizard`, `split` and `modal` render it; the default layout and `drawer` did
|
|
3023
|
+
not. Only the default layout is changed here — the `drawer` miss is a separate
|
|
3024
|
+
defect and is handed back as a finding rather than fixed under this card.
|
|
3025
|
+
|
|
3026
|
+
The boundary that did NOT move: the divider row exists only for a member that
|
|
3027
|
+
yields a heading (a `name` or a `label`), so a member carrying a `description`
|
|
3028
|
+
and neither of those still draws no divider and still drops its blurb. That gate
|
|
3029
|
+
also decides the section predicate and the membership claim, so widening it is a
|
|
3030
|
+
ruling about other keys, not this one.
|
|
3031
|
+
|
|
3032
|
+
`objectFormSectionMembers-8071`'s sixth row pinned the drop as behaviour; it is
|
|
3033
|
+
rewritten onto the new behaviour in this same change, plus a row pinning the
|
|
3034
|
+
boundary above. `registry-inputs-spec-parity`'s member-pin ledger entry for
|
|
3035
|
+
`object-form.sections` described the old row in prose and is corrected with it —
|
|
3036
|
+
no published behaviour of `@object-ui/console` changes.
|
|
3037
|
+
- 43ca9d5: `SimpleObjectForm`: consult a declared `submitHandler` before the inline-fields carve-out
|
|
3038
|
+
|
|
3039
|
+
`ObjectFormSchema.submitHandler` is documented as handing the collected values to the host INSTEAD of calling `dataSource.create` / `dataSource.update`, so a form that declares it has a submit target with or without an adapter. `SimpleObjectForm.handleSubmit` nevertheless opened with the inline-fields carve-out (`hasInlineFields && !dataSource`), which returned before the persistence chain: a host that had declared it owns the write was never asked, and `onSuccess` confirmed a write that never happened (measured `onSuccess 1 / submitHandler 0`).
|
|
3040
|
+
|
|
3041
|
+
The carve-out now fires only when no `submitHandler` is declared, and the "no submit target" refusal moved into the persistence chain after the seam — the shape the five variant renderers already use, reusing their shared refusal from `submitTarget.ts` rather than a private copy. A form with inline fields and no seam is unchanged: its `onSuccess` is still the write.
|
|
3042
|
+
- bd09957: fix(form): a whole-row field now spans the whole row at **every** breakpoint tier, not only the widest
|
|
3043
|
+
|
|
3044
|
+
The form's column count is resolved per tier by container queries
|
|
3045
|
+
(`grid-cols-1 @md:grid-cols-2 @2xl:grid-cols-3` is three column counts on one
|
|
3046
|
+
screen), but the renderer emitted a single col-span class for the widest tier
|
|
3047
|
+
that reached the target. A field authored `span: 'full'` — the spelling
|
|
3048
|
+
`@objectstack/spec` declares as «whole row at any column count» and tells
|
|
3049
|
+
authors to prefer — therefore took 1 of 2 cells at the middle tier, rendering
|
|
3050
|
+
pixel-identical to authoring nothing at all (measured in Chromium at 285px of
|
|
3051
|
+
a 586px grid). It now emits one class per multi-column tier, each clamped to
|
|
3052
|
+
that tier's column count: `@md:col-span-2 @2xl:col-span-3`.
|
|
3053
|
+
|
|
3054
|
+
⚠️ **Existing forms will look different at intermediate container widths** —
|
|
3055
|
+
that is the fix. A field that was silently narrow in a modal or drawer now
|
|
3056
|
+
occupies the full row there, as its metadata always asked. Narrowest and
|
|
3057
|
+
widest tiers are unchanged, and a `colSpan` smaller than the grid is
|
|
3058
|
+
unchanged. Layouts hand-tuned around the old behaviour should be re-checked at
|
|
3059
|
+
modal width.
|
|
3060
|
+
- ba306e3: Honour the declared `submitHandler` seam in every form variant, not just the simple one.
|
|
3061
|
+
|
|
3062
|
+
`ObjectFormSchema.submitHandler` is documented as the seam a host uses to own persistence: the form validates and hands the collected values over instead of calling `dataSource.create` / `dataSource.update`. `ObjectForm` forwarded the key into every variant it routes to, but only `SimpleObjectForm` read it — `TabbedForm`, `WizardForm`, `SplitForm`, `DrawerForm` and `ModalForm` persisted directly.
|
|
3063
|
+
|
|
3064
|
+
**Behaviour change on a persistence path.** A master-detail parent half rendered `tabbed` (or `split`) now commits through the atomic `batchTransaction` together with its child collections, instead of writing the parent independently through `dataSource.create`. Previously the child leg was never attempted on those layouts: the parent was committed alone, the entered line items were silently discarded, no compensation ran, and a success toast confirmed the save. A failing child leg now leaves no committed parent, on every layout that renders the parent half inline.
|
|
3065
|
+
|
|
3066
|
+
`WizardForm` additionally skips its own default success toast / redirect arms when a `submitHandler` is present, matching `ObjectForm`, so a host that owns the write also owns the outcome.
|
|
3067
|
+
|
|
3068
|
+
The `object-master-detail-form.formType` vocabulary is unchanged and stays `simple | tabbed`.
|
|
3069
|
+
- 26a2238: `navigateOnSuccess` now honours a mounted host, and says so when its destination is refused
|
|
3070
|
+
|
|
3071
|
+
`ObjectForm` and `WizardForm` consume `navigateOnSuccess` through
|
|
3072
|
+
`resolveSuccessNavigate`, and both arms travelled to an accepted destination with a bare
|
|
3073
|
+
`window.location.assign`. A rooted path such as `/apps/x/o/record/{id}` assigned that way
|
|
3074
|
+
resolves against the ORIGIN root, so under a host mounted at a sub-path (the framework CLI
|
|
3075
|
+
configures one for every embedded deployment) an authored in-app destination left the
|
|
3076
|
+
application. Both arms now route an app-relative destination through the injected
|
|
3077
|
+
navigation seam both components already held for `submitBehavior.url`, so a mounted host's
|
|
3078
|
+
basename is applied. With no host seam the behaviour is byte-for-byte what it was — a host
|
|
3079
|
+
with no router has no basename, so origin-rooted resolution is already correct there. A
|
|
3080
|
+
same-origin ABSOLUTE destination also keeps browser-level navigation: the seam's declared
|
|
3081
|
+
input is an application-relative path, and an author who spelled out a whole address asked
|
|
3082
|
+
for that address.
|
|
3083
|
+
|
|
3084
|
+
A declared `navigateOnSuccess` whose destination is refused — a mistyped value, or a written
|
|
3085
|
+
record carrying no usable id — used to produce a success toast identical to the one a form
|
|
3086
|
+
with no `navigateOnSuccess` produces, so the navigation failed with nobody told. That toast
|
|
3087
|
+
now carries a note that the declared navigation did not happen, and the template the author
|
|
3088
|
+
wrote is logged for them. The write genuinely succeeded, so this stays a success rather than
|
|
3089
|
+
becoming an error state.
|
|
3090
|
+
|
|
3091
|
+
Which destinations are ACCEPTED is unchanged: the same-origin guard, the `{id}` /
|
|
3092
|
+
`{recordId}` dialect and the unescaped interpolation are the subject of an open contract
|
|
3093
|
+
question and are deliberately untouched here.
|
|
3094
|
+
- 5d79faf: Variant forms refuse a submit that has nowhere to go, instead of reporting success
|
|
3095
|
+
|
|
3096
|
+
`TabbedForm`, `WizardForm`, `SplitForm`, `DrawerForm` and `ModalForm` each opened
|
|
3097
|
+
`handleSubmit` with `if (!dataSource) { await schema.onSuccess?.(data); return data; }`
|
|
3098
|
+
— a success signal emitted without consulting a declared `submitHandler` and without
|
|
3099
|
+
persisting anything. Through `MasterDetailForm`, whose parent schema declares both
|
|
3100
|
+
`submitHandler: submitViaBatch` and `onSuccess: handleSaved`, that produced a success
|
|
3101
|
+
toast and, in create mode, a form reset clearing values nobody wrote.
|
|
3102
|
+
|
|
3103
|
+
All five now answer the question the same way `SimpleObjectForm` and the `object-form`
|
|
3104
|
+
element gate already do. A form has a submit target when it has a `dataSource` or a
|
|
3105
|
+
declared `submitHandler`; with neither, the one legitimate shape is inline fields —
|
|
3106
|
+
a non-empty `customFields`, or `sections` whose fields are all inline runtime
|
|
3107
|
+
`FormField` objects — whose `onSuccess` is the write. Anything else throws
|
|
3108
|
+
`DataSource is required for form submission (inline mode not configured)`, which
|
|
3109
|
+
reaches `schema.onError` and is rethrown. A declared `submitHandler` is consulted
|
|
3110
|
+
first, so a host that owns the write is never bypassed for want of an adapter it
|
|
3111
|
+
never needed.
|
|
3112
|
+
- Updated dependencies [7b10bef]
|
|
3113
|
+
- Updated dependencies [97abedc]
|
|
3114
|
+
- Updated dependencies [b46c58f]
|
|
3115
|
+
- Updated dependencies [a507334]
|
|
3116
|
+
- Updated dependencies [ad694ac]
|
|
3117
|
+
- Updated dependencies [6f96fca]
|
|
3118
|
+
- Updated dependencies [afb2284]
|
|
3119
|
+
- Updated dependencies [3ac2de8]
|
|
3120
|
+
- Updated dependencies [0aacecc]
|
|
3121
|
+
- Updated dependencies [63f4f92]
|
|
3122
|
+
- Updated dependencies [777fca2]
|
|
3123
|
+
- Updated dependencies [c131d9e]
|
|
3124
|
+
- Updated dependencies [5f00ff4]
|
|
3125
|
+
- Updated dependencies [c9e073a]
|
|
3126
|
+
- Updated dependencies [0361d6b]
|
|
3127
|
+
- Updated dependencies [7b395d8]
|
|
3128
|
+
- Updated dependencies [0879812]
|
|
3129
|
+
- Updated dependencies [8cedb0d]
|
|
3130
|
+
- Updated dependencies [162621b]
|
|
3131
|
+
- Updated dependencies [4c6f549]
|
|
3132
|
+
- Updated dependencies [80c5412]
|
|
3133
|
+
- Updated dependencies [6cc910b]
|
|
3134
|
+
- Updated dependencies [061f5e8]
|
|
3135
|
+
- Updated dependencies [2dd4d3f]
|
|
3136
|
+
- Updated dependencies [e686f4d]
|
|
3137
|
+
- Updated dependencies [212c451]
|
|
3138
|
+
- Updated dependencies [a04b06d]
|
|
3139
|
+
- Updated dependencies [4ab4f1b]
|
|
3140
|
+
- Updated dependencies [b57107d]
|
|
3141
|
+
- Updated dependencies [e3ea4f9]
|
|
3142
|
+
- Updated dependencies [65f1e8d]
|
|
3143
|
+
- Updated dependencies [1f8ef0a]
|
|
3144
|
+
- Updated dependencies [bb5d4ee]
|
|
3145
|
+
- Updated dependencies [dc666f7]
|
|
3146
|
+
- Updated dependencies [3335767]
|
|
3147
|
+
- Updated dependencies [8b1f066]
|
|
3148
|
+
- Updated dependencies [af243c1]
|
|
3149
|
+
- Updated dependencies [cff4b77]
|
|
3150
|
+
- Updated dependencies [31938f0]
|
|
3151
|
+
- Updated dependencies [961ceaa]
|
|
3152
|
+
- Updated dependencies [f3f4e4c]
|
|
3153
|
+
- Updated dependencies [a05c350]
|
|
3154
|
+
- Updated dependencies [1dbb993]
|
|
3155
|
+
- Updated dependencies [2b5f509]
|
|
3156
|
+
- Updated dependencies [808f339]
|
|
3157
|
+
- Updated dependencies [6cf5999]
|
|
3158
|
+
- Updated dependencies [274e14a]
|
|
3159
|
+
- Updated dependencies [8c10f4f]
|
|
3160
|
+
- Updated dependencies [90dac98]
|
|
3161
|
+
- Updated dependencies [6096f20]
|
|
3162
|
+
- Updated dependencies [544aca2]
|
|
3163
|
+
- Updated dependencies [ea02938]
|
|
3164
|
+
- Updated dependencies [a14fb23]
|
|
3165
|
+
- Updated dependencies [ae98f1d]
|
|
3166
|
+
- Updated dependencies [f98eddf]
|
|
3167
|
+
- Updated dependencies [ce6bd99]
|
|
3168
|
+
- Updated dependencies [a5b08c9]
|
|
3169
|
+
- Updated dependencies [86982ac]
|
|
3170
|
+
- Updated dependencies [8c929e6]
|
|
3171
|
+
- Updated dependencies [cdefa2a]
|
|
3172
|
+
- Updated dependencies [2fc2a24]
|
|
3173
|
+
- Updated dependencies [0961d5e]
|
|
3174
|
+
- Updated dependencies [9d25b9b]
|
|
3175
|
+
- Updated dependencies [6276478]
|
|
3176
|
+
- Updated dependencies [5e67837]
|
|
3177
|
+
- Updated dependencies [ff14e29]
|
|
3178
|
+
- Updated dependencies [9b28151]
|
|
3179
|
+
- Updated dependencies [64563a9]
|
|
3180
|
+
- Updated dependencies [1a5003f]
|
|
3181
|
+
- Updated dependencies [09ab32b]
|
|
3182
|
+
- Updated dependencies [8acc51b]
|
|
3183
|
+
- Updated dependencies [ea9d17f]
|
|
3184
|
+
- Updated dependencies [45362a3]
|
|
3185
|
+
- Updated dependencies [2a943bf]
|
|
3186
|
+
- Updated dependencies [111fa4c]
|
|
3187
|
+
- Updated dependencies [93a689d]
|
|
3188
|
+
- Updated dependencies [4357a27]
|
|
3189
|
+
- Updated dependencies [e5f4343]
|
|
3190
|
+
- Updated dependencies [3261e64]
|
|
3191
|
+
- Updated dependencies [f5178a2]
|
|
3192
|
+
- Updated dependencies [2ad3671]
|
|
3193
|
+
- Updated dependencies [39bf246]
|
|
3194
|
+
- Updated dependencies [8740e86]
|
|
3195
|
+
- Updated dependencies [d22b37b]
|
|
3196
|
+
- Updated dependencies [8c0e550]
|
|
3197
|
+
- Updated dependencies [fde4caf]
|
|
3198
|
+
- Updated dependencies [caf0ed0]
|
|
3199
|
+
- Updated dependencies [1daf477]
|
|
3200
|
+
- Updated dependencies [3d614ea]
|
|
3201
|
+
- Updated dependencies [a7df45f]
|
|
3202
|
+
- Updated dependencies [6c2f3c5]
|
|
3203
|
+
- Updated dependencies [fb13e85]
|
|
3204
|
+
- Updated dependencies [c2d8659]
|
|
3205
|
+
- Updated dependencies [6516320]
|
|
3206
|
+
- Updated dependencies [98b1a7c]
|
|
3207
|
+
- Updated dependencies [e0f8202]
|
|
3208
|
+
- Updated dependencies [ac2d6f1]
|
|
3209
|
+
- Updated dependencies [9fbbb17]
|
|
3210
|
+
- Updated dependencies [c3a26cc]
|
|
3211
|
+
- Updated dependencies [a66e58e]
|
|
3212
|
+
- Updated dependencies [d89492c]
|
|
3213
|
+
- Updated dependencies [9a5f998]
|
|
3214
|
+
- Updated dependencies [1c5ee33]
|
|
3215
|
+
- Updated dependencies [9327397]
|
|
3216
|
+
- Updated dependencies [17cc3a3]
|
|
3217
|
+
- Updated dependencies [a9c5ea0]
|
|
3218
|
+
- Updated dependencies [02e6d36]
|
|
3219
|
+
- Updated dependencies [4758b33]
|
|
3220
|
+
- Updated dependencies [4758b33]
|
|
3221
|
+
- Updated dependencies [4758b33]
|
|
3222
|
+
- Updated dependencies [f905090]
|
|
3223
|
+
- Updated dependencies [3cd6c5e]
|
|
3224
|
+
- Updated dependencies [5bb855d]
|
|
3225
|
+
- Updated dependencies [9c78ebe]
|
|
3226
|
+
- Updated dependencies [12809a5]
|
|
3227
|
+
- Updated dependencies [9c78ebe]
|
|
3228
|
+
- Updated dependencies [12809a5]
|
|
3229
|
+
- Updated dependencies [7afc81d]
|
|
3230
|
+
- Updated dependencies [f9c06ef]
|
|
3231
|
+
- Updated dependencies [5ad3b88]
|
|
3232
|
+
- Updated dependencies [f9d772b]
|
|
3233
|
+
- Updated dependencies [bf14b64]
|
|
3234
|
+
- Updated dependencies [4345558]
|
|
3235
|
+
- Updated dependencies [97b6c21]
|
|
3236
|
+
- Updated dependencies [26ca2ad]
|
|
3237
|
+
- Updated dependencies [41ae65b]
|
|
3238
|
+
- Updated dependencies [baac95a]
|
|
3239
|
+
- Updated dependencies [13220af]
|
|
3240
|
+
- Updated dependencies [29b45f6]
|
|
3241
|
+
- Updated dependencies [39b8d51]
|
|
3242
|
+
- Updated dependencies [17b323e]
|
|
3243
|
+
- Updated dependencies [b956e69]
|
|
3244
|
+
- Updated dependencies [b32e7de]
|
|
3245
|
+
- Updated dependencies [ff94a12]
|
|
3246
|
+
- Updated dependencies [33e58d8]
|
|
3247
|
+
- Updated dependencies [256b4c9]
|
|
3248
|
+
- Updated dependencies [97672ba]
|
|
3249
|
+
- Updated dependencies [c5ec15c]
|
|
3250
|
+
- Updated dependencies [fec3b1a]
|
|
3251
|
+
- Updated dependencies [b8e0941]
|
|
3252
|
+
- Updated dependencies [0c50f18]
|
|
3253
|
+
- Updated dependencies [1dae95a]
|
|
3254
|
+
- Updated dependencies [e32dae1]
|
|
3255
|
+
- Updated dependencies [d0c0c7f]
|
|
3256
|
+
- Updated dependencies [30b11ad]
|
|
3257
|
+
- Updated dependencies [4aebea0]
|
|
3258
|
+
- Updated dependencies [f976774]
|
|
3259
|
+
- Updated dependencies [3d6badf]
|
|
3260
|
+
- Updated dependencies [25cb364]
|
|
3261
|
+
- Updated dependencies [a60539b]
|
|
3262
|
+
- Updated dependencies [de1b879]
|
|
3263
|
+
- Updated dependencies [c6678b1]
|
|
3264
|
+
- Updated dependencies [0638322]
|
|
3265
|
+
- Updated dependencies [e3782d2]
|
|
3266
|
+
- Updated dependencies [db0beb2]
|
|
3267
|
+
- Updated dependencies [997ce38]
|
|
3268
|
+
- Updated dependencies [ae0b9d3]
|
|
3269
|
+
- Updated dependencies [ad1785c]
|
|
3270
|
+
- Updated dependencies [3b469c8]
|
|
3271
|
+
- Updated dependencies [990a2d6]
|
|
3272
|
+
- Updated dependencies [990a2d6]
|
|
3273
|
+
- Updated dependencies [1c84036]
|
|
3274
|
+
- Updated dependencies [1c84036]
|
|
3275
|
+
- Updated dependencies [6650259]
|
|
3276
|
+
- Updated dependencies [4f8b7f8]
|
|
3277
|
+
- Updated dependencies [9e6619f]
|
|
3278
|
+
- Updated dependencies [f6ae5e2]
|
|
3279
|
+
- Updated dependencies [eb97ce6]
|
|
3280
|
+
- Updated dependencies [7343376]
|
|
3281
|
+
- Updated dependencies [b2683a2]
|
|
3282
|
+
- Updated dependencies [dded788]
|
|
3283
|
+
- Updated dependencies [b45d463]
|
|
3284
|
+
- Updated dependencies [54a7830]
|
|
3285
|
+
- Updated dependencies [f3135a4]
|
|
3286
|
+
- Updated dependencies [b5696d3]
|
|
3287
|
+
- Updated dependencies [3f9d926]
|
|
3288
|
+
- Updated dependencies [e978ed5]
|
|
3289
|
+
- Updated dependencies [6a7f24e]
|
|
3290
|
+
- Updated dependencies [b3c96d6]
|
|
3291
|
+
- Updated dependencies [8d0ca91]
|
|
3292
|
+
- Updated dependencies [c30c8dd]
|
|
3293
|
+
- Updated dependencies [244d516]
|
|
3294
|
+
- Updated dependencies [deca847]
|
|
3295
|
+
- Updated dependencies [ac15833]
|
|
3296
|
+
- Updated dependencies [ac15833]
|
|
3297
|
+
- Updated dependencies [e2dffc9]
|
|
3298
|
+
- Updated dependencies [328abeb]
|
|
3299
|
+
- Updated dependencies [dd0d78f]
|
|
3300
|
+
- Updated dependencies [24d3e65]
|
|
3301
|
+
- Updated dependencies [95a7c8d]
|
|
3302
|
+
- Updated dependencies [e227156]
|
|
3303
|
+
- Updated dependencies [cc4e476]
|
|
3304
|
+
- Updated dependencies [92970c4]
|
|
3305
|
+
- Updated dependencies [d570eaa]
|
|
3306
|
+
- Updated dependencies [4b742f4]
|
|
3307
|
+
- Updated dependencies [42687ba]
|
|
3308
|
+
- Updated dependencies [6cd8f66]
|
|
3309
|
+
- Updated dependencies [24a0f14]
|
|
3310
|
+
- Updated dependencies [797a30f]
|
|
3311
|
+
- Updated dependencies [b4075c0]
|
|
3312
|
+
- Updated dependencies [9b85600]
|
|
3313
|
+
- Updated dependencies [e327c89]
|
|
3314
|
+
- Updated dependencies [2eaf5be]
|
|
3315
|
+
- Updated dependencies [bf7ab35]
|
|
3316
|
+
- Updated dependencies [99878d8]
|
|
3317
|
+
- Updated dependencies [3c13675]
|
|
3318
|
+
- Updated dependencies [63ab761]
|
|
3319
|
+
- Updated dependencies [0eb9f36]
|
|
3320
|
+
- Updated dependencies [ae582b7]
|
|
3321
|
+
- Updated dependencies [51c2949]
|
|
3322
|
+
- Updated dependencies [a4b017e]
|
|
3323
|
+
- Updated dependencies [8732846]
|
|
3324
|
+
- Updated dependencies [559a2e2]
|
|
3325
|
+
- Updated dependencies [db11afd]
|
|
3326
|
+
- Updated dependencies [154075a]
|
|
3327
|
+
- Updated dependencies [582edef]
|
|
3328
|
+
- Updated dependencies [19f484f]
|
|
3329
|
+
- Updated dependencies [0a78a20]
|
|
3330
|
+
- Updated dependencies [615346d]
|
|
3331
|
+
- Updated dependencies [75dcc81]
|
|
3332
|
+
- Updated dependencies [55a12a8]
|
|
3333
|
+
- Updated dependencies [edfcf5a]
|
|
3334
|
+
- Updated dependencies [0a3e540]
|
|
3335
|
+
- Updated dependencies [c021b35]
|
|
3336
|
+
- Updated dependencies [f61dab1]
|
|
3337
|
+
- Updated dependencies [b0a05dd]
|
|
3338
|
+
- Updated dependencies [dd5ff19]
|
|
3339
|
+
- Updated dependencies [54997ff]
|
|
3340
|
+
- Updated dependencies [81f8498]
|
|
3341
|
+
- Updated dependencies [a782fa7]
|
|
3342
|
+
- Updated dependencies [1d6a23d]
|
|
3343
|
+
- Updated dependencies [0645133]
|
|
3344
|
+
- Updated dependencies [76e9df0]
|
|
3345
|
+
- Updated dependencies [0ecaa7d]
|
|
3346
|
+
- Updated dependencies [b24f93a]
|
|
3347
|
+
- Updated dependencies [6158e4c]
|
|
3348
|
+
- Updated dependencies [cff8641]
|
|
3349
|
+
- Updated dependencies [c27b575]
|
|
3350
|
+
- Updated dependencies [84b275c]
|
|
3351
|
+
- Updated dependencies [0e6e76b]
|
|
3352
|
+
- Updated dependencies [bf43afa]
|
|
3353
|
+
- Updated dependencies [385ebc5]
|
|
3354
|
+
- Updated dependencies [3a0e7ab]
|
|
3355
|
+
- Updated dependencies [858eafb]
|
|
3356
|
+
- Updated dependencies [a8c5509]
|
|
3357
|
+
- Updated dependencies [c2a8d23]
|
|
3358
|
+
- Updated dependencies [0ffc423]
|
|
3359
|
+
- Updated dependencies [3cc4fe5]
|
|
3360
|
+
- Updated dependencies [cd5b19a]
|
|
3361
|
+
- Updated dependencies [cd5b19a]
|
|
3362
|
+
- Updated dependencies [17dc167]
|
|
3363
|
+
- Updated dependencies [20d23be]
|
|
3364
|
+
- Updated dependencies [20d23be]
|
|
3365
|
+
- Updated dependencies [2e3da72]
|
|
3366
|
+
- Updated dependencies [1263e40]
|
|
3367
|
+
- Updated dependencies [e6bc087]
|
|
3368
|
+
- Updated dependencies [9419df1]
|
|
3369
|
+
- Updated dependencies [8bab157]
|
|
3370
|
+
- Updated dependencies [a7557a7]
|
|
3371
|
+
- Updated dependencies [7d074ba]
|
|
3372
|
+
- Updated dependencies [6158e4c]
|
|
3373
|
+
- Updated dependencies [6158e4c]
|
|
3374
|
+
- Updated dependencies [52aad5c]
|
|
3375
|
+
- Updated dependencies [18d1a0a]
|
|
3376
|
+
- Updated dependencies [7fed09d]
|
|
3377
|
+
- Updated dependencies [c1763e5]
|
|
3378
|
+
- Updated dependencies [58da8ae]
|
|
3379
|
+
- Updated dependencies [a8b9889]
|
|
3380
|
+
- Updated dependencies [138ad45]
|
|
3381
|
+
- Updated dependencies [138ad45]
|
|
3382
|
+
- Updated dependencies [5262f7d]
|
|
3383
|
+
- Updated dependencies [6aa029b]
|
|
3384
|
+
- Updated dependencies [2124d04]
|
|
3385
|
+
- Updated dependencies [be52115]
|
|
3386
|
+
- Updated dependencies [770cc5b]
|
|
3387
|
+
- Updated dependencies [1a88ce2]
|
|
3388
|
+
- Updated dependencies [a1a44d6]
|
|
3389
|
+
- Updated dependencies [e0a9c67]
|
|
3390
|
+
- Updated dependencies [5638529]
|
|
3391
|
+
- Updated dependencies [5638529]
|
|
3392
|
+
- Updated dependencies [d0fba91]
|
|
3393
|
+
- Updated dependencies [c476be0]
|
|
3394
|
+
- Updated dependencies [c82ff39]
|
|
3395
|
+
- Updated dependencies [6c3da53]
|
|
3396
|
+
- Updated dependencies [31987bd]
|
|
3397
|
+
- Updated dependencies [3c3ce15]
|
|
3398
|
+
- Updated dependencies [063119f]
|
|
3399
|
+
- Updated dependencies [e100589]
|
|
3400
|
+
- Updated dependencies [304f611]
|
|
3401
|
+
- Updated dependencies [e46ee77]
|
|
3402
|
+
- Updated dependencies [f9c8c4e]
|
|
3403
|
+
- Updated dependencies [6e9c8d2]
|
|
3404
|
+
- Updated dependencies [9547063]
|
|
3405
|
+
- Updated dependencies [3f6efd6]
|
|
3406
|
+
- Updated dependencies [52c95a1]
|
|
3407
|
+
- Updated dependencies [55d18c6]
|
|
3408
|
+
- Updated dependencies [c4ab6d0]
|
|
3409
|
+
- Updated dependencies [0e9058b]
|
|
3410
|
+
- Updated dependencies [c4ab6d0]
|
|
3411
|
+
- Updated dependencies [5988b6b]
|
|
3412
|
+
- Updated dependencies [6158e4c]
|
|
3413
|
+
- Updated dependencies [00ccdf7]
|
|
3414
|
+
- Updated dependencies [9d9ed54]
|
|
3415
|
+
- Updated dependencies [6007dd4]
|
|
3416
|
+
- Updated dependencies [4a1adb7]
|
|
3417
|
+
- Updated dependencies [4a1adb7]
|
|
3418
|
+
- Updated dependencies [4a1adb7]
|
|
3419
|
+
- Updated dependencies [185b7a0]
|
|
3420
|
+
- Updated dependencies [50c73fe]
|
|
3421
|
+
- Updated dependencies [ca3de72]
|
|
3422
|
+
- Updated dependencies [83e3f83]
|
|
3423
|
+
- Updated dependencies [401611b]
|
|
3424
|
+
- Updated dependencies [2c0ddf2]
|
|
3425
|
+
- Updated dependencies [4abc0aa]
|
|
3426
|
+
- Updated dependencies [f560ded]
|
|
3427
|
+
- Updated dependencies [2b188fa]
|
|
3428
|
+
- Updated dependencies [b654d4e]
|
|
3429
|
+
- Updated dependencies [f68e0a0]
|
|
3430
|
+
- Updated dependencies [aea682a]
|
|
3431
|
+
- Updated dependencies [fcdc8ec]
|
|
3432
|
+
- Updated dependencies [2d576e4]
|
|
3433
|
+
- Updated dependencies [8366acc]
|
|
3434
|
+
- Updated dependencies [95e58a3]
|
|
3435
|
+
- Updated dependencies [9d7419b]
|
|
3436
|
+
- Updated dependencies [fc7db05]
|
|
3437
|
+
- Updated dependencies [9ed8d0f]
|
|
3438
|
+
- Updated dependencies [c73cdb5]
|
|
3439
|
+
- Updated dependencies [6f5719e]
|
|
3440
|
+
- Updated dependencies [d0097af]
|
|
3441
|
+
- Updated dependencies [432882b]
|
|
3442
|
+
- Updated dependencies [64dae8e]
|
|
3443
|
+
- Updated dependencies [b06e374]
|
|
3444
|
+
- Updated dependencies [06a8af5]
|
|
3445
|
+
- Updated dependencies [6a91586]
|
|
3446
|
+
- Updated dependencies [a04d7c6]
|
|
3447
|
+
- Updated dependencies [5ccc500]
|
|
3448
|
+
- Updated dependencies [f3c2bb0]
|
|
3449
|
+
- Updated dependencies [978507b]
|
|
3450
|
+
- Updated dependencies [778138e]
|
|
3451
|
+
- Updated dependencies [9801765]
|
|
3452
|
+
- Updated dependencies [9cebfca]
|
|
3453
|
+
- Updated dependencies [460575f]
|
|
3454
|
+
- Updated dependencies [d796c8d]
|
|
3455
|
+
- Updated dependencies [594704f]
|
|
3456
|
+
- Updated dependencies [d3995fe]
|
|
3457
|
+
- Updated dependencies [1b1d772]
|
|
3458
|
+
- Updated dependencies [d88e20f]
|
|
3459
|
+
- Updated dependencies [2d7304d]
|
|
3460
|
+
- Updated dependencies [636b236]
|
|
3461
|
+
- Updated dependencies [4172589]
|
|
3462
|
+
- Updated dependencies [d6d8fb9]
|
|
3463
|
+
- Updated dependencies [64d624d]
|
|
3464
|
+
- Updated dependencies [053fdc8]
|
|
3465
|
+
- Updated dependencies [41b7ce3]
|
|
3466
|
+
- Updated dependencies [ae476b8]
|
|
3467
|
+
- Updated dependencies [39f4309]
|
|
3468
|
+
- Updated dependencies [95bad12]
|
|
3469
|
+
- Updated dependencies [d2fb6ef]
|
|
3470
|
+
- Updated dependencies [7cd3987]
|
|
3471
|
+
- Updated dependencies [ee3b878]
|
|
3472
|
+
- Updated dependencies [e304a4e]
|
|
3473
|
+
- Updated dependencies [fda49e5]
|
|
3474
|
+
- Updated dependencies [490d9a9]
|
|
3475
|
+
- Updated dependencies [fc62bb4]
|
|
3476
|
+
- Updated dependencies [41df893]
|
|
3477
|
+
- Updated dependencies [0cba1b7]
|
|
3478
|
+
- Updated dependencies [00f3eb5]
|
|
3479
|
+
- Updated dependencies [1ec291c]
|
|
3480
|
+
- Updated dependencies [453dbaa]
|
|
3481
|
+
- Updated dependencies [95f8704]
|
|
3482
|
+
- Updated dependencies [f8cdbf2]
|
|
3483
|
+
- Updated dependencies [69a2163]
|
|
3484
|
+
- Updated dependencies [24e027e]
|
|
3485
|
+
- Updated dependencies [2c3cd1b]
|
|
3486
|
+
- Updated dependencies [e176053]
|
|
3487
|
+
- Updated dependencies [e30ed15]
|
|
3488
|
+
- Updated dependencies [90665e0]
|
|
3489
|
+
- Updated dependencies [8d3a529]
|
|
3490
|
+
- Updated dependencies [5ac2e2c]
|
|
3491
|
+
- Updated dependencies [194fae1]
|
|
3492
|
+
- Updated dependencies [7e19d03]
|
|
3493
|
+
- Updated dependencies [b08b7eb]
|
|
3494
|
+
- Updated dependencies [1e946c9]
|
|
3495
|
+
- Updated dependencies [546ddf7]
|
|
3496
|
+
- Updated dependencies [864154e]
|
|
3497
|
+
- Updated dependencies [b023625]
|
|
3498
|
+
- Updated dependencies [75bd83d]
|
|
3499
|
+
- Updated dependencies [44d075b]
|
|
3500
|
+
- Updated dependencies [40c479a]
|
|
3501
|
+
- Updated dependencies [971d387]
|
|
3502
|
+
- Updated dependencies [ee851c3]
|
|
3503
|
+
- Updated dependencies [6414dfd]
|
|
3504
|
+
- Updated dependencies [a8d5c71]
|
|
3505
|
+
- Updated dependencies [905b21f]
|
|
3506
|
+
- Updated dependencies [88e9109]
|
|
3507
|
+
- Updated dependencies [2c45966]
|
|
3508
|
+
- Updated dependencies [db3a600]
|
|
3509
|
+
- Updated dependencies [3a3db76]
|
|
3510
|
+
- Updated dependencies [0d723a3]
|
|
3511
|
+
- Updated dependencies [0c95d3d]
|
|
3512
|
+
- Updated dependencies [3e4fa2c]
|
|
3513
|
+
- Updated dependencies [b5b928a]
|
|
3514
|
+
- Updated dependencies [6fd2cf7]
|
|
3515
|
+
- Updated dependencies [5fa06c4]
|
|
3516
|
+
- Updated dependencies [52a43de]
|
|
3517
|
+
- Updated dependencies [195052f]
|
|
3518
|
+
- Updated dependencies [e4559d1]
|
|
3519
|
+
- Updated dependencies [2c71482]
|
|
3520
|
+
- Updated dependencies [129bcc5]
|
|
3521
|
+
- Updated dependencies [a26b9e4]
|
|
3522
|
+
- Updated dependencies [5ef9c4f]
|
|
3523
|
+
- Updated dependencies [46f0bb4]
|
|
3524
|
+
- Updated dependencies [06b82b8]
|
|
3525
|
+
- Updated dependencies [8ec11e1]
|
|
3526
|
+
- Updated dependencies [6f81384]
|
|
3527
|
+
- Updated dependencies [22ba927]
|
|
3528
|
+
- Updated dependencies [8631c32]
|
|
3529
|
+
- Updated dependencies [f8c70f4]
|
|
3530
|
+
- Updated dependencies [5d3a2d1]
|
|
3531
|
+
- Updated dependencies [8f1d995]
|
|
3532
|
+
- Updated dependencies [b362c1b]
|
|
3533
|
+
- Updated dependencies [f9c34df]
|
|
3534
|
+
- Updated dependencies [dddb942]
|
|
3535
|
+
- Updated dependencies [00c665e]
|
|
3536
|
+
- Updated dependencies [29754cf]
|
|
3537
|
+
- Updated dependencies [d7de534]
|
|
3538
|
+
- Updated dependencies [3c2b6f7]
|
|
3539
|
+
- Updated dependencies [6e88630]
|
|
3540
|
+
- Updated dependencies [b84dc18]
|
|
3541
|
+
- Updated dependencies [ac8abb0]
|
|
3542
|
+
- Updated dependencies [9d86e1d]
|
|
3543
|
+
- Updated dependencies [3a5817f]
|
|
3544
|
+
- Updated dependencies [99a3c2d]
|
|
3545
|
+
- Updated dependencies [5961030]
|
|
3546
|
+
- Updated dependencies [f24de8b]
|
|
3547
|
+
- Updated dependencies [c8ea8af]
|
|
3548
|
+
- Updated dependencies [9602dc8]
|
|
3549
|
+
- Updated dependencies [3190414]
|
|
3550
|
+
- Updated dependencies [4e480f5]
|
|
3551
|
+
- Updated dependencies [38a123c]
|
|
3552
|
+
- Updated dependencies [299102e]
|
|
3553
|
+
- Updated dependencies [30c73cd]
|
|
3554
|
+
- Updated dependencies [830ed58]
|
|
3555
|
+
- Updated dependencies [d7acad6]
|
|
3556
|
+
- Updated dependencies [45a9aeb]
|
|
3557
|
+
- Updated dependencies [713db46]
|
|
3558
|
+
- Updated dependencies [c71e14d]
|
|
3559
|
+
- Updated dependencies [bf3a03c]
|
|
3560
|
+
- Updated dependencies [cb55718]
|
|
3561
|
+
- Updated dependencies [748494b]
|
|
3562
|
+
- Updated dependencies [5967be0]
|
|
3563
|
+
- Updated dependencies [831be72]
|
|
3564
|
+
- Updated dependencies [29cb85b]
|
|
3565
|
+
- Updated dependencies [3e028c8]
|
|
3566
|
+
- Updated dependencies [d0889e2]
|
|
3567
|
+
- Updated dependencies [ce503e5]
|
|
3568
|
+
- Updated dependencies [f20dcf0]
|
|
3569
|
+
- Updated dependencies [12402a9]
|
|
3570
|
+
- Updated dependencies [aff3d7a]
|
|
3571
|
+
- Updated dependencies [4ca30d0]
|
|
3572
|
+
- Updated dependencies [7a5da14]
|
|
3573
|
+
- Updated dependencies [fff9645]
|
|
3574
|
+
- Updated dependencies [9c3b7ce]
|
|
3575
|
+
- Updated dependencies [2c1c967]
|
|
3576
|
+
- Updated dependencies [9486ac6]
|
|
3577
|
+
- Updated dependencies [9486ac6]
|
|
3578
|
+
- Updated dependencies [4d5f9b4]
|
|
3579
|
+
- Updated dependencies [d6ceb8d]
|
|
3580
|
+
- Updated dependencies [dc4365c]
|
|
3581
|
+
- Updated dependencies [e321d52]
|
|
3582
|
+
- Updated dependencies [969ba84]
|
|
3583
|
+
- Updated dependencies [98188c2]
|
|
3584
|
+
- Updated dependencies [4c68077]
|
|
3585
|
+
- Updated dependencies [7977ff9]
|
|
3586
|
+
- Updated dependencies [3beef6d]
|
|
3587
|
+
- Updated dependencies [06b8c42]
|
|
3588
|
+
- Updated dependencies [46b9bc9]
|
|
3589
|
+
- Updated dependencies [f46bd39]
|
|
3590
|
+
- Updated dependencies [b98352a]
|
|
3591
|
+
- Updated dependencies [b76ca67]
|
|
3592
|
+
- Updated dependencies [45ac2cb]
|
|
3593
|
+
- Updated dependencies [b97790a]
|
|
3594
|
+
- Updated dependencies [dbd5194]
|
|
3595
|
+
- Updated dependencies [7c9b044]
|
|
3596
|
+
- Updated dependencies [e552c31]
|
|
3597
|
+
- Updated dependencies [d47de51]
|
|
3598
|
+
- Updated dependencies [3fe6463]
|
|
3599
|
+
- Updated dependencies [b392674]
|
|
3600
|
+
- Updated dependencies [4f3a1e2]
|
|
3601
|
+
- Updated dependencies [31ab372]
|
|
3602
|
+
- Updated dependencies [846889b]
|
|
3603
|
+
- Updated dependencies [2acd8e1]
|
|
3604
|
+
- Updated dependencies [7b90231]
|
|
3605
|
+
- Updated dependencies [26896c6]
|
|
3606
|
+
- Updated dependencies [67fc3b0]
|
|
3607
|
+
- Updated dependencies [8579e34]
|
|
3608
|
+
- Updated dependencies [d57db5d]
|
|
3609
|
+
- Updated dependencies [33a3b3c]
|
|
3610
|
+
- Updated dependencies [b87f15b]
|
|
3611
|
+
- Updated dependencies [045d20b]
|
|
3612
|
+
- Updated dependencies [a2d2515]
|
|
3613
|
+
- Updated dependencies [c18d099]
|
|
3614
|
+
- Updated dependencies [0caacca]
|
|
3615
|
+
- Updated dependencies [adb2a86]
|
|
3616
|
+
- Updated dependencies [03380aa]
|
|
3617
|
+
- Updated dependencies [4562ea5]
|
|
3618
|
+
- Updated dependencies [3619792]
|
|
3619
|
+
- Updated dependencies [3561bd2]
|
|
3620
|
+
- Updated dependencies [bf97b98]
|
|
3621
|
+
- Updated dependencies [320374d]
|
|
3622
|
+
- Updated dependencies [b0d308d]
|
|
3623
|
+
- Updated dependencies [b458300]
|
|
3624
|
+
- Updated dependencies [40f34b4]
|
|
3625
|
+
- Updated dependencies [bd0376d]
|
|
3626
|
+
- Updated dependencies [8063bcb]
|
|
3627
|
+
- Updated dependencies [b74a859]
|
|
3628
|
+
- Updated dependencies [d4493fd]
|
|
3629
|
+
- Updated dependencies [240b80f]
|
|
3630
|
+
- Updated dependencies [77cb489]
|
|
3631
|
+
- Updated dependencies [bfaa158]
|
|
3632
|
+
- Updated dependencies [777e5c6]
|
|
3633
|
+
- Updated dependencies [0c386dd]
|
|
3634
|
+
- Updated dependencies [39d69ad]
|
|
3635
|
+
- Updated dependencies [9e37d9b]
|
|
3636
|
+
- Updated dependencies [5ad86dd]
|
|
3637
|
+
- Updated dependencies [16a725f]
|
|
3638
|
+
- Updated dependencies [4dfdcc3]
|
|
3639
|
+
- Updated dependencies [6a449fc]
|
|
3640
|
+
- Updated dependencies [446d93d]
|
|
3641
|
+
- Updated dependencies [ecd9cb2]
|
|
3642
|
+
- Updated dependencies [f08bcd9]
|
|
3643
|
+
- Updated dependencies [98d4108]
|
|
3644
|
+
- Updated dependencies [0e3b3be]
|
|
3645
|
+
- Updated dependencies [a29ae2d]
|
|
3646
|
+
- Updated dependencies [220c18d]
|
|
3647
|
+
- Updated dependencies [00d3f09]
|
|
3648
|
+
- Updated dependencies [4388f71]
|
|
3649
|
+
- Updated dependencies [0b1ac58]
|
|
3650
|
+
- Updated dependencies [c93b4d5]
|
|
3651
|
+
- Updated dependencies [c1fe272]
|
|
3652
|
+
- Updated dependencies [3cab570]
|
|
3653
|
+
- Updated dependencies [8ad218d]
|
|
3654
|
+
- Updated dependencies [3e41187]
|
|
3655
|
+
- Updated dependencies [5f78953]
|
|
3656
|
+
- Updated dependencies [639114c]
|
|
3657
|
+
- Updated dependencies [639114c]
|
|
3658
|
+
- Updated dependencies [1490691]
|
|
3659
|
+
- Updated dependencies [e8e4c4d]
|
|
3660
|
+
- Updated dependencies [1f31d3a]
|
|
3661
|
+
- Updated dependencies [d1842ab]
|
|
3662
|
+
- Updated dependencies [78ca238]
|
|
3663
|
+
- Updated dependencies [d8ec8d6]
|
|
3664
|
+
- Updated dependencies [351eb31]
|
|
3665
|
+
- Updated dependencies [866cd1d]
|
|
3666
|
+
- Updated dependencies [20c04b2]
|
|
3667
|
+
- Updated dependencies [01c9023]
|
|
3668
|
+
- Updated dependencies [48c19bd]
|
|
3669
|
+
- Updated dependencies [a6d8b8d]
|
|
3670
|
+
- Updated dependencies [4b5bb95]
|
|
3671
|
+
- Updated dependencies [b652514]
|
|
3672
|
+
- Updated dependencies [adbda1b]
|
|
3673
|
+
- Updated dependencies [adbda1b]
|
|
3674
|
+
- Updated dependencies [8952395]
|
|
3675
|
+
- Updated dependencies [e2b3826]
|
|
3676
|
+
- Updated dependencies [0348bc9]
|
|
3677
|
+
- Updated dependencies [e8c553b]
|
|
3678
|
+
- Updated dependencies [2e32ed4]
|
|
3679
|
+
- Updated dependencies [554e647]
|
|
3680
|
+
- Updated dependencies [3ed3eec]
|
|
3681
|
+
- Updated dependencies [7c3df8f]
|
|
3682
|
+
- Updated dependencies [a4514e8]
|
|
3683
|
+
- Updated dependencies [db3896c]
|
|
3684
|
+
- Updated dependencies [b9f5ff1]
|
|
3685
|
+
- Updated dependencies [e75f4c9]
|
|
3686
|
+
- Updated dependencies [19f1639]
|
|
3687
|
+
- Updated dependencies [4704aa4]
|
|
3688
|
+
- Updated dependencies [47547d0]
|
|
3689
|
+
- Updated dependencies [1bee5d0]
|
|
3690
|
+
- Updated dependencies [858cd72]
|
|
3691
|
+
- Updated dependencies [cfc9b6d]
|
|
3692
|
+
- Updated dependencies [554f2b6]
|
|
3693
|
+
- Updated dependencies [72f55c9]
|
|
3694
|
+
- Updated dependencies [26e06d7]
|
|
3695
|
+
- Updated dependencies [669d71b]
|
|
3696
|
+
- Updated dependencies [ed27d7c]
|
|
3697
|
+
- Updated dependencies [52c8cf7]
|
|
3698
|
+
- Updated dependencies [2ceb43a]
|
|
3699
|
+
- Updated dependencies [7cdd2b9]
|
|
3700
|
+
- Updated dependencies [52c8cf7]
|
|
3701
|
+
- Updated dependencies [3399704]
|
|
3702
|
+
- Updated dependencies [71a4a53]
|
|
3703
|
+
- Updated dependencies [7bf244b]
|
|
3704
|
+
- Updated dependencies [f0bb9fa]
|
|
3705
|
+
- Updated dependencies [81a2eb1]
|
|
3706
|
+
- Updated dependencies [caa0cd3]
|
|
3707
|
+
- Updated dependencies [caa0cd3]
|
|
3708
|
+
- Updated dependencies [20cb8db]
|
|
3709
|
+
- Updated dependencies [25c7d58]
|
|
3710
|
+
- Updated dependencies [00d2fa6]
|
|
3711
|
+
- Updated dependencies [77b2a18]
|
|
3712
|
+
- Updated dependencies [c6198c2]
|
|
3713
|
+
- Updated dependencies [721d1e0]
|
|
3714
|
+
- Updated dependencies [c2f0f48]
|
|
3715
|
+
- Updated dependencies [1237ae4]
|
|
3716
|
+
- Updated dependencies [2f61238]
|
|
3717
|
+
- Updated dependencies [51eb515]
|
|
3718
|
+
- Updated dependencies [c354ce5]
|
|
3719
|
+
- Updated dependencies [8fe8e5c]
|
|
3720
|
+
- Updated dependencies [feac439]
|
|
3721
|
+
- Updated dependencies [9ae871d]
|
|
3722
|
+
- Updated dependencies [efbd566]
|
|
3723
|
+
- Updated dependencies [2a5bf45]
|
|
3724
|
+
- Updated dependencies [9587fc9]
|
|
3725
|
+
- Updated dependencies [e62c44e]
|
|
3726
|
+
- Updated dependencies [daf9d57]
|
|
3727
|
+
- Updated dependencies [23b9958]
|
|
3728
|
+
- Updated dependencies [c15d7ec]
|
|
3729
|
+
- Updated dependencies [5d0876c]
|
|
3730
|
+
- Updated dependencies [f7ace0a]
|
|
3731
|
+
- Updated dependencies [b041b9c]
|
|
3732
|
+
- Updated dependencies [ce2aaef]
|
|
3733
|
+
- Updated dependencies [544ecba]
|
|
3734
|
+
- Updated dependencies [2ce2612]
|
|
3735
|
+
- Updated dependencies [bc640ec]
|
|
3736
|
+
- Updated dependencies [1e215c4]
|
|
3737
|
+
- Updated dependencies [da6e191]
|
|
3738
|
+
- Updated dependencies [3e377c9]
|
|
3739
|
+
- Updated dependencies [a3eb5d0]
|
|
3740
|
+
- Updated dependencies [4ce14f1]
|
|
3741
|
+
- Updated dependencies [aef97e5]
|
|
3742
|
+
- Updated dependencies [2af1fa7]
|
|
3743
|
+
- Updated dependencies [c14d3a0]
|
|
3744
|
+
- Updated dependencies [a137d0c]
|
|
3745
|
+
- Updated dependencies [caf477f]
|
|
3746
|
+
- Updated dependencies [f6375da]
|
|
3747
|
+
- Updated dependencies [967e5d8]
|
|
3748
|
+
- Updated dependencies [c907a9c]
|
|
3749
|
+
- Updated dependencies [a4611b3]
|
|
3750
|
+
- Updated dependencies [20316ba]
|
|
3751
|
+
- Updated dependencies [d3499b3]
|
|
3752
|
+
- Updated dependencies [91f9276]
|
|
3753
|
+
- Updated dependencies [309c75e]
|
|
3754
|
+
- Updated dependencies [c9f9bae]
|
|
3755
|
+
- Updated dependencies [18897a4]
|
|
3756
|
+
- Updated dependencies [8b7ea39]
|
|
3757
|
+
- Updated dependencies [a915064]
|
|
3758
|
+
- Updated dependencies [dcbf0b2]
|
|
3759
|
+
- Updated dependencies [52cac38]
|
|
3760
|
+
- Updated dependencies [93fea2e]
|
|
3761
|
+
- Updated dependencies [1422a92]
|
|
3762
|
+
- Updated dependencies [d05fe17]
|
|
3763
|
+
- Updated dependencies [a480f79]
|
|
3764
|
+
- Updated dependencies [f08d1a8]
|
|
3765
|
+
- Updated dependencies [64a252d]
|
|
3766
|
+
- Updated dependencies [786bc91]
|
|
3767
|
+
- Updated dependencies [75fca96]
|
|
3768
|
+
- Updated dependencies [7ca6ddd]
|
|
3769
|
+
- Updated dependencies [f1cd290]
|
|
3770
|
+
- Updated dependencies [5a41ce7]
|
|
3771
|
+
- Updated dependencies [8d50bc2]
|
|
3772
|
+
- Updated dependencies [604476d]
|
|
3773
|
+
- Updated dependencies [d1bebb0]
|
|
3774
|
+
- Updated dependencies [95bf128]
|
|
3775
|
+
- Updated dependencies [335abea]
|
|
3776
|
+
- Updated dependencies [edea22a]
|
|
3777
|
+
- Updated dependencies [0f5cadf]
|
|
3778
|
+
- Updated dependencies [4f9f1ee]
|
|
3779
|
+
- Updated dependencies [66e8b2a]
|
|
3780
|
+
- Updated dependencies [aa083cd]
|
|
3781
|
+
- Updated dependencies [12b5992]
|
|
3782
|
+
- Updated dependencies [b93e245]
|
|
3783
|
+
- Updated dependencies [c842594]
|
|
3784
|
+
- Updated dependencies [290de37]
|
|
3785
|
+
- Updated dependencies [e1c27e4]
|
|
3786
|
+
- Updated dependencies [8c8da45]
|
|
3787
|
+
- Updated dependencies [8cd8eb5]
|
|
3788
|
+
- Updated dependencies [f52a9d7]
|
|
3789
|
+
- Updated dependencies [cf1d29e]
|
|
3790
|
+
- Updated dependencies [1bd79c8]
|
|
3791
|
+
- Updated dependencies [af9e957]
|
|
3792
|
+
- Updated dependencies [b1030c7]
|
|
3793
|
+
- Updated dependencies [c974edf]
|
|
3794
|
+
- Updated dependencies [ad852b6]
|
|
3795
|
+
- Updated dependencies [6778809]
|
|
3796
|
+
- Updated dependencies [3a3dd28]
|
|
3797
|
+
- Updated dependencies [7fb22a1]
|
|
3798
|
+
- Updated dependencies [ad66d79]
|
|
3799
|
+
- Updated dependencies [0758bd8]
|
|
3800
|
+
- Updated dependencies [ee4d19f]
|
|
3801
|
+
- Updated dependencies [496d31d]
|
|
3802
|
+
- Updated dependencies [7ed9808]
|
|
3803
|
+
- Updated dependencies [0ea7054]
|
|
3804
|
+
- Updated dependencies [ac0e39a]
|
|
3805
|
+
- Updated dependencies [9a853f2]
|
|
3806
|
+
- Updated dependencies [cb847fd]
|
|
3807
|
+
- Updated dependencies [ee70287]
|
|
3808
|
+
- Updated dependencies [3e98e13]
|
|
3809
|
+
- Updated dependencies [a695f50]
|
|
3810
|
+
- Updated dependencies [fc32921]
|
|
3811
|
+
- Updated dependencies [4eaa835]
|
|
3812
|
+
- Updated dependencies [8f9d87a]
|
|
3813
|
+
- Updated dependencies [b1777ae]
|
|
3814
|
+
- Updated dependencies [24845c4]
|
|
3815
|
+
- Updated dependencies [6f864cf]
|
|
3816
|
+
- Updated dependencies [24d1edd]
|
|
3817
|
+
- Updated dependencies [645087c]
|
|
3818
|
+
- Updated dependencies [33f4a19]
|
|
3819
|
+
- Updated dependencies [6e9a3d4]
|
|
3820
|
+
- Updated dependencies [4a292d2]
|
|
3821
|
+
- Updated dependencies [5323168]
|
|
3822
|
+
- Updated dependencies [841dd2b]
|
|
3823
|
+
- Updated dependencies [3014fc0]
|
|
3824
|
+
- Updated dependencies [dacb402]
|
|
3825
|
+
- Updated dependencies [846cec0]
|
|
3826
|
+
- Updated dependencies [91facae]
|
|
3827
|
+
- Updated dependencies [b38014e]
|
|
3828
|
+
- Updated dependencies [474797d]
|
|
3829
|
+
- Updated dependencies [704e695]
|
|
3830
|
+
- Updated dependencies [a407bd6]
|
|
3831
|
+
- Updated dependencies [317dbce]
|
|
3832
|
+
- Updated dependencies [309728c]
|
|
3833
|
+
- Updated dependencies [c03d03b]
|
|
3834
|
+
- Updated dependencies [aa08d7e]
|
|
3835
|
+
- Updated dependencies [3a43a15]
|
|
3836
|
+
- Updated dependencies [868e825]
|
|
3837
|
+
- Updated dependencies [f76f436]
|
|
3838
|
+
- Updated dependencies [ce45a03]
|
|
3839
|
+
- Updated dependencies [421544b]
|
|
3840
|
+
- Updated dependencies [fb01022]
|
|
3841
|
+
- Updated dependencies [e9d9212]
|
|
3842
|
+
- Updated dependencies [ecfb693]
|
|
3843
|
+
- Updated dependencies [639ca9d]
|
|
3844
|
+
- Updated dependencies [2152962]
|
|
3845
|
+
- Updated dependencies [abc1b18]
|
|
3846
|
+
- Updated dependencies [81a51db]
|
|
3847
|
+
- Updated dependencies [67749c7]
|
|
3848
|
+
- Updated dependencies [507b61b]
|
|
3849
|
+
- Updated dependencies [512c84b]
|
|
3850
|
+
- Updated dependencies [c300267]
|
|
3851
|
+
- Updated dependencies [fb3a101]
|
|
3852
|
+
- Updated dependencies [d4733f2]
|
|
3853
|
+
- Updated dependencies [7c9145f]
|
|
3854
|
+
- Updated dependencies [1570eac]
|
|
3855
|
+
- Updated dependencies [f391ede]
|
|
3856
|
+
- Updated dependencies [f5cfbbd]
|
|
3857
|
+
- Updated dependencies [f5cfbbd]
|
|
3858
|
+
- Updated dependencies [8b532cb]
|
|
3859
|
+
- Updated dependencies [64c3cdd]
|
|
3860
|
+
- Updated dependencies [4d65991]
|
|
3861
|
+
- Updated dependencies [c42554e]
|
|
3862
|
+
- Updated dependencies [3e71b26]
|
|
3863
|
+
- Updated dependencies [b89583b]
|
|
3864
|
+
- Updated dependencies [70c4523]
|
|
3865
|
+
- Updated dependencies [555b4ec]
|
|
3866
|
+
- Updated dependencies [64f1cf1]
|
|
3867
|
+
- Updated dependencies [da5e4f6]
|
|
3868
|
+
- Updated dependencies [1ccfc23]
|
|
3869
|
+
- Updated dependencies [542718f]
|
|
3870
|
+
- Updated dependencies [7f27bc5]
|
|
3871
|
+
- Updated dependencies [0a174f3]
|
|
3872
|
+
- Updated dependencies [676f677]
|
|
3873
|
+
- Updated dependencies [f95b140]
|
|
3874
|
+
- Updated dependencies [541ce4e]
|
|
3875
|
+
- Updated dependencies [6479086]
|
|
3876
|
+
- Updated dependencies [d79f525]
|
|
3877
|
+
- Updated dependencies [d1865d2]
|
|
3878
|
+
- Updated dependencies [88a629d]
|
|
3879
|
+
- Updated dependencies [cdd2542]
|
|
3880
|
+
- Updated dependencies [ab77513]
|
|
3881
|
+
- Updated dependencies [55ba3ff]
|
|
3882
|
+
- Updated dependencies [3ecc369]
|
|
3883
|
+
- Updated dependencies [f1190b0]
|
|
3884
|
+
- Updated dependencies [561abef]
|
|
3885
|
+
- Updated dependencies [ef52001]
|
|
3886
|
+
- Updated dependencies [6a4680b]
|
|
3887
|
+
- Updated dependencies [c3a4273]
|
|
3888
|
+
- Updated dependencies [abf710d]
|
|
3889
|
+
- Updated dependencies [093af32]
|
|
3890
|
+
- Updated dependencies [1bd1be7]
|
|
3891
|
+
- Updated dependencies [d234fa9]
|
|
3892
|
+
- Updated dependencies [adf5812]
|
|
3893
|
+
- Updated dependencies [0eaed83]
|
|
3894
|
+
- Updated dependencies [e36acd4]
|
|
3895
|
+
- Updated dependencies [5058336]
|
|
3896
|
+
- Updated dependencies [2f6b2bf]
|
|
3897
|
+
- Updated dependencies [2028b31]
|
|
3898
|
+
- Updated dependencies [63601ab]
|
|
3899
|
+
- Updated dependencies [c372b29]
|
|
3900
|
+
- Updated dependencies [152f0a7]
|
|
3901
|
+
- Updated dependencies [8693b85]
|
|
3902
|
+
- Updated dependencies [e82dad1]
|
|
3903
|
+
- Updated dependencies [58b7b3d]
|
|
3904
|
+
- Updated dependencies [84defab]
|
|
3905
|
+
- Updated dependencies [681d3f1]
|
|
3906
|
+
- Updated dependencies [969d4f2]
|
|
3907
|
+
- Updated dependencies [f3bc481]
|
|
3908
|
+
- Updated dependencies [b79aac2]
|
|
3909
|
+
- Updated dependencies [93fc0e7]
|
|
3910
|
+
- Updated dependencies [a4b723f]
|
|
3911
|
+
- Updated dependencies [2b10ca0]
|
|
3912
|
+
- Updated dependencies [7db4a81]
|
|
3913
|
+
- Updated dependencies [19a0b0e]
|
|
3914
|
+
- Updated dependencies [526fc11]
|
|
3915
|
+
- Updated dependencies [f8e3e9a]
|
|
3916
|
+
- Updated dependencies [b9d47ec]
|
|
3917
|
+
- Updated dependencies [3b6bc69]
|
|
3918
|
+
- Updated dependencies [6214db6]
|
|
3919
|
+
- Updated dependencies [6732df4]
|
|
3920
|
+
- Updated dependencies [fe9e0d0]
|
|
3921
|
+
- Updated dependencies [63fb72c]
|
|
3922
|
+
- Updated dependencies [804831c]
|
|
3923
|
+
- Updated dependencies [279e48e]
|
|
3924
|
+
- Updated dependencies [8700d6d]
|
|
3925
|
+
- Updated dependencies [8db2a0f]
|
|
3926
|
+
- Updated dependencies [689953a]
|
|
3927
|
+
- Updated dependencies [30443fb]
|
|
3928
|
+
- Updated dependencies [8d3dbb2]
|
|
3929
|
+
- Updated dependencies [efc1c9c]
|
|
3930
|
+
- Updated dependencies [da45e6b]
|
|
3931
|
+
- Updated dependencies [7533465]
|
|
3932
|
+
- Updated dependencies [835f0f3]
|
|
3933
|
+
- Updated dependencies [6d5db7b]
|
|
3934
|
+
- Updated dependencies [a9d97be]
|
|
3935
|
+
- Updated dependencies [ed35b44]
|
|
3936
|
+
- Updated dependencies [9ba7e9c]
|
|
3937
|
+
- Updated dependencies [729e851]
|
|
3938
|
+
- Updated dependencies [96919a4]
|
|
3939
|
+
- Updated dependencies [345e24a]
|
|
3940
|
+
- Updated dependencies [20b507a]
|
|
3941
|
+
- Updated dependencies [2e471dc]
|
|
3942
|
+
- Updated dependencies [6748587]
|
|
3943
|
+
- Updated dependencies [be50942]
|
|
3944
|
+
- Updated dependencies [775e079]
|
|
3945
|
+
- Updated dependencies [7e8b3c0]
|
|
3946
|
+
- Updated dependencies [53374dc]
|
|
3947
|
+
- Updated dependencies [f6fb83f]
|
|
3948
|
+
- Updated dependencies [2049b03]
|
|
3949
|
+
- Updated dependencies [2bf34f7]
|
|
3950
|
+
- Updated dependencies [15b33ae]
|
|
3951
|
+
- Updated dependencies [7cbc724]
|
|
3952
|
+
- Updated dependencies [4a94c38]
|
|
3953
|
+
- Updated dependencies [7098eed]
|
|
3954
|
+
- Updated dependencies [3df7c5c]
|
|
3955
|
+
- Updated dependencies [fb91ac9]
|
|
3956
|
+
- Updated dependencies [fb91ac9]
|
|
3957
|
+
- Updated dependencies [641fb55]
|
|
3958
|
+
- Updated dependencies [8524372]
|
|
3959
|
+
- Updated dependencies [7cbefa5]
|
|
3960
|
+
- Updated dependencies [72d6587]
|
|
3961
|
+
- Updated dependencies [a272a4f]
|
|
3962
|
+
- Updated dependencies [55f39ee]
|
|
3963
|
+
- Updated dependencies [0ce32d5]
|
|
3964
|
+
- Updated dependencies [0970a0e]
|
|
3965
|
+
- Updated dependencies [e427e9c]
|
|
3966
|
+
- Updated dependencies [bbc9dc3]
|
|
3967
|
+
- Updated dependencies [02f1813]
|
|
3968
|
+
- Updated dependencies [1ef89c0]
|
|
3969
|
+
- Updated dependencies [ba0b61a]
|
|
3970
|
+
- Updated dependencies [ba0b61a]
|
|
3971
|
+
- Updated dependencies [ac716ff]
|
|
3972
|
+
- Updated dependencies [e05553c]
|
|
3973
|
+
- Updated dependencies [f0f3cd5]
|
|
3974
|
+
- Updated dependencies [ab856ed]
|
|
3975
|
+
- Updated dependencies [f0f2046]
|
|
3976
|
+
- Updated dependencies [20f3e65]
|
|
3977
|
+
- Updated dependencies [bbba098]
|
|
3978
|
+
- Updated dependencies [87af769]
|
|
3979
|
+
- Updated dependencies [3be720e]
|
|
3980
|
+
- Updated dependencies [43c0d17]
|
|
3981
|
+
- Updated dependencies [c3df43a]
|
|
3982
|
+
- Updated dependencies [d16d0e9]
|
|
3983
|
+
- Updated dependencies [bbe57fd]
|
|
3984
|
+
- Updated dependencies [272a530]
|
|
3985
|
+
- Updated dependencies [1779e8d]
|
|
3986
|
+
- Updated dependencies [9aa2a57]
|
|
3987
|
+
- Updated dependencies [f7fcc2c]
|
|
3988
|
+
- Updated dependencies [f0f4d6c]
|
|
3989
|
+
- Updated dependencies [4128188]
|
|
3990
|
+
- Updated dependencies [b253c4e]
|
|
3991
|
+
- Updated dependencies [78a9c67]
|
|
3992
|
+
- Updated dependencies [2e97c8c]
|
|
3993
|
+
- Updated dependencies [4a7ef0d]
|
|
3994
|
+
- Updated dependencies [4598f6d]
|
|
3995
|
+
- Updated dependencies [dea17b4]
|
|
3996
|
+
- Updated dependencies [89bb77a]
|
|
3997
|
+
- Updated dependencies [06611e4]
|
|
3998
|
+
- Updated dependencies [44152c4]
|
|
3999
|
+
- Updated dependencies [3939545]
|
|
4000
|
+
- Updated dependencies [66abbde]
|
|
4001
|
+
- Updated dependencies [dc3893d]
|
|
4002
|
+
- Updated dependencies [1bbaa16]
|
|
4003
|
+
- Updated dependencies [6ee259a]
|
|
4004
|
+
- Updated dependencies [7649f43]
|
|
4005
|
+
- Updated dependencies [e708426]
|
|
4006
|
+
- Updated dependencies [3b6d53b]
|
|
4007
|
+
- Updated dependencies [e22fa12]
|
|
4008
|
+
- Updated dependencies [1560d46]
|
|
4009
|
+
- Updated dependencies [276d174]
|
|
4010
|
+
- Updated dependencies [2982ed9]
|
|
4011
|
+
- Updated dependencies [741864f]
|
|
4012
|
+
- Updated dependencies [a8198de]
|
|
4013
|
+
- Updated dependencies [0c789a4]
|
|
4014
|
+
- Updated dependencies [05a49f2]
|
|
4015
|
+
- Updated dependencies [b234a84]
|
|
4016
|
+
- Updated dependencies [a78cd37]
|
|
4017
|
+
- Updated dependencies [5ea623e]
|
|
4018
|
+
- Updated dependencies [4d7d322]
|
|
4019
|
+
- Updated dependencies [5eabe86]
|
|
4020
|
+
- Updated dependencies [ca5d671]
|
|
4021
|
+
- Updated dependencies [32bf2d6]
|
|
4022
|
+
- Updated dependencies [ff0c384]
|
|
4023
|
+
- Updated dependencies [af4fb29]
|
|
4024
|
+
- Updated dependencies [c698a81]
|
|
4025
|
+
- Updated dependencies [ff5ef1c]
|
|
4026
|
+
- Updated dependencies [befd40c]
|
|
4027
|
+
- Updated dependencies [9a97800]
|
|
4028
|
+
- Updated dependencies [6bca0e4]
|
|
4029
|
+
- Updated dependencies [81c0bc4]
|
|
4030
|
+
- Updated dependencies [3c76801]
|
|
4031
|
+
- Updated dependencies [60500cb]
|
|
4032
|
+
- Updated dependencies [2fcefb9]
|
|
4033
|
+
- Updated dependencies [77f846a]
|
|
4034
|
+
- Updated dependencies [bc5870c]
|
|
4035
|
+
- Updated dependencies [b55a346]
|
|
4036
|
+
- Updated dependencies [065bba7]
|
|
4037
|
+
- Updated dependencies [f760064]
|
|
4038
|
+
- Updated dependencies [dd19463]
|
|
4039
|
+
- Updated dependencies [6791717]
|
|
4040
|
+
- Updated dependencies [8ea3bee]
|
|
4041
|
+
- Updated dependencies [100547e]
|
|
4042
|
+
- Updated dependencies [3a58149]
|
|
4043
|
+
- Updated dependencies [6d1c155]
|
|
4044
|
+
- Updated dependencies [d7573b3]
|
|
4045
|
+
- Updated dependencies [bf3edfe]
|
|
4046
|
+
- Updated dependencies [2c8474c]
|
|
4047
|
+
- Updated dependencies [6ce89da]
|
|
4048
|
+
- Updated dependencies [0e05aac]
|
|
4049
|
+
- Updated dependencies [ae61ad4]
|
|
4050
|
+
- Updated dependencies [5aed9e4]
|
|
4051
|
+
- Updated dependencies [83c77dc]
|
|
4052
|
+
- Updated dependencies [3c9fca3]
|
|
4053
|
+
- Updated dependencies [18a8e7d]
|
|
4054
|
+
- Updated dependencies [e7957ab]
|
|
4055
|
+
- Updated dependencies [f7e34ca]
|
|
4056
|
+
- Updated dependencies [e719ebd]
|
|
4057
|
+
- Updated dependencies [516583b]
|
|
4058
|
+
- Updated dependencies [f9e4f91]
|
|
4059
|
+
- Updated dependencies [6ef48b1]
|
|
4060
|
+
- Updated dependencies [58be55e]
|
|
4061
|
+
- Updated dependencies [fa429cf]
|
|
4062
|
+
- Updated dependencies [ed8df3e]
|
|
4063
|
+
- Updated dependencies [fe76ece]
|
|
4064
|
+
- Updated dependencies [8b446f5]
|
|
4065
|
+
- Updated dependencies [8e74b27]
|
|
4066
|
+
- Updated dependencies [7102b20]
|
|
4067
|
+
- Updated dependencies [8ebd57f]
|
|
4068
|
+
- Updated dependencies [968dc1e]
|
|
4069
|
+
- Updated dependencies [9a1fb41]
|
|
4070
|
+
- Updated dependencies [617707a]
|
|
4071
|
+
- Updated dependencies [c40f3b8]
|
|
4072
|
+
- Updated dependencies [58770f3]
|
|
4073
|
+
- Updated dependencies [aefe428]
|
|
4074
|
+
- Updated dependencies [485f096]
|
|
4075
|
+
- Updated dependencies [7357447]
|
|
4076
|
+
- Updated dependencies [199d31b]
|
|
4077
|
+
- Updated dependencies [b655a9d]
|
|
4078
|
+
- Updated dependencies [a865c73]
|
|
4079
|
+
- Updated dependencies [3e01cb5]
|
|
4080
|
+
- Updated dependencies [7138bc1]
|
|
4081
|
+
- Updated dependencies [cef27e2]
|
|
4082
|
+
- Updated dependencies [4e8622b]
|
|
4083
|
+
- Updated dependencies [dffd752]
|
|
4084
|
+
- Updated dependencies [06973aa]
|
|
4085
|
+
- Updated dependencies [50798f3]
|
|
4086
|
+
- Updated dependencies [6a576c9]
|
|
4087
|
+
- Updated dependencies [105f3c5]
|
|
4088
|
+
- Updated dependencies [3ccd9e8]
|
|
4089
|
+
- Updated dependencies [689b979]
|
|
4090
|
+
- Updated dependencies [c70f865]
|
|
4091
|
+
- Updated dependencies [e546222]
|
|
4092
|
+
- Updated dependencies [fd13f52]
|
|
4093
|
+
- Updated dependencies [d7bd274]
|
|
4094
|
+
- Updated dependencies [98c3a74]
|
|
4095
|
+
- Updated dependencies [fffa30d]
|
|
4096
|
+
- Updated dependencies [e4e9557]
|
|
4097
|
+
- Updated dependencies [7a28e1e]
|
|
4098
|
+
- Updated dependencies [ebce5a3]
|
|
4099
|
+
- Updated dependencies [fb336df]
|
|
4100
|
+
- Updated dependencies [4dc80d0]
|
|
4101
|
+
- Updated dependencies [9d9040d]
|
|
4102
|
+
- Updated dependencies [20e317c]
|
|
4103
|
+
- Updated dependencies [0fce2ef]
|
|
4104
|
+
- Updated dependencies [42df928]
|
|
4105
|
+
- Updated dependencies [0e2ddd4]
|
|
4106
|
+
- Updated dependencies [b7479ab]
|
|
4107
|
+
- Updated dependencies [9850c6e]
|
|
4108
|
+
- Updated dependencies [de570cc]
|
|
4109
|
+
- Updated dependencies [b2ea297]
|
|
4110
|
+
- Updated dependencies [5b5a5c3]
|
|
4111
|
+
- Updated dependencies [14582b8]
|
|
4112
|
+
- Updated dependencies [51e144e]
|
|
4113
|
+
- Updated dependencies [19cbf10]
|
|
4114
|
+
- Updated dependencies [b6e83be]
|
|
4115
|
+
- Updated dependencies [ab92940]
|
|
4116
|
+
- Updated dependencies [a691c0b]
|
|
4117
|
+
- Updated dependencies [0b1326d]
|
|
4118
|
+
- Updated dependencies [1e66879]
|
|
4119
|
+
- Updated dependencies [c5200f0]
|
|
4120
|
+
- Updated dependencies [af3861f]
|
|
4121
|
+
- Updated dependencies [2609812]
|
|
4122
|
+
- Updated dependencies [515f171]
|
|
4123
|
+
- Updated dependencies [1f4e029]
|
|
4124
|
+
- Updated dependencies [4f14ad7]
|
|
4125
|
+
- Updated dependencies [258d264]
|
|
4126
|
+
- Updated dependencies [cac64b3]
|
|
4127
|
+
- Updated dependencies [4bb940b]
|
|
4128
|
+
- Updated dependencies [8033ad1]
|
|
4129
|
+
- Updated dependencies [fa140b8]
|
|
4130
|
+
- Updated dependencies [71cba28]
|
|
4131
|
+
- Updated dependencies [190fbd0]
|
|
4132
|
+
- Updated dependencies [c00bf28]
|
|
4133
|
+
- Updated dependencies [93127bd]
|
|
4134
|
+
- Updated dependencies [f2158ec]
|
|
4135
|
+
- Updated dependencies [759606e]
|
|
4136
|
+
- Updated dependencies [fd8dace]
|
|
4137
|
+
- Updated dependencies [72ffc34]
|
|
4138
|
+
- Updated dependencies [a51fa0c]
|
|
4139
|
+
- Updated dependencies [51f3d8d]
|
|
4140
|
+
- Updated dependencies [bf28341]
|
|
4141
|
+
- Updated dependencies [78cbdb5]
|
|
4142
|
+
- Updated dependencies [b7543a9]
|
|
4143
|
+
- Updated dependencies [6c6cee7]
|
|
4144
|
+
- Updated dependencies [42887e0]
|
|
4145
|
+
- Updated dependencies [f1690d4]
|
|
4146
|
+
- Updated dependencies [83fe6e7]
|
|
4147
|
+
- Updated dependencies [d1ab06f]
|
|
4148
|
+
- Updated dependencies [38a9568]
|
|
4149
|
+
- Updated dependencies [f90b8fb]
|
|
4150
|
+
- Updated dependencies [91783c4]
|
|
4151
|
+
- Updated dependencies [982885d]
|
|
4152
|
+
- Updated dependencies [dba7d84]
|
|
4153
|
+
- Updated dependencies [ca39427]
|
|
4154
|
+
- Updated dependencies [bd09957]
|
|
4155
|
+
- Updated dependencies [5a07e67]
|
|
4156
|
+
- Updated dependencies [2d36552]
|
|
4157
|
+
- Updated dependencies [45d8288]
|
|
4158
|
+
- Updated dependencies [b2437a7]
|
|
4159
|
+
- Updated dependencies [f157423]
|
|
4160
|
+
- Updated dependencies [7a90afd]
|
|
4161
|
+
- Updated dependencies [eddc1dd]
|
|
4162
|
+
- Updated dependencies [490f482]
|
|
4163
|
+
- Updated dependencies [27308c5]
|
|
4164
|
+
- Updated dependencies [8689166]
|
|
4165
|
+
- Updated dependencies [c9327c9]
|
|
4166
|
+
- Updated dependencies [920165d]
|
|
4167
|
+
- Updated dependencies [9101be5]
|
|
4168
|
+
- Updated dependencies [f53a8d0]
|
|
4169
|
+
- Updated dependencies [30266cf]
|
|
4170
|
+
- Updated dependencies [968dc1e]
|
|
4171
|
+
- Updated dependencies [57f9b07]
|
|
4172
|
+
- Updated dependencies [3c73d99]
|
|
4173
|
+
- Updated dependencies [d91aed9]
|
|
4174
|
+
- Updated dependencies [ed71d9e]
|
|
4175
|
+
- Updated dependencies [7776fc2]
|
|
4176
|
+
- Updated dependencies [e76634c]
|
|
4177
|
+
- Updated dependencies [c86185e]
|
|
4178
|
+
- Updated dependencies [fb96ecb]
|
|
4179
|
+
- Updated dependencies [1170ed1]
|
|
4180
|
+
- Updated dependencies [92814db]
|
|
4181
|
+
- Updated dependencies [4d73b07]
|
|
4182
|
+
- @object-ui/react@17.7.0
|
|
4183
|
+
- @object-ui/core@17.7.0
|
|
4184
|
+
- @object-ui/types@17.7.0
|
|
4185
|
+
- @object-ui/i18n@17.7.0
|
|
4186
|
+
- @object-ui/components@17.7.0
|
|
4187
|
+
- @object-ui/fields@17.7.0
|
|
4188
|
+
- @object-ui/permissions@17.7.0
|
|
4189
|
+
|
|
3
4190
|
## 17.6.0
|
|
4
4191
|
|
|
5
4192
|
### Minor Changes
|