From 2ad3e3f76e8e6fb7a6d4a84b6a6a79c0cb506355 Mon Sep 17 00:00:00 2001 From: Dusty Date: Wed, 21 Oct 2026 11:47:44 -0700 Subject: [PATCH] fix(desktop): keep the window responsive while installation holds the startup lock restore_window_on, which runs on the event loop for the macOS reopen event and the tray/menu "Open OpenBot" item, waited on shell.startup. prepare_installation holds that lock for the whole image download, so focusing or clicking the window during an install hung the UI (spinning cursor) until the download finished. When the lock is busy, the restore now shows and focuses the window as it is and skips only the choice between the setup page and the app, which is made again next time. With the lock free nothing changes. --- CHANGELOG.md | 5 +++ desktop/src-tauri/src/main.rs | 63 +++++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b1e6a8afc..6148997f96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,11 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged. `code_required` (below). A server and a computer must be upgraded together: an older computer has no `/sign-in/code`. +### The desktop window answers while software is installing + +Clicking or focusing the desktop window while it downloaded local software showed a spinning cursor +until the download finished. The window now comes forward at once and keeps the setup screen. + ### A new desktop install starts its server again A desktop install of v0.2.0 stopped at start with `server stopped straight away (exit status: 1)` and diff --git a/desktop/src-tauri/src/main.rs b/desktop/src-tauri/src/main.rs index 3877d8b906..d79dbb594f 100644 --- a/desktop/src-tauri/src/main.rs +++ b/desktop/src-tauri/src/main.rs @@ -3618,18 +3618,28 @@ fn restore_window_on(app: &tauri::AppHandle, ports: &openb return; }; let shell = app.state::(); - let _startup = shell.startup.lock().unwrap(); - let root = cleanup_root(&shell, &stack::default_root()); + // Reopen and the menus call this on the event-loop thread, and installation holds startup for + // the whole download. Waiting for it there froze the window until the download finished. A + // busy lock means setup is at work and the window is already on its screen, so show it as it + // is and leave which page it belongs on to the next restore. + let startup = match shell.startup.try_lock() { + Ok(guard) => Some(guard), + Err(std::sync::TryLockError::WouldBlock) => None, + Err(std::sync::TryLockError::Poisoned(error)) => panic!("{error}"), + }; // Restore has the same deployment ownership requirement as the setup page's passive probe. // A successful app-port response alone may belong to another installation or application. - let destination = if let Some(url) = (!recovery_required_or_pending_quit_notice(&shell, &root)) - .then(|| owned_app_url(&root, ports)) - .flatten() - { - url.parse().ok() - } else { - setup_destination(app).ok() - }; + let destination = startup.as_ref().and_then(|_| { + let root = cleanup_root(&shell, &stack::default_root()); + if let Some(url) = (!recovery_required_or_pending_quit_notice(&shell, &root)) + .then(|| owned_app_url(&root, ports)) + .flatten() + { + url.parse().ok() + } else { + setup_destination(app).ok() + } + }); if let Some(destination) = destination { let already_showing = window .url() @@ -10345,6 +10355,39 @@ fn main() { } } + #[test] + fn restore_window_shows_setup_without_waiting_for_startup_held_by_installation() { + let f = RestoreFixture::new(); + let app = f.app(&f.owned, "tauri://localhost/"); + let window = app.get_webview_window("main").unwrap(); + let installing = "tauri://localhost/#installing"; + window.navigate(installing.parse().unwrap()).unwrap(); + window.hide().unwrap(); + let shell = app.state::(); + let held = shell.startup.lock().unwrap(); + let (done, finished) = std::sync::mpsc::channel(); + let restoring = app.handle().clone(); + let ports = f.ports; + std::thread::spawn(move || { + restore_window_on(&restoring, &ports); + let _ = done.send(()); + }); + let returned = finished.recv_timeout(std::time::Duration::from_secs(5)); + drop(held); + returned.expect("restore waited on the startup lock an installation was holding"); + assert_eq!(window.url().unwrap().as_str(), installing); + assert!(window.is_visible().unwrap()); + + // Free again, the next restore makes the decision it skipped. + window.hide().unwrap(); + restore_window_on(app.handle(), &f.ports); + assert_eq!( + window.url().unwrap().as_str(), + format!("http://127.0.0.1:{}/", f.ports.app) + ); + assert!(window.is_visible().unwrap()); + } + #[test] fn restore_window_unavailable_runtime_replaces_stale_page_with_setup() { let mut f = RestoreFixture::new();