The garden learns which language a card is in, and read-aloud stops guessing

Phase 28 (c), the last of the phase. A word met inside a Portuguese
document is a Portuguese card: migration 0018 mirrors documents.doc_lang
onto vocab_words, set server-side from the ownership lookup capture was
already making. Every card still reviews — filtering the queue to the
half she is learning would drop the words she actually met.

Read-aloud was the larger surprise. detectLang routed Han/kana to
Chinese and everything else to en-US, so the zh pair was accidentally
right and every Latin pair wrong. doc_lang now reaches the client
read-only on the document JSON, and docLang(text, verdict) answers for a
passage taken out of it — with the script test still winning, because
quoted Chinese must never be spelled out one "Chinese letter" at a time.

Claude-Session: https://claude.ai/code/session_01GJHNvirh7Hzhc9RL3HAvz7
This commit is contained in:
prosolis
2026-07-28 23:45:26 -07:00
parent 29eb2fe1fc
commit 466055020f
15 changed files with 352 additions and 57 deletions
+7 -2
View File
@@ -28,6 +28,10 @@ type Phrase struct {
Meaning string // why it's better — the suggestion's explanation
Example string // the sentence she met it in, already corrected
DocID *string // where, so "where did I see this?" stays one tap
// Lang is the document's verdict ('' | 'en' | 'pair'), because the chunk is
// lifted out of her own prose and is therefore in whatever language that
// prose is. See migration 0018.
Lang string
}
// Phrase-card caps. A collocation is a short chunk; anything longer is a
@@ -84,13 +88,14 @@ func Plant(ex Execer, userID string, p Phrase) (bool, error) {
return false, nil
}
res, err := ex.Exec(
`INSERT INTO vocab_words (user_id, word, gloss, definition, phonetic, example, doc_id, due_at, interval_days)
VALUES (?, ?, '', ?, '', ?, ?, datetime('now', '+1 day'), 1)
`INSERT INTO vocab_words (user_id, word, gloss, definition, phonetic, example, doc_id, lang, due_at, interval_days)
VALUES (?, ?, '', ?, '', ?, ?, ?, datetime('now', '+1 day'), 1)
ON CONFLICT(user_id, word) DO NOTHING`,
userID, key,
clamp(strings.TrimSpace(p.Meaning), maxDefinitionLen),
clamp(strings.TrimSpace(p.Example), maxExampleLen),
p.DocID,
p.Lang,
)
if err != nil {
return false, err