📦 Current release: 2026.6.0 (team-ready). See the changelog for what shipped and what's intentionally not here yet.
🟢 Live read-only demo →. Explore the example board, hover regions, try Present mode. The editor itself needs the self-hosted version (link below).
Status reporting for visual products, built around the screens stakeholders actually see, not the tickets engineers actually file.
Show, don't tell. Upload a screenshot of your app. Drag rectangles over the parts you want to talk about. Tag each one as SHIPPED, MOCK, or MISSING. Share one link. Your exec reads it in 30 seconds.
Open source. Self-hosted. Airgap-ready (except for your own SSO). MIT.
This is v1, the cut you can deploy in a company.
- Manual screenshot upload (PNG / JPEG / WebP / GIF, up to 25 MB)
- Region tagging on the image (click + drag)
- Three states:
shipped/mock/missing - Public read-only share links: revocable, multiple per board
- Multi-user via Keycloak / OIDC (any OIDC-compliant IdP works; Keycloak is the documented default)
- Roles: owner / editor / viewer
- Append-only audit log of mutations (read directly from Postgres for now)
- Postgres-backed, multi-replica safe (with
ReadWriteManyfor uploads) - One container + one Postgres. Zero outbound calls except to your IdP.
What's not here yet (by design, see the build plan): headless capture, Jira sync, scheduled re-capture, diffs, journeys.
The repo ships a docker-compose.yaml with Postgres + Keycloak pre-seeded with two test users (alice / bob, password = same as username).
cp .env.example .env
docker compose up -d # starts postgres + keycloak
pnpm install
pnpm migrate # creates tables
pnpm devOpen http://localhost:3000, click Continue with Keycloak, sign in as alice. The first sign-in becomes the workspace owner.
If you already run Keycloak (or any other OIDC provider) and just want StateBoard + a Postgres next to it, deploy/docker-compose.yaml is the path. Postgres + StateBoard + a one-shot migrate job, no bundled IdP.
cp deploy/docker-compose.env.example deploy/.env
# edit deploy/.env: set STATEBOARD_BASE_URL, BETTER_AUTH_SECRET,
# POSTGRES_PASSWORD, and the three KEYCLOAK_* vars
docker compose -f deploy/docker-compose.yaml --env-file deploy/.env up -dIn your existing Keycloak realm, create a confidential client and register <STATEBOARD_BASE_URL>/api/auth/oauth2/callback/keycloak as a Valid redirect URI. See Self-hosting → Keycloak setup for the full checklist. Same env-var names work with any OIDC IdP; only the issuer URL changes.
Terminate TLS at your reverse proxy (Caddy, nginx, Traefik, a cloud LB) so STATEBOARD_BASE_URL is HTTPS.
helm dependency build deploy/helm/stateboard
helm install stateboard ./deploy/helm/stateboard \
--namespace stateboard --create-namespace \
--set auth.baseUrl=https://stateboard.example.com \
--set auth.secret="$(openssl rand -base64 32)" \
--set auth.keycloak.issuer=https://keycloak.example.com/realms/acme \
--set auth.keycloak.clientSecret=... \
--set postgresql.auth.password="$(openssl rand -hex 32)"The chart bundles a Bitnami Postgres sub-chart by default. Disable with --set postgresql.enabled=false and provide --set externalDatabaseUrl=... (or read it from a Secret via externalDatabaseUrlExistingSecret). See deploy/helm/stateboard/values.yaml for the full reference.
A pre-install / pre-upgrade Job runs pnpm migrate before any pods come up. Disable with --set migrate.enabled=false if you'd rather run schema changes out-of-band.
| Piece | Choice | Why |
|---|---|---|
| Framework | Next.js 16 (App Router) | One process serves UI + API + uploads |
| UI | React 19 + MUI 7 | Solid component library, small enough to skin |
| Persistence | Postgres via pg |
Multi-replica safe; standard ops your team knows |
| Auth | Better Auth + OIDC | First-class Keycloak helper; sessions in the same DB |
| File storage | Local filesystem | RWX PVC for v1; S3-compatible adapter planned for v2 |
| Telemetry | None | Airgap by default, that's the point |
Everything that touches the DB lives in src/lib/db.ts. Auth wiring is in src/lib/auth.ts (server) and src/lib/auth-client.ts (browser). Schema is plain SQL under migrations/, applied by scripts/migrate.mjs.
| Command | What it does |
|---|---|
pnpm dev |
Run on localhost:3000 |
pnpm build |
Production build |
pnpm start |
Run the production build |
pnpm migrate |
Apply pending SQL migrations against DATABASE_URL |
pnpm lint |
ESLint |
pnpm typecheck |
tsc --noEmit |
pnpm test |
Unit tests on Node's built-in runner (node:test) |
pnpm format:check |
Prettier check |
pnpm format |
Prettier write |
| Env var | Default | Purpose |
|---|---|---|
DATABASE_URL |
required | Postgres connection string |
STATEBOARD_BASE_URL |
required | Public URL the app reaches itself at |
BETTER_AUTH_SECRET |
required | 32-byte base64 secret for session cookies (openssl rand -base64 32) |
KEYCLOAK_ISSUER |
required | Realm URL, e.g. https://keycloak.example.com/realms/acme |
KEYCLOAK_CLIENT_ID |
required | Confidential client id |
KEYCLOAK_CLIENT_SECRET |
required | Client secret |
STATEBOARD_ALLOWED_EMAIL_DOMAINS |
empty (any) | Comma-separated allowlist |
STATEBOARD_DEFAULT_ROLE |
editor |
Role given to non-first sign-ins |
STATEBOARD_DATA_DIR |
./data |
Root of upload storage |
PORT |
3000 |
HTTP port |
NODE_EXTRA_CA_CERTS |
unset | Path to a PEM CA bundle to trust for an internal/self-signed IdP cert |
- v0 (the wedge) ✅: manual upload, region tagging, three states, share link, single user
- v1 (team-ready) ✅ (you are here): multi-user, OIDC, audit log, Postgres
- v1.x (still scope-OK): headless capture from URL, Jira issue linking, custom states
- v2 (make it living): scheduled re-capture, time-travel / diff view, two-way Jira sync, Slack, Notion/Confluence embed
- v3 (defensible): DOM region-detection, journey views, portfolio rollup, SAML, audit-log UI, template gallery
Full narrative in /docs/roadmap. Live status: milestones for the per-issue rollup, or the project board for a Kanban / Roadmap view.
The temptation will be to chase roadmap-tool features. We won't. The lane is screens, regions, states, and the integrations that keep them honest, and nothing else.
MIT. Use it, fork it, ship it inside your product, sell consulting around it. The license means what it says.