@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.
Files changed (70) hide show
  1. package/CHANGELOG.md +4187 -0
  2. package/README.md +376 -44
  3. package/dist/DrawerForm.d.ts +30 -2
  4. package/dist/DrawerForm.d.ts.map +1 -1
  5. package/dist/EmbeddableForm.d.ts.map +1 -1
  6. package/dist/FormAnalytics.d.ts.map +1 -1
  7. package/dist/LineItemsPanel.d.ts +6 -3
  8. package/dist/LineItemsPanel.d.ts.map +1 -1
  9. package/dist/MasterDetailForm.d.ts +31 -10
  10. package/dist/MasterDetailForm.d.ts.map +1 -1
  11. package/dist/ModalForm.d.ts +48 -4
  12. package/dist/ModalForm.d.ts.map +1 -1
  13. package/dist/ObjectForm.d.ts.map +1 -1
  14. package/dist/SplitForm.d.ts +37 -2
  15. package/dist/SplitForm.d.ts.map +1 -1
  16. package/dist/TabbedForm.d.ts +65 -3
  17. package/dist/TabbedForm.d.ts.map +1 -1
  18. package/dist/WizardForm.d.ts +143 -38
  19. package/dist/WizardForm.d.ts.map +1 -1
  20. package/dist/autoLayout.d.ts +12 -3
  21. package/dist/autoLayout.d.ts.map +1 -1
  22. package/dist/closedAffordanceNotice.d.ts +14 -0
  23. package/dist/closedAffordanceNotice.d.ts.map +1 -0
  24. package/dist/customFieldsMerge.d.ts +29 -0
  25. package/dist/customFieldsMerge.d.ts.map +1 -0
  26. package/dist/deriveMasterDetail.d.ts +32 -8
  27. package/dist/deriveMasterDetail.d.ts.map +1 -1
  28. package/dist/fieldGroups.d.ts +176 -0
  29. package/dist/fieldGroups.d.ts.map +1 -1
  30. package/dist/fieldWriteGate.d.ts +116 -0
  31. package/dist/fieldWriteGate.d.ts.map +1 -0
  32. package/dist/flatFields.d.ts +11 -2
  33. package/dist/flatFields.d.ts.map +1 -1
  34. package/dist/formChrome.d.ts +55 -0
  35. package/dist/formChrome.d.ts.map +1 -0
  36. package/dist/index.d.ts +124 -26
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +3435 -2093
  39. package/dist/index.umd.cjs +1 -1
  40. package/dist/initialRecord.d.ts +72 -0
  41. package/dist/initialRecord.d.ts.map +1 -0
  42. package/dist/loadFailure.d.ts +28 -0
  43. package/dist/loadFailure.d.ts.map +1 -0
  44. package/dist/masterDetailTx.d.ts.map +1 -1
  45. package/dist/occSave.d.ts +6 -1
  46. package/dist/occSave.d.ts.map +1 -1
  47. package/dist/recordInvalidation.d.ts +33 -0
  48. package/dist/recordInvalidation.d.ts.map +1 -0
  49. package/dist/recordReadGate.d.ts +34 -0
  50. package/dist/recordReadGate.d.ts.map +1 -0
  51. package/dist/sanitize.d.ts +187 -5
  52. package/dist/sanitize.d.ts.map +1 -1
  53. package/dist/schemaDefaults.d.ts +27 -6
  54. package/dist/schemaDefaults.d.ts.map +1 -1
  55. package/dist/sectionFields.d.ts +56 -2
  56. package/dist/sectionFields.d.ts.map +1 -1
  57. package/dist/sectionGroups.d.ts +62 -0
  58. package/dist/sectionGroups.d.ts.map +1 -0
  59. package/dist/sectionPredicateDiagnostic.d.ts +38 -0
  60. package/dist/sectionPredicateDiagnostic.d.ts.map +1 -0
  61. package/dist/submitTarget.d.ts +119 -0
  62. package/dist/submitTarget.d.ts.map +1 -0
  63. package/dist/successBehavior.d.ts +44 -25
  64. package/dist/successBehavior.d.ts.map +1 -1
  65. package/dist/thankYouRedirectCountdown.d.ts +1 -1
  66. package/dist/uploadGate.d.ts +35 -0
  67. package/dist/uploadGate.d.ts.map +1 -0
  68. package/dist/writePayload.d.ts +68 -0
  69. package/dist/writePayload.d.ts.map +1 -0
  70. 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