From 12f199b2b517956cbadb6db5a1332e4e68672f08 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Sun, 25 Oct 2026 13:55:49 -0400 Subject: [PATCH] feat(update): Updater::show_if holds the install back while the app has unsaved work The restart helper is scheduled before the app closes, so a close the user then cancels would leave it waiting for an exit that never comes. Apps pass whether it is safe to start. --- crates/update/src/updater.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/update/src/updater.rs b/crates/update/src/updater.rs index d6107891..7519ccc4 100644 --- a/crates/update/src/updater.rs +++ b/crates/update/src/updater.rs @@ -215,6 +215,13 @@ impl Updater { /// Draws the startup prompt and starts what it asks for. pub fn show(&mut self, ctx: &egui::Context) -> Action { + self.show_if(ctx, true) + } + + /// Like [`Self::show`], but an installation only starts when `can_start` is true. An app passes + /// `false` while it holds unsaved work: the restart helper is scheduled before the app closes, + /// and a close that the user then cancels would leave it waiting for an exit that never comes. + pub fn show_if(&mut self, ctx: &egui::Context, can_start: bool) -> Action { if std::mem::take(&mut self.restart) { if let Some(prompt) = &mut self.prompt { prompt.is_installing = false; @@ -226,7 +233,7 @@ impl Updater { match prompt.show(ctx) { StartupPromptResponse::Pending => Action::None, StartupPromptResponse::UpdateAndRestart => { - self.start_install(ctx); + self.request_install(ctx, can_start); Action::None } StartupPromptResponse::Download => { @@ -242,6 +249,14 @@ impl Updater { } } + fn request_install(&mut self, ctx: &egui::Context, can_start: bool) { + if can_start { + self.start_install(ctx); + } else if let Some(prompt) = &mut self.prompt { + prompt.status_message = Some("Save or close your open work first; the update restarts the app.".into()); + } + } + fn start_install(&mut self, ctx: &egui::Context) { let Some(prompt) = &mut self.prompt else { return }; if self.installed { @@ -422,6 +437,16 @@ mod tests { assert!(!u.prompt_open(), "no second offer after an install"); } + #[test] + fn an_install_waits_while_the_app_holds_unsaved_work() { + let ctx = egui::Context::default(); + let mut u = updater(Ok(None)); + u.offer(newer()); + u.request_install(&ctx, false); + assert!(u.install.is_none(), "nothing is scheduled while there is unsaved work"); + assert!(u.prompt.as_ref().is_some_and(|p| !p.is_installing && p.status_message.is_some())); + } + #[test] fn a_finished_install_asks_the_app_to_close_once() { let ctx = egui::Context::default();