Skip to content

feat(plugin-form,spec)!: ObjectForm lays the form out, the object-form block opens drawer/modal, split retires - #27305

Open
objectstack-fleet[bot] wants to merge 18 commits into
mainfrom
claude/project-thread-04ka71
Open

objectstack-fleet[bot] wants to merge 18 commits into
mainfrom
claude/project-thread-04ka71

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #27159

Clause-②: no (narrowing)
Metadata-protocol: narrowed (the object-form block's formType refuses split; its splitDirection, splitSize, splitResizable and sections[].pane, the object-master-detail-form block's sections[].pane, and FormViewSchema.splitDirection, are tombstones)
Skills-docs: skills/objectstack-ui/references/react-blocks.md regenerated by gen:react-blocks (the React ObjectForm contract drops split and splitDirection); no hand edit under skills/**

维护者速读

What changes

ADR-0050 Decision 4, the renderer half of #27155: a form's formType is its layout, and the container belongs to the caller. On the object-form block, the block itself is that caller.

  • @object-ui/plugin-form. ObjectForm drops its split, drawer and modal arms and keeps the three layouts (and master-detail routing). The drawer and modal arms move unchanged into the new ObjectFormContainer.tsx, which shares ObjectForm's schema preparation through the extracted usePreparedFormSchema hook. The registered object-form renderer sends formType: 'drawer' | 'modal' to ObjectFormContainer and everything else to ObjectForm. SplitForm.tsx is deleted and SplitForm / SplitFormProps / SplitFormSchema leave the exports. The block's published inputs drop split and the three split keys. The section pane reader (SECTION_LAYOUT_KEYS) goes, and the #6237 diagnostic's remedy no longer names split.
  • @objectstack/spec. The object-form block's formType is enumWithRetiredValues(['simple','tabbed','wizard','drawer','modal'], …), so split is refused by name with a prescription. splitDirection, splitSize, splitResizable and the block section's pane are retiredKey() tombstones, and so is FormViewSchema.splitDirection, which lost its last reader with SplitForm. The React contract (react-blocks.ts) drops split and splitDirection. The .describe() strings of drawerSide, drawerWidth, modalSize and modalCloseButton now name the block's drawer and modal containers.
  • Retirement kit, absorbed into spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155's entry. @objectstack/spec is 18.2.0 on main, so 18.3.0 is unreleased and the skill's same-major rule puts this in the existing D2 conversion form-view-presentation-types-removed. It now strips splitDirection from every form payload and has a new convertBlock over object-form page blocks (regions, slots and container nesting): it deletes formType: 'split', strips the three keys and every section pane, and leaves drawer / modal alone. The D3 entry ui-form-view-type-layout-only is widened to match. Four new RETIRED_KEYS_BY_MAJOR entry files cover ui/FormView:splitDirection and ui/ObjectFormProps:{splitDirection,splitSize,splitResizable}. Also updated: the liveness rows (splitDirection is now dead/REMOVED; the drawerSide / drawerWidth notes cite the container), the removal-version pins, and the regenerated references, authorable surface and state counts.
  • @object-ui/types. ObjectFormSchema.formType drops split. The three split keys stay declared, but as the spec's tombstones by reference (the defaultFilters precedent), so the parity census still pairs them. The section pane member is removed.
  • Console and Studio. The page-block inspector no longer offers Split (block-config.ts, and the studio string engine.inspector.pageBlock.option.formType.split is removed from en and zh). form-preview.ts and RecordFormPage.tsx drop the dead 'split' literals and the split keys. FormPage.tsx's FORM_SPEC_TYPES is the three layouts.
  • Docs. The plugin-form README and content/docs/frontend/plugins/plugin-form.mdx lose the SplitForm and pane passages, and the page gains "Where an object-form block opens". The ui/content/docs pages this PR first edited (plugins/plugin-form.mdx, api/schema-reference.md) were deleted on main by docs(ui-merge): fold objectui.org into objectstack docs, K4 — retire the site, host 301s, links #27329, so the merge takes the deletion. The spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155 changeset is left as written (a changeset on the base cannot change); this PR's changeset carries the BREAKING FROM → TO and a "Supersedes" bullet correcting spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155's splitDirection line. The object-master-detail-form block's sections[].pane is tombstoned too (the shared section shape) and the D2 conversion strips it there (contract review 6485040701, should-fix 1).

Tests

  • New: ui/packages/plugin-form/src/__tests__/objectFormContainer-27159.test.tsx. Through SchemaRenderer, a drawer / modal block opens a dialog that holds the form, and a tabbed block renders inline. A drawer / modal node handed straight to ObjectForm renders inline, so the container is the caller's (this fails on main, where ObjectForm opened the drawer). The [P1] Finish ADR-0021 analytics migration (chart view / dashboard+Studio / report) #1890 pin: a tabbed layout inside the modal container still draws its tabs. CONTROL: a layout handed to the container falls back to the inline form.
  • Spec: form-view-type-layout-retirement.test.ts gains an object-form block describe. It pins the vocabulary, the split refusal with prescription, a CONTROL that drawer / modal parse with their keys, the three key tombstones on every formType, the section pane tombstone, the RETIRED_KEYS_BY_MAJOR rows, a tsc pin and a stored-page conversion. The tree walk now also judges any formType: 'split'. splitDirection joins the form-view tombstone rows and the stored-view conversion pin.
  • About 20 plugin-form suites that mounted drawer / modal through ObjectForm now mount ObjectFormContainer, the block's own route. Split rows are removed from every arms/layout matrix. The sectionFaultCarry site census moves 5 → 2 + 2.

Acceptance notes

Verification

All runs below are local, on this branch. The spec and ui suites and dogfood ran on the merged head 6f5071c3, with 22 commits of main merged and the reference docs regenerated.

  • node scripts/pm/dispatch-gates.mjs --commands derived 122 commands, and all 122 exit 0. --ran reconciles 122 derived, 122 run, 0 unrun. Three of them first refused on a missing prerequisite and passed after it was provided: check-plugin-teardown-shape --self-test needed the pinned fixture fetched (shallow clone), check:skill-examples needed a client-react build, and check:dual-build-cjs-loads needed every dist. One gate found a real problem, doc-version-claims, because the docs named a version literal; the literal is removed.
  • pnpm --filter @objectstack/spec test: 749 files and 22377 tests pass. check:generated is current.
  • ui vitest over plugin-form, types, i18n, the console FormPage suites, the app-shell previews and RecordFormPage: 883 files and 16460 tests pass (14 skipped).
  • type-check passes for @object-ui/plugin-form, types, app-shell, console and i18n, and pnpm --dir ui run type-check:scripts passes.
  • node scripts/check-sdui-manifest.mjs passes after gen-sdui-manifest-node.mjs regenerated the manifest; the diff is the block's formType enum and the three removed inputs. node scripts/check-platform-checklist.mjs --changed-from origin/main passes.
  • Dogfood battery (packages/qa/dogfood, pnpm test) after rebuilding the closure: 348 files and 2806 tests pass. The CI Dogfood Regression Gate does not run on pull requests, so this local run is the evidence for that gate.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JQwxbWHWPRD5rZXeY69YvL

…m block opens drawer/modal; split retires

ADR-0050 Decision 4, the renderer half of the form-view narrowing.
ObjectForm keeps the simple, tabbed and wizard layouts; its drawer and
modal arms move unchanged into ObjectFormContainer, which the registered
object-form renderer uses for formType drawer/modal. SplitForm and the
split-only keys retire: the object-form block refuses formType 'split'
and tombstones splitDirection, splitSize, splitResizable and section
pane, and FormView.splitDirection is tombstoned too. The protocol-18
form-view-presentation-types-removed conversion now also reaches
object-form page blocks and splitDirection.

Claude-Session: https://claude.ai/code/session_01JQwxbWHWPRD5rZXeY69YvL
Co-authored-by: Claude <noreply@anthropic.com>
Reverts the hold commit now the PR is open: the React ObjectForm contract
regenerated by gen:react-blocks.

Claude-Session: https://claude.ai/code/session_01JQwxbWHWPRD5rZXeY69YvL
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl 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 29 documentable anchor(s). ⚠️ 7 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/state-counts/view.md, packages/spec/liveness/view.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

28 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 7614f8f05ca626b13d1e75320fe4f04d9b404f4e.

⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 7 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/state-counts/view.md, packages/spec/liveness/view.json, …) — pages documenting those are invisible to this run
  • 8 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 58 of 210 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 — 166 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 7614f8f05ca626b13d1e75320fe4f04d9b404f4e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 97da176cfe92032920826c2a63362d72c6ebbb60 — the merge of head 166b8dd90f9367a58f96016ef872492b9e496b64 into base 7614f8f05ca626b13d1e75320fe4f04d9b404f4e, 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 97da176cfe92032920826c2a63362d72c6ebbb60 && git checkout 97da176cfe92032920826c2a63362d72c6ebbb60
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7614f8f05ca626b13d1e75320fe4f04d9b404f4e 166b8dd90f9367a58f96016ef872492b9e496b64 && git checkout -B drift-repro 7614f8f05ca626b13d1e75320fe4f04d9b404f4e && git merge --no-ff 166b8dd90f9367a58f96016ef872492b9e496b64

node scripts/docs-audit/affected-docs.mjs --json 7614f8f05ca626b13d1e75320fe4f04d9b404f4e

⚠️ 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 7614f8f05ca626b13d1e75320fe4f04d9b404f4e → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat note · contract review owed · seat domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-28T15:57Z

This PR is Tier H. Its committed tree touches the published schema (packages/spec/src/ui/component.zod.ts, view.zod.ts, react-blocks.ts, conversions/registry.ts, four new RETIRED_KEYS_BY_MAJOR entries) and one governed path (skills/objectstack-ui/references/react-blocks.md, regenerated by gen:react-blocks, no hand edit). The body also declares Clause-②: no (narrowing). That is a CONTRACT_REVIEW_TIER review. The seat runs at TIER_DEFAULT because the ceiling model has been unavailable to the fleet since 2026-10-27, so it hands the review to triage. The PR stays draft on purpose: relay pr_ready on a Tier H diff requests the approvers.

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (ceiling model out for the fleet since 2026-10-27, seat runs at TIER_DEFAULT, 2026-10-28T15:57Z) · head 235fc7d74ff5e783bdfb45e4df3739aa452e5588

Inputs for the reviewer:

No reviewer is requested, and the PR stays draft until a PASS is on file and the seat's ACCEPT is posted (#26617).

The object-form block's formType row drops split and its input count
is 37, matching sdui.manifest.json.

Claude-Session: https://claude.ai/code/session_01JQwxbWHWPRD5rZXeY69YvL
Co-authored-by: Claude <noreply@anthropic.com>
split left the object-form block's formType with ADR-0050, so the
inline-sections control no longer runs it as a sectioned variant.

Claude-Session: https://claude.ai/code/session_01JQwxbWHWPRD5rZXeY69YvL
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat note · head moved after hand-off · seat domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-28T17:33Z

The head moved after the hand-off in 6473900402 (235fc7d7). Here are the three commits since then, as the seat read them:

  • 9a742647: changeset only.
  • 1f16817a: regenerated the SDUI component reference from the manifest. This is generated docs.
  • f282e8a9: test only. ui/apps/console/src/__tests__/objectname-neither-hint-11605.test.tsx drops split from its sectioned-variant list, since a retired split now falls through to the simple arm.

None of the three changes code or schema.

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (ceiling model out for the fleet since 2026-10-27, seat runs at TIER_DEFAULT, 2026-10-28T17:33Z) · head f282e8a966d52be3f95f34d415d2734501846076

The inputs in 6473900402 stand unchanged. The PR stays draft, and no reviewer has been requested.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head f282e8a966d52be3f95f34d415d2734501846076 · 2026-10-29T00:49Z

Triage seat, taking the Contract-review-handoff: triage … line on this head (#27062 (a)). The review runs at CONTRACT_REVIEW_TIER. If the lane claims or posts a record on this head first, triage withdraws.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f282e8a966d52be3f95f34d415d2734501846076
Local-runs: none
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__splitDirection.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitDirection.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitResizable.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitSize.ts, packages/spec/src/migrations/entries/semantic/18.ui-form-view-type-layout-only.ts, packages/spec/src/ui/component.zod.ts, packages/spec/src/ui/react-blocks.ts, packages/spec/src/ui/view.zod.ts, skills/objectstack-ui/references/react-blocks.md + packages/spec/src/shared/retired-key.ts (retiredKey, enumWithRetiredValues), packages/spec/src/conversions/walk.ts (mapPageComponents), packages/spec/scripts/build-react-blocks-contract.ts (the generator of the skill reference), packages/spec/src/migrations/registry.ts.template (retired-keys concatenation), packages/spec/liveness/view.json, packages/spec/authorable-surface/ui.json, docs/adr/0050-form-layout-vs-presentation.md, ui/packages/plugin-form/src/ObjectFormContainer.tsx, ui/packages/plugin-form/src/index.tsx (the object-form renderer routing the tombstone comments cite), ui/packages/plugin-form/src/ObjectForm.tsx, ui/packages/components/src/renderers/layout/react-page.tsx (how a kind:react page resolves the ObjectForm tag), ui/packages/types/src/objectql.ts, ui/packages/types/src/zod/objectql.zod.ts (first mirror consumer), .changeset/27159-object-form-split-retired.md, .changeset/27155-form-view-type-layout-only.md

Inputs read: card #27159 body and all 7 comments (scope notes 6465248799 / 6468180697, unblock 6468789520, order 6469180448, claim 6469269349, amendment 6469786533, os-dev report 6473832761); ruling 6464210455 on #27032 (gh api GET); PR #27305 body, 102-file list, net diff vs main, 4 PR comments; 65 check-runs on f282e8a9: 0 failed, 0 pending (re-fetched 2026-10-29; 56 success, 9 skipped — opt-in harnesses and matrix shards). The check-runs are the gate verdicts; nothing was built or run.

① Derived judgments

Accept-set changes the diff implies, each judged against the live tree:

  1. ObjectFormPropsSchema.formType (component.zod.ts L6936): z.enum([6 values]) → enumWithRetiredValues(['simple','tabbed','wizard','drawer','modal'], { split: … }). RIGHT. enumWithRetiredValues (retired-key.ts L402) refuses construction if the retired member is still in values, so the narrowing is enforced, not declared; the split prescription names the version (18.3.0), the replacement (tabbed / delete for simple), the keys to delete, object-master-detail-form for child records, and closes with the pinned os migrate meta --from 17 sentence. drawer / modal stay — the amendment's measured decision (showcase React pages author them; no other door to a built-in overlay form); the block renderer (index.tsx L268) is the caller that supplies the container via ObjectFormContainer. The two dimensions sharing one key on the block is acknowledged in the schema docblock; see ④.
  2. ObjectFormPropsSchema.splitDirection / splitSize / splitResizable → retiredKey() tombstones. RIGHT. retiredKey is z.never({ error }).optional() so absence passes and every authored value is refused by name; z.input types the key never (the tsc pin in form-view-type-layout-retirement.test.ts). The four describes (drawerSide, drawerWidth, modalSize, modalCloseButton) now name the block's containers — honest against ObjectFormContainer.tsx, which forwards exactly those four to DrawerForm / ModalForm.
  3. objectFormSection().pane → retiredKey(). RIGHT for the object-form block. NOTE: objectFormSection() is one instance shared by ObjectFormPropsSchema.sections (L6976) AND ObjectMasterDetailFormPropsSchema.sections (L7257), so the tombstone also reaches object-master-detail-form sections, where pane was accepted (inert) through 18.2.0. The D2 convertBlock is scoped to component.type === 'object-form' and does not strip pane from a master-detail block, and the prescription names "object-form property sections[].pane" on that block too. Reach gap, judged should-fix in ④.
  4. FormViewSchema.splitDirection (view.zod.ts) → retiredKey(FORM_VIEW_SPLIT_DIRECTION_RETIRED). RIGHT. Its last reader was SplitForm.tsx behind the React block's formType='split'; both are gone from the head tree (git grep SplitForm over non-test, non-changelog code finds only comments and ADR prose). drawerSide / drawerWidth / modalSize stay declared with a live reader: ObjectFormContainer.tsx → DrawerForm.tsx reads the first two; the console record dialog reads modalSize. The 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 question is left open as measured — correct.
  5. REACT_BLOCKS[object-form].dataProps drops splitDirection; formType interaction type drops split. RIGHT. react-page.tsx buildComponentScope() injects every public registry block as a SchemaRenderer wrapper, so a kind:react ObjectForm tag with formType="drawer" resolves to the object-form renderer and the container route — the skill header's "(b) Overlays: render an ObjectForm tag with formType drawer or modal, open, onOpenChange" still holds on main + this diff.
  6. skills/objectstack-ui/references/react-blocks.md: generator output (build-react-blocks-contract.ts → OUT_MD), no hand edit — the two truncated cells measure exactly 160 chars (clip(s, 160)), and check:react-blocks ran green in Lint · repo & tooling gates (lint.yml L5468). RIGHT.
  7. Four new RETIRED_KEYS_BY_MAJOR entries under entries/retired-keys/18.* — picked up by the generated registry (registry.ts.template concatenates the directory; registry.ts is gitignored, so no template change is owed). RIGHT; the exact def keys match the authorable-surface/ui.json [RETIRED] rows. The block section pane has no entry because the section is an anonymous shape, consistent with ui/FormSection:pane from spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155 covering the def-keyed row.
  8. D2 form-view-presentation-types-removed widened in place: strips splitDirection from every form payload; new convertBlock over mapPageComponents (walk.ts L403: regions, slots, container nesting, source-authored pages included) scoped to object-form. RIGHT: deletes formType: 'split' (lands on the simple default — the conversion invents no layout, matching spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155), strips the three keys and every sections[].pane, leaves drawer / modal and non-form components alone; fixture expectedNotices: 16 reconciles (6 + 6 + 4). Absorbed under the same-major rule: @objectstack/spec is 18.2.0 at the merge base, on origin/main and on the head, so 18.3.0 is unreleased — measured, as the amendment required.
  9. D3 ui-form-view-type-layout-only widened (surface, replacement, reason, acceptanceCriteria name the block; surface keeps the no-backtick rule). RIGHT.
  10. Liveness view.json: splitDirection → dead / REMOVED with the retiredKey-stays-in-shape precedent; drawerSide / drawerWidth notes cite ObjectFormContainer.tsx → DrawerForm.tsx; state-counts 76/7 → 75/8. RIGHT; Spec property liveness green.
  11. Public-surface (lane seat's remainder, read only to judge the spec): SplitForm / SplitFormProps / SplitFormSchema leave @object-ui/plugin-form; ObjectFormContainer is internal (imported by index.tsx, not exported); @object-ui/types ObjectFormSchema.formType drops split, the three split keys become tombstones by reference (SpecObjectFormPropsSchema.shape.*), pane leaves ObjectFormSection. All RIGHT against the spec rows.

② Semver level

.changeset/27159-object-form-split-retired.md: @objectstack/spec: minor, @object-ui/plugin-form: minor, @object-ui/types: minor, @objectstack/skills: patch (its files ships skills/**), @object-ui/i18n: patch, @object-ui/console: patch. Matches what the diff publishes and the house rule (ruling 6464210455 A′: a (narrowing) arm is BREAKING and ships as a minor with its migration; AGENTS.md step 3). The body carries the BREAKING banner, FROM → TO for the block and the form view, the one-line fix, the parse message, and the plugin-form export FROM → TO. Clause-②: no (narrowing) and Metadata-protocol: narrowed (…) are line-initial in both the PR body and the changeset body; Skills-docs: names the regenerated reference. ADR-0087 marker: not-required (already-registered form-view-presentation-types-removed, ui-form-view-type-layout-only) — both ids exist at the merge base (landed with #27229) and are widened, not added, so the disposition is the honest one by the gate's own rule (check-adr-0087-registration.mjs L143); Check Changeset green.

Clause-②: no (narrowing) — correct for a tombstoned key set and a refused enum value; no new authorable key is added.

One account-vs-tree mismatch: the PR body says "The #27155 changeset's stale 'Unchanged' line is corrected". It is not — .changeset/27155-form-view-type-layout-only.md L21 is byte-identical at the merge base and the head, and commit 9a742647 is titled "leave the #27155 note as written". The 27159 changeset instead carries a "Supersedes one line of the form view type entry (#27155)" bullet, so the CHANGELOG stays self-consistent within the release. Should-fix on the PR body (or edit the #27155 note), not a semver fault.

③ Boundary flags

open_questions: [] in the os-dev report. Deviations, each answered:

  • Tier H opened by the seat-1 route (hold commit 73eb8292, revert 235fc7d7, requested_reviewers empty): procedural; Governed Surface Queue Guard and PR-body guards green on the head. Answered.
  • "PR body footer lacks the house session-URL footer": the body on this head ends with the Generated-with-Claude-Code line and the session URL. Resolved.
  • "Model-tier reading: skills/** derives MANDATORY, differs from the container's tier": answered by this record — the governed path is reviewed at CONTRACT_REVIEW_TIER by the triage seat under the handoff line (6473900402 / 6476091523) and claim 6484754846.
  • better-sqlite3 install workaround: environment only; nothing in the tree.
  • @object-ui/types keeps the split keys as spec tombstones by reference (the defaultFilters precedent): judged RIGHT — the mirror parity census still pairs them, the input type is never, and the describe reads [REMOVED] ….
  • Changeset bumps for skills / i18n / console: RIGHT (② above). The "27155 Unchanged line corrected" claim in the same deviation is contradicted by the tree (② above).
  • Merged origin/main (22 commits) and regenerated protocol.mdx: generated output, not reviewed; check:generated family green.
  • on_27153: drawerSide / drawerWidth keep a live reader through ObjectFormContainer.tsx → DrawerForm.tsx — verified in the head tree; leaving 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 open is right.
  • Out-of-scope findings (stale SplitForm comments in four files; master-detail drawer / modal prescriptions still say "portal dialog"): noted, not filed — acceptable; the master-detail split prescription WAS re-worded in this diff (component.zod.ts L7081–7088), which is right since it pointed authors at object-form's split.
  • Order constraint "keep spec hunks to the object-form block and the three describes": held (the FormViewSchema.splitDirection tombstone and drawerSide / drawerWidth / modalSize describes are the amendment's (a) widening). No breach escalated.

④ Design judgment

For each hits file, against the code it cites and the scenario it is accepted against (ADR-0050 Decision 4 via ruling 6464210455 A′, scope notes 1–2, amendment 6469786533; first consumers read in the live tree):

  • packages/spec/src/ui/component.zod.ts — HOLDS. Refusals are enforced by construction (enumWithRetiredValues throws if split were still live; retiredKey accepts only absence). First consumers: ObjectFormRenderer (index.tsx) routes drawer / modal to ObjectFormContainer and everything else to ObjectForm; the registered inputs enum matches the schema's five values; @object-ui/types mirrors by reference. Break attempt 1 — a stored object-form block authored formType: 'split' with pane sections and the three keys: converted on read to a simple block with clean sections (fixture + stored-page pin), then parses. Break attempt 2 — drawer + modal with their keys on every layout: CONTROL passes. Break attempt 3 — a stored object-master-detail-form block whose section carries pane (accepted through 18.2.0 via the shared objectFormSection()): now refused at parse, convertBlock does not reach it (scoped to type === 'object-form'), and the prescription names object-form. SHOULD-FIX: extend the pane strip of convertBlock to object-master-detail-form components (two lines in the mapPageComponents callback) and word the pane prescription for both rows. Not a must-fix: the key was inert on that block (the master-detail renderer never read pane; its formType never admitted split), the describe advertised it as "the split form's panel", and the refusal carries an actionable one-line fix. Design note, not a finding: the block's formType deliberately keeps two dimensions in one key (the docblock says so); a drawer holding a tabbed form stays inexpressible on the block because contentLayout reaches the modal only. This is the amendment's accepted boundary, follows Decision 2's "the caller places the form" (the block is the caller), and a separate container key would be an added widening owed its own card.
  • packages/spec/src/ui/view.zod.ts — HOLDS. splitDirection had no reader once SplitForm.tsx left; the tombstone's prescription matches spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155's family wording; drawerSide / drawerWidth / modalSize describes are true against ObjectFormContainer.tsx and the console dialog. Break attempt — ViewMetadataSchema vocabulary derived from members: a tombstoned member still contributes vocabulary but refuses a value, so the test's switch to drawerSide is the right probe.
  • packages/spec/src/ui/react-blocks.ts — HOLDS. The React contract takes FormViewSchema as data-prop source; splitDirection leaves the allow-list; formType type and description match what react-page.tsx actually renders (SchemaRenderer → object-form renderer → container). contentLayout is a live block key (L7001).
  • skills/objectstack-ui/references/react-blocks.md — HOLDS against the spec it teaches: a byte-faithful regeneration (160-char clip, check:react-blocks green), formType row and the three re-worded data rows match react-blocks.ts and the describes; the header's overlay guidance remains true on main + this diff.
  • packages/spec/src/conversions/registry.ts — HOLDS. Reach: mapPageComponents (regions, slots, container nesting, source-authored pages) ∘ mapViewPayloads ∘ assembled items; order preserves spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155 behaviour; lossless deletes only; fixture notices reconcile; retiredAfter: '18.2.0' consistent with the 18.3.0 prescriptions and the removal-version pin. The one gap is the master-detail pane above.
  • packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__splitDirection.ts, 18.ui__ObjectFormProps__splitDirection.ts, 18.ui__ObjectFormProps__splitResizable.ts, 18.ui__ObjectFormProps__splitSize.ts — HOLD. Exact def keys (ui/FormView:…, ui/ObjectFormProps:…) match the [RETIRED] rows in authorable-surface/ui.json; the retired-keys gate (build-schemas-check-mode.retired-keys) is green; file headers cite D2 and D3 correctly.
  • packages/spec/src/migrations/entries/semantic/18.ui-form-view-type-layout-only.ts — HOLDS. Surface, replacement and acceptance criteria now describe both doors; surface stays backtick-free for the upgrade guide.

Findings outside hits (the lane seat's ACCEPT; read to judge the spec rows, not reviewed): (a) ui/packages/plugin-form/src/index.tsx L299 registered-input description, and therefore the generated sdui.manifest.json, tell a split author to "use tabbed, or subforms" — ObjectFormPropsSchema declares no subforms key (strictObject refuses it); the spec's own prescription correctly says object-master-detail-form. (b) content/docs/frontend/plugins/plugin-form.mdx L77 / L106 still list SplitForm and SplitFormSchema on the plugin-form export surface. (c) docs/qa/platform-checklist/areas/studio-authoring.json L3784 still says splitDirection renders. Should-fix for the lane seat or a follow-up.

Must-fix: none. Should-fix: (1) convertBlock pane strip and prescription wording for object-master-detail-form sections; (2) the PR body's "#27155 changeset corrected" line vs. the tree; (3)(a)–(c) above.

Implemented-by: claude/project-thread-04ka71
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: PASS


Generated by Claude Code

…ction pane

object-master-detail-form shares the object-form section shape, so its
sections[].pane parsed through 18.2.0 and is a tombstone now. A stored
master-detail page would have failed at parse after the upgrade.
convertBlock's page walk now strips sections[].pane on that block too
(fixture row, expectedNotices 17), the D3 entry names it, and the pane
prescription is worded for both blocks.

Claude-Session: https://claude.ai/code/session_01JQwxbWHWPRD5rZXeY69YvL
Co-authored-by: Claude <noreply@anthropic.com>
…ut; drop SplitForm from the export list

The registered input's prescription now matches the spec's; the
manifest and component reference are regenerated. The frontend
plugin-form page no longer lists SplitForm / SplitFormSchema, and the
platform checklist records the split keys as refused at parse.

Claude-Session: https://claude.ai/code/session_01JQwxbWHWPRD5rZXeY69YvL
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat note · delta review owed · seat domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-29T02:03Z

The seat took triage's should-fixes from 6485040701 (PASS on f282e8a9) as a patch round before landing. 18.3.0 is unreleased, so should-fix 1 is an upgrade hazard and not a follow-up.

These are the commits since f282e8a9, as the seat read them:

  • ee84fa2b, code, owes the delta read. convertBlock's page walk also strips sections[].pane on object-master-detail-form components and touches nothing else on that block. A fixture row is added for it (expectedNotices 16 → 17). The pane tombstone prescription now names both blocks. The master-detail sections describe drops pane, and the D3 entry names the block. New pins: the tombstone on both blocks with a CONTROL, a stored master-detail page conversion, and a removal-version refusal row.
  • 7cfa968d, prose and generated only.
    • 3(a): the object-form registered input now names object-master-detail-form instead of subforms, and sdui.manifest.json and the reference are regenerated.
    • 3(b): plugin-form.mdx exports.
    • 3(c): the studio-authoring.json splitDirection row. The claim was amended to cover it.
  • 1e59a2a3, changeset only: names the master-detail pane.
  • Should-fix 2 is fixed in the PR body by a seat relay edit. Its Docs line no longer claims the spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155 changeset changed, and its Metadata-protocol: line names the master-detail pane.

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (ceiling model out for the fleet since 2026-10-27, seat runs at TIER_DEFAULT, 2026-10-29T02:03Z) · head 1e59a2a3e380e6a521ea32c091578d98b1cdc516

The PR stays draft, and no reviewer is requested.

…ter merging main

Claude-Session: https://claude.ai/code/session_01JQwxbWHWPRD5rZXeY69YvL
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat note · head moved after hand-off · seat domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-29T02:14Z

The head moved after the hand-off in 6485852047 (1e59a2a3). The PR was dirty against main, so CI never started on that head. Two commits were added:

  • A merge of origin/main. The only conflict was the generated scripts/sdui-manifest.record.json.
  • A regeneration from the merged tree: the manifest record, the docs/ui/components pages, the view and protocol references, and the react-blocks skills reference. Every diff in it is main's own change (buttons.reset, list-view options). The os-regen hook confirmed all artifacts are current.

Neither commit changes this PR's code. The code owed a delta read is still only ee84fa2b.

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (ceiling model out for the fleet since 2026-10-27, seat runs at TIER_DEFAULT, 2026-10-29T02:14Z) · head bf2acff4aa6a37d2b7c90cef79ef9846b90a5ec7

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head bf2acff4aa6a37d2b7c90cef79ef9846b90a5ec7 · 2026-10-29T02:53Z

Triage seat, taking the Contract-review-handoff: triage … line on this head (#27062 (a)); a delta re-review from triage's PASS on f282e8a9. The review runs at CONTRACT_REVIEW_TIER. If the lane claims or posts a record on this head first, triage withdraws.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: bf2acff4aa6a37d2b7c90cef79ef9846b90a5ec7
Local-runs: none
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__splitDirection.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitDirection.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitResizable.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitSize.ts, packages/spec/src/migrations/entries/semantic/18.ui-form-view-type-layout-only.ts, packages/spec/src/ui/component.zod.ts, packages/spec/src/ui/react-blocks.ts, packages/spec/src/ui/view.zod.ts, skills/objectstack-ui/references/react-blocks.md + packages/spec/src/shared/retired-key.ts (retiredKey L193, enumWithRetiredValues L402), packages/spec/src/conversions/walk.ts (mapPageComponents L405), packages/spec/scripts/build-react-blocks-contract.ts (the generator of the skill reference, clip(s, 160)), packages/spec/src/shared/retired-key-removal-version.pin.test.ts (the JOIN rows the tombstones are pinned by), packages/spec/src/ui/form-view-type-layout-retirement.test.ts, packages/spec/liveness/view.json, packages/spec/authorable-surface/ui.json, ui/packages/plugin-form/src/ObjectFormContainer.tsx, ui/packages/plugin-form/src/index.tsx (the object-form renderer routing and registered inputs the tombstone comments cite), ui/packages/plugin-form/src/MasterDetailForm.tsx (the object-master-detail-form reader of sections), ui/packages/types/src/form.ts, .changeset/27159-object-form-split-retired.md, .changeset/27155-form-view-type-layout-only.md

Inputs read: card #27159 body and all 8 comments (scope notes 6465248799 / 6468180697, unblock 6468789520, order 6469180448, claim 6469269349, amendments 6469786533 and the 2026-10-29T02:02Z surface widening, os-dev report 6473832761); ruling 6464210455 on #27032 (gh api GET); PR #27305 body (as edited by the seat relay), 104-file list, net diff vs main at the merge base 74801d190f (its file list equals git diff --name-only origin/main...prr-27305), all 8 PR comments; 65 check-runs on bf2acff4aa: 58 success, 7 skipped (opt-in harnesses, matrix placeholders and the schedule gate), 0 failed, 0 pending. The check-runs are the gate verdicts; nothing was built or run. This is a re-review on a moved head: triage's prior record 6485040701 is a PASS on f282e8a9; the delta is five commits, judged one by one below.

Delta since f282e8a9, commit by commit

  • ee84fa2b (code — the one commit that owed a delta read). registry.ts: the mapPageComponents callback of form-view-presentation-types-removed now admits object-master-detail-form and routes it to stripSectionPanes(properties, 'sections', …) only, never to convertBlock (that block had no split member and no split keys — its formType is z.enum(['simple','tabbed']) with the MASTER_DETAIL_FORM_TYPE_RETIRED map); identity is kept when no pane is present (next === properties). Fixture: one master-detail row added on both sides, expectedNotices 16 → 17 (6 + 1 + 6 + 4 reconciles). surface and summary name the block. component.zod.ts: OBJECT_FORM_SECTION_PANE_RETIRED re-worded for both blocks; the buildObjectFormSection docblock no longer calls pane "the split form"; the master-detail sections describe drops pane? from both arms. D3 entry: surface, reason and acceptanceCriteria name the master-detail pane. Tests: the pane tombstone is now an it.each over both schemas with the prescription prefix, the migrate sentence and a CONTROL; a stored master-detail page conversion pin (pane goes, details / formType / sibling sections untouched, identity kept on the clean block, refused before / parses after); the removal-version JOIN gains an object-master-detail-form sections[].pane refusal row under form-view-presentation-types-removed. content/docs/references/ui/component.mdx regenerated for the two describe changes. Resolves prior should-fix 1 exactly.
  • 7cfa968d (prose and generated). index.tsx L299 registered-input description: "use tabbed, or subforms" → "use tabbed (one tab per section), or an object-master-detail-form block" (3(a), exactly; sdui.manifest.json and its record regenerated to match). content/docs/frontend/plugins/plugin-form.mdx L77 / L106 drop SplitForm / SplitFormSchema (3(b), exactly; git grep SplitForm over that page finds nothing). docs/qa/platform-checklist/areas/studio-authoring.json view.form row now says splitDirection / splitSize / splitResizable → refused at parse (retired in @objectstack/spec 18.3.0 …) (3(c), exactly; covered by the 02:02Z claim amendment).
  • 1e59a2a3 (changeset only): the Metadata-protocol: line, the block FROM → TO bullet and the one-line-fix bullet name the master-detail sections[].pane.
  • 83af1ffeb0 (merge of origin/main 74801d190f). Recomputed with git merge-tree --write-tree 1e59a2a3e3 74801d190f → tree 46da9c52, one conflict (scripts/sdui-manifest.record.json, the generatedAt / sha256 pair). The committed merge differs from the clean merge in SIX files, all generated: content/docs/references/api/protocol.mdx, content/docs/references/ui/view.mdx, content/docs/ui/components/index.mdx, content/docs/ui/components/plugins.mdx, skills/objectstack-ui/references/react-blocks.md and the record. In each the merge commit carried main's PRE-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 / PRE-feat(spec,types,plugin-list,app-shell)!: retire the list view's options bag; stored bags fold onto the top-level blocks, and the renderers read the blocks alone #27206 output (buttons.reset live again, the list-view options row back) — a stale regeneration committed into the merge, not a hand edit. No hits file other than the generated skill reference, and no source file, differs from the clean merge: component.zod.ts, react-blocks.ts, view.zod.ts, registry.ts, ObjectForm.tsx and the rest auto-merged byte-identically.
  • bf2acff4aa (regeneration): reverts exactly those six files to the clean-merge bytes. git diff 46da9c52 bf2acff4aa is ONE hunk — the record's resolved sha256 line. So the net head IS the clean merge plus a resolved conflict; the stale intermediate state never reaches main. check:react-blocks and check:generated (Lint · repo & tooling gates, green on this head) confirm every generated artifact is current.

Prior should-fixes: (1) resolved exactly (ee84fa2b); (2) resolved exactly — the PR body's Docs bullet now says "The #27155 changeset is left as written (a changeset on the base cannot change)", and .changeset/27155-form-view-type-layout-only.md is byte-identical at the merge base and the head, matching the body; (3)(a)–(c) resolved exactly (7cfa968d). The seat's rework note 6485852047 / 6486021665 is accurate against the tree in every line, including "the only conflict was the record" — true for the merge-tree run; what it does not say is that the merge commit also carried stale regenerated output, which the next commit undid.

① Derived judgments

Accept-set changes the diff implies, each judged against the live tree on this head:

  1. ObjectFormPropsSchema.formType (component.zod.ts L6942): z.enum([6 values]) → enumWithRetiredValues(['simple','tabbed','wizard','drawer','modal'], OBJECT_FORM_TYPE_RETIRED). RIGHT. enumWithRetiredValues (retired-key.ts L402) refuses construction if the retired member is still live, so the narrowing is enforced by construction; the split prescription names the version (18.3.0), the replacement (tabbed / delete for simple), the keys to delete, object-master-detail-form for child records, and the pinned os migrate meta --from 17 sentence. drawer / modal stay — the amendment's measured decision; the block renderer (index.tsx L268 isObjectFormContainerType → ObjectFormContainer) is the caller that supplies the container. The docblock states the two-dimensions-in-one-key choice.
  2. ObjectFormPropsSchema.splitDirection / splitSize / splitResizable → retiredKey() tombstones (L6983–6988). RIGHT. retiredKey is z.never({ error }).optional(): absence passes, every authored value is refused by name, z.input types the key never. The four describes (drawerSide, drawerWidth, modalSize, modalCloseButton) name the block's containers; ObjectFormContainer.tsx L91–92 forwards drawerSide / drawerWidth to DrawerForm and the modal arm forwards the modal pair.
  3. objectFormSection().pane → retiredKey(OBJECT_FORM_SECTION_PANE_RETIRED) (L6886). RIGHT, and on THIS head right for both consumers: objectFormSection() is the one instance shared by ObjectFormPropsSchema.sections (L6981) and ObjectMasterDetailFormPropsSchema.sections (L7262), the prescription names both blocks, the D2 conversion strips pane on both, and the removal-version JOIN and the retirement test pin both. The prior reach gap is closed. MasterDetailForm.tsx hands sections straight to the parent ObjectForm (L1984) and reads only fields off them (L723–728) — no renderer read pane on that block, so the strip is lossless there too.
  4. FormViewSchema.splitDirection (view.zod.ts L4677) → retiredKey(FORM_VIEW_SPLIT_DIRECTION_RETIRED). RIGHT. Its last reader, SplitForm.tsx, is gone from the head tree; drawerSide / drawerWidth / modalSize (L4690–4698) stay declared with their live readers named in the describes. 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 stays open as measured — correct. The 258-line change to view.zod.ts between f282e8a9 and this head is main's (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 buttons.reset, feat(spec,types,plugin-list,app-shell)!: retire the list view's options bag; stored bags fold onto the top-level blocks, and the renderers read the blocks alone #27206); the PR-side delta did not touch the file, and the merge of it recomputes identically.
  5. REACT_BLOCKS['object-form'].dataProps drops splitDirection (react-blocks.ts L242); formType interaction type is the five values (L246). RIGHT — matches what react-page.tsx renders (SchemaRenderer → object-form renderer → container route) and the skill header's overlay guidance.
  6. skills/objectstack-ui/references/react-blocks.md: generator output only (build-react-blocks-contract.ts → OUT_MD, clip(s, 160)), no hand edit. RIGHT. Its one line of churn in the delta is the merge's stale options row going in (83af1ffeb0) and out again (bf2acff4aa); the head equals the clean merge and check:react-blocks is green.
  7. Four RETIRED_KEYS_BY_MAJOR entries under entries/retired-keys/18.* — unchanged in the delta; def keys match the [RETIRED] rows in authorable-surface/ui.json (L574, L826–828). RIGHT. The master-detail pane needs no entry of its own: the section is an anonymous shape and ui/FormSection:pane (L551) carries the def-keyed row — the same reasoning the prior record accepted for the object-form section.
  8. D2 form-view-presentation-types-removed widened in place: strips splitDirection from every form payload; convertBlock over mapPageComponents (regions, slots, container nesting) scoped to object-form; the master-detail arm strips sections[].pane only. RIGHT: deletes formType: 'split' (lands on the simple default — the conversion invents no layout), strips the three keys and every sections[].pane, leaves drawer / modal and non-form components alone; fixture notices reconcile at 17. Absorbed under the same-major rule: @objectstack/spec is 18.2.0 at the merge base, on origin/main and on the head, so 18.3.0 is unreleased — measured.
  9. D3 ui-form-view-type-layout-only widened (surface, replacement, reason, acceptanceCriteria name both blocks; surface keeps the no-backtick rule). RIGHT.
  10. Liveness view.json: splitDirection → dead / REMOVED with the retiredKey-stays-in-shape precedent; drawerSide / drawerWidth notes cite ObjectFormContainer.tsx → DrawerForm.tsx. RIGHT; Spec property liveness green.
  11. Public-surface (lane seat's remainder, read only to judge the spec rows): SplitForm / SplitFormProps / SplitFormSchema leave @object-ui/plugin-form; ObjectFormContainer is internal; @object-ui/types ObjectFormSchema.formType drops split, the three split keys are tombstones by reference, pane leaves ObjectFormSection (the only pane left in ui/packages/types/src is fieldPanes in form.ts, a different feature). All RIGHT against the spec rows.

② Semver level

.changeset/27159-object-form-split-retired.md: @objectstack/spec: minor, @object-ui/plugin-form: minor, @object-ui/types: minor, @objectstack/skills: patch, @object-ui/i18n: patch, @object-ui/console: patch. Matches what the diff publishes and the house rule (ruling 6464210455 A′: a (narrowing) arm is BREAKING and ships as a minor carrying its migration). The body carries the BREAKING banner, FROM → TO for the block (now including the master-detail pane), FROM → TO for the form view, the one-line fix (naming the conversion's three reaches), the parse message, the plugin-form export FROM → TO, the @object-ui/types and console lines, a "Supersedes" bullet for #27155's splitDirection line, and an "Unchanged" bullet for the four kept keys. Clause-②: no (narrowing) and Metadata-protocol: narrowed (…) are line-initial in both the PR body and the changeset body, and the two Metadata-protocol: lines agree word for word on the tombstone set. ADR-0087 marker not-required (already-registered form-view-presentation-types-removed, ui-form-view-type-layout-only) — both ids exist at the merge base and are widened in place, the honest disposition; Check Changeset green.

Clause-②: no (narrowing) — correct for a refused enum value and a tombstoned key set on two blocks and the form view; no new authorable key is added.

The prior account-vs-tree mismatch on the #27155 changeset is gone: the body says the base changeset is left as written, and it is.

③ Boundary flags

open_questions: [] in the os-dev report. Every deviation, and every seat note since, answered:

  • Tier H opened by the seat-1 route (hold 73eb8292, revert 235fc7d7): procedural; Governed Surface Queue Guard and PR-body guards green on this head. Answered.
  • PR body footer: present on this head (Generated-with line and session URL). Resolved.
  • Model-tier reading for skills/**: answered by this record — the governed path is reviewed at CONTRACT_REVIEW_TIER by triage under the handoff lines 6485852047 / 6486021665 and claim 6486592056.
  • better-sqlite3 install workaround: environment only.
  • @object-ui/types keeps the split keys as spec tombstones by reference: RIGHT (① 11).
  • Changeset bumps for skills / i18n / console: RIGHT (②).
  • "Merged origin/main … only conflict was the record": true for the conflict; the merge commit additionally carried stale regenerated output in five more generated files, corrected by bf2acff4aa so that the head equals the clean merge (delta section). Nothing to escalate: the net tree is right, the gates that judge generated output are green, and no source or governed-by-hand file was affected.
  • on_27153: drawerSide / drawerWidth keep a live reader (ObjectFormContainer.tsx L91–92 → DrawerForm). Leaving 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 open is right.
  • Out-of-scope findings (stale SplitForm comments in four files; master-detail drawer / modal prescriptions still say "portal dialog"): noted, not filed — acceptable; the master-detail split prescription points at object-form's retirement correctly.
  • The 02:02Z claim amendment widened the surface by studio-authoring.json (one row) and content/docs/frontend/plugins/plugin-form.mdx; 7cfa968d touches exactly those two outside the earlier surface, plus content/docs/ui/components/plugins.mdx and the manifest pair, which are regenerated output of the index.tsx input change. No breach.

④ Design judgment

For each hits file, against the code it cites and the scenario it is accepted against (ADR-0050 Decision 4 via ruling 6464210455 A′, scope notes 1–2, amendments; first consumers read in the live tree at bf2acff4aa):

  • packages/spec/src/ui/component.zod.ts — HOLDS. Refusals are enforced by construction. First consumers: ObjectFormRenderer (index.tsx L268) routes drawer / modal to ObjectFormContainer and everything else to ObjectForm; the registered inputs enum is the schema's five values and its split guidance now matches the spec's prescription; MasterDetailForm passes sections through to the parent ObjectForm. Break attempt 1 — a stored object-form block authored formType: 'split' with pane sections and the three keys: converted on read to a simple block with clean sections, then parses (fixture + stored-page pin). Break attempt 2 — drawer + modal with their keys on every layout: CONTROL passes. Break attempt 3 — a stored object-master-detail-form block whose section carries pane (accepted through 18.2.0 via the shared objectFormSection()): now stripped by the conversion before the parse, details, formType and sibling sections untouched, identity kept on a clean block, and the prescription names the block — the prior should-fix closed. Break attempt 4 — a master-detail block carrying a split key (splitSize) or formType: 'split': strictObject refused the key and the enum map refused the value through 18.2.0 already, so no stored row can carry either and the master-detail arm correctly does NOT run convertBlock (running it would be harmless, but the narrower arm is the honest one). Break attempt 5 — a { group, pane } section on either block: stripSectionPanes strips any dict section, and the group refinement then judges the clean section. Design note, unchanged: the block's formType keeps two dimensions in one key by the amendment's accepted boundary; a drawer holding a tabbed form stays inexpressible on the block, and a separate container key would be an added widening owed its own card.
  • packages/spec/src/ui/view.zod.ts — HOLDS. splitDirection had no reader once SplitForm.tsx left; the tombstone's prescription matches the family wording; drawerSide / drawerWidth / modalSize describes are true against ObjectFormContainer.tsx and the console dialog. The file's other movement on this head is main's.
  • packages/spec/src/ui/react-blocks.ts — HOLDS. splitDirection leaves the allow-list; formType type and description match what a kind:react ObjectForm tag renders through the object-form renderer and its container route; contentLayout is a live block key.
  • skills/objectstack-ui/references/react-blocks.md — HOLDS against the spec it teaches: a byte-faithful regeneration on the clean merge (160-char clips, check:react-blocks green); the formType row and the three re-worded data rows match react-blocks.ts and the describes; the header's "(b) Overlays: render an ObjectForm tag with formType drawer or modal" remains true on main + this diff. The stale options row the merge commit briefly re-introduced is gone on the head.
  • packages/spec/src/conversions/registry.ts — HOLDS. Reach: mapPageComponents (regions, slots, container nesting) over object-form AND object-master-detail-form, ∘ mapViewPayloads ∘ assembled items; order preserves spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155 behaviour; lossless deletes only; identity preserved on untouched components; fixture notices reconcile at 17; retiredAfter: '18.2.0' consistent with the 18.3.0 prescriptions and the removal-version pin. The notice paths (….properties.sections[i].pane, from: 'pane') are the same shape on both blocks, so os migrate meta lists them uniformly.
  • packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__splitDirection.ts, 18.ui__ObjectFormProps__splitDirection.ts, 18.ui__ObjectFormProps__splitResizable.ts, 18.ui__ObjectFormProps__splitSize.ts — HOLD. Unchanged in the delta; exact def keys match the [RETIRED] rows; headers cite D2 and D3 correctly; the retired-keys gate is green.
  • packages/spec/src/migrations/entries/semantic/18.ui-form-view-type-layout-only.ts — HOLDS. Surface, reason and acceptance criteria describe all three doors (form view, object-form block, master-detail pane); surface stays backtick-free for the upgrade guide.

Findings outside hits (the lane seat's ACCEPT; read to judge the spec rows, not reviewed): none new. The three prior out-of-hits should-fixes are resolved on this head.

Must-fix: none. Should-fix: none open — all five prior should-fixes are resolved exactly on this head.

Implemented-by: claude/project-thread-04ka71
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · head bf2acff4aa · 2026-10-29T03:04Z

席位意见:ObjectForm 只负责布局(simple / tabbed / wizard),抽屉和弹窗由 object-form 区块经 ObjectFormContainer 承接,split 及其专用键整体退役。18.3.0 尚未发布,已存的页面、视图和 master-detail 区块都由同一个 D2 转换在读取时清理掉,升级后不会解析失败。

  1. 合约复核: triage CONTRACT_REVIEW_TIER 两轮 PASS。6485040701 在 f282e8a9,无必修,5 条应修。6486731528 在 bf2acff4,增量复核 ee84fa2b,5 条应修全部"resolved exactly",无遗留。
  2. 席位自读(复核 hits 之外的部分):
    • plugin-form:ObjectForm 去掉 split / drawer / modal 三个分支;ObjectFormContainer 把抽屉和弹窗分支原样搬过去;SplitForm 删除。
    • 约 20 个套件改为经 ObjectFormContainer 挂载抽屉和弹窗。
    • console:objectname-neither-hint-11605 去掉 split 行。这是测试改动,CI 曾在这里真红,已修。
    • types:三个 split 键改为引用 spec 的墓碑(defaultFilters 先例),pane 删除。
    • Studio:去掉 Split 选项和对应的 i18n 键。
    • 文档:README、mdx、平台清单行都已核对。
  3. changeset 逐句对照 diff:
  4. 形态: Fixes #27159;Clause-②: no (narrowing);Metadata-protocol: narrowed (…),含 master-detail 的 pane;Skills-docs: 写明重生成。属 Tier H,因为 skills/** 是生成产物。PR 用席位 1 的路线打开,requested_reviewers 至今为空。
  5. 门禁: bf2acff4 上 65 个 check-run:58 success、7 skipped、0 失败,mergeable clean。本地派生门禁 123/123 退出码为 0。
  6. 入队前还差一项: 本 PR 是 (narrowing),dogfood battery 需要钉在当前 head 上全量跑一遍。开发上次跑在 6f5071c3(348 个文件、2806 个测试),席位已要求在 bf2acff4 上重跑。读数回来前不入队。
  7. 落地条款: 席位现在翻 ready,按 Tier H 请维护者审批。批准且 dogfood 读数到位后,席位开启 auto-merge,跟到 MERGED。

维护者速读(席位终稿)

  • 改了什么:ObjectForm 只渲染 simple / tabbed / wizard 三种布局。object-form 区块的 drawer / modal 改由新的 ObjectFormContainer 打开。formType: 'split'、SplitForm 和 split 专用键(含 sections[].pane、FormView.splitDirection)全部退役,解析时按名拒收,并给出改法。
  • 为什么改:执行 ADR-0050 Decision 4(裁决 6464210455 A′)中渲染器这一半。spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155 已收窄表单视图的 type。
  • 风险与回滚:区块的抽屉和弹窗行为不变,代码只是原样搬家。直接挂载 ObjectForm 并写 formType: "drawer" 的宿主现在得到内联表单。已存数据由 D2 转换在读取时清理。回滚即还原本 PR,18.3.0 尚未发布。
  • 先看:ui/packages/plugin-form/src/ObjectFormContainer.tsx、index.tsx 的区块分流、component.zod.ts 的墓碑、conversions/registry.ts 的 convertBlock 和 master-detail 的 pane 清理。
  • 两分钟验证:cd packages/spec && npx vitest run src/ui/form-view-type-layout-retirement.test.ts,以及 pnpm --dir ui exec vitest run packages/plugin-form/src/__tests__/objectFormContainer-27159.test.tsx。
  • 需要你做的:请 hotlong 或 os-zhuang 在 head bf2acff4 上批准(Tier H:已发布 schema + skills/**)。批准后,席位在 dogfood 读数到位时开启自动合并。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 03:05
@objectstack-fleet

objectstack-fleet Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Seat note · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-29T03:31Z

The dogfood battery reading the seat's ACCEPT 6486748478 held back (item 6) is now on file. It is pinned to head bf2acff4aa6a37d2b7c90cef79ef9846b90a5ec7.

  • Run by the card's dev on git rev-parse HEAD = bf2acff4aa6a37d2b7c90cef79ef9846b90a5ec7, with a clean tree and no push.
  • The closure was rebuilt first: pnpm --workspace-concurrency=2 --filter '@objectstack/dogfood^...' build, exit 0.
  • packages/qa/dogfood pnpm test exited 0:
    • 348 test files passed and 3 skipped (351 in total).
    • 2806 tests passed and 20 skipped (2826 in total).
    • 0 red.
  • CI on this head is 59 success, 7 skipped, 0 failing.

[Edit 04:25Z: the queue took this PR at 03:32Z and removed it at 03:51Z on a merge conflict with main (#27329 deleted two ui docs it edits). The dev is re-merging main. A governed-path PR also needs a maintainer approval before the queue keeps it: Governed Surface Queue Guard refused #27361 for that reason. So the approval line below this note's first version was right, and the earlier correction was wrong. After the re-merge, the seat posts a new review handoff and waits for an approval before re-enqueueing.]

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 29, 2026
Takes main's deletion of ui/content/docs/api/schema-reference.md and
ui/content/docs/plugins/plugin-form.mdx (#27329). The plugin-form page's
new home, content/docs/frontend/plugins/plugin-form.mdx, gains the
"Where an object-form block opens" section.

Claude-Session: https://claude.ai/code/session_01JQwxbWHWPRD5rZXeY69YvL
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-warren · session_01JQwxbXpF772qWJjojJvRXw

Seat note · head moved after delta PASS · seat domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-29T05:12Z

The merge queue dropped this PR at 03:51Z on a merge conflict. Main's #27329 (7587fd4, which folds objectui.org into the objectstack docs) deleted ui/content/docs/api/schema-reference.md and ui/content/docs/plugins/plugin-form.mdx, and this PR edits both.

The head moved from bf2acff4 (triage delta PASS 6486731528) to ccde9658 in one commit: a merge of origin/main. The seat compared the PR's net delta file by file, before and after:

  • The two deleted files drop out of the PR. The resolution takes main's deletion.
  • content/docs/frontend/plugins/plugin-form.mdx, the page docs(ui-merge): fold objectui.org into objectstack docs, K4 — retire the site, host 301s, links #27329 kept, is the only file whose delta changed. It gains a "Where an object-form block opens" section that ports the deleted page's ObjectFormContainer / drawer / modal wording and names the split removal. That is prose only, outside the four contract faces.
  • The other 100 files carry a delta identical to bf2acff4's. No code, spec or skills/** byte changed.

CI on ccde9658: 65 check-runs, 58 success and 7 skipped, mergeable clean. The dogfood battery is owed again on this head (narrowing), and the dev is re-running it.

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (ceiling model out for the fleet since 2026-10-27, seat runs at TIER_DEFAULT, 2026-10-29T05:12Z) · head ccde96581f9705552cf1495d66a6d6ba90c9f65d

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-warren · session_01JQwxbXpF772qWJjojJvRXw

Seat note · 2026-10-29T05:13Z

The dogfood battery owed on the new head (narrowing) is on file, pinned to ccde96581f9705552cf1495d66a6d6ba90c9f65d. That is git rev-parse HEAD read inside the locked run, with a clean tree and no push.

  • Closure rebuild pnpm --workspace-concurrency=2 --filter '@objectstack/dogfood^...' build: exit 0.
  • packages/qa/dogfood pnpm test: exit 0. 348 files passed and 3 skipped (351). 2806 tests passed and 20 skipped (2826). 0 red.
  • Spec suite: 22296 tests pass. plugin-form + types: 593 files, 12760 tests pass. Regeneration is current. dispatch-gates: 123/123 exit 0.
  • CI on this head: 58 success, 7 skipped, 0 failing.

The PR body's Docs bullet now names the merge's doc resolution.

Before re-enqueue, the seat is waiting on two things: triage's read on this head (handoff 6488565227), then a maintainer approval, because this PR touches skills/** and Governed Surface Queue Guard requires one.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head ccde96581f9705552cf1495d66a6d6ba90c9f65d · 2026-10-29T05:53Z

Triage seat, taking the hand-off on this head (#27062 (a)); a delta re-review from triage's PASS on bf2acff4. The review runs at CONTRACT_REVIEW_TIER. If the lane claims or posts a record on this head first, triage withdraws.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ccde96581f9705552cf1495d66a6d6ba90c9f65d
Local-runs: none
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__splitDirection.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitDirection.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitResizable.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitSize.ts, packages/spec/src/migrations/entries/semantic/18.ui-form-view-type-layout-only.ts, packages/spec/src/ui/component.zod.ts, packages/spec/src/ui/react-blocks.ts, packages/spec/src/ui/view.zod.ts, skills/objectstack-ui/references/react-blocks.md + packages/spec/src/shared/retired-key.ts (retiredKey L193, enumWithRetiredValues L402), packages/spec/src/conversions/walk.ts (mapPageComponents L405), packages/spec/scripts/build-react-blocks-contract.ts (generator of the skill reference), packages/spec/src/migrations/registry.ts.template (the generated, git-ignored entry registry the four entry files feed), packages/spec/src/shared/retired-key-removal-version.pin.test.ts (JOIN rows L417–441), packages/spec/src/ui/form-view-type-layout-retirement.test.ts, packages/spec/liveness/view.json, packages/spec/authorable-surface/ui.json, ui/packages/plugin-form/src/ObjectFormContainer.tsx, ui/packages/plugin-form/src/index.tsx (the object-form renderer route L268 and registered inputs L299), ui/packages/plugin-form/src/MasterDetailForm.tsx, ui/packages/plugin-form/README.md, content/docs/frontend/plugins/plugin-form.mdx (the one file whose delta the merge changed), .changeset/27159-object-form-split-retired.md, .changeset/27155-form-view-type-layout-only.md

Inputs read: card #27159 body and all 8 comments (scope notes 6465248799 / 6468180697, unblock 6468789520, order 6469180448, claim 6469269349, amendments 6469786533 / 6485842645, os-dev report 6473832761); ruling 6464210455 on #27032 (gh api GET); PR #27305 body, 102-file list, net diff vs main at the merge base c4c515ad3c (its file list equals git diff --name-only origin/main...prr-27305), all 14 PR comments; 69 check-runs on ccde96581f: 60 success, 9 skipped (opt-in harnesses, matrix placeholders, the schedule gate, duplicate workflow rows), 0 failed, 0 pending — among the green: Governed Surface Queue Guard, Check Changeset, Spec property liveness, Lint · repo & tooling gates (check:generated, check:react-blocks), Check Documentation Links, Build Docs, Dogfood Regression Gate, Test Core 8/8, Test UI 16/16, every Type Check. The check-runs are the gate verdicts; nothing was built or run. This is a re-review on a moved head: triage's prior record 6486731528 is a PASS on bf2acff4aa; the delta is ONE commit, judged below.

Delta since bf2acff4aa, commit by commit

  • ccde96581f — merge of origin/main c4c515ad3c into the branch (parents bf2acff4aa, c4c515ad3c). Recomputed with git merge-tree --write-tree bf2acff4aa c4c515ad3c → tree 414eb9467f, TWO modify/delete conflicts: ui/content/docs/api/schema-reference.md and ui/content/docs/plugins/plugin-form.mdx, both deleted on main by docs(ui-merge): fold objectui.org into objectstack docs, K4 — retire the site, host 301s, links #27329 (7587fd4408, the objectui.org docs fold) and edited on the branch. git diff 414eb9467f ccde96581f is exactly THREE files: the two conflicted pages deleted (main's deletion taken — neither exists on main or on the head), and content/docs/frontend/plugins/plugin-form.mdx +13 lines, the "Where an object-form block opens" section ported from the deleted page into its surviving home. That port is a hand edit carried by a merge commit, declared in the commit message, and read here against the tree: every sentence is true on this head — formType as layout (simple/tabbed/wizard) or block container (drawer/modal), the block renderer as the caller (index.tsx L268–269), DrawerForm with drawerSide/drawerWidth (ObjectFormContainer.tsx L91–92), ModalForm with modalSize/modalCloseButton (L129–130), a host rendering ObjectForm directly gets the inline form (the objectFormContainer-27159 pin), and the split prescription (tabbed, or an object-master-detail-form block) matches OBJECT_FORM_TYPE_RETIRED. It carries no version literal (doc-version-claims stays satisfied; Build Docs and Check Documentation Links are green). The prior head's deleted page said the same with one extra clause (the console's New / Edit dialogs mount ModalForm themselves), so the port is content-equivalent, not new doctrine. No source, spec, test, generated or skills/** file differs from the clean merge: NOT an evil merge. All ten hits files are byte-identical between bf2acff4aa and ccde96581f, and none of them moved on main between the two merge bases (74801d190f..c4c515ad3c, 9 commits). Per-file comparison of the old and new net diffs: 101 files identical, 1 changed (plugin-form.mdx, the port), 2 gone (the deleted pages), 0 new — the seat's rework note 6488565227 is accurate in every line.
  • main's side of the merge, read for semantic conflicts with the retirement kit: feat(spec)!: retire the unserved Virtual ObjectQL request protocol (#27028) #27324 (9ac5ae5b76) adds ten entries/retired-defs/18.data__DataEngine*Request.ts files and entries/semantic/18.data-engine-rpc-protocol-retired.ts; all eleven are present on the head beside this PR's four retired-keys/18.ui__* files and its widened D3 entry. The migrations registry is GENERATED WHOLE from one-file-per-entry and git-ignored (registry.ts.template, .gitignore L68), so a merge cannot drop either side's entry silently; check:generated is green. @objectstack/spec is 18.2.0 at 74801d190f, at c4c515ad3c and on the head — 18.3.0 is still unreleased, so the same-major absorption into form-view-presentation-types-removed still holds. main's only touches near the lane remainder are URL/description rewrites in ui/packages/plugin-form/{README.md,package.json} and ui/packages/types/package.json (auto-merged, byte-identical to the clean merge).

Prior must-fixes: none existed. Prior should-fixes: none were open on bf2acff4aa (the five from 6485040701 were resolved exactly there); the files they named are unchanged in this delta except content/docs/frontend/plugins/plugin-form.mdx, where should-fix 3(b)'s SplitForm removal still holds (the page's only SplitForm mention, L92, is the sentence saying it was removed). All remain resolved exactly. The lane's rework note 6488565227 and dogfood note 6488587492 are claims; the tree and the check-runs above are the evidence, and they agree with the note.

① Derived judgments

Accept-set and public-surface changes the diff implies, each judged against the live tree at ccde96581f:

  1. ObjectFormPropsSchema.formType (component.zod.ts L6941): enumWithRetiredValues(['simple','tabbed','wizard','drawer','modal'], OBJECT_FORM_TYPE_RETIRED). RIGHT. enumWithRetiredValues (retired-key.ts L402) refuses construction if a retired member is still live; split is refused by name with the 18.3.0 prescription (replacement tabbed / delete for simple, the three keys to delete, object-master-detail-form for child records, the os migrate meta --from 17 sentence). drawer / modal stay by the amendment's measured decision; the block renderer (index.tsx L268 isObjectFormContainerType → ObjectFormContainer) is the caller that supplies the container.
  2. splitDirection / splitSize / splitResizable → retiredKey() tombstones (L6990 / 6992 / 6994). RIGHT. retiredKey is z.never({ error }).optional() (L193–194): absence passes, any authored value is refused by name, z.input types the key never. The drawerSide / drawerWidth / modalSize / modalCloseButton describes name the block's containers, which ObjectFormContainer.tsx L91–92 and L129–130 forward.
  3. objectFormSection().pane → retiredKey(OBJECT_FORM_SECTION_PANE_RETIRED) (L6886), shared by ObjectFormPropsSchema.sections (L6981) and ObjectMasterDetailFormPropsSchema.sections (L7262). RIGHT for both consumers: the prescription names both blocks, the D2 conversion strips pane on both (registry.ts L9003–9008), the removal-version JOIN pins both rows (pin test L436, L441). MasterDetailForm.tsx hands sections to the parent ObjectForm and reads only fields, so the strip is lossless there.
  4. FormViewSchema.splitDirection (view.zod.ts L4677) → retiredKey(FORM_VIEW_SPLIT_DIRECTION_RETIRED). RIGHT. Its last reader, SplitForm.tsx, is gone from the head tree; drawerSide / drawerWidth / modalSize (L4690–4698) stay declared with their live readers named in the describes. 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 stays open as measured — correct.
  5. REACT_BLOCKS['object-form'].dataProps drops splitDirection (react-blocks.ts L242); formType interaction type is the five values (L246). RIGHT — matches the object-form renderer's container route and the skill header's overlay guidance.
  6. skills/objectstack-ui/references/react-blocks.md: generator output only (build-react-blocks-contract.ts), no hand edit; byte-identical to the prior head and to the clean merge; check:react-blocks green. RIGHT.
  7. Four RETIRED_KEYS_BY_MAJOR entry files under entries/retired-keys/18.* — def keys match the [RETIRED] rows in authorable-surface/ui.json (L574, L826–828); the master-detail pane rides ui/FormSection:pane (L551) because the section is an anonymous shared shape. RIGHT. They sit beside main's new retired-defs entries with no id overlap.
  8. D2 form-view-presentation-types-removed (registry.ts L8947, retiredAfter: '18.2.0' L8950): strips splitDirection from every form payload (L8987), convertBlock over mapPageComponents scoped to object-form (deletes formType: 'split' → lands on the simple default, strips the three keys and every sections[].pane, leaves drawer / modal alone), master-detail arm strips sections[].pane only; expectedNotices: 17 (L9154). RIGHT, and the absorption is still legitimate (18.2.0 released, 18.3.0 not).
  9. D3 ui-form-view-type-layout-only widened: surface (backtick-free, L20–26) and the replacement text name the form view, the object-form block and the master-detail pane. RIGHT.
  10. Liveness view.json: splitDirection dead / REMOVED (L304); drawerSide / drawerWidth notes cite the container (L322, L328). RIGHT; Spec property liveness green.
  11. Public surface in the lane seat's remainder (read only to judge the spec rows): SplitForm / SplitFormProps / SplitFormSchema leave @object-ui/plugin-form; ObjectFormContainer is internal; @object-ui/types ObjectFormSchema.formType drops split, the three split keys are tombstones by reference, pane leaves the section. All RIGHT against the spec rows. The docs movement is now: the two ui/content/docs pages are gone (main's deletion), their SplitForm passages with them; the surviving content/docs/frontend/plugins/plugin-form.mdx carries the ported section; the schema-reference.md claim that the block's formType aligns with FormViewSchema.type (scope note 2) has no surviving carrier anywhere under content/docs, skills or the plugin-form README.

② Semver level

.changeset/27159-object-form-split-retired.md: @objectstack/spec: minor, @object-ui/plugin-form: minor, @object-ui/types: minor, @objectstack/skills: patch, @object-ui/i18n: patch, @object-ui/console: patch — unchanged in the delta and still matching what the diff publishes under ruling 6464210455 A′ (a (narrowing) arm is BREAKING and ships as a minor carrying its migration). The body carries the BREAKING banner, FROM → TO for the block (including the master-detail pane), FROM → TO for the form view, the one-line fix naming the conversion's three reaches, the parse message, the plugin-form export FROM → TO, the @object-ui/types and console lines, "Supersedes" for #27155's splitDirection line, and "Unchanged" for the four kept keys. Clause-②: no (narrowing) and Metadata-protocol: narrowed (…) are line-initial in the PR body and the changeset and agree word for word. ADR-0087 marker not-required (already-registered form-view-presentation-types-removed, ui-form-view-type-layout-only) — both ids exist at the merge base and are widened in place. .changeset/27155-form-view-type-layout-only.md is byte-identical on main and the head, as the body says. Check Changeset green on this head.

Clause-②: no (narrowing) — correct: a refused enum value and a tombstoned key set on two blocks and the form view; no new authorable key is added, and the delta adds none.

③ Boundary flags

open_questions: [] in the os-dev report; every deviation and every seat note since answered:

  • Merge conflict with docs(ui-merge): fold objectui.org into objectstack docs, K4 — retire the site, host 301s, links #27329 (seat notes 6487129531 edit, 6488565227): resolved by taking main's deletion and porting the one section the PR owed into the surviving page; verified against the clean merge above — nothing else moved. Answered.
  • Hand-off line 6488565227 says "CONTRACT_REVIEW_TIER unavailable … seat runs at TIER_DEFAULT": triage took the hand-off (claim 6489152302) and this record is rendered at CONTRACT_REVIEW_TIER. Answered.
  • Governed-path approval: Governed Surface Queue Guard green on this head; the maintainer approval it requires before the queue keeps the PR is procedural and the seat's to obtain (requested_reviewers left empty by the seat-1 route). Not a review finding.
  • Dogfood battery owed on the moved head (narrowing): the Dogfood Regression Gate check-run on ccde96581f concluded success; the seat's note 6488587492 adds a local reading pinned to this head (its claim). Nothing to escalate.
  • Tier H open by the seat-1 route (hold 73eb8292, revert 235fc7d7), PR body footer, better-sqlite3 install, model-tier reading for skills/**: unchanged from the prior record; procedural or environment only.
  • @object-ui/types keeps the split keys as spec tombstones by reference: RIGHT (① 11). Changeset bumps for skills / i18n / console: RIGHT (②).
  • on_27153: drawerSide / drawerWidth keep a live reader (ObjectFormContainer.tsx L91–92 → DrawerForm). Leaving 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 open is right.
  • Out-of-scope findings (stale SplitForm comments in four files, among them content/docs/protocol/objectui/layout-dsl.mdx L934 which survives the merge; master-detail drawer / modal prescriptions still say "portal dialog"): noted, not filed — acceptable.
  • The 02:02Z claim amendment's surface (studio-authoring.json, content/docs/frontend/plugins/plugin-form.mdx): the merge's port lands inside the second path, which is already on the claimed surface. No breach. The two deleted ui/content/docs paths leave the surface by main's hand, not the dev's.

④ Design judgment

For each hits file, against the code it cites and the scenario it is accepted against (ADR-0050 Decision 4 via ruling 6464210455 A′, scope notes 1–2, amendments; first consumers read in the live tree at ccde96581f). Every hits file is byte-identical to the prior head and main did not touch any of them, so each judgment below was re-read on this head rather than inherited:

  • packages/spec/src/ui/component.zod.ts — HOLDS. Refusals enforced by construction (enumWithRetiredValues, retiredKey). First consumers: ObjectFormRenderer (index.tsx L268) routes drawer / modal to ObjectFormContainer, everything else to ObjectForm; the registered inputs enum (L299) is the schema's five values with the same split guidance; MasterDetailForm passes sections through. Break attempts, all held: (1) a stored object-form block with formType: 'split', pane sections and the three keys → converted on read to a clean simple block, then parses (fixture + stored-page pin); (2) drawer / modal with their keys on every layout → CONTROL passes; (3) a stored object-master-detail-form block whose section carries pane → stripped before parse, siblings untouched, identity kept on a clean block; (4) a master-detail block carrying splitSize or formType: 'split' → strictObject and the enum map refused both through 18.2.0 already, so no stored row can carry them and the narrower arm is the honest one; (5) a { group, pane } section → stripSectionPanes strips any dict section, the group refinement judges the clean one; (6) new on this head — a page author who followed the now-deleted ui/content/docs/api/schema-reference.md claim that the block's formType aligns with FormViewSchema.type would author drawer / modal on a form view, not on the block; the form view refuses them with spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155's prescription and the block accepts them, and no surviving page restates the claim. Design note unchanged: the block's formType keeps two dimensions in one key by the amendment's accepted boundary; a drawer holding a tabbed form stays inexpressible on the block (only contentLayout: 'tabbed' for the modal), and a separate container key would be an added widening owed its own card.
  • packages/spec/src/ui/view.zod.ts — HOLDS. splitDirection had no reader once SplitForm.tsx left; the tombstone's prescription matches the family; drawerSide / drawerWidth / modalSize describes are true against ObjectFormContainer.tsx and the console dialog.
  • packages/spec/src/ui/react-blocks.ts — HOLDS. splitDirection leaves the allow-list; formType type and description match what a kind:react ObjectForm tag renders through the object-form renderer and its container route; contentLayout is a live block key.
  • skills/objectstack-ui/references/react-blocks.md — HOLDS against the spec it teaches: a byte-faithful regeneration, unchanged by the merge (identical to the clean merge), check:react-blocks green; the formType row and the three re-worded data rows match react-blocks.ts and the describes; the header's overlay guidance ("render an ObjectForm tag with formType drawer or modal") stays true on main + this diff.
  • packages/spec/src/conversions/registry.ts — HOLDS. Reach: mapPageComponents (regions, slots, container nesting) over object-form and object-master-detail-form, then mapViewPayloads, then assembled items; lossless deletes only; identity preserved on untouched components; fixture notices reconcile at 17; retiredAfter: '18.2.0' consistent with the 18.3.0 prescriptions and the removal-version pin. The absorption's precondition (18.3.0 unreleased) re-measured true on this merge base.
  • packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__splitDirection.ts, 18.ui__ObjectFormProps__splitDirection.ts, 18.ui__ObjectFormProps__splitResizable.ts, 18.ui__ObjectFormProps__splitSize.ts — HOLD. Exact def keys match the [RETIRED] rows; headers cite D2 and D3; they coexist with main's eleven new feat(spec)!: retire the unserved Virtual ObjectQL request protocol (#27028) #27324 entry files in the generated-whole registry (no append-table conflict is possible by construction); the retired-keys gate is green.
  • packages/spec/src/migrations/entries/semantic/18.ui-form-view-type-layout-only.ts — HOLDS. Surface, reason and acceptance criteria describe all three doors (form view, object-form block, master-detail pane); surface stays backtick-free for the upgrade guide.

Findings outside hits (the lane seat's ACCEPT; read to judge the spec rows and the merge, not reviewed): the ported doc section is true against the tree; the deleted pages' SplitForm passages leave with them; layout-dsl.mdx L934 still names SplitForm in a comment-grade list (already in the dev's out-of-scope findings). None new.

Must-fix: none. Should-fix: none.

Implemented-by: claude/project-thread-04ka71
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-warren · session_01JQwxbXpF772qWJjojJvRXw

席位核对

ACCEPT · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · head ccde96581f · 2026-10-29T06:06Z

接续 6486748478(当时在 bf2acff4)。head 随一次合入 main 移到 ccde9658,席位逐项重新核对:

  1. 契约复核: triage 在 ccde9658 上 PASS(6489327935),无必修、无应修。它用 git merge-tree 重算了合并结果,确认合并只改了三个文件:两份被 main 删除的 ui 文档取删除,content/docs/frontend/plugins/plugin-form.mdx 新增的那段逐句与树一致。
  2. 席位自读: 其余 100 个文件的 delta 与 bf2acff4 逐文件相同(席位核对见 6488565227)。PR 正文 Docs 一行已改为写明这次合并怎样处理文档。
  3. dogfood: 已钉在 ccde9658 上全量跑过(6488587492):348 个文件、2806 个测试,0 失败。
  4. 门禁: 69 个 check-run,60 success、9 skipped、0 失败,mergeable clean。
  5. 还差: 本 PR 改了 skills/**,Governed Surface Queue Guard 要求维护者批准后才能入队。已请 hotlong、os-zhuang 在 ccde9658 上批准,批准后席位重新入队。

…rable surface after merging main

Claude-Session: https://claude.ai/code/session_01JQwxbWHWPRD5rZXeY69YvL
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-warren · session_01JQwxbXpF772qWJjojJvRXw

Seat note · head moved after delta PASS · seat domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-29T08:09Z

Main moved on and the PR went dirty again. The only conflict was the generated scripts/sdui-manifest.record.json.

The head moved from ccde9658 (triage delta PASS 6489327935, seat ACCEPT 6489338631) to 166b8dd9 in two commits:

  • 13c318d0 merges origin/main.
  • 166b8dd9 regenerates with the repo's tools (gen-sdui-manifest-node.mjs, gen-sdui-reference.mjs, spec check:generated --fix).

The seat compared the PR's net delta file by file (patch-id against each head's own merge base):

  • 101 of 102 files: the delta is identical to ccde9658's. No code, spec, docs or skills/** byte of this PR changed.
  • scripts/sdui-manifest.record.json: only the record's own sha256 line differs. It hashes the regenerated manifest over the new base, and that base now carries main's object-metric polarity key and gantt wording. Before the merge the line read dc8b… to ebc9… plus the date; after, it reads 7761… to d555….

The dev reports 123/123 derived gates exit 0, with check-sdui-manifest and check:sdui-reference green. The dogfood battery is owed again on this head (narrowing), and the dev is running it pinned to 166b8dd9. CI on 166b8dd9 is running. Mergeable is blocked, waiting only on a review.

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (ceiling model out for the fleet since 2026-10-27, seat runs at TIER_DEFAULT, 2026-10-29T08:09Z) · head 166b8dd90f9367a58f96016ef872492b9e496b64

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-warren · session_01JQwxbXpF772qWJjojJvRXw

Dogfood battery · head 166b8dd90f9367a58f96016ef872492b9e496b64 · 2026-10-29T08:38Z

This is the narrowing battery, re-run on the merged head and pinned by git rev-parse HEAD inside the locked run on a clean tree, with no push. The closure rebuild exited 0. packages/qa/dogfood pnpm test exited 0: 348 test files passed and 3 skipped (351); 2806 tests passed and 20 skipped (2826); 0 failed. CI on the same head: 65 check-runs, 58 success and 7 skipped, 0 failed, mergeable clean. What remains: the triage delta read on handoff 6491444984, then a maintainer approval (Governed Surface Queue Guard, skills/**).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head 166b8dd90f9367a58f96016ef872492b9e496b64 · 2026-10-29T08:52Z

Triage seat, taking the hand-off on this head (#27062 (a)); a delta re-review from triage's PASS on ccde9658. The review runs at CONTRACT_REVIEW_TIER. If the lane claims or posts a record on this head first, triage withdraws.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 166b8dd90f9367a58f96016ef872492b9e496b64
Local-runs: none
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__splitDirection.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitDirection.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitResizable.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectFormProps__splitSize.ts, packages/spec/src/migrations/entries/semantic/18.ui-form-view-type-layout-only.ts, packages/spec/src/ui/component.zod.ts, packages/spec/src/ui/react-blocks.ts, packages/spec/src/ui/view.zod.ts, skills/objectstack-ui/references/react-blocks.md + packages/spec/src/shared/retired-key.ts (retiredKey L193, enumWithRetiredValues L402), packages/spec/src/conversions/walk.ts (mapPageComponents L405), packages/spec/scripts/build-react-blocks-contract.ts (generator of the skill reference), packages/spec/src/migrations/registry.ts.template (the generated, git-ignored entry registry the four entry files feed; .gitignore L68), packages/spec/src/shared/retired-key-removal-version.pin.test.ts (JOIN rows L421–442), packages/spec/src/ui/form-view-type-layout-retirement.test.ts, packages/spec/liveness/view.json, packages/spec/authorable-surface/ui.json, ui/packages/plugin-form/src/ObjectFormContainer.tsx, ui/packages/plugin-form/src/index.tsx (the object-form renderer route L268–269 and registered inputs L299), ui/packages/plugin-form/src/MasterDetailForm.tsx, scripts/gen-sdui-manifest-node.mjs (how the record's sha256 is computed, L343), scripts/sdui-manifest.record.json and sdui.manifest.json (the one file the delta's merge conflicted on, and the artefact it hashes), .changeset/27159-object-form-split-retired.md, .changeset/27155-form-view-type-layout-only.md

Inputs read: card #27159 body and all 8 comments (scope notes 6465248799 / 6468180697, unblock 6468789520, order 6469180448, claim 6469269349, amendments 6469786533 / 6485842645, os-dev report 6473832761 — no card comment since the prior record); ruling 6464210455 on #27032 (gh api GET); PR #27305 body, 102-file list, net diff vs main at the merge base 12a1aba3a2 (its file list equals git diff --name-only 12a1aba3a2 prr-27305), all 19 PR comments (five new since the prior record: ACCEPT 6489338631, head-moved note 6491444984, dogfood note 6491983684, triage claim 6492255190, and the prior record 6489327935 itself); 65 check-runs on 166b8dd90f: 58 success, 7 skipped (18.x upgrade harness opt-in, the Dogfood matrix placeholder, Dogfood Verify CLI, Packed-tarball smoke opt-in, the schedule gate, Temporal Conformance, UI Published Artifacts), 0 failed, 0 pending — among the green: Governed Surface Queue Guard, Check Changeset, Spec property liveness, Lint · repo & tooling gates, Lint · docs & code guards, Check Documentation Links, Build Docs, Dogfood Regression Gate, Test Core 8/8, Test UI 16/16, every Type Check. The check-runs are the gate verdicts; nothing was built or run (the one computation below is a sha256sum over a file read with git show, not a repo script). This is a re-review on a moved head: triage's prior record 6489327935 is a PASS on ccde96581f; the delta is TWO commits, judged below.

Delta since ccde96581f, commit by commit

  • 13c318d011 — merge of origin/main 12a1aba3a2 into the branch (parents ccde96581f, 12a1aba3a2; 25 main commits since the prior merge base c4c515ad3c). Recomputed with git merge-tree --write-tree ccde96581f 12a1aba3a2 → tree 093f54f1bc, ONE content conflict: scripts/sdui-manifest.record.json, the sha256 line (branch ebc9… against main 7761…, each the hash of its own side's sdui.manifest.json). git diff 093f54f1bc 13c318d011 is FIVE files, not one: the record resolved to the branch's ebc9…, and four auto-mergeable generated files taken on the branch's side — content/docs/references/ui/component.mdx (main's polarity row dropped), content/docs/ui/components/index.mdx (object-metric count 20 → 19), content/docs/ui/components/plugins.mdx (the polarity row dropped, main's docs(plugin-gantt): the object-gantt registration no longer teaches the retired view-level api source #27421 gantt data / filter wording reverted), packages/spec/authorable-surface/ui.json (ui/ObjectMetricProps:polarity dropped). On its own this commit was NOT the clean merge: it carried stale generated output that lost main's feat(spec,ui): an inline object-metric tile declares polarity, so a lower-is-better KPI draws a rise red #27361 / docs(plugin-gantt): the object-gantt registration no longer teaches the retired view-level api source #27421 rows. No hits file, source file, spec rule, test or skills/** file differed from the clean merge.
  • 166b8dd90f — "regenerate the manifest record, component references and authorable surface after merging main". Its diff is exactly those five files: the four generated files return to the clean merge's bytes (every dropped row restored, verified byte-equal), and the record's sha256 becomes d555767947…. git diff 093f54f1bc 166b8dd90f is ONE file, scripts/sdui-manifest.record.json, the sha256 line alone. The generator hashes the serialized manifest (gen-sdui-manifest-node.mjs L343); git show prr-27305:sdui.manifest.json | sha256sum = d55576794739e7893f7ffdfeadfe8a0ebdd480469e95f431dc7bc80565f4e229, equal to the record on the head (the same identity holds at 12a1aba3a2 → 7761…, ccde96581f → ebc9…, c4c515ad3c → dc8b…). sdui.manifest.json itself auto-merged and the regeneration left it at the clean merge's bytes. So the head tree equals the clean merge plus one honestly regenerated hash line: NOT an evil merge. The first commit's drift was corrected inside the same push and never reached a check-run on its own; the head is what is reviewed.
  • hits on this head against the prior head: nine of ten are byte-identical to ccde96581f (and main did not touch them in c4c515ad3c..12a1aba3a2; the four entry files exist only on the branch). packages/spec/src/ui/component.zod.ts differs from the prior head by exactly main's feat(spec,ui): an inline object-metric tile declares polarity, so a lower-is-better KPI draws a rise red #27361 hunk (dbf576b600): the DatasetMeasurePolarity import, OBJECT_METRIC_POLARITY_NOT_A_FLAG, three aliases and three guidance entries on ObjectMetricPropsSchema, and its polarity key — 59 lines, all above the object-form region, which shifts from L6236 to L6295 with no textual change. Per-file comparison of the old and new net diffs: 102 files in both, 0 gone, 0 new; 95 hunk-identical; 4 differ only in blob ids (component.mdx, index.mdx, ui.json, sdui.manifest.json); 3 differ beyond that — component.zod.ts and plugins.mdx by hunk line offsets only, sdui-manifest.record.json now a sha-only hunk because main moved generatedAt to 2026-10-29 itself. The seat's note 6491444984 ("101 of 102 identical by patch-id; the record's sha256 line alone differs") is accurate.
  • main past this merge base (6 commits to 7614f8f05c): intersects the PR's file list only at ui/apps/console/src/components/FormPage.tsx (fix(console): a refused form submit marks the fields it names; markdown placeholder in sans #27443, refusedWriteError at L1819+, far from the PR's FORM_SPEC_TYPES hunk at L259–264); git merge-tree --write-tree prr-27305 origin/main is CLEAN; pr.json reads mergeable clean (merge_commit_sha 97da176cfe). None of the six touches a hits file or any cited file. @objectstack/spec is 18.2.0 at c4c515ad3c, 12a1aba3a2, origin/main and the head — 18.3.0 is still unreleased, so the same-major absorption into form-view-presentation-types-removed still holds. main's feat(spec)!: retire the unserved Virtual ObjectQL request protocol (#27028) #27324 retired-defs/18.* entries and the PR's four retired-keys/18.ui__* entries coexist in the generated-whole registry (template byte-identical to the prior head; 261 retired-keys/18.*, 438 semantic/18.*, 194 retired-defs/18.* on the head; no duplicate entry string among the ui__FormView / ui__ObjectFormProps / ui__FormSection files).

Prior must-fixes: none existed. Prior should-fixes: none were open on ccde96581f; the five from 6485040701 stay resolved exactly (their files are hunk-identical in this delta). The lane's notes 6491444984 and 6491983684 are claims; the tree and the check-runs above are the evidence, and they agree with the notes.

① Derived judgments

Accept-set and public-surface changes the diff implies, each judged against the live tree at 166b8dd90f:

  1. ObjectFormPropsSchema.formType (component.zod.ts L7000): enumWithRetiredValues(['simple','tabbed','wizard','drawer','modal'], OBJECT_FORM_TYPE_RETIRED). RIGHT. enumWithRetiredValues (retired-key.ts L402) throws at construction if a retired member is still listed live or its guidance is blank; split is refused by name with the 18.3.0 prescription (L6306–6314: tabbed or delete for the simple default, the three keys and each section's pane to delete, object-master-detail-form for child records, the os migrate meta --from 17 sentence). drawer / modal stay by the amendment's measured decision; the block renderer (index.tsx L268–269, isObjectFormContainerType → ObjectFormContainer) is the caller that supplies the container.
  2. splitDirection / splitSize / splitResizable → retiredKey() tombstones (L7049 / 7051 / 7053). RIGHT. retiredKey is z.never({ error }).optional() (L193–194): absence passes, any authored value is refused with the key's own prescription, z.input types the key never. The drawerSide / drawerWidth / modalSize / modalCloseButton describes (L7054–7057) name the block's containers, which ObjectFormContainer.tsx L91–92 and L129–130 forward.
  3. objectFormSection().pane → retiredKey(OBJECT_FORM_SECTION_PANE_RETIRED) (L6945), shared by ObjectFormPropsSchema.sections (L7040) and ObjectMasterDetailFormPropsSchema.sections (L7319). RIGHT for both consumers: the prescription names both blocks (L6334–6338), the D2 conversion strips pane on both (registry.ts L9003–9008), the removal-version JOIN pins both rows (pin test L436–442). MasterDetailForm.tsx hands sections to the parent ObjectForm (L1984) and reads only fields off them (L728), so the strip is lossless there.
  4. FormViewSchema.splitDirection (view.zod.ts L4677) → retiredKey(FORM_VIEW_SPLIT_DIRECTION_RETIRED) (L4507–4511). RIGHT. Its last reader, SplitForm.tsx, is gone from the head tree; drawerSide / drawerWidth / modalSize (L4690–4698) stay declared with their live readers named in the describes. 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 stays open as measured — correct.
  5. REACT_BLOCKS['object-form'].dataProps drops splitDirection (react-blocks.ts L242); formType interaction type is the five values (L246). RIGHT — matches the object-form renderer's container route and the skill header's overlay guidance.
  6. skills/objectstack-ui/references/react-blocks.md: generator output only (build-react-blocks-contract.ts), no hand edit; byte-identical to the prior head and untouched by main or the merge; the Lint · repo & tooling gates run that holds check:react-blocks is green. RIGHT.
  7. Four RETIRED_KEYS_BY_MAJOR entry files under entries/retired-keys/18.* — def keys match the [RETIRED] rows in authorable-surface/ui.json (L574, L826–828); the master-detail pane rides ui/FormSection:pane (L551) because the section is an anonymous shared shape. RIGHT. They sit beside main's new retired-defs entries with no id overlap, and the regenerated ui.json on the head carries both the PR's [RETIRED] rows and main's ui/ObjectMetricProps:polarity (L988).
  8. D2 form-view-presentation-types-removed (registry.ts L8947, retiredAfter: '18.2.0' L8950): strips splitDirection from every form payload (L8987), convertBlock over mapPageComponents scoped to object-form (L8990–8998: deletes formType: 'split' → lands on the simple default, strips the three keys and every sections[].pane, leaves drawer / modal alone), master-detail arm strips sections[].pane only (L9007); expectedNotices: 17 (L9154). RIGHT, and the absorption is still legitimate (18.2.0 released, 18.3.0 not, re-measured on this merge base).
  9. D3 ui-form-view-type-layout-only widened: surface (backtick-free) and replacement, reason and acceptanceCriteria name the form view, the object-form block and the master-detail pane; conversionIds names the D2. RIGHT.
  10. Liveness view.json: splitDirection dead / REMOVED (L304); drawerSide / drawerWidth notes cite the container (L322, L328). RIGHT; Spec property liveness green.
  11. Public surface in the lane seat's remainder (read only to judge the spec rows): SplitForm / SplitFormProps / SplitFormSchema leave @object-ui/plugin-form; ObjectFormContainer is internal; @object-ui/types ObjectFormSchema.formType drops split, the three split keys are tombstones by reference, pane leaves the section. All RIGHT against the spec rows. The generated docs and manifest on the head are the clean merge's bytes (① above), so the published object-form input set (five formType values, no split inputs) and main's polarity row are both present — no regenerated surface was lost to the merge.

② Semver level

.changeset/27159-object-form-split-retired.md: @objectstack/spec: minor, @object-ui/plugin-form: minor, @object-ui/types: minor, @objectstack/skills: patch, @object-ui/i18n: patch, @object-ui/console: patch — byte-identical to the prior head and still matching what the diff publishes under ruling 6464210455 A′ (a (narrowing) arm is BREAKING and ships as a minor carrying its migration). The body carries the BREAKING banner, FROM → TO for the block (including the master-detail pane), FROM → TO for the form view, the one-line fix naming the conversion's three reaches, the parse message, the plugin-form export FROM → TO, the @object-ui/types and console lines, "Supersedes" for #27155's splitDirection line, and "Unchanged" for the four kept keys. Clause-②: no (narrowing) and Metadata-protocol: narrowed (…) are line-initial in the PR body and the changeset and agree word for word. ADR-0087 marker not-required (already-registered form-view-presentation-types-removed, ui-form-view-type-layout-only) — both ids exist at the merge base and are widened in place. .changeset/27155-form-view-type-layout-only.md is byte-identical on main 12a1aba3a2 and the head. Check Changeset green on this head. The delta publishes nothing new: the regeneration moves only generated output and a provenance hash.

Clause-②: no (narrowing) — correct: a refused enum value and a tombstoned key set on two blocks and the form view; no new authorable key is added, and the delta adds none.

③ Boundary flags

open_questions: [] in the os-dev report; every deviation and every seat note since answered:

  • Second merge conflict (seat note 6491444984): the only conflict was the generated scripts/sdui-manifest.record.json; resolved by regenerating with the repo's tools. Verified above: the head equals the clean merge plus an honest hash. The merge commit by itself carried four stale generated files; the regeneration commit in the same push restored them. Answered — the head is right, and the intermediate state is not reviewed.
  • Hand-off line 6491444984 says "CONTRACT_REVIEW_TIER unavailable … seat runs at TIER_DEFAULT": triage took the hand-off (claim 6492255190) and this record is rendered at CONTRACT_REVIEW_TIER. Answered.
  • Governed-path approval: Governed Surface Queue Guard green on this head; requested_reviewers now reads hotlong, os-zhuang, requested by the seat after the prior PASS + ACCEPT (6489338631) as the fleet-write: relay pr_create asks the governed approvers to review a Tier H draft before any contract review exists, so an approver can approve and merge it unreviewed (#26566, #26570) #26617 route prescribes. Procedural; not a review finding.
  • Dogfood battery owed on the moved head (narrowing): the Dogfood Regression Gate check-run on 166b8dd90f concluded success; the seat's note 6491983684 adds a local reading pinned to this head (its claim: 348 files / 2806 tests, 0 red). Nothing to escalate.
  • The seat note's "Mergeable is blocked, waiting only on a review" was written at 08:09Z while CI ran; pr.json at 08:52Z reads clean. Consistent with the check-runs; nothing to escalate.
  • PR body's Verification section still names head 6f5071c3 and "22 commits of main" — the first merge's account, not this one. Prose outside hits; the tree and the check-runs on 166b8dd90f are the evidence here. Noted, not a finding.
  • Tier H open by the seat-1 route (hold 73eb8292, revert 235fc7d7), PR body footer, better-sqlite3 install, model-tier reading for skills/**: unchanged from the prior records; procedural or environment only.
  • @object-ui/types keeps the split keys as spec tombstones by reference: RIGHT (① 11). Changeset bumps for skills / i18n / console: RIGHT (②).
  • on_27153: drawerSide / drawerWidth keep a live reader (ObjectFormContainer.tsx L91–92 → DrawerForm). Leaving 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 open is right.
  • Out-of-scope findings (stale SplitForm comments in four files; master-detail drawer / modal prescriptions still say "portal dialog", component.zod.ts L7154–7162): noted, not filed — acceptable.
  • The 02:02Z claim amendment's surface: the delta touches content/docs/references/ui/component.mdx, content/docs/ui/components/{index,plugins}.mdx, packages/spec/authorable-surface/ui.json, scripts/sdui-manifest.record.json — all regenerated files already on the claimed surface ("regenerated files"), and every one lands on the clean merge's bytes or the artefact's true hash. No breach.

④ Design judgment

For each hits file, against the code it cites and the scenario it is accepted against (ADR-0050 Decision 4 via ruling 6464210455 A′, scope notes 1–2, amendments; first consumers read in the live tree at 166b8dd90f). Nine hits files are byte-identical to the prior head and component.zod.ts carries only main's object-metric hunk above the reviewed region, so each judgment below was re-read on this head at its new line numbers rather than inherited:

  • packages/spec/src/ui/component.zod.ts — HOLDS. Refusals enforced by construction (enumWithRetiredValues, retiredKey). First consumers: ObjectFormRenderer (index.tsx L268–269) routes drawer / modal to ObjectFormContainer (L40–44 is the predicate), everything else to ObjectForm; the registered inputs enum (L299) is the schema's five values with the same split guidance; MasterDetailForm passes sections through. Break attempts, all held: (1) a stored object-form block with formType: 'split', pane sections and the three keys → converted on read to a clean simple block, then parses (fixture + stored-page pin); (2) drawer / modal with their keys on every layout → CONTROL passes; (3) a stored object-master-detail-form block whose section carries pane → stripped before parse, siblings untouched, identity kept on a clean block; (4) a master-detail block carrying splitSize or formType: 'split' → strictObject and MASTER_DETAIL_FORM_TYPE_RETIRED refused both through 18.2.0 already, so no stored row can carry them and the narrower arm is the honest one; (5) a { group, pane } section → stripSectionPanes strips any dict section, the group refinement judges the clean one; (6) new on this head — main's feat(spec,ui): an inline object-metric tile declares polarity, so a lower-is-better KPI draws a rise red #27361 adds aliases (betterWhen, goodDirection, trendDirection) and guidance (direction, higherIsBetter, lowerIsBetter) on ObjectMetricPropsSchema only; ObjectFormPropsSchema keeps guidanceSets: COMPONENT_LEVEL_GUIDANCE with no alias map, so no near-miss table on the form block changes and no key of the two schemas collides; the module still builds and every spec suite passes (Type Check, Test Core 8/8 green); (7) a page author following the deleted schema-reference.md claim that the block's formType aligns with FormViewSchema.type authors drawer / modal on a form view, not the block — the form view refuses them with spec(view): narrow FormView.type to layout only (simple | tabbed | wizard) — retire split, drawer, modal with tombstones (ADR-0050 Accepted, #27032 letter A′) #27155's prescription, the block accepts them, and no surviving page restates the claim. Design note unchanged: the block's formType keeps two dimensions in one key by the amendment's accepted boundary; a drawer holding a tabbed form stays inexpressible on the block (only contentLayout: 'tabbed' for the modal), and a separate container key would be an added widening owed its own card.
  • packages/spec/src/ui/view.zod.ts — HOLDS. splitDirection had no reader once SplitForm.tsx left; the tombstone's prescription (L4507–4511) matches the family's FORM_VIEW_TYPE_RETIRED_FIX; drawerSide / drawerWidth / modalSize describes are true against ObjectFormContainer.tsx and the console dialog.
  • packages/spec/src/ui/react-blocks.ts — HOLDS. splitDirection leaves the allow-list; formType type and description match what a kind:react ObjectForm tag renders through the object-form renderer and its container route; contentLayout is a live block key.
  • skills/objectstack-ui/references/react-blocks.md — HOLDS against the spec it teaches: a byte-faithful regeneration, untouched by both merges (identical to the clean merge, and main did not move it), check:react-blocks green; the formType row (L21) and the three re-worded data rows (L47–49) match react-blocks.ts and the describes; the header's overlay guidance ("render an ObjectForm tag with formType drawer or modal") stays true on main + this diff.
  • packages/spec/src/conversions/registry.ts — HOLDS. Reach: mapPageComponents (regions, slots, container nesting) over object-form and object-master-detail-form, then mapViewPayloads, then assembled items; lossless deletes only; identity preserved on untouched components; fixture notices reconcile at 17; retiredAfter: '18.2.0' consistent with the 18.3.0 prescriptions and the removal-version pin. The absorption's precondition (18.3.0 unreleased) re-measured true on this merge base and on origin/main 7614f8f05c.
  • packages/spec/src/migrations/entries/retired-keys/18.ui__FormView__splitDirection.ts, 18.ui__ObjectFormProps__splitDirection.ts, 18.ui__ObjectFormProps__splitResizable.ts, 18.ui__ObjectFormProps__splitSize.ts — HOLD. Exact def keys match the [RETIRED] rows; headers cite D2 and D3; they coexist with main's retired-defs entries in the generated-whole registry (no append-table conflict is possible by construction; registry.ts git-ignored, template unchanged); the retired-keys gate inside Lint · repo & tooling gates is green.
  • packages/spec/src/migrations/entries/semantic/18.ui-form-view-type-layout-only.ts — HOLDS. Surface, replacement, reason and acceptance criteria describe all three doors (form view, object-form block, master-detail pane); surface stays backtick-free for the upgrade guide; conversionIds names the D2.

Findings outside hits (the lane seat's ACCEPT; read to judge the spec rows and the merge, not reviewed): the regenerated docs, authorable surface and manifest on the head are the clean merge's bytes and the record's hash is the artefact's; the intermediate merge commit's stale generated output never reached the head. None new.

Must-fix: none. Should-fix: none.

Implemented-by: claude/project-thread-04ka71
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-warren · session_01JQwxbXpF772qWJjojJvRXw

席位核对

ACCEPT · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · head 166b8dd90f · 2026-10-29T09:01Z

接续 6489338631(当时在 ccde9658)。head 随一次合入 main 加一次重新生成移到 166b8dd9,席位逐项重新核对:

  1. 契约复核: triage 在 166b8dd9 上 delta PASS(6492416524),无必修、无应修。
  2. 席位自读: 102 个文件中 101 个的 net delta 与 ccde9658 相同;record 只差 sha256 一行,即重新生成的 manifest 的哈希(6491444984)。
  3. dogfood: 已钉在 166b8dd9 上全量跑过(6491983684):348 个文件、2806 个测试,0 失败。
  4. 门禁: 65 个 check-run,58 success、7 skipped、0 失败,mergeable clean。
  5. 还差: 本 PR 改了 skills/**,Governed Surface Queue Guard 要求维护者批准后才能入队。请 hotlong 或 os-zhuang 在 166b8dd9 上批准,批准后席位立即入队并跟到合并。

This branch has not been deployed

No deployments
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/xl tests tooling

Projects

None yet

2 participants