The authLimiter was blocking legitimate users because Better Auth's
client polls /get-session frequently (on mount, route changes, focus),
and /sign-out was also getting blocked after repeated session polls.
Skip rate limiting for:
- /get-session — read-only, requires valid cookie, no attack surface
- /sign-out — no attack value in blocking logout
- /callback/* — OAuth callbacks from providers
Brute force protection remains on /sign-in, /sign-up, and other
sensitive endpoints.
Adds HSTS, CSP, X-Frame-Options, X-Content-Type-Options,
and Referrer-Policy to lilastudy.com responses.
CSP allows connect-src to api.lilastudy.com over HTTPS and
wss:// for WebSocket multiplayer. Tailwind's inline styles
require style-src 'unsafe-inline'.
- write annotate.ts — matches CEFR source files against OMW translations
- match by word text + normalized POS
- add cefr_source vote to matched translations
- extract native example sentences from CEFR source files
- write one annotated JSON per language to stage-2-annotate/output/
- write conflicts.json for words with multiple CEFR levels
- update tsconfig to support all stage directories
- 2 German conflicts found (macht, bleiche)
- match rates: en 47k, fr 44k, de 26k, it 26k, es 26k
WebSocket server:
- WS auth via Better Auth session on upgrade request
- Router with discriminated union dispatch and two-layer error handling
- In-memory connections map with broadcastToLobby
- Lobby handlers: join, leave, start
- Game handlers: answer, resolve round, end game, game:ready for state sync
- Shared game state store (LobbyGameStore interface + InMemory impl)
- Timer map separate from store for Valkey-readiness
REST API:
- POST /api/v1/lobbies — create lobby + add host as first player
- POST /api/v1/lobbies/:code/join — atomic join with capacity/status checks
- getLobbyWithPlayers added to model for id-based lookup
Frontend:
- WsClient class with typed on/off, connect/disconnect, isConnected
- WsProvider owns connection lifecycle (connect/disconnect/isConnected state)
- WsConnector component triggers connection at multiplayer layout mount
- Lobby waiting room: live player list, copyable code, host Start button
- Game view: reuses QuestionCard, game:ready on mount, round results
- MultiplayerScoreScreen: sorted scores, winner highlight, tie handling
- Vite proxy: /ws and /api proxied to localhost:3000 for dev cookie fix
Tests:
- lobbyService.test.ts: create, join, retry, idempotency, full lobby
- auth.test.ts: 401 reject, upgrade success, 500 on error
- router.test.ts: dispatch all message types, error handling
- vitest.config.ts: exclude dist folder
Fixes:
- server.ts: server.listen() instead of app.listen() for WS support
- StrictMode removed from main.tsx (incompatible with WS lifecycle)
- getLobbyWithPlayers(id) added for handleLobbyStart lookup
- server.ts: switch from app.listen() to server.listen() so WebSocket
upgrade handler is on the same server as HTTP requests
- lobbyService: add host as first player on lobby creation
- ws-client: guard against reconnect when already connecting
- ws-provider: skip connect if already connected