The vocabulary loop (tap → lookup → save → underline → review)
Parent: key-designs
The product's core loop. Each stage is concrete in code.
1. Tap a word → 3-tier lookup (usecases/lookupWord.ts, useLookup.ts).
- L1 IndexedDB
DictCache(instant, offline); - L2 the mainbackend global dictionary (
DictionaryApi→/api/v1/dictionary/entries) — a shared cache across all users; - L3 the user-configured LLM (
api/llmLookup.ts,llmLookupBasic, temp 0.1) — word/lemma/POS/meaning/nounInfo. On an L3 hit it writes back to L1+L2 fire-and-forget, so the next reader who taps the same word pays nothing.
2. Verb enrichment (lazy, async) — if the POS is a verb, llmLookupVerbTable fills the conjugation grid and services/verbForms.ts harvests every conjugated form + the participle, appending them to the L2 dictionary. So future lookups of any conjugation hit the cache, not the LLM.
3. Save (usecases/markWordFromLookup.ts) — builds a VocabularyEntry: noun lemmas are gender-prefixed (der Kopf), the click is located to a book-absolute rawStart (via the dual-coordinate map — raw-display-cursor), and paragraph-level context is captured (paragraph → sentence → page-substring fallback) for the flashcard.
4. Underline everywhere — on book open, loadBookVocabulary paints cache-first then revalidates, and runs the all-forms migration (vocabFormsMigration): group entries by bookId::lemma, keep the earliest survivor (preserving SRS history), resolve the complete form-set (LLM first, dict fallback), rewrite word→lemma, delete duplicates — idempotent, concurrency-guarded. Then all-forms-underline paints every form.
5. Review — server-owned SRS. The client only sends a grade ('dont_know' | 'fuzzy' | 'know') to /reviews; the actual SM-2 lives in Go (mainbackend/internal/domain/review/srs.go — ease deltas −0.2/−0.15/+0.1 clamped ≥1.3, dont_know→1 day, know 1→6→×ease, grade validated server-side). Putting the algorithm on the server means the schedule is consistent across web and desktop and can't be gamed by a stale client.
The loop is why "intensive reading" works: reading produces graded vocabulary, and saved vocabulary feeds back into the reading surface as ambient underlines.