Extract animation frame data from upstream Unicode spinner libraries into the wiki's runtime-agnostic `spinners-catalog.json`. Use when Kevin asks to "import spinners from X", "add more spinners", "pull the frames from this repo", evaluates a new spinner library, wants to expand the loading-screens catalog, or asks "should I swap unicode-animations for X?". Handles the full flow: 1v1 evaluation vs the canonical library, shallow clone, frame extraction from TSX/JS component files, JSON normalization, and wiki doc updates.
Kevin's canonical spinner library is unicode-animations (npm-installable, framework-agnostic, 18 braille spinners). Do not swap it out. Instead, when a new spinner library is interesting, extract the frame data into the wiki's runtime-agnostic catalog at wiki/assets/spinners-catalog.json and leave the canonical library untouched.
This keeps unicode-animations as the one-line npm install for any project, while giving every future project agent a palette of extra frames to adopt without taking on a new dependency (especially important when the upstream is React Native–only, CLI-only, or otherwise wrong-runtime).
Before any git clone, do the comparison. The answer is almost always "keep the canonical library, extract the frames into the catalog" — but the 1v1 forces an honest look. Check:
| Dimension | Why it matters |
|---|---|
| Target runtime | If it imports from react-native, ink, blessed, etc., it's the wrong stack for Kevin's Next.js web work. Never a library swap. |
| Distribution | npm-installable > copy-paste. Copy-paste libraries can only contribute frame data, not be adopted whole. |
| Spinner count & variety | Count per category (braille / ASCII / arrow / emoji). If it's < 5 new distinct frames, probably skip. |
| Frame data format | Are frames inline as const FRAMES = [...]? Good — extractable. If the frames are generated at runtime from shaders/canvas, skip — this skill doesn't apply. |
| License | MIT or compatible. Attribution is mandatory in the catalog JSON. |
| Stars / activity | Not decisive, but a rough health signal. |
Present the comparison as a table in chat. Then confirm with Kevin: extract all? a subset? which categories? where should it land (wiki-only is the default)?
Never pollute the repo with an upstream clone. Always /tmp and never commit.
mkdir -p /tmp/<repo-slug>
git clone --depth 1 <repo-url> /tmp/<repo-slug>Most spinner libraries use one of two layouts:
src/components/spinners/*.tsx, each with const FRAMES = [...] and const INTERVAL = N. (expo-agent-spinners, most React Native libraries.)src/spinners.ts with a big object literal mapping names to { frames, interval }. (unicode-animations, cli-spinners, ora.)Read one or two source files first to confirm the format before running the extraction script. If it's layout 2, the extraction is a single file read + JSON stringify. If it's layout 1, use the bundled extractor.
The extractor at scripts/extract-frames.mjs handles layout 1. Invoke it with the source directory and output path:
node skills/engineering/import-spinner-frames/scripts/extract-frames.mjs \
--src /tmp/<repo-slug>/src/components/spinners \
--out /Users/kevinliu/Documents/GitHub/kevin-wiki/wiki/assets/spinners-catalog.json \
--source-url <repo-url> \
--license MIT \
--attribution "frames derived from <owner>/<repo>"The script:
*.tsx / *.ts / *.jsx / *.js file in the source dirconst FRAMES = [...] (single- or multi-line) and const INTERVAL = NFunction(...) eval on the array literal (no expressions, just string lits){ source, license, attribution, extracted, count, spinners: {...} }{ category, frames, interval } — category defaults to "uncategorized" and gets set via the optional category map belowIf you know the library's category structure from its README, pass a category map as a JSON file:
--categories /tmp/categories.jsonWhere categories.json looks like:
{
"braille": ["dots", "dots2", "sand", ...],
"ascii": ["arc", "rolling_line", ...],
"arrow": ["arrow", "double_arrow"],
"emoji": ["moon", "earth", "hearts"]
}If omitted, everything lands in "uncategorized" and you can categorize later by hand in the JSON.
If --out already exists, the script reads it first and merges. Same-name spinners are overwritten (upstream is authoritative). A run summary is printed showing adds, updates, and skips.
Sanity-check the JSON before touching docs:
node -e 'const c = require("<path>/spinners-catalog.json");
const counts = {}; for (const s of Object.values(c.spinners)) counts[s.category] = (counts[s.category]||0)+1;
console.log("total:", c.count, "by category:", counts);
const pick = Object.keys(c.spinners)[0];
console.log("sample:", pick, "→", c.spinners[pick].frames.slice(0,8).join(" "));'Render a few frames in the terminal to confirm the Unicode didn't get mangled. If anything looks broken (question-mark boxes, escape sequences), the source file probably had a multi-character frame split across template literal expressions — re-read that source file and extract manually.
wiki/design/loading-screens.md is the canonical reference. Update:
updated: to today's date in frontmatteremoji, ascii) to the tags: listunicode-animations as the default at the top of the "Braille Spinners" section — do not let the expanded catalog displace the canonical recommendationThe JSON is the source of truth for frame data. The doc should not duplicate every frame — it curates and points agents at the JSON for the full set.
Also update wiki/skills/loading-screens.md if a new category appears (ASCII, arrow, emoji were the initial additions from expo-agent-spinners).
Append a [YYYY-MM-DD] asset-add entry to wiki/log.md explaining:
unicode-animations stays default. The catalog is a palette, not a replacement.The same extraction pattern works for any component library that inlines animation frame data: keyframe arrays, ASCII art sequences, easing tables, sprite sheet indices. If a second use case emerges (e.g., extracting color palettes, icon sets, sound libraries), generalize this skill at that point — don't pre-generalize now.