Observation-class finding, measured while implementing objectui#9033. Filed unassigned, no labels — grading is the triage seat's. Generated by Claude Code, session session_01UzHd6hDYatoDn17BuwKxnZ, working the objectui#9033 dev seat.
The claim
objectui#9033 said of header-bar's rightContent: "It is the only remaining instance", on the strength of git grep -n '&& .*toRenderableSchema' returning exactly one line. That grep is scoped to the toRenderableSchema spelling of the right operand. The trap does not depend on that spelling — it depends only on the LEFT operand being a value React renders. Every SchemaNode-typed slot guarded by a bare && has it, whatever sits on the right.
Re-run with the spelling constraint dropped, eleven more sites leak, all in @object-ui/components, all published renderers. objectui#9033's own site is fixed; these are not.
Measured, on origin/main at 3b6bc6959
A temporary probe rendered each slot through the real renderer stack and read document.body.textContent (Radix overlays portal out of the render container, so reading the container alone is a blind instrument — the first pass of this probe read three overlay rows as "clean" for exactly that reason). Each row carries its own lit control: the same slot given 42, which must reach the text or the row reports NOT MEASURED instead of clean. The probe was deleted afterwards; git status --porcelain empty.
renderer slot verdict
container children LEAK baseline=[] with-0=[0]
flex children LEAK baseline=[] with-0=[0]
grid children LEAK baseline=[] with-0=[0]
stack children LEAK baseline=[] with-0=[0]
card header LEAK baseline=[] with-0=[0]
card body LEAK baseline=[] with-0=[0]
card children clean (control fired: with-42=[42])
card footer LEAK baseline=[] with-0=[0]
dialog footer LEAK baseline=[Close] with-0=[0Close]
sheet footer LEAK baseline=[Close] with-0=[0Close]
drawer footer LEAK baseline=[] with-0=[0]
table footer LEAK baseline=[H] with-0=[H0]
card children is the one clean row, and the reason is worth stating because it is not a guard: that site reads (schema.children || schema.body), and 0 || undefined evaluates to undefined. The || chain converts the falsy number away by accident. Author body: 0 on the same renderer with children absent and the same chain evaluates to 0 — which is the card body row directly above it. So the clean row is a coincidence of operand order, not protection.
Why these are authorable, not hypothetical
All of these slots are declared SchemaNode or SchemaNode | SchemaNode[] in @object-ui/types, and the zod mirror's node union carries a z.number() arm (nodeUnionOptions in packages/types/src/zod/base.zod.ts lists BaseSchemaCore, z.string(), z.number(), z.boolean(), z.null(), z.undefined()). The published validator therefore accepts children: 0; nothing between an author and this leak refuses it.
The census, with its population and its control
Instrument: the TypeScript checker, not a grep — for each JSX guard {X and-and …} in the tree, ask getTypeAtLocation(X) whether the type admits number.
- Population: 816 non-test
.tsx files under packages/ and apps/ (enumerated with git ls-files); 2396 JSX and-and guards in them; 2396 left operands across those guards.
- Reported: 66 operands whose type admits
number.
- Of those, typed
SchemaNode / SchemaNode | SchemaNode[]: 14 sites — objectui#9033's header-bar rightContent, plus complex/table.tsx footer, layout/card.tsx (four operands), layout/container.tsx / flex.tsx / grid.tsx / stack.tsx children, overlay/dialog.tsx and overlay/sheet.tsx footer, and plugin-detail's DetailView.tsx header and footer.
- Control (must fire):
header-bar.tsx — the one site already known to leak. It fired. A zero from an instrument with no lit control is not a reading.
- Indeterminate, reported rather than counted clean: 53 operands typed
any / unknown, which carry no numeric type flag and so are invisible to a flag-based census. overlay/drawer.tsx footer and layout/containers.tsx's page:card body / footer are in this bucket — their schema is any — and the runtime probe above shows drawer footer leaking, so the bucket is not empty of real defects.
Not the same defect as objectui#8908, for the same reason objectui#9033 was not
toRenderableSchema has mapped falsy primitives onto nothing since objectui#8908. It is not reached: and-and short-circuits, so the chain produces the raw 0 before any bridge is called. renderChildren's own if (!children) return null first leg is bypassed identically — several of the sites above call it on the right-hand side and leak anyway.
The shape of the fix is already ruled
objectui#8331 settled it on DataTableSchema.emptyAction and objectui#9033 ported it to header-bar: replace the and-and chain with a ternary yielding null, keeping the truthiness leg. objectui#7105 governs the declaration side — node slots relax the renderer, they do not narrow the declaration.
Two things this card should decide rather than assume, because they are what make it bigger than a repeat of objectui#9033:
- Is a per-site ternary the right remedy at this count, or does it want one guard? Eleven sites repaired one at a time is the same edit eleven times, and a twelfth slot added next month gets it wrong again. A shared
renderNodeSlot(value) helper — or teaching renderChildren to be the guard and dropping the and-and entirely, since its first leg already answers null for every falsy input — closes the class instead of the instances.
plugin-detail's two sites are in a different package and were not probed here. They are census hits, not measurements.
Why it was filed rather than folded into objectui#9033's pull request
That card's dispatch named this exact outcome as a stop condition: report the list, fix the one line the card is about, do not silently widen. Eleven more renderers and a class-level remedy question is not a reviewable diff on a one-line card.
Refs
- objectui#9033 —
header-bar's rightContent, the one site now fixed; this census came out of implementing it.
- objectui#8331 — the identical trap on
DataTableSchema.emptyAction, and the ruled shape.
- objectui#8908 — the bridge repair. Adjacent symptom, different mechanism, reaches none of these.
- objectui#7105 — node slots relax the renderer rather than narrow the declaration.
Observation-class finding, measured while implementing objectui#9033. Filed unassigned, no labels — grading is the triage seat's. Generated by Claude Code, session
session_01UzHd6hDYatoDn17BuwKxnZ, working the objectui#9033 dev seat.The claim
objectui#9033 said of
header-bar'srightContent: "It is the only remaining instance", on the strength ofgit grep -n '&& .*toRenderableSchema'returning exactly one line. That grep is scoped to thetoRenderableSchemaspelling of the right operand. The trap does not depend on that spelling — it depends only on the LEFT operand being a value React renders. EverySchemaNode-typed slot guarded by a bare&&has it, whatever sits on the right.Re-run with the spelling constraint dropped, eleven more sites leak, all in
@object-ui/components, all published renderers. objectui#9033's own site is fixed; these are not.Measured, on
origin/mainat3b6bc6959A temporary probe rendered each slot through the real renderer stack and read
document.body.textContent(Radix overlays portal out of the render container, so reading the container alone is a blind instrument — the first pass of this probe read three overlay rows as "clean" for exactly that reason). Each row carries its own lit control: the same slot given42, which must reach the text or the row reports NOT MEASURED instead of clean. The probe was deleted afterwards;git status --porcelainempty.cardchildrenis the one clean row, and the reason is worth stating because it is not a guard: that site reads(schema.children || schema.body), and0 || undefinedevaluates toundefined. The||chain converts the falsy number away by accident. Authorbody: 0on the same renderer withchildrenabsent and the same chain evaluates to0— which is thecard bodyrow directly above it. So the clean row is a coincidence of operand order, not protection.Why these are authorable, not hypothetical
All of these slots are declared
SchemaNodeorSchemaNode | SchemaNode[]in@object-ui/types, and the zod mirror's node union carries az.number()arm (nodeUnionOptionsinpackages/types/src/zod/base.zod.tslistsBaseSchemaCore,z.string(),z.number(),z.boolean(),z.null(),z.undefined()). The published validator therefore acceptschildren: 0; nothing between an author and this leak refuses it.The census, with its population and its control
Instrument: the TypeScript checker, not a grep — for each JSX guard
{X and-and …}in the tree, askgetTypeAtLocation(X)whether the type admitsnumber..tsxfiles underpackages/andapps/(enumerated withgit ls-files); 2396 JSXand-andguards in them; 2396 left operands across those guards.number.SchemaNode/SchemaNode | SchemaNode[]: 14 sites — objectui#9033'sheader-barrightContent, pluscomplex/table.tsxfooter,layout/card.tsx(four operands),layout/container.tsx/flex.tsx/grid.tsx/stack.tsxchildren,overlay/dialog.tsxandoverlay/sheet.tsxfooter, andplugin-detail'sDetailView.tsxheaderandfooter.header-bar.tsx— the one site already known to leak. It fired. A zero from an instrument with no lit control is not a reading.any/unknown, which carry no numeric type flag and so are invisible to a flag-based census.overlay/drawer.tsxfooterandlayout/containers.tsx'spage:cardbody/footerare in this bucket — theirschemaisany— and the runtime probe above showsdrawerfooterleaking, so the bucket is not empty of real defects.Not the same defect as objectui#8908, for the same reason objectui#9033 was not
toRenderableSchemahas mapped falsy primitives onto nothing since objectui#8908. It is not reached:and-andshort-circuits, so the chain produces the raw0before any bridge is called.renderChildren's ownif (!children) return nullfirst leg is bypassed identically — several of the sites above call it on the right-hand side and leak anyway.The shape of the fix is already ruled
objectui#8331 settled it on
DataTableSchema.emptyActionand objectui#9033 ported it toheader-bar: replace theand-andchain with a ternary yieldingnull, keeping the truthiness leg. objectui#7105 governs the declaration side — node slots relax the renderer, they do not narrow the declaration.Two things this card should decide rather than assume, because they are what make it bigger than a repeat of objectui#9033:
renderNodeSlot(value)helper — or teachingrenderChildrento be the guard and dropping theand-andentirely, since its first leg already answersnullfor every falsy input — closes the class instead of the instances.plugin-detail's two sites are in a different package and were not probed here. They are census hits, not measurements.Why it was filed rather than folded into objectui#9033's pull request
That card's dispatch named this exact outcome as a stop condition: report the list, fix the one line the card is about, do not silently widen. Eleven more renderers and a class-level remedy question is not a reviewable diff on a one-line card.
Refs
header-bar'srightContent, the one site now fixed; this census came out of implementing it.DataTableSchema.emptyAction, and the ruled shape.