feat(windows): implement native Windows toast notifications & opt-out alerting - #6876
feat(windows): implement native Windows toast notifications & opt-out alerting#6876ranjank2alpha wants to merge 1 commit into
Conversation
… alerting - Add native Windows WinRT Toast Notifications using tauri-winrt-notification and windows COM bindings. - Establish process-wide MTA and Start Menu shortcut AUMID registration for reliable Action Center delivery. - Direct desktop notification permission checks to native backend for Windows. - Decouple visual desktop toasts from sound-slot gating so DMs and channel messages toast reliably when desktop alerts are enabled. Signed-off-by: ranjankai <ranjan@rkai.in>
6570c4e to
3ee42ce
Compare
🔐 Codex Security Review
|
bernhardkaindl
left a comment
There was a problem hiding this comment.
@ranjank2alpha Please have a look at the Copilot review comments on this Copilot-review-PR created from this PR branch:
Maybe also review my PR
- #7614
and its review PR
youkeep-code#3
There was a problem hiding this comment.
Recommendation: split the Windows transport from alert-policy changes
I think the Windows WinRT transport work in this PR is worth pursuing, but I recommend not merging PR #6876 as one change. It combines two independently reviewable concerns:
- Enabling reliable native Windows toast delivery for the existing notification model.
- Changing the product policy to notify for every message in every unmuted channel, while also changing how mentions, DMs, thread replies, and sound settings are handled.
The first is platform enablement and reliability work. The second is a user-facing notification-policy change. Keeping them separate would make the Windows implementation easier to validate and would avoid deciding Buzz's notification defaults as a side effect of adding WinRT support.
Buzz's current product contract says that zero is the default, with Stream and Forum notifications disabled by default and DMs reserved for urgent activity. See VISION.md and VISION.md. The proposed WhatsApp-style channel behavior may be a good future option, but it should be an explicit product decision rather than an incidental consequence of this PR.
Review of the PR review findings
1. Startup registration is currently unwired
ensure_startup_registration is defined, but the Tauri setup path does not call it. The command registration in lib.rs registers show_native_notification, not the startup lifecycle hook, and the setup callback does not invoke the registration function.
As a result, the promised boot-time AUMID and Start Menu shortcut repair does not happen. In-place upgrades with an old shortcut can therefore remain broken even though the new code exists.
This is a small fix, but it should be accompanied by a startup test or an observable diagnostic. A dead lifecycle hook is easy to miss in review.
2. Registration has no completion barrier
The Windows startup function starts detached work in notifications.rs. The first frontend notification can therefore arrive before the AUMID registry entry and shortcut repair have completed.
The send path waits for the process MTA through ensure_process_mta, but that is not the same as waiting for the complete app-registration sequence. The shortcut is specifically described as required for unpackaged Win32 toast delivery, so the notification path should not report readiness until that operation has completed successfully.
I would make registration an awaitable, process-scoped initialization future or use a synchronization primitive that records both completion and failure. A failure should be returned to the first send attempt, not only written to eprintln!.
3. Notification delivery is reported as successful before native delivery succeeds
This is a particularly important reliability issue.
show_native_notification returns Ok(()) after spawning a worker thread. The actual Toast::show() call happens later in notifications.rs, where errors are only logged.
The frontend consequently treats the IPC call as a successful notification in desktop.ts. That can cause a failed toast to be treated as delivered, including playing a sound or advancing a caller's seen/deduplication state. In a release build, the eprintln! diagnostic is not a useful recovery mechanism because the app has no console.
The native command should return the result of Toast::show() through a channel or an async task. The frontend should receive false or an error when Windows rejects the toast.
4. The feed permission race from the previous notification fix remains
The PR still starts permission repair without awaiting it in use-feed-desktop-notifications.ts. It then immediately calls deliverFeedNotification.
This is the same race fixed in the previous reliable-notifications branch: the first feed item after startup can be marked seen while permission is still being repaired, so its toast is permanently lost. The permission strategy may differ, but the sequencing requirement does not. The feed path must await a shared check -> request if needed -> confirm granted -> deliver gate before sending.
I would retain the previous branch's awaited permission fix when porting the Windows transport.
5. Top-level mentions can be delivered by two owners
The duplicate-mention concern is valid.
The new channel handler in useAppShellDesktopNotifications.ts formats and sends a top-level mention as a channel notification. The live path also invokes the home-feed refresh callback in useLiveChannelUpdates.ts. The feed notification hook can then discover the same mention and send another toast in use-feed-desktop-notifications.ts.
The comment in the PR says the channel path replaces the feed path, but there is no shared event-ID delivery ledger proving that. The implementation needs one owner per category, or a shared deduplication record used by both paths.
6. The direct internal Tauri command is unnecessary coupling
The Windows permission workaround in desktop.ts invokes plugin:notification|is_permission_granted directly.
This may work with the current plugin, but it couples Buzz to an internal command string rather than the exported plugin API. The previous branch's narrower approach is preferable: isolate the known Windows startup-shim defect, keep permission access behind one Buzz helper, and use the public isPermissionGranted() / requestPermission() APIs everywhere else. Regardless of which state query is used, delivery must still await permission repair.
7. The new raw Windows FFI conflicts with the repository rule
The finding about unsafe is correct under the repository's current contribution rules. The new Windows module adds raw COM, registry, shell-link, and property-store FFI in notifications.rs, while the contributor guide explicitly prohibits new unsafe production code.
This does not necessarily mean the functionality is impossible. It means the implementation needs an approved safe wrapper/dependency boundary, or the repository policy needs an explicit, reviewed exception for this platform integration. That decision should be made consciously rather than hidden inside a feature PR.
Comparison with PR #7614
The #7614 is the better frontend base. It already addresses the reliability issues that matter across all platforms:
- awaited permission repair before feed delivery;
- explicit notification click intent so a normal message opens the channel timeline rather than an empty reply branch;
- dedicated thread targets remain capable of opening a real branch;
- live events are merged into the authoritative channel-window cache, so a new message appears without a manual reload;
- notification delivery and replay paths share event-level deduplication more deliberately.
PR #6876 contains useful Windows-specific transport work, but it does not contain those frontend/cache fixes. Merging #6876 would not fix those issues.
Suggested split
PR A: Windows native toast transport
Keep this narrowly focused on:
- AUMID registration (I think as the installer does it, this should only matter for a portable executable that does not need installation)
- Start Menu shortcut repair (As the installer does it, this should only matter for a portable executable that does not need installation)
- process COM/MTA setup;
- native toast posting;
- native activation forwarding;
- Windows permission compatibility;
- truthful error propagation;
- focused Windows integration tests.
Base this work on #7614, including its awaited permission gate and route/cache behaviour. Start-up registration should be wired from Tauri setup, and the first send should wait for registration completion.
The tests should cover at least:
- clean launch registration;
- in-place-upgrade shortcut repair;
- first notification immediately after launch;
- native toast failure propagation;
- a running-app click;
- a cold-start click, if cold-start activation is intended to be supported;
- no duplicate delivery when a mention reaches both live and feed paths.
PR B: notification policy and per-channel preferences
Treat the alerting model as separate product work. The proposal to decouple visual toasts from sound settings is directionally sound: disabling a sound should not silently disable the toast. But the default “every message in every unmuted channel” policy needs explicit agreement with the current zero-default contract.
Buzz already has persisted, remotely synchronized per-channel mute state in useChannelMutes.ts and channelMutesStorage.ts. That is a reasonable foundation for Slack-like channel preferences, but I would evolve it into a versioned policy rather than treating muted: boolean as the final model. For example:
default: follow the global Buzz policy;mentions_and_replies: notify for personally relevant activity;all_messages: notify for every eligible channel message;muted: suppress ordinary channel notifications.
The exact names and defaults are product decisions. The important design point is that per-channel policy should be persisted and synchronized independently from sound selection, and that mentions or other urgent events should have clearly documented precedence over ordinary channel muting.
Bottom line
The native WinRT direction is sound, but I recommend keeping this PR separate from the broader alert-policy changes and not merging #6876 unchanged. PR #7614 is a better basis for the Windows notification work because it preserves the existing notification policy while also addressing the asynchronous permission and delivery-reliability issues identified above. Reviewing or merging that focused implementation would let Buzz gain Windows toast support sooner, while leaving “notify for every channel message” and per-channel notification preferences for a separate product-focused change.
What this fixes
On Windows, Buzz delivered no native toast notifications, and worse, you couldn't even turn desktop alerts on: the toggle in Settings reverted to off and re-disabled itself on every launch. This PR fixes both, so incoming DMs, mentions, thread replies, and channel messages raise a real Action Center toast that opens the conversation when clicked.
There are two independent root causes here, and both are addressed.
1. The permission latch that force-disabled the toggle
@tauri-apps/plugin-notification's injected init script special-cases Windows: it setswindow.Notification.permission = "denied"at startup without ever asking the backend, which actually grants unconditionally. Because Buzz derivesdesktopEnabledfrom that value on boot, the setting was silently reset to off on every launch, and a user couldn't correct it even from devtools (the plugin script runs before Buzz's code). The fix is to queryplugin:notification|is_permission_granteddirectly on Windows ingetDesktopNotificationPermissionState()instead of trusting the shim's cached value. That is exactly the failure reported in #6982 and #4660.2. There was no WinRT toast path at all
show_native_notificationhad no Windows arm, so nothing was ever delivered. This adds native dispatch viatauri-winrt-notification. The load-bearing detail is COM apartment state:Toast::show()calls into WinRT and does no COM init of its own, and Buzz posts toasts from a barestd::thread, so without a live process MTA every call fails withCO_E_NOTINITIALIZED, and that error vanishes in a release build because there is no console. We establish a process-wide MTA on a dedicated thread that parks for the app's lifetime, so every toast thread joins it implicitly and click callbacks stay deliverable.Registration is the other half of why toasts were dropped for an unpackaged Win32 app:
SetCurrentProcessExplicitAppUserModelID) and register it underHKCU\Software\Classes\AppUserModelId\<aumid>with a display name and icon.PKEY_AppUserModel_IDon every launch. Tauri's NSIS macro skips recreating the shortcut on in-place updates (which is what every silent auto-update is), so an install whose shortcut predates this work would never get patched otherwise. Doing it at launch makes it self-healing regardless of install or update history.Clicking a toast now focuses the window and routes to the message, matching the existing Linux and macOS paths.
Alerting model
Alongside the delivery fix, the alerting model is brought in line with what people expect from a chat app: notify for every message in a channel until it is muted, and decouple the visual toast from the per-category sound flags. Previously the sound flag also gated the toast, so DMs and thread replies silently stopped toasting whenever their sound row happened to be off.
Fixes #6982
Fixes #4660
Testing
Verified on Windows 10 and 11, on both a fresh install and an in-place update: the toggle stays on across restarts, toasts deliver for DMs, mentions, thread replies, and channel messages, and clicking one opens the right conversation.
tsc, bothcargo checks, and the frontend test suite pass.