When an element's identity comes from a prop binding rather than a literal, the writer skips the element entirely — losing its styles, its layout row, and the backing prop.
Evidence
SpotlightIcon:
| path |
baseline |
round-trip |
anatomy.icon |
{type: glyph} |
(absent) |
props.icon |
{type: string, examples: [accessTime]} |
(absent) |
default.layout[0] |
{root: [icon]} |
"root" |
variants[0..3].elements.icon |
fill colour per variant |
(absent) |
Mark:
| path |
baseline |
round-trip |
anatomy.asset |
{type: instance, instanceOf: AssetMarkBrandMemberOnlyDeal} |
(absent) |
props.name |
{type: string, examples: [AssetMarkBrandMemberOnlyDeal]} |
(absent) |
default.elements.asset |
instanceOf: {$binding: "#/props/name"} + sizing |
(absent) |
variants[0..8].elements.asset |
sizing per variant |
(absent) |
Where — the fix already exists in this codebase
packages/figma-from-specs/src/:
Elements/GlyphElement.ts — create() resolves a glyph name only from static content (typeof spec.content === 'string' or $value) and returns null for a $binding, so Elements.createChild skips the element.
Elements/Elements.ts — expandCollapsedRoot() already solves exactly this for the ADR 058 collapsed-root case: it calls Element.resolveContentText(rootSpec, props) to resolve a bound content to the prop's first example before creating the leaf. The same resolution applied in createChild's glyph branch is the fix.
Props/Props.ts — swapDefaultId() mints no INSTANCE_SWAP property when the target has no manifest entry, which is the Mark half.
Note Mark's target is a genuinely external asset mark, so the element should still render with its example rather than resolving to a library component.
Fixtures
SpotlightIcon — bound glyph content
Mark — bound instanceOf
Two components, both with aligned variant counts, both small.
Verification
Round-trip both. anatomy.icon / anatomy.asset survive, the layout row is preserved ({root: [icon]} not "root"), and the backing prop is still in props.
Part of #281.
When an element's identity comes from a prop binding rather than a literal, the writer skips the element entirely — losing its styles, its layout row, and the backing prop.
Evidence
SpotlightIcon:anatomy.icon{type: glyph}props.icon{type: string, examples: [accessTime]}default.layout[0]{root: [icon]}"root"variants[0..3].elements.iconMark:anatomy.asset{type: instance, instanceOf: AssetMarkBrandMemberOnlyDeal}props.name{type: string, examples: [AssetMarkBrandMemberOnlyDeal]}default.elements.assetinstanceOf: {$binding: "#/props/name"}+ sizingvariants[0..8].elements.assetWhere — the fix already exists in this codebase
packages/figma-from-specs/src/:Elements/GlyphElement.ts—create()resolves a glyph name only from static content (typeof spec.content === 'string'or$value) and returnsnullfor a$binding, soElements.createChildskips the element.Elements/Elements.ts—expandCollapsedRoot()already solves exactly this for the ADR 058 collapsed-root case: it callsElement.resolveContentText(rootSpec, props)to resolve a bound content to the prop's first example before creating the leaf. The same resolution applied increateChild's glyph branch is the fix.Props/Props.ts—swapDefaultId()mints no INSTANCE_SWAP property when the target has no manifest entry, which is theMarkhalf.Note
Mark's target is a genuinely external asset mark, so the element should still render with its example rather than resolving to a library component.Fixtures
SpotlightIcon— bound glyphcontentMark— boundinstanceOfTwo components, both with aligned variant counts, both small.
Verification
Round-trip both.
anatomy.icon/anatomy.assetsurvive, the layout row is preserved ({root: [icon]}not"root"), and the backing prop is still inprops.Part of #281.