Repository navigation
feat(messages): let Mac users opt in to composer autocorrect - #2388
Merged
Merged
Conversation
On macOS, "Correct Spelling Automatically" in the composer's Spelling and Grammar menu was greyed out, while spell checking and grammar worked. The cause is the rich composer's ProseMirror root, which has set autocorrect="off" since #203 ("do not let native suggestions rewrite rich text"). No Tauri, wry or native code changes spelling settings. In WebKit, AlternativeTextController:: canEnableAutomaticSpellingCorrection() returns false when the editable root's autocorrect attribute is "off", and WebViewImpl::validateUserInterfaceItem uses that value to disable toggleAutomaticSpellingCorrection: and show it unchecked. The menu item alone cannot both keep the default off and let users turn it on. Its checked state is WebKit's app-wide TextChecker state. That state is saved under WebAutomaticSpellingCorrectionEnabled in the app's NSUserDefaults and follows the macOS keyboard setting until the user changes it. Dropping the attribute would therefore turn autocorrect on for most Mac users. Registering a native default of NO would also change every other text field in the app, which goes against the documented choice to keep ordinary writing fields unchanged. So this adds a device-local preference instead, off by default: Settings > Appearance > Conversations > "Correct spelling automatically". It is only shown on Mac WebKit (Safari and the Tauri WKWebView), the only place the attribute has a verified effect. When it is on, the composer root gets autocorrect="on". The macOS menu item is then enabled, and WebKit saves its choice as usual. The attribute updates live through the existing setProps effect and follows other windows through the storage event. If saving fails, the choice stays active and a retry toast appears, like the other Appearance preferences. Only MessageComposer -> RichComposerInput uses EditableInput, so all message composers share the setting and no other input changes. spellcheck, autocapitalize and writingsuggestions are unchanged. Verified: - Vitest: the attribute stays "off" until the opt-in, switches live, keeps the caret and keeps editing working. Breaking the attribute or the effect dependency makes the test fail. The Settings switch is hidden off Mac WebKit, defaults off, saves, survives a remount, follows a storage event and retries a failed save. - A temporary Playwright journey (not committed) ran the real app shell in WebKit and Chromium. WebKit showed the switch, Tab and Space worked, the composer attribute became "on", stayed "on" after a reload and went back to "off". Chromium did not show the switch. The switch was checked in light and dark themes, at 1280 px and 390 px, and at 200% interface size. - typecheck, Biome and design:check pass. Not verified: - The real macOS context menu and actual autocorrection in the Tauri app. The mechanism comes from reading WebKit's source, not from watching the menu. Synthetic key events never trigger macOS autocorrection, so this needs a person typing on a real keyboard: turn the setting on, right-click the composer and check that the menu item is enabled, then type a misspelling followed by a space. - At 390 px and 200% interface size, the shared PreferenceRow keeps the switch in its own column, so "automatically" wraps mid-word. That is a gap in the shared row layout and is left unchanged here. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Matt Toohey <[email protected]>
The Appearance switch added for composer autocorrect was labelled "Correct spelling automatically", the same words as the macOS Spelling and Grammar menu item. The two do different things. The switch only allows correction in the message box by setting autocorrect="on" on the composer root. Whether correction happens still depends on WebKit's app-wide setting, which the menu item toggles and which follows the macOS keyboard setting until the user changes it. So a user could turn the switch on and see no correction. The switch is now "Allow automatic spelling correction". Its description says that correction is turned on or off from the message box's Spelling and Grammar menu. docs/buzz-help.md uses the new label. The storage key (buzz-message-autocorrect.v1) is unchanged, so saved choices carry over. The existing Settings tests passed autocorrectAvailable explicitly, so none ran the real macWebKit() default. A new table test leaves the prop out and stubs navigator, as UnreadIndicatorSettings.test.tsx does. It covers Mac WebKit (shown), desktop-mode iPad with maxTouchPoints 5 (hidden) and Chrome on Mac with vendor "Google Inc." (hidden). Removing the vendor check, the touch check or the platform check in macWebKit() each fails exactly the matching case. macWebKit() gets no separate unit test: like scroll-correction.test.ts, the repo tests it through its callers. Verified: AppearanceSettings and EditableInput Vitest files, typecheck, Biome and git diff --check pass. Not re-checked in the app: the longer label at 390 px and 200% interface size, where the previous commit already noted that the shared PreferenceRow wraps the label. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Matt Toohey <[email protected]>
The macWebKit() table test in AppearanceSettings.test.tsx used platform "MacIntel" in every row. Deleting the navigator.platform check from macWebKit() left all rows passing. So commit a6e9cc8 was wrong to say that removing the platform check fails the matching case. That check is the only thing that keeps the switch out of the Linux desktop app. WebKitGTK reports vendor "Apple Computer, Inc." and maxTouchPoints 0, so without the check the Linux app would show a switch that does nothing. platform is now a table column, and a new row covers Linux WebKitGTK ("Linux x86_64", "Apple Computer, Inc.", 0) and expects the switch to be hidden. With the platform check removed, only the new row fails (1 failed, 3 passed). With it restored, all rows pass. docs/buzz-help.md said the setting was available "on a Mac". That also covers Chrome and other Chromium browsers on macOS, where the switch is hidden. It now says "in the Mac app or Safari", matching the gate. Verified: AppearanceSettings.test.tsx (20 tests), typecheck, Biome and git diff --check pass. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Matt Toohey <[email protected]>
matt2e
marked this pull request as ready for review
November 4, 2026 04:51
kalvinnchau
approved these changes
Nov 4, 2026
kalvinnchau
left a comment
Contributor
There was a problem hiding this comment.
🤖 Reviewed at 9d85550. No defects found. The preference module mirrors follow-up-preference.ts, the attributes callback is recomputed by setProps, and the platform gate reuses macWebKit(). Targeted vitest (345 tests), tsc and biome pass locally; CI is green.
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.
Problem
On macOS, Correct Spelling Automatically in the message box's right-click Spelling and Grammar menu is greyed out. Spell checking and grammar checking still work. The cause is the rich composer's ProseMirror root, which has set
autocorrect="off"since #203 ("Keep spelling hints, but do not let native suggestions rewrite rich text"). Nothing in Tauri, wry or Buzz's native code changes spelling settings. In WebKit,AlternativeTextController::canEnableAutomaticSpellingCorrection()returns false when the editable root saysautocorrect="off".WebViewImpl::validateUserInterfaceItemthen disablestoggleAutomaticSpellingCorrection:and shows it unchecked.Why an opt-in setting rather than removing the attribute
The menu item's checked state is WebKit's app-wide
TextCheckerstate, not something stored per field. WebKit saves it underWebAutomaticSpellingCorrectionEnabledin the app'sNSUserDefaults. Until the user changes it, it follows the macOS keyboard setting for correcting spelling automatically.autocorrect="off"would turn correction on in the message box for most Mac users, because that macOS setting is usually on. It would reverse Add rich composer formatting and spoiler rendering #203's choice for everyone.So this adds a per-device opt-in that is off by default. When it is on, the composer root gets
autocorrect="on", the menu item becomes available, and WebKit remembers the choice made there as usual.What changed
localStorage,buzz-message-autocorrect.v1). The description reads: "Lets macOS correct misspelled words in messages. Turn correction on or off from the message box’s Spelling and Grammar menu." The label is worded as a permission because the switch only allows correction. Whether WebKit actually corrects still depends on the menu item, which follows the macOS keyboard setting.EditableInputsetsautocorrect="on"on the ProseMirror root when the preference is on and"off"otherwise. The value updates live through the existingsetPropseffect and follows other windows through thestorageevent.spellcheck,autocapitalize,autocompleteandwritingsuggestionsare unchanged. All message composers share the setting, because onlyMessageComposer→RichComposerInputusesEditableInput. No other input changes.macWebKit()check: platformMacIntel, no touch points, vendorApple Computer, Inc.. The switch is hidden in Chrome and other Chromium browsers on a Mac, in Linux WebKitGTK (the Linux desktop app), on desktop-mode iPad and everywhere else. In those places it can't be turned on, so the composer keepsautocorrect="off"as before.docs/buzz-help.mdhas a Spelling entry describing the setting "in the Mac app or Safari".Shared owners used:
SettingsGroup,SwitchPreferenceRow,ToastNotice,Button. No new components, tokens or variants.Screenshots
Captured from this branch's head (
9d85550) in Playwright WebKit 1.63 (Safari 26.6 engine). The browser reportedMacIntel/Apple Computer, Inc., so the switch is shown. Runs were headless at 1280×800 at 2× pixel density, using the full app shell with the browser-test fixture's synthetic data. A temporary spec outside the repository drove the app. It was not committed.Settings › Appearance › Conversations, light theme
Dark theme
The compose box's
autocorrectattributeThe capture script added the dark label and blue outline; they are not part of the app. The label is redrawn every frame from the compose box's live
autocorrectattribute.The other attributes read from the DOM were the same before and after:
spellcheck="true",autocapitalize="off",autocomplete="off",writingsuggestions="false".The whole flow (Playwright WebKit, 960×640, 36 s). Captions at the top name each step and a red dot shows the pointer. Each step also checked the attribute, so the take would have failed on a wrong value.
autocorrect-flow.mp4
How it was verified
Vitest:
AppearanceSettings.test.tsxandEditableInput.test.tsxboth pass on9d85550(345 tests).autocorrect="off"until the device opts in, then switches live, keeps the caret and keeps editing working. Breaking the attribute or the effect dependency fails the test.storageevent. An unsaved choice stays active, and Retry saves that same choice.macWebKit()default against stubbednavigatorvalues. Mac WebKit shows the switch. Desktop-mode iPad (maxTouchPoints5), Chrome on Mac (vendorGoogle Inc.) and Linux WebKitGTK (Linux x86_64with vendorApple Computer, Inc.) hide it. On9d85550, removing each check frommacWebKit()failed exactly the matching row (1 failed, 19 passed each time): platform → Linux WebKitGTK, touch → iPad, vendor → Chrome on Mac.Playwright WebKit (temporary specs, not committed), using the real app shell:
9d85550(the captures above): the switch appeared. Turning it on set the composer toautocorrect="on", which stayed after a reload, and turning it off set it back to"off".2a6205b, before the label was reworded: Tab and Space operated the switch, and Chromium did not show it. That run checked the row in light and dark themes, at 1280 px and 390 px, and at 200% interface size.Native WKWebView probe: a throwaway Swift app hosted the system WKWebView, the same WebKit the desktop app uses. It loaded the real compose box component from the
link-messagestest page, with the preference set throughlocalStoragebefore load. It then right-clicked the compose box and read the context menu WebKit built. The probe ran on2a6205b. The composer code has not changed since; the later commits only change the Settings copy, the help text and tests.autocorrect="off")autocorrect="on")Asking WebKit directly whether
toggleAutomaticSpellingCorrection:is enabled gave the same answer. Both menus offered "tomorrow" as a spelling suggestion, so spell checking works either way. The item was checked with the setting on because the probe had no saved choice, so WebKit followed this Mac's keyboard setting, which is on.typecheck and Biome passed for each commit, and
design:checkpassed on2a6205b. The later commits only change copy, docs and tests.No browser journeys were added or removed.
Manual test on a real Mac keyboard
Synthetic key events never trigger macOS autocorrection, even in a native text view, so this needs a person typing in the desktop app:
main.tehwith the trailing space. It changes tothe.tehinside inline code and inside a code block. It staysteh, because code setsspellcheck="false".tehis no longer corrected.Remaining gaps
PreferenceRowkeeps the switch in its own column, so the label wraps. This was not re-checked with the longer label. It is a gap in the shared row layout and is left unchanged here.autocorrect, so phone and tablet browser users still get no correction and no setting, the same as onmain.--no-verifyat the author's request, so the local pre-push hooks did not run. Hosted CI is the check.🤖 Generated with Claude Code