Observation-class finding, measured while implementing #4357. Pre-existing on origin/main and unchanged by that PR; filed rather than widened into it, because closing it changes the exported props interface of two components.
What happens
Both components end their prop list with a ...props spread onto the Shadcn Card. #4357 keeps the spread deliberately — it is the components' genuine DOM/aria passthrough, and removing it would delete the only way a host can put id, role or an aria-* on a KPI card. But neither MetricWidgetProps nor MetricCardProps extends React.HTMLAttributes, and neither declares those keys, so what the runtime accepts the type refuses.
Measured against the built dist/index.d.ts from a consumer package:
probe.tsx(23,52): error TS2322: Type '{ label: string; value: number; id: string; role: string; "aria-label": string; }'
is not assignable to type 'IntrinsicAttributes & MetricWidgetProps & SchemaHostProps'.
The same source compiles and behaves correctly at runtime — the attributes reach the card. A JS consumer, or any SDUI author going through SchemaRenderer (which is untyped at that boundary), gets the passthrough; a TypeScript consumer importing the component directly cannot use it without a cast.
Why it is worth writing down even though nobody is blocked
It is the mirror image of #4357. That issue was renderer metadata reaching the DOM because the spread is open; this is DOM attributes being unreachable because the type is closed. Both come from the same unstated question — what may a dashboard widget legitimately receive? — and answering it once, in the type, is what makes "declared = enforced" true here in both directions instead of one.
Fix shape
Declare the passthrough: extend the interfaces with the subset of React.HTMLAttributes a KPI card should accept, or an explicit block mirroring what FieldWidgetDomProps does for field widgets (objectui#3221). Either way it widens the exported props interface, which by this repo's grading is a minor with the semantics in the changeset body (the objectui#4403 precedent), so it is not a drive-by rider on a patch fix. It should also be decided together with #4425 — if plugin widgets move to a toDomProps-style whitelist, the declared DOM set is exactly that whitelist and the two are one change.
No pm:queue — nothing a user hits today.
Generated by Claude Code
Observation-class finding, measured while implementing #4357. Pre-existing on
origin/mainand unchanged by that PR; filed rather than widened into it, because closing it changes the exported props interface of two components.What happens
Both components end their prop list with a
...propsspread onto the ShadcnCard. #4357 keeps the spread deliberately — it is the components' genuine DOM/aria passthrough, and removing it would delete the only way a host can putid,roleor anaria-*on a KPI card. But neitherMetricWidgetPropsnorMetricCardPropsextendsReact.HTMLAttributes, and neither declares those keys, so what the runtime accepts the type refuses.Measured against the built
dist/index.d.tsfrom a consumer package:The same source compiles and behaves correctly at runtime — the attributes reach the card. A JS consumer, or any SDUI author going through
SchemaRenderer(which is untyped at that boundary), gets the passthrough; a TypeScript consumer importing the component directly cannot use it without a cast.Why it is worth writing down even though nobody is blocked
It is the mirror image of #4357. That issue was renderer metadata reaching the DOM because the spread is open; this is DOM attributes being unreachable because the type is closed. Both come from the same unstated question — what may a dashboard widget legitimately receive? — and answering it once, in the type, is what makes "declared = enforced" true here in both directions instead of one.
Fix shape
Declare the passthrough: extend the interfaces with the subset of
React.HTMLAttributesa KPI card should accept, or an explicit block mirroring whatFieldWidgetDomPropsdoes for field widgets (objectui#3221). Either way it widens the exported props interface, which by this repo's grading is aminorwith the semantics in the changeset body (the objectui#4403 precedent), so it is not a drive-by rider on apatchfix. It should also be decided together with #4425 — if plugin widgets move to atoDomProps-style whitelist, the declared DOM set is exactly that whitelist and the two are one change.No
pm:queue— nothing a user hits today.Generated by Claude Code