Observation-class finding, recorded while writing the #4032 render pins. Nothing a user sees; filed so the next agent asserting not.toContain('[object Object]') on a dashboard container knows why it fails and does not "fix" it by widening the wrong assertion.
What happens
MetricWidget (packages/plugin-dashboard/src/MetricWidget.tsx) ends its prop list with ...props and spreads it onto the Shadcn Card:
export const MetricWidget = ({ label, value, /* … */ ...props }: MetricWidgetProps) => {
…
<Card className={…} onClick={…} {...props}>
When the widget is reached through SchemaRenderer (every dashboard KPI tile), the renderer hands it the widget schema, so schema lands in ...props and React — which passes unknown lowercase attributes straight through — writes it to the DOM stringified:
<div class="rounded-lg border bg-card …" schema="[object Object]" data-obj-type="metric">
MetricCard has the same {...props} spread and the same exposure.
Why it is worth writing down even though it is invisible
It is a trap for exactly the assertion this area attracts. #4163 pinned expect(container.innerHTML).not.toContain('[object Object]') on DashboardGridLayout, and that is the natural thing to write for any I18nLabel render site. On the metric path that assertion fails for a reason unrelated to labels, in both directions:
- before an i18n fix, it fails and looks like it caught the label bug;
- after the fix, it still fails, and the tempting repair is to delete or loosen the assertion — which throws away the real pin.
#4032's suite therefore asserts on the card heading element rather than the container, with a comment. That works, but it is a workaround for a defect that should just not be there.
Fix shape
Destructure the schema-shaped props out before the spread (or allow-list what reaches the DOM), the way a component that is both an SDUI block and a React component normally does. Low risk, but it touches the public prop contract of two exported components, so it is not a drive-by.
No pm:queue — nothing a user hits today.
Generated by Claude Code
Observation-class finding, recorded while writing the #4032 render pins. Nothing a user sees; filed so the next agent asserting
not.toContain('[object Object]')on a dashboard container knows why it fails and does not "fix" it by widening the wrong assertion.What happens
MetricWidget(packages/plugin-dashboard/src/MetricWidget.tsx) ends its prop list with...propsand spreads it onto the ShadcnCard:When the widget is reached through
SchemaRenderer(every dashboard KPI tile), the renderer hands it the widget schema, soschemalands in...propsand React — which passes unknown lowercase attributes straight through — writes it to the DOM stringified:MetricCardhas the same{...props}spread and the same exposure.Why it is worth writing down even though it is invisible
It is a trap for exactly the assertion this area attracts. #4163 pinned
expect(container.innerHTML).not.toContain('[object Object]')onDashboardGridLayout, and that is the natural thing to write for any I18nLabel render site. On the metric path that assertion fails for a reason unrelated to labels, in both directions:#4032's suite therefore asserts on the card heading element rather than the container, with a comment. That works, but it is a workaround for a defect that should just not be there.
Fix shape
Destructure the schema-shaped props out before the spread (or allow-list what reaches the DOM), the way a component that is both an SDUI block and a React component normally does. Low risk, but it touches the public prop contract of two exported components, so it is not a drive-by.
No
pm:queue— nothing a user hits today.Generated by Claude Code