Found while auditing content/docs/ui/react-pages.mdx for #13737 (docs-only change, PR #13955). Not fixed there — this is a parser defect, not a docs one.
Mechanism
packages/sdui-parser/src/parse.ts, at the end of parseElement(), builds the node as:
const node: SchemaElement = { type: tag, ...props };
props is spread after type: tag, so an authored type attribute overwrites the component discriminator the tag name established. Nothing downstream restores it: compile() in index.ts returns parsed.tree as-is, and validateTree then reads manifest.components[node.type] — the value the author wrote, not the tag they wrote.
The react tier has an explicit rescue for exactly this collision (the author's value is preserved beside the discriminator as specType, objectui#2880). The html tier has none, and the html tier is the one whose whole point is that unreviewed and AI-authored source is safe to accept.
Why it is silent rather than loud
type is a member of BASE_PROPS in packages/sdui-parser/src/validate.ts:
/** Base props every node may carry (mirrors BaseSchema) -- never "unknown prop". */
const BASE_PROPS = new Set(['type','id','className','style','visible','visibleOn','disabled','disabledOn','children']);
so the unknown-prop warning is skipped for it by design. Two outcomes, neither good:
- the value names another registered type — e.g. a
flex element carrying type="grid". validateTree finds grid in the manifest and every check passes. Zero diagnostics, and the page renders a grid where the author wrote a flex. This is the silent one.
- the value names nothing registered — e.g. an
object-chart element carrying type="bar", which is the shape a react-tier author would carry over. The diagnostic is unknown-component, message "bar" is not a known component. Loud, but it names the value and never says that a type attribute caused it, so it reads as a missing plugin rather than as a bad prop.
object-chart in the repo's sdui.manifest.json declares inputs objectName, data, filter, aggregate, drillDown — no type — so the second case is reachable purely by carrying a documented react-tier prop across tiers.
Evidence
Read from source at origin/main; I did not execute it, and no browser or app run is behind this report.
packages/sdui-parser/src/parse.ts — the spread, in parseElement()
packages/sdui-parser/src/index.ts — compile() returns parsed.tree unmodified
packages/sdui-parser/src/validate.ts — BASE_PROPS includes type; visit() looks up manifest.components[node.type]
sdui.manifest.json — 57 component keys; object-chart declares no type input
Suggested direction (not a decision)
The contract-first shape is to refuse it at parse time rather than to alias it: when a type attribute is authored on an element, that is a name collision with the envelope's discriminator and the parser can say so — one diagnostic naming both the tag and the attribute. Whether the html tier should instead carry the react tier's specType rescue is a protocol question (ADR-0080), and someone who owns that decision should take it; a consumer-side accommodation in a renderer would be the wrong end.
Generated by Claude Code
Generated by Claude Code
Found while auditing
content/docs/ui/react-pages.mdxfor #13737 (docs-only change, PR #13955). Not fixed there — this is a parser defect, not a docs one.Mechanism
packages/sdui-parser/src/parse.ts, at the end ofparseElement(), builds the node as:propsis spread aftertype: tag, so an authoredtypeattribute overwrites the component discriminator the tag name established. Nothing downstream restores it:compile()inindex.tsreturnsparsed.treeas-is, andvalidateTreethen readsmanifest.components[node.type]— the value the author wrote, not the tag they wrote.The react tier has an explicit rescue for exactly this collision (the author's value is preserved beside the discriminator as
specType, objectui#2880). The html tier has none, and the html tier is the one whose whole point is that unreviewed and AI-authored source is safe to accept.Why it is silent rather than loud
typeis a member ofBASE_PROPSinpackages/sdui-parser/src/validate.ts:so the
unknown-propwarning is skipped for it by design. Two outcomes, neither good:flexelement carryingtype="grid".validateTreefindsgridin the manifest and every check passes. Zero diagnostics, and the page renders a grid where the author wrote a flex. This is the silent one.object-chartelement carryingtype="bar", which is the shape a react-tier author would carry over. The diagnostic isunknown-component, message"bar" is not a known component. Loud, but it names the value and never says that atypeattribute caused it, so it reads as a missing plugin rather than as a bad prop.object-chartin the repo'ssdui.manifest.jsondeclares inputsobjectName,data,filter,aggregate,drillDown— notype— so the second case is reachable purely by carrying a documented react-tier prop across tiers.Evidence
Read from source at
origin/main; I did not execute it, and no browser or app run is behind this report.packages/sdui-parser/src/parse.ts— the spread, inparseElement()packages/sdui-parser/src/index.ts—compile()returnsparsed.treeunmodifiedpackages/sdui-parser/src/validate.ts—BASE_PROPSincludestype;visit()looks upmanifest.components[node.type]sdui.manifest.json— 57 component keys;object-chartdeclares notypeinputSuggested direction (not a decision)
The contract-first shape is to refuse it at parse time rather than to alias it: when a
typeattribute is authored on an element, that is a name collision with the envelope's discriminator and the parser can say so — one diagnostic naming both the tag and the attribute. Whether the html tier should instead carry the react tier'sspecTyperescue is a protocol question (ADR-0080), and someone who owns that decision should take it; a consumer-side accommodation in a renderer would be the wrong end.Generated by Claude Code
Generated by Claude Code