From 0ddf86c6782a97cb9f027a1b800a48e6fd268fb1 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Fri, 24 Jul 2026 20:16:55 -0400 Subject: [PATCH] fix(desktop): navigate to the message on notification click (route via rich toast) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/app/utils/dom.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/app/utils/dom.ts b/src/app/utils/dom.ts index b3ca88bf1..9c06b00e5 100644 --- a/src/app/utils/dom.ts +++ b/src/app/utils/dom.ts @@ -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 => { 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);