diff --git a/BUILD_PLAN.md b/BUILD_PLAN.md index d8c9549..21c9966 100644 --- a/BUILD_PLAN.md +++ b/BUILD_PLAN.md @@ -276,7 +276,7 @@ Each item independent and small; order within is free (SUGGESTIONS §5–§6). - Verified live on a throwaway DB (:8099, no dictionary, no LLM): accept → card `make a decision` with example *"I had to make a decision about the job."* bounded to its own sentence, then the journal reporting `kept:1`, `stuck:[{make a decision, docs:2}]` once the phrase appeared in a second document, and a seeded two-month-old pattern surfacing under `faded`. - Tests: `internal/vocab/plant_test.go` (PhraseKey table incl. rune-vs-byte, plant-once, unplantable is a silent no-op), `internal/suggestions/plant_test.go` (corrected-sentence example, only-collocations, idempotent-and-never-resets, sentence-rewrite skipped without failing the accept), `internal/suggestions/growth_test.go` (both windows, stuck needs a second document, the wrote-recently guard, still-happening excluded, and a per-writer isolation test seeding bob), `internal/db/db_test.go` (the backfill). Frontend: `journalCheers.test.ts` (silent before the fetch lands, once per line, one fetch however often warmed, silent on failure, pack resolved at call time) plus journal assertions in `i18n.test.ts`. - Verified: go build/vet, `go test ./internal/...` clean, tsc, vite build, vitest 131/131. - - ⚠️ **Not deployed and not seen in a browser.** Same standing gap as Phase 21: no pt-PT account exists, and this was a code-only session. The pt-PT journal copy is part of the pack a native speaker still has not reviewed. + - ✅ **Deployed 2026-07-27** with the rest of Phase 22 (migrations 0012+0013). ⚠️ Still not seen in a browser, and the pt-PT journal copy is part of the pack a native speaker has not reviewed. - [x] **Daily writing invitation** from the companion ✅ (2026-07-27) — offered to a *blank page* about a minute into a session, at most once a day. Petal always has a document open, so "a session that starts with no doc open" became "the page in front of her is still empty", which is the state the invitation was actually for. - **The stored value is a date, and that is the entire mechanism.** No count, no run of days, nothing that degrades with absence: coming back after a month reads exactly like coming back tomorrow. That is the one property this feature could lose silently, so the rule lives in its own file (`invitation.ts`) rather than inside the heartbeat, and the test names it — *treats a month away the same as a day away*. - **Both answers spend the day's invitation.** Being asked again after "not today" would make no into a negotiation. Declining costs a sleepy `好吧,我继续睡 😴` and nothing else; letting the bubble time out is a third way of saying no. @@ -294,7 +294,7 @@ Each item independent and small; order within is free (SUGGESTIONS §5–§6). - **The zh rules the plan named and this pack does not implement**: dropped articles and he/she slips. Neither is detectable from text alone — "She said he was late" is a perfect sentence whichever pronoun was meant — and flagging them would mean correcting correct writing, which is the one thing a rule pack running on every keystroke must not do. Said in a comment where the rules are, not only here. - Verified live on a throwaway DB (:8099, no dictionary, **no LLM configured at all**): an offline `did a mistake` → card → accept → garden card *made a mistake* with the example bounded to its own corrected sentence, and the journal reporting `kept:1`. - Tests: `grammarLite.test.ts` (30, every rule in both directions), `invitation.test.ts` (7), `offline_test.go` (the six engine-split cases), `db_test.go` (the 0013 backfill), plus false-friend shape/tone guards in `i18n.test.ts`. - - ⚠️ **Not deployed and not seen in a browser.** Same standing gap as Phases 21–22: the pt-PT copy added here is part of the pack a native speaker still has not reviewed. + - ✅ **Deployed 2026-07-27.** ⚠️ Still not seen in a browser; the pt-PT copy added here is part of the pack a native speaker has not reviewed. ### Phase 23 — Choosing her own pair (2026-07-27) Raised by the user, not by the plan: *"I see no way to change my language in the mobile UI."* She was right, and the gap was total — `users.pair_lang` had been readable since Phase 19 and writable by nobody. `/api/me` was GET-only, `Upsert` deliberately skips the column, and no screen anywhere offered the choice. Phases 19–21 built the machinery for a second pair and then left the switch off the wall, which is why ⚠️ *"no pt-PT account exists yet"* stood unresolved through two phases: **nothing could create one.** @@ -304,7 +304,10 @@ Raised by the user, not by the plan: *"I see no way to change my language in the - [x] **Each language names itself** — 中文, Português, and nothing else. The one place in Petal where bilingual copy would actively get in the way: a writer who has landed on the wrong pair cannot read "Portuguese" written in Chinese. The `aria-label` carries the English for a screen reader, which has no such problem. - [x] **No reload.** The pack was already a subscription (Phase 19), and `useSpellChecker` already reloads on `pack.code` while read-aloud already reads `pack().locale` — so the 2.66 MB pt-PT dictionary inflates, the wide alphabet turns on and the voice changes on the tap. Nothing here needed new plumbing; the switch is the only part that was missing. - [x] Tests: `internal/auth/pairlang_test.go` (round-trip and back again — a writer who tries a pair must be able to return; every unshipped code refused with the column unmoved; 400 vs 401 split so a lapsed session still becomes the sign-in overlay). `i18n.test.ts` asserts `shippedPacks()` offers exactly the pairs that have copy, and that every code it offers actually resolves. -- Verified: go build/vet, `go test ./...` clean, tsc, vite build, vitest 173/173. **Not deployed and not seen in a browser** — same standing gap as Phases 21–22. +- Verified: go build/vet, `go test ./...` clean, tsc, vite build, vitest 173/173. ⚠️ **Not seen in a browser** — no Chrome extension on this laptop, and the picker's appearance in a real mobile drawer is exactly the part unit tests cannot cover. + +**Deployed 2026-07-27** (`1f4ca47`), and it carried Phase 22 with it — the two could not be separated in the tree, so the user chose to ship both. Pre-deploy snapshot `data/backups/petal-pre-phase22-20260727T220410Z.db` (VACUUM INTO against the live app). On the box: migrations 0012 and 0013 applied, `source` backfilled **llm=100 / local=3** — the three are her pre-existing `mechanics` rows, claimed by type exactly as the migration intended. Her writing came through untouched: 9 documents, 33 versions, 103 suggestions, `integrity_check` ok. All four containers healthy, read-aloud still resolving `en/pt/zh`, dictionary still open on all five languages. `PATCH /api/me` answers **401 without a session** rather than 405, which is the only half of it a signed-out probe can prove — the route is mounted and behind auth. +- **All three accounts are still on `zh`, deliberately.** Flipping her pair is hers to do now that the button exists, and doing it for her from a shell is precisely the change this phase was built to stop needing. ### Phase 24 (planned) — the fr and es pairs Scope agreed with the user 2026-07-27: *"switcher for Chinese and Portuguese now, plan support for others in a later session or two."* Phase 21 is the groove; the work per language is the same five items, and the order below is the order in which each one stops being a blocker for the next.