Prevent duplicate desktop app instances

Notification clicks and repeated launches can enter the native app through OS-level activation paths, so the desktop runtime now registers Tauri's single-instance guard before sidecars or other plugins start. A second launch restores and focuses the existing main window instead of creating another app process.

Constraint: macOS and Windows launch paths must reuse the running desktop instance.
Rejected: Handle only notification click callbacks | does not cover Dock, shortcut, installer, or Windows relaunch paths.
Confidence: high
Scope-risk: narrow
Directive: Keep the single-instance plugin before sidecar startup so duplicate launches cannot create extra runtime processes.
Tested: SKIP_INSTALL=1 ./desktop/scripts/build-macos-arm64.sh
Tested: open -n built app twice; desktop_count stayed 1 with pid 69821
Tested: bun run check:native
Tested: cargo test --manifest-path desktop/src-tauri/Cargo.toml
Tested: cd desktop && bun run test -- desktopNotifications.test.ts
Tested: cd desktop && bun run lint
This commit is contained in:
程序员阿江(Relakkes) 2026-05-07 16:27:49 +08:00
parent 7fbce35be6
commit 4c2881aaf0
3 changed files with 21 additions and 0 deletions

View File

@ -485,6 +485,7 @@ dependencies = [
"tauri-plugin-notification",
"tauri-plugin-process",
"tauri-plugin-shell",
"tauri-plugin-single-instance",
"tauri-plugin-updater",
"tauri-runtime",
"tauri-runtime-wry",
@ -4386,6 +4387,21 @@ dependencies = [
"tokio",
]
[[package]]
name = "tauri-plugin-single-instance"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8f29386f5e9fdc699182388a33ee80a56de436d91b67459e86afef426282af"
dependencies = [
"serde",
"serde_json",
"tauri",
"thiserror 2.0.18",
"tracing",
"windows-sys 0.60.2",
"zbus",
]
[[package]]
name = "tauri-plugin-updater"
version = "2.10.1"

View File

@ -23,3 +23,4 @@ serde_json = "1"
anyhow = "1.0.102"
portable-pty = "0.9.0"
tauri-plugin-notification = "2"
tauri-plugin-single-instance = "2"

View File

@ -1487,6 +1487,10 @@ mod tests {
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let builder = tauri::Builder::default()
// Keep this first so duplicate launches are stopped before sidecars start.
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
show_main_window(app);
}))
.manage(ServerState::default())
.manage(AdapterState::default())
.manage(TerminalState::default())