Design fidelity
Skill hoiung/sst3-skills/plugins/design-fidelity/skills/design-fidelity
Screenshot a webpage, compare a local build against the live site, match the design, and pixel-diff drift. Visual-design-fidelity loop — headless full-page captures (desktop+mobile), live computed-CSS reads, and ImageMagick pixel-drift scoring — reusable in any repo.From its SKILL.md
npx -y skills add hoiung/sst3-skills --skill design-fidelityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
3 things to look at
- skips confirmationTells the agent to proceed without asking first, 1 time: "a per-page 3x loop runs its iterations without pausing to ask "should I continue?"".
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
- runs commandsInstructs the agent to run 4 commands, including `shoot.py` and 3 more.
SKILL.md
10.4 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it
Design-Fidelity — Visual Design-Fidelity Loop
Scope contract (Invariant): this skill ONLY shoots screenshots, diffs them, reads computed CSS, and SUGGESTS CSS changes for the operator to apply. It NEVER deploys, NEVER edits live-site config or DNS, and NEVER pushes to a live site. It reads; the operator decides and edits. Screenshot output is always written OUTSIDE any git repo so a capture can never be committed.
Related: drawing/annotating a schematic diagram (not matching a live page)? See
docs/guides/diagram-annotation-qa.md(../dotfiles/docs/guides/...from a consumer repo) — it reuses this skill'sshoot.pyfor the render-and-inspect step of the Hybrid diagram pipeline.
Hard dependency: the three helpers
scripts/shoot.py,scripts/compare_computed_style.py, andscripts/pixel-drift.sh; the two mechanics guidesreferences/playwright-fallback.md(capture + computed-CSS + pixel-diff command bodies) andreferences/chrome-devtools-mcp.md(MCP route); theplaywrightPython lib + its bundledchromium_headless_shell(per-repo.venvOR global python3 — the helpers fail loud with the install command if it is missing); and ImageMagickcompare(the pixel-diff engine — pixelmatch is NOT installed). The skill is available in every repo where its directory is present on the Claude Code skills path.
SST3 Anti-Patterns Governing This Skill
Respect these APs (full detail in ANTI-PATTERNS.md):
- AP #10 Duplicate Rules: the capture / computed-CSS / pixel-diff MECHANICS live in the two guides. This skill points at them — it does NOT restate the command bodies. Grep the guides before adding any mechanic doc here.
- AP #16 Monitor, Don't Fire-and-Forget: after every
shoot.py/compare_computed_style.py/pixel-drift.shrun, VERIFY the exit code and read the stderr telemetry (which wait-strategy fired, normalisation WxH,comparerc). "Ran the script" is not "got the shot". - AP #17 Keep Going Until Done: a per-page 3x loop runs its iterations without pausing to ask "should I continue?". Stop ONLY at the ~50%-remaining compact trigger (on long work, auto-
/handover+ compact + continue; never below 50% remaining), a destructive action needing consent, genuinely stuck, or task complete. - AP #18 Sample-Invocation: a real end-to-end shoot + compare + drift on one live page IS the Workflow/E2E-tier verification. Keep the per-repo
.design-fidelity.json+ the produced numbers as the evidence.
Operator Prerequisites
- playwright + chromium: per-repo
.venv/bin/python -m playwright install chromium(or global). WSL works out of the box with the bundledchromium_headless_shell— no Windows Chrome, no extra flags. - ImageMagick:
compare,convert,identifyon PATH. On a host whosepolicy.xmldenies the PNG coder,pixel-drift.sh's probe guard fails loud — re-allow the PNG coder. - A reachable base_url: the operator runs the local build server (e.g.
hugo server) OR supplies a live URL. The skill does not own the server lifecycle by default (an opt-inhugoblock exists for convenience). - A per-repo
.design-fidelity.json(optional): the page→path map, viewports, and out_dir for that repo. The sample values are an EXAMPLE, never a default.
When to Invoke
- "Screenshot this page / the whole site at desktop and mobile."
- "Compare my local build against the live site and tell me what's off."
- "Match the design of <live site> — measure how close we are and what to change."
- "Show me the pixel-diff drift between local and live for the home page."
- Greenfield capture-only (no live reference yet): just shoot the build for review.
Mandatory Reading on Invoke
references/playwright-fallback.md— the canonical capture + "Live computed-CSS reads" + "Pixel-diff scoring" mechanics (command bodies live HERE, not in this file).references/chrome-devtools-mcp.md— the MCP route + the browser-state security note.- The per-repo
.design-fidelity.jsonif it exists (page map / viewports / out_dir). - The screenshots output directory for the active repo under
~/DevProjects/screenshots/<repo>/(where captures and diffs land — OUTSIDE the repo).
The Per-Page 3x Iterate Loop
Work ONE page at a time. Each iteration runs this concrete dual-URL recipe:
- Shoot both sides at both viewports. Shoot the LOCAL build (
shoot.pywithbase_url= the local server) for the target page path at desktop (1440) + mobile (390), and shoot/cache the LIVE reference (shoot.pywithbase_url= the live base) for the same path at the same viewports. Capture local and live at identical viewport widths so the drift number is not inflated by stretch. - Measure the gap. Run
compare_computed_style.py --local-url <local> --live-url <live> --selector <target>on the selector(s) you are matching (exact spacing / colour / font deltas per viewport), AND runpixel-drift.sh <local.png> <live.png>on the PNG pair (a measurable AE + closeness% + a diff-highlight image). - Tweak and re-shoot. Apply the smallest CSS change the deltas point to, rebuild the local site, then re-shoot and re-measure.
Repeat at most 3 times per page. Watch the drift number trend DOWN and the computed-CSS deltas shrink toward zero. Then move to the next page.
Sub-Commands
/design-fidelity shoot— capture only (shoot.py). Works STANDALONE with no live reference (greenfield repo): just produces full-page desktop+mobile screenshots for review./design-fidelity compare— run the full per-page loop above against a live reference:shoot.py(local + live) →compare_computed_style.py→pixel-drift.sh. Fires ONLY when a live reference URL is supplied./design-fidelity diff— re-runpixel-drift.shon an existing local/live PNG pair to re-measure drift after a tweak.
Triple-Check Discipline
Maps to the Three-Tier Testing Framework (SST3
STANDARDS.md"Three-Tier Testing Framework").
- Unit Tier:
tests/run_unit_tests.shasserts each helper's contract (config-merge + parsers, the null-selector_errorpath, and the pixel-drift output binary) with no browser/live-site dependency. - Workflow Tier: a real
shoot.pycapture run against a reachablebase_url, exit 0, with the expected PNGs written outside the repo. - E2E Tier: a full shoot + compare + drift on one live page end-to-end, producing a real drift number + diff image — the AP #18 sample-invocation.
Security: chrome-devtools browser state
When you drive a LIVE site via the chrome-devtools MCP (the alternative to the headless playwright route), the MCP runs with full browser access — it can read open-tab cookies, auth tokens in request/response headers, pasted secrets in form fields, and cached credentials surfaced by the page (live browser state). Disable chrome-devtools via /plugin BEFORE pasting any secret. The DEFAULT route in this skill is headless playwright, which carries no live browser state, so prefer it. Full detail: references/chrome-devtools-mcp.md "Security note".
Anti-Overfit Constraint
The pixel-drift number is a TREND diagnostic — the operator watches it decrease across iterations; it is NEVER a hard pass-gate. The target is a STRUCTURAL match, not pixel-perfection (anti-aliasing, font-hinting, and dynamic content make pixel-perfect impossible and pointless). The page→path map, base_url, and viewport list are PER-REPO config — the proto values are an example, NOT a default.
File Locations
| File | Location |
|---|---|
| Capture helper | scripts/shoot.py |
| Computed-CSS delta helper | scripts/compare_computed_style.py |
| Pixel-drift helper | scripts/pixel-drift.sh |
| Unit tests | tests/run_unit_tests.sh |
| Capture / computed-CSS / pixel-diff mechanics | references/playwright-fallback.md |
| MCP route + browser-state security | references/chrome-devtools-mcp.md |
| Per-repo config | <consumer-repo>/.design-fidelity.json |
| Screenshot output (OUTSIDE any repo) | ~/DevProjects/screenshots/<repo>/ |
Skill Surface Contract
- Inputs: a
base_url(local build or live), a page→path map, viewports, and optional selectors. Via.design-fidelity.jsonand/or CLI overrides. - Outputs: full-page PNGs + diff-highlight PNGs under
~/DevProjects/screenshots/<repo>/; computed-CSS deltas + drift numbers on stdout; decision-branch telemetry on stderr. - Never writes into the consumer repo tree; never deploys; never edits the live site.
- Exit codes are load-bearing (AP #16): the helpers fail loud on a missing dependency, a blocked PNG coder, or a missing selector rather than producing a silent empty result.
- The
.design-fidelity.jsonconfig is treated as UNTRUSTED (it is repo-local, so a cloned repo could ship a hostile one).shoot.py: the opt-inhugo.cmdlauncher spawns NOTHING unless the operator passes the explicit--allow-hugo-launcherCLI flag (config alone can never start a process); when enabled it allowlists the executable to STATIC dev-servers only (hugo/jekyll/vite/serve/http-server — bare PATH names, no./hugopath component — and no interpreters ornpm run-style wrappers) and rejects inline-code/eval flags;base_urlis http(s)-only (nofile://); page labels + viewport names must be[A-Za-z0-9_-]+; viewport width/height must be ints in1..8192;scroll.stepmust be a positive int (settle clamped);out_dirmust resolve strictly UNDER$HOMEor the system temp dir. Each violation is a loud exit 2. - Residual trust the threat model deliberately does NOT remove — only pass
--allow-hugo-launcheron a repo you TRUST: (1)viteandjekyllexecute the repo's OWN build config (vite.config.{js,ts}, Jekyll_config.yml/_plugins) by design when they start, so launching them runs that repo's build code;hugo/serve/http-serverdo not. (2)base_url/page paths are scheme-restricted to http(s) but NOT host-filtered (the tool MUST reachhttp://127.0.0.1:<port>for local builds), so a hostile config can point captures at internal/loopback/cloud-metadata HTTP endpoints (e.g.169.254.169.254) — do not run an untrusted repo's config on a host with sensitive internal HTTP reachability.
What ships with it: 6 files
62.5 KB alongside SKILL.md, 4 of them executable
references/
- chrome-devtools-mcp.md7.2 KB
- playwright-fallback.md9.5 KB
scripts/
- compare_computed_style.pyruns8.9 KB
- pixel-drift.shruns6.1 KB
- shoot.pyruns27.2 KB
tests/
- run_unit_tests.shruns3.6 KB