StatsHub Docs

playground

The workbench for the shared web components and the web app's route map, on port 3004.

apps/statshub-playground is where @statshub/ui-web is looked at rather than used. It renders every shared component against its own previews, and it holds a screenshotted map of every route in web.

bun run dev:playground     # http://localhost:3004
bun run build:playground
bun run birds-eye          # re-shoot the route map

Two tools, both live once it is running: the component workbench and Bird's Eye View. Those links resolve through the app registry, so they point wherever this environment's playground actually is.

Next.js 16React 19TypeScriptTailwind CSS 4shadcn/uithe catalog it checks againstPlaywrightthe screenshotsBun

What it does

Every shared component, rendered

One entry per file in packages/ui-web/src/components, each with the previews that file exports. If a component is in the package, it is on this page.

Live controls

A preview declares its controls, and the panel builds itself from them — so a variant is checked by changing it, not by editing a file and waiting for HMR.

Source, docs and examples beside the preview

Four tabs per component. /api/components/[slug] reads the real file off disk at request time, so what you read is what ships.

Bird's eye view

Every route in web, screenshotted and wired into a map — the root page first, then a chip per tool category, then each route's screenshot hanging off the group it belongs to. It is how you see the shape of the product without clicking through it. Click a card for what the run recorded: HTTP status, how long the page took to settle, and the console errors it logged while it rendered.

What is missing from the set

The catalog carries the shadcn component list beside ours, so a primitive the design system has not built yet shows up as a gap rather than as nothing at all.

How it is built

A component without previews fails the build

prebuild runs check:previews, which walks packages/ui-web/src/components and requires exactly one export const <name>Previews per file, with globally unique preview ids. The gate is what stops the inventory quietly going stale as the package grows.

The registry is generated, not written

predev and prebuild run gen-preview-registry.mjs, which writes src/lib/component-previews.generated.ts — the static import list the bundler needs, since a dynamic import of a workspace path would not be traced.

The manifest is the whole contract

src/lib/birds-eye.ts is imported by two things that may never import each other: scripts/shoot-web-routes.ts, which drives Playwright from Node, and birds-eye-view.tsx, which draws in the browser. So the file touches neither node:fs nor React.

A JSON file on disk, not a build artefact

The shot manifest is read at runtime, so a fresh screenshot run shows up on reload rather than after a rebuild.

Tools are addressable

src/lib/playground-tools.ts is the list. It held one thing for long enough that the shell and the tool were the same component; adding a second one means a route, a card in that list, and nothing else.

Layout

PathWhat is in it
src/app/page.tsxThe component workbench
src/app/birds-eye-viewThe route map
src/app/api/components/[slug]Reads a component's source, docs and examples off disk
src/lib/component-catalog.tsOur components, and the shadcn set to compare against
src/lib/birds-eye.tsThe manifest shape both the shooter and the canvas read
scripts/shoot-web-routes.tsDrives Playwright over web and writes the manifest

Shooting the route map

bun run birds-eye opens every route in web once and writes public/birds-eye/manifest.json beside the screenshots. It takes its default target from the app registry, so it follows WEB_ORIGIN or DEV_HOST without a flag — and --base-url still wins when you want a preview or production.

bun run birds-eye --list                    # the route plan, opening nothing
bun run birds-eye --theme dark --full-page
bun run birds-eye --only /referees

The route list is discovered

Every page.tsx under apps/statshub-web/src/app, minus the parallel-route slots. A list written down here would go stale the first time somebody adds a board, and the failure is a map that quietly omits it.

One entity page stands in for all of them

[view] and [scope] enumerate from the page's own generateStaticParams, unioned with the catalogue in statshub-config. Entity routes cannot enumerate, so each takes one sample off the sitemap the API already serves — one team, one player, one fixture, one referee.

A build error is not a screenshot

The dev overlay paints instantly, holds still and contains no skeletons, so every settle check passes it. The first full run returned forty green ticks and forty identical pictures of "Module not found". The shooter now reads the overlay's shadow root after the shutter and marks those failed.

It imports the package, it does not copy it

@statshub/ui-web is a workspace dependency. A component is added to the package with its previews, and it appears here — there is nothing to register.

On this page