Skip to content

data-table: a ${...} expression authored in node-level data is not evaluated and renders an empty body silently #6665

Description

@claude

Found while implementing #6575 (the ignored-bind diagnostic). Not that card's defect and
outside its fence, so filed separately.

The fact

#6575 closes one silent way to get a data-table header over an empty body: an authored
bind, which the renderer never reads. There is a SECOND way, in the same family, that the
#6575 diagnostic does not cover — because the key involved is the one data-table really
does read.

skills/objectui/rules/protocol.md carries a measured table (stated as measured on
origin/main f1c27f037, real SchemaRenderer inside a SchemaRendererProvider holding
{ customers: [ 2 records ] }, identical columns in every leg, reading tbody td):

node rendered body cells
{ "type": "data-table", "data": "${data.customers}", "columns": [...] } No results found
{ "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

So a ${...} expression written into data at node level is not evaluated: rawData is
a string, Array.isArray(rawData) is false, and data falls back to EMPTY_ROWS. The same
expression under properties IS evaluated. The author gets a correct-looking header over
No results found, with no error and no warning — the same success-receipt-for-a-
disagreement shape #6575 exists to remove, one key over.

This is arguably the more common spelling of the two: data is the key the component
genuinely reads and the key every guide teaches, so writing an expression into it is the
natural next thing an author tries.

Why the #6575 diagnostic does not catch it

That diagnostic is keyed on an authored bind. These nodes carry no bind at all, so it
stays silent — correctly, per its own predicate. Catching this one needs a different
question asked at the same place: data was authored, it is a string rather than an array,
and it looks like an expression.

Provenance and what has NOT been re-measured

The table above is quoted from skills/objectui/rules/protocol.md, which states it as a
measurement on f1c27f037. I did not re-run it on my merge-base (813bf832) — that was
outside the #6575 fence and I did not want to spend a shared-verify-lock slot on it. What I
did confirm on 813bf832 is the mechanism that makes it plausible: in
packages/components/src/renderers/complex/data-table.tsx, rows are data: rawData = EMPTY_ROWS off the node and then const data = Array.isArray(rawData) ? rawData : EMPTY_ROWS, so any non-array rawData — a string included — silently becomes no rows.

First step for whoever takes this: re-measure the table on current main before deciding
anything. If it still reproduces, the shape question is whether the node-level data should
evaluate expressions (a behaviour change on a published component, so a ruling) or whether it
should be diagnosed the way #6575 diagnoses bind (no behaviour change, and the channel now
exists at packages/components/src/renderers/complex/dataTableBindDiagnostic.ts).

Refs: #6575, and the adjacent .passthrough() ceiling #5155 / #6269.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

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

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions