Menu ▾ ▴

#180 Windows: native toast module with per-conversation grouping and clear-on-read

closed
nobody
2026-08-28
2026-07-22
Anonymous
No

Originally created by: kristofferR

Windows sibling of [#174] parts 1–2 (Ref [#174]). This issue also establishes the Windows toast foundation that quick reply builds on.

Why a new toast path: notify-rust's Windows backend (tauri-winrt-notification) supports buttons but not text input, toast tag/group, or history removal — everything in scope needs raw WinRT. The windows crate with the needed features (UI_Notifications, Data_Xml_Dom, Foundation_Collections, Win32_Foundation) is already in the tree transitively via tauri-plugin-notification → tauri-winrt-notification, so adding it as a direct cfg(windows) dependency compiles zero new code.

Design

  • New src-tauri/src/windows/ (mod.rs + toast.rs), declared #[cfg(any(test, target_os = "windows"))] like the existing linux module pattern so pure helpers unit-test on Linux CI. show_message_notification becomes a clean three-way dispatch: macOS → deliver_notification_macos, Windows → windows::toast::deliver_notification_windows, Linux → the notify-rust thread path (this also delivers the unix/windows split [#167] assumed; Ref [#167]).
  • Event-driven like macOS, not thread-per-notification: build toast XML → CreateToastNotifier(aumid) → attach Activated/Dismissed/Failed handlers capturing the AppHandle → Show(). Keep each ToastNotification in a capped (~256) static map keyed by id so Action Center clicks after the banner times out still fire — a real improvement over today's notify-rust path, which drops them. Avatar temp files stop being deleted per-notification and rely on the existing 10-minute sweep, like macOS.
  • Toast XML (pure build_toast_xml(ToastSpec) -> String, XML-escaping is load-bearing — title/body are page-controlled): <toast launch="action=open&id=<hex>" activationType="foreground">, avatar as placement="appLogoOverride" hint-crop="circle" (nicer than today's inline image), <audio src="ms-winsoundevent:Notification.Default"/> when sound on, <audio silent="true"/> otherwise. Sync alerts: plain toast, no group.
  • Grouping: SetTag(hex id) + SetGroup(thread digits) when a validated thread path exists. Tag/group over 16 chars silently break toasts pre-Windows 10 1903 — hence hex-encoded ids and gating on build ≥ 18362 via windows-version (also already in-tree). Unique tag per notification: purely additive stacking under one Action Center group, mirroring [#174].
  • Clear-on-read: reuse [#174]'s carrier:thread-viewed signal exactly (thread-nav.ts pathname+focus watcher). One shared listen_any in lib.rs cfg-dispatches: macOS clear_delivered_for_thread, Windows clear_thread_group(thread_id) = ToastNotificationHistory.RemoveGroupWithId(group, aumid) + evict from the keep-alive map. Whichever of [#174]/this lands first ships the page half + listener.
  • AUMID: Tauri's NSIS template already stamps the bundle id (io.github.kristofferr.carrier) on the Start-menu/Desktop shortcuts, matching what the code passes today. The portable zip has no shortcut — windows::toast::init(app) registers HKCU\SOFTWARE\Classes\AppUserModelId\<aumid> (DisplayName "Carrier" + IconUri) at startup, idempotent, no admin — this makes portable-zip toasts show a name/icon at all. Post-quit toast activation (needs an INotificationActivationCallback COM server + ToastActivatorCLSID) is out of scope; documented limitation: clicks on toasts after app exit do nothing.

Edge cases: Focus Assist → toast lands in Action Center, later click still fires (keep-alive map); app notifications disabled → log ToastNotifier.Setting() once, best-effort; pre-1903 → no tag/group, otherwise unchanged.

Testing. Unit (Linux CI): XML building/escaping, parse_activation_args, hex tag/group derivation, keep-alive map eviction. Manual (Win10 + Win11): three messages one thread → one stacked Action Center group; open the thread in Carrier → group clears (including toasts from a previous run — history is OS-side); portable exe shows name/icon; body click after banner timeout opens the thread.

Part of [#187].

Related

Tickets: #167
Tickets: #174
Tickets: #187
Tickets: #233

Discussion

  • Anonymous

    Anonymous - 2026-08-28

    Originally posted by: kristofferR

    Implemented in PR [#233] and manually verified and finalized in PR [#247]. Native WinRT toasts group by conversation, clear on view and after replies, remain activatable from Action Center after the banner expires, show sender avatars, and handle the notification identity migration safely. Closing as complete.

     

    Related

    Tickets: #233
    Tickets: #247

  • Anonymous

    Anonymous - 2026-08-28

    Ticket changed by: kristofferR

    • status: open --> closed
     

Log in to post a comment.