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

tuffgal

v0.3.1-alpha.1

Published

JSON-driven visual regression for web apps.

Readme

Tuffgal

License: MIT

JSON-driven visual regression testing for web apps.

Status: Pre-1.0. Published on npm as [email protected].{n} with provenance. Linklater is the pilot consumer.

Public API is unstable until v1.0.0.

The idea

Tuffgal sits between component tests (which are fast but mocked) and end-to-end tests (which are real but verbose). You write actions (atomic user steps) and stories (chains of actions) as pure JSON. The harness runs them in a real browser, captures a screenshot after each action, and pixel-diffs against a committed baseline.

When a screenshot changes, a human reviews the diff and decides what to do.

CI owns the baselines

Committed baselines are the source of truth for what your UI is supposed to look like, and CI is their only writer. That makes a visual change a pull-request review gate, exactly like a code change: CI renders the current UI, diffs it against the committed baselines, and if anything drifted it fails the check and publishes the new screenshots as an artifact. A human looks at the diff and, if the change is intended, promotes those screenshots into the committed set. No local run can overwrite the baselines your reviewers rely on.

A tuffgal run executes under one of two modes:

  • CI mode compares against the committed baselines (paths.baselines) and never writes them. A missing baseline is new, an orphaned one is deleted, and drift is changed, all of which are written into a self-contained candidate tree under <report>/candidates/ for approval, not committed in place. CI mode gates: pending changes exit 2.
  • Local mode is advisory. It compares against a gitignored, per-machine cache (paths.localCache) so you can self-diff while you iterate, seeding a missing entry on first sight. A local run never reads or writes the committed baselines, and never fails your build on visual drift. It surfaces the signal in the report and on stdout and exits 0. Only a failed story (a step that threw) exits 1; the CI-gating codes 2 and 3 never fire locally.

Mode resolves from --ci / --local (an explicit flag always wins). With neither, Tuffgal picks CI when $CI is truthy and local otherwise, so the same tuffgal run does the right thing on a laptop and in a workflow.

Approving a visual change

Two ways to accept a changed/new candidate into the committed baselines, both writing through the single approve --from promotion path:

  • Download and commit. Grab the candidates artifact CI uploaded, run tuffgal approve --from <dir> against it, and commit the resulting baselines. --from refuses any candidate that is not a clean CI run (mode !== 'ci' or a failed story), so a local or broken run can never be promoted. Add --prune to also retire baselines the run flagged as deleted.
  • Comment on the PR. The intended flow is a @tuffgal approve PR comment that runs the same promotion on the CI side. The Action that handles this is a downstream, not-yet-shipped repo, so treat this as the direction of travel rather than a shipped button today.

Plain tuffgal approve (no --from) targets your local cache only. It accepts your own self-diff so your next local run is clean. It does not touch the committed set.

The environment manifest

approve --from also writes baselines/manifest.json recording the environment the promoted baselines were captured under. The keys compared for drift are capture schema, browser version, platform, capture mode, color scheme, breakpoints, device scale factor, and frozen time. On the next run --ci Tuffgal checks the live capture environment against it. A pixel-affecting mismatch still runs the comparison, but banners the report and exits 3: the signal is "expect a full re-approve", distinct from ordinary pending changes.

Exit codes

| Code | Mode | Meaning | | ---- | ---- | ------------------------------------------------------------------------- | | 0 | both | Clean: no failures; in CI, no pending changes and the environment matched | | 1 | both | One or more stories failed (a step threw). Highest precedence | | 3 | CI | Committed environment manifest diverged from this run's environment | | 2 | CI | Pending baseline changes to approve (new, changed, or deleted) |

Precedence is 1 > 3 > 2 > 0. Local mode only ever exits 1 or 0.

What ships in v1

  • 9 step primitives composed into actions: click, input, intercept, navigate, read, scroll, type, wait, waitFor
  • An implicit screenshot at the end of every action
  • Named breakpoint modes (mobile, tablet, laptop, desktop) keyed to Tailwind widths. Pick which to run per project or per story. Each mode gets its own baseline and a per-mode group in the HTML report
  • DAG scheduler with needs/produces labels and parallel workers
  • CI-owned baselines: CI is the sole writer, local runs are advisory self-diff, approval flows through a CI candidate tree + environment manifest
  • Visual diff gated on SSIM and a differing-pixel budget, plus a pixelmatch overlay and a11y-tree snapshots (in CI an a11y-tree drift also flips a pixel-passing action to changed)
  • Trace zip on failure (Playwright trace viewer)
  • Clock freeze (page.clock.install)
  • Storage-state persistence across stories
  • Static HTML reporter
  • V8 coverage (optional via monocart-coverage-reports)
  • Per-breakpoint-pass DB reset + per-story fixture hooks (consumer-supplied via config), plus a ${breakpoint} interpolation token for per-mode test data
  • Process supervisor for dev-server hot-reload rot (it happens)

What's explicitly out of scope (v1)

  • AI fuzzy locator matching
  • Hosted SaaS / cloud runs
  • Native mobile (Playwright cannot drive it)
  • WebDriver / Puppeteer substrate
  • Supporting browsers other than Chromium

Quick start

npm install -D tuffgal@alpha
npx tuffgal init  # scaffolds tuffgal.config.ts
npx tuffgal run   # runs all stories

For CI on GitHub Actions, use the companion nschneble/tuffgal-action composite action.

Documentation

License

MIT. See LICENSE.

Roadmap

| Milestone | Status | | -------------------------- | ------ | | Repo bootstrap | ✅ | | Core extraction | ✅ | | Bridges | ✅ | | Linklater migration | ✅ | | GitHub Action | ✅ | | v0.1.0-alpha npm publish | ✅ | | v1.0.0 public launch | ⏳ |

Acknowledgements

The Tuffgal logo is an illustration by Art Attack on Unsplash.