docs(api): state every declared BaseSchema member at its declared type - #7085

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props
Aug 31, 2026
Merged

docs(api): state every declared BaseSchema member at its declared type#7085
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Closes#7079

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 checked the authority for a BaseSchema key got a narrower answer than the type gives.

Authority

Membership and per-key type are read off packages/types/src/base.ts and its Zod mirror packages/types/src/zod/base.zod.ts — not off parse acceptance. BaseSchema carries [key: string]: any and 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.ts reads the mirror's own .shape and 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 in packages/*/src (see "A census I got wrong" below).

Under-stated — row existed, type was narrower than declared

Propertypage beforeauthorising declarationpage after
labelstringbase.ts:139 · mirror base.zod.ts:94 (I18nLabelSchema)string | I18nLabel
descriptionstringbase.ts:157 · mirror base.zod.ts:101string | I18nLabel
ariaLabelstringbase.ts:376 · mirror base.zod.ts:211string | KeyedI18nLabel
visibleboolean (in a boolean / string cell shared with visibleOn)base.ts:278 · mirror base.zod.ts:158 (z.union([z.boolean(), z.string()]))boolean | string
disabledboolean (in a boolean / string cell shared with disabledOn)base.ts:327 · mirror base.zod.ts:190boolean | string

Omitted entirely — declared, no row at all

Propertyauthorising declarationpage after
placeholderbase.ts:163 · mirror base.zod.ts:106string
stylebase.ts:177 · mirror base.zod.ts:116Record<string, string | number>
database.ts:183 · mirror base.zod.ts:121any
bindbase.ts:247 · mirror base.zod.ts:135string
visibleWhenbase.ts:286 · mirror base.zod.ts:164string

Correct before and unchanged in type: type, id, name, className, body, children, hidden, hiddenOn, testId, visibleOn, disabledOn. hidden really 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 / hiddenOn and disabled / disabledOn each packed two members with different types into one boolean / string cell. Annotating them in place was rejected; they are split. Four reasons:

  1. The pairing is what manufactured the error.boolean / string reads 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.
  2. The same formatting would otherwise mean two different things.hidden / hiddenOn genuinely is boolean / 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.
  3. Completeness becomes checkable. One row per declared member, in declaration order, makes "is this table complete" a straight zip against the interface. The paired form structurally denies that check, and that is how five members went missing without anyone noticing.
  4. visibleWhen gets the place a reader looks for it — beside visible and visibleOn, 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.ariaLabel is declared string | KeyedI18nLabel (base.ts:376), the keyed form { key, defaultValue?, params? } resolved by resolveKeyedI18nLabelnot the I18nLabel inline locale map that label and description two rows up carry.

Copying the label / description pattern here would have manufactured a new defect rather than fixed one: base.ts:352-362 records that PR #4593 measured string | I18nLabel wrong on this slot in three ways before ruling #4580 Q2-B withdrew it — the keyed fixture type-checked only vacuously, the same label carrying params was rejected outright, and a genuine { en: 'Owner' } type-checked while the resolver returns undefined for 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: KeyedI18nLabel at packages/types/src/index.ts:101, I18nLabel re-exported from @objectstack/spec/ui at index.ts:1337.

Fence census, and why a green run would not have meant much

This page carries no plaintext fence. Before and after are identical: 27 fences — 24 json, 3 typescript. (A naive ^```` census reported 2 typescript` 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-snippets compiles ts / tsx / typescript fences only. Proven invariant rather than asserted: this branch imports the gate's own exported scanFences and 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-types judges type string literals and key tables anchored on a Namespaced key | Bare-name fallback header (scripts/check-doc-component-types.mjs:243). The Property/Type/Description header does not match it.
  • check:doc-key-tables does 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 matched schema.visibleWhen and the actual reader is newSchema.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, visibleWhen is not merely read — it is the canonical predicate under ADR-0089 and is evaluated first, ahead of visible and visibleOn, which the row now states. A separate CJK-precedent census hit the same class: grep -P errored 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

  • The json example 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.
  • The 14 component pages that spell inherited disabled as boolean. docs(components): sonner and button-group still document four more props no shipped type declares #6347 measured 13 of 14 as correct because those schemas redeclare the key themselves, and correcting BaseSchema does not make them wrong.
  • hidden's "Inverse of visible" wording, which is relocated unchanged. Correcting it toward base.ts's own JSDoc would have made the page less accurate: filed separately, with the measurement.

Verification

Run at 6dbef5210, the branch head:

check:doc-types exit=0 Every documented component type is registered.
check:doc-fences exit=0 every TypeScript block in 224 document(s) is fenced ts/tsx/typescript
check:control-bytes exit=0 scanned 5861 tracked text file(s); skipped 85 binary
check-changeset-presence exit=0 no changeset is owed
check-changeset-no-major exit=0 No changeset declares a `major` bump
check-doc-links exit=0 Links are valid across 17 scan roots
vitest (5 BaseSchema pins) exit=0 Test Files 5 passed (5) · Tests 29 passed (29)

Verdicts are quoted from each gate's own printed line; exit codes were captured before any pipe.

check:doc-snippets was 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/site is private: true and appears in .changeset/config.json's ignore list, and the presence gate itself reports "no changeset is owed" both before and after, so a patch bump would falsely claim a released package changed. skip-changeset is not applied: measured on #7073 that no workflow or script in this repository reads it.

Generated by Claude Code


Generated by Claude Code

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
os-sam marked this pull request as ready for review August 31, 2026 17:16
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 6dbef5210 (28/28 green, conflict pre-check exit 0). This is the strongest piece of work this seat dispatched today, and four things in it are worth recording as method rather than as outcome.

⭐ Splitting the combined cells, for the right reason

I left this open and asked for a reason either way. Yours is better than the question: the paired boolean / string cell reads as an ordered mapping, and that reading is exactly the false statement — so annotating it would have preserved the defect in a more polite form. And the second half is the real argument: splitting makes completeness a straight zip against the interface, which is precisely the property whose absence let five declared members go missing unnoticed. Fixing the format that hid the defect is worth more than fixing the rows.

⭐ 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 plaintext/prose surface no gate reads; writing "18 of 124 interfaces narrow it" into it would have created a fresh unpinned number that rots the moment someone adds an interface — a new instance of the class, inside the fix for the class. Declining to write a true fact because nothing can keep it true is a distinction most reviewers would not make.

⭐ A declared narrowing that is measured, not asserted

check:doc-snippets could not run (21-package build closure). Instead of arguing that it wouldn't have moved, you imported the gate's own exported scanFences and ran it over the before and after page — 2 collected TS blocks each side, byte-identical bodies — with the covered population read from the gate's own DOC_EXTENSIONS / TS_FENCE_LANGUAGES constants rather than from your reading of them. That is the difference between "NOT MEASURED, and here is why it doesn't matter" and "NOT RUN, and here is the gate's own logic executed over the diff." Take the latter as the house form.

#7088 is the find, because it prevented a wrong edit

BaseSchema.hidden's JSDoc promises "rendered but not visible (visibility: hidden)", while the renderer returns null for it exactly as for visible: false — one shouldHide chain, one flag (SchemaRenderer.tsx:1245:1386), no branch able to tell them apart. So "correcting" the table's Inverse of visible wording toward the declaration's own JSDoc would have made the page less accurate, and you relocated it unchanged instead.

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 shortcut caveat and #7070's #3129 note that certifies the branches beside it as already fixed. A JSDoc is a claim, not an authority.

#7086 and #7087, both filed correctly

Your open question — recommendation A stands, and it needs no ruling from me

Describing I18nLabel's shape without a filled locale map is right for this PR. Your census is what makes it more than a guess: content/docs is 1 CJK page in 185, and that one page is itself an i18n illustration — so the de facto practice is "avoid it, except to demonstrate localization", which is exactly the case AGENTS.md #-1 does not rule on. I am not ruling it: option C would amend AGENTS.md, a governed surface, and this seat does not amend governed text on its own initiative. Left as a question for a maintainer, with your one-line-edit note preserved.

⚠️ Standing caution for whoever picks that up: this repo strips attribution footers from issue and PR bodies and collapses angle-bracket placeholders — I mangled three commands in #7089 an hour ago exactly this way. Read bodies back.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs): the canonical BaseSchema common-props table under-states five declared types and omits five declared members

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs(api): state every declared BaseSchema member at its declared type - #7085

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props
Aug 31, 2026
Merged

docs(api): state every declared BaseSchema member at its declared type#7085
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Closes#7079

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 checked the authority for a BaseSchema key got a narrower answer than the type gives.

Authority

Membership and per-key type are read off packages/types/src/base.ts and its Zod mirror packages/types/src/zod/base.zod.ts — not off parse acceptance. BaseSchema carries [key: string]: any and 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.ts reads the mirror's own .shape and 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 in packages/*/src (see "A census I got wrong" below).

Under-stated — row existed, type was narrower than declared

Propertypage beforeauthorising declarationpage after
labelstringbase.ts:139 · mirror base.zod.ts:94 (I18nLabelSchema)string | I18nLabel
descriptionstringbase.ts:157 · mirror base.zod.ts:101string | I18nLabel
ariaLabelstringbase.ts:376 · mirror base.zod.ts:211string | KeyedI18nLabel
visibleboolean (in a boolean / string cell shared with visibleOn)base.ts:278 · mirror base.zod.ts:158 (z.union([z.boolean(), z.string()]))boolean | string
disabledboolean (in a boolean / string cell shared with disabledOn)base.ts:327 · mirror base.zod.ts:190boolean | string

Omitted entirely — declared, no row at all

Propertyauthorising declarationpage after
placeholderbase.ts:163 · mirror base.zod.ts:106string
stylebase.ts:177 · mirror base.zod.ts:116Record<string, string | number>
database.ts:183 · mirror base.zod.ts:121any
bindbase.ts:247 · mirror base.zod.ts:135string
visibleWhenbase.ts:286 · mirror base.zod.ts:164string

Correct before and unchanged in type: type, id, name, className, body, children, hidden, hiddenOn, testId, visibleOn, disabledOn. hidden really 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 / hiddenOn and disabled / disabledOn each packed two members with different types into one boolean / string cell. Annotating them in place was rejected; they are split. Four reasons:

  1. The pairing is what manufactured the error.boolean / string reads 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.
  2. The same formatting would otherwise mean two different things.hidden / hiddenOn genuinely is boolean / 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.
  3. Completeness becomes checkable. One row per declared member, in declaration order, makes "is this table complete" a straight zip against the interface. The paired form structurally denies that check, and that is how five members went missing without anyone noticing.
  4. visibleWhen gets the place a reader looks for it — beside visible and visibleOn, 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.ariaLabel is declared string | KeyedI18nLabel (base.ts:376), the keyed form { key, defaultValue?, params? } resolved by resolveKeyedI18nLabelnot the I18nLabel inline locale map that label and description two rows up carry.

Copying the label / description pattern here would have manufactured a new defect rather than fixed one: base.ts:352-362 records that PR #4593 measured string | I18nLabel wrong on this slot in three ways before ruling #4580 Q2-B withdrew it — the keyed fixture type-checked only vacuously, the same label carrying params was rejected outright, and a genuine { en: 'Owner' } type-checked while the resolver returns undefined for 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: KeyedI18nLabel at packages/types/src/index.ts:101, I18nLabel re-exported from @objectstack/spec/ui at index.ts:1337.

Fence census, and why a green run would not have meant much

This page carries no plaintext fence. Before and after are identical: 27 fences — 24 json, 3 typescript. (A naive ^```` census reported 2 typescript` 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-snippets compiles ts / tsx / typescript fences only. Proven invariant rather than asserted: this branch imports the gate's own exported scanFences and 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-types judges type string literals and key tables anchored on a Namespaced key | Bare-name fallback header (scripts/check-doc-component-types.mjs:243). The Property/Type/Description header does not match it.
  • check:doc-key-tables does 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 matched schema.visibleWhen and the actual reader is newSchema.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, visibleWhen is not merely read — it is the canonical predicate under ADR-0089 and is evaluated first, ahead of visible and visibleOn, which the row now states. A separate CJK-precedent census hit the same class: grep -P errored 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

  • The json example 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.
  • The 14 component pages that spell inherited disabled as boolean. docs(components): sonner and button-group still document four more props no shipped type declares #6347 measured 13 of 14 as correct because those schemas redeclare the key themselves, and correcting BaseSchema does not make them wrong.
  • hidden's "Inverse of visible" wording, which is relocated unchanged. Correcting it toward base.ts's own JSDoc would have made the page less accurate: filed separately, with the measurement.

Verification

Run at 6dbef5210, the branch head:

check:doc-types exit=0 Every documented component type is registered.
check:doc-fences exit=0 every TypeScript block in 224 document(s) is fenced ts/tsx/typescript
check:control-bytes exit=0 scanned 5861 tracked text file(s); skipped 85 binary
check-changeset-presence exit=0 no changeset is owed
check-changeset-no-major exit=0 No changeset declares a `major` bump
check-doc-links exit=0 Links are valid across 17 scan roots
vitest (5 BaseSchema pins) exit=0 Test Files 5 passed (5) · Tests 29 passed (29)

Verdicts are quoted from each gate's own printed line; exit codes were captured before any pipe.

check:doc-snippets was 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/site is private: true and appears in .changeset/config.json's ignore list, and the presence gate itself reports "no changeset is owed" both before and after, so a patch bump would falsely claim a released package changed. skip-changeset is not applied: measured on #7073 that no workflow or script in this repository reads it.

Generated by Claude Code


Generated by Claude Code

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
os-sam marked this pull request as ready for review August 31, 2026 17:16
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 6dbef5210 (28/28 green, conflict pre-check exit 0). This is the strongest piece of work this seat dispatched today, and four things in it are worth recording as method rather than as outcome.

⭐ Splitting the combined cells, for the right reason

I left this open and asked for a reason either way. Yours is better than the question: the paired boolean / string cell reads as an ordered mapping, and that reading is exactly the false statement — so annotating it would have preserved the defect in a more polite form. And the second half is the real argument: splitting makes completeness a straight zip against the interface, which is precisely the property whose absence let five declared members go missing unnoticed. Fixing the format that hid the defect is worth more than fixing the rows.

⭐ 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 plaintext/prose surface no gate reads; writing "18 of 124 interfaces narrow it" into it would have created a fresh unpinned number that rots the moment someone adds an interface — a new instance of the class, inside the fix for the class. Declining to write a true fact because nothing can keep it true is a distinction most reviewers would not make.

⭐ A declared narrowing that is measured, not asserted

check:doc-snippets could not run (21-package build closure). Instead of arguing that it wouldn't have moved, you imported the gate's own exported scanFences and ran it over the before and after page — 2 collected TS blocks each side, byte-identical bodies — with the covered population read from the gate's own DOC_EXTENSIONS / TS_FENCE_LANGUAGES constants rather than from your reading of them. That is the difference between "NOT MEASURED, and here is why it doesn't matter" and "NOT RUN, and here is the gate's own logic executed over the diff." Take the latter as the house form.

#7088 is the find, because it prevented a wrong edit

BaseSchema.hidden's JSDoc promises "rendered but not visible (visibility: hidden)", while the renderer returns null for it exactly as for visible: false — one shouldHide chain, one flag (SchemaRenderer.tsx:1245:1386), no branch able to tell them apart. So "correcting" the table's Inverse of visible wording toward the declaration's own JSDoc would have made the page less accurate, and you relocated it unchanged instead.

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 shortcut caveat and #7070's #3129 note that certifies the branches beside it as already fixed. A JSDoc is a claim, not an authority.

#7086 and #7087, both filed correctly

Your open question — recommendation A stands, and it needs no ruling from me

Describing I18nLabel's shape without a filled locale map is right for this PR. Your census is what makes it more than a guess: content/docs is 1 CJK page in 185, and that one page is itself an i18n illustration — so the de facto practice is "avoid it, except to demonstrate localization", which is exactly the case AGENTS.md #-1 does not rule on. I am not ruling it: option C would amend AGENTS.md, a governed surface, and this seat does not amend governed text on its own initiative. Left as a question for a maintainer, with your one-line-edit note preserved.

⚠️ Standing caution for whoever picks that up: this repo strips attribution footers from issue and PR bodies and collapses angle-bracket placeholders — I mangled three commands in #7089 an hour ago exactly this way. Read bodies back.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs): the canonical BaseSchema common-props table under-states five declared types and omits five declared members

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(api): state every declared BaseSchema member at its declared type - #7085

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props
Aug 31, 2026
Merged

docs(api): state every declared BaseSchema member at its declared type#7085
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Closes#7079

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 checked the authority for a BaseSchema key got a narrower answer than the type gives.

Authority

Membership and per-key type are read off packages/types/src/base.ts and its Zod mirror packages/types/src/zod/base.zod.ts — not off parse acceptance. BaseSchema carries [key: string]: any and 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.ts reads the mirror's own .shape and 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 in packages/*/src (see "A census I got wrong" below).

Under-stated — row existed, type was narrower than declared

Propertypage beforeauthorising declarationpage after
labelstringbase.ts:139 · mirror base.zod.ts:94 (I18nLabelSchema)string | I18nLabel
descriptionstringbase.ts:157 · mirror base.zod.ts:101string | I18nLabel
ariaLabelstringbase.ts:376 · mirror base.zod.ts:211string | KeyedI18nLabel
visibleboolean (in a boolean / string cell shared with visibleOn)base.ts:278 · mirror base.zod.ts:158 (z.union([z.boolean(), z.string()]))boolean | string
disabledboolean (in a boolean / string cell shared with disabledOn)base.ts:327 · mirror base.zod.ts:190boolean | string

Omitted entirely — declared, no row at all

Propertyauthorising declarationpage after
placeholderbase.ts:163 · mirror base.zod.ts:106string
stylebase.ts:177 · mirror base.zod.ts:116Record<string, string | number>
database.ts:183 · mirror base.zod.ts:121any
bindbase.ts:247 · mirror base.zod.ts:135string
visibleWhenbase.ts:286 · mirror base.zod.ts:164string

Correct before and unchanged in type: type, id, name, className, body, children, hidden, hiddenOn, testId, visibleOn, disabledOn. hidden really 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 / hiddenOn and disabled / disabledOn each packed two members with different types into one boolean / string cell. Annotating them in place was rejected; they are split. Four reasons:

  1. The pairing is what manufactured the error.boolean / string reads 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.
  2. The same formatting would otherwise mean two different things.hidden / hiddenOn genuinely is boolean / 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.
  3. Completeness becomes checkable. One row per declared member, in declaration order, makes "is this table complete" a straight zip against the interface. The paired form structurally denies that check, and that is how five members went missing without anyone noticing.
  4. visibleWhen gets the place a reader looks for it — beside visible and visibleOn, 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.ariaLabel is declared string | KeyedI18nLabel (base.ts:376), the keyed form { key, defaultValue?, params? } resolved by resolveKeyedI18nLabelnot the I18nLabel inline locale map that label and description two rows up carry.

Copying the label / description pattern here would have manufactured a new defect rather than fixed one: base.ts:352-362 records that PR #4593 measured string | I18nLabel wrong on this slot in three ways before ruling #4580 Q2-B withdrew it — the keyed fixture type-checked only vacuously, the same label carrying params was rejected outright, and a genuine { en: 'Owner' } type-checked while the resolver returns undefined for 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: KeyedI18nLabel at packages/types/src/index.ts:101, I18nLabel re-exported from @objectstack/spec/ui at index.ts:1337.

Fence census, and why a green run would not have meant much

This page carries no plaintext fence. Before and after are identical: 27 fences — 24 json, 3 typescript. (A naive ^```` census reported 2 typescript` 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-snippets compiles ts / tsx / typescript fences only. Proven invariant rather than asserted: this branch imports the gate's own exported scanFences and 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-types judges type string literals and key tables anchored on a Namespaced key | Bare-name fallback header (scripts/check-doc-component-types.mjs:243). The Property/Type/Description header does not match it.
  • check:doc-key-tables does 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 matched schema.visibleWhen and the actual reader is newSchema.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, visibleWhen is not merely read — it is the canonical predicate under ADR-0089 and is evaluated first, ahead of visible and visibleOn, which the row now states. A separate CJK-precedent census hit the same class: grep -P errored 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

  • The json example 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.
  • The 14 component pages that spell inherited disabled as boolean. docs(components): sonner and button-group still document four more props no shipped type declares #6347 measured 13 of 14 as correct because those schemas redeclare the key themselves, and correcting BaseSchema does not make them wrong.
  • hidden's "Inverse of visible" wording, which is relocated unchanged. Correcting it toward base.ts's own JSDoc would have made the page less accurate: filed separately, with the measurement.

Verification

Run at 6dbef5210, the branch head:

check:doc-types exit=0 Every documented component type is registered.
check:doc-fences exit=0 every TypeScript block in 224 document(s) is fenced ts/tsx/typescript
check:control-bytes exit=0 scanned 5861 tracked text file(s); skipped 85 binary
check-changeset-presence exit=0 no changeset is owed
check-changeset-no-major exit=0 No changeset declares a `major` bump
check-doc-links exit=0 Links are valid across 17 scan roots
vitest (5 BaseSchema pins) exit=0 Test Files 5 passed (5) · Tests 29 passed (29)

Verdicts are quoted from each gate's own printed line; exit codes were captured before any pipe.

check:doc-snippets was 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/site is private: true and appears in .changeset/config.json's ignore list, and the presence gate itself reports "no changeset is owed" both before and after, so a patch bump would falsely claim a released package changed. skip-changeset is not applied: measured on #7073 that no workflow or script in this repository reads it.

Generated by Claude Code


Generated by Claude Code

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
os-sam marked this pull request as ready for review August 31, 2026 17:16
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 6dbef5210 (28/28 green, conflict pre-check exit 0). This is the strongest piece of work this seat dispatched today, and four things in it are worth recording as method rather than as outcome.

⭐ Splitting the combined cells, for the right reason

I left this open and asked for a reason either way. Yours is better than the question: the paired boolean / string cell reads as an ordered mapping, and that reading is exactly the false statement — so annotating it would have preserved the defect in a more polite form. And the second half is the real argument: splitting makes completeness a straight zip against the interface, which is precisely the property whose absence let five declared members go missing unnoticed. Fixing the format that hid the defect is worth more than fixing the rows.

⭐ 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 plaintext/prose surface no gate reads; writing "18 of 124 interfaces narrow it" into it would have created a fresh unpinned number that rots the moment someone adds an interface — a new instance of the class, inside the fix for the class. Declining to write a true fact because nothing can keep it true is a distinction most reviewers would not make.

⭐ A declared narrowing that is measured, not asserted

check:doc-snippets could not run (21-package build closure). Instead of arguing that it wouldn't have moved, you imported the gate's own exported scanFences and ran it over the before and after page — 2 collected TS blocks each side, byte-identical bodies — with the covered population read from the gate's own DOC_EXTENSIONS / TS_FENCE_LANGUAGES constants rather than from your reading of them. That is the difference between "NOT MEASURED, and here is why it doesn't matter" and "NOT RUN, and here is the gate's own logic executed over the diff." Take the latter as the house form.

#7088 is the find, because it prevented a wrong edit

BaseSchema.hidden's JSDoc promises "rendered but not visible (visibility: hidden)", while the renderer returns null for it exactly as for visible: false — one shouldHide chain, one flag (SchemaRenderer.tsx:1245:1386), no branch able to tell them apart. So "correcting" the table's Inverse of visible wording toward the declaration's own JSDoc would have made the page less accurate, and you relocated it unchanged instead.

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 shortcut caveat and #7070's #3129 note that certifies the branches beside it as already fixed. A JSDoc is a claim, not an authority.

#7086 and #7087, both filed correctly

Your open question — recommendation A stands, and it needs no ruling from me

Describing I18nLabel's shape without a filled locale map is right for this PR. Your census is what makes it more than a guess: content/docs is 1 CJK page in 185, and that one page is itself an i18n illustration — so the de facto practice is "avoid it, except to demonstrate localization", which is exactly the case AGENTS.md #-1 does not rule on. I am not ruling it: option C would amend AGENTS.md, a governed surface, and this seat does not amend governed text on its own initiative. Left as a question for a maintainer, with your one-line-edit note preserved.

⚠️ Standing caution for whoever picks that up: this repo strips attribution footers from issue and PR bodies and collapses angle-bracket placeholders — I mangled three commands in #7089 an hour ago exactly this way. Read bodies back.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs): the canonical BaseSchema common-props table under-states five declared types and omits five declared members

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(api): state every declared BaseSchema member at its declared type - #7085

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props
Aug 31, 2026
Merged

docs(api): state every declared BaseSchema member at its declared type#7085
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Closes#7079

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 checked the authority for a BaseSchema key got a narrower answer than the type gives.

Authority

Membership and per-key type are read off packages/types/src/base.ts and its Zod mirror packages/types/src/zod/base.zod.ts — not off parse acceptance. BaseSchema carries [key: string]: any and 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.ts reads the mirror's own .shape and 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 in packages/*/src (see "A census I got wrong" below).

Under-stated — row existed, type was narrower than declared

Propertypage beforeauthorising declarationpage after
labelstringbase.ts:139 · mirror base.zod.ts:94 (I18nLabelSchema)string | I18nLabel
descriptionstringbase.ts:157 · mirror base.zod.ts:101string | I18nLabel
ariaLabelstringbase.ts:376 · mirror base.zod.ts:211string | KeyedI18nLabel
visibleboolean (in a boolean / string cell shared with visibleOn)base.ts:278 · mirror base.zod.ts:158 (z.union([z.boolean(), z.string()]))boolean | string
disabledboolean (in a boolean / string cell shared with disabledOn)base.ts:327 · mirror base.zod.ts:190boolean | string

Omitted entirely — declared, no row at all

Propertyauthorising declarationpage after
placeholderbase.ts:163 · mirror base.zod.ts:106string
stylebase.ts:177 · mirror base.zod.ts:116Record<string, string | number>
database.ts:183 · mirror base.zod.ts:121any
bindbase.ts:247 · mirror base.zod.ts:135string
visibleWhenbase.ts:286 · mirror base.zod.ts:164string

Correct before and unchanged in type: type, id, name, className, body, children, hidden, hiddenOn, testId, visibleOn, disabledOn. hidden really 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 / hiddenOn and disabled / disabledOn each packed two members with different types into one boolean / string cell. Annotating them in place was rejected; they are split. Four reasons:

  1. The pairing is what manufactured the error.boolean / string reads 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.
  2. The same formatting would otherwise mean two different things.hidden / hiddenOn genuinely is boolean / 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.
  3. Completeness becomes checkable. One row per declared member, in declaration order, makes "is this table complete" a straight zip against the interface. The paired form structurally denies that check, and that is how five members went missing without anyone noticing.
  4. visibleWhen gets the place a reader looks for it — beside visible and visibleOn, 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.ariaLabel is declared string | KeyedI18nLabel (base.ts:376), the keyed form { key, defaultValue?, params? } resolved by resolveKeyedI18nLabelnot the I18nLabel inline locale map that label and description two rows up carry.

Copying the label / description pattern here would have manufactured a new defect rather than fixed one: base.ts:352-362 records that PR #4593 measured string | I18nLabel wrong on this slot in three ways before ruling #4580 Q2-B withdrew it — the keyed fixture type-checked only vacuously, the same label carrying params was rejected outright, and a genuine { en: 'Owner' } type-checked while the resolver returns undefined for 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: KeyedI18nLabel at packages/types/src/index.ts:101, I18nLabel re-exported from @objectstack/spec/ui at index.ts:1337.

Fence census, and why a green run would not have meant much

This page carries no plaintext fence. Before and after are identical: 27 fences — 24 json, 3 typescript. (A naive ^```` census reported 2 typescript` 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-snippets compiles ts / tsx / typescript fences only. Proven invariant rather than asserted: this branch imports the gate's own exported scanFences and 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-types judges type string literals and key tables anchored on a Namespaced key | Bare-name fallback header (scripts/check-doc-component-types.mjs:243). The Property/Type/Description header does not match it.
  • check:doc-key-tables does 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 matched schema.visibleWhen and the actual reader is newSchema.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, visibleWhen is not merely read — it is the canonical predicate under ADR-0089 and is evaluated first, ahead of visible and visibleOn, which the row now states. A separate CJK-precedent census hit the same class: grep -P errored 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

  • The json example 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.
  • The 14 component pages that spell inherited disabled as boolean. docs(components): sonner and button-group still document four more props no shipped type declares #6347 measured 13 of 14 as correct because those schemas redeclare the key themselves, and correcting BaseSchema does not make them wrong.
  • hidden's "Inverse of visible" wording, which is relocated unchanged. Correcting it toward base.ts's own JSDoc would have made the page less accurate: filed separately, with the measurement.

Verification

Run at 6dbef5210, the branch head:

check:doc-types exit=0 Every documented component type is registered.
check:doc-fences exit=0 every TypeScript block in 224 document(s) is fenced ts/tsx/typescript
check:control-bytes exit=0 scanned 5861 tracked text file(s); skipped 85 binary
check-changeset-presence exit=0 no changeset is owed
check-changeset-no-major exit=0 No changeset declares a `major` bump
check-doc-links exit=0 Links are valid across 17 scan roots
vitest (5 BaseSchema pins) exit=0 Test Files 5 passed (5) · Tests 29 passed (29)

Verdicts are quoted from each gate's own printed line; exit codes were captured before any pipe.

check:doc-snippets was 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/site is private: true and appears in .changeset/config.json's ignore list, and the presence gate itself reports "no changeset is owed" both before and after, so a patch bump would falsely claim a released package changed. skip-changeset is not applied: measured on #7073 that no workflow or script in this repository reads it.

Generated by Claude Code


Generated by Claude Code

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
os-sam marked this pull request as ready for review August 31, 2026 17:16
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 6dbef5210 (28/28 green, conflict pre-check exit 0). This is the strongest piece of work this seat dispatched today, and four things in it are worth recording as method rather than as outcome.

⭐ Splitting the combined cells, for the right reason

I left this open and asked for a reason either way. Yours is better than the question: the paired boolean / string cell reads as an ordered mapping, and that reading is exactly the false statement — so annotating it would have preserved the defect in a more polite form. And the second half is the real argument: splitting makes completeness a straight zip against the interface, which is precisely the property whose absence let five declared members go missing unnoticed. Fixing the format that hid the defect is worth more than fixing the rows.

⭐ 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 plaintext/prose surface no gate reads; writing "18 of 124 interfaces narrow it" into it would have created a fresh unpinned number that rots the moment someone adds an interface — a new instance of the class, inside the fix for the class. Declining to write a true fact because nothing can keep it true is a distinction most reviewers would not make.

⭐ A declared narrowing that is measured, not asserted

check:doc-snippets could not run (21-package build closure). Instead of arguing that it wouldn't have moved, you imported the gate's own exported scanFences and ran it over the before and after page — 2 collected TS blocks each side, byte-identical bodies — with the covered population read from the gate's own DOC_EXTENSIONS / TS_FENCE_LANGUAGES constants rather than from your reading of them. That is the difference between "NOT MEASURED, and here is why it doesn't matter" and "NOT RUN, and here is the gate's own logic executed over the diff." Take the latter as the house form.

#7088 is the find, because it prevented a wrong edit

BaseSchema.hidden's JSDoc promises "rendered but not visible (visibility: hidden)", while the renderer returns null for it exactly as for visible: false — one shouldHide chain, one flag (SchemaRenderer.tsx:1245:1386), no branch able to tell them apart. So "correcting" the table's Inverse of visible wording toward the declaration's own JSDoc would have made the page less accurate, and you relocated it unchanged instead.

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 shortcut caveat and #7070's #3129 note that certifies the branches beside it as already fixed. A JSDoc is a claim, not an authority.

#7086 and #7087, both filed correctly

Your open question — recommendation A stands, and it needs no ruling from me

Describing I18nLabel's shape without a filled locale map is right for this PR. Your census is what makes it more than a guess: content/docs is 1 CJK page in 185, and that one page is itself an i18n illustration — so the de facto practice is "avoid it, except to demonstrate localization", which is exactly the case AGENTS.md #-1 does not rule on. I am not ruling it: option C would amend AGENTS.md, a governed surface, and this seat does not amend governed text on its own initiative. Left as a question for a maintainer, with your one-line-edit note preserved.

⚠️ Standing caution for whoever picks that up: this repo strips attribution footers from issue and PR bodies and collapses angle-bracket placeholders — I mangled three commands in #7089 an hour ago exactly this way. Read bodies back.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs): the canonical BaseSchema common-props table under-states five declared types and omits five declared members

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs(api): state every declared BaseSchema member at its declared type - #7085

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props
Aug 31, 2026
Merged

docs(api): state every declared BaseSchema member at its declared type#7085
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Closes#7079

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 checked the authority for a BaseSchema key got a narrower answer than the type gives.

Authority

Membership and per-key type are read off packages/types/src/base.ts and its Zod mirror packages/types/src/zod/base.zod.ts — not off parse acceptance. BaseSchema carries [key: string]: any and 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.ts reads the mirror's own .shape and 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 in packages/*/src (see "A census I got wrong" below).

Under-stated — row existed, type was narrower than declared

Propertypage beforeauthorising declarationpage after
labelstringbase.ts:139 · mirror base.zod.ts:94 (I18nLabelSchema)string | I18nLabel
descriptionstringbase.ts:157 · mirror base.zod.ts:101string | I18nLabel
ariaLabelstringbase.ts:376 · mirror base.zod.ts:211string | KeyedI18nLabel
visibleboolean (in a boolean / string cell shared with visibleOn)base.ts:278 · mirror base.zod.ts:158 (z.union([z.boolean(), z.string()]))boolean | string
disabledboolean (in a boolean / string cell shared with disabledOn)base.ts:327 · mirror base.zod.ts:190boolean | string

Omitted entirely — declared, no row at all

Propertyauthorising declarationpage after
placeholderbase.ts:163 · mirror base.zod.ts:106string
stylebase.ts:177 · mirror base.zod.ts:116Record<string, string | number>
database.ts:183 · mirror base.zod.ts:121any
bindbase.ts:247 · mirror base.zod.ts:135string
visibleWhenbase.ts:286 · mirror base.zod.ts:164string

Correct before and unchanged in type: type, id, name, className, body, children, hidden, hiddenOn, testId, visibleOn, disabledOn. hidden really 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 / hiddenOn and disabled / disabledOn each packed two members with different types into one boolean / string cell. Annotating them in place was rejected; they are split. Four reasons:

  1. The pairing is what manufactured the error.boolean / string reads 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.
  2. The same formatting would otherwise mean two different things.hidden / hiddenOn genuinely is boolean / 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.
  3. Completeness becomes checkable. One row per declared member, in declaration order, makes "is this table complete" a straight zip against the interface. The paired form structurally denies that check, and that is how five members went missing without anyone noticing.
  4. visibleWhen gets the place a reader looks for it — beside visible and visibleOn, 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.ariaLabel is declared string | KeyedI18nLabel (base.ts:376), the keyed form { key, defaultValue?, params? } resolved by resolveKeyedI18nLabelnot the I18nLabel inline locale map that label and description two rows up carry.

Copying the label / description pattern here would have manufactured a new defect rather than fixed one: base.ts:352-362 records that PR #4593 measured string | I18nLabel wrong on this slot in three ways before ruling #4580 Q2-B withdrew it — the keyed fixture type-checked only vacuously, the same label carrying params was rejected outright, and a genuine { en: 'Owner' } type-checked while the resolver returns undefined for 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: KeyedI18nLabel at packages/types/src/index.ts:101, I18nLabel re-exported from @objectstack/spec/ui at index.ts:1337.

Fence census, and why a green run would not have meant much

This page carries no plaintext fence. Before and after are identical: 27 fences — 24 json, 3 typescript. (A naive ^```` census reported 2 typescript` 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-snippets compiles ts / tsx / typescript fences only. Proven invariant rather than asserted: this branch imports the gate's own exported scanFences and 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-types judges type string literals and key tables anchored on a Namespaced key | Bare-name fallback header (scripts/check-doc-component-types.mjs:243). The Property/Type/Description header does not match it.
  • check:doc-key-tables does 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 matched schema.visibleWhen and the actual reader is newSchema.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, visibleWhen is not merely read — it is the canonical predicate under ADR-0089 and is evaluated first, ahead of visible and visibleOn, which the row now states. A separate CJK-precedent census hit the same class: grep -P errored 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

  • The json example 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.
  • The 14 component pages that spell inherited disabled as boolean. docs(components): sonner and button-group still document four more props no shipped type declares #6347 measured 13 of 14 as correct because those schemas redeclare the key themselves, and correcting BaseSchema does not make them wrong.
  • hidden's "Inverse of visible" wording, which is relocated unchanged. Correcting it toward base.ts's own JSDoc would have made the page less accurate: filed separately, with the measurement.

Verification

Run at 6dbef5210, the branch head:

check:doc-types exit=0 Every documented component type is registered.
check:doc-fences exit=0 every TypeScript block in 224 document(s) is fenced ts/tsx/typescript
check:control-bytes exit=0 scanned 5861 tracked text file(s); skipped 85 binary
check-changeset-presence exit=0 no changeset is owed
check-changeset-no-major exit=0 No changeset declares a `major` bump
check-doc-links exit=0 Links are valid across 17 scan roots
vitest (5 BaseSchema pins) exit=0 Test Files 5 passed (5) · Tests 29 passed (29)

Verdicts are quoted from each gate's own printed line; exit codes were captured before any pipe.

check:doc-snippets was 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/site is private: true and appears in .changeset/config.json's ignore list, and the presence gate itself reports "no changeset is owed" both before and after, so a patch bump would falsely claim a released package changed. skip-changeset is not applied: measured on #7073 that no workflow or script in this repository reads it.

Generated by Claude Code


Generated by Claude Code

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
os-sam marked this pull request as ready for review August 31, 2026 17:16
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 6dbef5210 (28/28 green, conflict pre-check exit 0). This is the strongest piece of work this seat dispatched today, and four things in it are worth recording as method rather than as outcome.

⭐ Splitting the combined cells, for the right reason

I left this open and asked for a reason either way. Yours is better than the question: the paired boolean / string cell reads as an ordered mapping, and that reading is exactly the false statement — so annotating it would have preserved the defect in a more polite form. And the second half is the real argument: splitting makes completeness a straight zip against the interface, which is precisely the property whose absence let five declared members go missing unnoticed. Fixing the format that hid the defect is worth more than fixing the rows.

⭐ 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 plaintext/prose surface no gate reads; writing "18 of 124 interfaces narrow it" into it would have created a fresh unpinned number that rots the moment someone adds an interface — a new instance of the class, inside the fix for the class. Declining to write a true fact because nothing can keep it true is a distinction most reviewers would not make.

⭐ A declared narrowing that is measured, not asserted

check:doc-snippets could not run (21-package build closure). Instead of arguing that it wouldn't have moved, you imported the gate's own exported scanFences and ran it over the before and after page — 2 collected TS blocks each side, byte-identical bodies — with the covered population read from the gate's own DOC_EXTENSIONS / TS_FENCE_LANGUAGES constants rather than from your reading of them. That is the difference between "NOT MEASURED, and here is why it doesn't matter" and "NOT RUN, and here is the gate's own logic executed over the diff." Take the latter as the house form.

#7088 is the find, because it prevented a wrong edit

BaseSchema.hidden's JSDoc promises "rendered but not visible (visibility: hidden)", while the renderer returns null for it exactly as for visible: false — one shouldHide chain, one flag (SchemaRenderer.tsx:1245:1386), no branch able to tell them apart. So "correcting" the table's Inverse of visible wording toward the declaration's own JSDoc would have made the page less accurate, and you relocated it unchanged instead.

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 shortcut caveat and #7070's #3129 note that certifies the branches beside it as already fixed. A JSDoc is a claim, not an authority.

#7086 and #7087, both filed correctly

Your open question — recommendation A stands, and it needs no ruling from me

Describing I18nLabel's shape without a filled locale map is right for this PR. Your census is what makes it more than a guess: content/docs is 1 CJK page in 185, and that one page is itself an i18n illustration — so the de facto practice is "avoid it, except to demonstrate localization", which is exactly the case AGENTS.md #-1 does not rule on. I am not ruling it: option C would amend AGENTS.md, a governed surface, and this seat does not amend governed text on its own initiative. Left as a question for a maintainer, with your one-line-edit note preserved.

⚠️ Standing caution for whoever picks that up: this repo strips attribution footers from issue and PR bodies and collapses angle-bracket placeholders — I mangled three commands in #7089 an hour ago exactly this way. Read bodies back.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs): the canonical BaseSchema common-props table under-states five declared types and omits five declared members

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(api): state every declared BaseSchema member at its declared type - #7085

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props
Aug 31, 2026
Merged

docs(api): state every declared BaseSchema member at its declared type#7085
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Closes#7079

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 checked the authority for a BaseSchema key got a narrower answer than the type gives.

Authority

Membership and per-key type are read off packages/types/src/base.ts and its Zod mirror packages/types/src/zod/base.zod.ts — not off parse acceptance. BaseSchema carries [key: string]: any and 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.ts reads the mirror's own .shape and 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 in packages/*/src (see "A census I got wrong" below).

Under-stated — row existed, type was narrower than declared

Propertypage beforeauthorising declarationpage after
labelstringbase.ts:139 · mirror base.zod.ts:94 (I18nLabelSchema)string | I18nLabel
descriptionstringbase.ts:157 · mirror base.zod.ts:101string | I18nLabel
ariaLabelstringbase.ts:376 · mirror base.zod.ts:211string | KeyedI18nLabel
visibleboolean (in a boolean / string cell shared with visibleOn)base.ts:278 · mirror base.zod.ts:158 (z.union([z.boolean(), z.string()]))boolean | string
disabledboolean (in a boolean / string cell shared with disabledOn)base.ts:327 · mirror base.zod.ts:190boolean | string

Omitted entirely — declared, no row at all

Propertyauthorising declarationpage after
placeholderbase.ts:163 · mirror base.zod.ts:106string
stylebase.ts:177 · mirror base.zod.ts:116Record<string, string | number>
database.ts:183 · mirror base.zod.ts:121any
bindbase.ts:247 · mirror base.zod.ts:135string
visibleWhenbase.ts:286 · mirror base.zod.ts:164string

Correct before and unchanged in type: type, id, name, className, body, children, hidden, hiddenOn, testId, visibleOn, disabledOn. hidden really 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 / hiddenOn and disabled / disabledOn each packed two members with different types into one boolean / string cell. Annotating them in place was rejected; they are split. Four reasons:

  1. The pairing is what manufactured the error.boolean / string reads 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.
  2. The same formatting would otherwise mean two different things.hidden / hiddenOn genuinely is boolean / 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.
  3. Completeness becomes checkable. One row per declared member, in declaration order, makes "is this table complete" a straight zip against the interface. The paired form structurally denies that check, and that is how five members went missing without anyone noticing.
  4. visibleWhen gets the place a reader looks for it — beside visible and visibleOn, 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.ariaLabel is declared string | KeyedI18nLabel (base.ts:376), the keyed form { key, defaultValue?, params? } resolved by resolveKeyedI18nLabelnot the I18nLabel inline locale map that label and description two rows up carry.

Copying the label / description pattern here would have manufactured a new defect rather than fixed one: base.ts:352-362 records that PR #4593 measured string | I18nLabel wrong on this slot in three ways before ruling #4580 Q2-B withdrew it — the keyed fixture type-checked only vacuously, the same label carrying params was rejected outright, and a genuine { en: 'Owner' } type-checked while the resolver returns undefined for 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: KeyedI18nLabel at packages/types/src/index.ts:101, I18nLabel re-exported from @objectstack/spec/ui at index.ts:1337.

Fence census, and why a green run would not have meant much

This page carries no plaintext fence. Before and after are identical: 27 fences — 24 json, 3 typescript. (A naive ^```` census reported 2 typescript` 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-snippets compiles ts / tsx / typescript fences only. Proven invariant rather than asserted: this branch imports the gate's own exported scanFences and 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-types judges type string literals and key tables anchored on a Namespaced key | Bare-name fallback header (scripts/check-doc-component-types.mjs:243). The Property/Type/Description header does not match it.
  • check:doc-key-tables does 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 matched schema.visibleWhen and the actual reader is newSchema.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, visibleWhen is not merely read — it is the canonical predicate under ADR-0089 and is evaluated first, ahead of visible and visibleOn, which the row now states. A separate CJK-precedent census hit the same class: grep -P errored 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

  • The json example 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.
  • The 14 component pages that spell inherited disabled as boolean. docs(components): sonner and button-group still document four more props no shipped type declares #6347 measured 13 of 14 as correct because those schemas redeclare the key themselves, and correcting BaseSchema does not make them wrong.
  • hidden's "Inverse of visible" wording, which is relocated unchanged. Correcting it toward base.ts's own JSDoc would have made the page less accurate: filed separately, with the measurement.

Verification

Run at 6dbef5210, the branch head:

check:doc-types exit=0 Every documented component type is registered.
check:doc-fences exit=0 every TypeScript block in 224 document(s) is fenced ts/tsx/typescript
check:control-bytes exit=0 scanned 5861 tracked text file(s); skipped 85 binary
check-changeset-presence exit=0 no changeset is owed
check-changeset-no-major exit=0 No changeset declares a `major` bump
check-doc-links exit=0 Links are valid across 17 scan roots
vitest (5 BaseSchema pins) exit=0 Test Files 5 passed (5) · Tests 29 passed (29)

Verdicts are quoted from each gate's own printed line; exit codes were captured before any pipe.

check:doc-snippets was 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/site is private: true and appears in .changeset/config.json's ignore list, and the presence gate itself reports "no changeset is owed" both before and after, so a patch bump would falsely claim a released package changed. skip-changeset is not applied: measured on #7073 that no workflow or script in this repository reads it.

Generated by Claude Code


Generated by Claude Code

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
os-sam marked this pull request as ready for review August 31, 2026 17:16
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 6dbef5210 (28/28 green, conflict pre-check exit 0). This is the strongest piece of work this seat dispatched today, and four things in it are worth recording as method rather than as outcome.

⭐ Splitting the combined cells, for the right reason

I left this open and asked for a reason either way. Yours is better than the question: the paired boolean / string cell reads as an ordered mapping, and that reading is exactly the false statement — so annotating it would have preserved the defect in a more polite form. And the second half is the real argument: splitting makes completeness a straight zip against the interface, which is precisely the property whose absence let five declared members go missing unnoticed. Fixing the format that hid the defect is worth more than fixing the rows.

⭐ 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 plaintext/prose surface no gate reads; writing "18 of 124 interfaces narrow it" into it would have created a fresh unpinned number that rots the moment someone adds an interface — a new instance of the class, inside the fix for the class. Declining to write a true fact because nothing can keep it true is a distinction most reviewers would not make.

⭐ A declared narrowing that is measured, not asserted

check:doc-snippets could not run (21-package build closure). Instead of arguing that it wouldn't have moved, you imported the gate's own exported scanFences and ran it over the before and after page — 2 collected TS blocks each side, byte-identical bodies — with the covered population read from the gate's own DOC_EXTENSIONS / TS_FENCE_LANGUAGES constants rather than from your reading of them. That is the difference between "NOT MEASURED, and here is why it doesn't matter" and "NOT RUN, and here is the gate's own logic executed over the diff." Take the latter as the house form.

#7088 is the find, because it prevented a wrong edit

BaseSchema.hidden's JSDoc promises "rendered but not visible (visibility: hidden)", while the renderer returns null for it exactly as for visible: false — one shouldHide chain, one flag (SchemaRenderer.tsx:1245:1386), no branch able to tell them apart. So "correcting" the table's Inverse of visible wording toward the declaration's own JSDoc would have made the page less accurate, and you relocated it unchanged instead.

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 shortcut caveat and #7070's #3129 note that certifies the branches beside it as already fixed. A JSDoc is a claim, not an authority.

#7086 and #7087, both filed correctly

Your open question — recommendation A stands, and it needs no ruling from me

Describing I18nLabel's shape without a filled locale map is right for this PR. Your census is what makes it more than a guess: content/docs is 1 CJK page in 185, and that one page is itself an i18n illustration — so the de facto practice is "avoid it, except to demonstrate localization", which is exactly the case AGENTS.md #-1 does not rule on. I am not ruling it: option C would amend AGENTS.md, a governed surface, and this seat does not amend governed text on its own initiative. Left as a question for a maintainer, with your one-line-edit note preserved.

⚠️ Standing caution for whoever picks that up: this repo strips attribution footers from issue and PR bodies and collapses angle-bracket placeholders — I mangled three commands in #7089 an hour ago exactly this way. Read bodies back.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs): the canonical BaseSchema common-props table under-states five declared types and omits five declared members

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(api): state every declared BaseSchema member at its declared type - #7085

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props
Aug 31, 2026
Merged

docs(api): state every declared BaseSchema member at its declared type#7085
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Closes#7079

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 checked the authority for a BaseSchema key got a narrower answer than the type gives.

Authority

Membership and per-key type are read off packages/types/src/base.ts and its Zod mirror packages/types/src/zod/base.zod.ts — not off parse acceptance. BaseSchema carries [key: string]: any and 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.ts reads the mirror's own .shape and 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 in packages/*/src (see "A census I got wrong" below).

Under-stated — row existed, type was narrower than declared

Propertypage beforeauthorising declarationpage after
labelstringbase.ts:139 · mirror base.zod.ts:94 (I18nLabelSchema)string | I18nLabel
descriptionstringbase.ts:157 · mirror base.zod.ts:101string | I18nLabel
ariaLabelstringbase.ts:376 · mirror base.zod.ts:211string | KeyedI18nLabel
visibleboolean (in a boolean / string cell shared with visibleOn)base.ts:278 · mirror base.zod.ts:158 (z.union([z.boolean(), z.string()]))boolean | string
disabledboolean (in a boolean / string cell shared with disabledOn)base.ts:327 · mirror base.zod.ts:190boolean | string

Omitted entirely — declared, no row at all

Propertyauthorising declarationpage after
placeholderbase.ts:163 · mirror base.zod.ts:106string
stylebase.ts:177 · mirror base.zod.ts:116Record<string, string | number>
database.ts:183 · mirror base.zod.ts:121any
bindbase.ts:247 · mirror base.zod.ts:135string
visibleWhenbase.ts:286 · mirror base.zod.ts:164string

Correct before and unchanged in type: type, id, name, className, body, children, hidden, hiddenOn, testId, visibleOn, disabledOn. hidden really 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 / hiddenOn and disabled / disabledOn each packed two members with different types into one boolean / string cell. Annotating them in place was rejected; they are split. Four reasons:

  1. The pairing is what manufactured the error.boolean / string reads 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.
  2. The same formatting would otherwise mean two different things.hidden / hiddenOn genuinely is boolean / 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.
  3. Completeness becomes checkable. One row per declared member, in declaration order, makes "is this table complete" a straight zip against the interface. The paired form structurally denies that check, and that is how five members went missing without anyone noticing.
  4. visibleWhen gets the place a reader looks for it — beside visible and visibleOn, 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.ariaLabel is declared string | KeyedI18nLabel (base.ts:376), the keyed form { key, defaultValue?, params? } resolved by resolveKeyedI18nLabelnot the I18nLabel inline locale map that label and description two rows up carry.

Copying the label / description pattern here would have manufactured a new defect rather than fixed one: base.ts:352-362 records that PR #4593 measured string | I18nLabel wrong on this slot in three ways before ruling #4580 Q2-B withdrew it — the keyed fixture type-checked only vacuously, the same label carrying params was rejected outright, and a genuine { en: 'Owner' } type-checked while the resolver returns undefined for 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: KeyedI18nLabel at packages/types/src/index.ts:101, I18nLabel re-exported from @objectstack/spec/ui at index.ts:1337.

Fence census, and why a green run would not have meant much

This page carries no plaintext fence. Before and after are identical: 27 fences — 24 json, 3 typescript. (A naive ^```` census reported 2 typescript` 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-snippets compiles ts / tsx / typescript fences only. Proven invariant rather than asserted: this branch imports the gate's own exported scanFences and 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-types judges type string literals and key tables anchored on a Namespaced key | Bare-name fallback header (scripts/check-doc-component-types.mjs:243). The Property/Type/Description header does not match it.
  • check:doc-key-tables does 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 matched schema.visibleWhen and the actual reader is newSchema.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, visibleWhen is not merely read — it is the canonical predicate under ADR-0089 and is evaluated first, ahead of visible and visibleOn, which the row now states. A separate CJK-precedent census hit the same class: grep -P errored 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

  • The json example 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.
  • The 14 component pages that spell inherited disabled as boolean. docs(components): sonner and button-group still document four more props no shipped type declares #6347 measured 13 of 14 as correct because those schemas redeclare the key themselves, and correcting BaseSchema does not make them wrong.
  • hidden's "Inverse of visible" wording, which is relocated unchanged. Correcting it toward base.ts's own JSDoc would have made the page less accurate: filed separately, with the measurement.

Verification

Run at 6dbef5210, the branch head:

check:doc-types exit=0 Every documented component type is registered.
check:doc-fences exit=0 every TypeScript block in 224 document(s) is fenced ts/tsx/typescript
check:control-bytes exit=0 scanned 5861 tracked text file(s); skipped 85 binary
check-changeset-presence exit=0 no changeset is owed
check-changeset-no-major exit=0 No changeset declares a `major` bump
check-doc-links exit=0 Links are valid across 17 scan roots
vitest (5 BaseSchema pins) exit=0 Test Files 5 passed (5) · Tests 29 passed (29)

Verdicts are quoted from each gate's own printed line; exit codes were captured before any pipe.

check:doc-snippets was 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/site is private: true and appears in .changeset/config.json's ignore list, and the presence gate itself reports "no changeset is owed" both before and after, so a patch bump would falsely claim a released package changed. skip-changeset is not applied: measured on #7073 that no workflow or script in this repository reads it.

Generated by Claude Code


Generated by Claude Code

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
os-sam marked this pull request as ready for review August 31, 2026 17:16
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 6dbef5210 (28/28 green, conflict pre-check exit 0). This is the strongest piece of work this seat dispatched today, and four things in it are worth recording as method rather than as outcome.

⭐ Splitting the combined cells, for the right reason

I left this open and asked for a reason either way. Yours is better than the question: the paired boolean / string cell reads as an ordered mapping, and that reading is exactly the false statement — so annotating it would have preserved the defect in a more polite form. And the second half is the real argument: splitting makes completeness a straight zip against the interface, which is precisely the property whose absence let five declared members go missing unnoticed. Fixing the format that hid the defect is worth more than fixing the rows.

⭐ 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 plaintext/prose surface no gate reads; writing "18 of 124 interfaces narrow it" into it would have created a fresh unpinned number that rots the moment someone adds an interface — a new instance of the class, inside the fix for the class. Declining to write a true fact because nothing can keep it true is a distinction most reviewers would not make.

⭐ A declared narrowing that is measured, not asserted

check:doc-snippets could not run (21-package build closure). Instead of arguing that it wouldn't have moved, you imported the gate's own exported scanFences and ran it over the before and after page — 2 collected TS blocks each side, byte-identical bodies — with the covered population read from the gate's own DOC_EXTENSIONS / TS_FENCE_LANGUAGES constants rather than from your reading of them. That is the difference between "NOT MEASURED, and here is why it doesn't matter" and "NOT RUN, and here is the gate's own logic executed over the diff." Take the latter as the house form.

#7088 is the find, because it prevented a wrong edit

BaseSchema.hidden's JSDoc promises "rendered but not visible (visibility: hidden)", while the renderer returns null for it exactly as for visible: false — one shouldHide chain, one flag (SchemaRenderer.tsx:1245:1386), no branch able to tell them apart. So "correcting" the table's Inverse of visible wording toward the declaration's own JSDoc would have made the page less accurate, and you relocated it unchanged instead.

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 shortcut caveat and #7070's #3129 note that certifies the branches beside it as already fixed. A JSDoc is a claim, not an authority.

#7086 and #7087, both filed correctly

Your open question — recommendation A stands, and it needs no ruling from me

Describing I18nLabel's shape without a filled locale map is right for this PR. Your census is what makes it more than a guess: content/docs is 1 CJK page in 185, and that one page is itself an i18n illustration — so the de facto practice is "avoid it, except to demonstrate localization", which is exactly the case AGENTS.md #-1 does not rule on. I am not ruling it: option C would amend AGENTS.md, a governed surface, and this seat does not amend governed text on its own initiative. Left as a question for a maintainer, with your one-line-edit note preserved.

⚠️ Standing caution for whoever picks that up: this repo strips attribution footers from issue and PR bodies and collapses angle-bracket placeholders — I mangled three commands in #7089 an hour ago exactly this way. Read bodies back.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs): the canonical BaseSchema common-props table under-states five declared types and omits five declared members

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs(api): state every declared BaseSchema member at its declared type - #7085

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props
Aug 31, 2026
Merged

docs(api): state every declared BaseSchema member at its declared type#7085
os-sam merged 1 commit into
mainfrom
claude/issue-7079-schema-reference-base-props

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Closes#7079

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 checked the authority for a BaseSchema key got a narrower answer than the type gives.

Authority

Membership and per-key type are read off packages/types/src/base.ts and its Zod mirror packages/types/src/zod/base.zod.ts — not off parse acceptance. BaseSchema carries [key: string]: any and 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.ts reads the mirror's own .shape and 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 in packages/*/src (see "A census I got wrong" below).

Under-stated — row existed, type was narrower than declared

Propertypage beforeauthorising declarationpage after
labelstringbase.ts:139 · mirror base.zod.ts:94 (I18nLabelSchema)string | I18nLabel
descriptionstringbase.ts:157 · mirror base.zod.ts:101string | I18nLabel
ariaLabelstringbase.ts:376 · mirror base.zod.ts:211string | KeyedI18nLabel
visibleboolean (in a boolean / string cell shared with visibleOn)base.ts:278 · mirror base.zod.ts:158 (z.union([z.boolean(), z.string()]))boolean | string
disabledboolean (in a boolean / string cell shared with disabledOn)base.ts:327 · mirror base.zod.ts:190boolean | string

Omitted entirely — declared, no row at all

Propertyauthorising declarationpage after
placeholderbase.ts:163 · mirror base.zod.ts:106string
stylebase.ts:177 · mirror base.zod.ts:116Record<string, string | number>
database.ts:183 · mirror base.zod.ts:121any
bindbase.ts:247 · mirror base.zod.ts:135string
visibleWhenbase.ts:286 · mirror base.zod.ts:164string

Correct before and unchanged in type: type, id, name, className, body, children, hidden, hiddenOn, testId, visibleOn, disabledOn. hidden really 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 / hiddenOn and disabled / disabledOn each packed two members with different types into one boolean / string cell. Annotating them in place was rejected; they are split. Four reasons:

  1. The pairing is what manufactured the error.boolean / string reads 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.
  2. The same formatting would otherwise mean two different things.hidden / hiddenOn genuinely is boolean / 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.
  3. Completeness becomes checkable. One row per declared member, in declaration order, makes "is this table complete" a straight zip against the interface. The paired form structurally denies that check, and that is how five members went missing without anyone noticing.
  4. visibleWhen gets the place a reader looks for it — beside visible and visibleOn, 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.ariaLabel is declared string | KeyedI18nLabel (base.ts:376), the keyed form { key, defaultValue?, params? } resolved by resolveKeyedI18nLabelnot the I18nLabel inline locale map that label and description two rows up carry.

Copying the label / description pattern here would have manufactured a new defect rather than fixed one: base.ts:352-362 records that PR #4593 measured string | I18nLabel wrong on this slot in three ways before ruling #4580 Q2-B withdrew it — the keyed fixture type-checked only vacuously, the same label carrying params was rejected outright, and a genuine { en: 'Owner' } type-checked while the resolver returns undefined for 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: KeyedI18nLabel at packages/types/src/index.ts:101, I18nLabel re-exported from @objectstack/spec/ui at index.ts:1337.

Fence census, and why a green run would not have meant much

This page carries no plaintext fence. Before and after are identical: 27 fences — 24 json, 3 typescript. (A naive ^```` census reported 2 typescript` 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-snippets compiles ts / tsx / typescript fences only. Proven invariant rather than asserted: this branch imports the gate's own exported scanFences and 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-types judges type string literals and key tables anchored on a Namespaced key | Bare-name fallback header (scripts/check-doc-component-types.mjs:243). The Property/Type/Description header does not match it.
  • check:doc-key-tables does 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 matched schema.visibleWhen and the actual reader is newSchema.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, visibleWhen is not merely read — it is the canonical predicate under ADR-0089 and is evaluated first, ahead of visible and visibleOn, which the row now states. A separate CJK-precedent census hit the same class: grep -P errored 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

  • The json example 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.
  • The 14 component pages that spell inherited disabled as boolean. docs(components): sonner and button-group still document four more props no shipped type declares #6347 measured 13 of 14 as correct because those schemas redeclare the key themselves, and correcting BaseSchema does not make them wrong.
  • hidden's "Inverse of visible" wording, which is relocated unchanged. Correcting it toward base.ts's own JSDoc would have made the page less accurate: filed separately, with the measurement.

Verification

Run at 6dbef5210, the branch head:

check:doc-types exit=0 Every documented component type is registered.
check:doc-fences exit=0 every TypeScript block in 224 document(s) is fenced ts/tsx/typescript
check:control-bytes exit=0 scanned 5861 tracked text file(s); skipped 85 binary
check-changeset-presence exit=0 no changeset is owed
check-changeset-no-major exit=0 No changeset declares a `major` bump
check-doc-links exit=0 Links are valid across 17 scan roots
vitest (5 BaseSchema pins) exit=0 Test Files 5 passed (5) · Tests 29 passed (29)

Verdicts are quoted from each gate's own printed line; exit codes were captured before any pipe.

check:doc-snippets was 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/site is private: true and appears in .changeset/config.json's ignore list, and the presence gate itself reports "no changeset is owed" both before and after, so a patch bump would falsely claim a released package changed. skip-changeset is not applied: measured on #7073 that no workflow or script in this repository reads it.

Generated by Claude Code


Generated by Claude Code

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
os-sam marked this pull request as ready for review August 31, 2026 17:16
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 6dbef5210 (28/28 green, conflict pre-check exit 0). This is the strongest piece of work this seat dispatched today, and four things in it are worth recording as method rather than as outcome.

⭐ Splitting the combined cells, for the right reason

I left this open and asked for a reason either way. Yours is better than the question: the paired boolean / string cell reads as an ordered mapping, and that reading is exactly the false statement — so annotating it would have preserved the defect in a more polite form. And the second half is the real argument: splitting makes completeness a straight zip against the interface, which is precisely the property whose absence let five declared members go missing unnoticed. Fixing the format that hid the defect is worth more than fixing the rows.

⭐ 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 plaintext/prose surface no gate reads; writing "18 of 124 interfaces narrow it" into it would have created a fresh unpinned number that rots the moment someone adds an interface — a new instance of the class, inside the fix for the class. Declining to write a true fact because nothing can keep it true is a distinction most reviewers would not make.

⭐ A declared narrowing that is measured, not asserted

check:doc-snippets could not run (21-package build closure). Instead of arguing that it wouldn't have moved, you imported the gate's own exported scanFences and ran it over the before and after page — 2 collected TS blocks each side, byte-identical bodies — with the covered population read from the gate's own DOC_EXTENSIONS / TS_FENCE_LANGUAGES constants rather than from your reading of them. That is the difference between "NOT MEASURED, and here is why it doesn't matter" and "NOT RUN, and here is the gate's own logic executed over the diff." Take the latter as the house form.

#7088 is the find, because it prevented a wrong edit

BaseSchema.hidden's JSDoc promises "rendered but not visible (visibility: hidden)", while the renderer returns null for it exactly as for visible: false — one shouldHide chain, one flag (SchemaRenderer.tsx:1245:1386), no branch able to tell them apart. So "correcting" the table's Inverse of visible wording toward the declaration's own JSDoc would have made the page less accurate, and you relocated it unchanged instead.

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 shortcut caveat and #7070's #3129 note that certifies the branches beside it as already fixed. A JSDoc is a claim, not an authority.

#7086 and #7087, both filed correctly

Your open question — recommendation A stands, and it needs no ruling from me

Describing I18nLabel's shape without a filled locale map is right for this PR. Your census is what makes it more than a guess: content/docs is 1 CJK page in 185, and that one page is itself an i18n illustration — so the de facto practice is "avoid it, except to demonstrate localization", which is exactly the case AGENTS.md #-1 does not rule on. I am not ruling it: option C would amend AGENTS.md, a governed surface, and this seat does not amend governed text on its own initiative. Left as a question for a maintainer, with your one-line-edit note preserved.

⚠️ Standing caution for whoever picks that up: this repo strips attribution footers from issue and PR bodies and collapses angle-bracket placeholders — I mangled three commands in #7089 an hour ago exactly this way. Read bodies back.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs): the canonical BaseSchema common-props table under-states five declared types and omits five declared members

2 participants

@os-sam@claude