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:
@@ -220,3 +220,43 @@ describe('the pt-PT pack', () => {
|
||||
expect(ptPT.editor.alsoIn).not.toBe(zh.editor.alsoIn)
|
||||
})
|
||||
})
|
||||
|
||||
// False friends are a per-pair dataset rather than copy: the Latin pairs carry
|
||||
// the traps their writers actually fall into, and the zh pair legitimately has
|
||||
// none. Both halves of that are worth pinning.
|
||||
describe('false friends', () => {
|
||||
it('the zh pair has none, because the trap needs a shared script', () => {
|
||||
expect(Object.keys(zh.falseFriends)).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('the pt-PT pair carries the ones that cost most', () => {
|
||||
// Not an exhaustive list — these are the four every European Portuguese
|
||||
// speaker meets in their first month of writing English.
|
||||
for (const word of ['actually', 'pretend', 'realize', 'library']) {
|
||||
expect(ptPT.falseFriends[word], word).toBeDefined()
|
||||
}
|
||||
expect(Object.keys(ptPT.falseFriends).length).toBeGreaterThan(10)
|
||||
})
|
||||
|
||||
it('is keyed by the lowercase English word, so a lookup can find it', () => {
|
||||
for (const p of PACKS) {
|
||||
for (const key of Object.keys(p.falseFriends)) {
|
||||
expect(key, `${p.code}: "${key}" must be lowercase`).toBe(key.toLowerCase())
|
||||
expect(p.falseFriends[key].native.trim(), key).not.toBe('')
|
||||
expect(p.falseFriends[key].en.trim(), key).not.toBe('')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// The heads-up must never read as an accusation: she may well have meant the
|
||||
// word. It says what the English one means and stops there.
|
||||
it('never tells her she is wrong', () => {
|
||||
const forbidden = /wrong|mistake|error|incorrect|don't use|do not use|errado|erro|incorrecto/i
|
||||
for (const p of PACKS) {
|
||||
for (const [key, line] of Object.entries(p.falseFriends)) {
|
||||
expect(line.native, `${p.code}/${key}`).not.toMatch(forbidden)
|
||||
expect(line.en, `${p.code}/${key}`).not.toMatch(forbidden)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user