Commit Graph

4 Commits

Author SHA1 Message Date
prosolis
8aa437ec82 Phase 12 + 13: collocation coach + vocabulary garden
Phase 12 — collocation coach: a third suggestion family for gentle
"natives usually say…" hints on non-native word pairings, reusing the
existing runPass/pendingScope/rail machinery.
- llm/collocation.go (RunCollocation, 25s floor, reuses ParseCheckpoint)
  + collocationSystemPrompt/CollocationMessages (warm, Mandarin gloss,
  defers grammar/spelling to the grammar family)
- migration 0005 rebuilds the suggestions table to extend the type CHECK
  (SQLite can't ALTER a CHECK)
- collocationScope + CollocationLimit + POST /{id}/collocation
- fix: grammarScope was `type != 'voice'` and would wipe the new
  collocation flags; now `type NOT IN ('voice','collocation')`
- frontend: --color-blossom, "Make it sound natural 🌸" pill,
  collocating/runCollocation in useCheckpoint, StatusBar dot

Phase 13 — vocabulary garden: capture looked-up words and surface them
for gentle spaced repetition.
- new internal/vocab package: migration 0006 (vocab_words, SM-2-lite
  columns, doc_id ON DELETE SET NULL, UNIQUE(user_id,word)),
  scheduler.go (Leitner ladder 1/3/7/16/35 then geometric; gentle
  "again", no streak-shaming), handlers (capture-upsert/list/due/
  review/delete, owner-scoped, SQLite-side datetime math)
- auto-capture on word lookup (dictionary-known words only, captures
  the surrounding sentence + doc_id) + 🤍/💚 toggle on WordCard
- GardenPanel: blossom grid (bloom by reps), flashcard review (sentence
  blanked, flip, again/good/easy, recognition↔production), sleepy-kitten
  footer; opened from a global 🌷 header button

Tests: TestCollocationPassCoexists, vocab scheduler + handlers, db CHECK
extended. go build/vet/test + tsc + vite + vitest (51/51) clean;
migration verified against a copy of the live DB; live backend smoke
walked the full vocab lifecycle + the warm-502 collocation path.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 15:50:25 -07:00
prosolis
6783ce7a51 Suggestions: stop re-nagging resolved edits + fix typographic anchoring
Two fixes for the "accept Petal's change, then it nags about the same
sentence moments later" report:

- replacePending now suppresses any freshly-generated suggestion whose
  original->replacement matches one the user already accepted or
  dismissed for that doc. The model has no memory between passes, so
  without this it re-proposes the identical edit on the next checkpoint.

- findRange anchored suggestions by exact string match, which missed
  whenever the model echoed an `original` with plain ASCII (straight
  quotes, --, ...) while the document held the Typography-converted
  glyphs (curly quotes, em-dash, single-char ellipsis). The miss meant
  no highlight AND a silent no-op on accept, which then fed the re-nag
  above. foldTypography canonicalizes those variants (with a source
  index map for length changes) so matching survives the mismatch.

Covered by a server-side regression test (accept+dismiss then re-check
returns nothing) and frontend unit tests for the fold and anchoring.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 10:35:57 -07:00
prosolis
0fa70979a0 Phase 5: voice consistency pass
Tier-1 voice-consistency pass: whole-document LLM review surfacing passages
that read tonally out of place (formal/over-polished/paraphrased-too-closely),
as honey-decorated `voice` flags with no correction (awareness-only).

- internal/llm/voice.go: RunVoice sends the whole document (no TruncateDoc),
  MaxTokens 2048, 20s per-doc floor (VoiceInterval). Standalone voice prompt
  in prompts.go (not bundled with the grammar checkpoint, per spec).
- internal/suggestions: POST /api/docs/:id/voice. replacePending is now
  family-scoped (pendingScope) so grammar and voice never clobber each other's
  pending flags; both passes return the unified pending set. check/voice share
  one runPass helper. TestVoicePassCoexists covers both directions.
- Frontend: api.voiceDoc, useCheckpoint voicing/runVoice, honey "Check my
  voice" toolbar pill, breathing honey dot in StatusBar.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 21:16:53 -07:00
prosolis
a4069d5755 Phase 3: LLM grammar checkpoint
Backend (internal/llm): backend-agnostic LLMClient interface + factory
with vLLM (OpenAI-compat) and Ollama (native) clients, each Complete +
Stream. prompts.go holds the checkpoint and Ask Petal templates;
checkpoint.go salvages JSON from model output (brace-matched), enforces a
per-doc 30s RateLimiter, and truncates the doc to a latency cap.

internal/suggestions: POST /api/docs/:id/check runs a checkpoint and
replaces the doc's pending suggestions in one tx (accepted/rejected kept
as history); GET /api/docs/:id/suggestions lists pending;
POST /api/suggestions/:id/{accept,dismiss} resolves one. Throttled checks
return the current set rather than erroring.

Frontend: useCheckpoint (4s debounce, loads existing on open, stale-guard
tokens); SuggestionHighlight renders ProseMirror decorations re-anchored
by the `original` string on every doc change (not stored marks), with
precise textblock-offset→PM-position mapping; SuggestionCard shows the
type tag + diff + explanation and applies the replacement in-editor on
accept; breathing rose checkpoint dot in the StatusBar; fade-float +
breathe animations.

Tests: llm parse/rate-limit/truncate; suggestions full flow + rate-limit
over httptest with a stub client. Smoke-tested end-to-end against a fake
vLLM endpoint (anchoring verified) and the LLM-unreachable 502 path.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 20:45:30 -07:00