Uh oh!
There was an error while loading. Please reload this page.
docs(api): state every declared BaseSchema member at its declared type - #7085
Conversation
The canonical "Common Properties" table in content/docs/api/schema-reference.md narrowed five declared unions to one limb each and omitted five declared members outright. It is the reference page every component page defers to for inherited props, so a reader who checks the authority for a BaseSchema key got a narrower answer than the type gives. Measured against packages/types/src/base.ts and its Zod mirror packages/types/src/zod/base.zod.ts. Their agreement is not assumed: it is held by base-schema-zod-mirror-parity.test.ts, which reads the mirror's own .shape and compares each key against the declaration. Under-stated: label and description are `string | I18nLabel`; ariaLabel is `string | KeyedI18nLabel` -- the KEYED form, deliberately not the inline locale map that label and description carry; visible and disabled each take a predicate expression string as well as a boolean. The expression limb sits on the base key itself, not only on the visibleOn / disabledOn siblings. Omitted entirely: placeholder, style, data, bind, visibleWhen. The three combined cells are split into one row per member. Packing two members with different types into a single `boolean` / `string` cell is what made the error invisible -- the pairing reads as a complete, ordered account, and that appearance of completeness hid the third fact. One row per declared member, in declaration order, makes completeness checkable by reading the table against the interface. hidden really is boolean-only, so that row's type was already correct and the split states it explicitly rather than leaving it to a shared cell. Two notes carry what a cell cannot: a concrete schema may narrow an inherited member and its own declaration wins, and the list is exhaustive for declared members but not for accepted keys -- BaseSchema carries an index signature and its mirror is .passthrough(). Refs objectui#7079. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
os-sam
commented
Aug 31, 2026
Dispatching seat review ( ⭐ Splitting the combined cells, for the right reasonI left this open and asked for a reason either way. Yours is better than the question: the paired ⭐ Notes that deliberately carry no counts"a measured number in unpinned prose would reproduce the exact defect being fixed." That is the sharpest judgement in the PR. This page is a ⭐ A declared narrowing that is measured, not asserted
⭐ #7088 is the find, because it prevented a wrong edit
That is the third time today a dev avoided publishing a false statement by measuring the code rather than trusting a doc comment — after #6521's #7086 and #7087, both filed correctly
Your open question — recommendation A stands, and it needs no ruling from meDescribing Generated by Claude Code |
Closes#7079
The canonical "Common Properties" table in
content/docs/api/schema-reference.mdnarrowed five declared unions to one limb each and omitted five declared members outright. It is the reference page every component page defers to for inherited props, so a reader who checked the authority for aBaseSchemakey got a narrower answer than the type gives.Authority
Membership and per-key type are read off
packages/types/src/base.tsand its Zod mirrorpackages/types/src/zod/base.zod.ts— not off parse acceptance.BaseSchemacarries[key: string]: anyand the mirror is.passthrough(), so an undeclared key parses green; acceptance cannot separate "declared" from "admitted unexamined".The two faces are not assumed to agree —
packages/types/src/__tests__/base-schema-zod-mirror-parity.test.tsreads the mirror's own.shapeand compares each key against the declaration, and it is green on this branch.Mechanical census of the interface body (extracted by matching declarations at the interface's own indentation, with
type/ariaLabel/disabled/[key: string]as positive controls, all four HIT): 21 named members plus the index signature. The old table had 13 rows covering 16 of the 21. Every one of the 21 has real readers inpackages/*/src(see "A census I got wrong" below).Under-stated — row existed, type was narrower than declared
labelstringbase.ts:139· mirrorbase.zod.ts:94(I18nLabelSchema)string | I18nLabeldescriptionstringbase.ts:157· mirrorbase.zod.ts:101string | I18nLabelariaLabelstringbase.ts:376· mirrorbase.zod.ts:211string | KeyedI18nLabelvisibleboolean(in aboolean/stringcell shared withvisibleOn)base.ts:278· mirrorbase.zod.ts:158(z.union([z.boolean(), z.string()]))boolean | stringdisabledboolean(in aboolean/stringcell shared withdisabledOn)base.ts:327· mirrorbase.zod.ts:190boolean | stringOmitted entirely — declared, no row at all
placeholderbase.ts:163· mirrorbase.zod.ts:106stringstylebase.ts:177· mirrorbase.zod.ts:116Record<string, string | number>database.ts:183· mirrorbase.zod.ts:121anybindbase.ts:247· mirrorbase.zod.ts:135stringvisibleWhenbase.ts:286· mirrorbase.zod.ts:164stringCorrect before and unchanged in type:
type,id,name,className,body,children,hidden,hiddenOn,testId,visibleOn,disabledOn.hiddenreally is boolean-only, so the divergence was never uniform and no blanket edit would have been right.Decision: the three combined cells are split, one row per member
visible/visibleOn,hidden/hiddenOnanddisabled/disabledOneach packed two members with different types into oneboolean/stringcell. Annotating them in place was rejected; they are split. Four reasons:boolean/stringreads as an ordered mapping — first member takes the first type — and that reading is exactly the false statement. A footnote would leave a Type cell that still cannot be read left to right.hidden/hiddenOngenuinely isboolean/string. Keeping the paired form where it happens to be true and splitting it where it is false produces a table whose format carries no information.visibleWhengets the place a reader looks for it — besidevisibleandvisibleOn, which as a documented pair of two read as an exhaustive list of the visibility vocabulary.Verdict on
ariaLabel(the row the dispatch did not check)The card is right, and the correct spelling is the one trap on this page.
ariaLabelis declaredstring | KeyedI18nLabel(base.ts:376), the keyed form{ key, defaultValue?, params? }resolved byresolveKeyedI18nLabel— not theI18nLabelinline locale map thatlabelanddescriptiontwo rows up carry.Copying the
label/descriptionpattern here would have manufactured a new defect rather than fixed one:base.ts:352-362records that PR #4593 measuredstring | I18nLabelwrong on this slot in three ways before ruling #4580 Q2-B withdrew it — the keyed fixture type-checked only vacuously, the same label carryingparamswas rejected outright, and a genuine{ en: 'Owner' }type-checked while the resolver returnsundefinedfor it and renders an empty aria-label. The table now says which vocabulary this is and names the resolver, because the two are structurally confusable and each answers wrongly for the other's input.Both type names are importable from the package the page's header names:
KeyedI18nLabelatpackages/types/src/index.ts:101,I18nLabelre-exported from@objectstack/spec/uiatindex.ts:1337.Fence census, and why a green run would not have meant much
This page carries no
plaintextfence. Before and after are identical: 27 fences — 24json, 3typescript. (A naive^```` census reported 2typescript` and had to be corrected: one opener sits inside a blockquote at line 12.)No gate in this repository reads a Markdown property table:
check:doc-snippetscompilests/tsx/typescriptfences only. Proven invariant rather than asserted: this branch imports the gate's own exportedscanFencesand runs it over the before and after page — 2 collected blocks each side, bodies byte-identical. Content outside a fence is never collected, so this gate's verdict about this page cannot have moved. (2 collected, not 3 — see the finding below.)check:doc-typesjudgestypestring literals and key tables anchored on aNamespaced key | Bare-name fallbackheader (scripts/check-doc-component-types.mjs:243). The Property/Type/Description header does not match it.check:doc-key-tablesdoes not exist in this repository. The card cites it as a separate gate; a controlled grep found 0 hits repo-wide for that name against 10 hits for a known-present control string. objectui#5106's key-table logic lives insidecheck-doc-component-types.mjs. The card's conclusion — nothing reads this table — holds, and holds more strongly than its citation.So a green CI run here means "nothing else broke", not "the correction is right". The correction is carried by the declaration line numbers in the tables above, each of which a reviewer can open.
No pin test is added, and that is deliberate.#6347's pin was justified by a type-level claim a test could assert. The claim here is that a prose Type column matches a declaration — there is no assertion that reads a Markdown table without first building the table parser that objectui#5106 deliberately scoped to registration tables. Adding one reflexively would pin the table's current text, which reddens on every legitimate reword. If this class recurs, the right shape is a gate that parses property tables generally, not a pin on this page.
Two notes added below the table
The corrected table newly invites a wrong inference that the visibly-incomplete old one did not: that 21 rows are the accepted key set. Two short notes carry what a cell cannot — that a concrete schema may narrow an inherited member and its own declaration wins, and that the list is exhaustive for declared members but not for accepted keys.
The narrowing note deliberately carries no counts. Publishing "18 schemas narrow
disabled" in prose that nothing pins would reproduce the exact defect this PR fixes — a measured number going stale silently. The measurement is dated evidence and lives in the report, not in the page.A census I got wrong, recorded because the correction changed a row
My first reader census returned zero readers for
visibleWhen, which would have made it a declared-but-never-read member and therefore a declaration question to file rather than a row to write. It was wrong: the pattern matchedschema.visibleWhenand the actual reader isnewSchema.visibleWhen(packages/react/src/SchemaRenderer.tsx:1197). My control passed while the census was broken, because the control keys happened to exist in the lowercase form.Corrected,
visibleWhenis not merely read — it is the canonical predicate under ADR-0089 and is evaluated first, ahead ofvisibleandvisibleOn, which the row now states. A separate CJK-precedent census hit the same class:grep -Perrored out on this build and printed nothing, and only the control revealed that the zero was an error and not a measurement.Not changed, on purpose
jsonexample above the table. It shows a valid subset and makes no type claim; adding keys to it would edit a fence this PR has proven it does not touch.disabledasboolean. docs(components):sonnerandbutton-groupstill document four more props no shipped type declares #6347 measured 13 of 14 as correct because those schemas redeclare the key themselves, and correctingBaseSchemadoes not make them wrong.hidden's "Inverse ofvisible" wording, which is relocated unchanged. Correcting it towardbase.ts's own JSDoc would have made the page less accurate: filed separately, with the measurement.Verification
Run at
6dbef5210, the branch head:Verdicts are quoted from each gate's own printed line; exit codes were captured before any pipe.
check:doc-snippetswas narrowed, not skipped — its build closure is 21 packages, and the narrowing is measured with the gate's own collector as described above.Changeset is the empty-frontmatter form.
@object-ui/siteisprivate: trueand appears in.changeset/config.json'signorelist, and the presence gate itself reports "no changeset is owed" both before and after, so apatchbump would falsely claim a released package changed.skip-changesetis not applied: measured on #7073 that no workflow or script in this repository reads it.Generated by Claude Code
Generated by Claude Code