diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 95e20508..70cb92be 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -139,6 +139,12 @@ pub fn get_default_vault_path() -> Result { vault::default_vault_path().map(|p| p.to_string_lossy().to_string()) } +#[tauri::command] +pub fn reload_vault_entry(path: String) -> Result { + let path = expand_tilde(&path); + vault::reload_entry(std::path::Path::new(path.as_ref())) +} + #[tauri::command] pub fn save_image(vault_path: String, filename: String, data: String) -> Result { let vault_path = expand_tilde(&vault_path); @@ -671,6 +677,28 @@ mod tests { assert!(content.contains("Trashed at")); } + #[test] + fn test_reload_vault_entry_reads_from_disk() { + let dir = tempfile::TempDir::new().unwrap(); + let note = dir.path().join("note.md"); + std::fs::write(¬e, "---\nStatus: Active\n---\n# Test\n").unwrap(); + + let entry = reload_vault_entry(note.to_str().unwrap().to_string()).unwrap(); + assert_eq!(entry.title, "Test"); + assert_eq!(entry.status, Some("Active".to_string())); + + // Modify file on disk + std::fs::write(¬e, "---\nStatus: Done\n---\n# Test\n").unwrap(); + let fresh = reload_vault_entry(note.to_str().unwrap().to_string()).unwrap(); + assert_eq!(fresh.status, Some("Done".to_string())); + } + + #[test] + fn test_reload_vault_entry_nonexistent() { + let result = reload_vault_entry("/nonexistent/path.md".to_string()); + assert!(result.is_err()); + } + #[test] fn test_check_vault_exists_false() { assert!(!check_vault_exists("/nonexistent/path/abc123".to_string())); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index b7a4ccbc..746a5e1d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -137,6 +137,7 @@ pub fn run() { commands::check_claude_cli, commands::stream_claude_chat, commands::stream_claude_agent, + commands::reload_vault_entry, commands::save_image, commands::copy_image_to_vault, commands::purge_trash, diff --git a/src-tauri/src/vault/mod.rs b/src-tauri/src/vault/mod.rs index 75f2c347..7c6feb37 100644 --- a/src-tauri/src/vault/mod.rs +++ b/src-tauri/src/vault/mod.rs @@ -435,6 +435,15 @@ fn pod_to_json(pod: gray_matter::Pod) -> serde_json::Value { } } +/// Re-read a single file from disk and return a fresh VaultEntry. +/// Used after failed optimistic updates to restore the true filesystem state. +pub fn reload_entry(path: &Path) -> Result { + if !path.exists() { + return Err(format!("File does not exist: {}", path.display())); + } + parse_md_file(path) +} + /// Read the content of a single note file. pub fn get_note_content(path: &Path) -> Result { if !path.exists() { @@ -543,6 +552,27 @@ mod tests { parse_md_file(&dir.path().join(name)).unwrap() } + #[test] + fn test_reload_entry_returns_fresh_data() { + let dir = TempDir::new().unwrap(); + create_test_file(dir.path(), "note.md", "---\nStatus: Active\n---\n# My Note\n\nOriginal."); + let entry = reload_entry(&dir.path().join("note.md")).unwrap(); + assert_eq!(entry.title, "My Note"); + assert_eq!(entry.status, Some("Active".to_string())); + + // Modify on disk and reload — must see the new content + create_test_file(dir.path(), "note.md", "---\nStatus: Done\n---\n# My Note\n\nUpdated."); + let fresh = reload_entry(&dir.path().join("note.md")).unwrap(); + assert_eq!(fresh.status, Some("Done".to_string())); + } + + #[test] + fn test_reload_entry_nonexistent_file() { + let result = reload_entry(std::path::Path::new("/nonexistent/path/note.md")); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("does not exist")); + } + const FULL_FM_CONTENT: &str = "---\nIs A: Project\naliases:\n - Laputa\n - Castle in the Sky\nBelongs to:\n - Studio Ghibli\nRelated to:\n - Miyazaki\nStatus: Active\nOwner: Luca\nCadence: Weekly\n---\n# Laputa Project\n\nThis is a project note.\n"; #[test]