feat: on-device grammar check (Harper) in both composers#4
Open
theetherGit wants to merge 1 commit into
Open
Conversation
Add spelling/grammar checking to the message body using harper.js — a WASM engine that runs entirely client-side, so draft text never leaves the device (fits the encrypted-mail model; no Grammarly/LanguageTool cloud round-trip). Implementation - harper-extension.ts: a TipTap/ProseMirror extension. Flattens the doc to plaintext with a char->PM-position map, lints (debounced) in a shared WorkerLinter, and paints wavy-underline decorations. Clicking an underline opens a suggestion menu; picking one applies the fix and flashes the span green (a separate decoration layer so re-linting doesn't wipe it). - tiptap-editor.svelte: wires the extension, the click menu (portaled to <body> so it isn't clipped by the transformed compose drawer), a status pill, and a bodyClass prop for an inline max-height cap. Native spellcheck is turned off so Harper owns the squiggles. - reply-composer.svelte: switch from the execCommand rich-editor to the shared TiptapEditor so grammar check works in replies too. Performance - Lazy: the ~17 MB WASM is dynamically imported, never on SSR or the cold inbox — only when an editor mounts. Rollup emits it as a hashed, browser-cached asset. - Off the main thread: linting runs in a Web Worker (blob URL, no bundler worker config needed). - Warm-on-mount: getLinter() is kicked off at requestIdleCallback so the download/compile happens in the worker before the first word is typed; one memoized linter is shared across every editor. - The status pill only shows once the body has content. - vite.config: exclude harper.js from dep pre-bundling (dev-only) so its `new URL(..., import.meta.url)` WASM path resolves from node_modules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: theetherGit <meenashivam9650@gmail.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
doota-mail-jobs | 308627a | Jul 24 2026, 04:07 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
doota | 308627a | Commit Preview URL Branch Preview URL |
Jul 24 2026, 04:08 PM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On-device grammar & spell check (Harper)
Adds grammar/spell checking to the message body in both composers (new message + reply) using harper.js — a WASM engine that runs entirely client-side. Draft text never leaves the device (fits the encrypted-mail model; no Grammarly/LanguageTool cloud round-trip).
How it works
harper-extension.ts— a TipTap/ProseMirror extension. Flattens the doc to plaintext with a char→PM-position map, lints (debounced) in a sharedWorkerLinter, and paints wavy-underline decorations (red = spelling, amber = grammar/style).tiptap-editor.svelte— wires the extension, the suggestion menu (portaled to<body>so it isn't clipped by the transformed compose drawer), a status pill (only shown when there's content), and abodyClassprop for an inline max-height cap. Native spellcheck is turned off so Harper owns the squiggles.reply-composer.svelte— switched from theexecCommandrich-editor to the sharedTiptapEditor, so grammar check works in replies too.Performance
getLinter()fires atrequestIdleCallbackso the download/compile happens in the worker before the first word is typed; one memoized linter is shared across every editor.vite.config: excludesharper.jsfrom dep pre-bundling (dev-only) so itsnew URL(..., import.meta.url)WASM path resolves fromnode_modules.Verification
svelte-check: 0 errors (only the pre-existingvite.config.tsvite7/8 type-mismatch noise)vitest: 158/158pnpm build: passes (wasm emitted as a lazy hashed asset)🤖 Generated with Claude Code