Describe the bug
Two nested scenes that each define their own #clip, #shape, and #fx are legal per file and pass hyperframes check. After inline, the assembled document has duplicate IDs. Browser-native references (clip-path="url(#clip)", <use href="#shape">, CSS filter: url(#fx)) resolve to the first match in document order, so later scenes clip to nothing or paint the earlier scene's colour.
getElementById was scoped in #646. Media pipeline collisions were fixed with render keys in #3340. Native url(#…) / href="#…" were not rewritten, and lint does not flag generic duplicate ids — only (some) media ones.
Catalog blocks already hardcode ids such as url(#tracing-beam-glow), so two instances of the same block hit this without the author duplicating anything by hand.
Ask: rewrite url(#) / href="#" (and the matching ids) per composition on inline, or lint generic duplicate ids the way media ids are now covered. Rewrite is the one that makes nested catalog blocks work without unique-prefixing every SVG id.
Link to reproduction
https://github.com/ArcadeHQ/hyperframes-repros/tree/patch/duplicate-svg-ids
Steps to reproduce
- Clone https://github.com/ArcadeHQ/hyperframes-repros and check out
patch/duplicate-svg-ids.
- Run
npx hyperframes@latest check — 0 errors.
- Run
npx hyperframes@latest snapshot --at 1,3 --no-end --describe false
- Open
snapshots/frame-00-at-1s.png and snapshots/frame-01-at-3s.png.
Expected behavior
t=1s (scene-a): all four rows red
t=3s (scene-b): all four rows blue
Actual behavior
t=1s: all four rows red (ok)
t=3s: (1) control blue; (2) clip-path absent; (3) <use> red (scene-a's shape); (4) CSS filter red (scene-a's flood)
Environment
hyperframes doctor
✓ Version 0.8.14 (latest)
✓ Node.js v24.19.0 (darwin arm64)
✓ CPU 10 cores · Apple M1 Pro @ 2400MHz
✓ Memory 16.0 GB total · 4.2 GB available
✓ Disk 365.8 GB free
✓ Frames cache /var/folders/bl/c4hk6r7n34g1d81txd99gsgr0000gn/T/hyperframes-extract-cache-501 · 365.8 GB free at /var/folders/bl/c4hk6r7n34g1d81txd99gsgr0000gn/T/hyperframes-extract-cache-501 · default
✓ Archive extractor unzip
✓ Environment non-TTY
✗ whisper-cpp Not found (optional — needed for transcription)
✗ TTS (Kokoro) Not installed (optional — local voice fallback)
✗ BGM (MusicGen) Not installed (optional — local music fallback)
✓ FFmpeg ffmpeg 8.1.2 at /opt/homebrew/bin/ffmpeg
✓ FFprobe ffprobe 8.1.2 at /opt/homebrew/bin/ffprobe
✓ Chrome cache: /Users/val/.cache/puppeteer/chrome-headless-shell/mac_arm-152.0.7977.42/chrome-headless-shell-mac-arm64/chrome-headless-shell
✗ Docker Not found
✗ Docker running Not running
Additional context
Related, not the same: #646 (scoped getElementById), #3340 (media render keys). #556 was CSS attribute-selector scope across sibling instances.
Describe the bug
Two nested scenes that each define their own
#clip,#shape, and#fxare legal per file and passhyperframes check. After inline, the assembled document has duplicate IDs. Browser-native references (clip-path="url(#clip)",<use href="#shape">, CSSfilter: url(#fx)) resolve to the first match in document order, so later scenes clip to nothing or paint the earlier scene's colour.getElementByIdwas scoped in #646. Media pipeline collisions were fixed with render keys in #3340. Nativeurl(#…)/href="#…"were not rewritten, and lint does not flag generic duplicate ids — only (some) media ones.Catalog blocks already hardcode ids such as
url(#tracing-beam-glow), so two instances of the same block hit this without the author duplicating anything by hand.Ask: rewrite
url(#)/href="#"(and the matchingids) per composition on inline, or lint generic duplicate ids the way media ids are now covered. Rewrite is the one that makes nested catalog blocks work without unique-prefixing every SVG id.Link to reproduction
https://github.com/ArcadeHQ/hyperframes-repros/tree/patch/duplicate-svg-ids
Steps to reproduce
patch/duplicate-svg-ids.npx hyperframes@latest check— 0 errors.npx hyperframes@latest snapshot --at 1,3 --no-end --describe falsesnapshots/frame-00-at-1s.pngandsnapshots/frame-01-at-3s.png.Expected behavior
t=1s(scene-a): all four rows redt=3s(scene-b): all four rows blueActual behavior
t=1s: all four rows red (ok)t=3s: (1) control blue; (2) clip-path absent; (3)<use>red (scene-a's shape); (4) CSS filter red (scene-a's flood)Environment
Additional context
Related, not the same: #646 (scoped
getElementById), #3340 (media render keys). #556 was CSS attribute-selector scope across sibling instances.