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.2 on 2026-11-04T17:46:08Z · commit 761dd22

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.

I had to change the notification ref in call to just care about getting
a close function because if showNotification returns it now doesn't
always return a Notification.

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, 9 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.8.0",
2425 "@types/react": "<= 18.2.0",
2526 "@types/react-dom": "<= 18.2.0",
2627 "borogove": "^0.0.1",
+100 -2 src-tauri/Cargo.lock #
......@@ -477,6 +477,17 @@ version = "1.0.5"
477477 source = "registry+https://github.com/rust-lang/crates.io-index"
478478 checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
479479
480+[[package]]
481+name = "chacha20"
482+version = "0.10.2"
483+source = "registry+https://github.com/rust-lang/crates.io-index"
484+checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
485+dependencies = [
486+ "cfg-if",
487+ "cpufeatures 0.3.0",
488+ "rand_core",
489+]
490+
480491 [[package]]
481492 name = "cheogram-www"
482493 version = "0.0.1"
......@@ -494,6 +505,7 @@ dependencies = [
494505 "tauri",
495506 "tauri-build",
496507 "tauri-plugin-log",
508+ "tauri-plugin-notification",
497509 "tauri-plugin-opener",
498510 "tempfile",
499511 "tokio",
......@@ -590,6 +602,15 @@ dependencies = [
590602 "libc",
591603 ]
592604
605+[[package]]
606+name = "cpufeatures"
607+version = "0.3.0"
608+source = "registry+https://github.com/rust-lang/crates.io-index"
609+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
610+dependencies = [
611+ "libc",
612+]
613+
593614 [[package]]
594615 name = "crc32fast"
595616 version = "1.5.3"
......@@ -1304,6 +1325,7 @@ dependencies = [
13041325 "cfg-if",
13051326 "libc",
13061327 "r-efi 6.0.0",
1328+ "rand_core",
13071329 ]
13081330
13091331 [[package]]
......@@ -2038,6 +2060,20 @@ version = "0.4.34"
20382060 source = "registry+https://github.com/rust-lang/crates.io-index"
20392061 checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
20402062
2063+[[package]]
2064+name = "mac-notification-sys"
2065+version = "0.6.15"
2066+source = "registry+https://github.com/rust-lang/crates.io-index"
2067+checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca"
2068+dependencies = [
2069+ "cc",
2070+ "log",
2071+ "objc2",
2072+ "objc2-foundation",
2073+ "time",
2074+ "uuid",
2075+]
2076+
20412077 [[package]]
20422078 name = "markup5ever"
20432079 version = "0.39.0"
......@@ -2164,6 +2200,19 @@ version = "1.0.6"
21642200 source = "registry+https://github.com/rust-lang/crates.io-index"
21652201 checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
21662202
2203+[[package]]
2204+name = "notify-rust"
2205+version = "4.18.2"
2206+source = "git+https://github.com/hoodie/notify-rust?branch=main#ee6419cf764b896c7b0f9aed3515cf95982be4f4"
2207+dependencies = [
2208+ "futures-lite",
2209+ "log",
2210+ "mac-notification-sys",
2211+ "serde",
2212+ "tauri-winrt-notification",
2213+ "zbus",
2214+]
2215+
21672216 [[package]]
21682217 name = "num-conv"
21692218 version = "0.2.2"
......@@ -2334,6 +2383,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
23342383 dependencies = [
23352384 "bitflags 2.13.2",
23362385 "block2",
2386+ "libc",
23372387 "objc2",
23382388 "objc2-core-foundation",
23392389 ]
......@@ -2742,6 +2792,23 @@ version = "6.0.0"
27422792 source = "registry+https://github.com/rust-lang/crates.io-index"
27432793 checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
27442794
2795+[[package]]
2796+name = "rand"
2797+version = "0.10.3"
2798+source = "registry+https://github.com/rust-lang/crates.io-index"
2799+checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
2800+dependencies = [
2801+ "chacha20",
2802+ "getrandom 0.4.3",
2803+ "rand_core",
2804+]
2805+
2806+[[package]]
2807+name = "rand_core"
2808+version = "0.10.1"
2809+source = "registry+https://github.com/rust-lang/crates.io-index"
2810+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
2811+
27452812 [[package]]
27462813 name = "raw-window-handle"
27472814 version = "0.6.2"
......@@ -3154,7 +3221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
31543221 checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
31553222 dependencies = [
31563223 "cfg-if",
3157- "cpufeatures",
3224+ "cpufeatures 0.2.17",
31583225 "digest",
31593226 ]
31603227
......@@ -3165,7 +3232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
31653232 checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
31663233 dependencies = [
31673234 "cfg-if",
3168- "cpufeatures",
3235+ "cpufeatures 0.2.17",
31693236 "digest",
31703237 ]
31713238
......@@ -3594,6 +3661,26 @@ dependencies = [
35943661 "time",
35953662 ]
35963663
3664+[[package]]
3665+name = "tauri-plugin-notification"
3666+version = "2.8.0"
3667+source = "registry+https://github.com/rust-lang/crates.io-index"
3668+checksum = "5bc6c194a95a535abdfbe3bc1af9a43935b8b9b29c216b8e71af8b59132703cf"
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"
+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.8"
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,
+28 -4 src/App.tsx #
......@@ -4,6 +4,9 @@ import { CircularProgress } from "@mui/material";
44 import { newAccount } from "./components/Login";
55 import { Suspense, useEffect, useState } from "react";
66 import * as borogove from "borogove";
7+import { isTauri } from "@tauri-apps/api/core";
8+import { getCurrentWindow } from "@tauri-apps/api/window";
9+import { onAction } from "@tauri-apps/plugin-notification";
710 import { borogove_persistence_Sqlite as borogoveSqlite } from "borogove/sqlite-wasm";
811 import * as Sentry from "@sentry/browser";
912 import { stem } from "porter2";
......@@ -45,6 +48,11 @@ if (typeof navigator.storage?.getDirectory === "function" && (webkit || ios)) {
4548
4649 export type Accounts = Record<string, ReturnType<typeof newAccount>>;
4750
51+const chatForNotification = (data: NotificationData, accounts: Accounts) => {
52+ if (data.type === "loginRequest") return null;
53+ return accounts[data.accountId]?.client.getChat(data.chatId) ?? null;
54+};
55+
4856 export type Context = {
4957 accounts: Accounts;
5058 setAccounts: (
......@@ -166,10 +174,7 @@ const AppInner = () => {
166174 }>,
167175 ) => {
168176 const { event: action, data } = event.data;
169- const chat =
170- data.type === "loginRequest"
171- ? null
172- : (accounts[data.accountId]?.client.getChat(data.chatId) ?? null);
177+ const chat = chatForNotification(data, accounts);
173178 switch (action) {
174179 case "notificationclick":
175180 onNotificationClick(data, chat);
......@@ -186,6 +191,25 @@ const AppInner = () => {
186191 return () => serviceWorker?.removeEventListener("message", handler);
187192 }, [accounts, onNotificationClick, onNotificationClose]);
188193
194+ useEffect(() => {
195+ if (!isTauri()) return;
196+
197+ const handler = onAction(async ({ actionId, notification }) => {
198+ if (actionId !== "tap" || !notification) return;
199+
200+ const data = notification.extra as NotificationData;
201+ const chat = chatForNotification(data, accounts);
202+
203+ onNotificationClick(data, chat);
204+ const appWindow = getCurrentWindow();
205+ await appWindow.show();
206+ await appWindow.unminimize();
207+ await appWindow.setFocus();
208+ });
209+
210+ return () => void handler.then((handler) => handler.unregister());
211+ }, [accounts, onNotificationClick]);
212+
189213 return (
190214 <SettingsContext.Provider value={settings}>
191215 <Outlet
+1 -1 src/components/Call.tsx #
......@@ -234,7 +234,7 @@ export const Call = ({
234234 };
235235 }, [accounts]);
236236
237- const notificationRef = useRef<Notification | null>(null);
237+ const notificationRef = useRef<Pick<Notification, "close"> | null>(null);
238238 const stopNowRef = useRef(() => {});
239239 const playingCancelledRef = useRef(false);
240240 const callStatusEvent = useEffectEvent(() => {
+16 -2 src/notifications.ts #
......@@ -2,6 +2,11 @@ import { useCallback } from "react";
22 import { useNavigate } from "react-router";
33 import { assertNever } from "./assert-never";
44 import * as borogove from "borogove";
5+import { isTauri } from "@tauri-apps/api/core";
6+import {
7+ removeActive,
8+ sendNotification,
9+} from "@tauri-apps/plugin-notification";
510 import { href, readyServiceWorker } from "./util";
611
712 type ChatNotificationData = {
......@@ -65,9 +70,19 @@ export const useNotifications = () => {
6570 const showNotification = useCallback<ShowNotification>(
6671 async (title, options, chat) => {
6772 const worker = await readyServiceWorker;
73+
6874 if (worker) {
6975 await worker.showNotification(title, options);
7076 return null;
77+ } else if (isTauri()) {
78+ const id = crypto.getRandomValues(new Int32Array(1))[0];
79+ sendNotification({
80+ id,
81+ title,
82+ body: options.body,
83+ extra: options.data,
84+ });
85+ return { close: () => removeActive([{ id }]) };
7186 } else {
7287 const notification = new Notification(title, options);
7388 let clicked = false;
......@@ -93,8 +108,7 @@ export type ShowNotification = (
93108 title: string,
94109 options: NotificationOptions & { data: NotificationData },
95110 chat: borogove.Chat | null,
96-) => Promise<Notification | null>;
97-
111+) => Promise<Pick<Notification, "close"> | null>;
98112 export const showMessageNotification = async (
99113 accountId: string,
100114 sender: borogove.Member,
Back to top