You finalize an SVG that's already been rendered and visually verified. You're the last step before the file leaves the repo. Your job: make it small, accessible, and well-named.
What was passed in
$ARGUMENTS[0] is the preset slug (must exist in output/).
$ARGUMENTS[1..N] may include --out=<path> to copy the final file
somewhere outside output/.
If no slug, return:
usage:
/svg-export <preset-slug> [--out=<path>]
If output/<slug>.svg doesn't exist, ask the caller to run
/svg-animate (or node lib/render-cli.js <slug>) first.
Step 1 — Verify the source
Read output/<slug>.svg. Sanity-check:
- Contains
<svgopening tag and</svg>closing tag. - Has a
viewBoxattribute (otherwise scaling is broken). - Has a
<title>element (presets emit it via composeSVG'stitleopt). If missing, fall back to the slug. Log a warning — presets should set title and desc explicitly.
If structural checks fail, return:
EXPORT FAILED: Source: output/.svg Re-render via
/svg-animateornode lib/render-cli.js <slug>.
Step 2 — Optimize with SVGO
The repo ships an animation-safe svgo.config.mjs at its root, which SVGO
picks up automatically. Just point at input + output:
npx svgo -i output/<slug>.svg -o output/<slug>.min.svg
DO NOT pass --enable/--disable (those are SVGO v2; this repo runs v3, where
they're config-only) and DO NOT run stock preset-default. Stock SVGO is built
for static icons and silently breaks animations: it collapses the group
nesting that carries animation transforms (relocating transform-box: fill-box
origins — e.g. a blink's eyes jump to the corner), minifies <style> (dropping
@keyframes), and converts shape elements that are animation targets. The repo
config disables exactly those plugins while still compressing path data. See
docs/embedding-animated-svg.md.
Capture before/after sizes. Typical savings: 35–40% on an animated preset. If savings are < 5%, the file is already small — that's fine, not a problem.
Always re-verify the minified file animates (Step 3) — a size win that broke the motion is a regression, not an optimization.
Step 3 — Verify minified file still composes AND animates
Parse check — the file must contain <svg and </svg> and be non-empty.
For an animated source, ALSO confirm the motion survived:
- The
<style>block and every@keyframesstill present (grep count matches the source). A 0 here means SVGO dropped the animation. - If the source has
transform-box, it's still there. - Ideally, render the minified file as an
<img>(seesvg-verify's<img>step) and confirm it still moves — not just that it parses.
If any check fails, abort and keep the unminified version as authoritative:
WARNING: SVGO altered the animation (e.g. @keyframes dropped / blink detached).
Keeping output/<slug>.svg as final. Check svgo.config.mjs is being applied.
Step 4 — Copy to destination (if requested)
If --out=<path> was passed:
- If the path ends in
.svg, copyoutput/<slug>.min.svgthere. - If the path is a directory, copy as
<dir>/<slug>.svg(NOT.min.svg— users want a clean filename at the destination). - Create parent directories if missing.
- Refuse to overwrite without explicit acknowledgement (
--force).
Step 5 — Report
EXPORTED: <slug>
Source: output/<slug>.svg (<bytes> bytes)
Minified: output/<slug>.min.svg (<bytes> bytes, <pct>% saved)
Destination: <path or "stayed in output/">
Accessibility: title="..." desc="..."
ViewBox: "<viewBox>"
Keep it under 8 lines. The caller will share this with the user.
Anti-patterns
- ❌ Stripping
viewBox,<title>, or<desc>. They're tiny and critical for accessibility / responsive scaling. SVGO's defaultremoveViewBoxpreset would strip viewBox; we disable it. - ❌ Overwriting an existing destination file without a
--forceflag. Users have lost work to that. - ❌ Running SVGO on a file we're not sure about. Always do Step 1's structural check first.
- ❌ Forgetting to copy to
--out=when the user asked. The whole point of this skill is shipping the file to where they want it.