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

@excellent-so/screen-map

v0.2.2

Published

The map of your running app. One command screenshots every route, graphs how they connect, and finds the screens your nav forgot.

Readme

@excellent-so/screen-map

The map of your running app. Point it at your dev server and in one command you get a screenshot of every route, a graph of how they connect, and a place to leave notes that become tracked work. It finds the screens your nav forgot.

npx @excellent-so/screen-map build && npx @excellent-so/screen-map open

Zero config on a standard Next.js App Router app. Zero runtime dependencies. Fully local — no cloud, no account. (Installs a screen-map command.)


Why

Your app is too big to hold in your head, any static map is stale the day it's drawn, and a UX finding has nowhere durable to land. screen-map:

  1. Enumerates every route from your framework router — including orphan pages nothing links to, which a link-crawler structurally can't find.
  2. Screenshots each one by driving your real running app with headless Chrome.
  3. Graphs the navigation from your source (<Link> / router.push / redirect) and validates it against a live crawl — surfacing orphans (no way in) and dead-ends (no way out).
  4. Gives feedback a home — a local viewer with a screen board, a flow graph, durable multi-select notes, before/after diffing, and a note → agent → refresh review loop.
  5. Routes findings into tracked work — export a note to markdown, a GitHub issue, or (for Excellent users) a task on the ship/verify gate.

No existing tool combines those. Storybook renders hand-authored stories, not routes. Chromatic/Percy diff snapshots in the cloud. SEO crawlers graph a deployed URL and miss code-only routes. agent-browser screenshots a URL an agent names — no enumeration, no graph, no durable notes. The fusion is the point.

Install & run

# 1. First touch — zero install
npx @excellent-so/screen-map build   # enumerate → resolve → graph → capture → crawl ∪ walk
npx @excellent-so/screen-map open    # serve the viewer and open it

# 2. For your team — pin it as a devDependency
npm i -D @excellent-so/screen-map
#   then add to package.json "scripts": { "map": "screen-map build && screen-map open" }

# 3. Global — a one-liner from anywhere
npm i -g @excellent-so/screen-map    # installs the `screen-map` command

Requires Node ≥ 20, a dev server (either one you keep running, or let Screen Map run its own — see Self-hosted dev server), and a system Chrome/Chromium (it finds Chrome, Chromium, Edge, or Brave automatically; --download-chrome fetches one on demand — never a hidden postinstall, never a bundled browser).

Commands

| Command | What it does | |---|---| | screen-map build | The full pipeline → .screenmap/screenmap.json | | screen-map open | Serve the viewer and open it in your browser | | screen-map serve | Serve the viewer (notes save to disk) | | screen-map regraph | Fast, Chrome-free rebuild reusing the last shots | | screen-map recapture --ids=a,b | Re-screenshot specific screens (the review-loop refresh) | | screen-map route | Turn review notes into tracked work (see Findings) | | screen-map diff | What changed since the last build | | screen-map init | Scaffold a screen-map.config.mjs | | screen-map doctor | Report config / Chrome / output-adapter status |

Useful build flags: --no-capture (fast, offline graph), --no-crawl (router-walk only), --only=g1,g2 (restrict groups), --concurrency=N, --limit=N, --download-chrome, --managed-server (run its own dev server — see below).

Configuration

Zero-config works for a standard Next.js App Router app. To customize, drop a screen-map.config.{mjs,js,json} in your project root (screen-map init scaffolds one):

export default {
  appDir: "app",                       // or "src/app"; auto-detected if omitted
  devBaseUrl: "http://localhost:3000", // SCREENMAP_BASE overrides
  framework: "next-app-router",        // discovery adapter
  discovery: ["router-walk", "crawl"], // the union (see below)
  groups: (route) => route.split("/")[1] || "root", // section taxonomy
  navBackbone: [],                     // optional global-nav targets ("/settings", …)
  noChromeRoots: [],                   // route roots with no global chrome (auth, …)
  coldStartPattern: null,              // optional regex for empty-state hints
  flows: [],                           // optional declared state-machine flows
  stateSweep: true,                    // auto-discover hidden state screens (below)
  statePreview: null,                  // escape hatch for gated states: {param,value}
  output: { adapter: "markdown" },     // "markdown" | "github" | "excellent"
  server: null,                        // let it run its OWN dev server (see below)
  routeUrls: {},                       // pin dynamic routes to concrete URLs (see below)
};

Everything except appDir/devBaseUrl degrades gracefully to nothing when unset — the generic path never depends on any of it.

Pinning dynamic routes (routeUrls). Screen Map resolves [id]/[slug] routes by substituting seed values, then by scraping a real detail link off the nearest index page. When neither reaches a route — an overloaded [id] param (one seed can't fit both /runs/[id] and /leads/[id]), a detail route with no scrapeable index (/o/[uid]), or a section-scoped route whose real index lives elsewhere — pin it to a concrete URL:

// screen-map.config.mjs  → routeUrls, or a gitignored .screenmap/route-urls.json
routeUrls: {
  "/leads/[id]": "/leads/42",
  "/o/[uid]": "/o/01H…",
},

Self-hosted dev server (--managed-server)

By default Screen Map captures against a dev server you keep running. For long or unattended runs that coupling is fragile: if the shared server wedges partway through, every remaining screenshot fails. Give it a server block and it will spin up its own disposable dev server, capture against it, and tear it down fully:

export default {
  server: {
    command: "next dev",          // your dev command (argv; string is whitespace-split)
    readyPath: "/",               // polled until the server answers
    // port: 0,                   // 0 → auto-pick an ephemeral port (never collides)
    // distDirEnv: "MYAPP_DIST",  // env var your app reads to relocate its build dir,
    // distDir: ".next-screen-map", //   so two Next dev servers don't share one `.next`
    // env: { MYAPP_DATA_DIR: "/tmp/scratch" }, // point the app at a throwaway DB
    // prepare: "node scripts/seed-scratch-db.mjs", // one-time setup before boot
    // autostart: true,           // use the managed server on every run (no flag)
  },
};
screen-map build --managed-server      # boot it, capture, tear it down
screen-map build --managed-server --server-port=4700  # pin the port

The managed server gets an ephemeral port (never collides with your own), an optional isolated build dir, a health watchdog (a wedged server is killed and respawned on the same port so the capture URL never changes), a retry pass for any shots that failed during a hiccup, and full teardown (process-group kill wired to stop()/SIGINT/SIGTERM/exit — nothing leaks even on Ctrl-C). Set autostart: true to make it the default. This is what makes an overnight screen-map build reliable.

State-sweep: the screens a route walk can't see

The router walk gives one screenshot per route. But many routes hide N screens behind a state axis on a single route — a query param the page reads (/tour?mode=preview&step=3), or a dynamic [param] gated by app state (/welcome/[step]info…done). Older tools captured only the bare URL: one frame of a multi-screen flow.

stateSweep (ON by default) discovers those states from the route's own source — the string-union type, the VALID_STEPS/as const allow-list, or the array a numeric param indexes — and fans the route into one screen per state. No per-route declaration: point it at a project it's never seen and its wizards, tabs, and multi-step flows expand automatically. It only fans a route when it infers a confident value domain, so a free-text ?q= never explodes into noise.

stateSweep: { enabled: true, maxPerRoute: 24, maxTotal: 400 }, // or just `true` / `false`

Two things a hand-authored flows entry conflated, separated here: what the states are (discovered — no authoring) and how to force a gated one. A route that redirects unless it's in a valid state needs one generic escape hatch — statePreview appends ?<param>=<value> to gated states so they render on a live server:

statePreview: { param: "preview", value: "1" }, // your app's dev-preview convention

Query-param routes that honor their own params (the common case) need nothing at all. Declared flows still win where set — the sweep skips any route a flow already owns.

Discovery: router-walk ∪ crawl

Discovery is the union of two adapters:

  • Router-walk enumerates every route from the filesystem router, including orphans. This is the moat — the reason to use this over an SEO crawler.
  • Running-server crawl renders the real app (dynamic [slug] pages render for real), works beyond Next, and its link edges validate the static graph.

Their difference is the payoff: walk ∖ crawl-reached is the proven orphan report — a route that exists in code but nothing links to, demonstrated, not just inferred.

Findings

A review note you leave in the viewer becomes a portable finding — the exact screens, live URLs, screenshots, and graph signals (orphan / dead-end). Route it:

screen-map route --adapter=markdown   # append to .screenmap/findings.md + stdout (default)
screen-map route --adapter=github     # open a GitHub issue (needs GITHUB_TOKEN)
screen-map route --adapter=excellent  # a task on Excellent's ship/verify gate (needs excellent-mcp)

screen-map route --dry-run prints the findings without sending. Screen Map never holds a Slack/Linear token — the excellent adapter lands one finding and Excellent fans it out through its own integrations hub.

Editor extension

Screen Map ships a VS Code / Open VSX extension — a thin host that runs the CLI and renders the viewer inside your editor. Works in Cursor, Windsurf, and VSCodium too.

Output

Everything lands in .screenmap/ (gitignore it) — the JSON artifact, the shots, your notes, and the self-contained viewer.

License

MIT © Excellent