You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
finding(types): StackSchema, declared as Omit-of-FlexSchema-minus-type, erases every named member — it ships declaring only type, because Omit collapses under BaseSchema's index signature #6151
⚠️Rewritten 2026-08-24 because GitHub's body sanitizer silently ate every generic argument in the first version — including inside a fenced code block — which inverted the finding's meaning (extends Omit-of-FlexSchema-minus-type was rendered as extends Omit). Angle-bracket generics are therefore written in words throughout. The sanitizer strips a fragment that opens with an identifier in angle brackets, because it parses as an HTML tag; the escaped and elided spellings survive. Same hazard as AGENTS.md's "GitHub mutates body BYTES" clause.
Measured while sweeping #6143. Filed unassigned, not fixed there.
The fact
packages/types/dist/layout.d.ts:215 declares StackSchema as extending Omit of FlexSchema minus the key type, then redeclaring type: 'stack'. In words, because the literal spelling does not survive here:
FlexSchema declares 25 members — gap, children, align, justify, direction, wrap, plus everything inherited from BaseSchema. StackSchema is intended to be all of them minus type.
It is not. Measured against the built dist through the gate's own derivePackageTypePaths():
FlexSchema -> 25 declared properties: align ariaLabel body children className data
description direction disabled disabledOn gap hidden hiddenOn id justify
label name placeholder style testId type visible visibleOn visibleWhen wrap
StackSchema -> 1 declared property: type
checker.getPropertyOfType(StackSchema, 'gap') returns false. Same for children, align, justify.
Why
FlexSchema inherits BaseSchema's [key: string]: any (#5155).
Omit of T minus K is defined as Pick of T over Exclude of keyof T minus K. And keyof T, on a type carrying a string index signature, is string | number — the literal member names are absorbed. So excluding 'type' from string | number still leaves string | number, and the Pick reconstructs a type that has the index signature and none of the named members.
This is the same absorption #5155's clarification comment measures, in its REJECT half — but here it does not merely fail to reject an unknown key, it deletes known ones from the shipped declaration.
What it costs
Nothing at runtime, and nothing that produces an error — which is why it has survived. What it costs is every tool that reads the declaration:
Editor completion on StackSchema offers type and nothing else. An author writing a stack gets no gap, no align, no justify.
StackSchema is a member of the LayoutSchema union (layout.d.ts:687), so the erasure propagates into anything narrowing that union.
Scope
StackSchema is the one measured. ⚠️Any other interface whose heritage clause is an Omit, a Pick, or any mapped type over a schema type carries the identical erasure, and enumerating them is the first step of a fix rather than something this card asserts. Grepping for extends Omit and extends Pick across packages/*/src/**/*.ts would bound it.
Candidate directions, none ruled here
Restate the members on StackSchema instead of deriving them — honest and immediate, but a duplicate to keep in sync.
Drop the Omit and let StackSchema extend FlexSchema directly, narrowing type. An interface may narrow an inherited property only to a compatible subtype, and 'stack' is not a subtype of FlexSchema's type unless that is widened — so this needs checking rather than assuming.
The third is why this is filed rather than fixed: it may want sequencing behind #5155 rather than a local patch.
Refs: #5155 (the index signature, and the keyof absorption its clarification comment measures) · #6143 (where this surfaced) · #6150 (sibling finding from the same sweep, different mechanism).
extends Omit-of-FlexSchema-minus-typewas rendered asextends Omit). Angle-bracket generics are therefore written in words throughout. The sanitizer strips a fragment that opens with an identifier in angle brackets, because it parses as an HTML tag; the escaped and elided spellings survive. Same hazard as AGENTS.md's "GitHub mutates body BYTES" clause.Measured while sweeping #6143. Filed unassigned, not fixed there.
The fact
packages/types/dist/layout.d.ts:215declaresStackSchemaas extending Omit ofFlexSchemaminus the keytype, then redeclaringtype: 'stack'. In words, because the literal spelling does not survive here:FlexSchemadeclares 25 members —gap,children,align,justify,direction,wrap, plus everything inherited fromBaseSchema.StackSchemais intended to be all of them minustype.It is not. Measured against the built
distthrough the gate's ownderivePackageTypePaths():checker.getPropertyOfType(StackSchema, 'gap')returns false. Same forchildren,align,justify.Why
FlexSchemainheritsBaseSchema's[key: string]: any(#5155).Omitof T minus K is defined asPickof T overExcludeofkeyof Tminus K. Andkeyof T, on a type carrying a string index signature, isstring | number— the literal member names are absorbed. So excluding'type'fromstring | numberstill leavesstring | number, and thePickreconstructs a type that has the index signature and none of the named members.This is the same absorption #5155's clarification comment measures, in its REJECT half — but here it does not merely fail to reject an unknown key, it deletes known ones from the shipped declaration.
What it costs
Nothing at runtime, and nothing that produces an error — which is why it has survived. What it costs is every tool that reads the declaration:
StackSchemaofferstypeand nothing else. An author writing a stack gets nogap, noalign, nojustify.stack.mdxas documenting keys that do not exist.finding(docs): component reference pages declare their owninterface, so the doc-snippet gate can never see the props they invent — 4 measured on the 5 pages #6122 PR 2 touched alone #6143's sweep flaggedgap,childrenandclassNameon that page as divergences; they are not — the docs are right and the type is wrong. Correcting the page to match its type, which is what that card would otherwise have done, would have deleted three correct entries. This card exists so the next sweep does not.StackSchemais a member of theLayoutSchemaunion (layout.d.ts:687), so the erasure propagates into anything narrowing that union.Scope
StackSchemais the one measured.extends Omitandextends Pickacrosspackages/*/src/**/*.tswould bound it.Candidate directions, none ruled here
StackSchemainstead of deriving them — honest and immediate, but a duplicate to keep in sync.StackSchemaextendFlexSchemadirectly, narrowingtype. An interface may narrow an inherited property only to a compatible subtype, and'stack'is not a subtype ofFlexSchema'stypeunless that is widened — so this needs checking rather than assuming.BaseSchema— which is finding(types): BaseSchema's[key: string]: anyleaves every component schema open, so a "declare the surface" fix can never reject a misspelled TOP-LEVEL key #5155, already ruled (option C, phased zod.strict()). That ruling targets the rejection layer, not this erasure, so it is worth confirming whether its program incidentally closes this or leaves it standing.The third is why this is filed rather than fixed: it may want sequencing behind #5155 rather than a local patch.
Refs: #5155 (the index signature, and the
keyofabsorption its clarification comment measures) · #6143 (where this surfaced) · #6150 (sibling finding from the same sweep, different mechanism).Generated by Claude Code