diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs
--- a/crates/engine/src/cmd/edit.rs
+++ b/crates/engine/src/cmd/edit.rs
@@ -1,7 +1,7 @@
 //! Edit menu commands.
 use super::{
-    CommandSpec, always, bad, bool_or, can_redo, can_undo, clip_id, clip_ids_param, f32_or, f64_or, has_clipboard, has_range, has_selection,
-    has_tracks, i64_or, position_param, range_param, str_param, track_param, tracks_param, tracks_required,
+    CommandSpec, always, bad, bool_or, can_redo, can_undo, clip_id, clip_ids_param, f32_or, has_clipboard, has_range, has_selection, has_tracks,
+    i64_or, position_param, range_param, str_param, track_param, tracks_param, tracks_required,
 };
 use soundcraft_time::Samples;
 
@@ -125,15 +125,6 @@ pub fn specs() -> Vec<CommandSpec> {
             has_range,
             strip_silence
         ),
-        cmd!(
-            query "edit.strip_silence_preview",
-            "Strip Silence Rectangles",
-            [],
-            None,
-            "{threshold_db?: -48, min_length_ms?: 50, pad_before_ms?: 5, pad_after_ms?: 20, tracks?, start?, end?} (the ranges edit.strip_silence finds with the same settings, padded, on each track: what Strip keeps and the Strip Silence window draws as rectangles; changes nothing)",
-            has_range,
-            strip_silence_preview
-        ),
         cmd!(
             "edit.fades_create",
             "Create...",
@@ -812,38 +803,8 @@ fn copy_to_new_playlist(e: &mut Engine, p: &Value) -> Result<Value> {
     Ok(json!({"tracks": tracks.len()}))
 }
 
-/// Strip Silence's tracks, range, threshold, minimum strip duration and clip start and end pads.
-type StripSettings = (Vec<TrackId>, soundcraft_time::Range, f32, Samples, (Samples, Samples));
-
-/// The [`StripSettings`] in Strip Silence's params, with their defaults and limits. `cmd` names the
-/// command in errors.
-fn strip_settings(e: &Engine, cmd: &str, p: &Value) -> Result<StripSettings> {
-    let tracks = tracks_required(e, cmd, p)?;
-    let r = range_param(e, cmd, p)?;
-    let thr = f32_or(p, "threshold_db", -48.0).clamp(-96.0, 0.0);
-    let sr = e.session().sample_rate;
-    let ms = |k: &str, d: f64| sr.samples(f64_or(p, k, d).clamp(0.0, 10_000.0) / 1000.0);
-    Ok((tracks, r, thr, ms("min_length_ms", 50.0), (ms("pad_before_ms", 5.0), ms("pad_after_ms", 20.0))))
-}
-
-/// The scan `edit.strip_silence_preview` runs with params `p`, on the session as it is now. The Edit
-/// window runs it while the Strip Silence window or its Rename Selected Clips dialog is open: on a
-/// worker thread outside the web build, and on the frame thread in the web build or when no thread
-/// can be started.
-pub fn strip_silence_scan(e: &Engine, p: &Value) -> Result<crate::io::StripScan> {
-    let (tracks, range, threshold_db, min_len, pad) = strip_settings(e, "edit.strip_silence_preview", p)?;
-    Ok(crate::io::StripScan { session: e.session_arc(), tracks, range, threshold_db, min_len, pad, stop: Default::default() })
-}
-
-fn strip_silence_preview(e: &mut Engine, p: &Value) -> Result<Value> {
-    let found = strip_silence_scan(e, p)?.run()?;
-    let tracks: Vec<Value> =
-        found.iter().map(|(t, ranges)| json!({"track": t.0, "ranges": ranges.iter().map(|x| [x.start, x.end]).collect::<Vec<_>>()})).collect();
-    Ok(json!({"tracks": tracks}))
-}
-
 fn strip_silence(e: &mut Engine, p: &Value) -> Result<Value> {
-    let (tracks, r, thr, min_len, (pre, post)) = strip_settings(e, "edit.strip_silence", p)?;
+    let (tracks, r, thr, min_len, (pre, post)) = super::strip_silence::strip_settings(e, "edit.strip_silence", p)?;
     let mode = match p.get("mode") {
         None => crate::io::StripMode::Strip,
         Some(Value::String(m)) => crate::io::StripMode::from_id(m).ok_or_else(|| bad("edit.strip_silence", format!("unknown mode `{m}`")))?,
diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs
--- a/crates/engine/src/cmd/mod.rs
+++ b/crates/engine/src/cmd/mod.rs
@@ -28,6 +28,7 @@ mod plugin_state;
 mod query;
 pub(crate) mod sampler;
 mod setup_more;
+mod strip_silence;
 mod surround;
 mod tab;
 mod track;
@@ -63,7 +64,7 @@ mod panic_probe {
 pub use file::bounce_job;
 
 pub(crate) use edit::remember_selection;
-pub use edit::strip_silence_scan;
+pub use strip_silence::strip_silence_scan;
 
 pub type Run = fn(&mut Engine, &Value) -> Result<Value>;
 /// `Err(reason)` = disabled.
@@ -160,6 +161,7 @@ pub fn command_specs() -> &'static [CommandSpec] {
         v.extend(sampler::specs());
         v.extend(video::specs());
         v.extend(zoom_toggle::specs());
+        v.extend(strip_silence::specs());
         #[cfg(test)]
         v.extend(panic_probe::specs());
         v
diff --git a/crates/engine/src/cmd/strip_silence.rs b/crates/engine/src/cmd/strip_silence.rs
new file mode 100644
--- /dev/null
+++ b/crates/engine/src/cmd/strip_silence.rs
@@ -0,0 +1,49 @@
+//! Strip Silence Rectangles: the ranges Strip Silence keeps, which the Strip Silence window draws.
+use super::{CommandSpec, f32_or, f64_or, has_range, range_param, tracks_required};
+use soundcraft_model::TrackId;
+use soundcraft_time::Samples;
+
+use crate::{Engine, Result, cmd};
+use serde_json::{Value, json};
+
+pub fn specs() -> Vec<CommandSpec> {
+    vec![cmd!(
+        query "edit.strip_silence_preview",
+        "Strip Silence Rectangles",
+        [],
+        None,
+        "{threshold_db?: -48, min_length_ms?: 50, pad_before_ms?: 5, pad_after_ms?: 20, tracks?, start?, end?} (the ranges edit.strip_silence finds with the same settings, padded, on each track: what Strip keeps and the Strip Silence window draws as rectangles; changes nothing)",
+        has_range,
+        strip_silence_preview
+    )]
+}
+
+/// Strip Silence's tracks, range, threshold, minimum strip duration and clip start and end pads.
+pub(super) type StripSettings = (Vec<TrackId>, soundcraft_time::Range, f32, Samples, (Samples, Samples));
+
+/// The [`StripSettings`] in Strip Silence's params, with their defaults and limits. `cmd` names the
+/// command in errors.
+pub(super) fn strip_settings(e: &Engine, cmd: &str, p: &Value) -> Result<StripSettings> {
+    let tracks = tracks_required(e, cmd, p)?;
+    let r = range_param(e, cmd, p)?;
+    let thr = f32_or(p, "threshold_db", -48.0).clamp(-96.0, 0.0);
+    let sr = e.session().sample_rate;
+    let ms = |k: &str, d: f64| sr.samples(f64_or(p, k, d).clamp(0.0, 10_000.0) / 1000.0);
+    Ok((tracks, r, thr, ms("min_length_ms", 50.0), (ms("pad_before_ms", 5.0), ms("pad_after_ms", 20.0))))
+}
+
+/// The scan `edit.strip_silence_preview` runs with params `p`, on the session as it is now. The Edit
+/// window runs it while the Strip Silence window or its Rename Selected Clips dialog is open: on a
+/// worker thread outside the web build, and on the frame thread in the web build or when no thread
+/// can be started.
+pub fn strip_silence_scan(e: &Engine, p: &Value) -> Result<crate::io::StripScan> {
+    let (tracks, range, threshold_db, min_len, pad) = strip_settings(e, "edit.strip_silence_preview", p)?;
+    Ok(crate::io::StripScan { session: e.session_arc(), tracks, range, threshold_db, min_len, pad, stop: Default::default() })
+}
+
+fn strip_silence_preview(e: &mut Engine, p: &Value) -> Result<Value> {
+    let found = strip_silence_scan(e, p)?.run()?;
+    let tracks: Vec<Value> =
+        found.iter().map(|(t, ranges)| json!({"track": t.0, "ranges": ranges.iter().map(|x| [x.start, x.end]).collect::<Vec<_>>()})).collect();
+    Ok(json!({"tracks": tracks}))
+}
diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs
--- a/crates/ui-egui/src/dialogs.rs
+++ b/crates/ui-egui/src/dialogs.rs
@@ -406,6 +406,10 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) {
     audiosuite_window(app, ctx);
     crate::bounce::show(app, ctx);
     crate::import::show(app, ctx);
+    // Whichever window shows, closing Strip Silence drops its rectangles and stops their scan.
+    if !matches!(app.dialogs.open, Some(Dialog::StripSilence { .. } | Dialog::StripSilenceRename { .. })) {
+        app.dialogs.strip_rectangles = StripRectangles::default();
+    }
     let Some(mut d) = app.dialogs.open.take() else { return };
     let mut keep = true;
     let mut action: Option<(String, Value)> = None;
@@ -914,10 +918,7 @@ pub fn strip_silence_rectangles(app: &mut SoundApp, ctx: &egui::Context) -> Stri
     let values = match &app.dialogs.open {
         Some(Dialog::StripSilence { threshold, min_ms, pre_ms, post_ms }) => [*threshold, *min_ms, *pre_ms, *post_ms],
         Some(Dialog::StripSilenceRename { strip, .. }) => *strip,
-        _ => {
-            app.dialogs.strip_rectangles = StripRectangles::default();
-            return Arc::default();
-        }
+        _ => return Arc::default(),
     };
     let key: StripKey = (app.engine.revision, values.map(f64::to_bits));
     let r = &mut app.dialogs.strip_rectangles;
diff --git a/crates/ui-egui/tests/it/strip_silence_rectangles.rs b/crates/ui-egui/tests/it/strip_silence_rectangles.rs
--- a/crates/ui-egui/tests/it/strip_silence_rectangles.rs
+++ b/crates/ui-egui/tests/it/strip_silence_rectangles.rs
@@ -109,3 +109,18 @@ fn the_rectangles_stay_while_rename_is_open_and_go_when_the_window_closes() {
     assert_eq!(rectangles(&h), 0);
     assert_eq!(clips(&h, track), before);
 }
+
+#[test]
+fn closing_the_window_while_the_mix_window_shows_stops_the_scan() {
+    let (mut h, _) = strip_silence_dialog();
+    if let Some(Dialog::StripSilence { threshold, .. }) = &mut h.state_mut().dialogs.open {
+        *threshold = -30.0;
+    }
+    h.run_steps(1);
+    assert!(h.state().dialogs.strip_scan_running(), "a scan for the new threshold started");
+    h.state_mut().run("window.mix", json!({})).unwrap();
+    h.run_steps(2);
+    h.state_mut().dialogs.open = None;
+    h.run_steps(2);
+    assert!(!h.state().dialogs.strip_scan_running(), "the scan still runs after the window closed");
+}
