- Update all package names from @glossa/* to @lila/*
- Update all imports, container names, volume names
- Update documentation references
- Recreate database with new credentials
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
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 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