Files
PubScript/CHUNK#7.md
T

4.4 KiB

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 SpellHits; 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.