diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0c927d0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,14 @@ +name: CI + +on: + pull_request: + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - run: bun install --frozen-lockfile + - run: bun run typecheck + - run: bun test diff --git a/LICENSE b/LICENSE index b011ff6..6a7941f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 planetabhi +Copyright (c) 2026 Abhimanyu Rana @planetabhi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9313b94..dcd7d64 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ -# design-diff +# Design Diff -A CLI and library that overlays a design export on a screenshot of your page. Move a slider to compare them side by side, lower the opacity to see one on top of the other, or view a pixel diff to see exactly which pixels differ. +A coding agent can build a page and even look at it, but it cannot measure it against the design pixel for pixel. Design Diff does. It overlays the design on a screenshot of the live page at the same pixel size and returns one number for what matched plus the box around what did not. Call it, read the score, fix what the box points at, and repeat until the page matches. + +**It reduces to two values an agent acts on.** + +- **`matchPercent`** — how close the page is to the design. +- **`diffBounds`** — the box around what is still wrong. + +Read the first to know when to stop, aim the next edit at the second, and loop. Everything else in this README exists to make those two numbers trustworthy. + +Built agent-first. An agent or script drives it and reads back plain JSON. People get a slider and heatmap report to eyeball the same diff. And CI gets a hard pass/fail gate for merges. ## Usage @@ -20,7 +29,7 @@ bunx design-diff http://localhost:3000 --file --frame --scal --frame Figma frame to export (from the frame's URL). --actual Compare a local PNG instead of screenshotting the url. --scale Match the export: 1 for 1x, 2 for retina (default 1). ---threshold <0..1> Pixel-diff sensitivity (default 0.1). +--threshold <0..1> pixelmatch colour threshold, lower is stricter (default 0.1). --auth Playwright storageState for pages behind login. --out Where the report is written (default .design-diff). --ignore Rectangle (CSS px) to exclude from the diff. Repeatable. @@ -37,9 +46,9 @@ bunx design-diff http://localhost:3000 --file --frame --scal The design PNG sets the size, the page is screenshotted at the same pixel dimensions, so the two line up exactly. It waits for fonts and images to load and turns off animations before capturing. -With `--file/--frame`, it pulls the frame's size and PNG from the Figma API instead (needs `DESIGN_DIFF_FIGMA_TOKEN`). +With `--file/--frame`, it pulls the frame's size and PNG from the Figma API instead. Set `DESIGN_DIFF_FIGMA_TOKEN` to a Figma personal access token, created under Settings → Security → Personal access tokens. Read-only file access is enough, and view access to the file is all it needs. See [Figma's access token docs](https://www.figma.com/developers/api#access-tokens). -Use `--ignore` to mask dynamic regions (avatars, timestamps) so they never count as differences. Coordinates are CSS pixels, repeat the flag for multiple boxes. Prefer `--ignore-selector` when the region moves or resizes — every element matching the selector is masked by its live bounding box. +Use `--ignore` to mask dynamic regions (avatars, timestamps) so they never count as differences. Coordinates are CSS pixels, repeat the flag for multiple boxes. Prefer `--ignore-selector` when the region moves or resizes. Every element matching the selector is masked by its live bounding box. ```sh bunx design-diff http://localhost:3000 --png design.png \ @@ -60,7 +69,7 @@ Skip the browser entirely and diff two local PNGs with `--actual`. Handy offline bunx design-diff --actual screenshot.png --png design.png --json ``` -No page means no `--wait-for`, `--ignore-selector`, or `readiness`. `--scale` is ignored too — bounds are reported in image pixels. Everything else works the same. +No page means no `--wait-for`, `--ignore-selector`, or `readiness`. `--scale` is ignored too. Bounds are reported in image pixels. Everything else works the same. ## Pages behind login @@ -97,8 +106,9 @@ page coverage=100.0% | **Diff bounds** | The box enclosing every changed pixel, in CSS pixels. A tight box means one component is off, a box spanning the page points at a layout, viewport, or font problem. | | **Page coverage** | How much of the page that box spans. Tells a local issue from a global one. | +The shape of the diff is the diagnosis. A tight box is one component off, a box spanning the page points at a font that never loaded or a viewport mismatch. Read the shape, not just the number. -The same numbers are written to `metrics.json` in the run folder for scripting: +The same numbers are written to `metrics.json` in the run folder for scripting. ```json { @@ -115,7 +125,7 @@ The same numbers are written to `metrics.json` in the run folder for scripting: The overlay report draws the diff bounds as a box you can toggle on and off. Pass `--no-overlay` to skip the HTML report and heatmap when you only need the metrics. -Pass `--annotate` to also write `annotated.png` — the page screenshot with the diff box drawn on it. It needs no browser to view, so it drops straight into a PR comment or CI artifact. Nothing is written when there is no diff. +Pass `--annotate` to also write `annotated.png`. The page screenshot with the diff box drawn on it. It needs no browser to view, so it drops straight into a PR comment or CI artifact. Nothing is written when there is no diff. ## Scripting and CI @@ -127,7 +137,14 @@ bunx design-diff http://localhost:3000 --png design.png --json --fail-under 98 ``` `--fail-under` compares against **Visual match** (a percentage). It is separate -from `--threshold`, which is the per-pixel colour sensitivity. +from `--threshold`, which sets how different a single pixel must be to count as +changed. `--threshold` is passed straight to +[pixelmatch](https://github.com/mapbox/pixelmatch): a pixel is flagged when its +YIQ-weighted colour distance from the design exceeds that fraction of the maximum +possible distance, so it is a perceptual metric, not raw Euclidean RGB. Lower is +stricter, `0` flags any difference at all, and `0.1` is the default. + +For an agent the contract is simple. Read the JSON, act on `matchPercent` and `diffBounds`, run again, and stop when the score clears your bar. ## Programmatic API @@ -142,7 +159,8 @@ const result = await designDiff({ design: "design.png", // or { fileKey, frameId } scale: 1, threshold: 0.1, - ignore: [{ x: 24, y: 24, width: 48, height: 48 }], + ignore: [{ selector: "[data-dynamic]" }, { x: 24, y: 24, width: 48, height: 48 }], + waitFor: ".hero-loaded", }); if (result.matchPercent < 98) process.exit(1); @@ -164,6 +182,13 @@ try { } ``` +## Limitations + +- It compares pixels, not perception. Treat the score as a guide, not a verdict. +- Anti-aliasing and font rendering vary across machines, so a real page against a design export usually settles a point or two below 100 even when it looks right. Pick a threshold instead of chasing a perfect score. +- The design and the page must be the same size. A large mismatch is a hard error by design. +- A high score is necessary but not sufficient. A shifted component can still hide inside it, so keep the diff box and a human eye as the final check. + --- By [@planetabhi](https://planetabhi.com/) ⋛⋋( ⊙◊⊙)⋌⋚ \ No newline at end of file diff --git a/package.json b/package.json index c1eb21e..32bacb9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "design-diff", "version": "0.0.4", - "description": "CLI and library for overlaying design exports or Figma frames on live pages, with a before/after slider and pixel-diff heatmap.", + "description": "Give agents and humans a precise, machine-readable way to measure how closely a live web page matches a design.", "keywords": [ "design-diff", "visual-regression",