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

experience-compiler

v0.1.0

Published

Measure what a web application actually looks like, turn the parts a human confirms into promises, and refuse the changes that would undo them.

Readme

experience-compiler

Measure what a web application actually looks like, turn the parts a human confirms into promises, and refuse the changes that would undo them.

It is a command line tool for repositories that draw a user interface — today, Next and React applications. It reads your source, it drives your own application under a sandboxed environment, and it writes what it decided into a directory in your repository that you can read, edit and delete.

npm i -g experience-compiler

Node 22.12 or newer. experience --help lists all seventeen commands. Building from source instead: npm pack, then npm i -g ./experience-compiler-0.1.0.tgz.


Thirty seconds

experience scan --target ./your-app

It reads the source. Nothing has to be installed in that application, it refuses to write inside the repository it is measuring, and on a repository it has never compiled — which is every repository the first time — it opens no browser. What it prints (one real run, on an application of about fifty files):

MEASURED your-app @ main
  7 colours · 10 text sizes · 51 buttons · 51 files
  report → mirror-your-app.html (435 KB, 283 KB of it embedded typefaces)
  1 finding: named-then-typed · 3 of 12 rules could decide, 9 unmeasured (no screens were read)
  colour count is a floor: 37 colour classes across 180 places could not be resolved to a value
    35 of those 37 carry a variant or an opacity modifier that THIS PARSER does not strip — not a framework utility it cannot see
  measured from source only — no screens photographed

Three of those lines are the reason to keep reading. colour count is a floor is the tool saying its own headline number is wrong, in a known direction. THIS PARSER does not strip — not a framework utility it cannot see is it taking the blame for its own limit instead of implying your repository is strange. 3 of 12 rules could decide is it giving you the denominator. Nothing here is a score.

Open the page it wrote. The finding is specific:

A colour you named, and then typed out by hand. You gave this colour a name — --color-accent — and then wrote the value out 6 times instead of using it, in among 15 other places that do use the name.

…with six file:line locations.

Now check it, because you should not believe it. Change that token, start your application, and ask the page what it actually computed:

getComputedStyle(document.documentElement).getPropertyValue('--color-accent')
// the new value            ← the token moved
getComputedStyle(document.documentElement, '::before').backgroundImage
// rgba(34, 197, 94, …)     ← still the old colour, one of the six

It predicted, from source alone, before a single dependency was installed, that moving that token would leave six places behind. It left six places behind. That is the loop.


After you believe it

experience init writes .experience/ — five files, and it never writes over one that is already there. tokens.draft.json (the colours and sizes this product has names for), findings.json (what is worth fixing, with where and what instead), ratchet.json (today's numbers, each recorded with how it was measured), exceptions.json (empty), and EXPERIENCE.md explaining the directory in five sentences.

The room. experience console --install writes a designer into your repository — source, in .experience/console/, plus one route in your application that refuses to render outside next dev and 404s in a production build. Open /experience in your own dev server and you get your real application in an iframe, presets you can try on it, and a column that says, axis by axis, what it can and cannot move: "this application has no name for its corner radius, so there is nothing to move." --uninstall takes all of it back out and tells you what it did not undo.

experience propose fits a promise from what a run already measured, and argues against it. Every proposal carries a section headed THE GAP whose only job is to say why it is weaker than it looks — how many pages it would reach, how many were actually opened, and what the next run will say if you confirm it.

A generator does not get to make a promise. propose writes no invariant. Ever. --confirm is a separate act by a person, and what it writes says so: "it carries no mark saying a machine drafted it, because a person confirmed it with the evidence in front of them and that makes it theirs." Everything a machine authored is marked as scaffolding until a human takes it.

experience ratchet holds the line. Numbers that went past it are named; numbers it has never measured say UNMEASURED rather than held; and two numbers taken with different rulers are refused rather than called a regression — "a line drawn with one ruler cannot be checked with another."

.experience/exceptions.json is the place to say no. One entry answers one accusation — a reason code, a finding, a ratchet measure, a route — with a sentence you wrote, your name, and the date it stops holding. An answered accusation is still measured and still printed; it is printed as answered.

experience pointer puts an address to .experience/ in the file your coding agent already reads — AGENTS.md or CLAUDE.md — only when asked, exactly once however often it runs, and it prints the change before making it. What it does not claim:

This does not make an agent read it. It makes it possible to find out whether one did.


It never writes to your database

Every command in this tool that starts your application rewrites the destinations it finds in your environment first. A DATABASE_URL is replaced by an address that keeps the shape of the connection string — because an application may pick its driver from that shape — and loses the address: the host becomes a .localhost label on port 1, which is loopback by RFC 6761 and refused in milliseconds. Everything else your env files declare is withheld unless the binding names it, and an observer is armed to catch a process that dials somewhere it should not. All of it is printed before anything is spawned.

Two developers who had never seen this product pointed it at their own applications, both of which were configured against live remote Postgres. Here is exactly what each of them did, because the difference matters.

One checked it rather than believing it, from the outside. They read the substituted value out of the generated file, resolved the host, got ::1, and measured the connection refused in 1.9 ms — without running the tool at all. That is possible only because the substitution is written down as a mechanism instead of asserted as a result.

The other walked a whole session under it, and what they reported was the tool declining to certify itself. Its escape observer said nothing had dialled a blocked address — and then the same output refused to conclude that meant safe: "Either this route asks for no data, or its data path is one no hook in this process can see (a native engine, a vendored copy, a bundled driver)." It exits non-zero rather than printing a green. They called that refusal the single most professional thing in the tool, and it is the sentence to judge this section by: silence measured is not safety proved, and this will not tell you otherwise.

The guarantee is over the commands this tool drives. npm run dev, which you start yourself, is yours — so console prints the exact substitution a run would have used, so you can start it the same way.


The commands that open a browser

All seventeen, sorted by whether they need one — read off the three places in this engine that actually open a browser, not off which files import which:

  • Always. run, check, improve, and bind --observe.
  • Only where the repository already carries a compiled contract — which is not the case the first time you point this at anything: scan, init, console, adopt, propose, and ratchet --render. All six go through one function that returns before starting anything when there is no compiled contract to drive. scan --no-render and init --no-render keep it that way even when there is.
  • Never. status, codes, compile, review, pointer, migrate, seams, bind without --observe, and ratchet without --render. seams boots your whole application and still opens no browser.

A browser is about 570 MB, so this package does not install one for you:

npm i -g playwright
npx playwright install chromium

If you ask for a command that needs one and it is not there, the tool says so before it starts your application, not after. Those two commands are what has been tested, with the tool itself installed globally; whether a browser installed this way is found by a copy run through npx has not been measured.


Exit codes

0  clean — nothing blocking
1  blocking findings — a FAIL, an ERROR, or a regression the mode forbids
2  untrusted or incomplete — a dirty tree, a missing artifact, a ledger that no longer
   describes the code, a repository whose dependencies are not installed, or a fault here
3  usage, or a refusal from the linker before anything ran

experience codes prints every reason code the engine can emit and what each one means.

Licence

MIT. See LICENSE.