Finish Phase 22: the half of Petal that works with the tunnel down

Grammar lite, the false-friend list, the daily invitation and the offline
miscollocations — the four remaining §5–§6 items, all client-side and all
alive on a box that cannot reach the model.

The offline collocations forced a schema change. `type` had been doubling
as the answer to "which engine found this" — `mechanics` meant offline —
and that stops being true the moment an offline rule proposes a
collocation. Migration 0013 adds `source` (llm | local) and every pass now
scopes its DELETE by engine; without it the coach silently wiped every
offline chunk on the page. Existing rows backfill by type, so a pre-0013
collocation row is claimed as the coach's, which it was: the offline list
did not exist yet.

The rule pack is hand-curated rather than mined, and the entries left out
are the point — `married with` is wrong until "married with children",
`arrive to` wants at or in depending on the noun. A pack running on every
keystroke must not correct correct writing.

Claude-Session: https://claude.ai/code/session_016y6gyuHkQXPiEuW8RGQyua
This commit is contained in:
prosolis
2026-07-27 15:05:55 -07:00
parent e9b8595456
commit 1bbc8fc8d3
20 changed files with 1678 additions and 50 deletions
+110
View File
@@ -115,6 +115,23 @@ export const ptPT: Pack = {
en: `Wow — ${words} words already! Amazing. 🎉`,
}),
// Uma coisa pequena para escrever, oferecida uma vez por dia a uma página
// em branco. Memórias e opiniões, nunca exercícios: não há aqui nada que se
// possa errar, que é precisamente a intenção.
invitations: [
{ native: 'Escreve 50 palavras: uma coisa pequena que te fez sorrir hoje 🌸', en: 'Write 50 words: one small thing that made you smile today.' },
{ native: 'Escreve 50 palavras: a melhor coisa que comeste hoje', en: 'Write 50 words: the best thing you ate today.' },
{ native: 'Escreve 50 palavras: o que vês da tua janela neste momento', en: 'Write 50 words: what you can see out of your window right now.' },
{ native: 'Escreve 50 palavras: um sítio onde voltarias de bom grado', en: 'Write 50 words: somewhere you would happily go back to.' },
{ native: 'Escreve 50 palavras: uma coisa que aprendeste esta semana', en: 'Write 50 words: one thing you learned this week.' },
{ native: 'Escreve 50 palavras: algo para dizeres a ti própria daqui a um ano', en: 'Write 50 words: something to tell yourself a year from now.' },
{ native: 'Escreve 50 palavras: uma música que tens ouvido ultimamente', en: 'Write 50 words: a song you have had on lately.' },
{ native: 'Escreve 50 palavras: alguém a quem gostavas de agradecer hoje', en: 'Write 50 words: someone you would like to thank today.' },
],
inviteAccept: 'Vamos lá · Lets write',
inviteDecline: 'Hoje não · Not today',
declined: { native: 'Está bem, volto a dormir 😴', en: 'Fair enough — back to my nap. 😴' },
names: {
cat: 'Gato dorminhoco',
dog: 'Cão contente',
@@ -146,6 +163,99 @@ export const ptPT: Pack = {
itsOwn: '“its” = “it is”. Para dizer “o seu / dele”, é “its” — portanto “its own”.',
itsIs: (rest) => `Aqui é “its ${rest}” (it is); “its” é o possessivo.`,
thanNotThen: (word) => `Nas comparações usa-se “than”, não “then”: “${word} than”.`,
preposition: (wrong, right) => `Em inglês diz-se “${right}”, não “${wrong}” — esta preposição é fixa.`,
collocation: (wrong, right) => `Em inglês estas palavras andam juntas assim: “${right}”, e não “${wrong}”.`,
doubleComparative: (lead, word) => `${word}” já é o comparativo — não precisa do “${lead}”: basta “${word}”.`,
peopleArePlural: (verb) => `“People” é plural em inglês: “people ${verb}”.`,
ageIsNotHave: (years) => `Em inglês a idade é com o verbo *to be*, não com *have*: “I am ${years} years old”.`,
agreeIsAVerb: '“Agree” já é o verbo — não leva *to be* à frente: diz-se “I agree”.',
forNotSince: (duration) => `Para uma duração usa-se “for”: “for ${duration}”. O “since” marca o início (since 2020).`,
veryBeforeVerb: (verb) => `“Very” só acompanha adjectivos, não verbos: “really ${verb}”, ou “${verb} … very much”.`,
turnOnNotOpen: (thing, on) => `Em inglês os aparelhos não se abrem nem se fecham — ligam-se e desligam-se: “turn ${on ? 'on' : 'off'} the ${thing}”.`,
althoughOrBut: (word) => `Em inglês usa-se “${word}” ou “but”, nunca os dois na mesma frase.`,
},
// Os falsos amigos entre o português e o inglês — a armadilha que faz uma
// pessoa sentir-se tola em vez de apenas corrigida. Por isso são só um aviso:
// o Petal nunca troca a palavra, porque “actually” pode muito bem ser mesmo a
// que ela queria.
falseFriends: {
actually: {
native: '“Actually” quer dizer *na verdade*, não *actualmente*. Para “actualmente” diz-se “currently” / “nowadays”.',
en: '“Actually” means *in fact*. For the Portuguese *actualmente*, English uses “currently”.',
},
eventually: {
native: '“Eventually” quer dizer *por fim, mais cedo ou mais tarde* — não *eventualmente*. Para isso: “possibly” ou “if necessary”.',
en: '“Eventually” means *in the end*, not *possibly*.',
},
pretend: {
native: '“Pretend” é *fingir*. Para *pretender* diz-se “intend” ou “plan to”.',
en: '“Pretend” means to fake something; the Portuguese *pretender* is “intend”.',
},
realize: {
native: '“Realize” é *aperceber-se*. Para *realizar* (fazer, levar a cabo) diz-se “carry out” ou “achieve”.',
en: '“Realize” means to become aware; *realizar* is “carry out”.',
},
assist: {
native: '“Assist” é *ajudar*. Para *assistir a* (ver) diz-se “attend” ou “watch”.',
en: '“Assist” means to help; *assistir a* is “attend” or “watch”.',
},
library: {
native: '“Library” é a *biblioteca*. A *livraria* é “bookshop” / “bookstore”.',
en: '“Library” is where books are lent; a shop that sells them is a “bookshop”.',
},
parents: {
native: '“Parents” são só o pai e a mãe. Os *parentes* são “relatives”.',
en: '“Parents” means mother and father only; *parentes* are “relatives”.',
},
sensible: {
native: '“Sensible” é *sensato*. Para *sensível* diz-se “sensitive”.',
en: '“Sensible” means level-headed; *sensível* is “sensitive”.',
},
push: {
native: '“Push” é *empurrar* — o contrário de *puxar* (“pull”).',
en: '“Push” is the opposite of *puxar*, which is “pull”.',
},
college: {
native: '“College” é o ensino superior. O *colégio* (escola) é “school”.',
en: '“College” is higher education; a *colégio* is a “school”.',
},
exit: {
native: '“Exit” é a *saída*. O *êxito* é “success”.',
en: '“Exit” is the way out; *êxito* is “success”.',
},
fabric: {
native: '“Fabric” é o *tecido*. A *fábrica* é “factory”.',
en: '“Fabric” is cloth; a *fábrica* is a “factory”.',
},
lecture: {
native: '“Lecture” é uma *aula / palestra*. A *leitura* é “reading”.',
en: '“Lecture” is a talk; *leitura* is “reading”.',
},
notice: {
native: '“Notice” é *reparar* ou um *aviso*. A *notícia* é “news”.',
en: '“Notice” is to spot something; *notícia* is “news”.',
},
support: {
native: '“Support” é *apoiar*. Para *suportar* (aguentar) diz-se “put up with” ou “bear”.',
en: '“Support” means to back someone up; *suportar* is to “bear”.',
},
costume: {
native: '“Costume” é um *disfarce / traje*. Os *costumes* são “customs” ou “habits”.',
en: '“Costume” is fancy dress; *costumes* are “customs”.',
},
deception: {
native: '“Deception” é *engano*. A *decepção* é “disappointment”.',
en: '“Deception” means being misled; *decepção* is “disappointment”.',
},
injury: {
native: '“Injury” é uma *lesão*. A *injúria* é “insult”.',
en: '“Injury” is physical harm; *injúria* is an “insult”.',
},
balcony: {
native: '“Balcony” é a *varanda*. O *balcão* é “counter”.',
en: '“Balcony” is a terrace; a *balcão* is a “counter”.',
},
},
docs: {