Repository navigation
feat(spec)!: retire FormView drawerSide / drawerWidth — no form view opens in a drawer - #29287
Conversation
…opens in a drawer Tombstone both keys on FormViewSchema (ADR-0049 enforce-or-remove), move them to the React ObjectForm block's overlay (the drawer that reads them), add the form-view-drawer-keys-removed D2 conversion, its D3 entry, the RETIRED_KEYS_BY_MAJOR entries, the step-18 rationale fragment, the liveness rows and the pins. Generated artifacts follow in the next commit. Claude-Session: https://claude.ai/code/session_01ADHkmSVA1CYQ5wh8VMGZ5r Co-authored-by: Claude <noreply@anthropic.com>
authorable-surface (two [RETIRED] rows), reference docs, the react-blocks contract (drawerSide / drawerWidth now published from the ObjectForm overlay) and the liveness counts. Claude-Session: https://claude.ai/code/session_01ADHkmSVA1CYQ5wh8VMGZ5r Co-authored-by: Claude <noreply@anthropic.com>
…drawerWidth The reconciliation ledger excuses a tombstoned key automatically and refuses an entry for a key that is no longer authorable. Claude-Session: https://claude.ai/code/session_01ADHkmSVA1CYQ5wh8VMGZ5r Co-authored-by: Claude <noreply@anthropic.com>
…ING, minor on spec) Claude-Session: https://claude.ai/code/session_01ADHkmSVA1CYQ5wh8VMGZ5r Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 84b9f533d051118b740bd20e4be9bacb7aacefea && git checkout 84b9f533d051118b740bd20e4be9bacb7aacefea
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 504ba5108effc72f5b798070e73c8b0a699c63b1 ffc3fe89618b871f7bd587b607da9e42cad6244f && git checkout -B drift-repro 504ba5108effc72f5b798070e73c8b0a699c63b1 && git merge --no-ff ffc3fe89618b871f7bd587b607da9e42cad6244f
node scripts/docs-audit/affected-docs.mjs --json 504ba5108effc72f5b798070e73c8b0a699c63b1
|
… retirement The pending 27155 note said drawerSide / drawerWidth stay declared on the form view; this branch retires both in the same 18.3.0 release. Drop them from the sentence and say they retire in the same release. splitDirection and modalSize are left as written. Deliberate correction: check-empty-changeset exits 1 on a modified pending changeset by design. Claude-Session: https://claude.ai/code/session_01ADHkmSVA1CYQ5wh8VMGZ5r Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Head-sha: 维护者速读
席位意见:可落地,等契约复审。 席位核对 — ACCEPT ·
|
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Contract-review-claim: session_01ADHkmPi25XXKx92Xmg4viS · head 8a8c90a · 2026-11-02T11:38Z |
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: FAIL |
…size-bucket tag, bump skills - 27155's Unchanged bullet: only modalSize stays declared; splitDirection (#27159) and drawerSide / drawerWidth retire from the form view in the same release, and the React ObjectForm block keeps the drawer pair as its own props. Deliberate correction of a pending note: check-empty-changeset exits 1 by design. - The ObjectForm overlay drawerWidth no longer carries the form view's [DEPRECATED → size buckets] tag: the block's drawer has no size bucket. Its description now says what DrawerForm does with the value. react-blocks.md regenerated. - The drawer-keys changeset bumps @objectstack/skills (patch): the regenerated react-blocks.md ships in that package. Claude-Session: https://claude.ai/code/session_01ADHkmSVA1CYQ5wh8VMGZ5r Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Contract-review-claim: session_01ADHkmPi25XXKx92Xmg4viS · head ffc3fe8 · 2026-11-02T12:49Z |
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS
Requested by Steve · project thread
Fixes #29012
Clause-②: no (narrowing)
Metadata-protocol: narrowed
维护者速读
drawerSide/drawerWidth两个键退役,写了就在保存时被拒并给出修法;存量数据读取时自动删掉这两个键。React 页面里ObjectForm组件的同名属性保留不变。type: 'drawer'已在本版本退役),控制台、记录表单页和 Studio 都不读这两个键,作者写了能存能发布却毫无效果,属于“声明了但不生效”。packages/spec/src/ui/view.zod.ts(两个墓碑和提示文案)、packages/spec/src/ui/react-blocks.ts(两个属性改由ObjectForm自己声明)、packages/spec/src/conversions/registry.ts的form-view-drawer-keys-removed。pnpm --filter @objectstack/spec exec vitest run --project repo src/ui/form-view-drawer-keys-retirement.test.ts,应看到 29 个用例全部通过。What changes
FormViewSchema.drawerSide/drawerWidthareretiredKey()tombstones (packages/spec/src/ui/view.zod.ts). Each is refused at every form-view door (a view'sform,formViews.*, a form view item record, a flattened form overlay,defineStackasSTACK_SCHEMA_INVALID/ 422) with a prescription that says why it was inert, what to do instead (navigation.mode/navigation.sizefor a list's record drawer; the ReactObjectFormblock's own props for a page drawer) and theos migrate meta --from 17sentence. TypeScript rejects both at aFormViewposition. The describe that deferred the question to closed spec(ui): FormViewSchema declares drawerSide / drawerWidth and buttons.reset, but no console entry point renders a drawer form or a reset button; implement or retire them (ADR-0049) #27153 is gone.ObjectFormblock keeps both props (packages/spec/src/ui/react-blocks.ts). They leave the block'sdataProps(which readFormViewSchema) and move to its overlay asbindingprops with the same types;drawerSidekeeps its description, anddrawerWidth's drops the form view's[DEPRECATED → size buckets]tag (the block's drawer has no size bucket) and says whatDrawerFormdoes with the value: the drawer's width from the left or right edge, its height from the top or bottom, the [finding] ADR-0082 D4 ratchet: react-blocks<ObjectForm>gains 17 registry-only props after objectui#4648/#4901 — spec has not caught up #9392 rule for a prop only the react tier reads. The overlay entry shares the tombstone's name, soREACT_OVERLAY_SHADOWS.ObjectFormledgers it with that reason. The regeneratedskills/objectstack-ui/references/react-blocks.mdmoves the two rows from the data group to the binding group;validate-react-page-propsreads the overlay, so a React page'sdrawerSide="right"stays known.form-view-drawer-keys-removed(toMajor: 18,retiredFromLoadPath,retiredAfter: '18.2.0',stripKeyson every form payload and the assembledviewItemschannel; never walks page components, so theobject-formpage block keeps its keys). D3 semantic entryui-form-view-drawer-keys-retired.RETIRED_KEYS_BY_MAJOR[18]entriesui/FormView:drawerSideandui/FormView:drawerWidth. Step-18 rationale fragment. Removal-version pin rows.liveness/view.json: both rowsdeadwith the tombstone note (view counts 75/8 → 73/10). Regeneratedauthorable-surface/ui.json(two[RETIRED]rows),content/docs/references/{api/protocol,ui/view}.mdx,liveness/state-counts/view.md, the react-blocks contract..changeset/29012-form-view-drawer-keys-retired.md: BREAKING,minoron@objectstack/specandpatchon@objectstack/skills(the regeneratedskills/objectstack-ui/references/react-blocks.mdships there, as in ui(plugin-form):ObjectFormdrops thesplit/drawer/modalbranches andSplitFormretires; showcasetaskviews re-spelled (ADR-0050 cutover, renderer half of #27155) #27159's changeset), FROM → TO, the one-line fix,registeredADR-0087 marker.Forced test edits
src/ui/form-view-type-layout-retirement.test.ts: the "STATED BOUNDARY:drawerSide,drawerWidthandmodalSizestay declared" pin now holdsmodalSizeonly, and its stored-row case usesmodalSize: 'lg'as the bystander instead ofdrawerSide: 'left'(the new conversion strips that). The header's bookkeeping line points at the new pin.src/conversions/registry.tsform-view-presentation-types-removedfixture: thequickform's bystander istitle: 'Quick'instead ofdrawerSide: 'left', because fixtures must stay disjoint under whole-table replay. Its docblock now says the drawer keys left later in the same release.src/ui/view-metadata-schema.test.ts: the "a FormView key nobody would think to allow-list" probe usesshowStepIndicatorinstead ofdrawerSide.src/system/metadata-form-zod-reconciliation.test.ts: theomitrow forview.drawerWidthis dropped, as the ledger asks for a key that became a tombstone.New pin
src/ui/form-view-drawer-keys-retirement.test.ts(repo project, listed invitest.repo-tests.json): both keys refused at five doors with the prescription's first sentence and the migrate sentence; amodalSizecontrol at each door;defineStackenvelope;RETIRED_KEYS_BY_MAJOR;@ts-expect-errorat the authoring site; the block overlay publishes both and theobject-formpage block still accepts them; the conversion on a stored container and a form view item record, never on a page block, idempotent, off the load path, wired into step 18, with its D3 entry; tree-scoped absence over the five roots spec#test already declares.Why
Triage ruling on the card (comment 6571544001): retire under ADR-0049. Measured at
origin/main104cd1a:DrawerFormisui/packages/plugin-form/src/ObjectFormContainer.tsx, which serves theobject-formblock'sformType='drawer'; a form view'stypecannot bedrawersince spec(view): narrowFormView.typeto layout only (simple | tabbed | wizard) — retiresplit,drawer,modalwith tombstones (ADR-0050 Accepted, #27032 letter A′) #27155.ui/packages/plugin-designer/src/FieldDesigner.tsxcomposes its ownDrawerFormSchemain code. The console form page, the New / Edit dialogs (which readmodalSizeonly) and the record form page never read them.DrawerForm.tsxdeclaresdrawerSide?/drawerWidth?onDrawerFormSchema, and@object-ui/typesObjectFormSchemadeclares its own; neither derives fromFormViewSchema, soui/needs no edit (checklist 4 measured, below).drawerWidth's size-bucket deprecation had no conversion. The only conversion text naming it was the presentation-types docblock saying it stays. This entry deletes it; the prescription namesnavigation.size.examples/: 0 form views (2 hits areObjectForm formType="drawer" drawerSide="right"JSX on React pages, the block, unaffected);skills/: 0 (the generated block contract only). Control leg: the live form-view keysubmitBehaviorreads 8 inexamples/on the same tree. HotCRM fixtures are not in this repository: NOT MEASURED.Tests
All runs went through
os-verify-lock(each VERDICTcommand-exit 0unless stated).@objectstack/spec,@objectstack/lintand the spec repo project ran at0dc8fcc3, whose tree differs from the head8c7bfc30only by the changeset file;ui/plugin-form, the gates and dogfood ran at8c7bfc30.Round 2 re-ran at the head
ffc3fe89, underos-verify-lock:turbo run build --filter='./packages/**'exit 0;@objectstack/spectest Test Files 790 passed (790), Tests 23373 passed;@objectstack/linttest Test Files 189 passed (189), Tests 7830 passed;dispatch-gates --commands112 derived, 112 run, reconciled with--ranas a DERIVED zero, every exit 0 exceptcheck-empty-changeset --base origin/main(exit 1 by design, above). The spec repo project, ui plugin-form and dogfood readings are from8c7bfc30; since then the code delta is one description string inreact-blocks.tsplus its regenerated row, and the rest is changeset prose.pnpm --filter @objectstack/spec typecheck: exit 0 (compiles the new pin's two@ts-expect-errorlines).pnpm --filter @objectstack/spec test(local project): Test Files 789 passed, 1 skipped (790); Tests 23371 passed, 1 skipped, 1 todo.pnpm --filter @objectstack/spec test:repo(repo project, run locally because this diff adds and edits repo-project tests): Test Files 88 passed (88); Tests 1477 passed.@objectstack/lint(validate-react-page-propsreads the block overlay;lint-liveness-propertiesreads the ledger):pnpm --filter @objectstack/lint test: Test Files 189 passed (189); Tests 7825 passed, 5 skipped.ui/packages/plugin-form(run fromui/):pnpm --filter @object-ui/plugin-form test: Test Files 217 passed (217); Tests 2407 passed, 1 skipped. The first run failed 3 files on a prerequisite (@objectstack/clientunbuilt in a fresh container: "Failed to resolve import"); green after building it.ui/type-check: NOT MEASURED locally (prerequisite:@object-ui/plugin-form type-checkneeds the ui workspace's own packages built, TS2307 on@object-ui/typesand siblings). By source: noui/type derivesdrawerSide/drawerWidthfromFormView(DrawerFormSchemaandObjectFormSchemadeclare their own;git grepofui/forFormView[indexes names onlymodalSize,defaultTab,tabPosition,layout,buttons,defaults).Type Check UIon this PR is the reading.packages/metadata: no tombstone or conversion pin there names these keys (git grep drawerSide drawerWidth -- packages/outsidepackages/specis empty), so no run is owed.Ablations (each restored, blob == HEAD and git diff HEAD empty, by scripts/ablation-replace.mjs)
drawerSide: retiredKey(FORM_VIEW_DRAWER_SIDE_RETIRED)replaced by the oldz.enum([...]).optional(). The new pin went red: Tests 7 failed, 22 passed (29) — the five door refusals, thedefineStackenvelope and the@ts-expect-errorcase. Restored.stripKeys(form, ['drawerSide', 'drawerWidth'], emit, path)call replaced by a pass-through.conversions.test.tsfixture replay forform-view-drawer-keys-removedand the pin's two stored-row cases went red: Tests 3 failed, 235 passed (238). Restored. (An earlier attempt used a replacement that threw instead of passing through and reddened every form-view fixture; it was discarded and rerun as the pass-through above.)!and the(narrowing)arm) at8c7bfc30, afterturbo run build --filter='./packages/**'(78/78):pnpm --filter @objectstack/dogfood testunder the lock, VERDICTcommand-exit 0; Test Files 388 passed, 3 skipped (391); Tests 3208 passed, 28 skipped. No failures.Acceptance notes
.changeset/27155-form-view-type-layout-only.md(pending, 18.3.0) had an "Unchanged" bullet sayingsplitDirection,drawerSide,drawerWidthandmodalSizestay declared. All three notes ship in the same 18.3.0 release, so this PR corrects it: onlymodalSizestays declared (the console reads it for its record dialog),splitDirection(ui(plugin-form):ObjectFormdrops thesplit/drawer/modalbranches andSplitFormretires; showcasetaskviews re-spelled (ADR-0050 cutover, renderer half of #27155) #27159) anddrawerSide/drawerWidth(spec(view): FormViewSchema drawerSide / drawerWidth are accepted on a form view and read by no form-view surface; the describe says the question "stays open on #27153", which is closed #29012) retire from the form view in the same release, and the ReactObjectFormblock keeps the drawer pair as its own props. This is a DELIBERATE CORRECTION of a pending release note:check-empty-changesetexits 1 on it by design, andCheck Changesetis not a required context. Do not restore the original sentence and do not addskip-changeset.ui/packages/types/src/objectql.ts(theObjectFormSchemakey taxonomy docblock) still listsdrawerSide/drawerWidthamong the[spec-aligned]FormView keys. They are now the block's own props on both sides; a comment only, outside this card's file surface.docs/qa/platform-checklist/areas/studio-authoring.jsonrows that score the drawer keys as "fail today" now meet a save refusal instead of an inert save; the QA lane re-scores them.skills/objectstack-ui/references/react-blocks.mdis the generator-ownedspec-react-blocksrow: this diff regenerates it fromreact-blocks.tsand does not touchpackages/spec/scripts/.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat8c7bfc30: 112 derived families, all run; reconciled with--ran: "112 derived famil(ies) accounted for — 112 run, 0 NOT-MEASURED (a DERIVED zero)". First pass had six prerequisite exits in the fresh container (check-plugin-teardown-shape --self-testneeded its pinned commit fetched;check:doc-formula-expressions,check:doc-security-posture,check:skill-examples,check:dts-closure,check:dual-build-cjs-loadsneeded built packages); each exit 0 afterturbo run build --filter='./packages/**'(78/78).pnpm --filter @objectstack/spec check:generatedafter regeneration: in the gate list, exit 0 (first pass named react-blocks, docs and liveness counts stale; regenerated withgen:react-blocks,gen:docs,gen:liveness-counts).node scripts/check-adr-0087-registration.mjs --base origin/main: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … registered form-view-drawer-keys-removed, ui-form-view-drawer-keys-retired (new here: both)".node scripts/check-changeset-no-major.mjs --base origin/main --eventon this body: LEVEL AXIS ✓ forno (narrowing)+narrowed, minor.node scripts/check-empty-changeset.mjs --base origin/main: exit 1 by design atffc3fe89(DELIBERATE CORRECTION of the pending 27155 note, see Acceptance notes; the same red isCheck Changeseton CI).--self-testexit 0.node scripts/pm/check-governed-merges.mjs --teston the 20-path list: "0 of 20 path(s) hit the register after 1 generated-artifact lift(s) … NOT governed";react-blocks.mdis a pure regeneration.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses (CI-owned run).main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code