Callboard docs

Run Callboard

Getting started

Local development

Requirements:

  • Node.js 22.22 or newer
  • npm
  • No Cloudflare account for local development
npm install
cp .dev.vars.example .dev.vars
npm run migrate
npm run seed
npm run dev

Open http://localhost:5173. Use /demo for seeded one-click sessions, or /login to generate a development magic link.

Seeded accounts:

  • admin@callboard.dev
  • speaker@callboard.dev

Deployment

Authenticate Wrangler, create or select D1 and R2 resources, configure wrangler.jsonc, and set secrets:

npx wrangler login
npx wrangler d1 create callboard-db
npx wrangler r2 bucket create callboard-files
npx wrangler secret put SESSION_SECRET
npx wrangler secret put MAGIC_LINK_SECRET
npx wrangler secret put RATE_LIMIT_SECRET
npx wrangler secret put RESEND_API_KEY
npm run deploy

Set APP_URL to the deployed origin, CALLBOARD_URL to its public URL, and CALLBOARD_SMOKE_PROFILE=production. The checked-in wrangler.jsonc sets DEPLOYMENT_PROFILE=production and DEMO_MODE=0; the application also requires both values to opt into demo authentication, so a stray DEMO_MODE=1 cannot mint a seeded admin session in production.

Disposable judge demo

The judge demo is a separate deployment, not an environment toggle on production:

  1. Create a dedicated D1 database and R2 bucket. Do not reuse the production IDs, names, data, or secrets.

  2. Copy wrangler.demo.example.jsonc to the gitignored wrangler.demo.jsonc.

  3. Replace every REPLACE_* placeholder with the disposable demo origin, newly provisioned D1 ID, and an ISO-8601 DEMO_EXPIRES_AT no more than seven days away. Keep DEPLOYMENT_PROFILE=demo, DEMO_MODE=1, and MAIL_DRIVER=console. The one-click route fails closed when the deadline is missing, malformed, or passed.

  4. Set fresh demo-only SESSION_SECRET, MAGIC_LINK_SECRET, and RATE_LIMIT_SECRET values with npx wrangler secret put SECRET_NAME --config wrangler.demo.jsonc (once per secret, substituting each name).

  5. Preview the guarded resource targets, then execute the reset:

    npm run demo:reset -- --config=wrangler.demo.jsonc
    npm run demo:reset -- --config=wrangler.demo.jsonc --execute
    

    The execute path applies remote migrations first, deletes every R2 object recorded in D1 before wiping mutable rows, and restores the deterministic seed last. Do not run a separate post-reset migration or seed; the successful reset output is the migration-and-seed evidence for the final demo state.

  6. Deploy only through the guarded wrapper, then smoke the exact configured origin:

    npm run demo:deploy -- --config=wrangler.demo.jsonc
    npm run smoke:demo -- "$DEMO_URL"
    

    The wrapper accepts no implicit/default config or extra Wrangler overrides. It requires the regular, non-symlink wrangler.demo.jsonc at the repository root, parses comments and trailing commas consistently, rejects ambiguous duplicate safety keys and any Worker/origin/D1/R2 identity shared with the default config, reruns the profile/expiry/resource/mail checks, and invokes Wrangler with that exact config only after validation. Never use npm run deploy for the disposable demo: its production predeploy path is intentionally tied to the default wrangler.jsonc and callboard-db.

The reset command refuses the production config, non-disposable resource names, real email, unresolved placeholders, expired deadlines, and lifetimes over seven days. Runtime expiry returns 404 from the entire demo Worker; deleting the Worker and dedicated D1/R2 resources after judging is still an operator-owned Cloudflare cleanup step. The repository does not provision or edge-rate-limit those resources, so do not describe a disposable demo as ready until provisioning, reset output, deployed smoke, access verification, and teardown ownership are recorded.

Verification

npm run check          # types + repository guards + Vitest
npm run build          # production Worker/client build
npm run e2e            # seeds first, then Playwright
npm run release:verify # complete local release gate

Playwright refuses every non-loopback CALLBOARD_E2E_URL. The suite mutates data and uploads files, and a local process cannot prove that a remote Worker has disabled real email. Run it only against a disposable local server.

npm run release:verify runs the production build, unit/guard/type checks, migration-drift check, Playwright, a high/critical lockfile dependency audit, and a guard that rejects mutable GitHub Action references. The same gate is committed as a GitHub Actions workflow in .github/workflows/check.yml. Generated migrations must be committed.

Both deployed smoke profiles call /ready. The probe returns 503 unless SESSION_SECRET, MAGIC_LINK_SECRET, and RATE_LIMIT_SECRET are present and the D1 binding exposes the complete rate_limit_windows schema. It uses a zero-row D1 query and does not create sessions, tokens, limiter windows, or demo data. A passing smoke is repository evidence about those dependencies only; it is not deployment, walkthrough, or external-secret-scan evidence.

Commands

Command Purpose
npm run dev Start the local Vite/workerd server
npm run check Typecheck, repository guards, and unit tests
npm run build Build the production Worker and client bundles
npm run e2e Seed local D1 and run Playwright
npm run release:verify Run checks, build, migration drift, and E2E
npm run db:generate Generate committed Drizzle migrations
npm run migrate Apply local D1 migrations
npm run seed Seed the demo event
npm run demo:reset -- --config=wrangler.demo.jsonc Dry-run a guarded disposable-demo reset; add --execute after review
npm run demo:deploy -- --config=wrangler.demo.jsonc Validate and deploy only the repository-root disposable-demo config
npm run preview Upload a Cloudflare preview version
npm run deploy Check, build, migrate remote D1, deploy, and smoke
npm run smoke:demo -- "$DEMO_URL" Verify read-only runtime readiness, seeded judge data, and demo sign-in
npm run smoke:production -- "$APP_URL" Verify read-only runtime readiness and prove demo sign-in is disabled