Skip to content

finding(react): a non-object props is object-spread into indexed React props — props: "text" reaches the element as 0, 1, 2, … #6752

Description

@claude

Measured while implementing objectui#6708's diagnostic, outside its fence, so filed separately. Observation-class: nothing is known to depend on it today.

The fact, measured on faac0d935

SchemaRenderer's evaluation memo rebuilds the props bag under a BARE truthiness guard:

if (newSchema.props) {
  const newProps = { ...newSchema.props };
  for (const [key, val] of Object.entries(newProps)) {
    newProps[key] = evaluator.evaluate(val);
  }
  newSchema.props = newProps;
}

Spreading a string produces its character indices. So a node written { type: 'card', props: 'not-a-bag' } arrives at the createElement spread carrying nine React props named 0 through 8, one per character, and they are spread onto the component.

This was not deduced — it is what turned an objectui#6708 pin red. That test expected silence for a degenerate bag; the diagnostic instead reported nine dropped keys and told the author that the member 0 of schema was undefined. The published fix reads the AUTHORED bag off the original schema so it stays silent there, which means the underlying spread is still exactly as described.

Why it looks like an oversight rather than a decision

The sibling properties branch a few lines above carries a deliberately WIDER guard and says why in its comment: a degenerate properties must not have its shape reinterpreted by an object spread, because that value feeds the hoist. The props branch has no equivalent guard, and its value feeds the React-prop spread instead. Same hazard, one of the two channels guarded.

Blast radius, honestly bounded

Zero in-repo authored nodes carry a non-object props — the objectui#6708 census walked every JSON document, every json doc fence and every TypeScript object literal for nodes with both type and props and found no such node. So this is a latent shape, not a live failure, and the arm that follows from that may well be "leave it". What makes it worth recording is that the two bags disagree about a hazard the code itself documents on one side.

Arms, if anyone takes it

  • widen the props guard to match properties' — smallest, and makes the documented reasoning apply to both channels;
  • leave it and record the asymmetry as intended, in which case the properties comment should say why only one side needs it.

Either way it needs someone to decide, not to guess.

Related: objectui#6708 (where it was measured), objectui#5123 (the two-bag precedence ruling).


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

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

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions