A web app built with a methodology-first approach:
- architecture and constraints documented before code was written,
- conventions captured as they emerged from real data and runtime failures,
- and every significant decision recorded in DECISIONS.md with its rationale.
The app replaces a Google Sheets-based workflow used by school volunteers to track student reading performance, run weighted prize draws, and export records for archiving.
Key architecture and product decisions are recorded in DECISIONS.md with their rationale -- not just what was built, but why, and what it ruled out. Entries cover data residency constraints, authentication model, validation policy, and conventions that emerged from runtime failures and real historical data.
Live: https://read-a-thon-app.vercel.app
See Screenshots at the end of this document.
- Data entry — keyboard-driven entry of student reading records by a single volunteer (~150 students per year)
- Winner draws — weighted random draws for reading prizes (per grade), book review prizes, and class prize (highest total minutes)
- Student review — all entered students grouped by grade with minutes, reviews, and ballot counts
- CSV export — full data export for archiving
- Auto-cleanup — student records are hard-deleted after the configured retention period (default: 60 days post-event)
- Next.js 15 (App Router) + TypeScript
- Supabase — PostgreSQL hosted in
ca-central-1(Canada), UK-owned company - Prisma — schema migrations and type-safe DB access
- Tailwind CSS — monospace design system
- NextAuth v5 — single shared password, all routes protected
- Vercel — hosting, serverless functions, cron jobs
npm install
npm run dev # starts dev server at http://localhost:3000
npm run lint # ESLint
npm test # Vitest unit tests (ballot logic, draw logic)Copy .env.example to .env.local and fill in:
DATABASE_URL= # Supabase transaction pooler (port 6543, ?pgbouncer=true)
DIRECT_URL= # Supabase session pooler (port 5432, for migrations)
AUTH_SECRET= # Random secret: openssl rand -base64 32
APP_PASSWORD= # Shared login password
CRON_SECRET= # Secret for the /api/cron/cleanup endpoint
npx prisma migrate dev # apply schema changes
npx prisma studio # browse data locally
npx prisma generate # regenerate client after schema edits without migratingTo seed with real data from the 2026 export:
npx tsx scripts/seed-csv.ts- Every route requires authentication — no public-facing pages
- Full student names are stored; privacy is enforced by access control, not data truncation
- Database hosted in Canada (
ca-central-1), not the United States - Student names never appear in URLs, logs, or error messages
- Student records are hard-deleted after
dataRetentionDays(configurable per event); the CSV export is the long-term archive
See DECISIONS.md for the reasoning behind major architecture and product choices.



