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
+34
View File
@@ -99,6 +99,25 @@ export const zh: Pack = {
en: `Wow — ${words} words already! Amazing. 🎉`,
}),
// A small thing to write about, offered once a day to an empty page. Each
// one is a memory or an opinion rather than an exercise — nothing here can
// be got wrong, which is the point. Fifty words is a deliberately small ask.
invitations: [
{ native: '写 50 个字:今天让你微笑的一件小事 🌸', en: 'Write 50 words: one small thing that made you smile today.' },
{ native: '写 50 个字:今天吃到最好吃的东西', en: 'Write 50 words: the best thing you ate today.' },
{ native: '写 50 个字:你现在窗外看到了什么?', en: "Write 50 words: what you can see out of your window right now." },
{ native: '写 50 个字:一个你很想再去一次的地方', en: 'Write 50 words: somewhere you would happily go back to.' },
{ native: '写 50 个字:这个星期学到的一件事', en: 'Write 50 words: one thing you learned this week.' },
{ native: '写 50 个字:给一年后的自己说句话', en: 'Write 50 words: something to tell yourself a year from now.' },
{ native: '写 50 个字:最近听到的一首歌', en: 'Write 50 words: a song you have had on lately.' },
{ native: '写 50 个字:今天想谢谢谁?', en: 'Write 50 words: someone you would like to thank today.' },
],
inviteAccept: '好,来写 · Lets write',
inviteDecline: '今天不了 · Not today',
// The entire consequence of saying no. No streak was broken, because there
// is no streak.
declined: { native: '好吧,我继续睡 😴', en: 'Fair enough — back to my nap. 😴' },
// Keyed by the companion ids in Companion/companions.ts.
names: {
cat: '瞌睡猫',
@@ -131,8 +150,23 @@ export const zh: Pack = {
itsOwn: '“its” = “it is”;表示“它的”要用 “its”,所以是 “its own”。',
itsIs: (rest) => `这里应该是 “its ${rest}”(it is),“its” 是“它的”。`,
thanNotThen: (word) => `比较的时候用 “than”,不是 “then”:“${word} than”。`,
preposition: (wrong, right) => `英文习惯说 “${right}”,不是 “${wrong}”,这个介词是固定搭配。`,
collocation: (wrong, right) => `英文里这两个词一般这样搭:“${right}”,而不是 “${wrong}”。`,
doubleComparative: (lead, word) => `${word}” 本身已经是比较级了,前面不用再加 “${lead}”:写 “${word}” 就好。`,
peopleArePlural: (verb) => `“people” 在英文里是复数,要用 “people ${verb}”。`,
ageIsNotHave: (years) => `英文说年龄用 be 动词,不是 have:“I am ${years} years old”。`,
agreeIsAVerb: '“agree” 本身就是动词,前面不用加 be:说 “I agree” 就好。',
forNotSince: (duration) => `表示持续了多久用 “for”:“for ${duration}”;“since” 后面接的是起点(since 2020)。`,
veryBeforeVerb: (verb) => `“very” 只能修饰形容词,不能直接放在动词前面:可以说 “really ${verb}”,或者 “${verb} … very much”。`,
turnOnNotOpen: (thing, on) => `英文里电器是 turn ${on ? 'on' : 'off'},不用 open/close:“turn ${on ? 'on' : 'off'} the ${thing}”。`,
althoughOrBut: (word) => `英文里 “${word}” 和 “but” 只用一个就够了,不像中文的“虽然…但是…”。`,
},
// The zh pair has no false friends: the trap needs a shared script, and 汉字
// and the Latin alphabet never look alike. An empty list here is the honest
// answer, not an unwritten one.
falseFriends: {},
docs: {
sortRecent: '最近 · Recent',
sortTitle: '标题 · Title',