Skip to content

feat(messages): let Mac users opt in to composer autocorrect - #2388

Merged
matt2e merged 3 commits into
mainfrom
cant-enable-autocorrect
Nov 4, 2026
Merged

matt2e merged 3 commits into
mainfrom
cant-enable-autocorrect

Conversation

@matt2e

@matt2e matt2e commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

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 says autocorrect="off". WebViewImpl::validateUserInterfaceItem then disables toggleAutomaticSpellingCorrection: 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 TextChecker state, not something stored per field. WebKit saves it under WebAutomaticSpellingCorrectionEnabled in the app's NSUserDefaults. Until the user changes it, it follows the macOS keyboard setting for correcting spelling automatically.

  • Removing 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.
  • Forcing it off natively, by registering a default of NO, would change every other text field in the app too, not just the composer.

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

  • Setting: Settings › Appearance › Conversations › Allow automatic spelling correction. It is off by default and saved on this device (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.
  • Composer: EditableInput sets autocorrect="on" on the ProseMirror root when the preference is on and "off" otherwise. The value updates live through the existing setProps effect and follows other windows through the storage event. spellcheck, autocapitalize, autocomplete and writingsuggestions are unchanged. All message composers share the setting, because only MessageComposer → RichComposerInput uses EditableInput. No other input changes.
  • Platform gate: the switch only appears where the attribute has a verified effect: Mac WebKit, meaning the macOS desktop app (WKWebView) and Safari on a Mac. The gate reuses the existing macWebKit() check: platform MacIntel, no touch points, vendor Apple 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 keeps autocorrect="off" as before.
  • Save failure: if the choice can't be saved, it stays active and a "Spelling preference wasn’t saved" toast offers a retry, the same way other Appearance preferences work.
  • Help: docs/buzz-help.md has 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 reported MacIntel / 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

Light theme, switch off (the default): the new last row in the Conversations group reads "Allow automatic spelling correction", with its description below and a grey switch on the right.

Light theme, switch on: the same row with the switch filled dark and its knob on the right. Nothing else on the page changes.

Dark theme

Dark theme, switch off: label, description and the switch track stay readable on the dark background.

Dark theme, switch on: the track turns light with a dark knob, so the two states are easy to tell apart.

The compose box's autocorrect attribute

The 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 autocorrect attribute.

Default: the #Alpha compose box has autocorrect="off", shown in red.

After turning the setting on and closing Settings: the same compose box has autocorrect="on", shown in green.

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.tsx and EditableInput.test.tsx both pass on 9d85550 (345 tests).

    • The composer keeps 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.
    • The switch defaults off, saves, survives a remount and follows a storage event. An unsaved choice stays active, and Retry saves that same choice.
    • A table test runs the real macWebKit() default against stubbed navigator values. Mac WebKit shows the switch. Desktop-mode iPad (maxTouchPoints 5), Chrome on Mac (vendor Google Inc.) and Linux WebKitGTK (Linux x86_64 with vendor Apple Computer, Inc.) hide it. On 9d85550, removing each check from macWebKit() 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:

    • On 9d85550 (the captures above): the switch appeared. Turning it on set the composer to autocorrect="on", which stayed after a reload, and turning it off set it back to "off".
    • On 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-messages test page, with the preference set through localStorage before load. It then right-clicked the compose box and read the context menu WebKit built. The probe ran on 2a6205b. The composer code has not changed since; the later commits only change the Settings copy, the help text and tests.

    The real compose box in the macOS system WKWebView, with "See you tomorow" typed. The header line shows the attribute read from the DOM: "off" with the setting off (top) and "on" with it on (bottom).

    Spelling and Grammar item Setting off (autocorrect="off") Setting on (autocorrect="on")
    Show Spelling and Grammar enabled enabled
    Check Document Now enabled enabled
    Check Grammar With Spelling enabled, checked enabled, checked
    Correct Spelling Automatically disabled, unchecked enabled, checked

    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:check passed on 2a6205b. 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:

  • Settings › Appearance › Conversations shows Allow automatic spelling correction, switched off.
  • With the setting off, right-click the message box › Spelling and Grammar. Correct Spelling Automatically is greyed out, as it is on main.
  • Turn the setting on. Right-click the message box › Spelling and Grammar. Correct Spelling Automatically is now clickable. Make sure it is checked.
  • Type teh with the trailing space. It changes to the.
  • Type a misspelled word and press Enter straight away to send. The last word may go out uncorrected, because macOS corrects at the next word boundary and Enter sends instead of inserting one. This is expected, not a regression.
  • Type teh inside inline code and inside a code block. It stays teh, because code sets spellcheck="false".
  • Uncheck Correct Spelling Automatically in the menu. teh is no longer corrected.
  • Turn the Buzz setting off. The menu item is greyed out again and nothing is corrected.

Remaining gaps

  • The menu state comes from the WKWebView probe, not from the built desktop app, and no screenshot of the native menu was taken. Real autocorrection has not been observed; see the manual test above.
  • At 390 px with 200% interface size, the shared PreferenceRow keeps 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.
  • iOS and iPadOS Safari also honour autocorrect, so phone and tablet browser users still get no correction and no setting, the same as on main.
  • This branch was pushed with --no-verify at the author's request, so the local pre-push hooks did not run. Hosted CI is the check.

🤖 Generated with Claude Code

matt2e and others added 3 commits November 4, 2026 14:46
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
matt2e marked this pull request as ready for review November 4, 2026 04:51
@matt2e
matt2e requested a review from a team as a code owner November 4, 2026 04:51

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@matt2e
matt2e merged commit 6cbf1b8 into main Nov 4, 2026
41 checks passed
@matt2e
matt2e deleted the cant-enable-autocorrect branch November 4, 2026 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants