A privacy-first, offline grammar and spell checker Chrome extension. Works like Grammarly but runs entirely locally on your machine using WebAssembly and Chrome's built-in AI. No data ever leaves your browser.
Powered by Harper.js (Rust grammar engine compiled to WASM), a custom 50+ rule pattern engine, and Chrome's built-in Gemini Nano AI for intelligent proofreading, rewrites, and style improvements.
- 100% Offline -- All checking runs locally via WASM. Zero API calls, zero data collection.
- Spelling -- Catches misspellings with 250+ common misspelling corrections. Red wavy underlines.
- Grammar -- Subject-verb agreement, pronoun case, articles (a/an), homophones (your/you're, their/they're), tense errors, and more. Blue wavy underlines.
- Style -- Wordy phrases, redundant expressions, informal language, and rephrase suggestions. Amber wavy underlines.
- Punctuation -- Run-on sentences, comma splices, missing commas before conjunctions, introductory word commas.
- Works Everywhere -- Supports
<textarea>,<input>, andcontenteditableelements on any webpage, including sites with Shadow DOM / Web Components (e.g., Reddit). - Multiple Fix Methods:
- Click an underline to see suggestions in a popup
- Press Tab to auto-fix all issues at once
- Use the extension popup to review and fix issues one by one
- Non-destructive -- Never auto-corrects. All fixes are user-initiated.
- AI-Powered (Optional) -- On Chrome 138+, uses Chrome's built-in Gemini Nano for:
- AI Proofreading -- Additional grammar/spelling detection with explanations (purple underlines)
- AI Rewrite -- "Rephrase this sentence" with tone variants (Formal, Casual, Shorter)
- AI Improve -- Select text and click "Improve with AI" for intelligent rewrites
- Gracefully degrades -- all AI features are optional and additive
| Underlines on page | Click to see suggestions | Extension popup |
|---|---|---|
| Red (spelling), Blue (grammar), Amber (style) wavy underlines appear under errors | Click any underline to see the issue details and fix options | Click the extension icon to see all issues and fix them |
src/
├── background/ # Service Worker (runs Harper WASM)
│ ├── service-worker.js # WASM init, linting pipeline, suggestion post-processing, AI relay
│ └── custom-rules.js # 50+ regex pattern rules + 250 misspelling corrections
├── offscreen/ # Offscreen Document (AI Hub — Gemini Nano)
│ ├── offscreen.html # Minimal page for AI API access
│ └── offscreen.js # Proofreader, Rewriter, Prompt API handlers
├── content/ # Content Scripts (injected into web pages)
│ ├── content-script.js # Main orchestrator, Tab key, AI lint updates, improve button
│ ├── linter-client.js # Sends text to service worker, caches results
│ ├── element-detector.js # Detects textarea/input/contenteditable on focus
│ ├── overlay-manager.js # Mirror-div overlay for textarea/input underlines
│ ├── contenteditable-handler.js # Range API underlines for contenteditable elements
│ ├── suggestion-popup.js # Click-on-underline popup with fix buttons + AI rewrite
│ ├── fix-pill.js # "Tab to fix" hint that follows the cursor
│ └── styles.css # All extension styles (underlines, popups, hints, AI)
├── popup/ # Extension Popup UI
│ ├── popup.html # Popup markup with AI status
│ ├── popup.js # Loads issues for current tab, fix buttons, AI status
│ └── popup.css # Popup styling + AI badges
├── icons/ # Extension icons (16, 48, 128px)
└── manifest.json # Chrome Extension Manifest V3
User types in a text field
│
▼
ElementDetector attaches on focusin
│
▼
OverlayManager (textarea/input) OR ContentEditableHandler (contenteditable)
│
▼ (300ms debounce)
LinterClient sends text to Service Worker
│
▼
Service Worker runs Harper.js WASM linter
│
▼
fixHarperSuggestions() patches bad split-word suggestions
│
▼
runCustomRules() adds 50+ pattern-based checks
│
▼
Combined & sorted lints sent back to content script
│
▼
Colored wavy underlines rendered on the page
│
▼ (async, non-blocking — Phase 2)
AI Proofreader runs via Offscreen Document (Gemini Nano)
│
▼
New AI lints merge in via 'ai-lints-update' message
│
▼
Purple AI underlines added alongside existing ones
Chrome's AI APIs (Proofreader, Rewriter, Prompt) require a DOM context and cannot run in service workers. The extension uses an offscreen document as a hidden AI hub:
Content Script → Service Worker → Offscreen Document (AI APIs) → back
Two-Phase Lint Pipeline:
- Phase 1 (instant, ~50ms): Harper + custom rules render underlines immediately
- Phase 2 (async, 200-2000ms): AI Proofreader results merge in without blocking
AI APIs Used:
| API | Purpose | Chrome Version |
|---|---|---|
| Proofreader API | Grammar/spelling detection with explanations | 141+ (origin trial) |
| Rewriter API | Sentence rephrasing with tone variants | 137+ (origin trial) |
| Prompt API | Fallback for text improvement | 138+ (stable) |
Requirements for AI features: macOS 13+, Windows 10+, or Linux. 22GB free storage, GPU (4GB+ VRAM) or CPU (16GB RAM, 4+ cores). AI features are completely optional — the extension works fully without them.
-
Harper.js -- The Rust-based grammar engine handles ~571 built-in rules including spelling, capitalization, and many grammar patterns.
-
Suggestion Post-Processing -- Harper sometimes produces bad suggestions (e.g., splitting "writting" into "writ ting"). The
fixHarperSuggestions()function:- Checks a 250+ entry misspelling dictionary for known corrections
- Filters out nonsensical split-word suggestions
- Falls back to doubled-letter deduplication for unknown typos
-
Custom Rules Engine -- 50+ regex-based patterns organized by category:
- Missing articles ("eat apple" -> "eat an apple")
- Homophones (your/you're, their/they're, affect/effect, then/than, loose/lose)
- Subject-verb agreement ("he don't" -> "he doesn't")
- Pronoun case ("between you and I" -> "between you and me")
- Lay/lie, good/well, everyday/every day, amount/number, borrow/lend
- Double negatives, redundant expressions, wordy phrases
- Run-on sentence detection (subject+verb patterns AND imperative patterns)
- Comma splices, missing commas before conjunctions, introductory word commas
- "as...as" correlative comparison suggestions
- Plural after numbers ("5 apple" -> "5 apples")
-
Overlap Prevention -- Custom rules skip any text span already flagged by Harper, preventing duplicate errors.
- Download
writing-helper.zipfrom the latest release - Extract the zip folder
- Open Chrome and go to
chrome://extensions/ - Enable Developer mode (toggle in the top-right corner)
- Click Load unpacked and select the extracted folder
- The extension icon appears in your toolbar — you're ready to go!
If you want to modify the code or contribute:
# 1. Clone the repository
git clone https://github.com/ravigadgil/writing-helper.git
cd writing-helper
# 2. Install dependencies
npm install
# 3. Build the extension
npm run buildThis compiles the source into the dist/ folder. Then load dist/ as an unpacked extension in Chrome (same steps 3-6 above).
# Watch mode -- rebuilds on file changes
npm run watchAfter rebuilding, go to chrome://extensions/ and click the reload button on the extension card.
- Navigate to any webpage with a text input, textarea, or contenteditable field
- Click/focus on the text field and start typing
- Errors appear as colored wavy underlines:
- Red = Spelling error
- Blue = Grammar error
- Amber = Style suggestion
- Fix errors using any of these methods:
- Click an underline to see a popup with suggestions
- Press Tab to fix all errors at once
- Click the extension icon to see all issues and fix individually
Click the extension icon in your toolbar to:
- See a list of all detected issues on the current page
- Click green fix buttons to apply individual corrections
- Click "Fix All" to apply all corrections at once
- Toggle the extension on/off with the switch
| Key | Action |
|---|---|
Tab |
Fix all errors in the focused field |
Escape |
Close the suggestion popup |
- Permissions:
storage(to persist enable/disable state),offscreen(for AI API access) - Content Security Policy:
wasm-unsafe-evalrequired for loading Harper's WASM binary - Content Scripts: Injected on all URLs (except Google Docs/Sheets/Slides) at
document_idlein all frames - Service Worker: ESM module type for dynamic imports
- Web Accessible Resources:
content/styles.cssexposed for Shadow DOM CSS injection
For <textarea> and <input>:
Uses a mirror-div overlay technique. A transparent <div> is positioned exactly over the input element, matching its font, padding, and scroll position. <mark> elements inside the div create the wavy underlines.
For contenteditable:
Uses the Range API with getClientRects() to calculate exact positions of error text. Absolutely-positioned <div> underlines are placed below each error span. The system handles multi-line wrapping and scroll offsets.
Shadow DOM support:
For sites using Web Components with Shadow DOM (e.g., Reddit's <shreddit-*> Lit components), the extension injects CSS via <link> tags into shadow roots and uses inline fallback styles on underline elements. Focus detection uses Event.composedPath() to pierce shadow boundaries, and selection-related features use shadow root getSelection() where available.
Each rule in custom-rules.js follows this structure:
{
regex: /pattern/gi, // RegExp with global + case-insensitive flags
match: 0, // Capture group index to use as the error span (default: 0)
message: 'Explanation', // String or function(match) => string
suggest: ['fix1', 'fix2'],// Array or function(match) => string[]
kind: 'Grammar', // Lint kind label
pretty: 'Category Name', // Human-readable category
category: 'grammar', // Visual bucket: 'spelling' | 'grammar' | 'style'
}The COMMON_MISSPELLINGS export in custom-rules.js maps ~250 frequently misspelled words to their correct spelling. This overrides Harper's sometimes incorrect suggestions (e.g., preventing "writting" from being suggested as "writ ting" instead of "writing").
| File | Lines | Purpose |
|---|---|---|
src/background/service-worker.js |
~300 | WASM initialization, Harper linting, suggestion post-processing, AI relay to offscreen |
src/background/custom-rules.js |
~1070 | 250+ misspelling corrections, 50+ grammar/style/punctuation rules, run-on detection |
src/content/content-script.js |
~550 | Main orchestrator: Tab key, AI toolbar, Shadow DOM support, fix routing |
src/content/linter-client.js |
~35 | Message bridge to service worker with LRU cache (50 entries) |
src/content/element-detector.js |
~70 | Focus-based element detection with composedPath() for Shadow DOM |
src/content/overlay-manager.js |
~180 | Mirror-div overlay system for textarea/input underlines |
src/content/contenteditable-handler.js |
~770 | Range API underlines, Shadow DOM CSS injection, AI sentence highlights |
src/content/suggestion-popup.js |
~210 | Click-on-underline popup with fix buttons + AI rewrite diff view |
src/content/fix-pill.js |
~80 | "Tab to fix" floating hint near cursor |
src/content/tab-fix-all.js |
~60 | Tab key fix-all handler with contenteditable support |
src/content/styles.css |
~220 | All visual styles: underlines, popups, hints (3-color system) |
src/offscreen/offscreen.js |
~165 | Chrome Prompt API handler for AI improve/rephrase |
src/popup/popup.html |
~30 | Popup markup: toggle, issue list, fix-all button |
src/popup/popup.js |
~110 | Popup logic: loads lints, renders issue cards with fix buttons |
src/popup/popup.css |
~210 | Popup styling |
esbuild.config.mjs |
~80 | Build config: 4 bundles + asset copying |
To add a new grammar or style rule:
- Open
src/background/custom-rules.js - Add a new entry to the
RULESarray:
{
regex: wordBoundary("your\\s+pattern\\s+here"),
match: 0,
message: 'Explanation of the issue',
suggest: (m) => ['suggested fix'],
kind: 'Grammar', // or 'Spelling', 'Style', 'Punctuation', etc.
pretty: 'Rule Name',
category: 'grammar', // 'spelling', 'grammar', or 'style'
},- Rebuild with
npm run build - Reload the extension in
chrome://extensions/
Add entries to the COMMON_MISSPELLINGS object:
export const COMMON_MISSPELLINGS = {
'mispeled': 'misspelled',
// ... existing entries
};Keys must be lowercase. The correction is case-matched to the original automatically.
- Google Docs / Sheets / Slides -- These apps render text on a
<canvas>element rather than in the DOM, so the extension cannot detect or underline errors there. The extension automatically disables itself on these sites. - Shadow DOM CSS isolation -- On sites using Web Components with Shadow DOM (e.g., Reddit), the extension uses inline styles as a fallback since external CSS cannot penetrate shadow boundaries. Underlines work but may look slightly different from the main-document styling.
- AI features require Chrome 138+ -- The built-in Gemini Nano AI requires a supported Chrome version, macOS 13+ / Windows 10+ / Linux, 22GB free storage, and a GPU (4GB+ VRAM) or CPU (16GB RAM, 4+ cores). Without these, all Harper-based features still work normally.
contenteditable="plaintext-only"-- Supported but some advanced editors that heavily customize selection/input behavior may interfere with fix application.- iframes -- The extension runs in all frames (
"all_frames": true), but cross-origin iframes may have limited functionality depending on the site's CSP. - Very large text fields -- Harper runs in the service worker. Extremely long documents (10,000+ words) may experience slight lag on the first lint pass.
Two test scripts are included:
# Test Harper.js detection capabilities
node test-harper.mjs
# Test custom rules (true positives + false positive checks)
node test-custom-rules.mjsNote: If your project path contains spaces, the test scripts use a symlink at
/tmp/spelling-tab-linkto work around a WASM loading issue in Node.js.
| Package | Version | Purpose |
|---|---|---|
| harper.js | ^1.7.0 | Rust grammar/spell checker compiled to WASM |
| esbuild | ^0.27.3 | Fast JavaScript bundler (dev dependency) |
No runtime dependencies beyond Harper.js. No API keys, no cloud services, no tracking.
MIT — see LICENSE for details.