From 736f73125f3e8a1e47f62c8e4fe222eaafb630ca Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 11 Nov 2026 14:24:03 -0800 Subject: [PATCH] Plug-ins: OK after a failed preview runs the filter in the background A light filter that reports its progress kept a failed preview's interaction open, so OK ran it again on the UI thread without progress or Cancel. A failed preview is now rolled back and the run goes to the background. --- crates/ui-egui/src/dialogs/plugin.rs | 10 ++++++---- crates/ui-egui/src/tests/plugin_runs.rs | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/dialogs/plugin.rs b/crates/ui-egui/src/dialogs/plugin.rs index 9ee608a12c..eee33b460f 100644 --- a/crates/ui-egui/src/dialogs/plugin.rs +++ b/crates/ui-egui/src/dialogs/plugin.rs @@ -245,12 +245,14 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { } /// OK: a filter keeps its previewed run as one undo step; a heavy one, which has no preview, and a -/// light one that runs in the background ([`in_background`]) with Preview off, start in the -/// background ([`run`]) and the dialog closes. A preview still shown when Preview was just turned -/// off is rolled back first. +/// light one that runs in the background ([`in_background`]) with Preview off or a failed preview, +/// start in the background ([`run`]) and the dialog closes. A preview still shown when Preview was +/// just turned off, or a preview that failed, is rolled back first. fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { let own_run = d.str("__command").is_empty(); - let kept = |plugin: &Plugin| plugin.manifest().budget == Budget::Light && d.bool("preview") && app.session.in_interaction(); + let kept = |plugin: &Plugin| { + plugin.manifest().budget == Budget::Light && d.bool("preview") && app.session.in_interaction() && !d.fields.contains_key(form::REFUSED) + }; match registry::get(&d.str("__plugin")) { Some(plugin) if own_run && in_background(plugin.manifest()) && !kept(&plugin) => { if d.fields.contains_key(form::PREVIEWED) && app.session.in_interaction() { diff --git a/crates/ui-egui/src/tests/plugin_runs.rs b/crates/ui-egui/src/tests/plugin_runs.rs index 555aedd050..30df214db6 100644 --- a/crates/ui-egui/src/tests/plugin_runs.rs +++ b/crates/ui-egui/src/tests/plugin_runs.rs @@ -327,6 +327,26 @@ fn ok_runs_a_light_progress_filter_in_the_background_without_a_preview() { assert_eq!(app.session.doc().unwrap().history.undo.len(), undo + 3); } +/// In the dialog of a light filter that reports its progress, a failed preview is not kept: OK +/// rolls it back and runs the filter in the background instead of again on the UI thread. +#[test] +fn ok_after_a_failed_preview_runs_a_light_progress_filter_in_the_background() { + let mut app = app(); + let id = "test.ui.light.progress.fails"; + install_light(&mut app, id, true, r#"{"size":{"type":"number","min":1,"max":9,"default":3}}"#, "(unreachable)"); + let doc = app.session.doc().unwrap().doc.clone(); + app.run("plugin.dialog", json!({"id": id})).unwrap(); + frame(&mut app); + assert!(app.session.in_interaction(), "the preview's interaction"); + assert!(app.ui.dialog.as_ref().unwrap().fields.contains_key("__refused"), "the preview failed"); + dialogs::confirm(&mut app).unwrap(); + assert!(app.ui.dialog.is_none()); + assert!(!app.session.in_interaction(), "the failed preview was rolled back"); + assert_eq!(app.plugin_runs.running.len(), 1, "running in the background"); + finish_runs(&mut app); + assert_eq!(app.session.doc().unwrap().doc, doc); +} + /// A light filter without the `progress` permission still runs at once, on the UI thread. #[test] fn a_light_filter_that_does_not_report_progress_runs_at_once() {