diff --git a/CHANGELOG.md b/CHANGELOG.md index fbab451..10a25da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## Desktop [0.1.5] — 2026-04-20 + +### Fixed + +- Authed users no longer see a blank desktop app on second launch: the window now always shows on startup instead of being suppressed whenever `auto_start` was enabled and a worker secret was saved. The onboarding wizard sets `auto_start: true` on finish, which meant every returning user was silently hitting the tray-only code path in v0.1.4 and could not find a way back into the UI +- Closing the main window on macOS now hides it to the tray instead of destroying the window, so re-opening via the Dock or tray restores the same session +- Clicking the Dock icon (or re-launching the app from Finder while a copy is already running) now re-shows and focuses the main window via a new `RunEvent::Reopen` handler, instead of doing nothing + +### Changed + +- Worker auto-start and window visibility are now decoupled — `auto_start` only controls whether the worker daemon starts automatically on launch; the dashboard window is always visible on startup. Quit remains reachable via the tray menu and Cmd+Q + ## Desktop [0.1.4] — 2026-04-18 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index e83061b..ef06a5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2573,7 +2573,7 @@ dependencies = [ [[package]] name = "modelrelay-desktop" -version = "0.1.4" +version = "0.1.5" dependencies = [ "modelrelay-protocol", "modelrelay-worker", diff --git a/crates/modelrelay-desktop/Cargo.toml b/crates/modelrelay-desktop/Cargo.toml index be63576..3217e55 100644 --- a/crates/modelrelay-desktop/Cargo.toml +++ b/crates/modelrelay-desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "modelrelay-desktop" -version = "0.1.4" +version = "0.1.5" description = "Native desktop tray app for ModelRelay workers" edition.workspace = true license.workspace = true diff --git a/crates/modelrelay-desktop/src/main.rs b/crates/modelrelay-desktop/src/main.rs index e1c2a31..07ba391 100644 --- a/crates/modelrelay-desktop/src/main.rs +++ b/crates/modelrelay-desktop/src/main.rs @@ -2,11 +2,19 @@ use modelrelay_desktop::{AppSettings, AppStatus, WorkerManager, updater}; use tauri::{ - Emitter, Manager, + AppHandle, Emitter, Manager, RunEvent, WindowEvent, menu::{MenuBuilder, MenuItemBuilder}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, }; +fn show_main_window(app: &AppHandle) { + if let Some(window) = app.get_webview_window("main") { + let _ = window.unminimize(); + let _ = window.show(); + let _ = window.set_focus(); + } +} + #[tauri::command] async fn get_has_saved_settings(manager: tauri::State<'_, WorkerManager>) -> Result { let settings = manager.get_settings().await; @@ -137,7 +145,7 @@ fn main() { .with_writer(std::io::stderr) .init(); - tauri::Builder::default() + let app = tauri::Builder::default() .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_process::init()) @@ -152,44 +160,47 @@ fn main() { check_for_update, install_update, ]) + .on_window_event(|window, event| { + // Hide-to-tray on close so the app keeps running and the user can + // re-open via the tray or (on macOS) the Dock. Quit is still reachable + // via the tray menu and Cmd+Q, which go through RunEvent::ExitRequested. + if let WindowEvent::CloseRequested { api, .. } = event { + api.prevent_close(); + let _ = window.hide(); + } + }) .setup(|app| { - // Determine settings file path in the app's data directory let app_data_dir = app .path() .app_data_dir() .unwrap_or_else(|_| std::path::PathBuf::from(".")); let settings_path = app_data_dir.join("settings.json"); - let manager = WorkerManager::new(settings_path); - - // If auto_start is enabled and the user has a saved worker secret, start - // the worker immediately and stay silent in the tray. Otherwise show the - // main window so first-run users (and returning users without auto_start) - // see the onboarding/dashboard UI even if the tray click is misbehaving. - let rt = app.handle().clone(); - let auto_start = { - let settings_file = app_data_dir.join("settings.json"); - std::fs::read_to_string(settings_file) - .ok() - .and_then(|json| serde_json::from_str::(&json).ok()) - .is_some_and(|s| s.auto_start && !s.worker_secret.is_empty()) - }; + let manager = WorkerManager::new(settings_path.clone()); + + let auto_start_worker = std::fs::read_to_string(&settings_path) + .ok() + .and_then(|json| serde_json::from_str::(&json).ok()) + .is_some_and(|s| s.auto_start && !s.worker_secret.is_empty()); app.manage(manager); - if auto_start { - let handle = rt; + if auto_start_worker { + let handle = app.handle().clone(); tauri::async_runtime::spawn(async move { let manager = handle.state::(); if let Err(e) = manager.start_worker().await { tracing::error!(error = %e, "auto-start failed"); } }); - } else if let Some(window) = app.get_webview_window("main") { - let _ = window.show(); - let _ = window.set_focus(); } + // Always show the window on launch. Auto-start controls the worker, + // not window visibility — previously these were conflated, which meant + // every wizard-completed user (auto_start defaulted to true) became a + // silent-tray user on subsequent launches and appeared to have a dead app. + show_main_window(app.handle()); + build_tray(app)?; // Kick off a silent update check shortly after launch. Errors are @@ -198,6 +209,22 @@ fn main() { Ok(()) }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + .build(tauri::generate_context!()) + .expect("error while building tauri application"); + + app.run(handle_run_event); +} + +#[cfg(target_os = "macos")] +fn handle_run_event(app: &AppHandle, event: RunEvent) { + // macOS sends Reopen when the user clicks the Dock icon or re-launches the + // app from Finder while a copy is already running. Without handling it, the + // second launch does nothing visible — exactly the "busted if you're authed" + // symptom reported in v0.1.4. + if let RunEvent::Reopen { .. } = event { + show_main_window(app); + } } + +#[cfg(not(target_os = "macos"))] +fn handle_run_event(_app: &AppHandle, _event: RunEvent) {} diff --git a/crates/modelrelay-desktop/tauri.conf.json b/crates/modelrelay-desktop/tauri.conf.json index 8ee855b..d3ef592 100644 --- a/crates/modelrelay-desktop/tauri.conf.json +++ b/crates/modelrelay-desktop/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nickel-org/nickel.rs/master/docs/tauri.conf.json", "productName": "ModelRelay", - "version": "0.1.4", + "version": "0.1.5", "identifier": "com.modelrelay.desktop", "build": { "frontendDist": "./ui"