From 4c48859d0063f808aaeb1672873a15af72b0bbe4 Mon Sep 17 00:00:00 2001 From: lila Date: Sun, 19 Apr 2026 09:31:01 +0200 Subject: [PATCH] updating docs --- documentation/roadmap.md | 51 ++++--- documentation/spec.md | 10 +- scripts/create-issues.sh | 280 --------------------------------------- 3 files changed, 30 insertions(+), 311 deletions(-) delete mode 100644 scripts/create-issues.sh diff --git a/documentation/roadmap.md b/documentation/roadmap.md index 7c4b4ed..ce35ecd 100644 --- a/documentation/roadmap.md +++ b/documentation/roadmap.md @@ -176,37 +176,36 @@ _Note: Deployment was moved ahead of multiplayer — the app is useful without m **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 Express HTTP server -- [ ] WS auth middleware: validate JWT on upgrade -- [ ] WS message router: dispatch by `type` -- [ ] `room:join` / `room:leave` handlers → broadcast `room:state` -- [ ] Room membership tracked in Valkey (ephemeral) + PostgreSQL (durable) -- [ ] Define all WS event Zod schemas in `packages/shared` -- [ ] Frontend: `/multiplayer/lobby` — create room + join-by-code -- [ ] Frontend: `/multiplayer/room/:code` — player list, room code, "Start Game" (host only) -- [ ] Frontend: WS client singleton with reconnect +- [x] Write Drizzle schema: `lobbies`, `lobby_players` +- [x] Write and run migration +- [x] `POST /api/v1/lobbies` and `POST /api/v1/lobbies/:code/join` REST endpoints +- [x] `LobbyService`: create lobby with Crockford Base32 code, join lobby, enforce max player limit +- [x] WebSocket server: attach `ws` upgrade handler to Express HTTP server +- [x] WS auth middleware: validate Better Auth session on upgrade +- [x] WS message router: dispatch by `type` via Zod discriminated union +- [x] `lobby:join` / `lobby:leave` handlers → broadcast `lobby:state` +- [x] Lobby membership tracked in PostgreSQL (durable), game state in-memory (Valkey deferred) +- [x] Define all WS event Zod schemas in `packages/shared` +- [x] Frontend: `/multiplayer` — create lobby + join-by-code +- [x] Frontend: `/multiplayer/lobby/:code` — player list, lobby code, "Start Game" (host only) +- [x] Frontend: WS client class with typed message handlers --- ## Phase 5 — Multiplayer Game **Goal:** Host starts a game; all players answer simultaneously in real time; a winner is declared. -**Done when:** 2–4 players complete a 10-round game with correct live scores and a winner screen. +**Done when:** 2–4 players complete a 3-round game with correct live scores and a winner screen. -- [ ] `GameService`: generate question sequence, enforce 15s server timer -- [ ] `room:start` WS handler → 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 DB (transactional) -- [ ] Frontend: `/multiplayer/game/:code` route -- [ ] Frontend: reuse `QuestionCard` + `OptionButton`; add countdown timer -- [ ] Frontend: `ScoreBoard` component — live per-player scores -- [ ] Frontend: `GameFinished` screen — winner highlight, final scores, play again -- [ ] Unit tests for `GameService` (round evaluation, tie-breaking, timeout) +- [x] `MultiplayerGameService`: generate question sequence, enforce 15s server timer +- [x] `lobby:start` WS handler → broadcast first `game:question` +- [x] `game:answer` WS handler → collect per-player answers +- [x] On all-answered or timeout → evaluate, broadcast `game:answer_result` +- [x] After N rounds → broadcast `game:finished`, update DB (transactional) +- [x] Frontend: `/multiplayer/game/:code` route +- [x] Frontend: reuse `QuestionCard` + `OptionButton`; round results per player +- [x] Frontend: `MultiplayerScoreScreen` — winner highlight, final scores, play again +- [x] Unit tests for `LobbyService`, WS auth, WS router --- @@ -236,7 +235,7 @@ Phase 0 (Foundation) ✅ └── Phase 2 (Singleplayer UI) ✅ ├── Phase 3 (Auth) ✅ │ └── Phase 6 (Deployment + CI/CD) ✅ - └── Phase 4 (Multiplayer Lobby) - └── Phase 5 (Multiplayer Game) + └── Phase 4 (Multiplayer Lobby) ✅ + └── Phase 5 (Multiplayer Game) ✅ └── Phase 7 (Hardening) ``` diff --git a/documentation/spec.md b/documentation/spec.md index 637da00..d2d320f 100644 --- a/documentation/spec.md +++ b/documentation/spec.md @@ -80,7 +80,7 @@ The monorepo structure and tooling are already set up. This is the full stack. | Auth | Better Auth (Google + GitHub) | ✅ | | Deployment | Docker Compose, Caddy, Hetzner | ✅ | | CI/CD | Forgejo Actions | ✅ | -| Realtime | WebSockets (`ws` library) | ❌ post-MVP | +| Realtime | WebSockets (`ws` library) | ✅ | | Cache | Valkey | ❌ post-MVP | --- @@ -296,8 +296,8 @@ After completing a task: share the code, ask what to refactor and why. The LLM s | Deployment | Docker Compose, Caddy, Forgejo, CI/CD, Hetzner VPS | ✅ | | Hardening (partial) | CI/CD pipeline, DB backups | ✅ | | User Stats | Games played, score history, profile page | ❌ | -| Multiplayer Lobby | Room creation, join by code, WebSocket connection | ❌ | -| Multiplayer Game | Simultaneous answers, server timer, live scores, winner screen | ❌ | +| Multiplayer Lobby | Room creation, join by code, WebSocket connection | ✅ | +| Multiplayer Game | Simultaneous answers, server timer, live scores, winner screen | ✅ | | Hardening (rest) | Rate limiting, error boundaries, monitoring, accessibility | ❌ | ### Future Data Model Extensions (deferred, additive) @@ -355,8 +355,8 @@ Phase 0 (Foundation) ✅ └── Phase 2 (Singleplayer UI) ✅ ├── Phase 3 (Auth) ✅ │ └── Phase 6 (Deployment + CI/CD) ✅ - └── Phase 4 (Multiplayer Lobby) - └── Phase 5 (Multiplayer Game) + └── Phase 4 (Multiplayer Lobby) ✅ + └── Phase 5 (Multiplayer Game) ✅ └── Phase 7 (Hardening) ``` diff --git a/scripts/create-issues.sh b/scripts/create-issues.sh deleted file mode 100644 index fefb072..0000000 --- a/scripts/create-issues.sh +++ /dev/null @@ -1,280 +0,0 @@ -#!/bin/bash - -# Forgejo batch issue creator for lila -# Usage: FORGEJO_TOKEN=your_token ./create-issues.sh - -FORGEJO_URL="https://git.lilastudy.com" -OWNER="forgejo-lila" -REPO="lila" -TOKEN="${FORGEJO_TOKEN:?Set FORGEJO_TOKEN environment variable}" - -API="${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}" - -# Helper: create a label (ignores if already exists) -create_label() { - local name="$1" color="$2" description="$3" - curl -s -X POST "${API}/labels" \ - -H "Authorization: token ${TOKEN}" \ - -H "Content-Type: application/json" \ - -d "{\"name\":\"${name}\",\"color\":\"${color}\",\"description\":\"${description}\"}" > /dev/null - echo "Label: ${name}" -} - -# Helper: create an issue with labels -create_issue() { - local title="$1" body="$2" - shift 2 - local labels="$*" - - # Build labels JSON array - local label_ids="" - for label in $labels; do - local id - id=$(curl -s "${API}/labels" \ - -H "Authorization: token ${TOKEN}" | \ - python3 -c "import sys,json; [print(l['id']) for l in json.load(sys.stdin) if l['name']=='${label}']") - if [ -n "$label_ids" ]; then - label_ids="${label_ids},${id}" - else - label_ids="${id}" - fi - done - - curl -s -X POST "${API}/issues" \ - -H "Authorization: token ${TOKEN}" \ - -H "Content-Type: application/json" \ - -d "{\"title\":$(echo "$title" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))'),\"body\":$(echo "$body" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))'),\"labels\":[${label_ids}]}" > /dev/null - - echo "Issue: ${title}" -} - -echo "=== Creating labels ===" -create_label "feature" "#0075ca" "New user-facing functionality" -create_label "infra" "#e4e669" "Infrastructure, deployment, DevOps" -create_label "debt" "#d876e3" "Technical cleanup, refactoring" -create_label "security" "#b60205" "Security improvements" -create_label "ux" "#1d76db" "User experience, accessibility, polish" -create_label "multiplayer" "#0e8a16" "Multiplayer lobby and game features" - -echo "" -echo "=== Creating issues ===" - -# ── feature ── - -create_issue \ - "Add guest/try-now option — play without account" \ - "Allow users to play a quiz without signing in so they can see what the app offers before creating an account. Make auth middleware optional on game routes, add a 'Try without account' button on the login/landing page." \ - feature - -create_issue \ - "Add Apple login provider" \ - "Add Apple as a social login option via Better Auth. Requires Apple Developer account and Sign in with Apple configuration." \ - feature - -create_issue \ - "Add email+password login" \ - "Add traditional email and password authentication as an alternative to social login. Configure via Better Auth." \ - feature - -create_issue \ - "User stats endpoint + profile page" \ - "Add GET /users/me/stats endpoint returning games played, score history, etc. Build a frontend profile page displaying the stats." \ - feature - -# ── infra ── - -create_issue \ - "Google OAuth app verification and publishing" \ - "Currently only test users can log in via Google. Publish the OAuth consent screen so any Google user can sign in. Requires branding verification through Google Cloud Console." \ - infra - -create_issue \ - "Set up Docker credential helper on dev laptop" \ - "Docker credentials are stored unencrypted in ~/.docker/config.json. Set up a credential helper to store them securely. See https://docs.docker.com/go/credential-store/" \ - infra - -create_issue \ - "VPS monitoring and logging" \ - "Set up monitoring and centralized logging on the VPS. Options: chkrootkit/rkhunter for security, logwatch/monit for daily summaries, uptime monitoring for service health." \ - infra - -create_issue \ - "Move to offsite backup storage" \ - "Currently database backups live on the same VPS. Add offsite copies to Hetzner Object Storage or similar S3-compatible service to protect against VPS failure." \ - infra - -create_issue \ - "Replace in-memory game session store with Valkey" \ - "Add Valkey container to the production Docker stack. Implement ValkeyGameSessionStore using the existing GameSessionStore interface. Required before multiplayer." \ - infra - -create_issue \ - "Modern env management approach" \ - "Evaluate replacing .env files with a more robust approach (e.g. dotenvx, infisical, or similar). Current setup works but .env files are error-prone and not versioned." \ - infra - -create_issue \ - "Pin dependencies in package.json files" \ - "Pin all dependency versions in package.json files to exact versions to prevent unexpected updates from breaking builds." \ - infra - -# ── debt ── - -create_issue \ - "Rethink organization of datafiles and wordlists" \ - "The current layout of data-sources/, scripts/datafiles/, scripts/data-sources/, and packages/db/src/data/ is confusing with overlapping content. Consolidate into a clear structure." \ - debt - -create_issue \ - "Resolve eslint peer dependency warning" \ - "eslint-plugin-react-hooks 7.0.1 expects eslint ^3.0.0-^9.0.0 but found 10.0.3. Resolve the peer dependency mismatch." \ - debt - -# ── security ── - -create_issue \ - "Rate limiting on API endpoints" \ - "Add rate limiting to prevent abuse. At minimum: auth endpoints (brute force prevention), game endpoints (spam prevention). Consider express-rate-limit or similar." \ - security - -# ── ux ── - -create_issue \ - "404/redirect handling for unknown routes and subdomains" \ - "Unknown routes return raw errors. Add a catch-all route on the frontend for client-side 404s. Consider Caddy fallback for unrecognized subdomains." \ - ux - -create_issue \ - "React error boundaries" \ - "Add error boundaries to catch and display runtime errors gracefully instead of crashing the entire app." \ - ux - -create_issue \ - "Accessibility pass" \ - "Keyboard navigation for quiz buttons, ARIA labels on interactive elements, focus management during quiz flow." \ - ux - -create_issue \ - "Favicon, page titles, Open Graph meta" \ - "Add favicon, set proper page titles per route, add Open Graph meta tags for link previews when sharing." \ - ux - -# ── multiplayer ── - -create_issue \ - "Drizzle schema: lobbies, lobby_players + migration" \ - "Create lobbies table (id, code, host_user_id, status, is_private, game_mode, settings, created_at) and lobby_players table (lobby_id, user_id, score, joined_at). Run migration. See game-modes.md for game_mode values." \ - multiplayer - -create_issue \ - "REST endpoints: POST /lobbies, POST /lobbies/:code/join" \ - "Create lobby (generates short code, sets host) and join lobby (validates code, adds player, enforces max limit)." \ - multiplayer - -create_issue \ - "LobbyService: create lobby, join lobby, enforce player limit" \ - "Service layer for lobby management. Generate human-readable codes, validate join requests, track lobby state. Public lobbies are browsable, private lobbies require code." \ - multiplayer - -create_issue \ - "WebSocket server: attach ws upgrade to Express" \ - "Attach ws library upgrade handler to the existing Express HTTP server. Handle connection lifecycle." \ - multiplayer - -create_issue \ - "WS auth middleware: validate session on upgrade" \ - "Validate Better Auth session on WebSocket upgrade request. Reject unauthenticated connections." \ - multiplayer - -create_issue \ - "WS message router: dispatch by type" \ - "Route incoming WebSocket messages by their type field to the appropriate handler. Use Zod discriminated union for type safety." \ - multiplayer - -create_issue \ - "Lobby join/leave handlers + broadcast lobby state" \ - "Handle lobby:join and lobby:leave WebSocket events. Broadcast updated player list to all connected players in the lobby." \ - multiplayer - -create_issue \ - "Lobby state in Valkey (ephemeral) + PostgreSQL (durable)" \ - "Store live lobby state (connected players, current question, timer) in Valkey. Store durable records (who played, final scores) in PostgreSQL." \ - multiplayer - -create_issue \ - "WS event Zod schemas in packages/shared" \ - "Define all WebSocket message types as Zod discriminated unions in packages/shared. Covers lobby events (join, leave, start) and game events (question, answer, result, finished)." \ - multiplayer - -create_issue \ - "Frontend: lobby browser + create/join lobby" \ - "Lobby list showing public open lobbies. Create lobby form (game mode, public/private). Join-by-code input for private lobbies." \ - multiplayer - -create_issue \ - "Frontend: lobby view (player list, code, start game)" \ - "Show lobby code, connected players, game mode. Host sees Start Game button. Players see waiting state. Real-time updates via WebSocket." \ - multiplayer - -create_issue \ - "Frontend: WS client singleton with reconnect" \ - "WebSocket client that maintains a single connection, handles reconnection on disconnect, and dispatches incoming messages to the appropriate state handlers." \ - multiplayer - -create_issue \ - "GameService: question sequence + server timer" \ - "Generate question sequence for a lobby game. Enforce per-question timer (e.g. 15s). Timer logic varies by game mode — see game-modes.md." \ - multiplayer - -create_issue \ - "lobby:start WS handler — broadcast first question" \ - "When host starts the game, generate questions, change lobby status to in_progress, broadcast first question to all players." \ - multiplayer - -create_issue \ - "game:answer WS handler — collect answers" \ - "Receive player answers via WebSocket. Track who has answered. Behavior varies by game mode (simultaneous vs turn-based vs buzzer)." \ - multiplayer - -create_issue \ - "Answer evaluation + broadcast results" \ - "On all-answered or timeout: evaluate answers, calculate scores, broadcast game:answer_result to all players. Then send next question or end game." \ - multiplayer - -create_issue \ - "Game finished: broadcast results, update DB" \ - "After final round: broadcast game:finished with final scores and winner. Write game results to PostgreSQL (transactional). Change lobby status to finished." \ - multiplayer - -create_issue \ - "Frontend: multiplayer game route" \ - "Route for active multiplayer games. Receives questions and results via WebSocket. Reuses QuestionCard and OptionButton components." \ - multiplayer - -create_issue \ - "Frontend: countdown timer component" \ - "Visual countdown timer synchronized with server timer. Shows remaining seconds per question." \ - multiplayer - -create_issue \ - "Frontend: ScoreBoard component (live per-player scores)" \ - "Displays live scores for all players during a multiplayer game. Updates in real-time via WebSocket." \ - multiplayer - -create_issue \ - "Frontend: GameFinished screen" \ - "Winner highlight, final scores, play again option. Returns to lobby on play again." \ - multiplayer - -create_issue \ - "Multiplayer GameService unit tests" \ - "Unit tests for round evaluation, scoring, tie-breaking, timeout handling across different game modes." \ - multiplayer - -create_issue \ - "Graceful WS reconnect with exponential back-off" \ - "Handle WebSocket disconnections gracefully. Reconnect with exponential back-off. Restore game state on reconnection if game is still in progress." \ - multiplayer - -echo "" -echo "=== Done ==="