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:
cd docs
pnpm install --ignore-workspace
pnpm install-browsers # pulls Chromium ~150 MB into ~/.cache/ms-playwrightWithout 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, web | pnpm install-browsers once |
pnpm docs:build later | npx 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:
# 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:serveThis brings up the full stack with the demo-data seed already applied:
- Postgres + Redis via
docker compose klaxon-serveron:3000(applies migrations on startup)klaxon-authon:3001withDEV_LOGIN_ENABLED=true- Web dashboard dev server on
:5173, bound to0.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-loginThe 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.devEither 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):
| Name | Role in primary org | |
|---|---|---|
alex@meridian.dev | Alex Chen | owner (Meridian Labs) |
priya@meridian.dev | Priya Sharma | admin |
jordan@meridian.dev | Jordan Rivera | member |
sam@meridian.dev | Sam Okonkwo | member |
maya@meridian.dev | Maya Lin | viewer |
elena@harborcommerce.io | Elena Vasquez | owner (Harbor Commerce) |
marcus@harborcommerce.io | Marcus Thompson | admin |
aisha@harborcommerce.io | Aisha Patel | member |
kai@harborcommerce.io | Kai Nakamura | custom "Support" role |
lena@nimbus.cloud | Lena Johansson | owner (Nimbus Cloud) |
david@nimbus.cloud | David Kim | admin |
fatima@nimbus.cloud | Fatima Al-Rashid | member |
tomas@nimbus.cloud | Tomás Herrera | member |
yuki@nimbus.cloud | Yuki Tanaka | viewer |
olga@nimbus.cloud | Olga Petrov | member |
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):
# 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-desktopEach 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):
<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:
Committing captures
- Commit
docs/.heroshot/config.json— it's the source of truth for what gets captured. - Commit the
docs/public/heroshots/*.pngfiles — 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.