Skip to content

finding(react): a key authored under the props envelope never reaches a renderer that reads schema — measured as a silent empty data-table next to a working properties twin #6708

Description

@claude

Found while implementing #6665 (the non-array node-level data diagnostic). Measured on that
card's merge-base but outside its fence, so filed separately.

The fact

#6665's four-leg re-measurement was run on 5967be095 through the real SchemaRenderer
inside a SchemaRendererProvider holding { customers: [ 2 records ] }, identical columns
in every leg, reading tbody td. Leg 2 is this card:

node rendered body
{ "type": "data-table", "data": "${data.customers}", ... } No results found (that is #6665)
{ "type": "data-table", "props": { "data": "${data.customers}" }, ... } No results found
{ "type": "data-table", "properties": { "data": "${data.customers}" }, ... } the two rows
{ "type": "data-table", "data": [ 2 literal records ], ... } the two rows

The interesting pair is legs 2 and 3: the SAME key, the SAME value, one envelope apart, and
only properties puts rows on screen.

The mechanism

SchemaRenderer HOISTS properties.* onto the node (minus HOIST_PROTECTED_KEYS), which is
why leg 3 works — the hoisted data is a real array by the time DataTableRenderer
destructures it. props is not hoisted; it is spread as React props on the created element.
DataTableRenderer is declared as ({ schema }) and reads nothing else, so a key written
under props never reaches it.

That is not specific to data-table. It applies to EVERY renderer that reads only schema
which is the normal shape for the component renderers, as distinct from the element:* family
whose readProps() merges { ...schema.props, ...schema.properties }. So the same key
spelled props is honoured in one renderer family and silently dropped in the other.

props is documented as the annotated legacy alias for the config bag, and the 2026-08-18
ruling recorded on #5123 settled precedence between the two bags (properties wins on both
channels). Neither says the alias is INERT for component renderers, which is what the
measurement above shows.

Why it is not #6665

#6665 was fenced to node-level data, and its diagnostic is deliberately silent here: with
the key under props, node-level data is genuinely absent, so there is nothing for a
data-table-level predicate to honestly say. That silence is pinned by a test in that PR
(does NOT reach into the props envelope) rather than left to be rediscovered. Widening that
predicate to reach into props would patch one component against a repo-wide shape.

What a taker would need to decide first

Whether this is a defect or the declared design. Both readings are available today:

If it is a defect, the arms are the familiar three: hoist props like properties (a
behaviour change on published components, so a ruling), diagnose it at the SchemaRenderer
tier (no behaviour change, one place, covers every renderer), or refuse it at parse (blocked
on the .passthrough() ceiling, #5155 / #6269).

Refs: #6665 (where it was measured) - #6575 (the diagnostic precedent) - #5123 (the two-bag
precedence ruling) - #4795 (the properties authoring-channel contract question) - #5155 /
#6269 (the .passthrough() ceiling).


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions