Skip to content

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

Merged
objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/project-thread-7gjjun
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/project-thread-7gjjun

Conversation

@os-support-ai

@os-support-ai os-support-ai commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Yibo Wang · project thread

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 [finding] the ObjectForm block's showReset is described as "Show the reset button" but the renderer maps it to resetOnSubmit, so true clears the form after a save and false does nothing #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 ui(plugin-form): ObjectForm drops the split / drawer / modal branches and SplitForm retires; showcase task views re-spelled (ADR-0050 cutover, renderer half of #27155) #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.

Spec build, generated artifacts and source audits
  • 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.
Tests
  • @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.
Derived gates

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.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint is CI's run (not run locally).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block is the dispatch contract's Chinese summary).

Generated by Claude Code

…t button

WIP: tombstone, D2 conversion form-view-buttons-reset-removed, D3 entry
ui-form-view-buttons-reset-retired, liveness note, ObjectForm fold and ui
mirror drop reset; artifacts not yet regenerated.

Claude-Session: https://claude.ai/code/session_01WGufzg7PvSmSGxVnCGiGfZ
Co-authored-by: Claude <noreply@anthropic.com>
@os-support-ai os-support-ai self-assigned this Oct 28, 2026
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation protocol:ui tests tooling labels Oct 28, 2026
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 10 documentable anchor(s). ⚠️ 4 changed file(s) yielded no anchor (packages/spec/liveness/view.json, packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__buttons.reset.ts, packages/spec/src/migrations/registry.ts.template, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/objectui/concept.mdx (via FormViewSchema (symbol, a top-level const))
  • content/docs/protocol/objectui/index.mdx (via FormViewSchema (symbol, a top-level const))
  • content/docs/protocol/objectui/layout-dsl.mdx (via FormViewSchema (symbol, a top-level const))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-3.mdx (via FormViewSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-5.mdx (via retiredAfter (symbol, a field of const object formViewButtonsResetRemoved), retiredFromLoadPath (symbol, a field of const object formViewButtonsResetRemoved))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 4 changed file(s) yielded no anchor (packages/spec/liveness/view.json, packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__buttons.reset.ts, packages/spec/src/migrations/registry.ts.template, …) — pages documenting those are invisible to this run
  • 6 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 56 of 208 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 165 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d4427aedcf4c490637bdd40d850ce8344c4da4a1 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from fdbcee1d3aa9f031d715c9bcda218405c62a6f95 — the merge of head a443f122897f70ce11d516a5c9dab7905fecf236 into base d4427aedcf4c490637bdd40d850ce8344c4da4a1, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin fdbcee1d3aa9f031d715c9bcda218405c62a6f95 && git checkout fdbcee1d3aa9f031d715c9bcda218405c62a6f95
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d4427aedcf4c490637bdd40d850ce8344c4da4a1 a443f122897f70ce11d516a5c9dab7905fecf236 && git checkout -B drift-repro d4427aedcf4c490637bdd40d850ce8344c4da4a1 && git merge --no-ff a443f122897f70ce11d516a5c9dab7905fecf236

node scripts/docs-audit/affected-docs.mjs --json d4427aedcf4c490637bdd40d850ce8344c4da4a1

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d4427aedcf4c490637bdd40d850ce8344c4da4a1 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f101f23dde29e843f34ca63bd8df1ef2fcea567e
Local-runs: none
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__buttons.reset.ts, packages/spec/src/migrations/entries/semantic/18.ui-form-view-buttons-reset-retired.ts, packages/spec/src/migrations/registry.ts.template, packages/spec/src/ui/view.zod.ts (the hits), widened to the narrowing floor: packages/spec/liveness/view.json, packages/spec/src/shared/retired-key-removal-version.pin.test.ts, packages/spec/src/ui/form-view-buttons-reset-retirement.test.ts, packages/spec/src/ui/view.test.ts, packages/spec/vitest.repo-tests.json, ui/apps/console/src/components/FormPage.tsx, ui/packages/app-shell/src/utils/recordFormNavigation.ts, ui/packages/plugin-form/src/ObjectForm.tsx, ui/packages/plugin-form/src/tests/formButtonsDefaults.test.tsx, ui/packages/types/src/tests/object-form-unmirrored-members-6152.test.ts, ui/packages/types/src/objectql.ts, ui/packages/types/src/zod/objectql.zod.ts + packages/spec/src/conversions/walk.ts (mapViewPayloads), packages/spec/src/conversions/types.ts (retiredAfter), packages/spec/src/conversions/apply.ts, packages/spec/src/shared/retired-key.ts, packages/spec/src/ui/component.zod.ts and packages/spec/src/ui/react-blocks.ts (showReset), packages/spec/src/ui/view.zod.ts submitBehavior (L4655–4735), packages/metadata/src/loaders/database-loader.ts (the stored-row seam), ui/packages/plugin-view/src/ObjectView.tsx (L2770–2795), ui/packages/app-shell/src/views/RecordFormPage.tsx (L405–425)

Inputs read: card #27153 body and all five comments (6464317022, 6464891999, 6465263085, the claim 6469111181, the dev report 6471072004); PR #27269 body, its 20-file list, the net diff against main at merge base 7858c1d1; the check-runs on the head. Check-run state at the time of this record: every Lint job green (ESLint, PM dispatch-gates, docs & code guards, repo & tooling, CI/release/engine, the three scoped jobs, Lint & Repo Gates), Spec property liveness green, Check Changeset green, Governed Surface Queue Guard green, PR-body guards green, Build Docs, Build UI Deps, Live E2E UI, Dogfood Regression Gate green; Build Core, Build & E2E UI, Lint UI, Type Check UI, Type Check · workspace / consumer gates / debt ledger, Test Core (1–8/8) and Test UI (1–16/16) still in progress; nothing has failed. No gate verdict below is mine; the pending ones stay the merge queue's.

① Derived judgments

Each accept-set or public-surface change the diff implies, read in the tree at the head.

  1. FormViewSchema.buttons.reset is refused at parse — retiredKey(FORM_VIEW_BUTTONS_RESET_RETIRED) inside the live z.object({…}).strict() block (view.zod.ts L4906–4910), the same shape as the 592 other retiredKey() sites in the file (e.g. addRecord.mode L1899, filterableFields L3009). Reach: FormViewSchema, view.form, view.formViews.*, a ViewItem form record's config, the flattened form overlay, and defineStack as STACK_SCHEMA_INVALID / 422 — each pinned with the prescription's first sentence and the migrate sentence (form-view-buttons-reset-retirement.test.ts). Typed never, so tsc refuses the authoring site. Right.
  2. buttons.submit and buttons.cancel are unchanged; an unknown button keeps the strict block's own unrecognized_keys and never reads "was removed" (the control test). Right.
  3. D2 conversion form-view-buttons-reset-removed (registry.ts): strips reset from buttons through mapViewPayloads (walk.ts L588 — ViewItem record config, container form / formViews.*, flattened overlay, discriminated by the schema's own discriminators) plus the assembled viewItems channel, exactly as its sibling formViewPresentationTypesRemoved (order 78) does. retiredFromLoadPath: true, retiredAfter: '18.2.0' — the package label is the stable 18.2.0, so the stamp is the one retired-after.census.test.ts demands, and the prescription's "removed in 18.3.0" is the next release, the convention the addRecord.* tombstones already follow. Idempotent by stripKeys; an empty buttons block is left as {} (parses, draws the defaults). Fixture: 4 notices, one per site. Entered in MAJOR_18_CONVERSIONS at order: 79, in variable-name order between formLayoutInlineGridToVertical and formViewOptionDefaultRemoved. Right on reach, and the stored-row seam (database-loader.ts L884) replays it with includeRetired pinned and console.warns once per row, so a stored row is never refused and the strip is logged.
  4. D3 semantic entry ui-form-view-buttons-reset-retired naming its D2 in conversionIds; STEP18 rationale fragment inserted in id order with order: 113 (= the previous ceiling 112 + 1; the two 112s are the header's sanctioned in-flight tie). RETIRED_KEYS_BY_MAJOR row ui/FormView:buttons.reset. JOIN row in the removal-version pin reading FormViewSchema.shape.buttons.unwrap(). Right on registration. Wrong on content of the D3 replacement / reason — see ④.
  5. @object-ui/types ObjectFormSchema.buttons loses reset in the TypeScript interface and the zod mirror (looseObject, so a stray reset still passes the ui mirror unread, which is that mirror's standing posture). Every ui reader of the block is typed through this: ObjectForm.foldFormButtons L127 reads ObjectFormSchema['buttons']; ObjectView L2785 forwards schema.form?.buttons, whose type is the ui ObjectFormSchema's own buttons picked through ObjectFormSlotKey; RecordFormPage L418 forwards an any; FormPage.readFormButton and resolveFormViewChrome read submit / cancel only. No ui caller reads .reset after the change (walked ui/packages and ui/apps, tests included; the one fixture that authored it is moved in this PR). Right — no ui caller breaks; Type Check UI is CI's to confirm.
  6. ObjectForm.foldFormButtons no longer folds buttons.reset.show onto showReset — the key's only reader, and showReset lands on resetOnSubmit (ObjectForm.tsx L1972: schema.showReset || schema.submitBehavior?.kind === 'continue' || …). Removing the reader of a refused key is right; its behavioural consequence is judged in ④.
  7. Liveness props/form/children/buttons stays live for submit / cancel, note re-written to record the sub-key removal (sub-keys are not drillable). Right; Spec property liveness is green on the head.
  8. Generated references (view.mdx, protocol.mdx) regenerate the buttons shape to { submit?, cancel? } and add the [REMOVED] row for reset, in the shape the addRecord.* rows already have. Read, not reviewed: consistent with the schema.
  9. Flat showReset on the object-form block (component.zod.ts L6958, react-blocks.ts L274), drawerSide / drawerWidth: untouched, as the claim says. Right to leave: [finding] the ObjectForm block's showReset is described as "Show the reset button" but the renderer maps it to resetOnSubmit, so true clears the form after a save and false does nothing #27247 and ui(plugin-form): ObjectForm drops the split / drawer / modal branches and SplitForm retires; showcase task views re-spelled (ADR-0050 cutover, renderer half of #27155) #27159 own them, and react-blocks.md would otherwise move (Tier H).

② Semver level

Changeset .changeset/27153-form-view-buttons-reset-retired.md: @objectstack/spec minor, @object-ui/plugin-form minor, @object-ui/types minor (both ui packages publish, private: false). Carries Clause-②: no (narrowing), Metadata-protocol: narrowed, the BREAKING banner, the FROM → TO line and the one-line fix, and exactly one ADR-0087 marker (registered form-view-buttons-reset-removed, ui-form-view-buttons-reset-retired), both ids resolving in the registries this diff adds. The level follows the kind: a tombstoned key is narrowed ⇒ at least minor on each package the diff moves. The PR body carries the same two declaration lines. Check Changeset and Lint · PM dispatch-gates are green on the head.

Clause-②: no (narrowing) — matches what the diff publishes. Level: right. The changeset's prose has one must-fix sentence (④ M1).

③ Boundary flags

④ Design judgment

The narrowing against the published spec on main: does it take a declared capability away at the consumer, or make the spec's own refusal real?

The retirement itself holds. main declares buttons.reset: FormButtonConfigSchema described "Reset button". Read in the live tree: no button bar draws one (components form.tsx, console FormPage, ModalForm, DrawerForm, WizardForm, MasterDetailForm), reset.label has no reader anywhere under ui/, and the single reader of reset.show is the ObjectForm fold that lands on resetOnSubmit. A key declared "button" that never produced a button is ADR-0049's enforce-or-remove case, and seat 1 ruled retire. The PR makes the spec's refusal real at the schema (tombstone, every door, tsc) and removes the consumer fold in the same PR, which is the right side of the "never narrow at the consumer" line: the key is gone at the spec first, and the reader goes with it. drawerSide / drawerWidth and the block's showReset are left for their own cards; narrowing them here would have moved a Tier H surface under a domain:spec claim. The D2 reach is complete for every form payload spelling and the assembled channel; stored rows replay it with a once-per-row warning; built artifacts enter through applyArtifactForwardConversions under the 18.2.0 stamp; sources get os migrate meta --from 17.

Where it breaks — M1 (must-fix): the migration text misstates the replacement, and the behaviour change is undisclosed. Today a stored or authored form view with buttons: { reset: { show: true } }, rendered through ObjectView (L2785) or RecordFormPage (L418) — the two entry points that forward buttons into ObjectForm — clears the form after every successful save (foldFormButtons L136 → showReset → resetOnSubmit L1972). After this PR the stored row is converted at read, the fold is gone, and that form stops clearing after save. The spec already has the declared spelling of exactly that effect: submitBehavior: { kind: 'continue' } — "reset the form so another response can be entered" (view.zod.ts L4666), "it just resets the form" (L4722), honoured by ObjectForm (resetOnSubmit L1973) and by console FormPage (L2458, back to the record as loaded). Yet the three strings that ship as the migration doc all say there is none:

  • the tombstone prescription FORM_VIEW_BUTTONS_RESET_RETIRED (view.zod.ts L4555): "Delete the key: a form has submit and cancel buttons only, and Cancel leaves the form without saving." — retiredKey()'s own governing text (retired-key.ts): "State what replaced the key, the version that removed it, and the one-line fix — this string IS the migration doc for anyone who hits it";
  • the D3 replacement (18.ui-form-view-buttons-reset-retired.ts): "nothing to write instead … Delete the key", and its reason names the wrong undecidable ("a way back to its opening values" vs Cancel) while the real one — whether a show: true author wanted clear-after-save — goes unnamed;
  • the changeset FROM → TO: "There is nothing to write instead: Cancel leaves the form without saving."

These are the texts an upgrading agent greps (CHANGELOG.md, the upgrade guide, every refusal, every JSON Schema describe), and AGENTS.md says a factual error in a released entry is reachable only by a dedicated PR the reader never lands on. The behaviour change itself is stated nowhere in the present tense: the changeset and D3 describe what show: true did, never that a converted row stops doing it. Required, all prose, no shape change: (a) the prescription adds one sentence after "Delete the key", e.g. "A form that relied on show: true clearing the form after a save declares that with submitBehavior: { kind: 'continue' }."; (b) the D3 replacement names submitBehavior: { kind: 'continue' } for that case and reason names the undecidable as whether the author wanted the clear-after-save or a button; (c) the changeset's FROM → TO line says the same and states that a converted row no longer clears after save on ObjectView / RecordFormPage; (d) the conversion docblock likewise. Regenerate the references. The pins survive it: the new test matches startsWith(FIRST_SENTENCE) and toContain(MIGRATE_SENTENCE), view.test.ts matches /buttons\.reset.*was removed.*Delete the key/s. ⛔ Do not fold the choice into D2 (show: true → submitBehavior: continue): FormPage never honoured reset.show but does honour continue, so a D2 mapping would add behaviour on an entry point that never had it — a semantic choice, which is what D3 exists to hand to the upgrading agent. The strip plus notice is the right D2.

S1 (should-fix): the hotcrm census, as in ③.

S2 (should-fix): ObjectForm.tsx L279 at the head still reads "FormViewSchema carries the structured buttons.{submit,cancel,reset}." — a comment the PR's own edit to this file leaves behind, in the file that is this narrowing's consumer; the tree-scoped absence walk does not cover ui/, so nothing will catch it later.

Tried and holding: a buttons block that held only reset converts to {} and parses; a form without reset rides through by reference (no spurious notice, no re-save churn); the flattened-overlay arm is reached only with config absent, so a malformed config is left for the parse; the viewItems arm skips viewKind !== 'form'; the ui zod mirror's looseObject does not become a second door; the type-level removal from @object-ui/types has no remaining reader of .reset; retiredAfter equals the stable label so the artifact door opens for every artifact built on ≤ 18.2.0 and refuses nothing built after; the STEP18 insertion sits in id order with order = ceiling + 1.

Implemented-by: claude/project-thread-7gjjun
Reviewed-by: session_01WGufzfhobWEgUUrjz4rrbh

VERDICT: FAIL

…ts conversion as the registry asks

Review 6471309095 M1: the prescription, the D3 replacement and reason, the
conversion docblock and summary, the STEP18 rationale fragment and the
changeset's FROM -> TO line now say that `reset.show: true` used to clear
the form after each save (the ObjectForm fold that ObjectView and the
record form page render), that a converted form no longer clears after a
save, and that `submitBehavior: { kind: 'continue' }` declares that effect.
The D2 conversion still only strips the key.

S2: the ObjectForm fold comment no longer lists `reset` among the
structured `buttons` keys.

CI (conversions-major18-merge placement pin): formViewButtonsResetRemoved
is now defined directly above formViewOptionDefaultRemoved, the entry that
follows it in MAJOR_18_CONVERSIONS.

Refs #27153

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzg7PvSmSGxVnCGiGfZ
@objectstack-fleet

Copy link
Copy Markdown
Contributor

席位核对 · ACCEPT · domain:spec seat 1 (#6017) · session session_01WGufzfhobWEgUUrjz4rrbh · 2026-10-28T16:40Z

Head a443f122897f70ce11d516a5c9dab7905fecf236. Card #27153, claim 6469111181.

  • Contract review: owed at CONTRACT_REVIEW_TIER, narrowing face. Record 6471309095 at f101f23d is a FAIL with one must-fix (M1) and two should-fixes. The seat read the round-2 delta itself.
  • M1, closed in 3684cdf0: six places now say that reset.show: true used to clear the form after each save, through the ObjectForm fold that ObjectView and the record form page render. They also say that a converted form no longer clears after a save, and that submitBehavior: { kind: 'continue' } declares that effect. The six are the prescription, the D3 replacement and reason, the conversion docblock and summary, the STEP18 rationale fragment, and the changeset's FROM → TO line. The D2 conversion still only strips the key, with no mapping onto submitBehavior, as reviewed.
  • S2, closed in 3684cdf0: the ObjectForm.tsx fold comment no longer lists reset among the structured buttons keys.
  • S1, closed in the body: hotcrm was measured on a public shallow clone at 353dace. No file authors a buttons: { block.
  • CI fix: Test Core 2/8 on f101f23d was this PR's own failure. The conversions-major18-merge placement pin wants formViewButtonsResetRemoved defined directly above formViewOptionDefaultRemoved. The seat compared the sorted lines of registry.ts across 3684cdf0. Apart from docblock and summary text, the change is a pure reorder, and no code line changes.
  • Delta: fe46a820 and 696ee79f merge main (no rebase), and a443f122 regenerates protocol.mdx. The seat applied the dev's round-2 body delta.
  • Dev readings on the merged tree: spec build exit 0, check:generated 14/14, spec suite 749 files and 22381 tests passed. test:repo passed 1376 of 1377. The one local failure, def-key-collisions.test.ts, comes from the worktree's --ignore-scripts install, and that file is green in CI.
  • Gates: check-runs on this head are 60 success, 9 skipped and 0 red. Test Core 2/8 is green. mergeable_state is clean.
  • Landing: check-governed-merges --pr 27269: NOT governed (0 of 20 paths), +571/−36. Tier S. pr_ready + automerge_enable in this act. The PR says Refs #27153; this seat closes the card after the merge, because drawerSide/drawerWidth stay with ui(plugin-form): ObjectForm drops the split / drawer / modal branches and SplitForm retires; showcase task views re-spelled (ADR-0050 cutover, renderer half of #27155) #27159.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants