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:
prosolis
2026-07-27 14:16:59 -07:00
parent 7b845644be
commit e9b8595456
19 changed files with 1328 additions and 5 deletions
+17
View File
@@ -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', () => {