Skip to content

finding(types): PartialSchema< T > declares ONE property for every instantiation — the #6269 collapse in a mapped-type alias, and it has zero consumers #6397

Description

@claude

⚠️ Generic parameters are written spaced (Omit< T, K >) throughout — GitHub's body
sanitizer eats a fragment that opens with an identifier in angle brackets, which is what
mangled #6151's and #6269's first revisions.

Measured while fixing #6269, in the same pass that card explicitly asked for ("⚠️
index.ts:900 has a generic & Partial< Omit< T, 'type' > > whose behaviour depends on what
T is instantiated with — not measured, and worth checking as part of any fix here").
Filed unassigned and deliberately not fixed in #6269's PR: the dispatch order for that
card ruled "measure it and report the number — do not widen this PR to fix it without
reporting first."

The fact

packages/types/src/index.ts:905:

exporttypePartialSchema<TextendsBaseSchema>={type: T['type'];}&Partial<Omit<T,'type'>>;

Every instantiation declares exactly one property. Measured through the TypeScript
checker against the emitted index.d.ts (the same instrument as #6269's 61 → 0 reading):

PartialSchema< ObjectGridSchema > -> 1 declared property: type (source: 61)
PartialSchema< ObjectFormSchema > -> 1 declared property: type (source: 67)
PartialSchema< ObjectViewSchema > -> 1 declared property: type (source: 27+)
PartialSchema< ButtonSchema > -> 1 declared property: type (source: 27)

All four also carry a live [key: string]: any, so the type accepts anything.

Why — the #6269 / #6151 mechanism, third position

Omit< T, K > is Pick< T, Exclude< keyof T, K > >, and keyof T on a type carrying a
string index signature is string | number — the literal member names are absorbed. Every
T extends BaseSchema inherits BaseSchema's [key: string]: any (#5155), so the
Partial< Omit< T, 'type' > > half of the intersection rebuilds a type holding the index
signature and none of the named members. The explicit { type: T['type'] } half is the only
reason the count is 1 rather than 0.

This is the same collapse #6151 fixed in a heritage clause and #6269 fixed in property
position
. Here it is in a generic mapped-type alias, which is why neither card's guard
sees it: #6151's walks the LayoutSchema union, #6269's reads ObjectViewSchema's two slot
properties.

What it costs today: nothing measurable — the type has no consumers

Grepped across packages/, apps/, examples/, content/ and docs/ (excluding
node_modules): two occurrences of the identifier, and both are the declaration itself —
packages/types/src/index.ts:905 (source) and packages/types/dist/index.d.ts:130 (its own
emitted copy). No call site, no re-export under another name, no documentation page, no test.
Its doc comment says "Useful for partial schema definitions in editors"; nothing in this
repo's editors uses it.

So the harm is not a live miscompile — it is a published, exported type that promises
{ type } & everything-else-optional and delivers { type } & any
, available for the next
consumer to adopt and be silently unprotected by.

Options, none ruled here

  1. Retire it. Zero in-repo consumers, and it is a convenience alias rather than a
    capability — the startup-scope reading is that a declared-and-unconsumed surface does not
    earn a repair. Cost: it is exported from @object-ui/types, so an out-of-tree consumer
    could exist; needs the usual removal route (a minor under this repo's no-major
    convention, or a deprecation window).
  2. Repair it the finding(types): ObjectViewSchema's table and form slots declare ZERO properties — the same Omit-under-index-signature collapse as #6151, in property position #6269 way. Not directly portable: finding(types): ObjectViewSchema's table and form slots declare ZERO properties — the same Omit-under-index-signature collapse as #6151, in property position #6269 could spell out an explicit key
    list because ObjectGridSchema and ObjectFormSchema are concrete. T here is generic,
    so there is no literal key list to write. A repair would need something like
    { type: T['type'] } & { [K in keyof T as K extends 'type' ? never : K]?: T[K] }, which
    collapses for the same keyof T reason, or a BaseSchema-free member source — i.e. it
    effectively waits on option 3.
  3. Sequence behind finding(types): BaseSchema's [key: string]: any leaves every component schema open, so a "declare the surface" fix can never reject a misspelled TOP-LEVEL key #5155 (the phased zod .strict() ruling), which removes the index
    signature at the root and closes this class everywhere at once — at which point this alias
    starts working as written with no edit at all.

My reading is that 1 and 3 are the live pair, and they are compatible: retiring an
unconsumed alias is cheap now, and if it is kept instead, #5155 fixes it for free later. What
is not defensible is leaving it declared, published, collapsed, and unpinned.

Refs: #6269 (same mechanism, property position — this was measured in its PR) · #6151 (same
mechanism, heritage clause) · #5155 (the root index signature) · #6143 (where the class first
surfaced).


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions