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.4] — 2026-04-18

### Fixed

- Tray icon left-click is no longer a dead no-op on macOS: the tray menu now pops on left-click and a fallback handler shows the main window if the menu fails to open
- Tray icon renders as a real monochrome silhouette in the macOS menu bar instead of a black square — the previous full-color carrier-pigeon `icon.png` was being declared as a template image, which macOS rendered as a solid blob
- First launch now shows the main window so the onboarding wizard is reachable; only returning users with `auto_start` enabled and a saved worker secret get the silent tray-only launch

### Added

- New `crates/modelrelay-desktop/icons/tray-icon.png` and `tray-icon@2x.png` — monochrome black-on-alpha derivatives of the carrier-pigeon mark, sized for the macOS menu bar (22×22 / 44×44)

## Desktop [0.1.3] — 2026-04-18

### Changed
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.3"
version = "0.1.4"
description = "Native desktop tray app for ModelRelay workers"
edition.workspace = true
license.workspace = true
Expand Down
Binary file added crates/modelrelay-desktop/icons/tray-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added crates/modelrelay-desktop/icons/tray-icon@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
129 changes: 79 additions & 50 deletions crates/modelrelay-desktop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use modelrelay_desktop::{AppSettings, AppStatus, WorkerManager, updater};
use tauri::{
Emitter, Manager,
menu::{MenuBuilder, MenuItemBuilder},
tray::TrayIconBuilder,
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
};

#[tauri::command]
Expand Down Expand Up @@ -48,6 +48,76 @@ async fn stop_worker(manager: tauri::State<'_, WorkerManager>) -> Result<(), Str
Ok(())
}

fn build_tray(app: &tauri::App) -> tauri::Result<()> {
let show = MenuItemBuilder::with_id("show", "Open Dashboard").build(app)?;
let settings = MenuItemBuilder::with_id("settings", "Settings").build(app)?;
let check_updates =
MenuItemBuilder::with_id("check_updates", "Check for Updates\u{2026}").build(app)?;
let quit = MenuItemBuilder::with_id("quit", "Quit").build(app)?;

let menu = MenuBuilder::new(app)
.item(&show)
.item(&settings)
.separator()
.item(&check_updates)
.separator()
.item(&quit)
.build()?;

TrayIconBuilder::new()
.tooltip("ModelRelay - Disconnected")
.menu(&menu)
.show_menu_on_left_click(true)
.on_tray_icon_event(|tray, event| {
// Belt-and-suspenders fallback: even if the platform fails to pop
// the menu on left-click (seen on macOS in v0.1.3), reveal the main
// window so the tray is never a dead UI element.
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Up,
..
} = event
{
let app = tray.app_handle();
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}
})
.on_menu_event(|app, event| match event.id().as_ref() {
"show" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
let _ = window.emit("navigate-tab", "dashboard");
}
}
"settings" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
let _ = window.emit("navigate-tab", "settings");
}
}
"check_updates" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
let _ = window.emit("navigate-tab", "settings");
}
let _ = app.emit("updater-manual-check", ());
}
"quit" => {
app.exit(0);
}
_ => {}
})
.build(app)?;

Ok(())
}

#[tauri::command]
async fn check_for_update(app: tauri::AppHandle) -> Result<updater::UpdateSummary, String> {
updater::fetch_update_summary(&app).await
Expand Down Expand Up @@ -92,7 +162,10 @@ fn main() {

let manager = WorkerManager::new(settings_path);

// If auto_start is enabled, start the worker immediately
// 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");
Expand All @@ -112,56 +185,12 @@ fn main() {
tracing::error!(error = %e, "auto-start failed");
}
});
} else if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}

let show = MenuItemBuilder::with_id("show", "Open Dashboard").build(app)?;
let settings = MenuItemBuilder::with_id("settings", "Settings").build(app)?;
let check_updates =
MenuItemBuilder::with_id("check_updates", "Check for Updates\u{2026}")
.build(app)?;
let quit = MenuItemBuilder::with_id("quit", "Quit").build(app)?;

let menu = MenuBuilder::new(app)
.item(&show)
.item(&settings)
.separator()
.item(&check_updates)
.separator()
.item(&quit)
.build()?;

let _tray = TrayIconBuilder::new()
.tooltip("ModelRelay - Disconnected")
.menu(&menu)
.on_menu_event(|app, event| match event.id().as_ref() {
"show" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
let _ = window.emit("navigate-tab", "dashboard");
}
}
"settings" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
let _ = window.emit("navigate-tab", "settings");
}
}
"check_updates" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
let _ = window.emit("navigate-tab", "settings");
}
let _ = app.emit("updater-manual-check", ());
}
"quit" => {
app.exit(0);
}
_ => {}
})
.build(app)?;
build_tray(app)?;

// Kick off a silent update check shortly after launch. Errors are
// logged and never block startup.
Expand Down
4 changes: 2 additions & 2 deletions 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.3",
"version": "0.1.4",
"identifier": "com.modelrelay.desktop",
"build": {
"frontendDist": "./ui"
Expand All @@ -18,7 +18,7 @@
}
],
"trayIcon": {
"iconPath": "icons/icon.png",
"iconPath": "icons/tray-icon.png",
"iconAsTemplate": true
}
},
Expand Down