Skip to content

feat(spec)!: addRecord says what renders — enabled required, position top, mode/formView retired - #24970

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-24692-addrecord-retire-mode-formview
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-24692-addrecord-retire-mode-formview

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Closes #24692
Clause-②: yes (narrowing: mode / formView retire through ADR-0087 drop conversions; enabled becomes required)

A list's addRecord block now says what the renderer draws. enabled is required with no default, position defaults to top, and mode / formView are retired through the spec-property-retirement kit. It ships in 18.x under the standing ruling for breaking narrowings after GA (#6015, comment 6292005854, 「甲」), because ADR-0087 conversions cover every body that parsed under 18.0.

The ruling this implements (comment 6281834942, letter B′, items 1–5 verbatim)

  1. AddRecordConfigSchema (packages/spec/src/ui/view.zod.ts, the same schema on the page list-view node) keeps two keys: enabled, REQUIRED with no default, so addRecord: {} is refused at authoring and at publish instead of drawing nothing; and position, default top, values top / bottom / both as the renderer draws them.

  2. mode and formView retire through the spec-property-retirement kit: ADR-0087 conversion entries that drop the two keys from authored and stored bodies with a notice, retiredKey() tombstones on the strict object, the two form composites (view.form.ts:178, page.form.ts:165), regenerated artifacts, the liveness ledger. Clause-②: yes (narrowing), breaking, with its ADR-0087 disposition and the FROM → TO in the changeset.

  3. The producers follow in the same PR: showcase active-projects.page.ts:36, contact.view.ts:60 and the lint fixture showcase-shape.fixtures.ts:414 lose mode / formView; content/docs/ui/create-vs-edit-form.mdx:88 states the convention of item 4 and names no binding — the docs line is part of the retirement kit's surface and is declared in the claim as a cross-domain file.

  4. objectui: the create entry selects formViews.create when the object declares it, else today's form / formViews.default (app-shell/src/utils/recordFormNavigation.ts:218, one selection line, pinned by a test). list-view addRecord: the spec's declared defaults (enabled true, position bottom, mode inline) are not applied by the renderer (enforce or remove) objectui#13099 is that card; its parity row (p1-spec-alignment.test.ts:189, the mode default) changes when objectui's pin takes the spec release.

  5. ADR-0050 (Proposed) Decision 2 gains a dated note that the list add-record open mode is no longer declared on addRecord; how the create form opens stays the renderer's, and a per-list open mode, if ever wanted, is the list's existing navigation.mode, never a second enum. Tier H; it may ride the spec PR (the PR is then Tier H whole) or go as its own one-line PR — the spec seat chooses.

What changed

Schema (packages/spec/src/ui/view.zod.ts, AddRecordConfigSchema). One def serves every door: ListViewSchema, ObjectListViewSchema (an object's or a container's listViews), InterfacePageConfigSchema (a list page's interfaceConfig) and the page list-view node's props row.

  • enabled is z.boolean() with no default. If the key is omitted, the parse refuses with a prescription that names the key and says how to fix it.
  • position defaults to top. The values top / bottom / both are unchanged. I checked the renderer at objectui origin/main and at the pinned sha 616df6598b: ListView.tsx#resolveAddRecordPlacement maps an absent position to top, and showAddRecord needs enabled === true.
  • mode and formView are retiredKey() tombstones. Authoring either key is a tsc error and also a parse error that carries the prescription.

ADR-0087. There are two D2 conversions, both retired from the load path with retiredAfter: '18.0.0'.

  • They reach stack.views[] in all three persisted spellings, objects[].listViews.*, pages[].interfaceConfig and every page list-view node.
  • add-record-mode-form-view-removed strips both keys and emits one notice per key.
  • add-record-enabled-defaulted writes enabled: true onto a block that omits enabled. An explicit value is never touched.
  • RETIRED_KEYS_BY_MAJOR[18] gains ui/AddRecordConfig:mode and ui/AddRecordConfig:formView.
  • The D3 entries are add-record-mode-form-view-retired and add-record-enabled-required. Each has its own step-18 rationale fragment.
  • The two default moves are declared in DEFAULT_CHANGES_BY_MAJOR[18].

Ruling item 3 under 「甲」: the enabled half ships with a conversion, not as a warning. I measured whether any producer writes addRecord without enabled:

  • this repository at 71696121a6: the showcase's two blocks and the lint fixture all write enabled: true;
  • objectui's Studio design surface at the pin: it writes addRecord: { enabled: true };
  • production databases: not measured.

The measured population is zero, so the conversion is sound. It is lossless against the contract, because it writes the value the 18.0 parse already produced. It is not lossless in pixels, and the changeset says so: a stored block that omitted enabled drew no button, and once converted it draws the one it declared. No 19.0 refusal card is needed.

Forms. In view.form.ts and page.form.ts, the addRecord composites now list enabled and position explicitly. The new nested labels are translated in the three locales.

Liveness ledger. The liveness/view.json addRecord row records the removal. The sub-keys are one drill level below a recorded undrilled container, so no row is added or removed. The README residue line is updated.

Producers and docs.

  • showcase contact.view.ts and active-projects.page.ts, and the lint fixture showcase-shape.fixtures.ts, drop mode / formView.
  • content/docs/ui/create-vs-edit-form.mdx states the convention (formViews.create when declared, else form / formViews.default) and names no binding.
  • content/docs/ui/views.mdx drops addRecord.formView from where a named form is served and states the create convention.
  • @objectstack/lint: os explain list-view-field-unknown and one docblock no longer name addRecord.formView. lintViewRefs never read it.
  • The reference pages under content/docs/references/ are regenerated.

Tests.

  • New packages/spec/src/ui/add-record-config-retirement.test.ts, in the repo project. It covers every door, the prescriptions, the tsc channel, the conversions over stored view, page and object rows, idempotence, load-path retirement, the ledger rows, the forms, and a tree-scoped absence pin over the five declared roots.
  • retired-key-removal-version.pin.test.ts joins both new conversions.
  • The old view.test.ts / page.test.ts pins on mode and the bottom default are rewritten.

Not in this PR

Acceptance notes

  • The showcase's active-projects.page.ts sets userActions.addRecordForm: false next to addRecord: { enabled: true }. objectui's ListView withholds the Add button on addRecordForm === false, so that page probably shows no Add button, even though its comment says it shows one. I only read this from the code and did not reproduce it in a browser, so it is not filed. Carrier: none.
  • The 18.0 parse materialized position: 'bottom' into built artifacts that carried an addRecord block. Those artifacts keep bottom explicitly, so nothing moves on screen for them.

Verification (final head a05f141642, after merging origin/main)

  • pnpm --filter @objectstack/spec build and check:generated: ✓ All 14 generated artifacts are up to date.
  • pnpm --filter @objectstack/spec test: Test Files 703 passed (703), Tests 21369 passed | 1 todo.
  • pnpm --filter @objectstack/spec test:repo: 72 passed (72), 1236 passed.
  • pnpm --filter @objectstack/spec typecheck: exit 0.
  • pnpm --filter @objectstack/lint typecheck: exit 0.
  • pnpm --filter @objectstack/lint test: 151 passed (151), 7013 passed.
    • These six ran as one && chain under the verify lock, which reported VERDICT command-exit 0.
  • showcase: objectstack validate printed ✓ Validation passed, and tsc --noEmit exited 0.
  • Gates:
    • check-adr-0087-registration: registered, 4 ids new here.
    • check:i18n: in sync.
    • check:i18n-coverage: none new.
    • check:cross-package-test-inputs, check:nul-bytes and check:doc-authoring: green.
    • All 120 families dispatch-gates derives were run on the final head a05f141642, and every one exited 0. --ran reconciles: 120 run, 0 NOT-MEASURED.
  • Ablation of the absence pin: putting formView: 'create' back into contact.view.ts (ablation-replace: mutation landed, blob changed) turned the pin red, 1 failed | 34 passed, naming examples/app-showcase/src/ui/views/contact.view.ts. The restore was verified: the blob equals HEAD and git diff HEAD is empty.
  • pnpm lint (repo-wide eslint) is NOT MEASURED locally and left to CI.

Generated by Claude Code

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/lint, @objectstack/platform-objects, @objectstack/spec, touching 21 documentable anchor(s). ⚠️ 9 changed file(s) yielded no anchor (packages/lint/src/validate-list-view-field-refs.ts, packages/spec/authorable-defaults/ui.json, packages/spec/authorable-surface/ui.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/ui/create-vs-edit-form.mdx (via AddRecordConfigSchema (symbol, a top-level const), addRecord (literal, a string literal in sections))

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

  • content/docs/releases/v17/17-5.mdx (via retiredAfter (symbol, a field of const object addRecordEnabledDefaulted; a field of const object addRecordModeFormViewRemoved), retiredFromLoadPath (symbol, a field of const object addRecordEnabledDefaulted; a field of const object addRecordModeFormViewRemoved))

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

What this run could not see
  • 9 changed file(s) yielded no anchor (packages/lint/src/validate-list-view-field-refs.ts, packages/spec/authorable-defaults/ui.json, packages/spec/authorable-surface/ui.json, …) — pages documenting those are invisible to this run
  • 10 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 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 — 144 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 6440cf16334e1c8106dfdb31d87b6ca728d176c2 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1fe2d6a94e12ed4f6b9c54c1de3080cfa8983af1 — the merge of head fc643e459f6327a3840106256a3eb3a6f575a800 into base 6440cf16334e1c8106dfdb31d87b6ca728d176c2, 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 1fe2d6a94e12ed4f6b9c54c1de3080cfa8983af1 && git checkout 1fe2d6a94e12ed4f6b9c54c1de3080cfa8983af1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6440cf16334e1c8106dfdb31d87b6ca728d176c2 fc643e459f6327a3840106256a3eb3a6f575a800 && git checkout -B drift-repro 6440cf16334e1c8106dfdb31d87b6ca728d176c2 && git merge --no-ff fc643e459f6327a3840106256a3eb3a6f575a800

node scripts/docs-audit/affected-docs.mjs --json 6440cf16334e1c8106dfdb31d87b6ca728d176c2

⚠️ 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 6440cf16334e1c8106dfdb31d87b6ca728d176c2 → 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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fc643e459f6327a3840106256a3eb3a6f575a800
Local-runs: none
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__AddRecordConfig__formView.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__AddRecordConfig__mode.ts, packages/spec/src/migrations/entries/semantic/18.add-record-enabled-required.ts, packages/spec/src/migrations/entries/semantic/18.add-record-mode-form-view-retired.ts, packages/spec/src/migrations/registry.ts.template, packages/spec/src/ui/page.form.ts, packages/spec/src/ui/view.form.ts, packages/spec/src/ui/view.zod.ts + packages/spec/src/ui/page.zod.ts (the interfaceConfig.addRecord embed at :1060 and the page list-view node, read unchanged), packages/spec/scripts/lib/default-changes.ts, packages/spec/authorable-defaults/ui.json, packages/spec/authorable-surface/ui.json, packages/spec/liveness/view.json, packages/spec/src/shared/retired-key-removal-version.pin.test.ts, packages/spec/src/ui/add-record-config-retirement.test.ts, .changeset/24692-add-record-retired.md, examples/app-showcase/src/ui/views/contact.view.ts, examples/app-showcase/src/ui/pages/active-projects.page.ts, packages/lint/src/showcase-shape.fixtures.ts, content/docs/ui/create-vs-edit-form.mdx, content/docs/ui/views.mdx; for the landing question only: .github/workflows/ci.yml (console-pin), .github/workflows/spec-shape-gate.yml, .github/workflows/release.yml, scripts/build-console.sh, and objectui packages/types/src/__tests__/p1-spec-alignment.test.ts, packages/types/src/objectql.ts, packages/types/src/index.ts, packages/types/tsconfig.json, packages/types/tsconfig.test.json, packages/types/package.json at the pin 616df6598b and at objectui main cf153eb2e5.

Read against: card #24692 (body; ruling 6281834942 letter B′ items 1–6; seat-2 hold 6282077441; the 甲 note 6292027850; dispatch 6296388083; dev reports 6299365608 and 6303362402), the standing ruling #6015 comment 6292005854 (「甲」), the PR body, its 37-file list and the three-dot diff origin/main...fc643e459f, and the head's check-runs. This head supersedes the record's first reading at a05f141642 (FAIL on Test Core (3/6)): the delta between the two net diffs, taken by explicit sha, is one merge of origin/main (no content of this PR's) and one line in packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts — the zh-CN positive control 673 → 677, with a provenance comment — which is exactly the four addRecord.* row labels this PR authored in all three locales being counted as translated. No schema, conversion, form, bundle, producer or docs byte moved between the two heads, so every judgment below stands as read on the first head.

① Derived judgments

Every accept-set and public-surface change, named against the ruling:

  1. enabled REQUIRED, no default — right (ruling item 1). view.zod.ts replaces .default(true) with z.boolean({ error }) whose prescription fires only on issue.input === undefined; a non-boolean value gets zod's own type refusal (the new test pins both channels). addRecord: {} is refused at every door that embeds the def. AddRecordConfig is z.input, so the omission is also a tsc error at authoring (test fails tsc at the authoring site when enabled is omitted).
  2. position default bottom → top — right (ruling item 1). Values unchanged. DEFAULT_CHANGES_BY_MAJOR[18] carries the move; authorable-defaults/ui.json regenerated (enabled/mode rows gone, position = "top"). No conversion writes position, and that is the correct call: an omitted position rendered at the top before and after, and the 18.0 parse materialised bottom explicitly into built artifacts, which keep it.
  3. mode / formView retired — right (ruling item 2). Both are retiredKey() tombstones on the strict object (input type never, parse refusal carries the prescription with the house os migrate meta --from 17 sentence); authorable-surface/ui.json marks both [RETIRED] in the file's existing spelling; RETIRED_KEYS_BY_MAJOR[18] gains ui/AddRecordConfig:mode and :formView (the two retired-keys/18.* entries); D2 add-record-mode-form-view-removed strips both keys with one notice per key (fixture: 7 notices across all four doors); D3 add-record-mode-form-view-retired and its step-18 rationale fragment are present; retired-key-removal-version.pin.test.ts joins both tombstones and the enabled refusal under retiredAfter: '18.0.0', which is the newest published stable (packages/spec/package.json reads 18.0.0 on main).
  4. Every schema that embeds AddRecordConfigSchema — right. On main the def is embedded at view.zod.ts:3116 (ListViewSchema, from which ObjectListViewSchema and the page list-view node's props row derive) and page.zod.ts:1060 (InterfacePageConfigSchema). One def, so the tombstones and the required key reach all of them without a second edit. The conversion walker mapAddRecordBlocks reaches the same set: stack.views[] in all three persisted spellings via mapViewPayloads (kind list only — form payloads correctly untouched), objects[].listViews.*, pages[].interfaceConfig, and every page component of type: 'list-view' via mapPageComponents. The new test replays a stored view container, a stored page (both page doors) and a stored object through the strict doors; the pin test's walked-shape check keeps the [RETIRED] rows reachable.
  5. Forms — right. view.form.ts:178 and page.form.ts:165 composites now spell fields: [{ field: 'enabled' }, { field: 'position' }]; the four locale bundles gain the two nested labels (generated, check:i18n in the gate set). The liveness ledger row records the removal on the parent (the sub-keys are one drill level below a recorded undrilled container, so no row is added or removed — correct).
  6. Producers and docs — right (ruling item 3). active-projects.page.ts:36, contact.view.ts:60 and showcase-shape.fixtures.ts:414 lose both keys; create-vs-edit-form.mdx:88 states the convention and names no binding; views.mdx no longer teaches addRecord.formView. The convention the docs now state is live at the pin: objectui recordFormNavigation.ts at 616df6598b carries the objectui#13099 selection ("Which record flow a form view is selected for"), so the docs do not run ahead of the vendored renderer.

The add-record-enabled-defaulted conversion under 「甲」 rule 1 — within the derivation, no escalation owed. The triage derivation (6292027850) and the dispatch (6296388083) both name the exact alternative: "the PR either carries a conversion that writes the old default (true) onto an authored addRecord that omits it, or that half ships as a warning in 18.x and its refusal is filed for 19.0 — the dev decides from the measurement and says which." The PR takes the first branch and says so. Judged on the rule's own terms:

  • Lossless against the contract: yes. The written value is exactly what the 18.0 parse produced for the same body (.default(true)), so a converted row means what the published spec said it meant; it is idempotent (a converted block carries enabled, never touched again) and deliberately narrow (an explicit false, null or a non-boolean is left to the parse).
  • The pixel consequence (a stored block that omitted enabled drew no Add button because the renderer read the raw body, and now draws one) is the consequence of the branch triage prescribed — writing true is writing the button — not a choice the dev added. It is stated in the changeset, the D3 entry, the DEFAULT_CHANGES reason and the conversion docblock, with the one-line remedy (enabled: false or drop the block). The measured population is zero in this repo and in objectui's Studio design surface at the pin; production databases were not measured, and the PR says so. The alternative branch (warning now, refusal in 19.0) would have left addRecord: {} silently drawing nothing for another major, which is the trap the ruling's facet ③ exists to close. So: derivation honoured, honestly stated, no behaviour change beyond what the ruling and triage already decided.

Also read, nothing wrong found: the --from 17 wording in the prescriptions is the house sentence retiredKey's docblock pins; retiredFromLoadPath: true on both D2s means a live author meets the tombstone while stored rows, built artifacts inside their floor window and os migrate meta replay them (the RetiredConversionState contract); the two conversions are ordered 72 then 73, strip before default, so a block that carried only mode still gets enabled: true written after the strip; the old view.test.ts / page.test.ts pins on mode and the bottom default are rewritten rather than deleted.

② Semver level

  • Changeset .changeset/24692-add-record-retired.md: @objectstack/spec: minor, @objectstack/lint: patch; title feat(spec)!:; Clause-②: yes (narrowing); **BREAKING** paragraph with the FROM → TO table and the one-line fix. packages/platform-objects (generated i18n bundles) and the showcase are covered: platform-objects is in the same fixed group as spec in .changeset/config.json, the showcase is an example. Check Changeset concluded success.
  • What publishes vs what the level says: a published schema narrows (required key, two tombstones) and a parsed default moves. Under the standing ruling 「甲」 rule 1 a narrowing an ADR-0087 conversion carries ships in an 18.x minor; minor is therefore the ruled level, and the prescriptions' "since @objectstack/spec 18.1.0" matches the next minor off 18.0.0. The Clause-②: line on the PR body (yes (narrowing: …)) and on the changeset (yes (narrowing)) agree.
  • ADR-0087 registration/disposition: the changeset carries adr-0087: registered add-record-mode-form-view-removed, add-record-enabled-defaulted, add-record-mode-form-view-retired, add-record-enabled-required — the two D2 ids and the two D3 ids, matching the registry, the retired-keys/ and semantic/ entries and the two STEP18_RATIONALE fragments. The disposition section names the D2s, the D3s, the RETIRED_KEYS_BY_MAJOR[18] rows and the DEFAULT_CHANGES_BY_MAJOR[18] moves. The registration gate's conclusion on this head is the verdict on that registration (the Lint · family on the head concluded success).

③ Boundary flags

Gate verdicts on this head (the check-runs ARE the verdicts). 42 check-runs on fc643e459f, every one completed: 36 success, 6 skipped (the full-battery jobs on a pull_request event: Console Pin Gate, Dogfood Verify CLI, the dogfood shard, Temporal Conformance, and the two opt-in workflows), 0 failure. Test Core and all six shards, Build Core, Build Docs, Check Changeset, Governed Surface Queue Guard, Spec property liveness, the whole Lint · family (ESLint, the ADR-0087 registration gate, check:i18n) and every Type Check · job concluded success. On the previous head a05f141642, Test Core (3/6) was red on @objectstack/platform-objects (object-lifecycle-panel-echo-decisions.test.ts, "zh-CN positive control: expected 677 to be 673"): the control counts genuinely translated .label leaves per locale and rose by exactly the four addRecord.* row labels this PR authored in zh-CN / ja-JP / es-ES (已启用 / 位置, 有効 / 位置, Habilitado / Posición), so the fix is the pinned count with its provenance line, and no schema, bundle or form byte moved. The dev's second report (6303362402) reproduced the red locally before the fix (1 failed | 81 passed) and the green after (82 passed, 1232 tests), with the spec and lint batteries unchanged. One process note, answered: the first head's battery had not run pnpm --filter @objectstack/platform-objects test, which is why the gate saw it first; the second head's did.

Dev flag 1 — active-projects.page.ts sets userActions.addRecordForm: false beside addRecord: { enabled: true }, so objectui's showAddRecord memo likely withholds the button the page's comment promises. Answered as pre-existing: addRecordForm: false is on main before this PR and the PR changes neither key; the page still parses and validates. Not a blocker here. Escalated to the dispatching seat: file a showcase card (carrier: none today) or decline on the record.

Dev flag 2 / the landing hazard — objectui packages/types/src/__tests__/p1-spec-alignment.test.ts:189 expects addRecord.mode 'inline'. Determined from the workflows and the objectui files at the pin; the premise of the question is narrower than feared:

  • What the row is. The test is type-only against objectui's OWN ListViewSchema interface (packages/types/src/objectql.ts:3038: addRecord?: { enabled?: boolean; position?: string; mode?: string; formView?: string }, a hand-written local shape, identical at the pin and at objectui main cf153eb2e5). It assigns an object literal and reads .mode back; nothing is parsed through @objectstack/spec, and the literal is not typed as the spec's AddRecordConfig (which objectui re-exports from @objectstack/spec/ui at index.ts:1428 but does not use for this interface). So neither the spec's tombstone (z.input of mode becomes never) nor the required enabled reaches that file. It goes red only when objectui itself re-types the interface or re-pins the row, which is what ruling item 4 says happens on objectui's side.
  • Console Pin Gate (ci.yml console-pin). Full battery only: if: event != pull_request && != merge_group, so it never runs on a PR or a queue build (it reads skipped on this head). On the push to main it also needs the console path filter, which lists .objectui-sha, scripts/build-console.sh, the two check scripts, the probe scripts, packages/spec/package.json, packages/spec/tsup.config.ts and six other manifests; this PR moves none, so the push run skips it. On the hourly schedule (filter forced true) it restores the console dist by a cache key over those same inputs — unchanged here — so it hits and the build step is skipped; what runs is check:console-sha and check:console-injection --require-stamp, which replay the probes stamped at build time against the dist on disk, by the ruled cache design (the spec's content lag behind a cached dist is deliberate). On a miss, scripts/build-console.sh only builds (turbo run build on the console's deps, then the SPA's own vite build) — it never runs objectui's vitest, and objectui's packages/types/tsconfig.json excludes **/__tests__/** and *.test.ts from the build program, so the test is not even compiled there. release.yml on the main push runs the same build script under a narrower cache key. Nothing in this repo's workflows runs objectui's test suite.
  • Spec Shape Gate (spec-shape-gate.yml). Hourly on main (plus workflow_dispatch, and pull_request only for its own three files); type-checks objectui main — chained test projects included (tsconfig.test.json is in packages/types' type-check) — against the spec built from the commit. For the named row: no diagnostic, for the reason above (local interface). Residual, not a finding: any objectui site that types a literal as the spec's AddRecordConfig and writes mode/formView or omits enabled would red that hourly run; the dev reports a git grep at the pin found no source writing either key, and this review could not enumerate objectui's uses of AddRecordConfig from the brief's inputs. Under that gate's own rule a red is the lane's first work plus a one-line registration in scripts/ci/spec-shape-retirements.json (the PR does not pre-register, and none is owed before a diagnostic exists); it is a main-red the release gate reads, not a merge blocker.
  • Finding: merging this PR turns no main-or-queue job red on the named row. No precondition (objectui change or pin bump) is needed to land; the parity row changes on objectui's side when its pin takes this spec release, as ruling item 4 already binds.

Out-of-scope / remaining items from the dev report, each answered or escalated:

  • ADR-0050 Decision 2 dated note (ruling item 5, Tier H): not in this PR, by the choice item 5 grants; the exact one-line text is in the dev report. Escalated to the dispatching seat: dispatch the one-line Tier H PR so the ruling's item 5 does not go unowned.
  • Surfaces beyond the claim's file list (default-changes.ts, vitest.repo-tests.json, the migrations entries, registry.ts.template, the pin test, view/page tests, the new test, the liveness README, views.mdx, the two lint files, the i18n bundles, the regenerated references): all declared in the report, all required by the kit; none is a governed path (the Governed Surface Queue Guard concluded success). Answered.
  • Production databases not measured for addRecord blocks omitting enabled: stated in the PR, the changeset and the conversion docblock; the conversion is the safe branch for that unknown (no refusal of stored data). Answered.
  • Repo-wide pnpm lint left to CI: the Lint · ESLint check-run on the head is that measurement. Answered.

Implemented-by: claude/issue-24692-addrecord-retire-mode-formview
Reviewed-by: session_01D6F9axfoPzR7t8G1J7Ytzz

VERDICT: PASS

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