Skip to content

Commit 2655ebd

Browse files
feat(spec,plugin-form)!: a form view's buttons hold submit and cancel only — buttons.reset is retired, because no form ever drew a reset button (#27269)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBDeyq5DkFHhU9RodAzdP53y)_ Refs #27153 Ruling-ref: 6469111181 Clause-②: no (narrowing) Metadata-protocol: narrowed Skills-docs: none — no `skills/**` file or hand-written docs page authors or teaches `buttons.reset` (0 hits at base); only the generated references move ## 维护者速读 - 改了什么:表单视图 `buttons` 里的 `reset` 退役,写了会被拒并附改法。`buttons` 只剩 `submit` 和 `cancel`。ObjectForm 不再读 `buttons.reset.show`,ui 的 `ObjectFormSchema.buttons` 镜像同步去掉 `reset`。 - 为什么改:ADR-0049 声明即执行。六处表单按钮栏都只画提交和取消,从没有画过重置按钮,`reset.label` 无人读取;唯一读 `reset.show` 的地方把它当成"保存后清空表单",与"重置按钮"的含义不符。 - 风险与回滚:这是收窄,写了 `buttons.reset` 的元数据会被拒。本仓除两个测试夹具外没有任何生产者;已存的 `sys_metadata` 行读取时自动去掉该键,`os migrate meta --from 17` 改写源码。回滚就是 revert 本 PR。 - 先看:① `packages/spec/src/ui/view.zod.ts` 的 `FORM_VIEW_BUTTONS_RESET_RETIRED` 改法文案;② `packages/spec/src/conversions/registry.ts` 的 `formViewButtonsResetRemoved`;③ `ui/packages/plugin-form/src/ObjectForm.tsx` 删掉的那一行折叠。 - 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run --project repo src/ui/form-view-buttons-reset-retirement.test.ts`,预期 22 个测试全部通过。 - 需要你做的:无,席位落地。 ## What changes ### Spec (`@objectstack/spec`) - `FormViewSchema.buttons.reset` is a `retiredKey()` tombstone inside the live, strict `buttons` block. Its prescription: "`view.form.buttons.reset` was removed in @objectstack/spec 18.3.0 (ADR-0049) — no form ever drew a reset button, and its label was never read. Delete the key: a form has `submit` and `cancel` buttons only, and Cancel leaves the form without saving. `reset.show: true` used to clear the form after each save (the ObjectForm fold that ObjectView and the record form page render); a form converted by this release no longer clears after a save, and a form that should declares `submitBehavior: { kind: 'continue' }`." followed by the `os migrate meta --from 17` sentence. `buttons.submit` and `buttons.cancel` are unchanged. - **Behaviour after conversion (M1).** `reset.show: true` used to clear the form after each save, through the ObjectForm fold that ObjectView and the record form page render. A converted stored row no longer clears after a save. The D2 conversion does not map the key onto `submitBehavior`, because the row cannot say whether the author wanted the clear. The prescription, the D3 `replacement` and `reason`, the conversion docblock and summary, the STEP18 rationale fragment and the changeset's FROM → TO line now all say this. They name `submitBehavior: { kind: 'continue' }`, which ObjectForm and the console `FormPage` honour, for an author who wants the clear. - ADR-0087 kit (`spec-property-retirement`, key-tombstone route): - **D2 is owed**, so it ships: the block accepted `reset` through 18.2.0 and `PUT /meta/view` persists form views, so a stored `view` row can carry the key. Conversion `form-view-buttons-reset-removed` (`retiredFromLoadPath: true`, `retiredAfter: '18.2.0'`, MAJOR_18 order 79) strips `reset` from every form payload's `buttons`: a container's `form` and `formViews.*`, a form view item's `config`, a flattened form overlay, and the assembled `viewItems` channel. A block that held only `reset` stays an empty block. Its fixture has 4 notices. - D3 semantic entry `ui-form-view-buttons-reset-retired` (its `conversionIds` names the conversion), plus a STEP18_RATIONALE fragment. - One `RETIRED_KEYS_BY_MAJOR` row: `ui/FormView:buttons.reset` (a nested key of an inline block, so no `authorable-surface/` line of its own). - Removal-version pin: one JOIN row (the tombstone). - Liveness (`liveness/view.json`): the `props/form/children/buttons` row stays `live` for `submit` / `cancel`, and its note no longer claims `reset` folds. It records the removal and the measurement, because sub-keys cannot be drilled. - Regenerated: `content/docs/references/ui/view.mdx` and `references/api/protocol.mdx`. `authorable-surface`, `api-surface`, `react-blocks.md` and `skills/**` are byte-identical (`buttons` is not in the React `ObjectForm` block's `dataProps`). ### Renderer (`@object-ui/plugin-form`, `@object-ui/types`): the reader of the refused key, in the same PR - `ObjectForm.foldFormButtons` no longer folds `buttons.reset.show` onto `showReset`. That fold was the key's only reader, and it landed on `resetOnSubmit` (clear the form after a save), not on a button. The fold's comment no longer lists `reset` among the structured `buttons` keys. - The hand-written `ObjectFormSchema.buttons` mirror drops `reset` in both the TypeScript type (`types/src/objectql.ts`) and the zod mirror (`types/src/zod/objectql.zod.ts`), so the ui side no longer offers a key nothing reads. - The ui types fixture `object-form-unmirrored-members-6152.test.ts` now uses `cancel` where it used `reset`. - Comment-only: `FormPage.readFormButton`, `resolveFormViewChrome` and the plugin-form fold test header no longer describe `reset` as an unread key. - **Not touched:** the flat `showReset` prop and its `resetOnSubmit` mapping. That is the block contract's own defect (`component.zod.ts` and `react-blocks.ts` describe it as "Show the reset button"), filed by the seat as #27247. ## Why #27153 asked for enforce-or-remove on `buttons.reset` under ADR-0049. Measured at `origin/main` `fbe344b3`, six separate button bars draw a form's submit and cancel: the components form action bar (simple, sectioned, tabbed and split ObjectForm), console `FormPage`, the `ModalForm` and `DrawerForm` footers, `WizardForm` and `MasterDetailForm`. None of them draws a reset button. Two of them would need a semantic decision before one could (whether the wizard resets the step or the whole wizard, and whether master-detail resets line items). In-repo producers: 0 outside two test fixtures. Seat 1 ruled **retire** (claim 6469111181). ## Tests - New pin `packages/spec/src/ui/form-view-buttons-reset-retirement.test.ts` (22 tests, `repo` project, added to `vitest.repo-tests.json`): - five doors refuse `buttons.reset` (with `show: true`, `show: false`, a label, and empty) at `…buttons.reset`, with the prescription's first sentence and the migrate sentence. The doors are `FormViewSchema`, `view.form`, `view.formViews.*`, a ViewItem form record and the flattened form overlay; - the five doors still accept `submit` and `cancel`; - a control: an unknown button keeps the strict block's `unrecognized_keys`, not "was removed"; - `defineStack` refuses with `STACK_SCHEMA_INVALID` / 422 at `views.0.formViews.quick.buttons.reset`; - a `@ts-expect-error` at the authoring site; - the conversion over a stored view container and a stored form view item, its idempotence, its absence from the load path, its step-18 wiring and its D3 entry; - a tree-scoped absence walk with an anti-vacuity matcher test. As a probe, I wrote a temporary `packages/spec/src/ui/zz-probe-27153.ts` authoring `buttons: { submit: {}, reset: { show: true } }`; the walk named it (1 failed), and the probe was removed with a trap. - `view.test.ts`: the buttons block test drops `reset`, and a new case pins the tombstone. - `retired-key-removal-version.pin.test.ts`: JOIN row. ## Acceptance notes - **Producer census, at base `fbe344b3`:** `buttons.reset` was authored by 0 files in `examples/`, platform sources, `skills/**`, Studio `*.form.ts` and ui examples. Two test fixtures authored it: spec `view.test.ts:3222` and ui types `object-form-unmirrored-members-6152.test.ts:112`, both moved here. **hotcrm, measured** on a public shallow clone at `353dace` (1055 tracked files, at 2026-10-28T08:39Z): 0 files author a `buttons: {` block at all, and no `reset:` key appears outside a `package.json` script name. If a hotcrm source authored after that commit carries the key, it is refused at parse with the prescription, `os migrate meta --from 17 --write` deletes it, and a stored row is converted on read. - **Kept, as stated in the claim:** `drawerSide` and `drawerWidth` stay. They retire with #27159, which owns the React `ObjectForm` block contract; hence `Refs #27153`, not `Fixes`. - **Install note:** the worktree install ran with `--ignore-scripts`, because the `better-sqlite3` header download from nodejs.org was cut off twice. Its shipped `prebuilds/linux-x64.node` loads (`select 1` answered), so nothing measured here depended on the skipped script. ## Verification Head: `a443f1228`. It is `f101f23dd` plus `3684cdf07` (review 6471309095 round 2: M1, S2, and the `conversions-major18-merge` placement fix, which moves `formViewButtonsResetRemoved` directly above `formViewOptionDefaultRemoved`), a merge of `origin/main` at `696ee79f5` (made with `os-regen-merge.sh`), and the `protocol.mdx` regeneration it owed. On the merged tree: the spec build, `check:generated` (14/14), the spec suite (749 files, 22381 tests passed), and `test:repo` at 1376 of 1377. The one failure is `def-key-collisions.test.ts`: its sandbox copy cannot find tsx's `cli.mjs` because this worktree was installed with `--ignore-scripts`; it passed in CI on `f101f23dd`. Full dogfood ran on `5026fd569`. The round-1 notes below were measured on `f101f23dd`. <details><summary>Spec build, generated artifacts and source audits</summary> - `pnpm --filter @objectstack/spec build` (under os-verify-lock): `VERDICT command-exit 0`. - `check:generated`: 1 of 14 stale (`check:docs`), fixed with `--fix`; re-run: "All 14 generated artifacts are up to date". - `check:liveness` (exit 0, 1209 classified), `check:empty-state`, `check:exported-any`, `check:dual-source-exports`, `check:variant-docs`: exit 0 each. - `pnpm --filter @objectstack/spec typecheck`: exit 0; `check:test-typecheck` OK, 132 pinned signatures held. </details> <details><summary>Tests</summary> - `@objectstack/spec` `pnpm test`: 744 files and 22314 tests passed, 1 todo (`5026fd569`); after the merge, 748 files and 22353 tests passed, 1 todo (`f101f23dd`). - New pin (`--project repo`): 22/22. - `retired-key-removal-version.pin.test.ts` and `retired-key-migrate-sentence.test.ts`: 70/70. - ui `@object-ui/types`: `type-check` exit 0; `object-form-unmirrored-members-6152`, `zod-mirror-parity` and `object-view-slot-key-lists`: 165/165. - ui `@object-ui/plugin-form`: `type-check` exit 0; `formButtonsDefaults`, `formViewTabKeysAndButtons-26843` and `ObjectForm.test`: 19/19. - Full dogfood (`'@objectstack/dogfood^...'` closure build, then `pnpm --filter @objectstack/dogfood test`, under os-verify-lock, on `5026fd569`): `VERDICT command-exit 0`; 348 test files passed and 3 skipped (351); 2804 tests passed and 20 skipped. </details> <details><summary>Derived gates</summary> `node scripts/pm/dispatch-gates.mjs --commands` (tree `objectstack-ai/objectstack`, merge base `fbe344b3c`, 20 paths, 589 changed lines): all 32 `node scripts/…` commands exit 0 on both `5026fd569` and `f101f23dd`, `check-adr-0087-registration --base origin/main` included. `pnpm --dir ui exec vitest run scripts/`: 175 files passed, 2 skipped; 5020 tests passed. Every other family is CI's. </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [ ] 3. No formatter ran; `pnpm lint` is CI's run (not run locally). - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English (the 维护者速读 block is the dispatch contract's Chinese summary). --- _Generated by [Claude Code](https://claude.ai/code/session_01WGufzg7PvSmSGxVnCGiGfZ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 19f49ef commit 2655ebd

20 files changed

Lines changed: 571 additions & 36 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@object-ui/plugin-form': minor
4+
'@object-ui/types': minor
5+
---
6+
7+
feat(spec)!: a form view's `buttons` block holds `submit` and `cancel` only; `buttons.reset` is retired, because no form ever drew a reset button
8+
9+
Clause-②: no (narrowing)
10+
11+
Metadata-protocol: narrowed (`FormViewSchema.buttons.reset` is a tombstone)
12+
13+
<!-- adr-0087: registered form-view-buttons-reset-removed, ui-form-view-buttons-reset-retired -->
14+
15+
**BREAKING** accept-set narrowing on the form view surface (`@objectstack/spec`), ADR-0049 enforce-or-remove.
16+
17+
The console draws a form's Submit and Cancel buttons on every entry point (the form page, the New and Edit dialogs, the record form page, master-detail forms). None of them drew a reset button, and `buttons.reset.label` was never read. The one renderer fold that read `buttons.reset.show` passed it to ObjectForm's clear-after-save flag, so `show: true` emptied the form after a successful save instead of adding a button.
18+
19+
- **FROM → TO.** `buttons: { submit, cancel, reset }` → `buttons: { submit, cancel }`. No button replaces it: Cancel leaves the form without saving. `reset.show: true` used to clear the form after each save on ObjectView and the record form page; a converted form no longer clears after a save. A form that should clear after a save declares `submitBehavior: { kind: 'continue' }`, which ObjectForm and the console form page honour.
20+
- **The one-line fix.** Delete `buttons.reset`. `os migrate meta --from 17` lists the edit for existing sources, and `--write` applies it. A stored `sys_metadata` row is converted when it is read (the `form-view-buttons-reset-removed` conversion), so a form view saved with the key keeps loading.
21+
- **What the parse says.** "`view.form.buttons.reset` was removed in @objectstack/spec 18.3.0 (ADR-0049) — no form ever drew a reset button, and its label was never read. …", at every form view door (a view's `form` and `formViews.*`, a form view item record, a flattened form overlay) and at `defineStack` as `STACK_SCHEMA_INVALID` / 422. TypeScript rejects the key at the authoring site.
22+
- **Unchanged.** `buttons.submit` and `buttons.cancel` parse and render as before.
23+
24+
The renderer (`@object-ui/plugin-form`, `@object-ui/types`): `ObjectForm` no longer folds `buttons.reset.show` onto `showReset`, and the `ObjectFormSchema.buttons` mirror (type and zod) drops `reset`. The flat `showReset` prop itself is unchanged.

‎content/docs/references/api/protocol.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,7 +1918,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
19181918
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
19191919
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
19201920
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
1921-
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
1921+
| **buttons** | `{ submit?: object; cancel?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
19221922
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
19231923

19241924
### Nested Shape: `GetUiViewResponse.listViews[string]`
@@ -1997,7 +1997,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
19971997
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
19981998
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
19991999
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
2000-
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
2000+
| **buttons** | `{ submit?: object; cancel?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
20012001
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
20022002

20032003
### Nested Shape: `GetUiViewResponse.protection`

‎content/docs/references/ui/view.mdx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ Form-view select option — the object-field option shape minus the per-option `
408408
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
409409
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
410410
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
411-
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
411+
| **buttons** | `{ submit?: object; cancel?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
412412
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
413413

414414
### Nested Shape: `FormView.data[provider='object']`
@@ -504,7 +504,7 @@ Form-view select option — the object-field option shape minus the per-option `
504504
| :--- | :--- | :--- | :--- |
505505
| **submit** | `{ show?: boolean; label?: string \| Record<string, string> }` | optional | Submit button |
506506
| **cancel** | `{ show?: boolean; label?: string \| Record<string, string> }` | optional | Cancel button |
507-
| **reset** | `{ show?: boolean; label?: string \| Record<string, string> }` | optional | Reset button |
507+
| **reset** | `never` | optional | [REMOVED] `view.form.buttons.reset` was removed in @objectstack/spec 18.3.0 (ADR-0049) — no form ever drew a reset button, and its label was never read. Delete the key: a form has `submit` and `cancel` buttons only, and Cancel leaves the form without saving. `reset.show: true` used to clear the form after each save (the ObjectForm fold that ObjectView and the record form page render); a form converted by this release no longer clears after a save, and a form that should declares `submitBehavior: { kind: 'continue' }`. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
508508

509509

510510
---
@@ -1788,7 +1788,7 @@ Tab configuration for multi-tab view interface
17881788
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
17891789
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
17901790
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
1791-
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
1791+
| **buttons** | `{ submit?: object; cancel?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
17921792
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
17931793

17941794
### Nested Shape: `View.listViews[string]`
@@ -1867,7 +1867,7 @@ Tab configuration for multi-tab view interface
18671867
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
18681868
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
18691869
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
1870-
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
1870+
| **buttons** | `{ submit?: object; cancel?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
18711871
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
18721872

18731873
### Nested Shape: `View.protection`
@@ -2104,7 +2104,7 @@ This schema accepts one of the following structures:
21042104
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
21052105
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
21062106
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
2107-
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
2107+
| **buttons** | `{ submit?: object; cancel?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
21082108
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
21092109

21102110
### Nested Shape: `ViewItem[viewKind='form'].protection`
@@ -2288,7 +2288,7 @@ This schema accepts one of the following structures:
22882288
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
22892289
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
22902290
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
2291-
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
2291+
| **buttons** | `{ submit?: object; cancel?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
22922292
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
22932293

22942294
### Nested Shape: `ViewItemWire[viewKind='form'].protection`

‎packages/spec/liveness/view.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@
370370
},
371371
"buttons": {
372372
"status": "live",
373-
"note": "objectui: ObjectForm.tsx foldFormButtons() reads schema.buttons and folds submit/cancel/reset {show,label} onto the flat showSubmit/submitText/showCancel/cancelText/showReset props (framework#1894 / #2998); ObjectView + RecordFormPage forward it. Flat keys kept as deprecated back-compat (explicit flat key wins)."
373+
"note": "objectui: ObjectForm.tsx foldFormButtons() reads schema.buttons and folds submit/cancel {show,label} onto the flat showSubmit/submitText/showCancel/cancelText props (framework#1894 / #2998); ObjectView forwards it, and every console form entry point resolves submit/cancel through resolveFormViewChrome / readFormButton (#26843). Flat keys kept as deprecated back-compat (explicit flat key wins). SUB-KEY buttons.reset REMOVED 2026-10-28 (#27153, ADR-0049 enforce-or-remove, ruled retire): measured at origin/main fbe344b3, six button bars (components form.tsx, console FormPage, ModalForm, DrawerForm, WizardForm, MasterDetailForm) drew submit/cancel and none drew reset; reset.label had no reader; the one fold of reset.show landed on ObjectForm showReset, which means clear-after-save (resetOnSubmit), not a button. Tombstoned inside the buttons block (retiredKey carries the prescription) and stripped from stored rows and sources by the protocol-18 form-view-buttons-reset-removed conversion; the fold was removed in the same change. Sub-keys cannot be drilled (one level only), so the removal is recorded here; submit and cancel keep this row live."
374374
},
375375
"defaults": {
376376
"status": "live",

0 commit comments

Comments
 (0)