Repository navigation
finding(types): two layout schemas' @default JSDoc disagrees with the value the renderer actually applies #7361
Description
Filed out-of-scope from the #7251 skills-optimization flight, noticed while writing a layout-primitive table from the renderers rather than from the types. Unassigned. A concrete instance of #4631 (a component type's declared surfaces disagreeing with what the renderer reads), narrowed to defaults.
Measured at 4704aa4.
Row 1 — container.maxWidth
packages/types/src/layout.ts, ContainerSchema:
/**
* Max width constraint
* @default 'lg'
*/
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | ... | false;
packages/components/src/renderers/layout/container.tsx:
const maxWidth = schema.maxWidth ?? 'xl';
So a container with no maxWidth renders max-w-xl (36rem), while the type says it renders max-w-lg (32rem). An author reading the declaration to decide whether to set the key gets the wrong answer, in the direction that makes them skip setting it.
Row 2 — flex.align
packages/types/src/layout.ts, FlexLayoutProps (shared by FlexSchema and StackSchema):
/**
* Align items
* @default 'center'
*/
align?: 'start' | 'end' | 'center' | 'baseline' | 'stretch';
The two renderers that read it:
flex.tsx:18 const align = schema.align || 'start';
stack.tsx:24 const align = schema.align || 'stretch'; // Stack items usually stretch
Neither is 'center'. And because one JSDoc block serves both types by design (FlexLayoutProps exists so the members are declared once — see its docblock and #6151), a single @default cannot be right for both: flex and stack deliberately diverge here, which is most of what distinguishes them.
Why it is worth a card rather than a silent fix
The @default tag is not decoration on this surface. sdui-parser serializes registry metadata into sdui.manifest.json and sdui-intrinsics.d.ts, and #4889 is the precedent for what happens when the declaration and the read disagree on this exact file: maxWidth: false was a declared value the renderer folded into max-w-xl, so an author asking for no constraint got the opposite of what they asked for. This is the same file, the same key, one layer up — the declaration is now right about the domain and wrong about the default.
Both rows are one-line documentation fixes if the renderers are taken as authoritative, which they should be: they are what runs. Row 2 additionally needs the shared @default to say that the two types differ, or the tag to move onto each type.
Not a duplicate of #4889 (closed — that was the ||-vs-?? read of false) or #7088 (BaseSchema.hidden's JSDoc promising visibility: hidden), but the same family as both.
Activity
os-zhuang commentedon Sep 5, 2026
分诊 —
domain:spec/priority:p3/pm:queue/finding/documentation锚定 (anchoring):修的是
packages/types/src/layout.ts的 JSDoc ⇒domain:spec。渲染器不动(它们是权威),所以不是domain:ui。在
origin/maina472b07上复核 —— 两行都成立,逐字三处读取、两处
@default,与卡片一字不差(卡片没给container.tsx的行号,补上::26)。Row 1:声明说
lg(32rem),渲染器给xl(36rem)。Row 2:声明说
center,两个渲染器谁都不是center,而且互不相同。⭐ Row 2 的结构性论证是这张卡最硬的部分
卡片指出:
FlexLayoutProps是刻意共享的(成员只声明一次,见其 docblock 与 #6151),而flex与stack在align上刻意分道(startvsstretch)——stack.tsx:24那句行内注释 "Stack items usually stretch" 就是在说这件事是有意的。所以一个共享的
@default标签在结构上不可能同时正确。 这不是"写错了一个值",是"这个位置放不下一个正确的值"。⛔ 接手者不要简单把'center'改成'start'就交差 —— 那会让stack从"错"变成"另一种错"。定级理由
priority:p3:sdui-parser把 registry 元数据序列化进sdui.manifest.json与sdui-intrinsics.d.ts,所以@default是会被下游读走的声明,不是注释。@default"gets the wrong answer, in the direction that makes them skip setting it" —— 作者以为默认已经是自己要的值,于是不写,然后拿到别的值。定型:文档修正,不踩 manual floor
⛔ 给执行席的边界
@default 'lg'→'xl'。@default改写成"两个类型不同"的说明,或者把标签下移到各自的类型上。⛔ 不要留一个单值。若走"下移",注意FlexLayoutProps存在的目的正是"成员只声明一次"(finding(types):StackSchema, declared as Omit-of-FlexSchema-minus-type, erases every named member — it ships declaring onlytype, because Omit collapses underBaseSchema's index signature #6151),所以下移标签而不下移成员,理由要写进注释。sdui-parser的序列化输出。既然@default会进sdui.manifest.json/sdui-intrinsics.d.ts,改完要确认那两个产物跟着更新,且没有别的门禁把旧值钉住。⛔ 只改 JSDoc 不看产物,等于修了一半。layout.ts全量@defaultvs 渲染器兜底的对照,把分母写进 PR,这个类就能一次收干净 —— ⛔ 但这是加分项不是要求,别让它把一张 p3 卡拖成普查工程。ContainerSchema.maxWidth: false是声明了却读不到的值 ——container.tsx用||把它折成max-w-xl#4889(已关闭,||vs??对false的读取)或 BaseSchema.hidden's JSDoc promisesvisibility: hidden, but the renderer returns null for it exactly as forvisible: false#7088(BaseSchema.hidden的 JSDoc)合并 —— 卡片已正确判定它们同族但不同卡。⛔ 分诊席不认领、不派单、不写码。
Generated by Claude Code
os-justin commentedon Sep 5, 2026
Claim: PM loop round R1 (fourth wave)
Session:
session_01BAZFhALsQsGqxui8sNqM8sBranch:
claude/issue-7361-layout-default-jsdocWorktree:
objectui-issue-7361Domain:
domain:specFile surface (region level):
packages/types/src/layout.ts— theContainerSchema.maxWidthdocblock (:215–:219onb74a859) and theFlexLayoutProps.aligndocblock (:285–:289) only; ⛔ no member, type or default VALUE moves, ⛔ renderers untouched (they are the authority) · one pin underpackages/types/src/__tests__/(the@defaulttext of each of the two members agrees with the renderer's actual fallback, read off disk fromcontainer.tsx/flex.tsx/stack.tsx; control: an unrelated@defaultin the same file unchanged) ·.changeset/*only ifcheck-changeset-presence.mjsdemands one (JSDoc on published source — expect it does;@object-ui/types: patch)Container & model:
XS,mode:subagent,model: opus(default judgment tier; JSDoc only)Clause-②: no — no accept-set, type, export or runtime behaviour change;
@defaultis descriptiveSerial constraints cleared (file / region granularity per #5734 comment 5551274423): no in-flight branch touches
layout.ts(#7295complex.ts; #7433 the parity test; PR #7726base.ts/base.zod.ts/form.zod.tstop; PR #7730base.zod.tsClassNameStylePropsSchema) — measured bygit diff --name-onlyagainst each branch at 12:46Z; the dev mergesorigin/mainbefore opening the PR; the queue arbitrates.Premise re-verified by this seat on
origin/main=b74a859(12:46Z):layout.ts:217 @default 'lg'onContainerSchema.maxWidthvscontainer.tsx:26 schema.maxWidth ?? 'xl';layout.ts:287 @default 'center'on the sharedFlexLayoutProps.alignvsflex.tsx:18 schema.align || 'start'andstack.tsx:24 schema.align || 'stretch'— all three reads and both tags as the card states. Triage boundary 3 (the serialized artefacts): nosdui.manifest.json/sdui-intrinsics.d.tsis checked in;scripts/dump-public-manifest.mjsandpackages/sdui-parser/srccontain no@defaultreader — the dev confirms on its tree whether any generator consumes the JSDoc tag and states the reading in the PR body. Thealign?: 'left' | 'center' | …atlayout.ts:113is a different member (text alignment) — ⛔ not this card's.No maintainer ruling; triage (5548503487) grades it a docs correction with the renderers authoritative and five boundaries: Row 1 is one line (
'lg'→'xl'); Row 2 is NOT a single value — either a shared tag that states the two types differ, or the tag moved onto each type with the #6151 "declare once" reason kept in a comment; check the serialized artefacts; a fulllayout.ts@default-vs-fallback census is a bonus with its denominator, not a requirement; ⛔ no merge with #4889 / #7088. All carried into the dispatch.Generated by Claude Code
os-justin commentedon Sep 5, 2026
Dev claim — implementing this card under the
domain:specPM dispatch (sessionsession_01BAZFhALsQsGqxui8sNqM8s, R1 fourth wave).claude/issue-7361-layout-default-jsdocobjectui-issue-7361(dedicated; the shared checkout is untouched)origin/main=b74a859, since merged forward toa00db9efPremise re-verified on my own tree before any edit — all five sites read exactly as the card and the triage state them:
Renderers untouched, as ruled. Row 2 keeps the tag off the shared member and states both consumers in prose rather than moving it onto
FlexSchema/StackSchema— the reasoning is in the pull request body.Generated by Claude Code
os-justin commentedon Sep 5, 2026
os-dev-report
{ "issue": 7361, "status": "done", "branch": "claude/issue-7361-layout-default-jsdoc", "pr": "https://github.com/objectstack-ai/objectui/pull/7736", "head": "243236ea2741765db6bb9310d6c9393907341eb2", "base_at_dispatch": "b74a8598d348df7ce76a9b5547eb28d2cd9b9d0c", "premise_still_valid": true, "clause_2": "no", "line_budget": "n/a", "summary": "Both rows corrected in packages/types/src/layout.ts, docblock text only; renderers untouched. Row 1: ContainerSchema.maxWidth @default 'lg' becomes 'xl', naming container.tsx and its ?? read. Row 2: the shared FlexLayoutProps.align tag is removed rather than re-valued, and the docblock states both consumers in prose (flex.tsx || 'start', stack.tsx || 'stretch'). Shape (a) was chosen over the dispatch-preferred (b) because @default is a member-level tag: on a FlexSchema/StackSchema interface docblock it would attach to the interface reflection, not to align, so (b) would have swapped a wrong-value tag for a wrongly-placed one. One pin added deriving both sides off disk; one changeset (@object-ui/types patch).", "files_changed": [ "packages/types/src/layout.ts", "packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts", ".changeset/layout-default-jsdoc-7361.md" ], "gates": [ { "command": "pnpm exec vitest run --maxWorkers=2 packages/types/", "exit": 0, "head": "243236ea", "detail": "116 files, 1977 tests passed" }, { "command": "pnpm --filter @object-ui/types build", "exit": 0, "head": "86f288be", "detail": "dist completeness 120 emitted files" }, { "command": "pnpm --filter @object-ui/types type-check", "exit": 0, "head": "243236ea", "detail": "exit captured inside the locked batch, not from the wrapper" }, { "command": "pnpm lint (whole repo)", "exit": 0, "head": "243236ea", "detail": "0 errors; pre-existing warnings only" }, { "command": "node scripts/check-changeset-presence.mjs", "exit": 0, "head": "243236ea", "detail": "declares @object-ui/types patch" }, { "command": "node scripts/check-changeset-fixed.mjs", "exit": 0, "head": "243236ea" }, { "command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "head": "243236ea" }, { "command": "node scripts/check-changeset-overwrite.mjs", "exit": 0, "head": "243236ea" }, { "command": "node scripts/check-control-bytes.mjs", "exit": 0, "head": "243236ea", "detail": "6302 tracked text files; re-run after staging" }, { "command": "node scripts/check-spec-symbol-derivation.mjs", "exit": 0, "head": "243236ea" }, { "command": "node scripts/pm/check-governed-merges.mjs --test FINAL_FILE_LIST", "exit": 0, "head": "243236ea", "detail": "NOT governed, 0 of 3 paths; objectui-local check-governed-queue-guard.mjs agrees" }, { "command": "tsc -p tsconfig.test.json --listFilesOnly", "exit": 0, "head": "243236ea", "detail": "the pin is in the program, proven not assumed" } ], "tests": "Pin packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts, 11 tests, all green on 243236ea. Every expected value is DERIVED off disk: renderer fallbacks extracted with narrow regexes from container.tsx / flex.tsx / stack.tsx and compared against docblock text extracted from layout.ts, so the pin reddens if either side moves. Positive controls guard against a regex matching nothing; negative controls pin the neighbouring centered / justify / gap tags. ABLATION (fix committed first at 86f288be, trap-guarded, absolute paths): layout.ts reverted to b74a859; mutation proven on disk by anchored counts (@default lg 1, @default center 1, @default xl 0) AND blob 266e700e differing from HEAD blob 92225fa2, so it was not a no-op; vitest exit 1 with 5 failed and 6 passed of 11 - red were the two row-1 legs and the three row-2 legs, green were both positive controls, the divergence assertion and all three negative controls; restore via git checkout HEAD -- PATH returned blob 92225fa2 identical to HEAD with git diff HEAD empty. The pin reads source off disk rather than dist, so no rebuild is needed for a mutation to take effect and the dist preflight does not apply.", "mcp_calls": "11 - three search_issues (one known-hit control plus one per finding), two issue_write, two issue_read read-backs, one claim comment, one create_pull_request, one pull_request_read read-back, and this report comment. Card and comment reads went through the zero-quota payload channel, not MCP.", "deviations": [ "REST channel unavailable to this seat: a repo-scoped GET returned 403 'GitHub access is not enabled for this session' while /rate_limit showed a live 15000/hr credential, i.e. the session gate, not a quota. Declared channel switch: dedup ran on one targeted MCP search_issues per finding, each preceded by a known-hit control query that returned #7361 as its top hit, proving the search channel was live in this session.", "Shallow clone: the first git merge origin/main refused with 'unrelated histories' because the newly fetched tip sat at the shallow graft with zero parents. Resolved by git fetch --deepen=50, after which merge-base with the branch resolved to exactly the pinned BASE b74a8598. The pre-deepen git show of that commit listed the whole tree and would have read as a layout.ts overlap; the two-point git diff BASE..tip showed the true change set (three files, no overlap).", "pnpm --filter '@object-ui/types^...' build exited 1 with ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT: @object-ui/types has zero workspace dependencies by design, so the closure is genuinely empty. Built the package itself instead. Not a failure of this change.", "Row 2 used the dispatch's shape (a) rather than the preferred (b); the preference was conditional and its condition fails. Reasoning is in the PR body.", "The verify lock printed 'VERDICT batch-last-exit' for the type-check plus lint batch and warned it does not cover the whole command; each part's exit code was captured individually inside the batch and both are reported above.", "create_pull_request appended a second attribution footer to the PR body (the documented platform mutation). Left as-is per AGENTS.md, which warns that repairing a body can destroy a correct one and that a PATCH would downgrade the session-URL form." ], "open_questions": [], "out_of_scope_findings": [ "filed as #7734: FlexLayoutProps.direction carries a shared @default 'row' that is correct for flex and wrong for stack, whose renderer reads || 'col' under the comment 'Default to column for Stack'. Structurally identical to row 2 and on the same interface; surfaced by the boundary-4 census, which neither the card nor the triage had run. Unassigned, unlabelled.", "filed as #7735: packages/types/src/zod/layout.zod.ts declares runtime .optional().default(VALUE) on the same members, and .default() substitutes at parse time. Measured through the built safeValidateSchema: a bare container node parses to maxWidth 'lg' and a bare flex node parses to align 'center', values the renderers never apply, so a node that has been through the mirror renders differently from the same node authored as-is. StackSchema declares no defaults at all. This is runtime rather than documentation and is arguably more serious than this card; both candidate remedies change published behaviour, so it is written up as needing a ruling, not a patch. Unassigned, unlabelled.", "not filed, handed back for a decision: scripts/check-doc-component-types.mjs line 422 cites 'packages/types/src/layout.ts:566' inside an exemption REASON string. That citation was already stale at b74a859 (line 566 there is an unrelated comment, and no 'interface PageVariable' exists in the file), so this change did not cause it; no gate logic reads the number and check:doc-types is unaffected. Left unfiled as a trivial pre-existing prose drift rather than opened as a card - say the word if you want one." ], "census": { "denominator": 24, "cleanly_comparable": 14, "mismatched": 3, "agreed": 11, "not_comparable": 10, "note": "matched per component type, not by member name; names such as variant, size, orientation and gap recur across unrelated schemas and a name-only sweep produces false pairs" }, "boundary_3_reading": "No generator consumes the JSDoc @default tag anywhere in this repo. dump-public-manifest.mjs builds sdui.manifest.json from runtime registrations (config.inputs via manifestFromConfigs, read out of a real browser) and never opens a TypeScript source file; sdui-parser/src/codegen.ts generates the intrinsics declaration from that manifest, not from JSDoc; neither artefact is checked in. Repo-wide there is no JSDoc-tag parser (no getJSDocTags, no jsDocTags, no @microsoft/tsdoc - only prose mentions of TSDoc in comments). What the tag does reach: TypeDoc is wired up (typedoc.json, docs:api, devDependency) with packages/types as its first entry point and a gitignored output dir, and the docblock ships in the emitted packages/types/dist/layout.d.ts, verified to carry both corrected blocks. So the card's premise that the tag reaches downstream holds for the shipped declaration file, the API reference and IDE hover, not for the serialized SDUI artefacts." }Generated by Claude Code
os-justin commentedon Sep 5, 2026
ACCEPT — PR #7736 head
243236ea(collection review 2026-09-05T13:25Z,domain:specseat, sessionsession_01BAZFhALsQsGqxui8sNqM8s; Clause-②noconfirmed on the diff — docblock text only, no member, type, export, union, default VALUE or runtime behaviour moves, so no contract-tier gate applies)Read against the card, triage 5548503487 and the claim 5551918170:
ContainerSchema.maxWidth):@default 'lg'→'xl', read site named; the registration'sinputs[].defaultValueanddefaultProps.maxWidthalready said'xl'— three surfaces agree, only the tag dissented. CORRECT.FlexLayoutProps.align): no single-value tag remains; the docblock states both consumers (flex.tsx|| 'start',stack.tsx|| 'stretch') and the finding(types):StackSchema, declared as Omit-of-FlexSchema-minus-type, erases every named member — it ships declaring onlytype, because Omit collapses underBaseSchema's index signature #6151 declare-once reason. This is triage's first allowed shape. The dispatch's conditional preference for the second shape (tag moved ontoFlexSchema/StackSchema) was declined for a measured reason —@defaultis a member-level tag and on an interface docblock attaches to the interface reflection, not toalign— so shape (b) would have replaced a wrong value with a wrong placement. ACCEPTED as the better reading of the boundary.dump-public-manifest.mjsreads runtime registrations;sdui-parser/src/codegen.tsreads the manifest; no JSDoc-tag parser anywhere); the tag reaches the shippeddist/layout.d.ts, TypeDoc and IDE hover. The card's route was wrong, its premise that the tag is published holds. Recorded; nothing to regenerate.@defaulttags inlayout.ts, 14 comparable, 3 mismatched (the two rows here +FlexLayoutProps.direction), 11 agree; matched per component type. Out-of-scope findings filed with dedup controls: finding(types): the shared@default 'row'onFlexLayoutProps.directioncannot be right for bothflexandstack#7734 (direction, same class), finding(types): the zod layout mirror substitutes runtime.default()values the renderers never apply — a parsedcontainerrenders a different width than an unparsed one #7735 (layout.zod.ts.optional().default(VALUE)substitutesmaxWidth: 'lg'/align: 'center'at parse time — runtime, needs a ruling). Both left unlabelled for triage's sweep; this seat does not grade.ContainerSchema.maxWidth: false是声明了却读不到的值 ——container.tsx用||把它折成max-w-xl#4889 / BaseSchema.hidden's JSDoc promisesvisibility: hidden, but the renderer returns null for it exactly as forvisible: false#7088 untouched); renderers untouched;layout.ts:113(TextSchema.align) untouched.layout-default-jsdoc-7361.test.ts: both sides derived off disk, positive controls against vacuous regexes, negative controls oncentered/justify/gap, ablation 5 failed / 6 passed with blob-hash mutation and restore proof. Changeset@object-ui/types: patch. Governed predicate: 0 of 3 paths. Package build / suite / type-check / repo lint / changeset gates all exit 0 on243236ea.layout.ts:566citation inside an exemption reason string atscripts/check-doc-component-types.mjs:422, pre-existing atb74a859, no gate reads it): no card — recorded here; the next PR that touches that script fixes it as a declared rider.Farm on
243236eaat 13:23Z: 25 completed (24 success + 3 skipped counted among 32), 6 in progress, and ONEcompleted: failure—Docs Route Eager Closure Check(job 101312910554), which died 1 s intopnpm check:docs-route-closurewhile Corepack downloaded the pnpm tarball (AssertionError: assert(!this.paused)inside undici), before the check body ran. Infrastructure, not this PR's three files; the one permitted re-run was requested for run 33968565118 at 13:25Z. A second failure is treated as real. Landing (ready flip, auto-merge SQUASH, queue-ref probe, landing note) follows once every check run on this head iscompleted: success(skipped allowed).Generated by Claude Code
os-justin commentedon Sep 5, 2026
Landing note — PR #7736 enqueued (2026-09-05T15:26Z)
Pre-landing three:
noat claim and confirmed on the diff (docblock text only; no member, type, union, default VALUE or runtime behaviour moves); ACCEPT on record at comment 5552118929; no contract-tier gate applies.@defaultdocblocks with the renderer fallbacks #7736 ever carriedneeds:contract-review(PR labels read at 15:24Z:package: types,tests); nothing to clear.243236ea: 29completed: success+ 3skipped, none failed, none pending (full farm read by name at 15:24Z). The one red at 13:19Z —Docs Route Eager Closure Check, an undici crash inside Corepack's pnpm download before the check body ran — was re-run once at 13:25Z and completedsuccessat 13:24:40Z on the same head; no second failure.Landing actions:
draft:falseat 15:25Z (the 13:34Z flip re-check was pre-empted by the account usage-window reset at 15:20Z; state re-read in full before acting).mergeMethod: SQUASHexplicitly at 15:25Z (tool echoes empty method/time — known reading, seat post §4).git ls-remote origin 'refs/heads/gh-readonly-queue/main/*'showsrefs/heads/gh-readonly-queue/main/pr-7736-caf477fa87579f0b212e10aad14d193b5a0ae346ate546222b, queued behind PR feat(types): refusechartTypeon a chart series by name, pointing attype#7737 (another lane);main=83c77dc3. The PR base (9587fc95) is behindmainby four squashes — a behind PR enqueues fine; the queue merges onto the current tip.Next reading: MERGED by two readings (PR
merged: true/ this card'sclosed_by_pull_requestsstate MERGED, andorigin/maincontaining the squash), then strippm:dispatchedhere. Follow-ups #7734 and #7735 stay unlabelled for triage.Generated by Claude Code
os-justin commentedon Sep 5, 2026
MERGED — two readings (2026-09-05T15:44Z, session
session_01BAZFhALsQsGqxui8sNqM8s): (1) PR #7736 readsmerged: true,merged_at15:39:52Z, and this card'sclosed_by_pull_requestsnames PR #7736 in state MERGED (card closed 15:39:54Z,state_reason: completed); (2)origin/maintip is the squash itself,e546222b("docs(types): align two layout@defaultdocblocks with the renderer fallbacks (#7736)"), read 15:44Z. Landed head243236ea; triage's five boundaries executed as ACCEPTed (5552118929).pm:dispatchedstripped in this round;documentation,finding,priority:p3,domain:specstay. Follow-ups #7734 (FlexLayoutProps.direction, same class) and #7735 (layout.zod.ts.default()substitutes values the renderers never apply — runtime, needs a ruling) remain unlabelled for triage.Generated by Claude Code
fix(types,components): correct the four @default tags that describe t…
fix(types,components): the zod mirrors stop authoring defaults (objec…