The translate card, pointed the other way, and a call that no longer happens
Phase 28's step (b): both remaining items are about direction, and both had
a wrong answer that looked right.
isTranslation could not simply be read backwards. readsAsEnglish is a
deliberately low bar — Latin letters, not swamped by another script — which
every Portuguese sentence clears as easily as English does, so swapping its
two halves would have called every genuine Portuguese correction inside a
Portuguese document a translation. The flipped direction uses sentenceLang
from doclang.go instead, where English has its own curated marker list and
has to out-evidence the pair language to win. The English-document path is
untouched; reconcilePending carries the verdict to ask the question the
right way round.
The tap-through's whole observable change is a model call that stops
happening. /suggestions/{id}/translate now recovers the explanation's
language by re-running targetFor rather than assuming the pair, which gives
today's answer everywhere except the case that was broken: the Portuguese
writer whose explanation already arrived in Portuguese, previously
round-tripped through the model into Portuguese again. It answers "" there,
and the client's existing `res.translation.trim() || explanation` fallback
seeds the bubble with the explanation itself — no frontend change at all.
It deliberately does not render that explanation into English on the
grounds that English is technically the other half: an unasked-for
rendering into the language she is practising is noise, not a seed.
Tests pin both directions of the detector, with Portuguese-in-Portuguese as
the case the file exists for, plus four handler tests through the real
/check and /translate paths — including the skipped seed asserting the
model was never called, and the learning_pair zh learner whose English
explanation still renders into Chinese.
Left of the phase: (c) the garden's language tagging and read-aloud.
Claude-Session: https://claude.ai/code/session_01GJHNvirh7Hzhc9RL3HAvz7
This commit is contained in:
@@ -317,10 +317,22 @@ func (h *Handler) runPass(w http.ResponseWriter, r *http.Request, limiter *llm.R
|
||||
return
|
||||
}
|
||||
|
||||
// What language is this document in, and so what language should its cards be
|
||||
// written in? Computed from the whole content_text — never from `askText`,
|
||||
// which on a chunked pass is only the sentences that changed, and would put an
|
||||
// English card in a Portuguese journal the moment she edits its one English
|
||||
// line.
|
||||
//
|
||||
// Decided before the empty-document exit so every reconcile below is told the
|
||||
// same verdict. An emptied document has nothing to go on and holds whatever it
|
||||
// said last (see documentLang), which is what keeps a Portuguese journal
|
||||
// Portuguese while she clears it to start the entry again.
|
||||
docLang := documentLang(contentText, pairLang, prevLang)
|
||||
|
||||
// Nothing to analyze on an empty document — skip the LLM round-trip. The
|
||||
// family's rows go with the text they were about.
|
||||
if strings.TrimSpace(contentText) == "" {
|
||||
if err := h.reconcilePending(docID, contentText, pairLang, nil, scope, nil, nil, false); err != nil {
|
||||
if err := h.reconcilePending(docID, contentText, pairLang, docLang, nil, scope, nil, nil, false); err != nil {
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
@@ -333,12 +345,6 @@ func (h *Handler) runPass(w http.ResponseWriter, r *http.Request, limiter *llm.R
|
||||
return
|
||||
}
|
||||
|
||||
// What language is this document in, and so what language should its cards be
|
||||
// written in? Computed from the whole content_text — never from `askText`,
|
||||
// which on a chunked pass is only the sentences that changed, and would put an
|
||||
// English card in a Portuguese journal the moment she edits its one English
|
||||
// line.
|
||||
docLang := documentLang(contentText, pairLang, prevLang)
|
||||
if docLang != normalizeDocLang(prevLang) {
|
||||
if _, err := h.DB.Exec(
|
||||
`UPDATE documents SET doc_lang = ? WHERE id = ? AND user_id = ?`,
|
||||
@@ -376,7 +382,7 @@ func (h *Handler) runPass(w http.ResponseWriter, r *http.Request, limiter *llm.R
|
||||
if len(changed) == 0 {
|
||||
// Every sentence has already been read. Drop the rows whose sentence is
|
||||
// gone, keep the rest exactly as they are, and answer immediately.
|
||||
if err := h.reconcilePending(docID, contentText, pairLang, nil, scope, chunks, nil, false); err != nil {
|
||||
if err := h.reconcilePending(docID, contentText, pairLang, docLang, nil, scope, chunks, nil, false); err != nil {
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
@@ -421,7 +427,7 @@ func (h *Handler) runPass(w http.ResponseWriter, r *http.Request, limiter *llm.R
|
||||
|
||||
// A whole-document pass re-read everything, so every one of its rows is up for
|
||||
// re-proposal; a chunked pass only puts the sentences it asked about in play.
|
||||
if err := h.reconcilePending(docID, contentText, pairLang, raw, scope, chunks, fresh, !scope.chunked); err != nil {
|
||||
if err := h.reconcilePending(docID, contentText, pairLang, docLang, raw, scope, chunks, fresh, !scope.chunked); err != nil {
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user