fix(desktop): navigate to the message on notification click (route via rich toast)

On the Windows/Tauri build, clicking a message notification opened the app but
didn't navigate to the message. showOsNotification preferred the service worker
(registration.showNotification) and returned early; WebView2 has a service
worker, so the SW-owned toast always won and its click (SW notificationclick →
client.focus + postMessage → navigate) focused the app but the navigate didn't
complete in WebView2.

The desktop build injects a window.Notification shim that routes tagged message
toasts to the native rich WinRT toast, whose click emits lotus-notification-
activate with the path → useTauriToastActions → navigate. But the SW path
shadowed `new Notification()`, so that shim (and show_rich_toast) never ran on
desktop. Skipping the SW path under Tauri lets the shim take over and navigate.

Web browsers are unchanged (isDesktopApp() is false → SW path as before). Two
review agents verified the diagnosis + no web regression across both repos.

DESKTOP-QA REQUIRED — this activates a previously-dead code path. Known desktop
follow-ups it exposes (documented in LOTUS_TODO, both in cinny-desktop Rust):
- tag-coalescing is lost (rapid same-room messages stack toasts instead of
  collapsing) — show_rich_toast doesn't dedupe by room.
- thread/invite quick-reply misroutes: the reply target is the coalescing tag
  (roomId:threadId / 'lotus-invites'), not a real room id → sendMessage fails.
Navigation itself (body click) is correct for all cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 20:16:55 -04:00
co-authored by Claude Opus 4.8
parent bd5f6a0855
commit 0ddf86c678
+14 -1
View File
@@ -268,13 +268,26 @@ export const notificationPermission = (permission: NotificationPermission) => {
* (with the provided `onClick`) when no service worker is available, preserving
* the previous behaviour.
*/
// Tauri v2 injects `__TAURI_INTERNALS__` into the webview. On the desktop build,
// an injected `window.Notification` shim routes `tag`-bearing message toasts to
// the native rich WinRT toast, whose click focuses the app AND navigates to the
// message (via the `lotus-notification-activate` event → useTauriToastActions).
const isDesktopApp = (): boolean =>
(window as unknown as { __TAURI_INTERNALS__?: { invoke?: unknown } }).__TAURI_INTERNALS__
?.invoke !== undefined;
export const showOsNotification = async (
title: string,
options: NotificationOptions & { data?: { path?: string } },
onClick?: () => void,
): Promise<void> => {
try {
if ('serviceWorker' in navigator) {
// On desktop, skip the service-worker notification: WebView2 exposes a
// service worker, so the SW-owned toast would win here and bypass the
// Notification shim above — its click focuses the app but never navigates to
// the message. Falling through to `new Notification()` lets the shim route
// to the native rich toast, which does navigate.
if (!isDesktopApp() && 'serviceWorker' in navigator) {
const registration = await navigator.serviceWorker.ready;
if (registration && typeof registration.showNotification === 'function') {
await registration.showNotification(title, options);