# CHUNK #7 โ€” Spell & Grammar **Status:** ๐ŸŸก In progress โ€” this is the current work order. **Duration:** ~2 days ยท **Prereqs:** CHUNK #3 (editor, decoration pipeline) ยท **Follows:** CHUNK #6 ยท **Unblocks:** CHUNK #9 (AI suggestions share the decoration pipeline) **Purpose:** Add offline spellcheck (hunspell compiled to WASM) and optional grammar/style checking (self-hostable LanguageTool server), rendered as inline squiggles with a side panel to jump/apply/ignore. Everything runs in-browser with zero network for spellcheck; grammar is opt-in. --- ## Steps ### 1. Spellcheck engine (`packages/editor/src/spell/`) 1. **hunspell-WASM** (`packages/editor/src/spell/hunspell.ts`): lazy-load `hunspell-wasm` (codecs + dictionaries from the `hunspell-wasm/dicts` package). Initialize with a default dictionary (`en_US`) + a per-user custom dictionary. 2. **Dictionary management** (`packages/editor/src/spell/dicts.ts`): - Load dictionaries on demand (lazy, per active language). - **Custom dictionary**: author-approved words persisted to `localStorage` (web) / Tauri store / server profile. Words added here are never re-underlined. - Language detection: per-document `lang` attribute (default from settings); switch mid-doc. 3. **Checker** (`packages/editor/src/spell/checker.ts`): given a ProseMirror doc, produce a list of `SpellHit { from, to, word, suggestions: string[] }` โ€” skipping code, already-accepted mentions, and custom-dictionary words. ### 2. Grammar engine (`packages/editor/src/grammar/`) 1. **LanguageTool adapter** (`packages/editor/src/grammar/languagetool.ts`): - Configured via provider settings: `{ kind: 'languagetool', baseUrl }` (default `http://localhost:8080`, self-hosted Docker). - `check(text, lang)` โ†’ POST to `/v2/check`, parse `matches[]` โ†’ `GrammarHit { from, to, message, replacements[], ruleId }`. - Debounced (800ms) + cached per text hash to avoid spamming the server. 2. **Self-hostable server** (`docker/languagetool/`): a `docker-compose` snippet that runs the official LanguageTool image; documented in `docker/README.md`. API is OpenAI-compatible-ish (documented endpoints). ### 3. Decoration integration (`packages/editor/src/decorations/`) 1. Plug both engines into the **decoration pipeline** built in Chunk 3 (`useDecorations`): - Spellcheck โ†’ red squiggle (`Decoration.inline` with `class="ps-spell"`). - Grammar โ†’ blue squiggle (`class="ps-grammar"`). 2. Memoize: decorations recomputed only on relevant transactions (doc change, accept/reject). ### 4. Side panel (`apps/web/src/features/check/CheckPanel.tsx`) 1. Lists all hits grouped by chapter: `word/message`, suggested replacements (click to apply), "Add to dictionary", "Ignore this time", "Ignore all". 2. **Jump**: clicking a hit scrolls the editor to that range and highlights it. 3. Counts: X spelling / Y grammar issues remaining; per-chapter breakdown. ### 5. Settings 1. Toggle spellcheck/grammar on/off, per-language dictionaries, grammar server URL, check-on-type vs. check-on-save. ### 6. Tests 1. Unit: `checker` on a known doc โ†’ correct `SpellHit`s; custom dictionary word ignored. 2. Unit: grammar adapter parses a mock LanguageTool response correctly. 3. E2E: type a misspelled word โ†’ squiggle appears; add to dictionary โ†’ squiggle gone; reload โ†’ persists. 4. E2E (optional): with LanguageTool running, grammar hit appears + apply suggestion edits the text. --- ## Acceptance criteria (all must pass) - [ ] Misspelled word โ†’ red squiggle; suggestions shown in panel. - [ ] "Add to dictionary" โ†’ word never underlined again; persists across reload. - [ ] Grammar (if LanguageTool configured) โ†’ blue squiggle; apply suggestion edits text. - [ ] Panel jumps to a hit and highlights it in the editor. - [ ] Code blocks and mentions are never spellchecked. - [ ] Toggle spellcheck off โ†’ no squiggles; setting persists. - [ ] Contract suite still green on both adapters. ## Definition of done Authors get inline spellcheck (fully offline) and optional grammar checking, with a clean panel to review and act โ€” no wall of unactionable errors. ## Risks & fallbacks - **hunspell-WASM bundle size** โ†’ lazy-load dictionaries; only load the active language(s). - **LanguageTool self-host complexity** โ†’ make it opt-in and clearly documented; app degrades gracefully (grammar disabled) if the server is unreachable. - **Squiggle perf on huge docs** โ†’ check only the visible viewport + a buffer, not the whole doc, on each scroll.