Skip to content

html-tier page source: a type attribute overwrites the SDUI component discriminator — silently when the value names another registered type #13957

Description

@claude

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.tscompile() returns parsed.tree unmodified
  • packages/sdui-parser/src/validate.tsBASE_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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions