diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs
index 0744dbb1..d3862de4 100644
--- a/apps/desktop-shell/scripts/check-config.mjs
+++ b/apps/desktop-shell/scripts/check-config.mjs
@@ -147,7 +147,10 @@ const requiredMainSnippets = [
'"file.exportImage"',
'"file.importImage"',
'"file.imageDropped"',
+ '"notification.showLocal"',
'tauri_plugin_dialog::init()',
+ 'tauri_plugin_notification::init()',
+ 'tauri_plugin_notification::NotificationExt',
'"copied_to_clipboard"',
'"file export cancelled"',
'"file import cancelled"',
@@ -163,6 +166,7 @@ const requiredMainSnippets = [
'resolve_desktop_network_status',
'network.statusChanged',
'file.imageDropped',
+ 'app.notification().builder()',
];
for (const permission of requiredPermissions) {
diff --git a/apps/desktop-shell/src-tauri/Cargo.lock b/apps/desktop-shell/src-tauri/Cargo.lock
index b5ba963e..83d90b98 100644
--- a/apps/desktop-shell/src-tauri/Cargo.lock
+++ b/apps/desktop-shell/src-tauri/Cargo.lock
@@ -63,7 +63,7 @@ dependencies = [
"objc2-foundation",
"parking_lot",
"percent-encoding",
- "windows-sys 0.59.0",
+ "windows-sys 0.60.2",
"wl-clipboard-rs",
"x11rb",
]
@@ -1284,6 +1284,7 @@ dependencies = [
"tauri-build",
"tauri-plugin-clipboard-manager",
"tauri-plugin-dialog",
+ "tauri-plugin-notification",
"tauri-plugin-opener",
]
@@ -2067,6 +2068,20 @@ version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
+[[package]]
+name = "mac-notification-sys"
+version = "0.6.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca"
+dependencies = [
+ "cc",
+ "log",
+ "objc2",
+ "objc2-foundation",
+ "time",
+ "uuid",
+]
+
[[package]]
name = "markup5ever"
version = "0.38.0"
@@ -2190,6 +2205,20 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "notify-rust"
+version = "4.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891"
+dependencies = [
+ "futures-lite",
+ "log",
+ "mac-notification-sys",
+ "serde",
+ "tauri-winrt-notification",
+ "zbus",
+]
+
[[package]]
name = "num-conv"
version = "0.2.2"
@@ -2629,7 +2658,7 @@ checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1"
dependencies = [
"base64 0.22.1",
"indexmap 2.14.0",
- "quick-xml",
+ "quick-xml 0.39.4",
"serde",
"time",
]
@@ -2689,6 +2718,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
[[package]]
name = "precomputed-hash"
version = "0.1.1"
@@ -2779,6 +2817,15 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+[[package]]
+name = "quick-xml"
+version = "0.37.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "quick-xml"
version = "0.39.4"
@@ -2809,6 +2856,35 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+[[package]]
+name = "rand"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
[[package]]
name = "raw-window-handle"
version = "0.6.2"
@@ -3682,6 +3758,25 @@ dependencies = [
"url",
]
+[[package]]
+name = "tauri-plugin-notification"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc"
+dependencies = [
+ "log",
+ "notify-rust",
+ "rand",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "tauri",
+ "tauri-plugin",
+ "thiserror 2.0.18",
+ "time",
+ "url",
+]
+
[[package]]
name = "tauri-plugin-opener"
version = "2.5.4"
@@ -3804,6 +3899,18 @@ dependencies = [
"toml 1.1.2+spec-1.1.0",
]
+[[package]]
+name = "tauri-winrt-notification"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
+dependencies = [
+ "quick-xml 0.37.5",
+ "thiserror 2.0.18",
+ "windows",
+ "windows-version",
+]
+
[[package]]
name = "tempfile"
version = "3.27.0"
@@ -4563,7 +4670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
dependencies = [
"proc-macro2",
- "quick-xml",
+ "quick-xml 0.39.4",
"quote",
]
diff --git a/apps/desktop-shell/src-tauri/Cargo.toml b/apps/desktop-shell/src-tauri/Cargo.toml
index e5daeac8..6b3b74a4 100644
--- a/apps/desktop-shell/src-tauri/Cargo.toml
+++ b/apps/desktop-shell/src-tauri/Cargo.toml
@@ -14,4 +14,5 @@ serde_json = "1"
tauri = { version = "2.11.2", features = [] }
tauri-plugin-clipboard-manager = "2.3.2"
tauri-plugin-dialog = "2.7.1"
+tauri-plugin-notification = "2.3.3"
tauri-plugin-opener = "2.5.4"
diff --git a/apps/desktop-shell/src-tauri/src/main.rs b/apps/desktop-shell/src-tauri/src/main.rs
index 68c19094..4638c90f 100644
--- a/apps/desktop-shell/src-tauri/src/main.rs
+++ b/apps/desktop-shell/src-tauri/src/main.rs
@@ -14,6 +14,7 @@ use tauri::WebviewWindow;
use tauri::WindowEvent;
use tauri_plugin_clipboard_manager::ClipboardExt;
use tauri_plugin_dialog::DialogExt;
+use tauri_plugin_notification::NotificationExt;
use tauri_plugin_opener::OpenerExt;
const HOST_BRIDGE_PROTOCOL: &str = "GenarrativeHostBridge";
@@ -27,6 +28,8 @@ const EXPORT_FILE_NAME_FALLBACK: &str = "genarrative-export.txt";
const EXPORT_FILE_NAME_MAX_LENGTH: usize = 120;
const BADGE_COUNT_MAX: i64 = 99999;
const DESKTOP_NETWORK_CHECK_TIMEOUT_MS: u64 = 1200;
+const LOCAL_NOTIFICATION_TITLE_MAX_LENGTH: usize = 80;
+const LOCAL_NOTIFICATION_BODY_MAX_LENGTH: usize = 240;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -102,6 +105,7 @@ fn capabilities() -> Vec<&'static str> {
"file.exportImage",
"file.importImage",
"file.imageDropped",
+ "notification.showLocal",
]
}
@@ -244,6 +248,56 @@ fn badge_count_payload(request: &HostBridgeRequest) -> Result