| name | design-cover |
| description | Turn a design (Figma file or an exported reference deck) into a build ladder — inventory every frame, extract tokens and atoms, find the gaps the designer still owes you, and write a /build-ready plan plus the .claude/design-gate.json rungs that /visual-verify will check. Use BEFORE writing any UI code from a design. Not for features without a design. |
Skill: /design-cover
ORCHESTRATOR. You do not read the design yourself and you do not write UI code. You run
subagents, keep a deterministic checklist, and emit two artifacts: a plan /build can
execute, and the rung list /visual-verify can check.
Invocation
/design-cover "<figma-url-or-ref-dir>" [<slug>]
Why it is shaped like this
Building a screen straight from a design is where agents quietly hallucinate: they eyeball
a frame, write plausible CSS, and never check a single number. Three rules exist to stop
that, and they are the whole point of this skill:
- Cover before you build. You cannot know what is missing from a design until you
have enumerated all of it. The inventory is the checklist; "done" is the checklist
being full, not an agent's opinion that it looks complete.
- Bottom-up, and each rung is verified before the next is built. Tokens, then atoms,
then components made of atoms, then screens made of components. A screen diff is
useless as feedback — twenty things are wrong at once and you cannot tell which. An
atom diff names the one thing.
- Whoever reads the design does not get to grade the implementation.
design-reader
extracts. visual-critic re-extracts independently and diffs. One agent trusting its
own read is exactly the failure mode.
Steps
-
Slugify → <slug>. Create design-ref/ if it does not exist.
-
Get the design in. Two different reads, two different tools — do not confuse them.
(a) STRUCTURE — the numbers. Where padding, font size, hex, x/y/w/h come from.
| source | when | notes |
|---|
.fig decoded offline (pip install fig-kiwi) | the path | Ask the designer for File → Save local copy. Free, offline, no quota, no seat. Decodes tens of thousands of nodes in seconds, and carries prototypeInteractions — the transition type, duration, easing and spring params for every animation. |
| measuring exported PNGs | no .fig available | You lose variable names and get pixel measurements. Record the export scale in every passport. |
Figma MCP is not on this list on purpose. A View/Collab seat is capped at 6 MCP
calls and 6 REST calls per month — not per file, per month — so it cannot read a
design at all; it needs an interactive login, so it never runs in CI; and its answers
are a live read you cannot commit, which is the opposite of what step 2c needs. The
reference-pixel path below does not go through MCP either, so dropping it costs
nothing there. One thing to check before cutting it entirely on your project: confirm
the .fig decode gives you the designer's variable names, not just resolved values.
If it does not, that naming is the only thing MCP had that nothing else does.
(b) REFERENCE PIXELS — the image each rung is diffed against.
| source | fidelity | notes |
|---|
Figma Plugin API exportAsync in the desktop app | authoritative | Renders through Figma's own engine. Zero quota, and no Dev seat when the .fig opens as a local draft. Measured: opaque rungs reproduce at 0.0016–0.0081. Currently a manual few-click flow — that is fine per design revision, it is not per build. |
| a 1:1 export the designer sends you | authoritative | Same engine. Pin down the scale in writing. |
@grida/refig — local .fig → PNG renderer |
2b. Find the storyboard pages. A Figma page whose frames sit in a left-to-right
sequence — named transitions, hover, preloader, scenario, animatik — is a
keyframe animation spec, not loose art. Enumerate those pages too, and link each
sequence to the component it animates.
This is a measured failure, not a precaution: a build that read only the main page
shipped a menu that opened with a 1.25s spring and closed abruptly, because frame 12 of
a transitions page — the one saying close is the reverse of open, same timings — was
never read. Nothing in a resting-state screenshot can catch that.
2c. SERIALIZE the structure and commit it. This is the load-bearing step.
Project the decoded .fig into normalized JSON under design-ref/structure/, and put
it in git. The design stops being a live thing you re-read and becomes a versioned
artifact you own: git diff design-ref/structure/ is the design changelog. That is
what makes an incremental re-cover possible, and it is reviewable by a human in a pull
request — "the designer changed button padding 24 → 20 on three screens" is three lines
in a diff instead of a thing nobody notices.
design-ref/
structure/
index.json # frameId -> {namePath, sha256, file}
<frameId>.json # one normalized frame per file
assets/ # gitignored — extracted image/video fills
ASSETS.md # sha256 manifest of assets/, committed
Normalization is the whole job. A raw decoder dump diffs uselessly.
- One file per frame. Editing one frame must touch one file, not rewrite a 40 MB blob.
- Sort every key. Round every float to 2 decimals. Without this, a re-export diffs
on float noise and every frame looks changed.
- Drop volatile fields — session and user ids, timestamps, editor state.
- Binaries never go in git. An image or video fill becomes a sha256 plus a path into
the gitignored
assets/. (A real file carried 665 embedded video fills.)
- Serialize
prototypeInteractions too. The motion spec versions the same way the
layout does, and this is the only machine-readable form it has.
- Identity = node id, with the name path recorded beside it as the human label. A
rename or a move keeps the id; a frame the designer deleted and redrew gets a new one
and reads as delete + add. That is honest, not a bug — but verify on your own file
that ids survive a fresh
Save local copy. Nobody has confirmed that, and the whole
incremental scheme rests on it.
Two costs, stated plainly. The .fig format is officially internal and unstable —
Figma's own engineers say tools built on it will break repeatedly — so the decoder is a
maintenance liability, and a Figma update can break it with no warning. The committed
JSON is the hedge: when the decoder breaks you still hold the last known design state in
git, which is more than a live MCP read ever gave you.
- INVENTORY (deterministic). Enumerate every frame/screen. Write
design-ref/INVENTORY.md: one row per frame — id, name, size, and covered: no.
This file is the termination predicate for the whole skill. Nothing else decides "done".
3b. On a re-run, diff instead of re-reading. Re-serialize, then
git diff --name-only design-ref/structure/:
- changed file → that frame re-enters the loop
- new file → new frame,
covered: no
- deleted file → flag for human-confirmed removal. Never silently drop coverage.
- unchanged → skip entirely, keep its passport
Report the diff to the user before doing anything with it. "Six frames changed since the
last cover" is often the most useful sentence this skill produces.
-
PASSPORT each frame.
The numeric half is a projection of the committed JSON, generated by code — font
size, padding, hex, x/y/w/h, z-order. Do not have an agent retype numbers that are
already sitting in a file you own: it is slower, it costs money, and it is the one place
an LLM can silently invent a value that then survives review looking like evidence.
Spawn design-reader per frame (batched ≤6) for the part code cannot do: which frame
this is, which state it shows, what reads as one reusable component, and what the
design does not say. Prompt each:
Answer TERSE. Read .claude/agents/design-reader.md and follow it exactly.
Frame: <id>. Structure: design-ref/structure/<id>.json. Reference: <png>.
Write to design-ref/frames/<id>.md.
Where there is no .fig — PNG-only projects — the agent measures instead, and every
measured number is marked as measured, with the export scale.
-
EXTRACT the shared layer. From the passports, and only from them:
design-ref/DESIGN-TOKENS.md — the colour ramp, type scale, spacing scale, radii.
A value used in three or more frames is a token. A value used once is a one-off; say
so rather than promoting it.
design-ref/ATOMS.md — the inventory of reusable pieces (button, input, tag, avatar,
icon). Per atom: every state it appears in across the frames (default / hover /
pressed / disabled / loading / empty / error / overflow), and which frames it came
from. Missing a state that a screen obviously needs is a gap, not something for
you to invent.
- Fonts: name the actual family and weights, and whether you have the files. A
substituted font is a permanent, unfixable diff — flag it now, not at verify time.
-
GAP REPORT. Write design-ref/GAPS.md. A gap is anything the code will need and the
design does not have: an unstyled state, a missing breakpoint, a screen referenced by a
flow but not drawn, contradictory spacing between two frames using the "same" component,
text that overflows its box in the design itself.
Do not close a gap by inventing a design. Gaps go to the human, who takes them to
the designer. This report is the deliverable the designer actually wants.
-
LADDER. Order the work bottom-up and write it into .claude/design-gate.json
(create from .claude/design-gate.json.example if absent) as :
Loop guards
- Coverage is the
INVENTORY.md checklist, never an agent saying "looks complete".
- Cap at 2 passport attempts per frame. Still failing → mark the frame
covered: blocked
with the reason and move on. A stuck frame must not stall the other forty.
- Re-run on the same design:
git diff design-ref/structure/ decides what re-enters the
loop (step 3b). Never re-passport the whole file because it is easier.
design-ref/structure/ uncommitted or dirty when a re-run starts → say so and stop. A
diff against an uncommitted baseline is not a diff; it will report either everything or
nothing, and both are wrong.
- No
.fig and no exports → stop and ask for one. Never fabricate node ids or measurements.
What this skill does NOT do
- Does not write UI code — that is
/build.
- Does not run diffs — that is
/visual-verify.
- Does not invent missing design states to make the inventory look full.
- Does not push anything to Figma. Read-only, always.