From fd02000fb5fe394ee268e31464c27af71d189e99 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Sat, 10 Oct 2026 19:53:09 -0700 Subject: [PATCH] fix(desktop): skip section prefetch during structure changes Hold the notebook structure guard while section prefetch opens its replica and reads an owned page. Optional prefetch skips busy notebooks, preventing queued native jobs from reopening replicas during folder rename. Add a real notebook regression proving a guarded prefetch does not create its section replica. Fixes #109 Assisted-by: gpt-6.1-sol --- crates/snowbound/src/prefetch.rs | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/crates/snowbound/src/prefetch.rs b/crates/snowbound/src/prefetch.rs index 17c6764037d229aa0a756664785be0df8cbbff1e..5e450671a70c2e5d345f59d593b4566891cd95cf 100644 --- a/crates/snowbound/src/prefetch.rs +++ b/crates/snowbound/src/prefetch.rs @@ -208,6 +208,9 @@ fn prepare(job: Job, layouts: &Mutex, scenes: &Scenes) -> Result<(), last, notify, } => { + let Some(_structure) = library.structure.try_lock() else { + return Ok(()); + }; let replica = library.prefetch(&path, notify)?; let pages = replica.pages()?; let space = last @@ -360,6 +363,47 @@ impl State { mod tests { use super::*; + #[test] + fn section_prefetch_skips_while_notebook_structure_changes() { + use notebook::session::Notebook; + + let root = std::env::temp_dir().join(format!("snowbound-prefetch-{}", std::process::id())); + let _ = notebook::fs::remove_dir_all(&root); + notebook::fs::create_dir_all(&root).unwrap(); + let cache = root.join("cache"); + let page = onestore::PageCreation::new(None, Some("original"), "Fixture").unwrap(); + let notebook = + Notebook::create(root.join("Personal"), &cache, Notebook::NEW_COLOR, &page).unwrap(); + let path = "New Section 1.one"; + let replica = notebook.replica_path(path).unwrap(); + drop(notebook); + let library = Arc::new(Library::notebook( + root.join("Personal").to_str().unwrap(), + &cache, + )); + if let Some(background) = &library.background { + background.stop(); + } + assert!(notebook::fs::metadata(&replica).is_err()); + { + let _changing = library.structure.try_lock().unwrap(); + prepare( + Job::Section { + library: Arc::clone(&library), + path: path.into(), + last: None, + notify: Box::new(|| {}), + }, + &Mutex::new(TextEngine::default()), + &Scenes::new(Mutex::new(Recent::new(SCENES))), + ) + .unwrap(); + assert!(notebook::fs::metadata(&replica).is_err()); + } + drop(library); + notebook::fs::remove_dir_all(root).unwrap(); + } + #[test] fn the_least_recent_go_first_past_the_count() { let mut recent = Recent::new(2); -- 2.54.0