d961a89bf80447839429bcbdb6e5628c2354cde6
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7fa98d03c7 |
Both sections of the advice arrived in the language she is not learning
Reported from the phone: "I have my main language set as Portuguese and I say
I'm learning English but yet Petal presents the Ask Petal advice in both
sections as Portuguese."
Nothing was wrong with targetFor again. A Portuguese document by a Portuguese
writer is explained in Portuguese, which is the whole point of Phase 28. The
card was right. What was wrong was the tap underneath it: /suggestions/{id}
/translate answered "" for exactly that case, on the reasoning that an
unasked-for English rendering of an explanation she can already read is not a
seed but noise. That reasoning had the writer facing the wrong way. She is
learning English. The half she is *practising* is the half worth a tap, and the
bubble sits directly beneath the explanation inside the same card, so answering
"" left her with Portuguese, the same Portuguese again, and no English anywhere
on the card. targetFor's own comment promised the other language stays one tap
away in both directions; only one direction had ever been built.
So the endpoint keeps the one rule it always claimed: render into whichever
half the explanation is not already in. English explanation into her language,
as before; her language into the English she is learning, which is new. Both
ends of that are now parameters — TranslateMessages took the source language
for granted as English because until Phase 28 it always was. The zh prompt is
unchanged byte for byte, which its test still pins.
The client fallback was the same symptom from a different cause and would have
survived the server fix: an empty answer, or an unreachable model, seeded the
bubble with the explanation itself — a verbatim repeat of the line two above
it, which reads as Petal replying in the language the tap was pressed to
escape. With the endpoint always having somewhere to go, empty now means only
that the model didn't answer, so the panel opens with no bubble at all and the
input where she can ask. AskPetal no longer takes the explanation as a prop; it
never needed anything but the id.
The test that pinned the refusal now pins the rendering, and carries the report.
Claude-Session: https://claude.ai/code/session_01KGACAtTPjvZ2PipDZ5qD99
|
||
|
|
76dede8856 |
Correct the language she wrote in, not the one she was practising
Every pass was English-shaped: CheckpointMessages took the text and the tone and
nothing else, so there was never a language decision to get wrong. On the live
build two pt-PT sentences drew no cards at all — Petal read the Portuguese, said
nothing about it, and filed a mechanics note about the one English line.
The rule is two decisions reading different state. What gets corrected follows
the document. What language the explanation is written in follows the writer —
the half of her pair she is not learning, from users.direction — because an
explanation is teaching, and teaching lands in the language she reads most
easily. Those coincide for every account that exists today (learnerPairs is
{"zh"}), which is a fact about the roster and not about the design, so Target
keeps them apart. It carries a third language too: the collocation gloss is
addressed to her rather than to the document, and folding it into Explain would
have quietly moved it into English on every English document.
The document verdict is a proportion, not a presence — one Portuguese quotation
must not flip an English essay. Per sentence, three-way: pair, English, or no
answer. The third value is the load-bearing one; counting the undecided as
English is exactly what would hold a journal of short Portuguese sentences in
English forever, so the Latin pairs needed an englishMarkers list curated against
pt/fr/es as carefully as latinMarkers was curated against English. Hysteresis at
70/40 because a bilingual paragraph would otherwise alternate its cards' language
every few keystrokes, and hysteresis needs a yesterday — hence the column. Plus a
corroboration floor: a ratio computed over "Não. Eu." is 100% of nothing, and a
flip rewrites every card in the document.
The verdict folds into the chunk salt beside the tone, so a document that changes
language re-opens every sentence rather than serving back cards in a language it
no longer speaks.
checkpointSystemPrompt could not simply take a language — it opens by naming the
reader an ESL learner, and appending "explain in Portuguese" hands the model two
contradictory framings. Separate constants, sharing the JSON contract below the
framing. Both carry a "never translate it into English" line, which is the
instruction the model will most want to disobey. The English prompts are
untouched byte for byte, and a golden says so out loud.
Collocation deliberately did not move: its prompt is per-language knowledge, not
framing, and "natives usually say" for Portuguese is a claim Petal cannot back.
Not deployed and not smoked against a real model. The tests drive the real router
and a real DB; what none of them prove is how Qwen behaves on a Portuguese
document, in particular whether the never-translate line holds.
Claude-Session: https://claude.ai/code/session_01GJHNvirh7Hzhc9RL3HAvz7
|
||
|
|
978cb80642 |
Ask Petal answers in both languages, with room to read
The tutor prompt said "never mix languages in a single response" and mirrored the language of the question, so asking in English — which she does, because she is practising — returned the one explanation surface that gives nothing in her own language. It now answers in both, pair language first, halves separated by a blank line. Which half is the safety net and which is the lesson depends on who is writing: the pair is (English + X) and Petal is used from both ends, so the prompt asks for both and says it doesn't know which way round. The split is a rendering nicety, never a parse the reply depends on: a half-streamed reply is all one half, a model that ignores the instruction renders as one block, and nothing is ever dropped. For the height, the first attempt clamped the box to the room left below the anchored card so it could never overhang — measured, that gave 176px against a 442px answer, worse than the 220px it replaced. The card's own chrome spends ~290px of an 810px window, so "fits below the word" and "room to read" are not both available. The ceiling is now a flat 50vh and the overhang is made navigable instead, per item 4: the card reports its reach like the rail already does, the column grows, and the page can scroll to the actions below it. |
||
|
|
336cae93e0 |
Phase 19: the copy stops being hardcoded Mandarin
Every `中文 · English` string moves out of ~29 components into web/src/i18n: one Pack type, a verbatim zh pack, and two ways to read it — usePack() for components, pack() for the modules that build a line when something happens rather than when something renders. Anything with a value in it is a function on the pack rather than a template at the call site, English pluralisation included: word order isn't universal, and a pack author has to be able to move the number. The roster constants (tones, rewrite styles, export formats, companions) keep only value + emoji, so a label can't drift from its key. On the server, internal/llm/lang.go replaces "Simplified Chinese" in the three prompts that actually name her language. pt-PT is spelled "European Portuguese (pt-PT, never Brazilian Portuguese)" in the prompt itself, and each Lang carries her word for "why" so the tutor prompt still recognises the question when she asks it her way. pair_lang reaches the model through the row-scoped query each handler already ran — the one that proves she owns the document — rather than a second lookup that could disagree with it. Also records Phase 18's deploy: migration 0011 rehearsed against a copy of the live VPS database, then applied for real. |