diff --git a/studio/src-tauri/src/install.rs b/studio/src-tauri/src/install.rs index d7226bf901..39d67dc427 100644 --- a/studio/src-tauri/src/install.rs +++ b/studio/src-tauri/src/install.rs @@ -783,6 +783,14 @@ pub fn record_install_intentional_stop(state: &InstallState, diagnostics: &Diagn } } +/// True while an installer runs; quitting now would leave a broken venv. +pub fn is_install_running(state: &InstallState) -> bool { + state + .lock() + .map(|install| install.child.is_some()) + .unwrap_or(false) +} + /// Stop a running install process gracefully. /// Unix: SIGTERM to process group -> wait up to 5s -> SIGKILL /// Windows: hidden taskkill /T /F to terminate the installer tree diff --git a/studio/src-tauri/src/main.rs b/studio/src-tauri/src/main.rs index a867700035..0d39217ecd 100644 --- a/studio/src-tauri/src/main.rs +++ b/studio/src-tauri/src/main.rs @@ -85,6 +85,33 @@ fn setup_custom_titlebar(app: &tauri::App) -> Result<(), Box bool { + use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind}; + + let Some(install_state) = app.try_state::() else { + return true; + }; + if !install::is_install_running(&install_state) { + return true; + } + app.dialog() + .message( + "Unsloth Studio is still installing. Quitting now stops it part-way and \ + leaves the installation incomplete, so it will need to be repaired before \ + it can start.", + ) + .kind(MessageDialogKind::Warning) + .title("Installation in progress") + .buttons(MessageDialogButtons::OkCancelCustom( + "Quit anyway".to_string(), + "Keep installing".to_string(), + )) + .blocking_show() +} + fn cleanup_child_processes(app: &tauri::AppHandle) { let diagnostics_state = app .try_state::() @@ -138,6 +165,9 @@ fn setup_tray(app: &tauri::App) -> Result<(), Box> { // leaving the backend orphaned. let app_handle = app.clone(); std::thread::spawn(move || { + if !confirm_quit_during_install(&app_handle) { + return; + } cleanup_child_processes(&app_handle); app_handle.exit(0); });