canvas: a sprite's <use> is inlined into the inspector's copy, and its host is no asset - #74
canvas: a sprite's <use> is inlined into the inspector's copy, and its host is no asset#74Jing-yilin wants to merge 1 commit into
Conversation
…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>
There was a problem hiding this comment.
💡 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'));} |
There was a problem hiding this comment.
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 👍 / 👎.
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 theviewBoxon the symbol the row's size read 0×0. The copy is self-contained again:<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'sviewBox; 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).viewBoxwhen it has none, and the row'sw/hread from the copy, so the size is right and the key signs like the symbol's file would.<svg width="0" height="0"><defs><symbol>…passed the content guard becausequerySelector('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>(grepovermockups/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 theGradientcontrol below).Evidence
The real
AGENTstring (viainjectAgent) run against a sprite fixture matching the issue's repro in headless Chrome (--headless --disable-gpu --dump-dom, the wayrefkitalready drives it), reading thesp:readymessage the agent posts. Fixture, harness and captures are inscratch/issue-73/(gitignored).Before
Rendered as the inspector's
<img src="data:image/svg+xml,…">: the host, Home and Alerts are blank (Alerts is a broken image, thexlink:prefix without its namespace makes the copy invalid), Dot is blank, Gradient draws.After
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 theAGENTsource ininspectorAgent.test.ts),bun run build. No dependency added.Left out
<symbol><use href="#a" x=6 width=12/></symbol>) renders right but shares the row of the symbol it nests:svgSignaturesigns 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.fill,class) are not carried; the inlined geometry inherits the root's computed fill/stroke, as noted in the code.fill="url(#grad)"from the inlined geometry into the sprite host's<defs>still dangles; no board does it.🤖 Generated with Claude Code