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);