Skip to content

ui:icon reads the SDUI identity key name as its lucide icon name, so any icon node that authors name renders nothing at all #5631

Description

@os-sales

Found while widening the DOM-leak sweep in #5574 (PR #5629). Out of that card's scope — it touches no renderer source — so recorded here.

What happens

packages/components/src/renderers/basic/icon.tsx resolves its glyph from schema.name:

const iconName = toPascalCase(schema.name);
const mappedIconName = iconNameMap[iconName] || iconName;
const Icon = (icons as any)[mappedIconName];

if (!Icon) {
  console.warn(...);
  return null;
}

But name is not this renderer's private prop — it is the SDUI identity key every authored node carries, alongside id. So a perfectly ordinary node like

{ type: 'ui:icon', id: 'save_icon', name: 'save_icon' }

resolves SaveIcon, misses, and returns null. Nothing renders. There is no error boundary, no placeholder, no visible failure — just an absent icon and one console.warn, which Vitest 4 discards for a passing test (silent: 'passed-only') and which nobody reads in a browser either.

How it was measured

The sweep renders every registry-reachable widget through the real SchemaRenderer path with a canary node that sets name: 'canary_node' — an ordinary authored identity, nothing exotic. ui:icon was one of 12 targets that rendered zero elements and therefore read as clean, because an empty scan reports no leaks. It only started rendering once the target was given schemaExtras: { name: 'check' } to override the identity key, and once it did it turned out to leak 14 attributes (it is in PR #5629's ledger under BARE_SPREAD_ON_SVG).

So the collision was invisible in two independent ways at once: silent at runtime, and clean-looking to a gate.

Why this is a contract question, not a one-line fix

The tempting fix — read schema.icon ?? schema.name in the consumer — is the tolerant-consumer shape #5574's family is being migrated away from, and it would make name mean two different things depending on whether the lookup happens to hit. The real question is which key names an icon on an icon node, answered once in the spec rather than per renderer:

  1. icon is the icon key; name is identity, always. Consistent with every other renderer and with action:*, which already reads schema.icon. Breaks any authored content relying on name, which is why it needs a look at real metadata rather than a guess.
  2. ui:icon keeps name, and the identity key is documented as unavailable on this one node type — cheap, but it is a carve-out in the authoring model that an author cannot discover before it silently renders nothing.

Either way the failure mode should stop being silent: an unresolvable icon should render a visible placeholder or be refused at publish time, not return null.

Related but distinct: #5622 and #5586 both concern retired lucide spellings reaching this same icons-record resolver. Those are about which names resolve; this is about which schema key is consulted in the first place. A fix for either does not close the other.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions