Skip to content

canvas: a sprite's <use> is inlined into the inspector's copy, and its host is no asset - #74

Open
Jing-yilin wants to merge 1 commit into
mainfrom
canvas/inspector-sprite-use
Open

canvas: a sprite's <use> is inlined into the inspector's copy, and its host is no asset#74
Jing-yilin wants to merge 1 commit into
mainfrom
canvas/inspector-sprite-use

Conversation

@Jing-yilin

Copy link
Copy Markdown
Contributor

Fixes #73

addSvg() cloned only the root <svg>, so a <use href="#id"> into a sprite of <symbol>s dangled in the copy and drew nothing, and with the viewBox on the symbol the row's size read 0×0. The copy is self-contained again:

  • Same-document <use> is inlined, after the fill/stroke pass (which walks the original and the copy in lockstep by index, so inlining first would shift every later index). A <symbol> becomes an inner <svg> carrying the use's own attributes (x, y, width, height, a fill) and the symbol's viewBox; any other target is cloned in place. The inlined geometry takes the root's computed fill/stroke that the function already writes in. External references (file.svg#id) are left alone. Eight passes cap a chain or a self-referencing symbol (ponytail: comment).
  • The root adopts the symbol's viewBox when it has none, and the row's w/h read from the copy, so the size is right and the key signs like the symbol's file would.
  • The sprite host is no asset. <svg width="0" height="0"><defs><symbol>… passed the content guard because querySelector('path') reaches inside the <symbol>, and was handed out as an unnamed 0×0 row that drew nothing (confirmed below). Geometry inside <defs>/<symbol> no longer counts.

No committed board holds a <use> (grep over mockups/canvases/*/*.html: 0), so nothing regenerates. A census over the 1,512 root <svg>s in the committed boards (33 with <defs>/<symbol>) shows the new guard changes the outcome for none of them; the in-svg gradient references keep working (see the Gradient control below).

Evidence

The real AGENT string (via injectAgent) run against a sprite fixture matching the issue's repro in headless Chrome (--headless --disable-gpu --dump-dom, the way refkit already drives it), reading the sp:ready message the agent posts. Fixture, harness and captures are in scratch/issue-73/ (gitignored).

Before

[(unnamed)] w=0 h=0 uses=[1]        <- the sprite host, item 3
  <svg width="0" height="0" xmlns="http://www.w3.org/2000/svg" fill="rgb(0, 0, 0)" stroke="none"><defs>
  <symbol id="ic-home" viewBox="0 0 24 24"><path d="M4 11 12 4l8 7v8a1 1 0 0 1-1 1h-4v-6h-6v6H5a1 1 0 0 1-1-1z"></path></symbol>
  …</defs></svg>
[Home] w=0 h=0 uses=[11]
  <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="rgb(28, 28, 30)" stroke="none"><use href="#ic-home"></use></svg>
[Alerts] w=0 h=0 uses=[14]
  <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="rgb(224, 36, 94)" stroke="none"><use xlink:href="#ic-home"></use></svg>
[Dot] w=24 h=24 uses=[23]           <- <use> of a plain <path id> in the sprite
  <svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="rgb(28, 28, 30)" stroke="none"><use href="#dot"></use></svg>
[Gradient] w=24 h=24 uses=[26]      <- control: in-svg <defs> reference, already fine
  <svg … viewBox="0 0 24 24" …><defs><linearGradient id="g">…</linearGradient></defs><rect width="24" height="24" fill="url(#g)"></rect></svg>
[External] w=0 h=0 uses=[33]
  <svg width="24" height="24" … ><use href="icons.svg#ic-home"></use></svg>

Rendered as the inspector's <img src="data:image/svg+xml,…">: the host, Home and Alerts are blank (Alerts is a broken image, the xlink: prefix without its namespace makes the copy invalid), Dot is blank, Gradient draws.

After

[Home] w=24 h=24 uses=[11,17]
  <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="rgb(28, 28, 30)" stroke="none" viewBox="0 0 24 24"><svg viewBox="0 0 24 24"><path d="M4 11 12 4l8 7v8a1 1 0 0 1-1 1h-4v-6h-6v6H5a1 1 0 0 1-1-1z"></path></svg></svg>
[Alerts] w=24 h=24 uses=[14]
  <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="rgb(224, 36, 94)" stroke="none" viewBox="0 0 24 24"><svg viewBox="0 0 24 24"><path d="M4 11 …z"></path></svg></svg>
[Loop] w=24 h=24 uses=[20]          <- a symbol that uses itself: stops after eight passes
  <svg … viewBox="0 0 24 24"><svg viewBox="0 0 24 24">…×8…<use href="#ic-loop"></use></svg>…</svg>
[Dot] w=24 h=24 uses=[23]
  <svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="rgb(28, 28, 30)" stroke="none"><svg><path id="dot" d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0-8 0"></path></svg></svg>
[Gradient] w=24 h=24 uses=[26]      <- unchanged
[External] w=0 h=0 uses=[33]        <- unchanged, left alone

The sprite host row is gone; Home and Alerts draw the house in their own colour (.red svg{fill:#e0245e}), Dot draws, Gradient is byte-identical to before, Loop and External stay blank.

Checks

bun run lint, bun run test (90 passed; one new assertion on the AGENT source in inspectorAgent.test.ts), bun run build. No dependency added.

Left out

  • A symbol that nests another symbol at an offset (<symbol><use href="#a" x=6 width=12/></symbol>) renders right but shares the row of the symbol it nests: svgSignature signs drawing elements, not the inner <svg> wrapper. Pre-existing property of the key (a <g transform> collides the same way); not worth changing the index's keys for.
  • The symbol's own presentation attributes (fill, class) are not carried; the inlined geometry inherits the root's computed fill/stroke, as noted in the code.
  • A fill="url(#grad)" from the inlined geometry into the sprite host's <defs> still dangles; no board does it.

🤖 Generated with Claude Code

…s host is no asset

addSvg() cloned only the root <svg>, so a <use href="#id"> into a sprite of
<symbol>s dangled in the copy and drew nothing, and with the viewBox on the
symbol the row's size read 0x0. The copy now resolves same-document references
after the fill/stroke pass, which walks the original and the copy in lockstep by
index; a symbol becomes an inner <svg> carrying the use's own attributes and the
symbol's viewBox, which the root adopts when it has none. The sprite host's own
<defs> of symbols passed the content guard and was handed out as an unnamed 0x0
row that drew nothing; geometry inside <defs> or <symbol> no longer counts.
No committed board holds a <use>, so nothing regenerates.

Fixes #73

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2de723723d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if(!t)continue;g=document.createElementNS('http://www.w3.org/2000/svg','svg');
for(n=0;n<u.attributes.length;n++)if(!/href$/.test(u.attributes[n].name))g.setAttribute(u.attributes[n].name,u.attributes[n].value);
if(t.tagName==='symbol'){if(t.hasAttribute('viewBox')){g.setAttribute('viewBox',t.getAttribute('viewBox'));
if(!c.hasAttribute('viewBox'))c.setAttribute('viewBox',t.getAttribute('viewBox'));}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the outer coordinate system when inlining symbols

When a root SVG without a viewBox has dimensions or a composition that differs from the referenced symbol—for example, a 48×24 SVG placing two 24-unit symbols side by side—copying the first symbol's viewBox onto the root changes the root coordinate system, rescales or clips the inlined content, and reports the asset as 24×24 rather than 48×24. Keep the symbol's viewBox on its replacement inner SVG and only synthesize an outer viewBox when it is known to represent a single full-root instance.

AGENTS.md reference: AGENTS.md:L54-L58

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inspector hands out an empty asset for an icon drawn with <use> into a sprite

1 participant