From f14bb9931304ac50d96a4a291add68ae3d98b203 Mon Sep 17 00:00:00 2001 From: multiplex55 <6619098+multiplex55@users.noreply.github.com> Date: Mon, 16 Feb 2026 21:03:31 -0500 Subject: [PATCH] Refactor note save slug collision handling --- src/plugins/note.rs | 129 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-) diff --git a/src/plugins/note.rs b/src/plugins/note.rs index c91602de..8835b990 100644 --- a/src/plugins/note.rs +++ b/src/plugins/note.rs @@ -656,14 +656,34 @@ pub fn available_tags() -> Vec { pub fn save_note(note: &mut Note, overwrite: bool) -> anyhow::Result { let dir = notes_dir(); std::fs::create_dir_all(&dir)?; - // Ensure slug lookup is aware of existing notes - let _ = load_notes(); + let existing_slugs: HashSet = CACHE + .lock() + .map(|cache| { + cache + .notes + .iter() + .filter(|cached| { + (note.path.as_os_str().is_empty() || cached.path != note.path) + && (note.slug.is_empty() || cached.slug != note.slug) + }) + .map(|cached| cached.slug.clone()) + .collect() + }) + .unwrap_or_default(); + let slug = if note.slug.is_empty() { + reset_slug_lookup(); + for existing in &existing_slugs { + register_slug(existing); + } unique_slug(¬e.title) } else { note.slug.clone() }; let path = dir.join(format!("{slug}.md")); + if existing_slugs.contains(&slug) && note.path != path && !overwrite { + return Ok(false); + } if path.exists() && note.path != path && !overwrite { return Ok(false); } @@ -1990,4 +2010,109 @@ Body", restore_cache(original); } + + #[test] + fn save_existing_note_succeeds_without_overwrite() { + use std::fs; + use tempfile::tempdir; + + let dir = tempdir().unwrap(); + let prev = std::env::var("ML_NOTES_DIR").ok(); + std::env::set_var("ML_NOTES_DIR", dir.path()); + + let path = dir.path().join("alpha.md"); + fs::write(&path, "# Alpha\n\nold").unwrap(); + refresh_cache().unwrap(); + + let mut note = Note { + title: "Alpha".into(), + path: path.clone(), + content: "# Alpha\n\nupdated".into(), + tags: Vec::new(), + links: Vec::new(), + slug: "alpha".into(), + alias: None, + entity_refs: Vec::new(), + }; + + let saved = save_note(&mut note, false).unwrap(); + assert!(saved); + assert_eq!(fs::read_to_string(path).unwrap(), "# Alpha\n\nupdated"); + + if let Some(p) = prev { + std::env::set_var("ML_NOTES_DIR", p); + } else { + std::env::remove_var("ML_NOTES_DIR"); + } + } + + #[test] + fn save_as_new_generates_unique_slug_from_cache() { + use tempfile::tempdir; + + let dir = tempdir().unwrap(); + let prev = std::env::var("ML_NOTES_DIR").ok(); + std::env::set_var("ML_NOTES_DIR", dir.path()); + + std::fs::write(dir.path().join("alpha.md"), "# Alpha\n\nbody").unwrap(); + refresh_cache().unwrap(); + + let mut note = Note { + title: "Alpha".into(), + path: PathBuf::new(), + content: "Body".into(), + tags: Vec::new(), + links: Vec::new(), + slug: String::new(), + alias: None, + entity_refs: Vec::new(), + }; + + let saved = save_note(&mut note, false).unwrap(); + assert!(saved); + assert_eq!(note.slug, "alpha-1"); + assert!(dir.path().join("alpha-1.md").exists()); + + if let Some(p) = prev { + std::env::set_var("ML_NOTES_DIR", p); + } else { + std::env::remove_var("ML_NOTES_DIR"); + } + } + + #[test] + fn save_note_renames_slug_and_path() { + use tempfile::tempdir; + + let dir = tempdir().unwrap(); + let prev = std::env::var("ML_NOTES_DIR").ok(); + std::env::set_var("ML_NOTES_DIR", dir.path()); + + let old_path = dir.path().join("alpha.md"); + std::fs::write(&old_path, "# Alpha\n\nbody").unwrap(); + refresh_cache().unwrap(); + + let mut note = Note { + title: "Alpha renamed".into(), + path: old_path.clone(), + content: "Body".into(), + tags: Vec::new(), + links: Vec::new(), + slug: "alpha-renamed".into(), + alias: None, + entity_refs: Vec::new(), + }; + + let saved = save_note(&mut note, false).unwrap(); + assert!(saved); + assert!(!old_path.exists()); + assert_eq!(note.path, dir.path().join("alpha-renamed.md")); + assert!(note.path.exists()); + + if let Some(p) = prev { + std::env::set_var("ML_NOTES_DIR", p); + } else { + std::env::remove_var("ML_NOTES_DIR"); + } + } }