Let the garden keep what she was given, not only what she sought
Two halves of the same idea, both read out of work Petal already records. Planting: an accepted collocation is a learnable chunk, so it becomes a phrase card. The scheduler didn't need to know — a three-word chunk climbs the ladder exactly like a looked-up word. What needed care was deciding what *isn't* a chunk (single words are word choice; a six-word-plus "collocation" is a rewritten sentence, and sentences make miserable flashcards), and that the example must be the *corrected* sentence — the stored draft still holds the phrasing she just left behind. Re-accepting the same chunk leaves the existing card alone rather than resetting a schedule it has been climbing. The whole thing is best-effort: accepting an edit must never fail because a flashcard couldn't be made. The growth journal: kept this month beside kept the month before, the phrasing that stuck, the patterns that faded. The queries were the easy part; the honesty is the feature. "Stuck" needs the phrase in a *second* document, because one document is just the edit where she left it. "Faded" says nothing at all unless she has been writing lately — otherwise a month away from Petal comes back to her as progress, which is the one way this could lie. And a suggestion had to start recording when she *decided* it, not when the model proposed it, so 0012 adds resolved_at and backfills the old rows to their created_at. It lives as a second tab in the garden, and it feeds the kitten: after an accept she now sometimes hears something true of her alone, once per line, half the time, never waited for. Claude-Session: https://claude.ai/code/session_016y6gyuHkQXPiEuW8RGQyua
This commit is contained in:
@@ -97,6 +97,9 @@ describe('the zh pack', () => {
|
||||
expect(zh.garden.growing(2)).toContain('2 blossoms growing')
|
||||
expect(zh.history.daysAgo(1)).toBe('1 day ago · 1 天前')
|
||||
expect(zh.history.daysAgo(3)).toBe('3 days ago · 3 天前')
|
||||
expect(zh.journal.kept(1)).toContain('1 thing you took on board')
|
||||
expect(zh.journal.kept(9)).toContain('9 things you took on board')
|
||||
expect(zh.journal.stuck('make a decision', 3)).toContain('3 of your pieces')
|
||||
})
|
||||
|
||||
// A pack with a hole in it renders an empty label rather than failing, which
|
||||
@@ -194,6 +197,20 @@ describe('the pt-PT pack', () => {
|
||||
expect(ptPT.garden.reviewDue(4)).toContain('4 palavras ·')
|
||||
expect(ptPT.garden.growing(1)).toContain('1 flor no jardim')
|
||||
expect(ptPT.garden.growing(3)).toContain('3 flores no jardim')
|
||||
expect(ptPT.journal.kept(1)).toContain('1 coisa que')
|
||||
expect(ptPT.journal.kept(5)).toContain('5 coisas que')
|
||||
})
|
||||
|
||||
// The growth journal is the one surface that talks about her progress, so it
|
||||
// is the one most easily spoiled by a stray comparison. The rule is enforced
|
||||
// in SQL on the backend; here it is enforced in the copy.
|
||||
it('keeps the journal to growth and to her own past self', () => {
|
||||
const text = JSON.stringify({ zh: zh.journal, pt: ptPT.journal }, (_k, v) =>
|
||||
typeof v === 'function' ? JSON.stringify(v(2, 3)) : v,
|
||||
)
|
||||
for (const bad of ['error', 'mistake', 'wrong', 'streak', 'average', 'erro', 'errada', '错误']) {
|
||||
expect(text.toLowerCase(), `the journal must not talk about "${bad}"`).not.toContain(bad)
|
||||
}
|
||||
})
|
||||
|
||||
it('says the collision line the zh pair never needed', () => {
|
||||
|
||||
@@ -258,6 +258,30 @@ export const ptPT: Pack = {
|
||||
gradeEasy: { native: 'Fácil', en: 'Easy' },
|
||||
},
|
||||
|
||||
journal: {
|
||||
tabGarden: '🌷 Jardim · Garden',
|
||||
tabJournal: '🌱 Progresso · Growth',
|
||||
subtitle: 'Your own writing, month by month — only ever you and your past self',
|
||||
empty: 'Escreve mais um pouco — esta página nasce do teu próprio trabalho. · Keep writing; this page grows out of your own work.',
|
||||
keptHead: 'Este mês · This month',
|
||||
kept: (n) => `${n} coisa${n === 1 ? '' : 's'} que aproveitaste · ${n} thing${n === 1 ? '' : 's'} you took on board`,
|
||||
keptBefore: (n) => `${n} no mês anterior · ${n} the month before`,
|
||||
stuckHead: 'Ficou contigo · Stayed with you',
|
||||
stuck: (phrase, docs) =>
|
||||
`«${phrase}» — já a usas sozinha, em ${docs} textos teus · now in ${docs} of your pieces`,
|
||||
fadedHead: 'Já não precisas de corrigir · You stopped needing this',
|
||||
faded: (pattern, times) =>
|
||||
`«${pattern}» — ${times}× nessa altura, nenhuma este mês · ${times}× back then, none this month`,
|
||||
cheerStuck: (phrase) => ({
|
||||
native: `Já escreves «${phrase}» sozinha! 🌱`,
|
||||
en: `You’re using “${phrase}” on your own now! 🌱`,
|
||||
}),
|
||||
cheerFaded: (pattern) => ({
|
||||
native: `Há já algum tempo que «${pattern}» não precisa de correção 😌`,
|
||||
en: `“${pattern}” hasn’t needed fixing in a while 😌`,
|
||||
}),
|
||||
},
|
||||
|
||||
history: {
|
||||
title: 'Histórico · History',
|
||||
kinds: {
|
||||
|
||||
@@ -244,6 +244,29 @@ export const zh: Pack = {
|
||||
gradeEasy: { native: '太简单', en: 'Easy' },
|
||||
},
|
||||
|
||||
journal: {
|
||||
tabGarden: '🌷 花园 · Garden',
|
||||
tabJournal: '🌱 成长 · Growth',
|
||||
subtitle: 'Your own writing, month by month — only ever you and your past self',
|
||||
empty: '再写一阵子,这里就会长出东西来。· Keep writing — this page grows out of your own work.',
|
||||
keptHead: '这个月 · This month',
|
||||
kept: (n) => `你采纳了 ${n} 处建议 · ${n} thing${n === 1 ? '' : 's'} you took on board`,
|
||||
keptBefore: (n) => `上个月是 ${n} 处 · ${n} the month before`,
|
||||
stuckHead: '记住了 · Stayed with you',
|
||||
stuck: (phrase, docs) => `“${phrase}” — 你后来又自己用了,出现在 ${docs} 篇里 · now in ${docs} of your pieces`,
|
||||
fadedHead: '不再需要改了 · You stopped needing this',
|
||||
faded: (pattern, times) =>
|
||||
`“${pattern}” — 以前改过 ${times} 次,这个月一次都没有 · ${times}× back then, none this month`,
|
||||
cheerStuck: (phrase) => ({
|
||||
native: `“${phrase}” 你现在自己就会用了!🌱`,
|
||||
en: `You’re using “${phrase}” on your own now! 🌱`,
|
||||
}),
|
||||
cheerFaded: (pattern) => ({
|
||||
native: `好久没见你写错 “${pattern}” 了 😌`,
|
||||
en: `“${pattern}” hasn’t needed fixing in a while 😌`,
|
||||
}),
|
||||
},
|
||||
|
||||
history: {
|
||||
title: '历史 · History',
|
||||
kinds: {
|
||||
|
||||
@@ -199,6 +199,34 @@ export interface Pack {
|
||||
gradeEasy: Line
|
||||
}
|
||||
|
||||
// The growth journal, a second tab inside the garden: what she has been
|
||||
// learning, read back out of her own accepted edits.
|
||||
//
|
||||
// Every line here is bound by two rules the backend enforces in SQL, and the
|
||||
// copy must not undo them: it reports growth rather than tallying mistakes,
|
||||
// and the only writer it ever compares her to is herself. A pack author has
|
||||
// room to change the warmth and the word order; there is no room for a line
|
||||
// that grades her, congratulates her on beating anyone, or invents a streak.
|
||||
journal: {
|
||||
tabGarden: string
|
||||
tabJournal: string
|
||||
subtitle: string
|
||||
// Nothing to say yet — a quiet month stays quiet rather than being padded.
|
||||
empty: string
|
||||
keptHead: string
|
||||
kept: (n: number) => string
|
||||
// Last month's number, offered flat: no better, no worse, just her own past.
|
||||
keptBefore: (n: number) => string
|
||||
stuckHead: string
|
||||
stuck: (phrase: string, docs: number) => string
|
||||
fadedHead: string
|
||||
faded: (pattern: string, times: number) => string
|
||||
// The journal's material, handed to the companion. Genuinely personal
|
||||
// praise beats a generic cheer, which is the whole point of §5a.
|
||||
cheerStuck: (phrase: string) => Line
|
||||
cheerFaded: (pattern: string) => Line
|
||||
}
|
||||
|
||||
history: {
|
||||
title: string
|
||||
kinds: Record<string, Line> // manual | auto | pre_restore
|
||||
|
||||
Reference in New Issue
Block a user