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
Found while implementing #6575 (the ignored-
binddiagnostic). Not that card's defect andoutside its fence, so filed separately.
The fact
#6575 closes one silent way to get a
data-tableheader over an empty body: an authoredbind, 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-tablereallydoes read.
skills/objectui/rules/protocol.mdcarries a measured table (stated as measured onorigin/mainf1c27f037, realSchemaRendererinside aSchemaRendererProviderholding{ customers: [ 2 records ] }, identicalcolumnsin every leg, readingtbody td):{ "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}" }, ... }{ "type": "data-table", "data": [ 2 literal records ], ... }So a
${...}expression written intodataat node level is not evaluated:rawDataisa string,
Array.isArray(rawData)is false, anddatafalls back toEMPTY_ROWS. The sameexpression under
propertiesIS evaluated. The author gets a correct-looking header overNo 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:
datais the key the componentgenuinely 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 nobindat all, so itstays silent — correctly, per its own predicate. Catching this one needs a different
question asked at the same place:
datawas 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 ameasurement on
f1c27f037. I did not re-run it on my merge-base (813bf832) — that wasoutside the #6575 fence and I did not want to spend a shared-verify-lock slot on it. What I
did confirm on
813bf832is the mechanism that makes it plausible: inpackages/components/src/renderers/complex/data-table.tsx, rows aredata: rawData = EMPTY_ROWSoff the node and thenconst data = Array.isArray(rawData) ? rawData : EMPTY_ROWS, so any non-arrayrawData— a string included — silently becomes no rows.First step for whoever takes this: re-measure the table on current
mainbefore decidinganything. If it still reproduces, the shape question is whether the node-level
datashouldevaluate 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 nowexists at
packages/components/src/renderers/complex/dataTableBindDiagnostic.ts).Refs: #6575, and the adjacent
.passthrough()ceiling #5155 / #6269.Generated by Claude Code