npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

norma-scope

v0.10.1

Published

Compare your implementation screenshots against Figma designs with a zero-friction, local pre-commit report.

Readme

Normascope

Verify that what you shipped matches what you intended.

The intent can be a design file, yesterday's approved build, or the mock your AI agent was handed. Normascope captures your running UI, compares honestly, and puts the result where your team works — as a local, self-contained report today, and in your PRs next. No servers, no LLM, no blocking.

What people use it for

  1. Visual regression — catch unintended UI changes in every PR. Approve a known-good build as your baseline and every later run diffs against it. (Baseline mode ships in the current release cycle.)
  2. Design fidelity — ship what was designed. Compare your implementation against the design source of truth, with a diff tuned so anti-aliasing noise doesn't drown out real drift.
  3. Agent verification — give your coding agent eyes. Point Normascope at the mock your agent was handed and let it verify its own work. (MCP server ships in the current release cycle.)

Design sources

Normascope compares against reference images, wherever they come from:

  • Figma — frames exported straight from your file (available today; the flows documented below)
  • Image folders — any directory of reference PNGs, no design tool required (current release cycle)
  • URLs — capture a second environment as the reference, e.g. production vs staging (current release cycle)

Free and paid

The CLI — everything in this README — is Normascope: free, open, local-first, forever. Normascope Cloud is the upcoming paid tier for teams: hosted reports, trends over time, and PR workflow features. Nothing in the free CLI is ever gated.

Install

npx norma-scope init

Run this once per project. It walks you through connecting your design source and picking which frames to track.

For a practical guide to every command, scenario, prerequisite, generated file, and troubleshooting path, see USER-GUIDE.md. For the shorter command-by-command reference, see COMMANDS.md.

Two ways to use Normascope

Normascope has two distinct pathways. You can use the original manual screenshot flow, or the newer Auto flow that captures your running app for you. They share the same Figma config, screenshot folder, diff engine, and report.

Path 1 — Manual screenshots

Use this when you want full control over what gets captured, your app is not easy to run locally, or the UI state is easier to prepare by hand.

1. npx norma-scope init
2. Build your feature
3. Take a screenshot at the exact dimensions init printed
4. Drop it into .bridge/screenshots/
5. git commit
   → compare runs from the pre-commit hook
   → .bridge/reports/report.html is generated
6. Open the report and share if needed

Path 2 — Auto screenshots

Use this when your app is running locally or on a preview URL and you want Normascope to capture screenshots automatically.

1. npx norma-scope init
   → optionally enter app.baseUrl during setup
2. Add or refine per-frame route/selector fields in .bridge/config.json
3. Start your app
4. npx norma-scope check
   → auto captures screenshots
   → compare diffs them against Figma
   → .bridge/reports/report.html is generated
5. Open the report and share if needed

Frames without a route stay on the manual path. Frames with route/selector can use Auto. Both pathways can live in the same project at the same time.

The pre-commit hook installed by init never blocks a commit — it only generates the report. If your Figma token is missing or invalid, the hook skips the comparison and prints a warning, but your commit still goes through.

Screenshot naming convention

Screenshot filenames are the lowercased, hyphenated version of the matching Figma frame name. init tells you the exact filenames it expects:

Figma frame name     Screenshot filename
────────────────     ───────────────────
"Hero Section"    →  hero-section.png
"Primary Button"  →  primary-button.png
"Nav Bar"         →  nav-bar.png

Path 1 in detail: Manual screenshots

Normascope compares images pixel-for-pixel from the top-left corner — it doesn't understand scrolling or page sections. So your screenshot needs to be a full-page capture at the exact dimensions init showed you (e.g. 1440×10661px), not just what's visible in your browser viewport. A partial screenshot (like just the footer) will get compared against the top of the design and produce a meaningless, huge diff %.

Chrome / Edge (Mac and Windows — same steps, since this is a browser feature, not an OS one):

  1. Open the page and resize your browser window to match the frame's width (e.g. 1440px wide)
  2. Open DevTools (Cmd+Option+I on Mac, Ctrl+Shift+I on Windows)
  3. Open the Command Menu (Cmd+Shift+P on Mac, Ctrl+Shift+P on Windows)
  4. Type "screenshot" and choose Capture full size screenshot
  5. Chrome downloads a PNG of the entire page, full height, no scrolling needed

Firefox (Mac and Windows):

  1. Right-click anywhere on the page and choose Take Screenshot (or press Shift+F2, type screenshot --fullpage, then Enter)
  2. Choose Save full page

Browser extensions (any browser, if you prefer a permanent toolbar button):

Once you have the PNG, drop it into .bridge/screenshots/ with the exact filename init printed for that frame, then git commit as normal.

Path 2 in detail: Automatic screenshots

Instead of screenshotting by hand for every frame, Normascope can capture your running app for you:

npx norma-scope auto

auto opens your app in a headless Chromium (via Playwright), captures each configured frame, and saves the PNGs into .bridge/screenshots/ — the same place manual screenshots go. The existing compare pipeline then works on them without any special handling.

Manual screenshots still work exactly as before. Automatic capture is opt-in, per frame: frames without Auto config are simply skipped by auto and expected to be supplied manually.

To enable it:

  1. Start your app first. Normascope does not start dev servers — it assumes your app is already running (this is a V2.0 limitation).
  2. Add app.baseUrl and per-frame capture fields to .bridge/config.json:
{
  "figmaFileKey": "abc123XYZ",
  "threshold": 5,
  "app": {
    "baseUrl": "http://localhost:3000"
  },
  "frames": [
    {
      "label": "Hero Section",
      "screenshot": "hero-section.png",
      "figmaFrameId": "12:34",
      "width": 1440,
      "height": 900,
      "viewport": { "width": 1440, "height": 900 },
      "route": "/",
      "selector": "[data-norma='hero-section']"
    },
    {
      "label": "Pricing Page",
      "screenshot": "pricing-page.png",
      "figmaFrameId": "56:78",
      "route": "/pricing",
      "capture": "fullPage",
      "viewport": { "width": 1440, "height": 1200 }
    },
    {
      "label": "Manual Only Modal",
      "screenshot": "manual-only-modal.png",
      "figmaFrameId": "90:12"
    }
  ]
}
  1. Run npx norma-scope auto, then npx norma-scope compare — or both at once:
npx norma-scope check

Capture fields

| Field | Meaning | |---|---| | app.baseUrl | Where your app is running. Required for auto. Usually http://localhost:PORT, but a deployed/public URL also works for verification. | | route | Which page to open (baseUrl + route). No route = manual frame, skipped by auto. | | selector | CSS selector of the DOM element to capture. A dedicated attribute like data-norma="hero-section" makes this stable: "selector": "[data-norma='hero-section']". | | capture | "selector", "viewport", or "fullPage". Defaults to "selector" when a selector is set, otherwise "viewport". | | width / height | The Figma frame's dimensions — init fills these in automatically. | | viewport | Browser size for the capture. init defaults it to the Figma frame dimensions, so your capture and the design export are the same size — comparing images of different sizes is the most common source of meaningless, huge diff percentages. Selection order: viewportwidth/height → 1440×900 with a warning. | | waitForSelector | Optional: wait for this selector before capturing (app readiness). | | waitMs | Optional: extra fixed delay in ms — use sparingly, for animations. |

You still tell Normascope explicitly which route/selector maps to which Figma frame — it does not infer DOM ↔ Figma matching on its own. auto never blocks: unreachable app, missing selector, or missing app.baseUrl all just print a warning and exit 0.

During init, one optional question ("Is your app running locally or deployed for preview?") writes app.baseUrl and gives every selected frame a starter route: "/" — so the common setup path needs no hand-edited JSON. You only edit the config to fine-tune routes and selectors.

Checking your setup

npx norma-scope doctor

doctor validates everything before you capture or compare: config parses, Figma token is valid, the file and every configured frame ID exist, app.baseUrl is reachable, every route loads, every selector resolves, and a usable browser is available. It reports pass/warn/fail per check and exits non-zero only on hard failures — it diagnoses, it doesn't fix anything itself.

How comparison works (and what the numbers mean)

Normascope's diff is deterministic pixel comparison (pixelmatch) — it is not semantically intelligent. V2.1 tunes it so noise doesn't drown out signal:

  • Anti-aliasing-aware: anti-aliased pixel differences (subpixel text rendering, browser vs Figma rasterization) are detected and excluded from the mismatch percentage. The raw percentage (including AA) is available as a tooltip on the report's diff badge.
  • Significant regions: mismatched pixels are clustered into regions, and each component row shows a count (e.g. "3 significant regions"). Tiny isolated specks are ignored; a real layout shift shows up as one or more concrete regions.
  • Same-size captures: because auto captures at the Figma frame's own dimensions, the percentage reflects visual differences rather than canvas-size mismatch.

Browser requirement

Normascope uses playwright-core and does not download a browser on install. auto and doctor look for a browser in this order: installed Chrome → installed Edge → NORMA_BROWSER_PATH env var → Playwright's bundled Chromium. Most machines already have Chrome, so usually nothing is needed; if no browser is found:

npx playwright install chromium

Running manually

The pre-commit hook runs this for you automatically, but you can also run it by hand any time:

npx norma-scope compare

Add --fresh to bypass the local Figma cache and force a re-fetch — use this when your designer has pushed changes to a tracked frame:

npx norma-scope compare --fresh

Normascope caches Figma frame exports locally and by default never re-fetches them automatically (cacheMode: "aggressive"). This protects free-tier Figma tokens from quota exhaustion — the pre-commit hook fires on every commit, and a short cache window would burn through the API allowance fast. --fresh is the intentional trigger for picking up design changes.

To capture screenshots automatically and compare in one go (your app must already be running):

npx norma-scope check

Cleaning up

npx norma-scope clean

Empties .bridge/screenshots/, .bridge/diff/, .bridge/reports/, and .bridge/.cache/. Useful when switching to a different Figma file or reclaiming disk space — .bridge/config.json is never touched.

Note: clean wipes the Figma export cache, so the next compare will re-fetch from Figma. If you only want updated design exports without clearing everything, use compare --fresh instead.

Config

init writes .bridge/config.json, which is committed to your repo:

{
  "figmaFileKey": "abc123XYZ",
  "threshold": 5,
  "cacheMode": "aggressive",
  "frames": [
    {
      "label": "Hero Section",
      "screenshot": "hero-section.png",
      "figmaFrameId": "12:34"
    }
  ]
}

threshold is the mismatch percentage above which a component is flagged in the report — it's a visual flag, not a blocker.

cacheMode controls when Normascope re-fetches Figma frame exports:

| Value | TTL | Use when | |---|---|---| | aggressive (default) | No expiry | Free-tier tokens, stable design files. Zero API calls after first fetch. | | mid | 24 hours | Designers ship daily updates. | | normal | 4 hours | Fast-moving teams with frequent frame changes. |

compare --fresh always bypasses the cache regardless of mode.

Your Figma personal access token is stored in .env.local, which is never committed.

Report size

The report embeds downscaled JPEG thumbnails and stays self-contained; a typical run targets under 5MB (not a hard guarantee — very many or very tall frames can exceed it, and the terminal will say so). When everything fits, the lightbox gets full-resolution images automatically; otherwise it uses the thumbnails and the full-res PNGs remain in .bridge/screenshots/ and .bridge/diff/. To force full resolution into the report:

npx norma-scope compare --full

What's not committed

.bridge/screenshots/
.bridge/reports/
.bridge/diff/
.bridge/.cache/
.env.local

Only .bridge/config.json is committed — everything else is local to your machine.

Normascope is screenshot diffing — manual or automatic capture — local and free, deterministic, with no LLM involved. Reports are generated locally and never block commits or CI.

Want hosted reports, trends, and team workflow on top of this? That's Normascope Cloud, coming soon.

Command reference

| Command | What it does | |---|---| | npx norma-scope init | One-time setup: connect Figma file, pick frames, install pre-commit hook | | npx norma-scope doctor | Validate config, token, frame IDs, app URL, selectors, browser | | npx norma-scope auto | Capture screenshots from your running app | | npx norma-scope compare | Fetch Figma exports (from cache), diff, generate report | | npx norma-scope compare --fresh | Same, but re-fetch Figma exports (designer pushed changes) | | npx norma-scope compare --full | Same, but embed full-resolution images in the report | | npx norma-scope check | auto + compare in one command | | npx norma-scope clean | Wipe screenshots, diffs, reports, and Figma export cache | | npx norma-scope upload | Send the last run to Normascope Cloud. Paid, explicit, never automatic — see COMMANDS.md |

Day-to-day workflows

Manual screenshots (always available):

1. npx norma-scope init                    once per project
2. Build your feature
3. Take a full-page screenshot at the exact dimensions init printed
4. Drop it into .bridge/screenshots/hero-section.png
5. git commit
   → pre-commit hook fires
   → .bridge/reports/report.html generates
   → commit lands regardless of the diff result
6. Open report.html — share with your designer if needed

Automatic screenshots (optional, requires a running app):

1. npx norma-scope init                    once per project
2. Add app.baseUrl + route/selector to .bridge/config.json
3. Build your feature and start your app
4. npx norma-scope check
   → captures screenshots from the running app
   → diffs against Figma, generates report
5. Open report.html — share with your designer if needed

Frames without a route stay manual. Both workflows mix freely in one config.

Picking up a designer's changes:

npx norma-scope compare --fresh   # re-fetches Figma exports, re-diffs, new report

License

Apache-2.0 — see LICENSE and NOTICE. "Normascope" is a trademark; the license grants no rights to the name or logo.


Normascope is a product of the Norma family, Built and maintained by Harsha Attray.