- Add Better Auth config with Google + GitHub social providers
- Mount auth handler on /api/auth/* in Express
- Generate and migrate auth tables (user, session, account, verification)
- Deduplicate term_glosses data for tighter unique constraint
- Drop legacy users table
- Add Vite proxy for /api → localhost:3000 (no CORS needed in dev)
- Create /play route with hardcoded game settings (en→it, nouns, easy)
- Three-phase state machine: loading → playing → finished
- Show prompt, optional gloss, and 4 answer buttons per question
- Submit answers to /api/v1/game/answer, show correct/wrong feedback
- Manual Next button to advance after answering
- Score screen on completion
- Add selectedOptionId to AnswerResult schema (discovered during
frontend work that the result needs to be self-contained for
rendering feedback without separate client state)
Intentionally unstyled — component extraction and polish come next.
The service now tracks the correct optionId for each question and
stores the answer key in the GameSessionStore after building the
session. The client response is unchanged — the store is invisible
to the outside.
- Build answerKey (questionId → correctOptionId) during question
assembly by finding the correct answer's position after shuffle
- Store the answer key via gameSessionStore.create() before returning
- Add excludeText parameter to getDistractors to prevent a distractor
from having identical text to the correct answer (different term,
same translation). Solved at the query level, not with post-filtering.
- Module-level InMemoryGameSessionStore singleton in the service
Add the session storage infrastructure for tracking correct answers
during a game. Designed for easy swap to Valkey in Phase 4.
- GameSessionStore interface with create/get/delete methods, all async
to match the eventual Valkey implementation
- InMemoryGameSessionStore backed by a Map
- GameSessionData holds only the answer key (questionId → correctOptionId)
- Also fix root build script to build packages in dependency order
Extend the game flow from raw term rows to a complete GameSession
matching the shared schema:
- Add getDistractors model query: fetches N same-POS, same-difficulty,
same-target-language terms excluding a given termId. Returns bare
strings since distractors only need their display text.
- Fix getGameTerms select clause to use the neutral field names
(sourceText, targetText, sourceGloss) that the type already declared.
- Rename gameService entry point to createGameSession; signature now
takes a GameRequest and returns a GameSession.
- Per question: fetch 3 distractors, combine with the correct answer,
shuffle (Fisher-Yates), assign optionIds 0-3 by post-shuffle index,
and assemble into a GameQuestion with a fresh UUID.
- Wrap the questions in a GameSession with its own UUID.
- Run per-question distractor fetches in parallel via Promise.all.
Known gap: the correct option is not yet remembered server-side, so
/game/answer cannot evaluate submissions. SessionStore lands next.
Add the shared schemas for the quiz request/response cycle, defining
the contract between the API and the frontend.
- Reorganise packages/shared: move schemas into a schemas/ subdirectory
grouped by domain. Delete the old flat schema.ts.
- Add AnswerOption, GameQuestion, GameSession, AnswerSubmission, and
AnswerResult alongside the existing GameRequest.
- optionId is an integer 0-3 (positional, shuffled at session-build
time so position carries no information).
- questionId and sessionId are UUIDs (globally unique, opaque, natural
keys for Valkey storage later).
- gloss is rather than optional, for a predictable
shape on the frontend.
- options array enforced to exactly 4 elements at the schema level.
- Add double join on translations for source/target languages
- Left join term_glosses for optional source-language glosses
- Filter difficulty on target side only (intentionally asymmetric:
a word's difficulty can differ between languages, and what matters
is the difficulty of the word being learned)
- Return neutral field names (sourceText, targetText, sourceGloss)
instead of quiz semantics; service layer maps to prompt/answer
- Tighten term_glosses unique constraint to (term_id, language_code)
to prevent the left join from multiplying question rows
- Add TODO for ORDER BY RANDOM() scaling post-MVP
- Add GameRequestSchema and derived types to packages/shared
- Add SupportedLanguageCode, SupportedPos, DifficultyLevel type exports
- Add getGameTerms() model to packages/db with pos/language/difficulty/limit filters
- Add prepareGameQuestions() service skeleton in apps/api
- Add createGame controller with Zod safeParse validation
- Wire POST /api/v1/game/start route
- Add scripts/gametest/test-game.ts for manual end-to-end testing
- add deck_terms to schema imports
- add addTermsToDeck — diffs source term IDs against existing deck_terms,
inserts only new ones, returns count of inserted terms
- add updateValidatedLanguages — recalculates and persists validated_languages
on every run so coverage stays accurate as translation data grows
- wire both functions into main with isNewDeck guard to avoid redundant
validated_languages update on deck creation
- add final summary report
- fix possible undefined on result[0] in createDeck
- tick off remaining roadmap items
Implements packages/db/src/seed.ts — reads all JSON files from
scripts/datafiles/, validates filenames against supported language
codes and POS, and upserts synsets into and
via onConflictDoNothing. Safe to re-run; produces 0 writes on
a duplicate run.
- terms, translations, term_glosses with cascade deletes and pos check constraint
- language_pairs with source/target language check constraints and no-self-pair guard
- users with openauth_sub as identity provider key
- decks and deck_terms with composite PK and position ordering
- indexes on all hot query paths (distractor generation, deck lookups, FK joins)
- SUPPORTED_POS and SUPPORTED_LANGUAGE_CODES as single source of truth in @glossa/shared
- Configure PostgreSQL 18 and Valkey 9.1 services
- Create multi-stage Dockerfiles for API and Web apps
- Set up pnpm workspace support in container builds
- Configure hot reload via volume mounts for both services
- Add healthchecks for service orchestration
- Support dev/production stage targets (tsx watch vs compiled)