Skip to content

finding(components): eleven more SchemaNode slots carry the same numeric-falsy JSX trap objectui#9033 closed on header-bar — the census the card's grep could not see #9162

Description

@os-tesla

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:

  1. 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.
  2. 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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions