Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/modelrelay-desktop/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
75 changes: 51 additions & 24 deletions crates/modelrelay-desktop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool, String> {
let settings = manager.get_settings().await;
Expand Down Expand Up @@ -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())
Expand All @@ -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::<AppSettings>(&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::<AppSettings>(&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::<WorkerManager>();
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
Expand All @@ -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) {}
2 changes: 1 addition & 1 deletion crates/modelrelay-desktop/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Loading