Fig is a standard npm package with zero runtime dependencies. One reviewable install step wires it into your app; after that it never edits a tracked file again.
Pick whichever fits your setup — the first is recommended. Fig ships as the
figstate package.
# 1. As a dev-dependency of your app (resolves babel plugins + runtime from node_modules)
npm i -D figstate
npx figstate install # wraps babel.config → require("figstate/babel"); reversible
# 2. Global — install once, point it at each app
npm i -g figstate
figstate install --app-dir /path/to/app
# 3. No install — run straight from a checkout
npx /path/to/cli install --app-dir /path/to/app
figstate install is the one wiring step: it wraps your babel config with
withFigstate (env-gated, committable, and reversed byte-for-byte by
figstate uninstall). Verify the wiring any time with figstate doctor.
FIGSTATE_*) and lands in the
gitignored .figstate/ — a normal expo start / EAS build is byte-identical
whether or not Fig is installed.Fig is built for coding agents. Paste this into Claude (or drop it in your
CLAUDE.md) to have it download, install, and drive the loop:
# Fig — design-to-code, measured against the designer's intent.
# 1. Add Fig to the app and wire it in (one-time, reviewable, reversible):
npm i -D figstate && npx figstate install
npx figstate doctor # confirm babel wrap + env + tools
# 2. Reach the state a Figma frame depicts, then diff it:
npx figstate diff --slug home --figma-url "<frame-url>" --overlay
# 3. Read report.json. Fix whatever keeps parity_trustworthy false,
# hit the ABSOLUTE targets it gives you, and re-run step 2 until:
# { "parity_trustworthy": true, "coverage": { "match_score": 1.0 }, "warnings": [] }
The rules that make this work with an agent:
parity_trustworthy is true and warnings is empty — nothing weaker.Every Fig workflow is the same four beats:
Deep-link or drive the app to the exact composed state the frame shows — including interactive/active states a plain link can't reach.
Dump the app's real on-screen geometry (DOM on web, render tree on mobile) plus a screenshot.
The shared Python engine compares capture vs frame and emits a deterministic report.json.
The agent acts on the report's absolute targets and re-runs — until the gate is green.
Every diff emits the same report.json. These are the fields the loop turns on:
| Field | Meaning |
|---|---|
parity_trustworthy | The hard gate. true only when the schema is valid, the visual gate passes, everything is within point-tolerance, and warnings is empty. |
coverage.match_score | Symmetric overlap — accounted nodes ÷ (accounted + spare) across design and app. Presence/matching, not placement. |
warnings[] | Actionable codes: schema_invalid, visual_invalid, uncovered_design (design node with no app match), app_extra (app node with no design match). |
Generated from the toolchain source
(…) by website/scripts/gen-cli-docs.mjs — it can't drift from the code.