@bobfrankston/mailx-settings 0.1.38 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/harper-integration.md +120 -0
- package/package.json +3 -3
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Harper grammar-check integration
|
|
2
|
+
|
|
3
|
+
Status: **desktop SHIPPED v1.2.168 (2026-07-22); Android deferred.**
|
|
4
|
+
What shipped diverges from the original sketch in one big way: the linter
|
|
5
|
+
runs **in the daemon**, not in the compose page — harper.js works in Node,
|
|
6
|
+
and the 18 MB WASM engine has no business in the page load path. The client
|
|
7
|
+
(`client/compose/harper-lint.ts`) sends plain-text blocks over IPC
|
|
8
|
+
(`grammarLint`), paints dotted-blue squiggle overlays from the returned
|
|
9
|
+
spans, and shows a suggestion popup on squiggle click. One generic
|
|
10
|
+
implementation covers Quill, tiptap, AND TinyMCE (all contenteditable DOM;
|
|
11
|
+
TinyMCE's editable body reached via `nativeEditor.getBody()`, overlay parked
|
|
12
|
+
on the iframe's `<html>` so nothing ever lands inside the editable subtree).
|
|
13
|
+
Settings → "Grammar check (local)", default ON (`ui.grammarCheck` +
|
|
14
|
+
`mailx-grammar-check` localStorage; boot-snapshot carries it to popouts).
|
|
15
|
+
Quoted reply chains (blockquotes) are skipped. Spelling lints are skipped
|
|
16
|
+
(native WebView2 spellcheck owns spelling). Windows note: harper's Node
|
|
17
|
+
loader mangles drive letters (`fs.readFile(new URL(u).pathname)` →
|
|
18
|
+
`Y:\Y:\...`); the daemon sidesteps it by handing the engine a
|
|
19
|
+
`data:application/wasm;base64,` URI.
|
|
20
|
+
|
|
21
|
+
**Remaining — Android** (bridge has no `grammarLint`; the client goes
|
|
22
|
+
silently dark there): host a `LocalLinter` in android-bootstrap, lazy-loaded
|
|
23
|
+
post-boot; ship harper.js dist in the APK assets like sql.js; ArrayBuffer
|
|
24
|
+
instantiate for the file:// wasm fetch. ~+18 MB APK. Also still open:
|
|
25
|
+
per-rule LintConfig, harper user-dictionary merge with userdict.jsonc,
|
|
26
|
+
ignore-this-lint memory.
|
|
27
|
+
|
|
28
|
+
## What Harper is
|
|
29
|
+
|
|
30
|
+
- Offline, Rust-based grammar checker from Automattic. Apache-2.0, free.
|
|
31
|
+
- Catches grammar, awkward phrasing, capitalization, spelling — the mechanical
|
|
32
|
+
layer above spellcheck, below meaning-level AI rewrite.
|
|
33
|
+
- `harper.js` npm package: ESM wrapping the engine compiled to WASM. Runs
|
|
34
|
+
in-browser (WebView2, Android WebView) and Node. `WorkerLinter` (off-thread,
|
|
35
|
+
preferred) and `LocalLinter`. Lint returns spans + suggestions; suggestions
|
|
36
|
+
are applicable programmatically. Sub-10 ms lint passes; ~1/50th of
|
|
37
|
+
LanguageTool's memory.
|
|
38
|
+
- English only (dialect-aware: US/UK/CA/AU/IN). API flagged "early access /
|
|
39
|
+
not yet stable" — pin the version, expect churn on upgrade.
|
|
40
|
+
- Docs: https://writewithharper.com/docs/harperjs/introduction
|
|
41
|
+
|
|
42
|
+
## Why it fits rmfmail
|
|
43
|
+
|
|
44
|
+
- Local-first like the app; drafts never leave the machine. No API key, no
|
|
45
|
+
Ollama, no feature gate needed.
|
|
46
|
+
- Matches Bob's standing rule: AI assists with **proofreading, not drafting**
|
|
47
|
+
— and this layer doesn't even need the AI providers.
|
|
48
|
+
- Fills the gap between native WebView2 spellcheck (spelling only) and the
|
|
49
|
+
on-demand AI Proofread (whole-text rewrite, cloud/Ollama): live squiggles
|
|
50
|
+
as you type.
|
|
51
|
+
- Could subsume the bundled nspell + dictionary-en machinery (harper does
|
|
52
|
+
spelling too) — one engine, less code. Fold `userdict.jsonc` into Harper's
|
|
53
|
+
user-dictionary API (it has one — verify shape at build time).
|
|
54
|
+
|
|
55
|
+
## Architecture sketch
|
|
56
|
+
|
|
57
|
+
One shared module (`client/compose/harper-lint.ts`), per-editor adapters as
|
|
58
|
+
thin as possible:
|
|
59
|
+
|
|
60
|
+
1. **Linting**: `WorkerLinter` singleton in the compose page. Debounced
|
|
61
|
+
(~500 ms after idle, reuse the draft-checkpoint debounce cadence). Feed it
|
|
62
|
+
PLAIN TEXT extracted per block (paragraph-level granularity keeps lint
|
|
63
|
+
cheap and mapping simple); harper.js documents contenteditable-oriented
|
|
64
|
+
extraction incl. soft breaks.
|
|
65
|
+
2. **Rendering — prefer the OVERLAY approach over per-editor decorations**:
|
|
66
|
+
absolutely-positioned underline elements computed from `Range.getClientRects()`
|
|
67
|
+
over the editor's DOM, in a pointer-events:none layer above the editor.
|
|
68
|
+
ONE renderer serves Quill, tiptap, AND TinyMCE (TinyMCE = same-origin
|
|
69
|
+
iframe: overlay goes inside its doc, or map rects to the host). Editor
|
|
70
|
+
decorations (Quill formats / tiptap decorations / TinyMCE annotations)
|
|
71
|
+
mutate editor state, fight undo stacks, and need three implementations —
|
|
72
|
+
the overlay touches nothing. Re-paint on scroll/resize/input (cheap:
|
|
73
|
+
rects only for on-screen spans).
|
|
74
|
+
3. **Span→DOM mapping**: lint per text node / per block so span offsets map
|
|
75
|
+
directly into that node's Range. Avoid whole-document offset arithmetic —
|
|
76
|
+
rich text invalidates global offsets constantly.
|
|
77
|
+
4. **Suggestions UI**: reuse the msger native context-menu plumbing
|
|
78
|
+
(contextMenuItems + `__msgerContextCommand`) — right-click on a squiggle
|
|
79
|
+
inserts Harper's suggestions above the native spellcheck entries. Applying
|
|
80
|
+
a fix goes through the EDITOR's own API (Quill deleteText/insertText,
|
|
81
|
+
tiptap transaction, TinyMCE `editor.selection` + `insertContent`) so undo
|
|
82
|
+
history stays coherent — apply is the only per-editor code besides text
|
|
83
|
+
extraction.
|
|
84
|
+
5. **Settings**: View→Settings toggle "Grammar check (Harper, local)" —
|
|
85
|
+
default ON once stable (it's private and cheap); per-rule config later
|
|
86
|
+
(harper has lint-rule toggles).
|
|
87
|
+
|
|
88
|
+
## Platform notes
|
|
89
|
+
|
|
90
|
+
- **Desktop WebView2 / msger**: WASM served via the msger custom protocol —
|
|
91
|
+
verify `application/wasm` content-type or use `WebAssembly.instantiate`
|
|
92
|
+
with ArrayBuffer (avoid `instantiateStreaming` MIME dependency).
|
|
93
|
+
- **Android WebView**: `file:///android_asset` fetch of .wasm can be finicky —
|
|
94
|
+
same ArrayBuffer fallback; bundle the .wasm as a base64 asset if needed.
|
|
95
|
+
harper.js worker mode inside Android WebView needs a check — fall back to
|
|
96
|
+
`LocalLinter` on the main thread there (lints are ms-scale; acceptable).
|
|
97
|
+
- **Bundling**: esbuild (`bin/build-bundles.mjs`) — mark the .wasm as an
|
|
98
|
+
external asset copied next to the bundle, not inlined, unless base64
|
|
99
|
+
inlining proves simpler for the msger protocol.
|
|
100
|
+
|
|
101
|
+
## Phasing + estimates (Bob-calibrated, hours not weeks)
|
|
102
|
+
|
|
103
|
+
1. **P1 — TinyMCE first** (since that's what Bob uses): WorkerLinter + overlay
|
|
104
|
+
renderer + context-menu apply, TinyMCE adapter. ~1 day.
|
|
105
|
+
2. **P2 — Quill + tiptap adapters** on the same renderer: ~½ day each,
|
|
106
|
+
mostly text-extraction + apply-fix glue.
|
|
107
|
+
3. **P3 — retire nspell path**, merge userdict.jsonc into Harper dictionary;
|
|
108
|
+
decide whether native WebView2 spellcheck stays on (probably yes — free,
|
|
109
|
+
and its suggestion menu already works) with Harper owning grammar only,
|
|
110
|
+
to avoid double-squiggles on spelling errors: configure Harper with
|
|
111
|
+
spelling lints OFF while native spellcheck is on.
|
|
112
|
+
4. **P4 — Android**: same bundle; verify WASM load + LocalLinter fallback.
|
|
113
|
+
|
|
114
|
+
## Open questions for build time
|
|
115
|
+
|
|
116
|
+
- harper.js API stability — re-check docs/changelog before starting
|
|
117
|
+
(https://github.com/Automattic/harper/releases).
|
|
118
|
+
- Does Harper's dialect setting matter for Bob? (Default American English.)
|
|
119
|
+
- Interaction with AI ghost-text autocomplete overlay (both paint over the
|
|
120
|
+
editor — z-order and pointer-events need a look).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-settings",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"license": "ISC",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
20
|
+
"@bobfrankston/mailx-types": "^0.1.30",
|
|
21
21
|
"jsonc-parser": "^3.3.1"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
".transformedSnapshot": {
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
36
|
+
"@bobfrankston/mailx-types": "^0.1.30",
|
|
37
37
|
"jsonc-parser": "^3.3.1"
|
|
38
38
|
}
|
|
39
39
|
}
|