cheogram-www

created pr with cheogram-www/tauri-alpha.1 on 2026-10-29T17:17:18Z · by 3c31bc8c
commented on 2026-10-29T17:17:19Z · by 3c31bc8c
Changes are available at https://tangled.org/mrry.band/cheogram-www on branch tauri-alpha.
added cheogram-www/tauri-alpha.2 on 2026-11-04T17:46:08Z · by 3c31bc8c
1: 45e8ad7 = 1: 45e8ad7 Refactor service worker access
2: fab4022 ! 2: ce66e8f Move notifications code to one file
-: ------- > 3: 10e3574 Refactor notification code
3: d127ee0 ! 4: ba0891f npm run tauri init
4: 9e006bd = 5: 5b3abf9 Request microphone and camera usage on mac
5: 03d4934 = 6: 994a2e9 Tauri file system media store
6: b96216a = 7: 320fa8f Don't register or use serviceworker in tauri
7: faace0a = 8: e76db17 Open links in browser
8: 9c07308 ! 9: 761dd22 Tauri notifications
cmds
checkout latest patchset:
ssh pr.pico.sh pull cheogram-www:tauri-alpha | git am -3
checkout specific patchset revision:
ssh pr.pico.sh pull cheogram-www:tauri-alpha [rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh cheogram-www:tauri-alpha
add comment to patch request:
ssh pr.pico.sh comment cheogram-www:tauri-alpha "lgtm!"

Patchset cheogram-www/tauri-alpha.1 on 2026-10-29T17:17:18Z · commit 9c07308

Tauri notifications
Eric Roberts 2026-10-19T19:40:11Z
Adding a new code path in showNotification to call Tauri notification
code.

Released versions of notify-rust had a macOS bug that didn't allow the
click handlers to work. It's been fixed just not released, so I'm
pointing at their main branch so we get the fix.

A couple places had to change because showNotification doesn't always
return a Notification so we have to check for addEventListener. For the
same reason I had to update notification ref to just care about getting
a close function.

Notifications are kind of weird in Tauri. If we just left everything
like it was, then new Notification would result in a notification
showing up. But the onAction handler we setup wouldn't get any of the
notification data that we rely on in extra so we wouldn't be able to
have clicking the notification take you to the chat (or really know
anything about what the notification was for). We might be able to patch
the plugin to fix this, but for now I think the easiest path forward is
to send Tauri notifications explicitly. If we did the patch then we
could presumably also do the addEventListener and not have the useEffect
in App.tsx at all.
Semantic diff summary
0 added, 10 modified, 0 signature changed, 0 removed across 4 analyzed files (4 files skipped: unsupported file type)
+1 -0 package.json #
......@@ -21,6 +21,7 @@
2121 "@sentry/browser": "^10.52.0",
2222 "@sqlite.org/sqlite-wasm": "^3.54.1-build1",
2323 "@tauri-apps/api": "^2.12.4",
24+ "@tauri-apps/plugin-notification": "^2.7.0",
2425 "@types/react": "<= 18.2.0",
2526 "@types/react-dom": "<= 18.2.0",
2627 "borogove": "^0.0.1",
+118 -0 src-tauri/Cargo.lock #
......@@ -494,6 +494,7 @@ dependencies = [
494494 "tauri",
495495 "tauri-build",
496496 "tauri-plugin-log",
497+ "tauri-plugin-notification",
497498 "tauri-plugin-opener",
498499 "tempfile",
499500 "tokio",
......@@ -2038,6 +2039,20 @@ version = "0.4.34"
20382039 source = "registry+https://github.com/rust-lang/crates.io-index"
20392040 checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
20402041
2042+[[package]]
2043+name = "mac-notification-sys"
2044+version = "0.6.15"
2045+source = "registry+https://github.com/rust-lang/crates.io-index"
2046+checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca"
2047+dependencies = [
2048+ "cc",
2049+ "log",
2050+ "objc2",
2051+ "objc2-foundation",
2052+ "time",
2053+ "uuid",
2054+]
2055+
20412056 [[package]]
20422057 name = "markup5ever"
20432058 version = "0.39.0"
......@@ -2164,6 +2179,19 @@ version = "1.0.6"
21642179 source = "registry+https://github.com/rust-lang/crates.io-index"
21652180 checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
21662181
2182+[[package]]
2183+name = "notify-rust"
2184+version = "4.18.2"
2185+source = "git+https://github.com/hoodie/notify-rust?branch=main#ee6419cf764b896c7b0f9aed3515cf95982be4f4"
2186+dependencies = [
2187+ "futures-lite",
2188+ "log",
2189+ "mac-notification-sys",
2190+ "serde",
2191+ "tauri-winrt-notification",
2192+ "zbus",
2193+]
2194+
21672195 [[package]]
21682196 name = "num-conv"
21692197 version = "0.2.2"
......@@ -2334,6 +2362,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
23342362 dependencies = [
23352363 "bitflags 2.13.2",
23362364 "block2",
2365+ "libc",
23372366 "objc2",
23382367 "objc2-core-foundation",
23392368 ]
......@@ -2645,6 +2674,15 @@ version = "0.2.1"
26452674 source = "registry+https://github.com/rust-lang/crates.io-index"
26462675 checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958"
26472676
2677+[[package]]
2678+name = "ppv-lite86"
2679+version = "0.2.21"
2680+source = "registry+https://github.com/rust-lang/crates.io-index"
2681+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
2682+dependencies = [
2683+ "zerocopy",
2684+]
2685+
26482686 [[package]]
26492687 name = "precomputed-hash"
26502688 version = "0.1.1"
......@@ -2742,6 +2780,35 @@ version = "6.0.0"
27422780 source = "registry+https://github.com/rust-lang/crates.io-index"
27432781 checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
27442782
2783+[[package]]
2784+name = "rand"
2785+version = "0.9.5"
2786+source = "registry+https://github.com/rust-lang/crates.io-index"
2787+checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
2788+dependencies = [
2789+ "rand_chacha",
2790+ "rand_core",
2791+]
2792+
2793+[[package]]
2794+name = "rand_chacha"
2795+version = "0.9.0"
2796+source = "registry+https://github.com/rust-lang/crates.io-index"
2797+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
2798+dependencies = [
2799+ "ppv-lite86",
2800+ "rand_core",
2801+]
2802+
2803+[[package]]
2804+name = "rand_core"
2805+version = "0.9.5"
2806+source = "registry+https://github.com/rust-lang/crates.io-index"
2807+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
2808+dependencies = [
2809+ "getrandom 0.3.4",
2810+]
2811+
27452812 [[package]]
27462813 name = "raw-window-handle"
27472814 version = "0.6.2"
......@@ -3594,6 +3661,26 @@ dependencies = [
35943661 "time",
35953662 ]
35963663
3664+[[package]]
3665+name = "tauri-plugin-notification"
3666+version = "2.7.0"
3667+source = "registry+https://github.com/rust-lang/crates.io-index"
3668+checksum = "b939d7b19c8af1381a72ef8474b3aba2b56c1ddac2c23f0c8e96abbcf86b198b"
3669+dependencies = [
3670+ "log",
3671+ "notify-rust",
3672+ "rand",
3673+ "serde",
3674+ "serde_json",
3675+ "serde_repr",
3676+ "tauri",
3677+ "tauri-plugin",
3678+ "thiserror 2.0.21",
3679+ "time",
3680+ "url",
3681+ "zbus",
3682+]
3683+
35973684 [[package]]
35983685 name = "tauri-plugin-opener"
35993686 version = "2.7.2"
......@@ -3714,6 +3801,17 @@ dependencies = [
37143801 "toml 1.1.8+spec-1.1.0",
37153802 ]
37163803
3804+[[package]]
3805+name = "tauri-winrt-notification"
3806+version = "0.8.1"
3807+source = "registry+https://github.com/rust-lang/crates.io-index"
3808+checksum = "f37a6c354fd28fc9e322ed9bd47e3959576dad28c9d58ea1cf888cce1c7ccb36"
3809+dependencies = [
3810+ "thiserror 2.0.21",
3811+ "windows",
3812+ "windows-version",
3813+]
3814+
37173815 [[package]]
37183816 name = "tempfile"
37193817 version = "3.27.0"
......@@ -4832,6 +4930,26 @@ dependencies = [
48324930 "serde",
48334931 ]
48344932
4933+[[package]]
4934+name = "zerocopy"
4935+version = "0.8.63"
4936+source = "registry+https://github.com/rust-lang/crates.io-index"
4937+checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
4938+dependencies = [
4939+ "zerocopy-derive",
4940+]
4941+
4942+[[package]]
4943+name = "zerocopy-derive"
4944+version = "0.8.63"
4945+source = "registry+https://github.com/rust-lang/crates.io-index"
4946+checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
4947+dependencies = [
4948+ "proc-macro2",
4949+ "quote",
4950+ "syn 2.0.119",
4951+]
4952+
48354953 [[package]]
48364954 name = "zerofrom"
48374955 version = "0.1.8"
+4 -0 src-tauri/Cargo.toml #
......@@ -30,7 +30,11 @@ tokio = { version = "1", features = ["time"] }
3030 sha1 = "0.10"
3131 sha2 = "0.10"
3232 uuid = { version = "1", features = ["v4"] }
33+tauri-plugin-notification = "2.7"
3334 infer = "0.22.0"
3435 tempfile = "3.27.0"
3536
3637 sanitize-filename = "0.6"
38+
39+[patch.crates-io]
40+notify-rust = { git = "https://github.com/hoodie/notify-rust", branch = "main" }
+1 -1 src-tauri/capabilities/default.json #
......@@ -3,5 +3,5 @@
33 "identifier": "default",
44 "description": "enables the default permissions",
55 "windows": ["main"],
6- "permissions": ["core:default", "opener:default"]
6+ "permissions": ["core:default", "opener:default", "notification:default", "core:window:allow-show", "core:window:allow-unminimize", "core:window:allow-set-focus"]
77 }
+1 -0 src-tauri/src/lib.rs #
......@@ -6,6 +6,7 @@ use tauri::Manager;
66 pub fn run() {
77 tauri::Builder::default()
88 .plugin(tauri_plugin_opener::init())
9+ .plugin(tauri_plugin_notification::init())
910 .invoke_handler(tauri::generate_handler![
1011 media::begin_media,
1112 media::write_media,
+22 -1 src/App.tsx #
......@@ -1,8 +1,9 @@
11 import { safeParse } from "valibot";
22 import {
33 showMessageNotification,
4- handleNotificationClick,
54 handleNotificationClose,
5+ handleNotificationClick,
6+ NotificationDataSchema,
67 NotificationEventSchema,
78 } from "./notifications";
89 import "./App.scss";
......@@ -10,6 +11,9 @@ import { CircularProgress } from "@mui/material";
1011 import { newAccount } from "./components/Login";
1112 import { Suspense, useEffect, useState } from "react";
1213 import * as borogove from "borogove";
14+import { isTauri } from "@tauri-apps/api/core";
15+import { getCurrentWindow } from "@tauri-apps/api/window";
16+import { onAction } from "@tauri-apps/plugin-notification";
1317 import { borogove_persistence_Sqlite as borogoveSqlite } from "borogove/sqlite-wasm";
1418 import * as Sentry from "@sentry/browser";
1519 import { stem } from "porter2";
......@@ -181,6 +185,23 @@ const AppInner = () => {
181185 return () => serviceWorker?.removeEventListener("message", handler);
182186 }, [accounts, navigate]);
183187
188+ useEffect(() => {
189+ if (!isTauri()) return;
190+
191+ const handler = onAction(async ({ actionId, notification }) => {
192+ if (actionId !== "tap" || !notification) return;
193+ const result = safeParse(NotificationDataSchema, notification.extra);
194+ if (result.success)
195+ handleNotificationClick(result.output, accounts, navigate);
196+ const appWindow = getCurrentWindow();
197+ await appWindow.show();
198+ await appWindow.unminimize();
199+ await appWindow.setFocus();
200+ });
201+
202+ return () => void handler.then((handler) => handler.unregister());
203+ }, [accounts, navigate]);
204+
184205 return (
185206 <SettingsContext.Provider value={settings}>
186207 <Outlet
+2 -3 src/components/Call.tsx #
......@@ -153,7 +153,7 @@ const showCallNotification = async (
153153 };
154154
155155 const notification = await showNotification(title, options);
156- if (notification) {
156+ if (notification && "addEventListener" in notification) {
157157 let clicked = false;
158158 notification.addEventListener("click", () => {
159159 clicked = true;
......@@ -163,7 +163,6 @@ const showCallNotification = async (
163163 if (!clicked) session.hangup();
164164 });
165165 }
166-
167166 return notification;
168167 };
169168
......@@ -277,7 +276,7 @@ export const Call = ({
277276 };
278277 }, [accounts]);
279278
280- const notificationRef = useRef<Notification | null>(null);
279+ const notificationRef = useRef<Pick<Notification, "close"> | null>(null);
281280 const stopNowRef = useRef(() => {});
282281 const playingCancelledRef = useRef(false);
283282 const callStatusEvent = useEffectEvent(() => {
+14 -3 src/notifications.ts #
......@@ -1,6 +1,11 @@
11 import type { NavigateFunction } from "react-router";
22 import * as borogove from "borogove";
33 import * as v from "valibot";
4+import { isTauri } from "@tauri-apps/api/core";
5+import {
6+ removeActive,
7+ sendNotification,
8+} from "@tauri-apps/plugin-notification";
49 import { href, readyServiceWorker } from "./util";
510
611 export const NotificationDataSchema = v.object({
......@@ -29,11 +34,17 @@ export const showNotification = async (
2934 options: NotificationOptions & { data: NotificationData },
3035 ) => {
3136 const serviceWorker = await readyServiceWorker;
32- if (serviceWorker) {
37+
38+ if (isTauri()) {
39+ const id = crypto.getRandomValues(new Int32Array(1))[0];
40+ sendNotification({ id, title, body: options.body, extra: options.data });
41+ return { close: () => removeActive([{ id }]) };
42+ } else if (serviceWorker) {
3343 await serviceWorker.showNotification(title, options);
3444 return null;
45+ } else {
46+ return new Notification(title, options);
3547 }
36- return new Notification(title, options);
3748 };
3849
3950 export const showMessageNotification = async (
......@@ -83,7 +94,7 @@ export const showMessageNotification = async (
8394 },
8495 };
8596 const notif = await showNotification(title, options);
86- if (notif) {
97+ if (notif && "addEventListener" in notif) {
8798 notif.addEventListener("click", () => {
8899 history.pushState(
89100 null,
Back to top