Phase 7: browser-side spell check (nspell, en-US)

Vendored Hunspell en.aff/en.dic into web/public/dictionaries/en/ (served as a
static asset + embedded in the binary, kept out of the JS bundle). dictionary-en
moved to a devDep — only used to source the files.

- useSpellChecker (App-level, loads once/session): fetches the dict, builds an
  nspell instance, replays a localStorage personal word list; addWord persists
  and bumps a version so consumers re-decorate. Ambient types in
  src/types/nspell.d.ts (the package ships none).
- SpellCheck Tiptap extension: misspellings as ProseMirror decorations (no
  stored marks), recomputed on edit / caret move / checker swap. Latin-only
  tokenizer so CJK is never flagged; skips short tokens + all-caps acronyms;
  exempts the caret word to avoid mid-typing jitter. Reuses mapOffset (now
  exported from SuggestionHighlight); wordAt resolves the exact span on click.
- MisspellCard: soft rose wavy underline, bilingual popover with up to 5 nspell
  corrections (click to replace) + add-to-dictionary. Closes on outside-pointer,
  edit, or doc switch.

Chinese spell check intentionally omitted — nspell is dictionary-based and
English-only; Chinese typos (homophone 别字) need an LLM, out of v1 scope.

tsc/vite/go build+vet clean; live server serves both dict files; nspell
behavior smoke-tested.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
This commit is contained in:
prosolis
2026-06-25 22:18:21 -07:00
parent 183219b5de
commit 660f00d452
14 changed files with 50622 additions and 3 deletions

View File

@@ -136,6 +136,26 @@ button, a, input {
animation: petal-suggestion-in 200ms ease both;
}
/* --- Spell check ------------------------------------------------------------
Browser-side nspell flags misspellings with a soft rose wavy underline (a
gentler take on the classic red squiggle, to fit the pastel palette). Like the
suggestion layer these are ProseMirror decorations, never stored marks.
Clicking a flagged word opens its MisspellCard with corrections. */
.petal-misspelling {
cursor: pointer;
text-decoration: underline wavy var(--color-accent);
text-decoration-skip-ink: none;
text-underline-offset: 2px;
}
.petal-misspelling:hover {
background: var(--color-surface-alt);
border-radius: 2px;
}
.petal-misspell-card {
animation: petal-suggestion-in 200ms ease both;
}
/* --- Accept confetti --------------------------------------------------------
A tiny CSS-only burst played where a suggestion is accepted: four colored
dots spray up-and-out, then fade. No JS animation — each dot reads its