Menu

#322 tray: add tauri-plugin-single-instance (duplicate trays / install race on relaunch)

open
nobody
enhancement (7)
2026-06-20
2026-06-20
Anonymous
No

Originally created by: Akarsh-Hegde

Problem

The tray registers no single-instance guard (tray/src-tauri/src/lib.rs has positioner/notification/opener plugins, but not tauri-plugin-single-instance). macOS therefore lets a second copy of Meridian.app (com.meridiona.tray) run alongside the first instead of focusing the running one.

Impact (user-facing)

  • Duplicate menubar icons / trays if the user double-clicks the app while it's already running (common — it's a menubar app with no Dock icon to check).
  • Two poll loops + two MLX managers briefly contending over meridian.db reads and the MLX server (port 7823).
  • First-run install race: with the bundled-backend install (PR [#321], backend_install::ensure_backend_installed), two instances could both run the launchd bootout/bootstrap dance on the same agents concurrently.

Invisible on a clean single-launch install, which is why it's a papercut, not a release blocker — but a real first-run/relaunch annoyance to close before ship.

Fix

  1. Add tauri-plugin-single-instance (v2, matches the 2.11 stack) to tray/src-tauri/Cargo.toml.
  2. Register it first in the builder with a callback that focuses/shows the existing tray + popover on a second launch:

    :::rust
    .plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
    if let Some(win) = app.get_webview_window("main") {
    let _ = win.show();
    let _ = win.set_focus();
    }
    }))

  3. Verify: launch the built .app twice → second invocation focuses the first; no duplicate tray, no second poll loop.

Notes

  • ~30 min, one file (+ Cargo). Also makes the bundled-backend install path race-proof for free.
  • Surfaced during the slice-1b live DMG test (Gap-2 Bucket 1): launching the test .app displaced the running tray. See PR [#321].

Related

Tickets: #321

Discussion


Log in to post a comment.