You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(types): the zod layout mirror substitutes runtime .default() values the renderers never apply — a parsed container renders a different width than an unparsed one #7735
Found while measuring objectui#7361's triage boundary 3 (does anything downstream consume the declared default). Unassigned. Higher stakes than objectui#7361, because this surface is RUNTIME rather than documentation.
Measured on origin/main = b74a859, against the built packages/types/dist.
What is declared
packages/types/src/zod/layout.zod.ts is a published runtime validator (@object-ui/types/zod, reached through safeValidateSchema, which is AnyComponentSchema.safeParse and returns the parsed object as result.data). Several members carry .optional().default(VALUE), and .default() is not documentation — parse SUBSTITUTES that value into the output when the key is absent.
A container that omits maxWidth renders max-w-xl when it reaches the renderer as authored, and max-w-lg when it has been through the mirror first — the mirror writes 'lg' into the node, so the renderer's ?? finds a value and never applies its own default. Same shape for flex and align: 'center' is substituted, and 'center' is a value NEITHER flex nor stack applies. One authored document, two code paths, two layouts.
StackSchema in the same file declares no defaults at all (see the stack row above), so it is unaffected by the substitution — the mirror is also internally inconsistent between the two sibling types.
Relationship to other cards
objectui#7361 corrected the JSDoc @default on ContainerSchema.maxWidth and FlexLayoutProps.align. Its file surface was the docblocks only and its dispatch explicitly ruled out moving any default VALUE, so the mirror was deliberately left as it stands. After that change the two surfaces disagree with each other in the tree, which is part of why this needs its own ruling.
Distinct from objectui#5927, which is about mirrors being NARROWER than their TypeScript declarations (accept-set drift). This is default substitution — a different axis.
Another instance of objectui#4631.
Why a ruling rather than a patch
Two candidate shapes, and an implementing agent should not pick between them:
Move the values (.default('lg') becomes .default('xl'), .default('center') becomes .default('start')). This changes what parse emits for every existing document, so it is a behaviour change on a published surface.
Drop .default() from the mirror entirely, so parsing stops substituting and the renderer's fallback is the single source of the default. This is arguably the contract-first shape, and it is already what StackSchema does.
The wider question behind both: whether a validator on this surface should be allowed to author values at all.
Filed by an ObjectUI development agent while working objectui#7361. No assignee, no labels — left for triage.
Ruling recorded — option 乙: the mirrors stop authoring defaults; the renderer is the authoritative face (director seat, decision batch #69, 2026-09-07)
Maintainer reply, verbatim: 「其他同意」 (this card: adopted as recommended).
Ruling and principle. A validator validates; it does not write values into an author's document. The renderer's fallback is the single authoritative default; the JSDoc @defaultdescribes it; the zod mirror carries no .default() (what StackSchema already does). All 45 .default() sites across the seven packages/types/src/zod/*.zod.ts files are removed, not just the two measured outliers (option 甲 refused — it leaves 38 unaudited sites and no gate).
Execution notes.
Measure first: find every consumer that reads result.data (the parsed document) expecting a substituted default — that is the only risk of 乙 (rendering does not change, because every renderer already carries its own fallback: 5 of 7 audited sites matched, the two that did not are the bug). Record the count on this card; fix those readers to fall back themselves.
Remove the 45 .default() calls; where a mirror used .default() to imply optionality, keep .optional().
Published parse output changes for documents that omit these keys: changeset on @object-ui/types, needs:contract-review (Clause-② conformance limb: output shape narrows, accept set unchanged).
Claim: PM loop round R1
Session: session_01QtGhnU3WnnWyiWeYQhw2aX
Branch: claude/issue-7735-zod-mirrors-stop-authoring-defaults
Worktree: objectui-issue-7735
Domain: domain:spec
File surface: packages/types/src/zod/{layout,crud,form,views,app}.zod.ts (the .default() call sites) · their TypeScript JSDoc twins packages/types/src/{layout,crud,form,views,app}.ts (@default tags only) · ⚠️cross-domain, one region: packages/components/src/renderers/layout/flex.tsxdefaultProps.align (~:132) — the third face #8229 found, folded in by the ruling · any consumer the census in step 1 turns up · new issue-numbered pins · one .changeset/ entry. ⛔ NOT in surface: packages/types/src/zod/{objectql,complex,base}.zod.ts (measured: zero real .default() call sites — see below), packages/types/src/complex.ts (#7952's, this batch), packages/types/src/base.ts (#6950's, this batch), the renderer fallback reads in container.tsx / flex.tsx:18 / stack.tsx:24 (they are the authority — ⛔ they do not move) (stop on breach; explain in the report)
Container & model: L, mode:subagent, model: claude-opus-5 (TIER_DEFAULT — ruling implementation, clause ② not met: this narrows parse output and leaves the accept set untouched, which is the mechanical boundary test's "pull back to the declared contract" side)
Clause-②: no
Serial constraints cleared: #7952 and #6950 (this same batch — file surfaces measured disjoint; #7952 owns complex.ts, #6950 owns base.ts + sdui-parser + core, ⛔ this card touches none of them) · #6939 (pm:dispatched, groups 2–6 queued, none in flight; its own group-1 report states layout.zod.ts and the parity ledger were NOT touched, but its later groups will edit zod/ — re-read its claim 5521422355 before you touch any mirror it names) · #8229 (bare, unassigned, and its own comment 5565511525 hands its defaultProps face to whoever takes this card — that is this branch) · domain:ui in flight is #7727 and #7635, neither declares flex.tsx (targeted cross-domain in-flight check, run because this surface crosses a lane line) · ⚠️4f9f1ee touched packages/types/src/zod/ recently — merge origin/main and re-read before editing
Adjudication — ⛔ not re-openable
Ruled by the director seat, decision batch #69, 2026-09-07 (comment 5565508219). Maintainer reply, verbatim: 「其他同意」.
Ruling and principle. A validator validates; it does not write values into an author's document. The renderer's fallback is the single authoritative default; the JSDoc @defaultdescribes it; the zod mirror carries no .default() (what StackSchema already does). All 45 .default() sites across the seven packages/types/src/zod/*.zod.ts files are removed, not just the two measured outliers (option 甲 refused — it leaves 38 unaudited sites and no gate).
Its five execution notes stand in full: (1) census the consumers that read a substituted default off result.data, record the count here, fix them to fall back themselves; (2) remove the calls, keeping .optional() where .default() was carrying optionality; (3) the two pins; (4) changeset + needs:contract-review; (5) #7361's JSDoc corrections stand.
⭐ PM measurement that corrects the ruling's ARITHMETIC — the direction is untouched
Measured on origin/main = fc32921 at 2026-09-07T08:14Z. .default( matches 45 times, but 4 of those are prose inside docblocks. There are 41 real call sites, in 5 files, not 45 in 7:
file
raw .default(
in-comment
real call sites
layout.zod.ts
22
0
22
crud.zod.ts
11
0
11
form.zod.ts
5
0
5
views.zod.ts
2
0
2
app.zod.ts
1
0
1
objectql.zod.ts
3
3
0
complex.zod.ts
1
1
0
base.zod.ts
0
0
0
45
4
41
Positive control, so a null result would have meant something: both ruled outliers come back as real call sites under the same discriminator (layout.zod.ts:181.default('lg'), :202.default('center')). The renderer controls are verbatim at container.tsx:26 (?? 'xl') and flex.tsx:18 (|| 'start').
⚠️Two consequences, and the second is a trap that would have shipped red:
objectql.zod.ts and complex.zod.ts have nothing to remove. Editing them to reach "45" would be inventing a diff.
⛔ Pin (i) cannot be a bare text search for .default(. Written literally as the ruling words it — "no .default( under packages/types/src/zod/**" — it is red on the day it lands, because those 4 docblock mentions survive your change and are legitimate prose. Write the pin against call sites (parse/AST, or a matcher that excludes comment lines), or reword the four docblocks, and say in the PR which you chose and why. Prove the pin discriminates: it must go red when a .default() is reintroduced and stay green with the prose in place.
⇒ Re-derive the census yourself before you start. 41 is my reading, not a specification — if you get a different number, yours wins and you say so in the report.
PM mechanism assumptions — measure these; you are encouraged to falsify them
Execution note 1 is the whole risk of this card and it is unmeasured. Rendering does not change (every renderer already carries its own fallback — 5 of 7 audited pairs agreed, and the 2 that did not are the bug). What can change is any consumer that reads result.data and expects the substituted value to be present. ⛔ Do not assume that population is empty because it is convenient. Find it, count it, record the count on this card, and fix those readers to fall back themselves. If it turns out non-trivial in a way that changes the shape of the fix, stop and report a fork — ⛔ do not silently re-scope and ⛔ do not quietly fall back to option 甲.
The flex.tsx hunk is one line and it crosses a lane line.defaultProps.align: 'center' → the renderer's 'start'. finding(components): flex.tsx's own registration declares defaultProps.align: 'center' while its renderer falls back to 'start' #8229's own bounded census says this is the only disagreement in 8 comparable defaultProps/fallback pairs across flex.tsx and stack.tsx — ⇒ if you find a second, that is a finding, ⛔ not a licence to widen this PR. Its two "not comparable" rows (absent defaultProps entries) are explicitly a different question and ⛔ out of scope.
.default() sometimes carries optionality, not just a value. The ruling anticipates this in note 2. A member written .default(x) without .optional() is optional because of the default; removing it naively makes the key required — which would be a silent accept-set narrowing on a published surface, i.e. exactly the contract change this card is not authorised to make. Check every one of the 41.
Gates
⚠️scripts/pm/dispatch-gates.mjs cannot derive for this repo and refuses rather than guessing: "REFUSING — asked for 'objectstack-ai/objectui', but this checkout is 'objectstack-ai/objectstack' … a sister repo's list is hand-derived from its own package manifest and its own workflow files." The list below is hand-derived from objectui's own package.json at fc32921 — a lead, not a spec. Re-derive against your actual diff:
pnpm --filter @object-ui/types type-check · turbo run type-check for the consuming packages your census touches · lint · pnpm exec vitest run packages/types/ packages/components/ examples/schema-catalog/ · check:sdui-registration-pins (⭐ your flex.tsx hunk is a registration change) · check:doc-types · check:doc-snippets · check:doc-fences · check:spec-symbols · check:control-bytes · changeset-presence.
Capture each exit code to disk before reading any output, then reconcile.
Route
Draft PR, Fixes #7735 on the first line, target main. Merge origin/main before opening, and again if a sibling lands first. Attach needs:contract-review to the PR (the ruling's note 4 — this is the conformance limb, not clause ②). ⛔ Do not enqueue, ⛔ do not flip ready, ⛔ do not auto-merge. ⛔ Do not leave a second claim: this comment is the identity for this card; check that the newest Claim: names your branch, and stop and report if it does not. The assignee field is set and is ⛔ not yours to change.
⚠️ Also add a line to the PR body saying whether #8229 is fully covered by your pin or only partly — that card closes on this landing or narrows to whatever the pin does not reach, and its author cannot tell which without you saying.
Final report: machine-checkable fields only — gates / line_budget / deviations / files_changed, plus the consumer-census count from execution note 1. ⛔ Do not restate the PR body's narrative. Post it to this card with the os-dev-report marker before returning.
{
"issue": 7735,
"status": "done",
"branch": "claude/issue-7735-zod-mirrors-stop-authoring-defaults",
"pr": "https://github.com/objectstack-ai/objectui/pull/8299",
"premise_still_valid": true,
"summary": "Ruling 乙 implemented. Re-derived the population with the TypeScript AST: 41 real .default() call sites in 5 files, not 45 in 7 — the PM's correction confirmed exactly (objectql.zod.ts 3 and complex.zod.ts 1 are docblock prose; those files are untouched). All 41 removed; all 41 were spelled TYPE.optional().default(v), so 0 of 41 carried optionality and the accept set is unchanged. flex.tsx defaultProps.align 'center' -> 'start' (the #8229 third face). Two derived pins land: a call-site AST pin (⛔ not a text search — a text pin is red on landing over 4 legitimate prose mentions, demonstrated) and a defaultProps/fallback pin over flex.tsx + stack.tsx. Both ablated red. NEW MEASUREMENT the ruling did not have: 57 ZodDefault nodes remain reachable from the published barrel after the removal, every one inside a subschema imported by reference from @objectstack/spec, so safeValidateSchema still substitutes on those keys — recorded, ⛔ not filed, not this repo's to remove.",
"consumer_census_note_1": {
"count": 0,
"population_searched": "every non-test importer of @object-ui/types/zod (4 modules) + every .parse/.safeParse call site repo-wide + the whole test corpus",
"detail": "cli/check.ts reads .success only; cli/validate.ts reads result.data.{type,id,label,title,children} — none ever carried a default; plugin-map discards result.data and its schema (objectql.zod.ts) had 0 call sites; scripts/measure-strict-authoring-face.mjs rebuilds its keyProbe with z.any().optional() so defaults never reach it, and it is not wired into any gate. 6105 tests pass unchanged — none asserted a substituted default.",
"bonus_measurement": "the render path never ran this validator at all: SchemaRenderer calls validateSchema from @object-ui/core (structural, {valid,errors}, no zod, no substitution). This answers the open question in comment 5564118219, which was explicitly unmeasured."
},
"files_changed": [
"packages/types/src/zod/layout.zod.ts (-22 .default)",
"packages/types/src/zod/crud.zod.ts (-11 .default)",
"packages/types/src/zod/form.zod.ts (-5 .default)",
"packages/types/src/zod/views.zod.ts (-2 .default)",
"packages/types/src/zod/app.zod.ts (-1 .default)",
"packages/components/src/renderers/layout/flex.tsx (defaultProps.align)",
"packages/types/src/__tests__/zod-mirror-authors-no-defaults-7735.test.ts (new, pin i)",
"packages/components/src/renderers/layout/__tests__/registration-defaults-match-renderer-8229.test.ts (new, pin ii)",
".changeset/7735-zod-mirrors-stop-authoring-defaults.md (new)"
],
"line_budget": "9 files, +642 / -42 (no skills/** in the diff, so no published-skill line ratchet applies)",
"gates": {
"pnpm --filter @object-ui/types --filter @object-ui/components run type-check": "0 (both Done; tsconfig.test.json is in the run — it caught a real error in the new pin, so the test files are provably in the program)",
"pnpm --filter @object-ui/types --filter @object-ui/components run lint": "0 (0 errors)",
"pnpm exec eslint . --no-inline-config --format json": "1 repo-wide — 4421 files judged, 94 pre-existing errors across 78 files, 0 in any file this PR touches, 0 new warnings",
"pnpm exec vitest run packages/types/ packages/components/ packages/cli/ examples/schema-catalog/ scripts/": "0 — 6105 passed / 0 failed",
"check:sdui-registration-pins": "0 (after building the console closure + console: all 16 registrations present, 518 chunks weighed)",
"check:comment-mask-corpus": "0 (the new gate from #8277, run on the merged head; no residue added)",
"check:control-bytes": "0",
"check:doc-types": "0",
"check:doc-snippets": "0 (after the scoped --build-filter build; 624/624 blocks judged)",
"check:doc-fences": "0",
"check:spec-symbols": "0",
"check:phantom-deps": "0",
"check:handler-key-reads": "0",
"check:unreferenced-sources": "0",
"check:published-tsconfig-exclude": "0",
"check:readme-exports": "0",
"check:entry-guard": "0",
"check:dist-completeness": "0",
"check-changeset-presence": "0",
"check-changeset-no-major": "0",
"ci": "in_progress — not waited on"
},
"tests": "6105 passed / 0 failed across packages/types, packages/components, packages/cli, examples/schema-catalog, scripts (re-run on merged head a6ac2bb). ABLATION, run from the committed tree, on-disk mutation proved by grep before each run and restore proved by blob-hash equality (not by exit code), trap on EXIT/INT/TERM, absolute paths: LEG A reintroduce one .default() in layout.zod.ts -> pin (i) 4 failed / 19 passed (call-site census, residue attribution, prose discrimination, and the container parse-output assertion), restored diff=[] blob-match=YES. LEG B defaultProps.align back to 'center' -> pin (ii) 3 failed / 5 passed, restored diff=[] blob-match=YES. LEG C a bare text pin would report complex.zod.ts:1 + objectql.zod.ts:3 = red on landing. No dist/build leg needed: both pins read source off disk and import through the vitest source alias.",
"mcp_calls": "2 — issue_read get + issue_read get_comments on #7735. Everything else went through git, the public-repo payload channel (#8229) and container REST (PR create 201, additive label POST 200, label + full-body read-back).",
"deviations": [
"DECLARED NARROWING: none. The repo-wide `eslint . --no-inline-config` sweep was run in full rather than narrowed (4421 files, ~40s), so the lint reading is a complete measurement.",
"PM gate list item 'turbo run type-check for the consuming packages your census touches' was NOT run beyond @object-ui/types + @object-ui/components: the census touched no consumer (count 0), so no consuming package's type surface moved. Declared to CI.",
"check:sdui-registration-pins, check:doc-snippets and check:readme-exports each first returned exit 2/1 as PREREQUISITE NOT MET (unbuilt packages), NOT as findings. Both were re-run to exit 0 after building what each names; the first readings are recorded as NOT MEASURED, not as failures.",
"Pin (ii) is bounded to flex.tsx + stack.tsx rather than repo-wide, and this is a measured decision, not caution — see out_of_scope_findings."
],
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: 57 ZodDefault nodes remain reachable from @object-ui/types/zod after this change, every one inside a subschema imported by reference from @objectstack/spec (app.active/isDefault, object-view.navigation.{mode,preventNavigation,openNewTab,size}, list-view.sharing.type, kanban.grouping.fields[].{order,collapsed}, page.interfaceConfig.*, dashboard chartConfig.*). Reproducible: safeValidateSchema({type:'object-view',objectName:'account',navigation:{}}) returns navigation:{mode:'page',preventNavigation:false,openNewTab:false,size:'auto'}. The 'one authored document, two shapes' defect therefore survives on those keys. The values are written in @objectstack/spec, so the fix is an objectstack-side card and the ruling would have to be extended to that face; ⛔ not filed from here because it is another repo's surface and the ruling named these seven files. PM to decide whether to raise it upstream.",
"noted, not filed: re-deriving #8229's defaultProps/fallback comparison REPO-WIDE gives 55 comparable pairs and 7 disagreements — the flex.align row fixed here plus action-bar.tsx variant ('outline' vs 'ghost'), action-button.tsx size ('md' vs 'default'), action-group.tsx size ('sm' vs 'default'), pagination.tsx totalPages (10 vs 1), file-upload.tsx buttonText ('Choose files' vs 'DROP PAYLOAD OR CLICK TO UPLOAD'), plugin-markdown/src/index.tsx content (a sample document vs ''). ⛔ Not six more instances of this bug and deliberately not filed: defaultProps is a designer SEED as much as a default claim (pagination's totalPages:10 and flex's own three-button children array are seeds), and whether the two roles may diverge is exactly the question #4631 is holding, pm:on-hold. A repo-wide pin would have decided that by gate rather than by ruling.",
"measurement, not a finding: #8229's own bounded census reproduces exactly — 8 comparable pairs across flex.tsx + stack.tsx, 1 disagreement (flex.align), row for row. #8229 is FULLY covered by this PR; nothing of it narrows into a follow-up.",
"measurement, not a finding: 39 of the 41 de-defaulted keys carry a JSDoc @default tag in their TypeScript twin and 38 of those agree verbatim with the value the mirror used to write. The only divergence is ContainerSchema.maxWidth (tag 'xl', mirror 'lg') — objectui#7361's correction, i.e. the bug itself — and the only two keys with NO tag are FlexLayoutProps.direction/align, objectui#7361/#7734's deliberate removals. So the ruling's note 3(ii) is already satisfied on the JSDoc face and needed no edit there."
]
}
Independent, adversarial review: no summary of the author's or dispatcher's reasoning was taken as input, and CI state was not read. Tree facts were taken from origin/main = 3c6394c (also the PR's merge-base) and the PR head a6ac2bb via git show / git grep, never the working tree. Where running code was needed, the PR head's packages/types/src and renderers/layout were extracted into a scratchpad and run under @objectstack/spec@17.3.0 (lockfile-exact), zod 4.5.4 (lockfile 4.4.3; same 4.x _zod.def protocol), tsx and vitest 4.1.11.
① Derived judgments (accept set / public surface)
Population — correct. 45 .default( text matches in 7 files; 4 are comment lines (complex.zod.ts:690, objectql.zod.ts:451, :629, :847); 41 call sites in 5 files (layout 22 · crud 11 · form 5 · views 2 · app 1). Reproduced two independent ways: an AST census (CallExpression whose callee name is default) gives base text=45/calls=41 with the author's per-file split and head text=4/calls=0; a graph walk of every schema reachable from the published barrel gives 98 ZodDefault on base → 57 on head = 41, so every one of the 41 was reachable. objectql.zod.ts / complex.zod.ts correctly untouched.
Load-bearing optionality claim — correct. 33 of the 41 removed lines are spelled .optional().default( inline; the 8 multi-line chains (form.zod.ts:165/169/177/195, layout.zod.ts:80/194/198/202) each carry .optional() on the preceding context line of the diff. Zero sites carried optionality through .default(). Accept set unchanged, and checked beyond the pin: 22 minimal documents — the pin's 10 plus tabs, scroll-area, resizable, page, action, detail, crud-dialog, detail-view, app, object-view, list-view — all parse green on the head barrel, and AnyComponentSchema is a plain union of discriminated unions with no catch-all arm, so a required-key regression would have been red, not masked.
Output narrowing — as declared. On head every probed arm returns exactly the authored keys except the spec-derived residue (below). Base output showed the substitution on all 20 de-defaulted arms.
Pin (i) genuinely discriminates. The real file zod-mirror-authors-no-defaults-7735.test.ts runs 23/23 on the head copy with the 4 prose mentions in place; with one .default('lg') reinserted in layout.zod.ts it runs 4 failed / 19 passed, the same four assertions the author named (call-site census, residue attribution, prose discrimination, container output). A text pin would report 4 on landing. Non-blocking gaps: the population is the top-level *.zod.ts files (which is the whole directory today — 19 files plus a README, no subdirectories); .prefault() / .catch() are not covered (both 0 on head, measured); "entirely spec-derived" is inferred from the call-site count, not guarded on the import graph.
flex.tsxdefaultProps.align'center' → 'start' — correct value, correct direction (toward flex.tsx:18|| 'start', the authority under the ruling), one-token code change. Pin (ii) real run: 8/8 on head, 3 failed / 5 passed with the seed put back to 'center'. Incorrect: the new comment at flex.tsx:145 names __tests__/registration-defaults-match-renderer-8229.test.tsx; the file added is .test.ts.
57-node residue — (a) measurement correct. Reproduced exactly (57; 58 on the ablated copy). Attribution holds: the mirror files import only zod, @objectstack/spec/ui, @objectstack/spec/data and local non-zod constants, so with 0 call sites every surviving node is spec-constructed. One more substituting key than the author listed — page.kind: 'full' — is covered by the changeset's "and others". (b) Wording — the changeset headline, PR title, pin names and PR body are true at the level they speak (the mirror files author nothing), and the changeset's closing paragraph discloses the residue by name. Not false.
FALSE as written — changeset, Migration paragraph:"Rendering does not change: every renderer already carried its own fallback, and the two that disagreed were the bug." The "two" was the triage seat's 7-key sample; nobody measured the other 34, and the ruling said so. Auditing the 22 layout keys against their renderers on origin/main finds six disagreements, not two: container.maxWidth (mirror 'lg' / ?? 'xl'), flex.align ('center' / || 'start'), grid.columns (3 / let baseCols = 2, grid.tsx:53), text.variant ('body' / no variant — bare span, text.tsx:148, the TextSchema.variant is a nine-value enum that the ui:text renderer never reads — declared-but-unenforced, and it refuses 5 catalog entries #6942 "ABSENCE IS NOT body" rule), resizable.withHandle (true / passed through undefined → no grip, ui/resizable.tsx:86), page.template ('default' → FullWidthTemplate / omitted → pageType dispatch, page.tsx:397, :560ff). For any consumer that renders result.data, rendering changes on all six. The sentence is a published CHANGELOG statement and must say what was measured.
FALSE as of this PR — two shipped @object-ui/components docblocks the change makes stale, unchanged on the head: text.tsx:137-138"The Zod mirror declares .default('body'), which materialises on a document that is PARSED through it" and text.tsx:179"The PUBLISHED default lives in layout.zod.ts (.default('body'))". Both were true on origin/main and are false after this diff — the same class the fix(types): declare the component-node arm of DashboardComponentSchema.widgets on the TypeScript face #8296 review failed on.
Ruling note 3(ii), first half, not delivered and misreported. The ruling's pin (ii) is "JSDoc @default on each affected key equals the renderer fallback". The report's "already satisfied on the JSDoc face" compares the tags with the old mirror value (38 of 39 agree) — the wrong comparator. Against the renderer, four layout.ts tags in this card's own file surface misdescribe it: GridSchema.columns @default 3 (renderer 2), TextSchema.variant @default 'body' (renderer applies none), ResizableSchema.withHandle @default true (renderer falsy), PageNodeSchema.template @default 'default' (renderer dispatches on pageType). The existing layout-default-jsdoc-7361.test.ts pins only maxWidth / align / direction / justify. grid.columns now has four values across four faces (seed 1, renderer 2, JSDoc 3, old mirror 3).
Consumer census 0 — correct, re-derived. Four real importers (cli/check.ts → .success; cli/validate.ts → type/id/label/title/children; plugin-map → .success/.error, returns raw config; scripts/measure-strict-authoring-face.mjs → z.any().optional() twin). plugin-gantt imports GanttConfigSchema from @objectstack/spec/ui, not the barrel, and discards .data; plugin-list / plugin-view mention the barrel in prose only; the vite configs are aliases. The z.infer types on changed schemas (AppSchemaType, DetailSchemaType, CRUDDialogSchemaType, DetailViewSchemaType) are not re-exported by the barrel and have no importers, so no published TypeScript surface moves.
② Semver grading
'@object-ui/types': minor — correct under this repo's convention (AGENTS.md: major is forbidden in changesets and enforced by check-changeset-no-major; objectui's own breaking changes are graded minor with the breaking semantics written in the body). '@object-ui/components': patch — acceptable; the fixed group bumps together anyway.
The body describes the result.data change and the migration but never says it is breaking for readers of result.data; the convention asks for that to be stated. Fold into the rewrite required by ①7.
③ Boundary-flag disposition
turbo run type-check for consumers not run beyond types + components — accepted (census 0, no exported type surface moved). Named falsifiable condition: pnpm --filter @object-ui/cli --filter @object-ui/plugin-map run type-check red on this branch reopens it.
check:sdui-registration-pins / doc-snippets / readme-exports first readings recorded as NOT MEASURED, then re-run — accepted.
57-node spec residue, noted not filed — escalate. It is a reproducible instance of this card's defect class on the same published barrel (app.active / isDefault, page.kind, object-view.navigation.*, list-view.sharing.type, …). "Not this repo's to remove" is a ruling question, not a fact: this repo already unwraps a spec default locally (objectql.zod.ts:452.removeDefault()). The dev was right not to decide it; it needs a card (objectstack-side or a ruling extension) so it is not lost.
"Note 3(ii) already satisfied on the JSDoc face" — not accepted (①9).
Verdict: FAIL — a patch round is owed
Must change:
.changeset/7735-zod-mirrors-stop-authoring-defaults.md — replace "Rendering does not change … the two that disagreed were the bug" with what was measured: the in-repo render path never ran this validator; for a consumer rendering result.data, output changes on every de-defaulted key and rendering changes wherever the mirror's value differed from the renderer's — at least the six layout keys in ①7. State plainly that it is breaking for result.data readers.
packages/components/src/renderers/basic/text.tsx:137-138 and :179 — rewrite so neither says the mirror declares or publishes .default('body').
{
"issue": 7735,
"status": "done",
"round": "contract-review patch round (review 5569648055 — FAIL)",
"branch": "claude/issue-7735-zod-mirrors-stop-authoring-defaults",
"pr": "https://github.com/objectstack-ai/objectui/pull/8299",
"head_sha": "d28d27a",
"merged_origin_main": "8f9d87a",
"premise_still_valid": true,
"review_items_addressed": {
"1_changeset_false_measurement": "DONE — the Migration paragraph no longer says 'rendering does not change … the two that disagreed were the bug'. It now opens '⚠️ Breaking for consumers that read result.data', states that all 41 keys change parse output, and carries the six-row measured table (container.maxWidth, flex.align, grid.columns 3 vs `let baseCols = 2`, text.variant 'body' vs none, resizable.withHandle true vs a bare forward that draws no grip, page.template 'default' vs the pageType dispatch). It keeps the true half — the in-repo render path never ran this validator — as a separate, scoped sentence. The same 'breaking for result.data readers' statement was added to the PR body.",
"2_note_3ii_wrong_comparator": "DONE — re-measured all 41 tags against the RENDERER, not the old mirror value. Four layout.ts tags corrected; a derived pin extended over them; the remainder declared as a named narrowing with a follow-up card.",
"3_text_tsx_stale_docblocks": "DONE — text.tsx:137-138 and :179 rewritten. Neither now says the mirror declares or publishes `.default('body')`; both state it in the past tense and name objectui#7735 as what removed it.",
"4_flex_tsx_pin_filename": "DONE — flex.tsx:145 `.test.tsx` → `.test.ts`."
},
"jsdoc_vs_renderer_remeasured": {
"comparator": "the value a registered renderer applies when the key is ABSENT — NOT the value the zod mirror used to write (that was the error the review caught)",
"total": 41,
"tag_agrees_with_renderer": 19,
"tag_corrected_this_round": 4,
"no_tag_deliberately": 2,
"no_registered_renderer_reads_the_key": 16,
"corrected": {
"GridSchema.columns": "@default 3 -> @default 2 (grid.tsx opens `let baseCols = 2`; the ramp that rewrites it needs `typeof schema.columns === 'number'`, so absence keeps 2)",
"TextSchema.variant": "@default 'body' -> NO tag + prose (text.tsx reads `schema.variant ? VARIANT_CLASS[schema.variant] : undefined`; absence is not body, objectui#6942)",
"ResizableSchema.withHandle": "@default true -> NO tag + prose (resizable.tsx forwards `withHandle={schema.withHandle}` bare; ui/resizable.tsx gates the grip on `{withHandle && …}`, so absence draws NO grip — the tag said the opposite)",
"PageNodeSchema.template": "@default 'default' -> NO tag + prose (resolveTemplate opens `if (!schema.template) return null;` and a null template falls through to the pageType switch; `'default'` IS a real TEMPLATE_REGISTRY key, so the mirror's substitution really did skip that dispatch)"
},
"agree_19_how_verified": "13 by the `schema.x ??/|| 'lit'` sweep; 6 by hand where the renderer uses another shape — container.centered (`!== false`), card.hoverable (`|| isClickable`), card.clickable (`|| !!props.onClick`), aspect-ratio.ratio (`|| 16 / 9`, a BinaryExpression the sweep cannot read as a literal), separator.orientation and icon.size (forwarded bare; the primitive's own default is the value the tag names)",
"not_comparable_16": "CardSchema.variant · PageNodeSchema.isDefault · ActionSchema.{level,method,chainMode,reload,close} · DetailSchema.{showBack,loading} · CRUDDialogSchema.{size,closeOnOutsideClick,closeOnEscape,showClose} · DetailViewSchema.{showBack,loading} · AppComponentSchema.layout. CardSchema.variant is the measured one: neither file registering 'card' (renderers/layout/card.tsx, renderers/layout/containers.tsx) reads schema.variant anywhere, while its two siblings on the same interface ARE read — the control that makes the absence a reading, not a missed search."
},
"named_narrowing": {
"what_is_pinned": "packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts now covers 8 rows (maxWidth, align, direction, justify from #7361/#7734, plus the four corrected here) with 2 negative controls — both sides read off disk, every extraction guarded by a positive control.",
"what_is_not": "33 of the 41 keys stay unpinned against the renderer.",
"why_row_by_row_and_not_a_sweep": "measured, not caution: a generic `schema.x || 'lit'` sweep scores grid.columns as 'read, no literal fallback' and compares nothing — which is exactly how four wrong tags survived. A general instrument must model an initialiser (`let baseCols = 2`), a `!== false` read, a bare forward whose default is the absence of an affordance, and a registry lookup returning null; and it must decide what 'the renderer for this type' means when two files register 'text' with non-overlapping variant vocabularies (renderers/basic/text.tsx and renderers/basic/elements.tsx both do).",
"follow_up_card": "https://github.com/objectstack-ai/objectui/issues/8318 — filed with the 16-key list, the CardSchema.variant measurement, and both open questions (doc defect vs ADR-0049 dead key; whether a general pin is buildable). No labels, no assignee, per filing discipline. Dedupe: REST list over open domain:spec (47) and domain:ui (277), matched locally on @default / JSDoc / renderer fallback, each sweep with a control term that returned non-zero; bounded, ⛔ not a state=all search."
},
"files_changed": [
".changeset/7735-zod-mirrors-stop-authoring-defaults.md (+32/-10 — the Migration rewrite)",
"packages/types/src/layout.ts (+35/-8 — the four corrected @default tags)",
"packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts (+174/-2 — four derived rows, positive controls, prose/tag discrimination control)",
"packages/components/src/renderers/basic/text.tsx (+17/-10 — two stale docblocks)",
"packages/components/src/renderers/layout/flex.tsx (+1/-1 — pin filename)"
],
"line_budget": "patch round: 5 files, +259 / -31. Whole PR vs merge-base, this card's files: 12 files, +890 / -62. No skills/** in the diff, so no published-skill line ratchet applies.",
"gates": {
"pnpm --filter @object-ui/types --filter @object-ui/components run type-check": "0 (both Done)",
"pnpm --filter @object-ui/types --filter @object-ui/components run lint": "0 — 0 errors; per changed file: layout.ts 0/0, the extended pin 0/0, text.tsx 0 errors + 1 pre-existing warning, flex.tsx the same",
"pnpm exec vitest run packages/types/ packages/cli/ examples/schema-catalog/ scripts/": "0 — 301 files, 8491 passed / 0 failed",
"pnpm exec vitest run packages/components/": "0 — 238 files, 2204 passed / 0 failed",
"check:comment-mask-corpus": "0 — re-run after the docblock rewrites, no residue added",
"check:unused-deps": "0 (the gate that landed with #8215)",
"check:control-bytes": "0",
"check:doc-types": "0",
"check:doc-snippets": "0 (after the scoped --build-filter build)",
"check:doc-fences": "0",
"check:readme-exports": "0",
"check:spec-symbols": "0",
"check:phantom-deps": "0",
"check:handler-key-reads": "0",
"check:unreferenced-sources": "0",
"check:published-tsconfig-exclude": "0",
"check:entry-guard": "0",
"check-changeset-presence": "0 (11 source files of 2 released packages, 1 changeset)",
"check-changeset-no-major": "0",
"check:sdui-registration-pins": "0 measured at a6ac2bb; DECLARED, not re-measured at d28d27a — see deviations",
"ci": "in_progress — not waited on"
},
"tests": "10695 tests passed / 0 failed on the merged head d28d27a (8491 across types/cli/schema-catalog/scripts + 2204 across components). Exit codes captured to disk before any output was read. ABLATION of the four new pin rows, run with a trap on EXIT/INT/TERM, absolute paths, on-disk mutation proved by grep before each run and restore proved by blob-hash equality (never by an exit code): LEG D put GridSchema.columns' tag back to 3 — '@default 3' on disk = 1, blob differs from pre-ablation = YES, 2 failed / 30 passed (32), restored blob-match=YES. LEG E re-added the retired @default 'body' to TextSchema.variant — occurrences 1 -> 2 (1 in prose + 1 injected), 2 failed / 30 passed (32), restored blob-match=YES. Round-1 ablations (pin i, pin ii, the text-pin-would-be-red control) still stand and were not re-run: neither pin file changed this round.",
"mcp_calls": "0 this round (2 for the whole task, both in round 1). The review comment, the PR body PATCH, the new card POST and every read-back went through container REST; the dedupe search went through the REST list endpoint plus local grep, with a control term.",
"deviations": [
"check:sdui-registration-pins is DECLARED, not re-measured at d28d27a. It needs a built console (the closure build plus the app is ~6 minutes under the shared lock) and the worktree was rebuilt this round, so its dist is gone. It was measured green at a6ac2bb, and every line packages/components changed since is a COMMENT — `git diff a6ac2bb -- packages/components` yields no non-comment +/- line, verified mechanically — so the bundle's registration set cannot have moved. Falsifiable condition: a red run of this gate on this branch reopens it. CI re-measures it in performance-budget.yml.",
"The type-check first returned exit 2 in the rebuilt worktree with `Cannot find module '@object-ui/i18n'` etc. — PREREQUISITE NOT MET (an unbuilt dependency closure), NOT a finding. Recorded as NOT MEASURED, the closure was built, and the re-run is the 0 in the table.",
"The repo-wide `eslint . --no-inline-config` sweep was NOT re-run this round; the four changed files were linted exactly instead (0 errors each). Round 1's full sweep stands as the population reading.",
"The 19 'tag agrees' rows are verified but NOT all pinned — 6 of them were hand-read because the renderer's shape is not a `||`/`??` literal. That is part of the declared narrowing and is what objectui#8318 holds."
],
"open_questions": [],
"out_of_scope_findings": [
"escalated to PM, ⛔ not widened here as instructed: the six defaultProps/fallback disagreements (for #4631) and the 57 spec-derived ZodDefault nodes. Unchanged from round 1.",
"noted, not filed, NEW this round: two files register the component type 'text' — packages/components/src/renderers/basic/text.tsx and .../basic/elements.tsx — and their `variant` vocabularies do not overlap ('h1'…'overline' vs 'heading'|'subheading'|'body'|'caption'). It matters here only because it is why 'the renderer for this type' is not a function today, which is one of the two reasons the JSDoc pin is row-by-row; it is recorded in objectui#8318's second open question. ⛔ Not filed as a defect of its own: which registration wins and whether that is intended is a ruling question of the objectui#4631 family, not something this card measured.",
"measurement, not a finding: the automated `schema.x ||/?? 'lit'` instrument mis-scored TextSchema.variant as agreeing, because it resolved 'text' to elements.tsx (`props.variant ?? 'body'`) rather than to text.tsx. The four corrections were established by reading the renderers, and each pin row extracts that renderer's actual shape rather than trusting the sweep — the instrument's limit is stated in the pin's own docblock so the next reader does not re-trust it."
]
}
Head judged:d28d27ab5541d95071dc8496cad562529be81204, merge-base with origin/main = 8f9d87a (main has since moved to c842594; not an input here). Every item below was checked against the diff — the full PR diff vs the merge-base (12 files, +890/−62) and the patch delta a6ac2bb..d28d27a — and against the renderers at d28d27a, never against the author's description. The head's packages/types/src and packages/components/src were extracted into a scratchpad and run under @objectstack/spec@17.3.0 / zod 4.5.4 / vitest 4.1.11; CI was not read.
The four required items
Changeset Migration paragraph — corrected, and says what is true. The sentence "Rendering does not change … the two that disagreed were the bug" is gone. The note now opens "⚠️ Breaking for consumers that read result.data", states that all 41 keys change parse output, and carries the six-row measured table. I re-verified every row against the renderer at d28d27a: grid.tsx:54let baseCols = 2; text.tsx:151schema.variant ? VARIANT_CLASS[…] : undefined; renderers/complex/resizable.tsx:40withHandle={schema.withHandle} bare with ui/resizable.tsx:86{withHandle && (; page.tsx:397if (!schema.template) return null; → :566switch (pageType), with 'default': FullWidthTemplate real at :388. The true half it keeps is now scoped — "What does NOT change is rendering inside this repository: the render path never ran this validator at all" — and cannot be read as the general claim I failed. Nit, non-blocking: "six" is stated for the 22 layout keys; the other 19 (5 form rows whose tag agrees with the renderer, 14 read by no registered renderer) carry no rendering change — the PR body says so, the changeset could in one clause.
Ruling note 3(ii) — re-measured against the renderer; the four corrections are substantively right.
GridSchema.columns@default 3 → @default 2: correct. Absence keeps baseCols = 2 and skips the mobile-first ramp, which requires typeof schema.columns === 'number', so an omitted grid renders grid-cols-2 at every breakpoint. Precision nit: an authoredcolumns: 2 takes the ramp (1 → sm:2 → md:2), so absence and an authored 2 are not identical; the tag describes absence, which is what @default means.
TextSchema.variant → no tag + prose: correct. The read is conditional, Tag falls to span, no ||/?? literal anywhere on the key (TextSchema.variant is a nine-value enum that the ui:text renderer never reads — declared-but-unenforced, and it refuses 5 catalog entries #6942). On the two-registrations point: elements.tsx:96 registers with { namespace: 'element', skipFallback: true }, and Registry.register writes element:text only and never overwrites (fullType = namespace:type; if (this.components.has(fullType))), so bare 'text' resolves deterministically to text.tsx. The prose "no renderer ever applied it" is true for TextSchema.
ResizableSchema.withHandle → no tag + prose: correct; an omitted key reaches the handle as undefined and draws no grip — the old true asserted the opposite.
PageNodeSchema.template → no tag + prose: correct; absence dispatches on pageType, and because 'default' is a real registry key the mirror's substitution really did skip that dispatch.
The 19 "tag agrees" rows: each verified by me against its renderer (?? 'xl', !== false, || 'row'/'start', ?? 2, || false, ?? 4, || isClickable / || !!props.onClick, || 'horizontal', || 'vertical', || 16 / 9, bare forwards to a primitive default for separator.orientation / icon.size / button.variant / button.size, !== 'right', || "button", || 'text'). The 16 "no registered renderer reads the key": card.tsx confirmed — no schema.variant read while clickable/hoverable are read.
The extended pin layout-default-jsdoc-7361.test.ts: 32/32 on d28d27a. Ablated three ways: tag back to 3 → row 4 red; a real @default 'body' block tag re-added to variant → 2 red, and the extractor is demonstrably not fooled by the quoted prose; renderer-side let baseCols = 3 → 1 red, so the pin reads both sides.
text.tsx:137-138 and :179 — rewritten. Both now speak in the past tense ("used to declare .default('body') … objectui#7735 removed that"; "carried .default('body') until objectui#7735 removed it"), and the new claim that TextSchema.variant publishes no @default tag is true on this head.
check:sdui-registration-pins declared, not re-measured. Verified mechanically: git diff a6ac2bb d28d27a -- packages/components touches two files (text.tsx, flex.tsx), 29 changed lines, 29 comment / 0 code; the merged main (61 files) touched nothing under packages/components. A comment-only delta cannot move the bundle's registration set. Acceptable, with the falsifiable condition stated and the landing pre-check re-measuring on the merged head (main's own changes are main's, re-measured there).
Substance of the four corrections — covered in item 2: each new tag, or its deliberate absence, is what that renderer does on absence.
Everything passed in round 1 is still intact at d28d27a
Census 4 text / 0 call sites; graph walk 57 ZodDefault; 22 minimal documents green with output equal to input except the spec residue; pin (i) 23/23; pin (ii) 8/8; the five zod-file deltas identical to round 1; defaultProps.align: 'start'.
Verdict: PASS
Non-blocking, for PM: (a) a correcting comment on #8318 (registry semantics; add the literal-shaped rows to its scope); (b) the six defaultProps/fallback disagreements cross-referenced on #4631; (c) a card for the 57 spec-derived ZodDefault residue; (d) the one-clause changeset note on the 19 non-layout keys; (e) the grid ramp precision nit.
ACCEPT — PR #8299 at head d28d27a, domain:spec @ objectui seat, R1, 2026-09-07T11:49Z
Reviewed against GitHub, not against the report's self-description. One patch round; what failed was again the written record, not the contract.
The round that failed
The first delivery had the ruled removal right — and the in-seat review returned FAIL (5569648055) on four written statements the tree contradicted. Two mattered:
⭐ The changeset said "the two that disagreed were the bug / rendering does not change". Measured: six of 22 layout keys disagree with their renderer. That sentence was about to ship as release-note copy.
⭐ Ruling note 3(ii) was reported satisfied against the wrong comparator. The tags had been compared with the value the mirror used to write; the ruling requires the renderer's fallback, which is the entire point of option 乙. Different reference, and four layout.ts tags were misdescribing the renderer.
What the patch round produced
All 41 tags re-measured against the renderer: 19 agree, 4 corrected, 2 deliberately untagged, 16 have no registered renderer reading the key. The corrections, each verified by the reviewer against the renderer at d28d27a:
key
was
now
why
GridSchema.columns
@default 3
@default 2
grid.tsx opens let baseCols = 2; the ramp needs typeof schema.columns === 'number', so absence keeps 2
TextSchema.variant
@default 'body'
no tag + prose
absence is not body
ResizableSchema.withHandle
@default true
no tag + prose
⭐ the renderer forwards bare and the primitive gates on {withHandle && …} — absence draws no grip, so the old tag asserted the opposite of the truth
PageNodeSchema.template
@default 'default'
no tag + prose
resolveTemplate returns null on absence and falls through to the pageType dispatch
The extended pin covers 8 rows with 2 negative controls and went red under three separate ablations (the tag, a block-tag planted in prose, the renderer side).
Checklist conclusions at d28d27a
item
reading
PR shape
draft, base main, first line Fixes #7735; body scanned, no closing keyword beside another open issue
Head pinning
head.sha = d28d27ab5541d95071dc8496cad562529be81204; re-review judged d28d27a; check-suite event carried the same sha
CI
32 check runs, all completed: 29 success + 3 skipped. Zero failures.Lint → success 11:36:10Z, Type Check → success 11:40:45Z; all four test shards green
Round-1 passes intact
census 41→0, the 57-node walk, 22 minimal documents green, pin (i) 23/23, pin (ii) 8/8 — all re-measured, not assumed
Gate
PASS (5570095671) after FAIL (5569648055)
No barrel collision
this PR does not touch packages/types/src/index.ts, so it is clear of the shared-file near-miss #8296/#8297 had
Carrier asymmetry — recorded so an audit does not read it as a strip
needs:contract-review is on PR #8299 only; this card never carried it. That is by construction, not by omission: I judged Clause-②: no at claim time (this narrows parse output and leaves the accept set untouched), so the card side was never hung, and the dev hung the PR side because the ruling's note 4 required it. ⇒ Clearing leaves one removal, and the reason is this paragraph rather than a missing second one.
⚠️ The Clause-②: no call was also vindicated by measurement: all 41 sites were spelled TYPE.optional().default(v), so none carried optionality and the accept set is genuinely unchanged. Had even one been a bare .default(x), removing it would have made that key required — a silent narrowing on a published surface — and the verdict would have had to change.
Deviations dispositioned
check:sdui-registration-pins is declared, not re-measured at this head (it needs a built console). The reviewer verified the argument mechanically: 29 comment lines and zero code lines under packages/components since a6ac2bb, and the main merge touched nothing there. Accepted; CI re-measures it in performance-budget.yml.
The named narrowing — 8 rows pinned, 33 not — was judged a legitimate carrier (#8318) rather than parked work. ⚠️ One of the card's two stated reasons was overstated and I have corrected it there (5570111809): the two text registrations sit in different namespaces (element:text vs ui:text), so the registry never overwrites and "the renderer for this type" is well defined; the real fault was the sweep resolving a bare 'text' to the wrong namespace, which is fixable. ⇒ The honest narrowing is nearer ~23 rows than 33. That correction is on #8318 and ⛔ does not reopen this PR.
Escalations from this card, all carried
#4631 now holds the 55/7 defaultProps census (5569668079); the 57 remaining ZodDefault nodes are #8317; the JSDoc narrowing is #8318.
Verdict: ACCEPT. Clearing the PR-side carrier, flipping ready, enqueueing.
MERGED — PR #8299 landed as 8b7ea39 on main, 2026-09-07T12:06Z
Verified on the tree, not on the merge notification. pm:dispatched stripped and the assignee cleared in the same stroke as this note; bug, finding, priority:p2 and domain:spec stay.
probe
reading on origin/main8b7ea39
the 41 .default() call sites across the five zod mirrors
0 — layout, crud, form, views, app all clear
⭐ the 4 docblock prose mentions (objectql.zod.ts ×3, complex.zod.ts ×1)
zod-mirror-authors-no-defaults-7735.test.ts, layout-default-jsdoc-7361.test.ts, registration-defaults-match-renderer-8229.test.ts — all present
⭐ The second row is the one worth keeping. At dispatch this seat flagged that pin (i), worded literally as the ruling puts it — "no .default( under packages/types/src/zod/**" — would be red on the day it landed, because four of the 45 raw matches are legitimate prose inside docblocks. The implementation wrote the pin against call sites instead, and the landed tree is the proof the distinction was real: the call sites went to zero and the prose survived. A text-matching pin would be failing on main right now.
That correction also fixed the ruling's arithmetic: the population is 41 call sites in five files, not 45 in seven — objectql.zod.ts and complex.zod.ts never had one to remove. The implementing session re-derived it independently with a TypeScript AST census and a ZodDefault graph delta (98 on the base → 57 on the head = 41) and reached the same number.
Why this card took a patch round
The contract was right in round 1. The written record was not: the changeset claimed "the two that disagreed were the bug / rendering does not change" when six of 22 layout keys disagree with their renderer, and ruling note 3(ii) was reported satisfied after comparing JSDoc tags against the old mirror value rather than the renderer fallback — a different comparator, and four layout.ts tags were misdescribing the renderer under it. ⭐ The sharpest of those: ResizableSchema.withHandle carried @default true while the renderer forwards bare and the primitive gates the grip on {withHandle && …} — absence draws no grip, so the tag asserted the opposite of the behaviour.
Both gates on this card caught statements, not code. That is what they are for.
Activity
os-zhuang commentedon Sep 6, 2026
分诊路由 · triage seat
标签:
domain:spec·bug·finding·needs-user-decision·priority:p2⛔ 无
pm:*——needs-user-decision与pm:*不共存。1. 锚点复核(
origin/main0558e0f;卡片测于b74a859)layout.zod.ts:170 maxWidth … .default('lg'):181layout.zod.ts:191 align … .default('center'):202container.tsx:26 schema.maxWidth ?? 'xl'flex.tsx:18 schema.align || 'start'stack.tsx:24 schema.align || 'stretch'StackSchema无任何默认值⇒ 两处不一致确实存在:一个
container未写maxWidth时,走过 mirror 渲染max-w-lg、未走 mirror 渲染max-w-xl。一份文档,两条路径,两种版式。2. ⭐ 我把这个类补测了一圈 —— 结论会改变裁决的形状
卡片只测了 2 个键。我把
FlexSchema的全部默认值、以及 grid / container 的邻近键一并对照了渲染器:flex.direction'row'(:194)flex.tsx:16 || 'row'flex.justify'start'(:198)flex.tsx:17 || 'start'flex.align'center'(:202)flex.tsx:18 || 'start'flex.gap2(:204)flex.tsx:23 ?? 2flex.wrapfalse(:205)flex.tsx:24 || falsegrid.gap4(:231)grid.tsx:88 ?? 4container.maxWidth'lg'(:181)container.tsx:26 ?? 'xl'⭐ 这推翻了一个容易顺手做出的结论:mirror 写默认值并不是普遍与渲染器打架的 —— 绝大多数是对齐的。缺陷是两个离群值,⛔ 不是一条普遍失效的实践。
.default(在 zod 树上共 45 处 ——layout.zod.ts22、crud.zod.ts11、form.zod.ts5、objectql.zod.ts3、views.zod.ts2、app.zod.ts1、complex.zod.ts1。我只审了其中 7 个。⇒ 剩下 38 处未审,任何一处都可能是第三个离群值。3. 决策块(四面)
① 待决问题
一个跑在已发布运行期上的校验器,该不该替作者写入值? 若可以,那两个离群值改哪一边;若不可以,45 处
.default()一起退场。② 选项与代价 ——⚠️ 第 2 节的普查改变了两个选项的价签
'lg'→'xl','center'→'start')parse的输出 ⇒ 已发布行为变更,但渲染结果反而变成路径无关。⛔ 保留"校验器可以写值"这一实践,⇒ 第 38 处未审的离群值仍会一个个冒出来.default(),渲染器回退成唯一真源parse输出会从"带默认值"变成"键缺席" —— ⭐ 渲染结果不变,但任何读result.data并期待默认值在场的消费者会受影响。对 2 个离群值,渲染结果变成路径无关。⭐ 与StackSchema的现状一致,也是 contract-first 的形状③ 已测量、可据以决策的事实
dist的实测输出。StackSchema已经在实践乙 ⇒ 同一文件内部已经不自洽,⇒ 无论裁哪边,都会消除一处现存的内部矛盾。parse写入的默认值(乙的真正风险点)。⇒④ 不决策的后果
objectui#7361刚刚把 JSDoc 的@default改对,⇒ 现在文档面与 mirror 面在树上互相打架(卡片自己指出了这一点,我复核认同)。⛔ 放着不裁,等于让两份"权威"各说各话,而第三份(渲染器)才是真正生效的那一份。4. 对实施者的边界
parse输出变更,风险全在"有没有消费者读result.data的默认值"上 —— 而那一项没有人测过。.default()"(乙)。⭐ 没有这条 pin,第 46 个.default()明天就能重新引入同一个缺陷,而这一整轮测量白做。claude-opus-5,CONTRACT_REVIEW_TIER硬门要求 fable。5. 车道与定级
domain:spec:落点是packages/types/src/zod/**(声明面)。domain:ui—— ⇒ 现在不预先加第二条车道。bug:⭐ 这不是"声明不好看"。同一份文档经过校验与不经过校验,渲染出不同的版式 —— 这是一个用户可见的、路径依赖的错误结果。机械边界测试上它既不扩大也不收窄 accept set(.default()不影响接受与否),修复方向是让两条路径的结果一致 ⇒ Bug 一侧。@defaultJSDoc disagrees with the value the renderer actually applies #7361 那种文档面卡高一档,因为这个面是运行期。6. 去重
卡片自列的 #7361(只改 JSDoc,其 dispatch 明确排除了移动任何默认值,⇒ 正是本卡存在的原因,⛔ 非重复)· #5927(mirror 比 TS 声明更窄的 accept-set 漂移,⇒ 不同轴,⛔ 非重复)· #4631(同类的另一实例)—— 逐一核对,均非重复。
⚠️ 本席未做
本席补一条:与本轮同批的 #7759(mirror 比声明更宽)也不重复 —— 那是 accept-set 轴,本卡是默认值替换轴。⭐ 但三张卡(#5927 更窄 / #7759 更宽 / 本卡 默认值)合起来才是"mirror 与声明之间有几个独立轴"的完整图景,建议裁决者一并过目。
state=all全量搜索,⇒ 有界读数,⛔ 不要升级为 exhaustive。⛔ 本席(triage)的禁令:不认领 · 不派单 · 不写代码 · 不合并 · 不裁决 decision-box 卡。
Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
Ruling recorded — option 乙: the mirrors stop authoring defaults; the renderer is the authoritative face (director seat, decision batch #69, 2026-09-07)
Maintainer reply, verbatim: 「其他同意」 (this card: adopted as recommended).
Ruling and principle. A validator validates; it does not write values into an author's document. The renderer's fallback is the single authoritative default; the JSDoc
@defaultdescribes it; the zod mirror carries no.default()(whatStackSchemaalready does). All 45.default()sites across the sevenpackages/types/src/zod/*.zod.tsfiles are removed, not just the two measured outliers (option 甲 refused — it leaves 38 unaudited sites and no gate).Execution notes.
result.data(the parsed document) expecting a substituted default — that is the only risk of 乙 (rendering does not change, because every renderer already carries its own fallback: 5 of 7 audited sites matched, the two that did not are the bug). Record the count on this card; fix those readers to fall back themselves..default()calls; where a mirror used.default()to imply optionality, keep.optional()..default(underpackages/types/src/zod/**(with the two outliers as the positive control before removal); (ii) JSDoc@defaulton each affected key equals the renderer fallback — fold in finding(components):flex.tsx's own registration declaresdefaultProps.align: 'center'while its renderer falls back to'start'#8229's third face (defaultProps) soalignhas one value, not three.parseoutput changes for documents that omit these keys: changeset on@object-ui/types,needs:contract-review(Clause-② conformance limb: output shape narrows, accept set unchanged).@defaultJSDoc disagrees with the value the renderer actually applies #7361's JSDoc corrections stand (they describe the renderer, which is now the authority).Labels:
needs-user-decision→pm:queue. Ledger on objectstack#12708 (batch #69).Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
Claim: PM loop round R1⚠️ cross-domain, one region: ⚠️
Session:
session_01QtGhnU3WnnWyiWeYQhw2aXBranch:
claude/issue-7735-zod-mirrors-stop-authoring-defaultsWorktree:
objectui-issue-7735Domain:
domain:specFile surface:
packages/types/src/zod/{layout,crud,form,views,app}.zod.ts(the.default()call sites) · their TypeScript JSDoc twinspackages/types/src/{layout,crud,form,views,app}.ts(@defaulttags only) ·packages/components/src/renderers/layout/flex.tsxdefaultProps.align(~:132) — the third face #8229 found, folded in by the ruling · any consumer the census in step 1 turns up · new issue-numbered pins · one.changeset/entry. ⛔ NOT in surface:packages/types/src/zod/{objectql,complex,base}.zod.ts(measured: zero real.default()call sites — see below),packages/types/src/complex.ts(#7952's, this batch),packages/types/src/base.ts(#6950's, this batch), the renderer fallback reads incontainer.tsx/flex.tsx:18/stack.tsx:24(they are the authority — ⛔ they do not move) (stop on breach; explain in the report)Container & model:
L,mode:subagent,model: claude-opus-5(TIER_DEFAULT— ruling implementation, clause ② not met: this narrows parse output and leaves the accept set untouched, which is the mechanical boundary test's "pull back to the declared contract" side)Clause-②: no
Serial constraints cleared:
#7952and#6950(this same batch — file surfaces measured disjoint; #7952 ownscomplex.ts, #6950 ownsbase.ts+sdui-parser+core, ⛔ this card touches none of them) ·#6939(pm:dispatched, groups 2–6 queued, none in flight; its own group-1 report stateslayout.zod.tsand the parity ledger were NOT touched, but its later groups will editzod/— re-read its claim 5521422355 before you touch any mirror it names) ·#8229(bare, unassigned, and its own comment 5565511525 hands itsdefaultPropsface to whoever takes this card — that is this branch) ·domain:uiin flight is#7727and#7635, neither declaresflex.tsx(targeted cross-domain in-flight check, run because this surface crosses a lane line) ·4f9f1eetouchedpackages/types/src/zod/recently — mergeorigin/mainand re-read before editingAdjudication — ⛔ not re-openable
Ruled by the director seat, decision batch #69, 2026-09-07 (comment 5565508219). Maintainer reply, verbatim: 「其他同意」.
Its five execution notes stand in full: (1) census the consumers that read a substituted default off
result.data, record the count here, fix them to fall back themselves; (2) remove the calls, keeping.optional()where.default()was carrying optionality; (3) the two pins; (4) changeset +needs:contract-review; (5) #7361's JSDoc corrections stand.⭐ PM measurement that corrects the ruling's ARITHMETIC — the direction is untouched
Measured on
origin/main=fc32921at 2026-09-07T08:14Z..default(matches 45 times, but 4 of those are prose inside docblocks. There are 41 real call sites, in 5 files, not 45 in 7:.default(layout.zod.tscrud.zod.tsform.zod.tsviews.zod.tsapp.zod.tsobjectql.zod.tscomplex.zod.tsbase.zod.tsPositive control, so a null result would have meant something: both ruled outliers come back as real call sites under the same discriminator (
layout.zod.ts:181.default('lg'),:202.default('center')). The renderer controls are verbatim atcontainer.tsx:26(?? 'xl') andflex.tsx:18(|| 'start').objectql.zod.tsandcomplex.zod.tshave nothing to remove. Editing them to reach "45" would be inventing a diff..default(. Written literally as the ruling words it — "no.default(underpackages/types/src/zod/**" — it is red on the day it lands, because those 4 docblock mentions survive your change and are legitimate prose. Write the pin against call sites (parse/AST, or a matcher that excludes comment lines), or reword the four docblocks, and say in the PR which you chose and why. Prove the pin discriminates: it must go red when a.default()is reintroduced and stay green with the prose in place.⇒ Re-derive the census yourself before you start. 41 is my reading, not a specification — if you get a different number, yours wins and you say so in the report.
PM mechanism assumptions — measure these; you are encouraged to falsify them
result.dataand expects the substituted value to be present. ⛔ Do not assume that population is empty because it is convenient. Find it, count it, record the count on this card, and fix those readers to fall back themselves. If it turns out non-trivial in a way that changes the shape of the fix, stop and report a fork — ⛔ do not silently re-scope and ⛔ do not quietly fall back to option 甲.flex.tsxhunk is one line and it crosses a lane line.defaultProps.align: 'center'→ the renderer's'start'. finding(components):flex.tsx's own registration declaresdefaultProps.align: 'center'while its renderer falls back to'start'#8229's own bounded census says this is the only disagreement in 8 comparabledefaultProps/fallback pairs acrossflex.tsxandstack.tsx— ⇒ if you find a second, that is a finding, ⛔ not a licence to widen this PR. Its two "not comparable" rows (absentdefaultPropsentries) are explicitly a different question and ⛔ out of scope..default()sometimes carries optionality, not just a value. The ruling anticipates this in note 2. A member written.default(x)without.optional()is optional because of the default; removing it naively makes the key required — which would be a silent accept-set narrowing on a published surface, i.e. exactly the contract change this card is not authorised to make. Check every one of the 41.Gates
scripts/pm/dispatch-gates.mjscannot derive for this repo and refuses rather than guessing: "REFUSING — asked for 'objectstack-ai/objectui', but this checkout is 'objectstack-ai/objectstack' … a sister repo's list is hand-derived from its own package manifest and its own workflow files." The list below is hand-derived from objectui's ownpackage.jsonatfc32921— a lead, not a spec. Re-derive against your actual diff:pnpm --filter @object-ui/types type-check·turbo run type-checkfor the consuming packages your census touches ·lint·pnpm exec vitest run packages/types/ packages/components/ examples/schema-catalog/·check:sdui-registration-pins(⭐ yourflex.tsxhunk is a registration change) ·check:doc-types·check:doc-snippets·check:doc-fences·check:spec-symbols·check:control-bytes· changeset-presence.Capture each exit code to disk before reading any output, then reconcile.
Route
Draft PR,
Fixes #7735on the first line, targetmain. Mergeorigin/mainbefore opening, and again if a sibling lands first. Attachneeds:contract-reviewto the PR (the ruling's note 4 — this is the conformance limb, not clause ②). ⛔ Do not enqueue, ⛔ do not flip ready, ⛔ do not auto-merge. ⛔ Do not leave a second claim: this comment is the identity for this card; check that the newestClaim:names your branch, and stop and report if it does not. The assignee field is set and is ⛔ not yours to change.Final report: machine-checkable fields only —
gates/line_budget/deviations/files_changed, plus the consumer-census count from execution note 1. ⛔ Do not restate the PR body's narrative. Post it to this card with theos-dev-reportmarker before returning.Generated with Claude Code
https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX
Generated by Claude Code
claude commentedon Sep 7, 2026
os-dev-report
{ "issue": 7735, "status": "done", "branch": "claude/issue-7735-zod-mirrors-stop-authoring-defaults", "pr": "https://github.com/objectstack-ai/objectui/pull/8299", "premise_still_valid": true, "summary": "Ruling 乙 implemented. Re-derived the population with the TypeScript AST: 41 real .default() call sites in 5 files, not 45 in 7 — the PM's correction confirmed exactly (objectql.zod.ts 3 and complex.zod.ts 1 are docblock prose; those files are untouched). All 41 removed; all 41 were spelled TYPE.optional().default(v), so 0 of 41 carried optionality and the accept set is unchanged. flex.tsx defaultProps.align 'center' -> 'start' (the #8229 third face). Two derived pins land: a call-site AST pin (⛔ not a text search — a text pin is red on landing over 4 legitimate prose mentions, demonstrated) and a defaultProps/fallback pin over flex.tsx + stack.tsx. Both ablated red. NEW MEASUREMENT the ruling did not have: 57 ZodDefault nodes remain reachable from the published barrel after the removal, every one inside a subschema imported by reference from @objectstack/spec, so safeValidateSchema still substitutes on those keys — recorded, ⛔ not filed, not this repo's to remove.", "consumer_census_note_1": { "count": 0, "population_searched": "every non-test importer of @object-ui/types/zod (4 modules) + every .parse/.safeParse call site repo-wide + the whole test corpus", "detail": "cli/check.ts reads .success only; cli/validate.ts reads result.data.{type,id,label,title,children} — none ever carried a default; plugin-map discards result.data and its schema (objectql.zod.ts) had 0 call sites; scripts/measure-strict-authoring-face.mjs rebuilds its keyProbe with z.any().optional() so defaults never reach it, and it is not wired into any gate. 6105 tests pass unchanged — none asserted a substituted default.", "bonus_measurement": "the render path never ran this validator at all: SchemaRenderer calls validateSchema from @object-ui/core (structural, {valid,errors}, no zod, no substitution). This answers the open question in comment 5564118219, which was explicitly unmeasured." }, "files_changed": [ "packages/types/src/zod/layout.zod.ts (-22 .default)", "packages/types/src/zod/crud.zod.ts (-11 .default)", "packages/types/src/zod/form.zod.ts (-5 .default)", "packages/types/src/zod/views.zod.ts (-2 .default)", "packages/types/src/zod/app.zod.ts (-1 .default)", "packages/components/src/renderers/layout/flex.tsx (defaultProps.align)", "packages/types/src/__tests__/zod-mirror-authors-no-defaults-7735.test.ts (new, pin i)", "packages/components/src/renderers/layout/__tests__/registration-defaults-match-renderer-8229.test.ts (new, pin ii)", ".changeset/7735-zod-mirrors-stop-authoring-defaults.md (new)" ], "line_budget": "9 files, +642 / -42 (no skills/** in the diff, so no published-skill line ratchet applies)", "gates": { "pnpm --filter @object-ui/types --filter @object-ui/components run type-check": "0 (both Done; tsconfig.test.json is in the run — it caught a real error in the new pin, so the test files are provably in the program)", "pnpm --filter @object-ui/types --filter @object-ui/components run lint": "0 (0 errors)", "pnpm exec eslint . --no-inline-config --format json": "1 repo-wide — 4421 files judged, 94 pre-existing errors across 78 files, 0 in any file this PR touches, 0 new warnings", "pnpm exec vitest run packages/types/ packages/components/ packages/cli/ examples/schema-catalog/ scripts/": "0 — 6105 passed / 0 failed", "check:sdui-registration-pins": "0 (after building the console closure + console: all 16 registrations present, 518 chunks weighed)", "check:comment-mask-corpus": "0 (the new gate from #8277, run on the merged head; no residue added)", "check:control-bytes": "0", "check:doc-types": "0", "check:doc-snippets": "0 (after the scoped --build-filter build; 624/624 blocks judged)", "check:doc-fences": "0", "check:spec-symbols": "0", "check:phantom-deps": "0", "check:handler-key-reads": "0", "check:unreferenced-sources": "0", "check:published-tsconfig-exclude": "0", "check:readme-exports": "0", "check:entry-guard": "0", "check:dist-completeness": "0", "check-changeset-presence": "0", "check-changeset-no-major": "0", "ci": "in_progress — not waited on" }, "tests": "6105 passed / 0 failed across packages/types, packages/components, packages/cli, examples/schema-catalog, scripts (re-run on merged head a6ac2bb). ABLATION, run from the committed tree, on-disk mutation proved by grep before each run and restore proved by blob-hash equality (not by exit code), trap on EXIT/INT/TERM, absolute paths: LEG A reintroduce one .default() in layout.zod.ts -> pin (i) 4 failed / 19 passed (call-site census, residue attribution, prose discrimination, and the container parse-output assertion), restored diff=[] blob-match=YES. LEG B defaultProps.align back to 'center' -> pin (ii) 3 failed / 5 passed, restored diff=[] blob-match=YES. LEG C a bare text pin would report complex.zod.ts:1 + objectql.zod.ts:3 = red on landing. No dist/build leg needed: both pins read source off disk and import through the vitest source alias.", "mcp_calls": "2 — issue_read get + issue_read get_comments on #7735. Everything else went through git, the public-repo payload channel (#8229) and container REST (PR create 201, additive label POST 200, label + full-body read-back).", "deviations": [ "DECLARED NARROWING: none. The repo-wide `eslint . --no-inline-config` sweep was run in full rather than narrowed (4421 files, ~40s), so the lint reading is a complete measurement.", "PM gate list item 'turbo run type-check for the consuming packages your census touches' was NOT run beyond @object-ui/types + @object-ui/components: the census touched no consumer (count 0), so no consuming package's type surface moved. Declared to CI.", "check:sdui-registration-pins, check:doc-snippets and check:readme-exports each first returned exit 2/1 as PREREQUISITE NOT MET (unbuilt packages), NOT as findings. Both were re-run to exit 0 after building what each names; the first readings are recorded as NOT MEASURED, not as failures.", "Pin (ii) is bounded to flex.tsx + stack.tsx rather than repo-wide, and this is a measured decision, not caution — see out_of_scope_findings." ], "open_questions": [], "out_of_scope_findings": [ "noted, not filed: 57 ZodDefault nodes remain reachable from @object-ui/types/zod after this change, every one inside a subschema imported by reference from @objectstack/spec (app.active/isDefault, object-view.navigation.{mode,preventNavigation,openNewTab,size}, list-view.sharing.type, kanban.grouping.fields[].{order,collapsed}, page.interfaceConfig.*, dashboard chartConfig.*). Reproducible: safeValidateSchema({type:'object-view',objectName:'account',navigation:{}}) returns navigation:{mode:'page',preventNavigation:false,openNewTab:false,size:'auto'}. The 'one authored document, two shapes' defect therefore survives on those keys. The values are written in @objectstack/spec, so the fix is an objectstack-side card and the ruling would have to be extended to that face; ⛔ not filed from here because it is another repo's surface and the ruling named these seven files. PM to decide whether to raise it upstream.", "noted, not filed: re-deriving #8229's defaultProps/fallback comparison REPO-WIDE gives 55 comparable pairs and 7 disagreements — the flex.align row fixed here plus action-bar.tsx variant ('outline' vs 'ghost'), action-button.tsx size ('md' vs 'default'), action-group.tsx size ('sm' vs 'default'), pagination.tsx totalPages (10 vs 1), file-upload.tsx buttonText ('Choose files' vs 'DROP PAYLOAD OR CLICK TO UPLOAD'), plugin-markdown/src/index.tsx content (a sample document vs ''). ⛔ Not six more instances of this bug and deliberately not filed: defaultProps is a designer SEED as much as a default claim (pagination's totalPages:10 and flex's own three-button children array are seeds), and whether the two roles may diverge is exactly the question #4631 is holding, pm:on-hold. A repo-wide pin would have decided that by gate rather than by ruling.", "measurement, not a finding: #8229's own bounded census reproduces exactly — 8 comparable pairs across flex.tsx + stack.tsx, 1 disagreement (flex.align), row for row. #8229 is FULLY covered by this PR; nothing of it narrows into a follow-up.", "measurement, not a finding: 39 of the 41 de-defaulted keys carry a JSDoc @default tag in their TypeScript twin and 38 of those agree verbatim with the value the mirror used to write. The only divergence is ContainerSchema.maxWidth (tag 'xl', mirror 'lg') — objectui#7361's correction, i.e. the bug itself — and the only two keys with NO tag are FlexLayoutProps.direction/align, objectui#7361/#7734's deliberate removals. So the ruling's note 3(ii) is already satisfied on the JSDoc face and needed no edit there." ] }Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
In-seat contract review — PR #8299 (Clause-② conformance limb, ruling batch #69 option 乙) — FAIL
Implemented-by: claude/issue-7735-zod-mirrors-stop-authoring-defaults
Reviewed-by: session_01QtGhnU3WnnWyiWeYQhw2aX (contract-review-tier subagent)
Independent, adversarial review: no summary of the author's or dispatcher's reasoning was taken as input, and CI state was not read. Tree facts were taken from
origin/main=3c6394c(also the PR's merge-base) and the PR heada6ac2bbviagit show/git grep, never the working tree. Where running code was needed, the PR head'spackages/types/srcandrenderers/layoutwere extracted into a scratchpad and run under@objectstack/spec@17.3.0(lockfile-exact), zod 4.5.4 (lockfile 4.4.3; same 4.x_zod.defprotocol), tsx and vitest 4.1.11.① Derived judgments (accept set / public surface)
.default(text matches in 7 files; 4 are comment lines (complex.zod.ts:690,objectql.zod.ts:451,:629,:847); 41 call sites in 5 files (layout 22 · crud 11 · form 5 · views 2 · app 1). Reproduced two independent ways: an AST census (CallExpressionwhose callee name isdefault) gives base text=45/calls=41 with the author's per-file split and head text=4/calls=0; a graph walk of every schema reachable from the published barrel gives 98ZodDefaulton base → 57 on head = 41, so every one of the 41 was reachable.objectql.zod.ts/complex.zod.tscorrectly untouched..optional().default(inline; the 8 multi-line chains (form.zod.ts:165/169/177/195,layout.zod.ts:80/194/198/202) each carry.optional()on the preceding context line of the diff. Zero sites carried optionality through.default(). Accept set unchanged, and checked beyond the pin: 22 minimal documents — the pin's 10 plustabs,scroll-area,resizable,page,action,detail,crud-dialog,detail-view,app,object-view,list-view— all parse green on the head barrel, andAnyComponentSchemais a plain union of discriminated unions with no catch-all arm, so a required-key regression would have been red, not masked.zod-mirror-authors-no-defaults-7735.test.tsruns 23/23 on the head copy with the 4 prose mentions in place; with one.default('lg')reinserted inlayout.zod.tsit runs 4 failed / 19 passed, the same four assertions the author named (call-site census, residue attribution, prose discrimination,containeroutput). A text pin would report 4 on landing. Non-blocking gaps: the population is the top-level*.zod.tsfiles (which is the whole directory today — 19 files plus a README, no subdirectories);.prefault()/.catch()are not covered (both 0 on head, measured); "entirely spec-derived" is inferred from the call-site count, not guarded on the import graph.flex.tsxdefaultProps.align'center'→'start'— correct value, correct direction (towardflex.tsx:18|| 'start', the authority under the ruling), one-token code change. Pin (ii) real run: 8/8 on head, 3 failed / 5 passed with the seed put back to'center'. Incorrect: the new comment atflex.tsx:145names__tests__/registration-defaults-match-renderer-8229.test.tsx; the file added is.test.ts.zod,@objectstack/spec/ui,@objectstack/spec/dataand local non-zod constants, so with 0 call sites every surviving node is spec-constructed. One more substituting key than the author listed —page.kind: 'full'— is covered by the changeset's "and others". (b) Wording — the changeset headline, PR title, pin names and PR body are true at the level they speak (the mirror files author nothing), and the changeset's closing paragraph discloses the residue by name. Not false.origin/mainfinds six disagreements, not two:container.maxWidth(mirror'lg'/?? 'xl'),flex.align('center'/|| 'start'),grid.columns(3/let baseCols = 2,grid.tsx:53),text.variant('body'/ no variant — barespan,text.tsx:148, theTextSchema.variantis a nine-value enum that theui:textrenderer never reads — declared-but-unenforced, and it refuses 5 catalog entries #6942 "ABSENCE IS NOTbody" rule),resizable.withHandle(true/ passed throughundefined→ no grip,ui/resizable.tsx:86),page.template('default'→FullWidthTemplate/ omitted →pageTypedispatch,page.tsx:397,:560ff). For any consumer that rendersresult.data, rendering changes on all six. The sentence is a published CHANGELOG statement and must say what was measured.@object-ui/componentsdocblocks the change makes stale, unchanged on the head:text.tsx:137-138"The Zod mirror declares.default('body'), which materialises on a document that is PARSED through it" andtext.tsx:179"The PUBLISHED default lives inlayout.zod.ts(.default('body'))". Both were true onorigin/mainand are false after this diff — the same class the fix(types): declare the component-node arm of DashboardComponentSchema.widgets on the TypeScript face #8296 review failed on.@defaulton each affected key equals the renderer fallback". The report's "already satisfied on the JSDoc face" compares the tags with the old mirror value (38 of 39 agree) — the wrong comparator. Against the renderer, fourlayout.tstags in this card's own file surface misdescribe it:GridSchema.columns @default 3(renderer 2),TextSchema.variant @default 'body'(renderer applies none),ResizableSchema.withHandle @default true(renderer falsy),PageNodeSchema.template @default 'default'(renderer dispatches onpageType). The existinglayout-default-jsdoc-7361.test.tspins onlymaxWidth/align/direction/justify.grid.columnsnow has four values across four faces (seed1, renderer2, JSDoc3, old mirror3).cli/check.ts→.success;cli/validate.ts→type/id/label/title/children;plugin-map→.success/.error, returns rawconfig;scripts/measure-strict-authoring-face.mjs→z.any().optional()twin).plugin-ganttimportsGanttConfigSchemafrom@objectstack/spec/ui, not the barrel, and discards.data;plugin-list/plugin-viewmention the barrel in prose only; the vite configs are aliases. Thez.infertypes on changed schemas (AppSchemaType,DetailSchemaType,CRUDDialogSchemaType,DetailViewSchemaType) are not re-exported by the barrel and have no importers, so no published TypeScript surface moves.② Semver grading
'@object-ui/types': minor— correct under this repo's convention (AGENTS.md:majoris forbidden in changesets and enforced bycheck-changeset-no-major; objectui's own breaking changes are gradedminorwith the breaking semantics written in the body).'@object-ui/components': patch— acceptable; the fixed group bumps together anyway.result.datachange and the migration but never says it is breaking for readers ofresult.data; the convention asks for that to be stated. Fold into the rewrite required by ①7.③ Boundary-flag disposition
turbo run type-checkfor consumers not run beyond types + components — accepted (census 0, no exported type surface moved). Named falsifiable condition:pnpm --filter @object-ui/cli --filter @object-ui/plugin-map run type-checkred on this branch reopens it.check:sdui-registration-pins/doc-snippets/readme-exportsfirst readings recorded as NOT MEASURED, then re-run — accepted.flex.tsx+stack.tsx; repo-wide 55 pairs / 7 disagreements, six neither fixed nor filed — correct scope discipline on the fix: the ruling folded in exactly finding(components):flex.tsx's own registration declaresdefaultProps.align: 'center'while its renderer falls back to'start'#8229'salignface and A component type has THREE declared surfaces that disagree (TS schema type / registry meta inputs / renderer prop reads), and nothing reconciles or enforces them #4631 (pm:on-hold) holds whether seed and fallback may diverge; a repo-wide gate would have ruled by test. Escalate on the record-keeping: PM should drop a one-comment cross-reference on A component type has THREE declared surfaces that disagree (TS schema type / registry meta inputs / renderer prop reads), and nothing reconciles or enforces them #4631 itself, where the six will be read; and note the instrument is pattern-bounded (schema.x || lit), so 55/7 is a floor —grid.columnsseed1vslet baseCols = 2is invisible to it.app.active/isDefault,page.kind,object-view.navigation.*,list-view.sharing.type, …). "Not this repo's to remove" is a ruling question, not a fact: this repo already unwraps a spec default locally (objectql.zod.ts:452.removeDefault()). The dev was right not to decide it; it needs a card (objectstack-side or a ruling extension) so it is not lost.flex.tsx's own registration declaresdefaultProps.align: 'center'while its renderer falls back to'start'#8229 is FULLY covered" — accepted. Its measured defect (defaultProps.align) is reconciled and pinned; its two "not comparable" rows were out of scope by its own text. finding(components):flex.tsx's own registration declaresdefaultProps.align: 'center'while its renderer falls back to'start'#8229 can close on landing.Verdict: FAIL — a patch round is owed
Must change:
.changeset/7735-zod-mirrors-stop-authoring-defaults.md— replace "Rendering does not change … the two that disagreed were the bug" with what was measured: the in-repo render path never ran this validator; for a consumer renderingresult.data, output changes on every de-defaulted key and rendering changes wherever the mirror's value differed from the renderer's — at least the six layout keys in ①7. State plainly that it is breaking forresult.datareaders.packages/components/src/renderers/basic/text.tsx:137-138and:179— rewrite so neither says the mirror declares or publishes.default('body').packages/components/src/renderers/layout/flex.tsx:145—.test.tsx→.test.ts.layout.tstags in ①9 so each describes the renderer (or remove the tag with prose, as finding(types): two layout schemas'@defaultJSDoc disagrees with the value the renderer actually applies #7361 did foralign); re-measure the remaining 37 against the renderer and replace the report's "already satisfied" claim with that measurement; extend the derived pin (the finding(types): two layout schemas'@defaultJSDoc disagrees with the value the renderer actually applies #7361 pattern, or the new one) over at least the corrected rows — or, if the full JSDoc-equals-renderer pin is out of reach in this round, declare a named narrowing in the PR body with the follow-up card.Not required in this round: touching the six
defaultPropsdisagreements; unwrapping the spec residue.Nothing in the accept set, the 41-site removal, pin (i), pin (ii) or the
flex.tsxvalue needs to move — those are correct and were reproduced.Generated by Claude Code
claude commentedon Sep 7, 2026
os-dev-report
{ "issue": 7735, "status": "done", "round": "contract-review patch round (review 5569648055 — FAIL)", "branch": "claude/issue-7735-zod-mirrors-stop-authoring-defaults", "pr": "https://github.com/objectstack-ai/objectui/pull/8299", "head_sha": "d28d27a", "merged_origin_main": "8f9d87a", "premise_still_valid": true, "review_items_addressed": { "1_changeset_false_measurement": "DONE — the Migration paragraph no longer says 'rendering does not change … the two that disagreed were the bug'. It now opens '⚠️ Breaking for consumers that read result.data', states that all 41 keys change parse output, and carries the six-row measured table (container.maxWidth, flex.align, grid.columns 3 vs `let baseCols = 2`, text.variant 'body' vs none, resizable.withHandle true vs a bare forward that draws no grip, page.template 'default' vs the pageType dispatch). It keeps the true half — the in-repo render path never ran this validator — as a separate, scoped sentence. The same 'breaking for result.data readers' statement was added to the PR body.", "2_note_3ii_wrong_comparator": "DONE — re-measured all 41 tags against the RENDERER, not the old mirror value. Four layout.ts tags corrected; a derived pin extended over them; the remainder declared as a named narrowing with a follow-up card.", "3_text_tsx_stale_docblocks": "DONE — text.tsx:137-138 and :179 rewritten. Neither now says the mirror declares or publishes `.default('body')`; both state it in the past tense and name objectui#7735 as what removed it.", "4_flex_tsx_pin_filename": "DONE — flex.tsx:145 `.test.tsx` → `.test.ts`." }, "jsdoc_vs_renderer_remeasured": { "comparator": "the value a registered renderer applies when the key is ABSENT — NOT the value the zod mirror used to write (that was the error the review caught)", "total": 41, "tag_agrees_with_renderer": 19, "tag_corrected_this_round": 4, "no_tag_deliberately": 2, "no_registered_renderer_reads_the_key": 16, "corrected": { "GridSchema.columns": "@default 3 -> @default 2 (grid.tsx opens `let baseCols = 2`; the ramp that rewrites it needs `typeof schema.columns === 'number'`, so absence keeps 2)", "TextSchema.variant": "@default 'body' -> NO tag + prose (text.tsx reads `schema.variant ? VARIANT_CLASS[schema.variant] : undefined`; absence is not body, objectui#6942)", "ResizableSchema.withHandle": "@default true -> NO tag + prose (resizable.tsx forwards `withHandle={schema.withHandle}` bare; ui/resizable.tsx gates the grip on `{withHandle && …}`, so absence draws NO grip — the tag said the opposite)", "PageNodeSchema.template": "@default 'default' -> NO tag + prose (resolveTemplate opens `if (!schema.template) return null;` and a null template falls through to the pageType switch; `'default'` IS a real TEMPLATE_REGISTRY key, so the mirror's substitution really did skip that dispatch)" }, "agree_19_how_verified": "13 by the `schema.x ??/|| 'lit'` sweep; 6 by hand where the renderer uses another shape — container.centered (`!== false`), card.hoverable (`|| isClickable`), card.clickable (`|| !!props.onClick`), aspect-ratio.ratio (`|| 16 / 9`, a BinaryExpression the sweep cannot read as a literal), separator.orientation and icon.size (forwarded bare; the primitive's own default is the value the tag names)", "not_comparable_16": "CardSchema.variant · PageNodeSchema.isDefault · ActionSchema.{level,method,chainMode,reload,close} · DetailSchema.{showBack,loading} · CRUDDialogSchema.{size,closeOnOutsideClick,closeOnEscape,showClose} · DetailViewSchema.{showBack,loading} · AppComponentSchema.layout. CardSchema.variant is the measured one: neither file registering 'card' (renderers/layout/card.tsx, renderers/layout/containers.tsx) reads schema.variant anywhere, while its two siblings on the same interface ARE read — the control that makes the absence a reading, not a missed search." }, "named_narrowing": { "what_is_pinned": "packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts now covers 8 rows (maxWidth, align, direction, justify from #7361/#7734, plus the four corrected here) with 2 negative controls — both sides read off disk, every extraction guarded by a positive control.", "what_is_not": "33 of the 41 keys stay unpinned against the renderer.", "why_row_by_row_and_not_a_sweep": "measured, not caution: a generic `schema.x || 'lit'` sweep scores grid.columns as 'read, no literal fallback' and compares nothing — which is exactly how four wrong tags survived. A general instrument must model an initialiser (`let baseCols = 2`), a `!== false` read, a bare forward whose default is the absence of an affordance, and a registry lookup returning null; and it must decide what 'the renderer for this type' means when two files register 'text' with non-overlapping variant vocabularies (renderers/basic/text.tsx and renderers/basic/elements.tsx both do).", "follow_up_card": "https://github.com/objectstack-ai/objectui/issues/8318 — filed with the 16-key list, the CardSchema.variant measurement, and both open questions (doc defect vs ADR-0049 dead key; whether a general pin is buildable). No labels, no assignee, per filing discipline. Dedupe: REST list over open domain:spec (47) and domain:ui (277), matched locally on @default / JSDoc / renderer fallback, each sweep with a control term that returned non-zero; bounded, ⛔ not a state=all search." }, "files_changed": [ ".changeset/7735-zod-mirrors-stop-authoring-defaults.md (+32/-10 — the Migration rewrite)", "packages/types/src/layout.ts (+35/-8 — the four corrected @default tags)", "packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts (+174/-2 — four derived rows, positive controls, prose/tag discrimination control)", "packages/components/src/renderers/basic/text.tsx (+17/-10 — two stale docblocks)", "packages/components/src/renderers/layout/flex.tsx (+1/-1 — pin filename)" ], "line_budget": "patch round: 5 files, +259 / -31. Whole PR vs merge-base, this card's files: 12 files, +890 / -62. No skills/** in the diff, so no published-skill line ratchet applies.", "gates": { "pnpm --filter @object-ui/types --filter @object-ui/components run type-check": "0 (both Done)", "pnpm --filter @object-ui/types --filter @object-ui/components run lint": "0 — 0 errors; per changed file: layout.ts 0/0, the extended pin 0/0, text.tsx 0 errors + 1 pre-existing warning, flex.tsx the same", "pnpm exec vitest run packages/types/ packages/cli/ examples/schema-catalog/ scripts/": "0 — 301 files, 8491 passed / 0 failed", "pnpm exec vitest run packages/components/": "0 — 238 files, 2204 passed / 0 failed", "check:comment-mask-corpus": "0 — re-run after the docblock rewrites, no residue added", "check:unused-deps": "0 (the gate that landed with #8215)", "check:control-bytes": "0", "check:doc-types": "0", "check:doc-snippets": "0 (after the scoped --build-filter build)", "check:doc-fences": "0", "check:readme-exports": "0", "check:spec-symbols": "0", "check:phantom-deps": "0", "check:handler-key-reads": "0", "check:unreferenced-sources": "0", "check:published-tsconfig-exclude": "0", "check:entry-guard": "0", "check-changeset-presence": "0 (11 source files of 2 released packages, 1 changeset)", "check-changeset-no-major": "0", "check:sdui-registration-pins": "0 measured at a6ac2bb; DECLARED, not re-measured at d28d27a — see deviations", "ci": "in_progress — not waited on" }, "tests": "10695 tests passed / 0 failed on the merged head d28d27a (8491 across types/cli/schema-catalog/scripts + 2204 across components). Exit codes captured to disk before any output was read. ABLATION of the four new pin rows, run with a trap on EXIT/INT/TERM, absolute paths, on-disk mutation proved by grep before each run and restore proved by blob-hash equality (never by an exit code): LEG D put GridSchema.columns' tag back to 3 — '@default 3' on disk = 1, blob differs from pre-ablation = YES, 2 failed / 30 passed (32), restored blob-match=YES. LEG E re-added the retired @default 'body' to TextSchema.variant — occurrences 1 -> 2 (1 in prose + 1 injected), 2 failed / 30 passed (32), restored blob-match=YES. Round-1 ablations (pin i, pin ii, the text-pin-would-be-red control) still stand and were not re-run: neither pin file changed this round.", "mcp_calls": "0 this round (2 for the whole task, both in round 1). The review comment, the PR body PATCH, the new card POST and every read-back went through container REST; the dedupe search went through the REST list endpoint plus local grep, with a control term.", "deviations": [ "check:sdui-registration-pins is DECLARED, not re-measured at d28d27a. It needs a built console (the closure build plus the app is ~6 minutes under the shared lock) and the worktree was rebuilt this round, so its dist is gone. It was measured green at a6ac2bb, and every line packages/components changed since is a COMMENT — `git diff a6ac2bb -- packages/components` yields no non-comment +/- line, verified mechanically — so the bundle's registration set cannot have moved. Falsifiable condition: a red run of this gate on this branch reopens it. CI re-measures it in performance-budget.yml.", "The type-check first returned exit 2 in the rebuilt worktree with `Cannot find module '@object-ui/i18n'` etc. — PREREQUISITE NOT MET (an unbuilt dependency closure), NOT a finding. Recorded as NOT MEASURED, the closure was built, and the re-run is the 0 in the table.", "The repo-wide `eslint . --no-inline-config` sweep was NOT re-run this round; the four changed files were linted exactly instead (0 errors each). Round 1's full sweep stands as the population reading.", "The 19 'tag agrees' rows are verified but NOT all pinned — 6 of them were hand-read because the renderer's shape is not a `||`/`??` literal. That is part of the declared narrowing and is what objectui#8318 holds." ], "open_questions": [], "out_of_scope_findings": [ "escalated to PM, ⛔ not widened here as instructed: the six defaultProps/fallback disagreements (for #4631) and the 57 spec-derived ZodDefault nodes. Unchanged from round 1.", "noted, not filed, NEW this round: two files register the component type 'text' — packages/components/src/renderers/basic/text.tsx and .../basic/elements.tsx — and their `variant` vocabularies do not overlap ('h1'…'overline' vs 'heading'|'subheading'|'body'|'caption'). It matters here only because it is why 'the renderer for this type' is not a function today, which is one of the two reasons the JSDoc pin is row-by-row; it is recorded in objectui#8318's second open question. ⛔ Not filed as a defect of its own: which registration wins and whether that is intended is a ruling question of the objectui#4631 family, not something this card measured.", "measurement, not a finding: the automated `schema.x ||/?? 'lit'` instrument mis-scored TextSchema.variant as agreeing, because it resolved 'text' to elements.tsx (`props.variant ?? 'body'`) rather than to text.tsx. The four corrections were established by reading the renderers, and each pin row extracts that renderer's actual shape rather than trusting the sweep — the instrument's limit is stated in the pin's own docblock so the next reader does not re-trust it." ] }Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
In-seat contract re-review — PR #8299 at head
d28d27a(patch round for review 5569648055) — PASSImplemented-by: claude/issue-7735-zod-mirrors-stop-authoring-defaults
Reviewed-by: session_01QtGhnU3WnnWyiWeYQhw2aX (contract-review-tier subagent)
Head judged:
d28d27ab5541d95071dc8496cad562529be81204, merge-base withorigin/main=8f9d87a(main has since moved toc842594; not an input here). Every item below was checked against the diff — the full PR diff vs the merge-base (12 files, +890/−62) and the patch deltaa6ac2bb..d28d27a— and against the renderers atd28d27a, never against the author's description. The head'spackages/types/srcandpackages/components/srcwere extracted into a scratchpad and run under@objectstack/spec@17.3.0/ zod 4.5.4 / vitest 4.1.11; CI was not read.The four required items
result.data", states that all 41 keys change parse output, and carries the six-row measured table. I re-verified every row against the renderer atd28d27a:grid.tsx:54let baseCols = 2;text.tsx:151schema.variant ? VARIANT_CLASS[…] : undefined;renderers/complex/resizable.tsx:40withHandle={schema.withHandle}bare withui/resizable.tsx:86{withHandle && (;page.tsx:397if (!schema.template) return null;→:566switch (pageType), with'default': FullWidthTemplatereal at:388. The true half it keeps is now scoped — "What does NOT change is rendering inside this repository: the render path never ran this validator at all" — and cannot be read as the general claim I failed. Nit, non-blocking: "six" is stated for the 22 layout keys; the other 19 (5 form rows whose tag agrees with the renderer, 14 read by no registered renderer) carry no rendering change — the PR body says so, the changeset could in one clause.GridSchema.columns@default 3→@default 2: correct. Absence keepsbaseCols = 2and skips the mobile-first ramp, which requirestypeof schema.columns === 'number', so an omitted grid rendersgrid-cols-2at every breakpoint. Precision nit: an authoredcolumns: 2takes the ramp (1 → sm:2 → md:2), so absence and an authored2are not identical; the tag describes absence, which is what@defaultmeans.TextSchema.variant→ no tag + prose: correct. The read is conditional,Tagfalls tospan, no||/??literal anywhere on the key (TextSchema.variantis a nine-value enum that theui:textrenderer never reads — declared-but-unenforced, and it refuses 5 catalog entries #6942). On the two-registrations point:elements.tsx:96registers with{ namespace: 'element', skipFallback: true }, andRegistry.registerwriteselement:textonly and never overwrites (fullType = namespace:type;if (this.components.has(fullType))), so bare'text'resolves deterministically totext.tsx. The prose "no renderer ever applied it" is true forTextSchema.ResizableSchema.withHandle→ no tag + prose: correct; an omitted key reaches the handle asundefinedand draws no grip — the oldtrueasserted the opposite.PageNodeSchema.template→ no tag + prose: correct; absence dispatches onpageType, and because'default'is a real registry key the mirror's substitution really did skip that dispatch.?? 'xl',!== false,|| 'row'/'start',?? 2,|| false,?? 4,|| isClickable/|| !!props.onClick,|| 'horizontal',|| 'vertical',|| 16 / 9, bare forwards to a primitive default forseparator.orientation/icon.size/button.variant/button.size,!== 'right',|| "button",|| 'text'). The 16 "no registered renderer reads the key":card.tsxconfirmed — noschema.variantread whileclickable/hoverableare read.layout-default-jsdoc-7361.test.ts: 32/32 ond28d27a. Ablated three ways: tag back to3→ row 4 red; a real@default 'body'block tag re-added tovariant→ 2 red, and the extractor is demonstrably not fooled by the quoted prose; renderer-sidelet baseCols = 3→ 1 red, so the pin reads both sides.text.tsx:137-138and:179— rewritten. Both now speak in the past tense ("used to declare.default('body')… objectui#7735 removed that"; "carried.default('body')until objectui#7735 removed it"), and the new claim thatTextSchema.variantpublishes no@defaulttag is true on this head.flex.tsx:145—.test.ts.align: 'start'unchanged.The three weighings
@defaultthat no registered renderer reads, and only 8 of 41 are pinned against the renderer #8318. The 16 no-referent keys are a genuine ruling question (doc defect vs ADR-0049 dead key) that this PR had no authority to decide; finding(types): 16 of the 41 keys #7735 de-defaulted publish a JSDoc@defaultthat no registered renderer reads, and only 8 of 41 are pinned against the renderer #8318 is the right carrier and itsCardSchema.variantcontrol is real. The non-literal-shape limit is real — my ownschema.x ||/?? 'lit'sweep missed all four corrected rows. But the'text'ambiguity is not a real limit: the registry resolves it (above); the sweep mis-scoredvariantbecause it resolved'text'by file rather than by registry key — an instrument bug. finding(types): 16 of the 41 keys #7735 de-defaulted publish a JSDoc@defaultthat no registered renderer reads, and only 8 of 41 are pinned against the renderer #8318's "'the renderer for this type' is not a function today" and the report's "which registration wins … is a ruling question" are overstated and should be corrected on finding(types): 16 of the 41 keys #7735 de-defaulted publish a JSDoc@defaultthat no registered renderer reads, and only 8 of 41 are pinned against the renderer #8318 by a comment (PM or author; not a PR item). Thin spot: of the 17 agreeing-but-unpinned rows, roughly ten are literal-shaped (flex.justify/gap/wrap,grid.gap,tabs.orientation,scroll-area.orientation,resizable.direction,button.buttonType,input.inputType, …) and the existing finding(types): two layout schemas'@defaultJSDoc disagrees with the value the renderer actually applies #7361 instrument pins exactly that shape today — finding(types): 16 of the 41 keys #7735 de-defaulted publish a JSDoc@defaultthat no registered renderer reads, and only 8 of 41 are pinned against the renderer #8318 should carry those rows explicitly, not only the general-pin question. (The report's "13 by the sweep" is ~10 by my count —button.variant/sizeare bare forwards,iconPositionis!== 'right'— the verdicts hold either way.) Judgement: a legitimate carrier, not parking — the published surface is measured-correct today and the gap concerns future drift, declared by name.check:sdui-registration-pinsdeclared, not re-measured. Verified mechanically:git diff a6ac2bb d28d27a -- packages/componentstouches two files (text.tsx,flex.tsx), 29 changed lines, 29 comment / 0 code; the merged main (61 files) touched nothing underpackages/components. A comment-only delta cannot move the bundle's registration set. Acceptable, with the falsifiable condition stated and the landing pre-check re-measuring on the merged head (main's own changes are main's, re-measured there).Everything passed in round 1 is still intact at
d28d27aCensus 4 text / 0 call sites; graph walk 57
ZodDefault; 22 minimal documents green with output equal to input except the spec residue; pin (i) 23/23; pin (ii) 8/8; the five zod-file deltas identical to round 1;defaultProps.align: 'start'.Verdict: PASS
Non-blocking, for PM: (a) a correcting comment on #8318 (registry semantics; add the literal-shaped rows to its scope); (b) the six
defaultProps/fallback disagreements cross-referenced on #4631; (c) a card for the 57 spec-derivedZodDefaultresidue; (d) the one-clause changeset note on the 19 non-layout keys; (e) the grid ramp precision nit.Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
ACCEPT — PR #8299 at head
d28d27a,domain:spec @ objectuiseat, R1, 2026-09-07T11:49ZReviewed against GitHub, not against the report's self-description. One patch round; what failed was again the written record, not the contract.
The round that failed
The first delivery had the ruled removal right — and the in-seat review returned FAIL (5569648055) on four written statements the tree contradicted. Two mattered:
layout.tstags were misdescribing the renderer.What the patch round produced
All 41 tags re-measured against the renderer: 19 agree, 4 corrected, 2 deliberately untagged, 16 have no registered renderer reading the key. The corrections, each verified by the reviewer against the renderer at
d28d27a:GridSchema.columns@default 3@default 2grid.tsxopenslet baseCols = 2; the ramp needstypeof schema.columns === 'number', so absence keeps 2TextSchema.variant@default 'body'bodyResizableSchema.withHandle@default true{withHandle && …}— absence draws no grip, so the old tag asserted the opposite of the truthPageNodeSchema.template@default 'default'resolveTemplatereturns null on absence and falls through to the pageType dispatchThe extended pin covers 8 rows with 2 negative controls and went red under three separate ablations (the tag, a block-tag planted in prose, the renderer side).
Checklist conclusions at
d28d27amain, first lineFixes #7735; body scanned, no closing keyword beside another open issuehead.sha=d28d27ab5541d95071dc8496cad562529be81204; re-review judgedd28d27a; check-suite event carried the same shacompleted: 29success+ 3skipped. Zero failures.Lint→ success 11:36:10Z,Type Check→ success 11:40:45Z; all four test shards greenpackages/types/src/index.ts, so it is clear of the shared-file near-miss #8296/#8297 hadCarrier asymmetry — recorded so an audit does not read it as a strip
needs:contract-reviewis on PR #8299 only; this card never carried it. That is by construction, not by omission: I judgedClause-②: noat claim time (this narrows parse output and leaves the accept set untouched), so the card side was never hung, and the dev hung the PR side because the ruling's note 4 required it. ⇒ Clearing leaves one removal, and the reason is this paragraph rather than a missing second one.Clause-②: nocall was also vindicated by measurement: all 41 sites were spelledTYPE.optional().default(v), so none carried optionality and the accept set is genuinely unchanged. Had even one been a bare.default(x), removing it would have made that key required — a silent narrowing on a published surface — and the verdict would have had to change.Deviations dispositioned
check:sdui-registration-pinsis declared, not re-measured at this head (it needs a built console). The reviewer verified the argument mechanically: 29 comment lines and zero code lines underpackages/componentssincea6ac2bb, and the main merge touched nothing there. Accepted; CI re-measures it inperformance-budget.yml.The named narrowing — 8 rows pinned, 33 not — was judged a legitimate carrier (#8318) rather than parked work.⚠️ One of the card's two stated reasons was overstated and I have corrected it there (5570111809): the two
textregistrations sit in different namespaces (element:textvsui:text), so the registry never overwrites and "the renderer for this type" is well defined; the real fault was the sweep resolving a bare'text'to the wrong namespace, which is fixable. ⇒ The honest narrowing is nearer ~23 rows than 33. That correction is on #8318 and ⛔ does not reopen this PR.Escalations from this card, all carried
#4631 now holds the 55/7
defaultPropscensus (5569668079); the 57 remainingZodDefaultnodes are #8317; the JSDoc narrowing is #8318.Verdict: ACCEPT. Clearing the PR-side carrier, flipping ready, enqueueing.
Generated with Claude Code
https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX
Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
MERGED — PR #8299 landed as
8b7ea39onmain, 2026-09-07T12:06ZVerified on the tree, not on the merge notification.
pm:dispatchedstripped and the assignee cleared in the same stroke as this note;bug,finding,priority:p2anddomain:specstay.origin/main8b7ea39.default()call sites across the five zod mirrorsobjectql.zod.ts×3,complex.zod.ts×1)flex.tsxdefaultProps.align(#8229's third face):148nowalign: 'start'zod-mirror-authors-no-defaults-7735.test.ts,layout-default-jsdoc-7361.test.ts,registration-defaults-match-renderer-8229.test.ts— all present⭐ The second row is the one worth keeping. At dispatch this seat flagged that pin (i), worded literally as the ruling puts it — "no
.default(underpackages/types/src/zod/**" — would be red on the day it landed, because four of the 45 raw matches are legitimate prose inside docblocks. The implementation wrote the pin against call sites instead, and the landed tree is the proof the distinction was real: the call sites went to zero and the prose survived. A text-matching pin would be failing onmainright now.That correction also fixed the ruling's arithmetic: the population is 41 call sites in five files, not 45 in seven —
objectql.zod.tsandcomplex.zod.tsnever had one to remove. The implementing session re-derived it independently with a TypeScript AST census and aZodDefaultgraph delta (98 on the base → 57 on the head = 41) and reached the same number.Why this card took a patch round
The contract was right in round 1. The written record was not: the changeset claimed "the two that disagreed were the bug / rendering does not change" when six of 22 layout keys disagree with their renderer, and ruling note 3(ii) was reported satisfied after comparing JSDoc tags against the old mirror value rather than the renderer fallback — a different comparator, and four
layout.tstags were misdescribing the renderer under it. ⭐ The sharpest of those:ResizableSchema.withHandlecarried@default truewhile the renderer forwards bare and the primitive gates the grip on{withHandle && …}— absence draws no grip, so the tag asserted the opposite of the behaviour.Both gates on this card caught statements, not code. That is what they are for.
Carried forward
@defaultthat no registered renderer reads, and only 8 of 41 are pinned against the renderer #8318 — the named narrowing (8 rows pinned, ~23 not).textregistrations sit in different namespaces, so the registry never overwrites; the real fault was a namespace-resolution bug in the sweep, which is fixable.ZodDefaultnodes still reachable from the published@object-ui/types/zodbarrel after #7735 — batch #69's principle stops at the files it named, and the rest are imported by reference from@objectstack/spec#8317 — the 57ZodDefaultnodes still reachable from the published barrel, all imported by reference from@objectstack/spec(upstream population: 1546 call sites). Batch Redesign examples based on new JSON project specification #69's principle is delivered for this repo's own mirrors and open upstream.defaultPropscensus (5569668079), including the "seed vs default" distinction that card is itself holding.flex.tsx's own registration declaresdefaultProps.align: 'center'while its renderer falls back to'start'#8229 — fully covered by this landing; its own bounded census reproduced row for row.Generated with Claude Code
https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX
Generated by Claude Code
fix(types,plugin-detail): the four corrections the contract review named
feat(scripts): report-only component surface parity checker (objectui…