From 297c819f7beb632c09853fa4ff7fbd31500d5a22 Mon Sep 17 00:00:00 2001 From: Cloud Eric Date: Sat, 18 Apr 2026 11:42:34 +0000 Subject: [PATCH 1/2] Fix desktop tray click, template icon, and first-run window (v0.1.4) Three stacked bugs made the v0.1.3 macOS install unusable on first launch (black-square tray, dead left-click, no window ever appeared): 1. Tray icon was the full-color carrier-pigeon brand mark with iconAsTemplate: true. macOS template icons must be monochrome (black + alpha), so the cream/slate-blue PNG rendered as a solid black blob. Generated black-on-alpha derivatives at icons/tray-icon.png (22x22) and tray-icon@2x.png (44x44) and pointed trayIcon.iconPath at them. Bundle icon references unchanged. 2. TrayIconBuilder had no left-click affordance. Added show_menu_on_left_click(true) plus an on_tray_icon_event fallback that shows + focuses the main window on left-click up, so the tray is never a dead UI element even if the platform fails to pop the menu. 3. Main window had visible: false and no setup() show() call, so a broken tray meant no UI at all on first launch. Now show + focus the main window unless the user is a returning auto_start + non-empty worker_secret case (silent tray launch). Bumped Cargo.toml, tauri.conf.json, and Cargo.lock to 0.1.4 and added a CHANGELOG entry summarizing the three fixes. --- CHANGELOG.md | 12 ++++++++ Cargo.lock | 2 +- crates/modelrelay-desktop/Cargo.toml | 2 +- crates/modelrelay-desktop/icons/tray-icon.png | Bin 0 -> 448 bytes .../modelrelay-desktop/icons/tray-icon@2x.png | Bin 0 -> 1058 bytes crates/modelrelay-desktop/src/main.rs | 28 ++++++++++++++++-- crates/modelrelay-desktop/tauri.conf.json | 4 +-- 7 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 crates/modelrelay-desktop/icons/tray-icon.png create mode 100644 crates/modelrelay-desktop/icons/tray-icon@2x.png diff --git a/CHANGELOG.md b/CHANGELOG.md index e737db3..fbab451 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.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 diff --git a/Cargo.lock b/Cargo.lock index 324e333..e83061b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2573,7 +2573,7 @@ dependencies = [ [[package]] name = "modelrelay-desktop" -version = "0.1.3" +version = "0.1.4" dependencies = [ "modelrelay-protocol", "modelrelay-worker", diff --git a/crates/modelrelay-desktop/Cargo.toml b/crates/modelrelay-desktop/Cargo.toml index 1b6d90e..be63576 100644 --- a/crates/modelrelay-desktop/Cargo.toml +++ b/crates/modelrelay-desktop/Cargo.toml @@ -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 diff --git a/crates/modelrelay-desktop/icons/tray-icon.png b/crates/modelrelay-desktop/icons/tray-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dd22a786b7699a70e03bb8c11f7d3695baa0a471 GIT binary patch literal 448 zcmV;x0YCnUP)BjTYH-L+U219wqM=Ee-CSB5GzfyA$SE2`#Gs+TEn5Vm zprL|FF!e^|z13|v@1f7rDfK!DK5#hSpZ9&9?|r`a;{YY7pdbQrVLhP1Rb|*hS}obUi_bDUeqaM#xDpi{ z$5~v%6FkJ}i0|+OC?BK_!Votc!X2<7BS7R@A^KmO#7%65$k#~9AIo#}m*iE_ zLH_mO42ESMZ^-=YmR(*0?_`~EJ1VG#a9(MvsM3k^n31*G7>>n$Cl2}hVH7)RTciZV qhOgTCFm_FtCZ~3_jXe|WU;GBMLRn8`t|wan0000m}`4AN(6?8G9AbJReprVJOh#(>;2?APhfuAeZRj)^@Q5>F(F~sZXv8(WX5*o&Vyxxb0a2;M%IW&n^a7xY} z=oaBdE_M^v;TeqLF!tAoe~bIXZoI8JpkL#PwAGbJmqk{K=)EYUuvJ8QG>I2Ue2F`R zgP-TK38DL9Z_agHNUu)w;|#jc$5aN4PBy2!3kyPlO@D5TfoHIXR}3LTHOjiLN4(O!{rT0-YU0h(@g z>{v;sbCL&eE-t`lI1A^9BGObvrZ_i*E3ph$iyhdEv++}YvjW$MiaCfacnafbZz65W z`FMaEA-p{UABgA-t0LO!V5d%cAD+dHNunVt2&pW^n|KLlCfNnpfp5xM(-nFl zeR>*QEi%W1s-$;96Z5b*36xY=w7DS1e!PxL#4aww54a^AdQSvsawaFUke(;<@;$lO z*c5~%!J_p>?ODIbB-`;3wuvk=Bo6J)ba108b6G@^EzFRaqO`)yfBs1JLhH9g9ccZwownn4& zJ8S<9=i@RV{=K>IAt8y$B-a!&8P74HYV1ody%EdP=2U6$U(v4N0g=F0;c{Foa$>*e zz)D)%fwyp;C?pM$;`;Kt5s^oC;Zyva_K!G3M@zU~g4f~Nyk9J$HjuyX;$>0sW`cgz zp_?--7kd2#PvHCxBnW5Qdx_FAmKJhOQ9lHOB3IVmb^Y)BbWx95S0XhVXy5K0dg!5t c9%dJR0aqIuGD2 { if let Some(window) = app.get_webview_window("main") { diff --git a/crates/modelrelay-desktop/tauri.conf.json b/crates/modelrelay-desktop/tauri.conf.json index 150cd64..8ee855b 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.3", + "version": "0.1.4", "identifier": "com.modelrelay.desktop", "build": { "frontendDist": "./ui" @@ -18,7 +18,7 @@ } ], "trayIcon": { - "iconPath": "icons/icon.png", + "iconPath": "icons/tray-icon.png", "iconAsTemplate": true } }, From 673ebb56a8fb35e30350bbeb93d98ed4fe315b1f Mon Sep 17 00:00:00 2001 From: Cloud Eric Date: Sat, 18 Apr 2026 11:46:22 +0000 Subject: [PATCH 2/2] Extract build_tray helper to satisfy clippy::too_many_lines --- crates/modelrelay-desktop/src/main.rs | 137 +++++++++++++------------- 1 file changed, 71 insertions(+), 66 deletions(-) diff --git a/crates/modelrelay-desktop/src/main.rs b/crates/modelrelay-desktop/src/main.rs index bf593be..e1c2a31 100644 --- a/crates/modelrelay-desktop/src/main.rs +++ b/crates/modelrelay-desktop/src/main.rs @@ -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::fetch_update_summary(&app).await @@ -120,72 +190,7 @@ fn main() { 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) - .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)?; + build_tray(app)?; // Kick off a silent update check shortly after launch. Errors are // logged and never block startup.