Spellcheck
The editor has two independent, opt-in correction sources. Spellcheck delegates word checks to macOS NSSpellChecker instead of browser contenteditable spellcheck and remains macOS desktop only. Japanese typo suggestions use a bundled local matcher for exactly three literal forms on every platform. Both underline findings and share a left-click / Alt+Enter candidate popup. Japanese suggestions do not check grammar, and neither source rewrites text automatically.
Where the code lives
| Piece | File |
|---|---|
Rust commands (spell_is_supported, spell_check, spell_suggest, spell_learn, spell_ignore) | tauri- |
| Platform-independent DTOs, misspelling loop, request limits (mirrored for Linux CI) | tauri- |
Bridge contract and createUnsupportedSpell() | packages/ |
| Mock adapter fixture | packages/ |
| Tauri/REST adapter wiring | packages/, packages/ |
| CodeMirror extension (segment extraction, debounce, underline decorations) | tauri- |
| Japanese literal rules and matcher | tauri- |
| Japanese CodeMirror marks and local worker scheduler | tauri-, tauri-, tauri- |
| Shared correction snapshot, exact edit and popup | tauri-, tauri-, tauri- |
useSpellSupported hook | packages/ |
| Editor context menu "Spelling" group | tauri- |
| Editor pane wiring (misspelling lookup, suggestion fetch, replace/learn/ignore handlers) | tauri- |
| Settings row (Settings → Editor → Spellcheck) | packages/ |
Command palette entry (editor-toggle-spellcheck, "Toggle Spellcheck") | tauri- |
| Setting shape, shortcut key, defaults, validation | packages/, packages/, packages/ |
| Help catalog entry | tauri- |
Bridge contract
See Spell for the full SpellAPI shape, adapter support matrix, and the mock fixture's word list.
Design decisions
NSSpellChecker over browser-native spellcheck. WebKit draws its own underlines but exposes suggestions only through its own context menu, which the editor replaces with the app's
ContextMenu, and WebKit spellcheck needs a process-levelWebContinuousSpellCheckingEnabledNSUserDefault that Tauri never sets. Owning the engine makes the underline and the correction menu agree by construction.NSSpellChecker over Hunspell/WASM or a JS spell-check library. Hunspell needs a bundled dictionary payload and its own suggestion ranking; a JS library like cspell has no browser build.
NSSpellCheckerreuses the account's system dictionary and user-added words for free, at the cost of being macOS-only —isSupported()gating is what keeps the feature invisible everywhere else.6-arg
checkSpellingOfString:startingAt:language:wrap:inSpellDocumentWithTag:wordCount:withwrap: falseand a spell-document tag, not the 2-arg variant. Without a tag,ignoreWord:inSpellDocumentWithTag:has nothing to scope to, so Ignore would never take effect; withwrap: truethe search restarts from the beginning of the string instead of terminating, so the misspelling loop would never end.wrap: falseplusNSNotFoundas the stop condition is what makescheck_textsintauri-a bounded loop.app/ core/ src/ spell. rs spawn_blockingfrom async Tauri commands, notrun_on_main_threadplus an awaited oneshot. A non-async command already runs on the main thread, so a blocking receive of main-thread work from inside one would deadlock. Everyspell_*command isasync fnand offloads the AppKit call to the blocking pool instead (see the module doc comment at the top oftauri-).app/ src/ commands/ spell. rs Per-segment checking, one paragraph-sized prose segment per array entry, not one concatenated blob. Each segment is passed to
NSSpellCheckerwithout an explicit language, leaving language selection to the system service. This does not promise mixed-language detection or grammar checking.Three Japanese literal rules in a bundled worker, separate from the native word-spelling service. The worker checks only
うる覚え→うろ覚え,シュミレーション→シミュレーション, andコミニュケーション→コミュニケーション; it does not tokenize text or infer grammar.Replace-within-span decoration semantics. The extension keeps its own
StateField<DecorationSet>instead of@codemirror/lint, and a re-check only replaces decorations inside the checked span rather than rebuilding the whole set — so underlines on the line being edited clear immediately while typing, and decorations elsewhere in the document are position-mapped through the edit rather than dropped and redrawn.Per-view refresh limitation. Add to Dictionary and Ignore only refresh the view that triggered them (
spellcheckRefresheffect dispatched intauri-); other open split views showing the same word catch up on their own next debounced check rather than being broadcast to immediately. Documented as a v1 limitation, not a bug.app/ renderer/ components/ editor- pane/ index. tsx No automatic correction. A selected candidate replaces only its validated source span in one undoable editor change. The editor never rewrites text while typing.
User-facing behavior
Setting:
editor.spellcheck(default off), Settings → Editor → Spellcheck. The row itself is hidden whenbridge.spell.isSupported()is false.Command palette: Toggle Spellcheck (
editor-toggle-spellcheck), disabled with reason "Spellcheck needs the macOS desktop app" when unsupported.Shortcut:
shortcuts.toggleSpellcheck, no default binding — rebindable in Settings → Shortcuts.Context menu: right-clicking a misspelled word shows a Spelling group first — up to 5 suggestions, Add to Dictionary, Ignore.
Setting:
editor.japaneseTypoSuggestions(default off), Settings → Editor → Japanese typo suggestions. It works on every platform and checks locally in a bundled worker without sending document text over the network.Popup: left-click an underlined finding or put the caret in it and press
Alt+Enter. Choose the displayed candidate to replace that occurrence; useArrowUp,ArrowDown,Tab, andEnterto navigate.Escapeor the close button only dismisses the popup. Outside clicks, editor/page scrolling, edits, and IME composition dismiss it; scrolling within the candidate list keeps it open. Native right-click Spelling actions remain available for native findings.Scope: exactly the three literals listed above. This is not Japanese grammar, context, or general typo checking; there is no automatic replacement or replace-all action.
Japanese end-user walkthrough: manual/.