Skip to content

Screenshots

Klaxon's docs and README are illustrated with screenshots produced by heroshot. Shots are defined once in docs/.heroshot/config.json, captured into docs/public/heroshots/, and regenerated on demand as the UI evolves.

⚠ Prerequisites

heroshot drives a real browser under the hood (via Playwright), and Playwright doesn't download its browsers at pnpm install time. One-off setup, on every machine that will run captures:

bash
cd docs
pnpm install --ignore-workspace
pnpm install-browsers   # pulls Chromium ~150 MB into ~/.cache/ms-playwright

Without this, heroshot config fails with a terse "Something went wrong" (run with -v to see Playwright's actual "browser missing" message).

The interactive picker (heroshot config) also needs a display — running it on a headless dev box fails with "Missing X server or $DISPLAY". Run the picker on a workstation with a browser; keep the capture stack (pnpm cap:serve) on the dev box.

When to regenerate

  • A UI change alters the visual of a page that's illustrated in the docs.
  • The demo-data seed (crates/klaxon-server/fixtures/seed.sql) grew a new variant worth showing off.
  • You added a new <Heroshot> component reference to a guide page.
  • A visual regression snuck in — regen, commit, the diff shows the fix.

Where each piece runs

On the dev box (headless OK)On your workstation (needs a display)
pnpm cap:serve — brings up Postgres, auth, server, webpnpm install-browsers once
pnpm docs:build laternpx heroshot config — visual picker
npx heroshot oneshot (headless regen, future CI)Browser pointed at the dev box's URL

The Vite dev server proxies /auth/*, /api/*, /oauth/*, /mcp, and /.well-known/* through to the right backend — matching production's ingress — so the workstation browser only needs the dev box's $CAP_HOST:5173 to be reachable.

One-command capture stack

From repo root:

bash
# Auto-detects the primary outbound IP (e.g. 10.0.0.26) so a laptop
# browser on the same network can reach it without extra config.
pnpm cap:serve

# Override when you have a nicer name (Tailscale, LAN DNS, etc.):
CAP_HOST=tyler-dev.tail1234.ts.net pnpm cap:serve

# Or force loopback-only when everything's on one box with a browser:
CAP_HOST=localhost pnpm cap:serve

This brings up the full stack with the demo-data seed already applied:

  • Postgres + Redis via docker compose
  • klaxon-server on :3000 (applies migrations on startup)
  • klaxon-auth on :3001 with DEV_LOGIN_ENABLED=true
  • Web dashboard dev server on :5173, bound to 0.0.0.0
  • Logs tail to /tmp/klaxon-{server,auth,web}.log

The Vite dev server proxies /api, /auth, /oauth, /mcp, and /.well-known/* through to the right backend — matching production's ingress routing — so a laptop browser only needs to reach port 5173 on the dev box. Ports 3000 and 3001 don't have to be exposed externally.

Ctrl+C tears the Rust + Node processes down; Postgres + Redis stay up (stop with docker compose down when you're really done).

Persona login (dev-login bypass)

The DEV_LOGIN_ENABLED=true env flag exposes a one-shot login at /auth/dev-login that skips the magic-link / IdP round-trip.

Persona picker — bookmark this one URL and switch identities by clicking. Substitute $HOST with your CAP_HOST (dev box LAN IP, Tailscale name, or localhost):

http://$HOST:5173/auth/dev-login

The picker queries the DB for every seeded user and renders a grouped grid of login buttons — so it stays in sync with crates/klaxon-server/fixtures/seed.sql automatically.

Direct link — skip the picker and log in as a specific persona:

http://$HOST:5173/auth/dev-login?user=alex@meridian.dev

Either form looks up the user by email, mints a session + bearer token, and 302s to http://$HOST:5173/?token=… — the SPA picks up the token on load and stashes it in localStorage. heroshot reuses that session for subsequent captures.

Optional &return_to=/some/path lands the browser on a deep link after login (handy for capturing specific pages directly).

The endpoint 404s when DEV_LOGIN_ENABLED isn't set, so the route is safe to wire unconditionally in production builds. Handler: crates/klaxon-auth/src/handlers/idp/dev_login.rs.

heroshot stores the resulting browser session encrypted so subsequent captures replay the same identity without re-logging in.

The 15 seeded personas (from crates/klaxon-server/fixtures/seed.sql):

EmailNameRole in primary org
alex@meridian.devAlex Chenowner (Meridian Labs)
priya@meridian.devPriya Sharmaadmin
jordan@meridian.devJordan Riveramember
sam@meridian.devSam Okonkwomember
maya@meridian.devMaya Linviewer
elena@harborcommerce.ioElena Vasquezowner (Harbor Commerce)
marcus@harborcommerce.ioMarcus Thompsonadmin
aisha@harborcommerce.ioAisha Patelmember
kai@harborcommerce.ioKai Nakamuracustom "Support" role
lena@nimbus.cloudLena Johanssonowner (Nimbus Cloud)
david@nimbus.cloudDavid Kimadmin
fatima@nimbus.cloudFatima Al-Rashidmember
tomas@nimbus.cloudTomás Herreramember
yuki@nimbus.cloudYuki Tanakaviewer
olga@nimbus.cloudOlga Petrovmember

The /auth/dev-login picker always shows the live set — this table is for convenience.

Capturing shots

From the docs/ directory on your workstation (see Prerequisites at the top — pnpm install-browsers must have run once on this machine):

bash
# Interactive — add or edit shot definitions by pointing and clicking.
# Opens a local heroshot picker in your default browser; it navigates
# to the dev stack at http://$CAP_HOST:5173 and lets you click the
# element you want captured. The picker auto-derives a CSS selector and
# writes it to .heroshot/config.json.
cd docs && npx heroshot config

# Sync — regenerate every shot already in config.json. Fully headless.
# This is the CI-friendly path.
cd docs && npx heroshot oneshot

# One-off — capture a single URL without adding it to config.
cd docs && npx heroshot oneshot http://localhost:5173 \
  --selector '.app-shell' \
  --desktop \
  --output inbox-desktop

Each shot produces multiple variants automatically: {name}-{viewport}-{colorScheme}.png, e.g. inbox-desktop-light.png, inbox-mobile-dark.png.

Referencing shots in docs

In VitePress Markdown pages (where the Vue component is available):

md
<Heroshot name="inbox" alt="Klaxon inbox" />

docs/.vitepress/theme/index.ts registers the component globally and the Vite plugin (docs/.vitepress/config.ts) injects the manifest as a virtual module. Light/dark and viewport variants are selected automatically based on the reader's device and theme.

In the root README.md (rendered by GitHub, plain Markdown) reference the committed PNG directly:

md
![Klaxon inbox](./docs/public/heroshots/inbox-desktop-light.png)

Committing captures

  • Commit docs/.heroshot/config.json — it's the source of truth for what gets captured.
  • Commit the docs/public/heroshots/*.png files — they're the rendered assets the site and README reference.
  • Do not commit docs/.heroshot/sessions/ (gitignored — encrypted auth state specific to your machine).

Regenerating in CI (future)

A follow-up PR will add .github/workflows/heroshots.yml that runs cap-serve-style service containers, runs heroshot oneshot headlessly, and commits any diffs back to the PR branch on a regen-shots label. Until then, regen is a local-then-commit workflow.