Skip to content
User guide

The page to come back to when you’re stuck.

What each command does, what must happen before and after it, which workflow to choose, and what to check when a run does not behave as expected.

The shortest path

1npx norma-scope init
2npx norma-scope doctor
3npx norma-scope check
4open .bridge/reports/report.html

This is the default path. The rest of the guide helps you choose a different one when your project needs it.

1 · The core idea

Normascope compares a screenshot of your running UI with a reference image.

The reference may be:

A Figma frame

design fidelity

An approved browser capture

visual regression, or baseline mode

A local PNG directory

no design service at all

A second URL or environment

staging against production

The comparison is deterministic and local. It produces a diff image, a self-contained HTML report, and optionally machine-readable JSON. AI explanation is a separate, opt-in step.

your UI
  │
  ├─ manual screenshot ───────┐
  └─ `auto` / `check` capture ─┤
                               ▼
                    `compare` against reference
                               │
                    diff + report + optional JSON
                               │
                    optional `explain` (AI hypothesis)

Swipe the diagram sideways to see the rest.

The free local path never requires Normascope Cloud. It does not upload screenshots, reports, source files, or API keys.

2 · Which workflow should I use?

Find your situation. Start there.

  • First-time project setup

    npx norma-scope init

    Reference · Figma or configured source

  • I want to take screenshots myself

    init, then compare

    Reference · Figma, images, or URL

  • My app is already running and routes are configured

    doctor, then check

    Reference · Figma or configured source

  • I want to compare two browser builds

    configure URL source, then auto + compare

    Reference · second URL

  • I want to catch regressions after approving a good build

    baseline, then compare

    Reference · committed baseline

  • I want design exports committed and CI to work without Figma calls

    snapshot, then compare

    Reference · committed snapshot

  • I need a CI or PR result

    compare --json, then comment

    Reference · configured source

  • I need to understand why a frame differs

    explain

    Reference · existing comparison

  • I changed the design and need fresh Figma exports

    compare --fresh

    Reference · Figma

  • My local generated files are stale or confusing

    clean

    Reference · none

3 · One-time setup: init

Run this once, in the project whose UI you want to verify.

npx norma-scope init

Before

  1. 1

    Have the Figma file URL and a Figma personal access token ready, if you are using Figma.

  2. 2

    Run the command from the project root — the directory containing the app source.

  3. 3

    If you want automatic capture, know the URL where the app will run. The app does not need to be running during init.

During

init asks for the design source and frames to track. It creates .bridge/config.json, records frame dimensions, creates local output directories, adds generated paths to .gitignore, and installs a non-blocking pre-commit hook.

If you enter an app URL, init creates starter routes. Treat those routes as placeholders: edit them to match the actual pages and selectors in your app.

After

Inspect .bridge/config.json. A typical automatic frame looks like this:

{
  "label": "Pricing page",
  "screenshot": "pricing-page.png",
  "figmaFrameId": "123:456",
  "width": 1440,
  "height": 2400,
  "route": "/pricing",
  "capture": "fullPage"
}

Commit .bridge/config.json. Do not commit .env.local, screenshots, reports, diffs or cache files unless your team explicitly chooses to.

Important warning

Running init again replaces .bridge/config.json. It does not merge frame selections. Old generated screenshots are not automatically deleted.

Run npx norma-scope clean before recapturing a completely different project or design file.

4 · Validate setup: doctor

Run this before debugging capture or comparison.

npx norma-scope doctor

It checks configuration, Figma access, frame IDs, app URL reachability, browser availability, routes, selectors and dimensions. It diagnoses; it does not modify your files.

Before you run it: complete init, edit routes and selectors, and start the app if automatic capture is configured.

Pass

The check is usable.

Warn

The workflow may still run, but inspect the message.

Fail

Fix this before interpreting visual results.

app.baseUrl is unreachable

Start the app or correct the URL. Do not try to solve this by changing the diff threshold.

selector not found on /pricing

Open the page and verify the selector, or remove the selector and use viewport or fullPage capture.

5 · Manual screenshot workflow

Use this when you need exact control over the captured state.

The app needs a special logged-in state, a modal must be opened by hand, or the route is difficult to automate.

  1. 1

    npx norma-scope init

  2. 2

    Build or change the UI

  3. 3

    Open the page at the design frame's width

  4. 4

    Capture the full page or exact target region

  5. 5

    Save it as .bridge/screenshots/<expected-name>.png

  6. 6

    npx norma-scope compare

  7. 7

    Open .bridge/reports/report.html

The filename must exactly match the value in .bridge/config.json, for example pricing-page.png.

Capture at the dimensions printed by init. A Retina 2x screenshot, or a viewport with the wrong width, can create a misleadingly large diff.

For a full-page browser screenshot, use the browser’s “Capture full size screenshot” command. A viewport-only image is appropriate only when the frame is intended to represent the viewport.

Manual and automatic frames can coexist in the same project. auto skips frames without routes; compare then uses the manually supplied PNG for those frames.

6 · Automatic capture: auto

auto captures configured routes from an already-running app.

npx norma-scope auto

Before

  1. 1

    Ensure .bridge/config.json has app.baseUrl.

  2. 2

    Give each automatic frame a route.

  3. 3

    Add selector, capture, viewport, waitForSelector or waitMs only when needed.

  4. 4

    Start the app yourself. Normascope does not start the development server.

Example

{
  "app": { "baseUrl": "http://localhost:3000" },
  "frames": [
    {
      "label": "Dashboard",
      "screenshot": "dashboard.png",
      "figmaFrameId": "123:456",
      "width": 1440,
      "height": 1800,
      "route": "/dashboard",
      "capture": "fullPage",
      "waitForSelector": "[data-testid='dashboard-ready']"
    }
  ]
}

What happens

Normascope launches a headless browser, navigates to each route, waits for practical page settling, waits for configured readiness conditions, captures at 1x scale, and writes PNGs to .bridge/screenshots/.

Capture modes

selector

captures the first matching element

viewport

captures the visible browser viewport

fullPage

captures the entire page

If a frame has no route, it is reported as “manual screenshot expected”. If a route or selector fails, that frame is skipped with a warning. Always inspect the output: a successful process does not mean every frame was captured.

7 · Capture and compare together: check

This is the normal automatic development loop.

npx norma-scope check

It is exactly:

run `auto`
then run `compare`

It captures fresh screenshots first, then compares them against the configured reference and regenerates the report.

Before

  1. 1

    Start the app.

  2. 2

    Run doctor at least once after changing routes or selectors.

  3. 3

    Confirm that protected pages can load in the capture environment. Normascope does not invent a user session.

After

.bridge/reports/report.html
.bridge/diff/<frame-name>-diff.png

Use the report to decide whether a difference is an intended change, a real regression, or a capture problem. Use --strict when the command should fail on measured regressions.

npx norma-scope check --strict

Skipped frames and setup problems do not become measured regressions. For CI, pair strict comparison with doctor, or another check that ensures required frames were captured. check does not call an LLM and does not upload anything to Cloud.

8 · Compare existing screenshots

Use compare when screenshots already exist.

That includes screenshots produced manually, or by a separate build system. It loads screenshots from .bridge/screenshots/, obtains references according to source, runs the diff, writes diff images and generates report.html.

Common variants

# Fetch updated Figma exports instead of using cache
npx norma-scope compare --fresh

# Include full-resolution images in the HTML report
npx norma-scope compare --full

# Write machine-readable output for CI or integrations
npx norma-scope compare --json

# Exit 1 when a compared frame exceeds threshold
npx norma-scope compare --strict

# Combine flags
npx norma-scope compare --fresh --json --strict

The default cache is deliberately conservative, to protect Figma API quotas. Use --fresh when the designer has changed a tracked frame and you need the new export. Do not use clean merely to refresh Figma data: clean also removes local screenshots and reports.

Interpreting results

  • Lower aligned mismatch percentage is better.
  • SSIM and significant regions provide additional context.
  • A dimension warning usually means an incorrect capture size, scaling, or capture mode.
  • A skipped frame is not a passing comparison. Investigate its skip reason.
  • A difference is evidence, not a decision. Confirm whether the change was intended.

Every number in the report is explained on the report guide.

9 · Design source choices

The source block in .bridge/config.json determines the reference.

Figma

{ "source": { "type": "figma" } }

The reference is a design frame.

Images

{ "source": { "type": "images", "dir": "test/visual-reference" } }

Use this when there is no Figma dependency.

URL

{ "source": { "type": "url", "baseUrl": "https://production.example.com" } }

Use this for staging against production, or preview against production.

The URL must be reachable from the machine running Normascope, and should be explicitly configured before capture.

Important example · compare localhost with live production

Your local product page against the live one.

This example compares the local product page at http://localhost:3000/products/red-shoes with the live product page at https://shop.example.com/products/red-shoes. The local page is the version being tested. Production is the reference. Normascope captures both pages in the same run and compares the screenshots.

Step 1 · configure both websites

In .bridge/config.json, use:

{
  "source": {
    "type": "url",
    "baseUrl": "https://shop.example.com"
  },
  "app": {
    "baseUrl": "http://localhost:3000"
  },
  "frames": [
    {
      "label": "Product page",
      "screenshot": "product-page.png",
      "route": "/products/red-shoes",
      "capture": "fullPage",
      "viewport": {
        "width": 1440,
        "height": 900
      }
    }
  ]
}

The route is shared by both websites. Normascope builds these two URLs:

local capture:     http://localhost:3000/products/red-shoes
reference capture: https://shop.example.com/products/red-shoes

The explicit viewport makes both browser pages use the same 1440 by 900 window. fullPage then captures the entire page. Do not use the page’s full document height as the browser viewport; that can change how 100vh layouts render.

Step 2 · start the local website

In terminal 1:

cd ~/projects/storefront
npm run dev

Before continuing, open http://localhost:3000/products/red-shoes in your own browser. If the page does not work there, Normascope cannot capture it either.

Step 3 · validate both websites

In terminal 2:

npx norma-scope doctor

Doctor should confirm that the local app and the production source are reachable. If production is behind a login screen, it may be technically reachable but still unsuitable for a meaningful comparison.

Step 4 · capture and compare

npx norma-scope check

What happens:

  1. 1.auto opens the local baseUrl plus the route.
  2. 2.auto saves the local screenshot as .bridge/screenshots/product-page.png.
  3. 3.compare opens the source baseUrl plus the same route.
  4. 4.compare captures production as the reference image.
  5. 5.compare diffs local against production.
  6. 6.compare writes .bridge/diff/product-page-diff.png and .bridge/reports/report.html.
open .bridge/reports/report.html

Read the result as: “how different is my local product page from the live production product page?”

Step 5 · check differences before blaming CSS

  1. 1

    Open both URLs yourself.

  2. 2

    Check that the same product, price, images, and availability are displayed.

  3. 3

    Check that the same cookie banner, navigation, and logged-in state are visible.

  4. 4

    Check that both pages finished loading before capture.

  5. 5

    Only then decide whether the difference is a CSS or layout regression.

A different product price is a data difference, not necessarily a visual implementation bug. A missing Add to cart button on localhost is likely a real local regression if both pages have the same data.

Authentication and private pages

The current URL capture flow does not automatically log into either site, and does not transfer browser cookies. This example works best when the product page is public, both environments show stable test data, and neither page has personalised content. For private pages, use a deterministic test route or a manually captured screenshot. Do not put passwords, session cookies or tokens in .bridge/config.json.

Compare another product

Change only the route:

"route": "/products/blue-jacket"

Then run:

npx norma-scope check

Both localhost and production must expose the same route. Otherwise the two screenshots are not equivalent pages.

10 · Baseline regression workflow

When the browser output itself is the source of truth.

  1. 1

    Configure the frame with "mode": "baseline".

  2. 2

    Capture the known-good build manually or with auto.

  3. 3

    Run npx norma-scope baseline.

  4. 4

    Review the committed baseline files.

  5. 5

    On later changes, run npx norma-scope compare.

baseline approves the current capture into .bridge/baseline/. Future comparisons for that frame use this browser capture instead of Figma. Approve only after reviewing the report. Baselines should be captured consistently on the same operating system and browser family; otherwise font rasterization can create noise.

11 · Committed design snapshots

When CI should run without contacting Figma.

npx norma-scope snapshot
git add .bridge/design
git commit -m "Record design snapshot"

Then compare normally. To check whether the live Figma file has moved on:

npx norma-scope snapshot --check

This is an informational drift check. Review and intentionally regenerate the snapshot when the design change is accepted.

12 · CI and pull-request workflow

Compare first. Comment second. The order matters.

npx norma-scope compare --json --strict
npx norma-scope comment

compare --json writes .bridge/reports/summary.json. comment renders markdown from the most recent summary; it does not run a new comparison.

The GitHub Action can run the comparison and publish the report and artifacts.

CI must have the required design token unless you use committed snapshots or local images.
Automatic capture requires a running preview server and routes that CI can reach.
If CI needs a hard failure, use --strict. The default pre-commit flow is intentionally non-blocking.
A skipped frame should be treated as a coverage problem even if the process exits successfully.
13 · AI explanations: explain

Optional, and separate from visual scoring.

Before

  1. 1

    Run compare first, so a current comparison exists.

  2. 2

    Enable explanation in .bridge/config.json.

  3. 3

    Install the optional SDK if required.

  4. 4

    Set ANTHROPIC_API_KEY in the environment. Never put it in the config and never commit it.

  5. 5

    Review SECURITY-LLM.md before sending project context to an external provider.

Useful variants

npx norma-scope explain pricing-page.png
npx norma-scope explain --all
npx norma-scope explain --deep

explain analyses flagged frames by default. It produces hypotheses about likely causes and saves findings to .bridge/reports/findings.json. It does not change code, does not determine the deterministic score, and does not automatically apply fixes. Verify every finding.

A secret scanner runs before network calls. Treat any AI explanation as a review aid, not as permission to upload private application data without checking the configured scope.

14 · MCP and coding-agent flow

The safe agent loop.

The optional MCP package exposes local tools such as list_frames, capture, compare, summary and explain to a coding agent.

implement change
  → capture/compare current app
  → inspect score and drifted regions
  → revise implementation
  → compare again

The MCP compare tool recaptures by default, so the score reflects the current code. A caller may disable capture only when it intends to score screenshots already on disk. Capture URLs are default-deny and must be configured. Page text returned by tools is data, not instructions.

15 · Cleanup: clean

Remove generated local artifacts.

npx norma-scope clean

This empties screenshots, diffs, reports and cache directories. It never changes .bridge/config.json.

Use it when switching projects or design files, or when generated output is stale. Do not use it just to refresh a Figma export: use compare --fresh, which refreshes and re-caches the export without deleting your captures.

16 · Common stuck states

Debug the capture before you debug the CSS.

“No app.baseUrl configured”

You ran auto or check without an automatic capture URL. Add app.baseUrl, or use the manual screenshot workflow.

“No screenshot found”

The frame has no PNG at the exact configured path. Either start the app and run auto, or place a correctly named PNG in .bridge/screenshots/.

Every frame is skipped by auto

Check that each intended frame has a route. Frames without routes are intentionally manual. Then run doctor.

The diff percentage is unexpectedly huge

Check, in order: the filename matches the config; the width and scale match the design; you captured the full page when the reference is full page; the correct route, authentication state, fonts and data loaded; the Figma cache is current; the selector or viewport is the intended region. Do not lower the threshold until capture dimensions and state are correct.

compare says the design is unavailable

Run doctor. Check the token, file and frame access, network, and cache. If the design was previously cached, the comparison may still be able to use it. For stable CI, use snapshot or an image source.

explain does nothing or reports no frames

Run a new compare, confirm at least one frame was compared, enable explanation, install the optional SDK, and export ANTHROPIC_API_KEY. Use explain --all to analyse non-flagged frames deliberately.

The report is too large

Open the normal report first; it uses thumbnails. Use compare --full only when full-resolution embedding is worth the larger file. The full-resolution PNGs stay in the local output directories.

I expected a non-zero exit but got zero

Most commands are intentionally diagnostic and non-blocking. Use --strict for measured comparison regressions. Skipped frames are not measured regressions, so enforce capture coverage separately in CI.

17 · Beginner cookbook

Copy these examples.

This section assumes you have never used Normascope before. Copy the commands exactly, then replace only the paths, URLs and page names that belong to your project.

Example A

First setup for a pricing page

You have a web app in a folder called storefront. Your app runs on port 3000 and the page you care about is /pricing.

cd ~/projects/storefront
npx norma-scope init

During the prompts, choose the Figma frame named Pricing Page and enter http://localhost:3000. Then open .bridge/config.json and make sure the frame contains “route”: “/pricing” and “capture”: “fullPage”.

npm run dev
npx norma-scope doctor

If doctor reports that the route is reachable, the project is ready for the next example.

Example B

Automatic screenshot and compare

You changed the pricing page and want Normascope to capture it and compare it with Figma. Keep the app running in one terminal and run the check in another.

npm run dev

npx norma-scope check
open .bridge/reports/report.html

The report should show the current pricing screenshot, the Figma reference, the diff, and the mismatch score. If no screenshot appears, check that the route is /pricing and that the app is still running.

Example C

Manual screenshot of a login modal

The login modal appears only after a human clicks Sign in, so automatic capture is not suitable.

  1. 1.Open the login page in your browser.
  2. 2.Click Sign in until the modal is visible.
  3. 3.Set the browser width to the width printed by init.
  4. 4.Use the browser command Capture full size screenshot.
  5. 5.Rename the downloaded file to login-modal.png.
  6. 6.Move it into .bridge/screenshots/.
npx norma-scope compare
open .bridge/reports/report.html

If the report says no screenshot found, the filename is probably different from the name in .bridge/config.json. Rename the file; do not invent a new name.

Example D

Only compare a screenshot that already exists

You already have dashboard.png in .bridge/screenshots/ and do not need a new browser capture.

npx norma-scope compare

Do not run auto first. Compare reads the screenshot already on disk.

Example E

The designer changed Figma

You compared the page yesterday, but the designer changed the Figma hero section today.

npx norma-scope compare --fresh

Fresh means “download the current Figma export now”. The ordinary compare command may use the cached export from yesterday.

Example F

Fail a pull request when the UI regresses

Run the check in strict mode.

npx norma-scope check --strict

Exit code 0 means every compared frame stayed within the threshold. Exit code 1 means at least one compared frame exceeded it. If the command succeeds but you expected a failure, look for skipped frames. A skipped frame was not actually tested.

Example G

Make a known-good browser page a regression baseline

You have approved the current checkout page and want future changes checked against this exact browser rendering. Add mode baseline to the frame in .bridge/config.json, then:

npx norma-scope auto
npx norma-scope baseline

# after future code changes
npx norma-scope check --strict

Commit the generated baseline files. If a change is intentional, review the report and run baseline again to approve the new version. Do not approve a baseline before looking at the report.

Example H

Compare staging with production

Production is the reference. Staging is the version you want to test. In .bridge/config.json, use:

"source": {
  "type": "url",
  "baseUrl": "https://www.example.com"
},
"app": {
  "baseUrl": "https://staging.example.com"
}
npx norma-scope doctor
npx norma-scope check

Both sites must show comparable content. If production has five products and staging has two, the diff may be caused by different data, not different CSS.

Example I

Create a report for a PR

Run the comparison and create machine-readable output, then create the PR comment.

npx norma-scope compare --json --strict
npx norma-scope comment > pr-comment.md

The order matters. comment reads the summary created by compare --json; it does not perform a comparison itself.

Example J

Ask AI why one frame differs

First create a comparison, then explain one frame.

npx norma-scope compare
npx norma-scope explain pricing-page.png

The result is a suggestion such as “the heading may have moved because the font size changed”. Check the CSS yourself. To analyse every frame use explain --all; to use the deeper model use explain --deep. Both can make more provider calls, so use them deliberately.

Example K

Save design references for CI

Run this on a machine that has Figma access.

npx norma-scope snapshot
git add .bridge/design
git commit -m "Add visual design snapshot"

After that, CI can compare against the committed design export without needing a Figma token on every run.

Example L

Remove stale generated files

You switched to another Figma file and old screenshots are confusing you.

npx norma-scope clean
npx norma-scope auto
npx norma-scope compare

Clean removes generated screenshots, diffs, reports and cache. It does not remove .bridge/config.json. If you only want a new Figma export, use compare --fresh instead.

Example M

Use a coding agent with MCP

After starting your app, ask the coding agent to compare the configured page after making a UI change. The safe loop is:

change code
compare current page
read the diff regions
fix code
compare again

If capture is refused, configure the exact allowed app origin. Do not disable the origin policy just because an agent asks.

18 · Visual flowcharts

The same journeys, drawn.

These diagrams summarise the main user journeys. When a command changes one of these journeys, the diagram changes with it.

Choosing a workflow

What to run, and in what order, the first time and every time after.

First use in this project?Run initRun doctorIs the app already running?Capture manuallyRun checkRun compareReview report and diffsNeed likely causes?Run explainFix, approve, or shareYesNoNo, or special UI stateYes, routes configuredYesNo

Swipe the diagram sideways to see the rest. The caption above describes the whole journey.

Automatic capture and comparison

How a running page becomes a PNG, and how that PNG becomes a report.

Start the appRead .bridge/config.jsonbaseUrl and route configured?Report manualscreenshot expectedOpen route in headless browserWait for page, selector, or waitMsCapture modeCapture matching elementCapture browser viewportCapture full pageWrite PNG to .bridge/screenshotsCompare available screenshotsLoad Figma, baseline, image,or URL referenceDiff and generate reportNoYesselectorviewportfullPage

Swipe the diagram sideways to see the rest. The caption above describes the whole journey.

auto, compare and check

auto creates screenshots. compare scores screenshots already on disk. check runs both.

checkautocompareCreate or refresh screenshotsScore screenshotsalready on diskexplainAnalyze an existing comparisonGenerate hypotheses;never change code

Swipe the diagram sideways to see the rest. The caption above describes the whole journey.

CI and pull requests

Where the job fails, and where the comment comes from.

Push branch or open PRStart preview app if neededCapture required framescompare --jsonstrict regression measured?Fail comparison jobWrite summary.jsonOpen report and fix orapprove intentionallyRun commentPublish PR commentand report artifactReview resultYesNo

Swipe the diagram sideways to see the rest. The caption above describes the whole journey.

Optional AI explanation

What has to be true before anything leaves your machine.

Run compare firstEnable explanation andcode pointersSet ANTHROPIC_API_KEYin the environmentRun explainScan outbound context for secretsBlock that frameand explain whySend permitted crops,metadata, and excerptsSave findings.json andshow hypothesesHuman verifies beforechanging codeSecret foundSafe

Swipe the diagram sideways to see the rest. The caption above describes the whole journey.

19 · A complete daily example

The whole workflow, in one place.

# Once per project
npx norma-scope init
npx norma-scope doctor

# Every feature change
npm run dev
npx norma-scope check
open .bridge/reports/report.html

# If the designer changed the Figma frame
npx norma-scope compare --fresh

# If the result is difficult to diagnose
npx norma-scope explain pricing-page.png

# Before opening a PR
npx norma-scope compare --json --strict
npx norma-scope comment
Normascope terminal output showing a baseline workflow
The CLI is intentionally explicit: it tells you what it is checking and where the result lives.

Start with one page.

Pick a page your team knows well, learn the report, then expand the workflow to the rest of your app.

npx norma-scope init

Want the visual explanation first? Read How It Works · Every number in the output: the report guide · Every command and flag: Commands