lila/documentation/roadmap.md

7.2 KiB
Raw Blame History

Vocabulary Trainer — Roadmap

Each phase produces a working, deployable increment. Nothing is built speculatively.


Phase 0 — Foundation

Goal: Empty repo that builds, lints, and runs end-to-end.
Done when: pnpm dev starts both apps; GET /api/health returns 200; React renders a hello page.

  • Initialise pnpm workspace monorepo: apps/web, apps/api, packages/shared, packages/db
  • Configure TypeScript project references across packages
  • Set up ESLint + Prettier with shared configs in root
  • Set up Vitest in api and web
  • Scaffold Express app with GET /api/health
  • Scaffold Vite + React app with TanStack Router (single root route)
  • Configure Drizzle ORM + connection to local PostgreSQL
  • Write first migration (empty — just validates the pipeline works)
  • docker-compose.yml for local dev: api, web, postgres, valkey
  • .env.example files for apps/api and apps/web
  • testing ts config inheritance with tsx --showConfig

Phase 1 — Vocabulary Data

Goal: Word data lives in the DB and can be queried via the API.
Done when: GET /api/terms?pair=en-it&limit=10 returns 10 terms, each with 3 distractors attached.

  • Run scripts/extract_omw.py locally → generates packages/db/src/seed.json
  • Write Drizzle schema: terms, translations, language_pairs
  • Write and run migration
  • Write packages/db/src/seed.ts (reads seed.json, populates tables)
  • Implement TermRepository.getRandom(pairId, limit)
  • Implement QuizService.attachDistractors(terms) — same POS, server-side, no duplicates
  • Implement GET /language-pairs and GET /terms endpoints
  • Define Zod response schemas in packages/shared
  • Unit tests for QuizService (correct POS filtering, never includes the answer)

Phase 2 — Auth

Goal: Users can log in via Google or GitHub and stay logged in.
Done when: JWT from OpenAuth is validated by the API; protected routes redirect unauthenticated users; user row is created on first login.

  • Add OpenAuth service to docker-compose.yml
  • Write Drizzle schema: users
  • Write and run migration
  • Implement JWT validation middleware in apps/api
  • Implement GET /api/auth/me (validate token, upsert user row, return user)
  • Define auth Zod schemas in packages/shared
  • Frontend: login page with "Continue with Google" + "Continue with GitHub" buttons
  • Frontend: redirect to auth.yourdomain.com → receive JWT → store in memory + HttpOnly cookie
  • Frontend: TanStack Router auth guard (redirects unauthenticated users)
  • Frontend: TanStack Query api.ts attaches token to every request
  • Unit tests for JWT middleware

Phase 3 — Single-player Mode

Goal: A logged-in user can complete a full solo quiz session.
Done when: User sees 10 questions, picks answers, sees their final score.

  • Frontend: /singleplayer route
  • useQuizSession hook: fetch terms, manage question index + score state
  • QuestionCard component: prompt word + 4 answer buttons
  • OptionButton component: idle / correct / wrong states
  • ScoreScreen component: final score + play-again button
  • TanStack Query integration for GET /terms
  • RTL tests for QuestionCard and OptionButton

Phase 4 — Multiplayer Rooms (Lobby)

Goal: Players can create and join rooms; the host sees all joined players in real time.
Done when: Two browser tabs can join the same room and see each other's display names update live via WebSocket.

  • Write Drizzle schema: rooms, room_players
  • Write and run migration
  • POST /rooms and POST /rooms/:code/join REST endpoints
  • RoomService: create room with short code, join room, enforce max player limit
  • WebSocket server: attach ws upgrade handler to the Express HTTP server
  • WS auth middleware: validate OpenAuth JWT on upgrade
  • WS message router: dispatch incoming messages by type
  • room:join / room:leave handlers → broadcast room:state to all room members
  • Room membership tracked in Valkey (ephemeral) + room_players in PostgreSQL (durable)
  • Define all WS event Zod schemas in packages/shared
  • Frontend: /multiplayer/lobby — create room form + join-by-code form
  • Frontend: /multiplayer/room/:code — player list, room code display, "Start Game" (host only)
  • Frontend: ws.ts singleton WS client with reconnect on drop
  • Frontend: Zustand gameStore handles incoming room:state events

Phase 5 — Multiplayer Game

Goal: Host starts a game; all players answer simultaneously in real time; a winner is declared.
Done when: 24 players complete a 10-round game with correct live scores and a winner screen.

  • GameService: generate question sequence for a room, enforce server-side 15 s timer
  • room:start WS handler → begin question loop, broadcast first game:question
  • game:answer WS handler → collect per-player answers
  • On all-answered or timeout → evaluate, broadcast game:answer_result
  • After N rounds → broadcast game:finished, update rooms.status + room_players.score in DB
  • Frontend: /multiplayer/game/:code route
  • Frontend: extend Zustand store with currentQuestion, roundAnswers, scores
  • Frontend: reuse QuestionCard + OptionButton; add countdown timer ring
  • Frontend: ScoreBoard component — live per-player scores after each round
  • Frontend: GameFinished screen — winner highlight, final scores, "Play Again" button
  • Unit tests for GameService (round evaluation, tie-breaking, timeout auto-advance)

Phase 6 — Production Deployment

Goal: App is live on Hetzner, accessible via HTTPS on all subdomains.
Done when: https://app.yourdomain.com loads; wss://api.yourdomain.com connects; auth flow works end-to-end.

  • docker-compose.prod.yml: all services + nginx-proxy + acme-companion
  • Nginx config per container: VIRTUAL_HOST + LETSENCRYPT_HOST env vars
  • Production .env files on VPS (OpenAuth secrets, DB credentials, Valkey URL)
  • Drizzle migration runs on api container start
  • Seed production DB (run seed.ts once)
  • Smoke test: login → solo game → create room → multiplayer game end-to-end

Phase 7 — Polish & Hardening (post-MVP)

Not required to ship, but address before real users arrive.

  • Rate limiting on API endpoints (express-rate-limit)
  • Graceful WS reconnect with exponential back-off
  • React error boundaries
  • GET /users/me/stats endpoint + profile page
  • Accessibility pass (keyboard nav, ARIA on quiz buttons)
  • Favicon, page titles, Open Graph meta
  • CI/CD pipeline (GitHub Actions → SSH deploy on push to main)
  • Database backups (cron → Hetzner Object Storage)

Dependency Graph

Phase 0 (Foundation)
  └── Phase 1 (Vocabulary Data)
        └── Phase 2 (Auth)
              ├── Phase 3 (Singleplayer)   ← parallel with Phase 4
              └── Phase 4 (Room Lobby)
                    └── Phase 5 (Multiplayer Game)
                          └── Phase 6 (Deployment)