The es pair, and a dictionary that was quietly Spain's
Phase 25. Spanish was never built — the groundwork was all [x] (DreamDict data, the prompt language, the L1 rule gating, TTS env-discovery), which is why the plan read as though it had shipped. shippedPairs was the honest answer: the server had been refusing es on purpose. The langpack is neutral Latin American, chosen with the user: tú, ustedes, no vosotros, and the pan-American half of every vocabulary split. A vitest greps for the peninsular twins the way fr is greped for québécismes — including coger, which is not merely regional but obscene through most of Latin America. The dictionary is the story. Debian's hunspell-es symlinks twenty country codes to one file, which reads as pan-Hispanic; RLA publishes twenty-four builds per release, one per country plus a generic es that is the union, and Debian ships peninsular es_ES. The 58,622-form gap is essentially voseo, so the first version of this commit underlined vení and tenés as misspellings and called it a considered gap. The MUST_ACCEPT list was written to catch exactly that and structurally could not: it asserted the pan-Hispanic vocabulary, and every RLA variant carries the full pan-Hispanic vocabulary — only the paradigms are localised. The REP table cited as the second witness is shared by all builds too. Two independent-looking proofs, neither able to distinguish anything, agreeing with each other. The profile now demands what discriminates, each verified against the build it targets: voseo rejects es_ES and Debian, vosotros rejects es_MX, and arepa/chévere/bacán reject es_AR, which has both paradigms and would otherwise pass. 717,640 forms, 1.74 MB gzipped, 762 ms / 97 MB in a real nspell. fr and pt-PT rebuild byte-identical from their own upstream debs, so the shared script still means what it meant. Shipping the union is fr's call arrived at from the other side: coût and cout are both correct French, tienes and tenés are both correct Spanish. The dictionary holds every variety because underlining is all it can do; the copy picks a register because speaking requires one. Reviewed by four models at the usual >=2-of-4 threshold, 5 of 27 findings applied — one catching the bedtime proverb as fr's Qui dort dîne calqued into Spanish, gloss and all, which is the rule the fr header states. One below-threshold finding (a missing ¡, seen by 1 of 4 because an absent opening mark has no closing ! to look wrong against) was applied and turned into an assertion instead: the suite now rejects any native line that closes ? or ! without opening one. piper-es on es_MX-ald-medium, not the es_ES-davefx-medium the plan named — six of Piper's nine Spanish voices are peninsular, so the obvious pick was the pt-PT trap through a different door. go build/vet/test, tsc, vite, vitest 251/251. Not deployed, not seen in a browser, not read by a native speaker, and no es account exists.
This commit is contained in:
+40
-3
@@ -330,12 +330,48 @@ Scope agreed with the user 2026-07-27: *"switcher for Chinese and Portuguese now
|
||||
- Verified: go build/vet, `go test ./...` clean, tsc, vite build, vitest 190/190 (33 in the i18n suite after the review pass). **Not seen in a browser** — no Chrome extension on this laptop; the 1.19 MB dictionary inflating in a real tab and the picker's third entry in a real mobile drawer are what unit tests cannot cover.
|
||||
- **Not deployed.** No migration, so it is a rebuild whenever the user wants it; the Piper sidecar wants `docker compose up -d piper-fr` and a voice download on the box.
|
||||
|
||||
### Phase 25 (planned) — the es pair
|
||||
Everything above, minus the surprises: item 3's expander now handles what Spanish's `es_ES.aff` is likely to need (single-char flags, no compounding), so the work is the langpack, a native review, `hunspell-es` (packaged per country — check what `es_ES` actually is before vendoring), a `piper-es` service with `es_ES-davefx-medium`, and nothing at all for the lexicon: es is the *best*-covered pair in `dict.db` at 68.6%.
|
||||
### Phase 25 ✅ (2026-07-28, code half) — the es pair, and the first regional decision made *for* a variety rather than against one
|
||||
Scope agreed with the user 2026-07-28: **Latin American neutral, quorum review, code only** — deploy is its own step, as with fr. The plan predicted this phase would be Phase 24 minus the surprises, and on the mechanical items it was exactly right. The surprise was the one thing the plan had already decided.
|
||||
|
||||
1. [x] **Latin American, not peninsular — and this is the first pack whose regional question had no default at all.** pt-PT's decision was forced by a packaging trap and fr's turned out not to exist; es had a genuine choice, and the user made it: *tú*, **ustedes**, no *vosotros*, and the pan-American half of every vocabulary split (*computadora*, *celular*, *carro*, *jugo*, *papa*, *departamento*, *lentes*, *boleto*). A vitest greps the pack for the peninsular twins the way fr is greped for québécismes — including **`coger`**, which is not merely regional but obscene through most of Latin America and is the one word a warm companion must never produce by accident.
|
||||
2. [x] **The dictionary — and the phase's real mistake, caught by the user asking "should we pick a different Spanish dictionary?"** The first build of this pair shipped Debian's `hunspell-es` and this entry claimed, in some detail, that it was pan-Hispanic. It is not. `hunspell-es` installs twenty country codes all symlinked to one file, which reads like "one dictionary for all of Spanish" — but RLA (`sbosio/rla-es`) publishes **twenty-four** dictionaries per release: one per country, **plus a generic `es` that is the union of all of them**, and Debian ships the **peninsular `es_ES`** under the collapsed name. Petal now ships the generic build, taken from the upstream v2.9 release rather than from apt.
|
||||
|
||||
| build | forms | voseo | vosotros |
|
||||
|---|---|---|---|
|
||||
| Debian `hunspell-es` (first shipped) | 659,085 | ✗ | ✓ |
|
||||
| upstream `es_ES` | 659,018 | ✗ | ✓ |
|
||||
| upstream `es_MX` | 554,923 | ✗ | ✗ |
|
||||
| upstream `es_AR` | 669,605 | ✓ | ✓ |
|
||||
| **upstream `es` (generic, shipped)** | **717,640** | **✓** | **✓** |
|
||||
|
||||
- **The 58,622-form gap is essentially the voseo paradigm** — *vení*, *tenés*, *querés*, *sabés*, *andá* — the ordinary present tense of Argentina, Uruguay, Paraguay and much of Central America. Under the first build, a writer using it would have had her own verbs underlined as misspellings, and this document would have told her that was a considered decision.
|
||||
- **Why the wrong file passed a MUST_ACCEPT list designed to catch exactly this.** The profile asserted the pan-Hispanic *lexicon* — *computadora* and *ordenador*, *papa* and *patata* — and **every RLA variant carries the full pan-Hispanic vocabulary**; only the verb paradigms are localised. So the assertion was satisfiable by all twenty-four builds and discriminated nothing. The `REP` table was cited here as a second witness (`ll`↔`y`, `ás`↔`az` "are not Castilian confusions") and that was wrong too: it is shared by every build. **Two witnesses, both non-witnesses, agreeing.**
|
||||
- **The profile now demands the three things that do discriminate**, and each was verified to fail on the build it targets: **voseo** rejects `es_ES` and Debian's package; **vosotros** (`tenéis`, `escribid`) rejects `es_MX`; and **another region's everyday words** (`arepa`, `chévere`, `bacán`) reject `es_AR`, which has both paradigms and would otherwise have passed. All four neighbouring builds were run through it and confirmed refused.
|
||||
- **Accepting every variety is the same decision fr made**, arrived at from the other side: Petal ships `hunspell-fr-comprehensive` because *coût* and *cout* are both correct French, and it ships generic Spanish because *tienes* and *tenés* are both correct Spanish. The dictionary's only power is to underline, so it should hold the union; the *copy* chooses a register, because speaking requires one. The two pulling in opposite directions is not a contradiction — it is the difference between Petal speaking and Petal listening.
|
||||
- The expander needed **no changes at all**: `FLAG UTF-8` (already handled), and none of the directives it refuses — no compounding, no NEEDAFFIX, no FULLSTRIP, no CIRCUMFIX. **717,640 forms, 1.74 MB gzipped**, and in a real nspell **762 ms / 97 MB** (pt-PT: 842 ms / 139 MB). `jardin` → *jardín*, `corazon` → *corazón*, and `vení`/`tenés`/`andá` verified accepted in the shipped asset itself.
|
||||
- **Both existing dictionaries were rebuilt from their own upstream debs and are byte-identical to the shipped assets** — the check that makes "the shared script still means what it meant" a claim rather than a hope.
|
||||
3. [~] **Reviewed by a quorum of four models, not by a native speaker** — the same interim measure and the same ≥2-of-4 threshold as Phase 24, on four *different* models reading blind and returning verbatim substrings so agreement could be counted mechanically. 27 findings, **5 above threshold, all applied**:
|
||||
- `el idioma no se movió` → **No se pudo cambiar el idioma — sigue igual** (3/4; a language does not "move", and the line still has to say that nothing changed).
|
||||
- `me hice bolas` → **me enredé** (3/4) — narrowly Mexican slang in a pack whose entire premise is pan-American neutrality. The reviewers caught the copy contradicting its own header.
|
||||
- `traes en repetición` → **no has parado de escuchar** (2/4), a calque of "on repeat".
|
||||
- `restoring: 'Restoring…'` → **Restaurando… · Restoring…** (2/4), the one user-facing string left untranslated.
|
||||
- `Quien duerme, cena.` → **Dormir es el mejor remedio.** (2/4). **This is the finding worth keeping**, because it caught me breaking the packs' own rule: that line was fr's *Qui dort dîne* calqued into Spanish, English gloss and all — precisely the "a pack is not a translation of another pack" the fr header states. Reviewers split on the replacement, so per Phase 24's rule the wording is mine and the reasoning is here rather than averaged: a real, pan-Hispanic saying with its own English gloss, and not a third proverb about haste beside the two already there.
|
||||
- **One below-threshold finding applied anyway, on stated grounds**: `Qué linda elección de palabra` opens an exclamative with no `¡` (1/4). It is not a matter of taste but a mechanical orthography rule the pack's own header commits to, and the reason only one reviewer saw it is that a missing *opening* mark has no closing `!` to look wrong against. **So it became an assertion instead of a judgment** — the suite now rejects any Line opening with `Qué/Cómo/Cuánto` without `¡`, plus the general rule that a native half closing `?`/`!` must open one. The lesson is Phase 24's, one level up: what a review found once, a test should find every time.
|
||||
- **Below threshold, deliberately not applied** (1/4 each): the fronted `Cansada, escribes mal` (the fr pack's equivalent line *was* changed on a 4/4, but this one drew one vote and reads fine); `precísalo`; `me regreso a dormir`; `Perro meneacola`; `¿Tienes una duda?` → *alguna*; `A quien madruga, Dios lo ayuda` → *le* (both are current, and *lo* is the American one); the `constipada` gloss; `Hace rato`; `Longitud promedio`; `¡Sigamos!`; and three about the file's *structure* (`daysAgo` using `n > 1`, `helperRestingEn` ending in Spanish, `matchCase` putting English first) which are **shared with the fr pack by design** and are not es's to change alone.
|
||||
- **A finding about the other packs, surfaced here**: `restoring` is untranslated in **fr and pt-PT too**. Only es was fixed, because changing shipped copy is not this phase's business — but it is now written down instead of re-discovered.
|
||||
- ⚠️ **Still not a native speaker.** Four models agreeing is agreement, not authority.
|
||||
4. [x] **Piper voice — and the plan's own choice was the trap.** Phase 25 was written to use `es_ES-davefx-medium`; with the copy Latin American, that is the pt-PT mistake exactly, the wrong country arriving through the obvious default. Piper ships **nine** Spanish voices and **six are es_ES**; only `es_AR-daniela-high` and the `es_MX` pair are American. **`es_MX-ald-medium`** — Mexican is the neutral broadcast standard, and *medium* matches the register of the other four. No Go at all, again: `piper-es` is a compose service and two `.env` lines, which is Phase 21's discovery holding for the third pair running.
|
||||
5. [x] **Lexicon: nothing to do, and it is the best-covered pair Petal has** — es reaches **68.6%** of the 2,000 commonest English words, against fr's 63.1%, pt-PT's 62.1% and zh's 53.2%. `lexicon.Set.For` already routes every non-Chinese pair to DreamDict.
|
||||
- Free, because Phases 19–24 did them: `internal/llm/lang.go` has carried es since Phase 19, `grammarLite`'s L1 rules already gate *ter 30 anos* / "I am agree" / "since three years" to pt+fr+**es**, and the sidebar picker derives itself from the shipped packs — so offering **Español** is not a line of new UI.
|
||||
- Tests: `i18n.test.ts` (the peninsular grep incl. `coger`; the inverted-punctuation rule in both directions; `flor`/`flores`, which takes *-es* and is the agreement a translator gets wrong; *1 pétalo* / *2 pétalos*; the es false friends, asserted to be the **longest** list of the four because Spanish shares the most Latin with English — led by **`embarrassed`**, the false friend most likely to be said out loud to a room). `pairlang_test.go` round-trips all four pairs, and its unshipped examples moved to **`es-ES`** — the near-miss that now matters as much as `pt-BR`, since a peninsular code must not be quietly served American copy and a Mexican voice. `config_test.go` discovers a fifth voice.
|
||||
- Verified: go build/vet, `go test ./...` clean, tsc, vite build, **vitest 251/251**. ⚠️ **Not seen in a browser** — no Chrome extension on this laptop; the 1.59 MB dictionary inflating in a real tab and the picker's fourth entry in a real mobile drawer are what unit tests cannot cover.
|
||||
- **Not deployed.** No migration, so it is a rebuild whenever the user wants it; `piper-es` wants `docker compose up -d piper-es` and a voice download on the box. **No es account exists**, and all three accounts are still on `zh` — flipping a pair is hers to do from the picker.
|
||||
|
||||
### Later / explicitly not now
|
||||
- Learner-facing Chinese writing (the zh pair's second direction) — own phase with its own spec (SUGGESTIONS §4); only after Phases 19–21 prove the pair model
|
||||
- ~~Spanish pair — gated on DreamDict growing an es dataset~~ **ungated 2026-07-26** (DreamDict added Spanish). Now a normal follow-on pair after pt-PT and fr — see Phase 25.
|
||||
- ~~Spanish pair — gated on DreamDict growing an es dataset~~ **ungated 2026-07-26**, **shipped (code) 2026-07-28** — see Phase 25. What it still owes: a deploy, a native reader, and a writer who actually uses it.
|
||||
- ~~Voseo for the es pair~~ **resolved 2026-07-28 before shipping** — the fix was not to generate the paradigm but to stop using Debian's package, which is peninsular. RLA's generic build has it. See Phase 25 item 2.
|
||||
- **`restoring` is untranslated in the fr and pt-PT packs** — surfaced by the es review, fixed only in es. One line each, whenever those packs are next touched.
|
||||
- Reactive-animation puppy companion — wishlist, low priority; `companions.ts` roster + mood engine is the drop-in point
|
||||
- Copyleaks Tier-2 — revisit once Phase 15 provides a public webhook endpoint
|
||||
|
||||
@@ -347,6 +383,7 @@ Everything above, minus the surprises: item 3's expander now handles what Spanis
|
||||
- [x] **Phase 14 — companion warmth + bedtime nag + night mode**: more encouraging phrases, a gentle "go to bed" nudge after 11pm, and a calm dark theme + falling stars at night. ✅ (see Phase 14 above)
|
||||
|
||||
## Session log
|
||||
- 2026-07-28: **Phase 25 — the es pair, and a plan that had quietly chosen the wrong Spanish** (user asked where Spanish support had gone, then "yes" to starting the phase; scope chosen with the user: **Latin American neutral**, quorum review, code only). The starting point was a misreading worth recording: the plan *reads* as though Spanish shipped, because the DreamDict rebuild, the LLM language entry, the L1 rule gating and the TTS env-discovery are all `[x]` — every piece of groundwork was done and the pair itself had never been built. `shippedPairs` was the honest answer all along: the server had been refusing `es` on purpose. **The regional question was the phase.** pt-PT's was forced by packaging and fr's turned out not to exist; es had a real choice with no default, and once the user chose Latin American, the plan's own two concrete decisions were both wrong. It warned that `hunspell-es` is "packaged per country — check what `es_ES` actually is": it ships twenty country codes and **every one is a symlink to one pan-Hispanic file**, so the trap was not there. And it named **`es_ES-davefx-medium`** for the voice, which *is* the trap — six of Piper's nine Spanish voices are peninsular, so the obvious pick would have read Latin American copy in a Castilian accent, the pt-PT mistake arriving through a different door. `es_MX-ald-medium` instead. **Then the user asked "should we pick a different Spanish dictionary?" and the answer was yes** — the phase had shipped the wrong one and written a confident justification for it. Debian's `hunspell-es` symlinks twenty country codes to one file, which reads as pan-Hispanic; RLA actually publishes twenty-four builds per release, one per country **plus a generic `es` that is the union**, and Debian ships **peninsular `es_ES`**. The 58,622-form difference is essentially **voseo**: under the first build, *vení* and *tenés* — the ordinary present tense of Argentina, Uruguay, Paraguay and much of Central America — were underlined as misspellings, and this document called that a known gap handled on principle. **What makes it worth writing down is that the MUST_ACCEPT list was designed to catch exactly this and could not**: it asserted the pan-Hispanic *vocabulary*, and every RLA variant carries the full pan-Hispanic vocabulary — only the paradigms are localised — so it was satisfiable by all twenty-four. The `REP` table cited as the corroborating witness (yeísmo, seseo) is likewise shared by every build. Two independent-looking proofs, neither of which could distinguish anything, agreeing with each other. The profile now demands **voseo** (rejects es_ES and Debian), **vosotros** (rejects es_MX) and **another region's everyday words** — *arepa*, *chévere*, *bacán* (rejects es_AR, which has both paradigms and would otherwise pass); all four neighbours were run through it and confirmed refused. Shipping the union is the same call fr made between *coût* and *cout*: the dictionary's only power is to underline, so it holds every variety, while the *copy* picks a register because speaking requires one. 717,640 forms, 1.74 MB gzipped, **762 ms / 97 MB** in a real nspell, and **fr and pt-PT rebuild byte-identical** from their own upstream debs. **The quorum review earned its place twice**: four models, ≥2-of-4, 5 of 27 findings applied — one of which caught the pack's bedtime proverb being *Qui dort dîne* calqued into Spanish, English gloss and all, which is exactly the "a pack is not a translation of another pack" rule the fr header states and I had broken while writing it. And one below-threshold finding (a missing `¡` on an exclamative, seen by 1 of 4 because an absent *opening* mark has no closing `!` to look wrong against) was applied anyway and **turned into an assertion**: the suite now rejects any native line that closes `?`/`!` without opening one. That is Phase 24's lesson one level up — what a review finds once, a test should find every time. go build/vet/test, tsc, vite, **vitest 251/251**. ⚠️ **Not deployed, not seen in a browser, not read by a native speaker, and no es account exists** — all four accounts' worth of Spanish experience is still hypothetical, and the pack says so in its own header.
|
||||
- 2026-07-27: **Phase 24 — the fr pair, and a "generalizes" that did not** (user: "resume the build plan"; scope chosen with the user: French end to end, code only, deploy its own step). The plan's five items were meant to be mechanical, and four of them were — the Piper voice is a compose service and two env lines because Phase 21 made a language configuration; the lexicon needed nothing at all, fr having been measured at 63.1% during Phase 20's rebuild, better than the pair that already shipped; the sidebar picker grew a third entry without a line of UI because it derives itself from the shipped packs. **Item 3 was the one that had been recorded as done and wasn't.** `build_ptpt_dictionary.py` was said to generalize; it handled single-character flags and plain PFX/SFX and stopped on everything else, and `fr.aff` uses four of the things it stopped on. `FLAG long` is the dangerous one: French flags are two characters, so the pt-PT reader's `set(flagstr)` yields a bag of unrelated letters and expands every entry through the wrong paradigm without erroring. Plus continuation flags (French really does affix an affixed form), NEEDAFFIX on 68,075 of 84,140 stems, and FULLSTRIP. The rewritten `build_hunspell_dictionary.py` carries a per-language profile and asserts that CIRCUMFIX and FORBIDDENWORD are still unused — and **rebuilds pt-PT byte-identical to the shipped asset**, which is the only thing that makes "generalized" a claim rather than a hope. **The second decision was elision, and it was made by measuring both halves**: keeping `l'arbre` and its thirty-three siblings costs 3,159,832 forms and 8.25 MB gzipped; dropping them costs 473,326 and 1.19 MB. They are not new words, but the tokenizer keeps internal apostrophes, so they really would have been underlined — so they moved out of the dictionary and into `withElision`, which splits at a known clitic and still requires the remainder to be a word (`l'zzzz` stays flagged). Real nspell: 369 ms and 74 MB for the larger language, against pt-PT's 842 ms and 139 MB. **Where the regional trap lives is the mirror image of Portuguese's**: every `fr_*` Piper voice is fr_FR and every Debian fr dictionary is one shared word list, so nothing can be quietly wrong about the country — the whole decision is in the copy, which is why the pack is greped for *courriel* and *magasiner* the way pt-PT is greped for *arquivo*. What French does have instead is the 1990 reform, packaged three ways; Petal ships comprehensive, because Petal never corrects her French and *coût* and *cout* are both correct. go build/vet/test, tsc, vite, vitest 190/190. **Two things owed and both said plainly**: no native speaker has read the pack (SUGGESTIONS §3's bar, unmet for pt-PT too), and nothing here has been seen in a browser. **Then, same session, an interim answer to the first of those** (user: "perhaps for now, we could leverage multiple LLMs to act as reviewers?"): four models reviewed each Latin pack independently, and only findings ≥2 of them reached on their own were applied — five per pack. It earned its keep on the pack that was *already shipped*: pt-PT had **pre-Acordo spellings in a file whose own header commits to post-Acordo**, because the Phase 21 greps checked for Brazilian vocabulary and never checked the pack against its own spelling policy. That grep now exists and was confirmed to fail on the old text. Where reviewers agreed a line was wrong but split on the fix, the wording is mine and the reasoning is in the phase entry rather than averaged away. Still not a native speaker, and both packs now say so precisely.
|
||||
- 2026-07-27: **Phase 22 finished — the build plan's last four items, and the LLM stops holding anything hostage** (user: "let's finish the last phase of the build plan"; code only, no VPS work). The four remaining items shared one theme, and it only became visible while building them: **§6's left-hand column is now complete.** Spell, define, gloss, pronounce, catch the common mistakes, review vocabulary, prove authorship — every daily-writing need works with the tunnel down. **The plan asked for "grammar lite as a fourth suggestion family", and the fourth family already existed**: Phase 8's deterministic `mechanics` pass was the plumbing, so this was the rule pack it had been waiting for rather than new machinery — preposition pairs, doubled comparatives, `people is`, plus per-pair L1 interference. **Q6 answered by hand-curating rather than mining LanguageTool**: that corpus is broad because it aims at recall, and this pack aims at the exact opposite, so every entry is a pairing wrong in essentially *all* contexts and the ones only *usually* wrong were left out on purpose — `married with` is a mistake until "married with children", `arrive to` wants at or in depending on the noun, `different than` is ordinary American English. Each rule is pinned in both directions, the guard case being the correct English next to the mistake. **The L1 rules are gated by pair, and the gating is what earns them their confidence** — *ter 30 anos* → "I am 30 years old" is a near-certainty for a Portuguese writer and only a guess for anyone else. The two zh rules the plan itself named are the ones this pack **refuses** to implement: dropped articles and he/she slips are not detectable from text alone ("She said he was late" is perfect whichever pronoun was meant), and flagging them would mean correcting correct writing. **The miscollocation list forced the session's one real design change.** It had to file as `collocation` rather than as its own family — same rail, same phrasing, and an accepted chunk plants in the garden exactly as the coach's would — but `type` had been quietly doubling as the answer to *which engine found this*, and that breaks the instant an offline rule proposes a collocation. Migration `0013_suggestion_source` splits the two apart: each pass now scopes its DELETE by engine, and the span tiebreak moved with it (an exact offline card beats an overlapping LLM one by source, not by type — an offline miscollocation is as exact as an offline comma). Without it the coach silently wiped every offline chunk on the page and the offline pass left the coach's rows to pile up; both directions are now tested, and a pre-0013 collocation row correctly backfills to the coach, since the offline list did not exist yet. **The daily invitation's whole substance is one stored date** — no count, no run of days, nothing that gets worse for being away, so a month away reads exactly like a day away; it lives in its own file because that is the property this feature would lose silently, and the test is named for it rather than for the query. Both answers spend the day's invitation, because being asked again after "not today" would make no a negotiation. **False friends are the one thing here that never becomes a card**: ~19 curated en↔pt entries, shown as a lavender block above the WordCard's definition and as at most one companion note per pass, with no `fix` anywhere — *actually* may well be the word she meant, and this is the mistake that makes a learner feel foolish rather than merely corrected. zh has none, which is the honest answer and not an unwritten one: the trap needs a shared script. Copy for the invitation and the false friends is greped by tests the same way the journal's is (*streak / in a row / 连续 / todos os dias*; *wrong / mistake / errado*) — the framing is the feature, and it is the part a future edit would undo while meaning well. Verified: go build/vet, `go test ./internal/...` clean, tsc, vite build, vitest 172/172 (30 new rule cases, 7 invitation, plus false-friend shape/tone guards), and a live throwaway DB on :8099 with **no LLM configured at all** — offline `did a mistake` → card → accept → garden card *made a mistake*, example bounded to its own corrected sentence, journal `kept:1`. ⚠️ **Not deployed and not seen in a browser**, and this one carries a migration, so it is a deploy rather than a rebuild. The pt-PT copy added here joins the pack a native speaker still has not reviewed.
|
||||
- 2026-07-27: **Phase 21 deployed — the pt-PT pair has a voice** (user: "continue the build plan"; scope chosen: deploy Phase 21 to the VPS rather than start Phase 22). The plan's remaining line was "Piper pt-PT voice instance on parodia", and it hid two things. **A language was still a code change**: read-aloud knew exactly two, named in the Config struct as `TTSEndpointZH`/`TTSVoiceZH`, so adding Portuguese meant editing Go to add Portuguese. Petal now discovers its Piper instances from the environment — English keeps the unsuffixed pair, everything else is `TTS_ENDPOINT_<LANG>`/`TTS_VOICE_<LANG>`, base tag only because an env var name cannot hold pt-PT's hyphen — and a language configured by halves is dropped rather than routed, so it reaches the client as "no voice, use Web Speech" instead of erroring on every tap. fr and es now cost a compose service and two `.env` lines. **And the voice itself repeated Phase 21's own lesson in a new place**: `pt_PT-tugão-medium` is the *only* European Portuguese voice in Piper's catalogue — the other five are Brazilian — so, exactly as with `dictionary-pt` packaging VERO, the default anyone reaches for ships the wrong country. Then it wouldn't download at all: `piper.download_voices` pastes the voice name into the HTTP request line and `http.client` encodes that as ASCII, so it dies with `UnicodeEncodeError` on the *ã* before a byte leaves the container — a failure that lands on precisely the one voice this pair needs and on no other. The entrypoint falls back to fetching the model and its config itself with the path percent-encoded, which is all the downloader was missing. **The slow replay** (§5e) went in while there: `slow: true` raises `length_scale` to ~4/3, and the pace is part of the **cache key** — without that, asking to hear slowly a word already heard at speed serves the fast clip back, which is the one request where the difference is the entire point. **The L1 voice asks the pack, not the letters**: a new `locale` field, because "comum" is spelled the same in both halves and a detector would have to guess — the same reason the gloss shows both directions. **Deploying is what finally ran the reverse lookup against real data**, the item the previous session left open because this laptop has no `dict.db`: *data* → "date", *comum* → "common; usual", *tarde* → "evening; afternoon", *ali* → "there", with *think*, *computer* and *garden* correctly silent; and *think* glossing to **pensar** first confirms Phase 20's sense-agreement ordering on the real 550 MB database rather than on a fixture. zh flipped back is byte-for-byte ECDICT again. go build/vet/test, tsc, vitest 125/125, vite; laptop smoke against two fake Pipers, then the real thing on the box. Her data untouched: 8 documents, 33 versions, 103 suggestions, FTS matching, integrity ok, `schema_migrations` still at 11 (no migration in this phase). **Two things Phase 21 still owes, both said plainly**: the pack has not been read by a pt-PT speaker, and no pt-PT account exists — both writers are on the zh pair, so nothing she sees changed today and the browser half of the Portuguese experience has never had a human in front of it.
|
||||
|
||||
Reference in New Issue
Block a user