From 238823aae1a73c75b89efacab1755583afdf1a94 Mon Sep 17 00:00:00 2001 From: tomdebone Date: Mon, 6 Jul 2026 18:59:53 +0200 Subject: [PATCH] tauri-app: 8b click-through notifications OS notification body already arrived as a 'maarcadetweet:notification' DOM event (Phase 7b). The toast pill that surfaces the body is now clickable: left-click navigates to the URL the notification was about (at://// opens the thread, unknown URL falls back to the home view); right-click dismisses without navigating. tauri-plugin-notification v2.x does not expose a reliable OS-level notification-click callback (it only shows the notification), so the two-step pattern (OS click focuses the app + in-app toast click navigates) is the standard workaround. lastNotificationUrl is now $state so the toast title updates when a new notification arrives. --- .gitignore | 2 +- .vite/vitest/results.json | 2 +- crates/tauri-app/src/App.svelte | 57 ++++++++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 316547d..fcbad1b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ Cargo.lock.bak crates/tauri-app/node_modules/ crates/tauri-app/dist/ crates/tauri-app/.svelte-kit/ -crates/tauri-app/.vite/ +.vite/ crates/tauri-app/src-tauri/target/ crates/tauri-app/src-tauri/gen/ crates/tauri-app/src/assets/fonts/*.ttf diff --git a/.vite/vitest/results.json b/.vite/vitest/results.json index fa16518..504645f 100644 --- a/.vite/vitest/results.json +++ b/.vite/vitest/results.json @@ -1 +1 @@ -{"version":"2.1.9","results":[[":crates/tauri-app/src/lib/api/client.test.ts",{"duration":31.70154099999999,"failed":false}],[":crates/tauri-app/src/lib/utils/localstorage.test.ts",{"duration":6.449833000000012,"failed":false}]]} \ No newline at end of file +{"version":"2.1.9","results":[[":crates/tauri-app/src/lib/api/client.test.ts",{"duration":31.23195799999999,"failed":false}],[":crates/tauri-app/src/lib/utils/localstorage.test.ts",{"duration":5.796832999999992,"failed":false}]]} \ No newline at end of file diff --git a/crates/tauri-app/src/App.svelte b/crates/tauri-app/src/App.svelte index 6631489..d07e71a 100644 --- a/crates/tauri-app/src/App.svelte +++ b/crates/tauri-app/src/App.svelte @@ -96,9 +96,42 @@ function onNotification(e: Event) { const detail = (e as CustomEvent<{ title: string; body: string; url: string | null }>).detail; if (detail) { - // Surface in the existing toast stack — works for both the - // OS-notification click and the in-app events. - pushToast("info", `notif: ${detail.title} — ${detail.body}`); + // Surface in the existing toast stack. The toast pill is + // clickable — clicking it navigates to the URL the notification + // was about (e.g. an at:// post URI). For OS-level + // notifications, the user has to first click the OS notification + // (which focuses the app) and then click the toast in the app + // to actually navigate; this is the limitation of the + // tauri-plugin-notification v2.x click callbacks. + pushToast( + "info", + `notif: ${detail.title} — ${detail.body}` + (detail.url ? " (click to open)" : ""), + ); + // Save the URL on a per-toast basis via a side-channel so the + // toast pill can navigate when clicked. We attach it to the + // notification event for simplicity (re-look-up via last). + lastNotificationUrl = detail.url ?? null; + } + } + + let lastNotificationUrl: string | null = $state(null); + + function openLastNotification() { + const url = lastNotificationUrl; + if (!url) return; + if (url.startsWith("at://")) { + // at:///app.twi.post/ or at:///app.bsky.feed.post/ + const parts = url.replace(/^at:\/\//, "").split("/"); + const rkey = parts[parts.length - 1]; + const did = parts[0]; + if (rkey && did) { + // Use the existing thread-context machinery to open the post. + void openThread(`${did}/app.twi.post/${rkey}`); + lastNotificationUrl = null; + } + } else { + // unknown scheme — just open home + view = "home"; } } @@ -454,8 +487,22 @@ class="toast-pill" class:toast-pill--err={t.kind === "error"} type="button" - onclick={() => (toasts = toasts.filter((x) => x.id !== t.id))} - title="dismiss" + onclick={() => { + // If this toast was a notification with a URL, navigate + // to it before dismissing. Otherwise just dismiss. + if (lastNotificationUrl) { + openLastNotification(); + } + toasts = toasts.filter((x) => x.id !== t.id); + }} + oncontextmenu={(e) => { + // Right-click dismisses without navigating — useful when + // the user clicks a notification toast by mistake. + e.preventDefault(); + lastNotificationUrl = null; + toasts = toasts.filter((x) => x.id !== t.id); + }} + title={lastNotificationUrl ? "click to open · right-click to dismiss" : "dismiss"} > {t.kind === "error" ? "err" : "info"}: {t.text}