diff --git a/src-tauri/src/vault/cache.rs b/src-tauri/src/vault/cache.rs index 78c78378..eefc9801 100644 --- a/src-tauri/src/vault/cache.rs +++ b/src-tauri/src/vault/cache.rs @@ -7,7 +7,7 @@ use super::{parse_md_file, scan_vault, VaultEntry}; // --- Vault Cache --- /// Bump this when VaultEntry fields change to force a full rescan. -const CACHE_VERSION: u32 = 4; +const CACHE_VERSION: u32 = 5; #[derive(Debug, Serialize, Deserialize)] struct VaultCache { diff --git a/src-tauri/src/vault/mod.rs b/src-tauri/src/vault/mod.rs index 03f6ecfd..360cbaaa 100644 --- a/src-tauri/src/vault/mod.rs +++ b/src-tauri/src/vault/mod.rs @@ -14,8 +14,8 @@ pub use rename::{rename_note, RenameResult}; pub use trash::{delete_note, purge_trash}; use parsing::{ - capitalize_first, contains_wikilink, count_body_words, extract_outgoing_links, extract_snippet, - extract_title, parse_iso_date, + contains_wikilink, count_body_words, extract_outgoing_links, extract_snippet, extract_title, + parse_iso_date, title_case_folder, }; use gray_matter::engine::YAML; @@ -274,7 +274,7 @@ fn infer_type_from_folder(folder: &str) -> String { "month" => "Month", "essay" => "Essay", "evergreen" => "Evergreen", - _ => return capitalize_first(folder), + _ => return title_case_folder(folder), } .to_string() } @@ -960,6 +960,28 @@ References: assert_eq!(entry.is_a, Some("Recipe".to_string())); } + #[test] + fn test_infer_type_from_hyphenated_folder_title_cases() { + let dir = TempDir::new().unwrap(); + let cases = vec![ + ("monday-ideas", "Monday Ideas"), + ("key-result", "Key Result"), + ("my_custom_type", "My Custom Type"), + ("mix-and_match", "Mix And Match"), + ]; + for (folder, expected) in cases { + create_test_file(dir.path(), &format!("{}/test.md", folder), "# Test\n"); + let entry = parse_md_file(&dir.path().join(folder).join("test.md")).unwrap(); + assert_eq!( + entry.is_a, + Some(expected.to_string()), + "folder '{}' should infer type '{}'", + folder, + expected + ); + } + } + #[test] fn test_infer_type_frontmatter_overrides_folder() { let dir = TempDir::new().unwrap(); diff --git a/src-tauri/src/vault/parsing.rs b/src-tauri/src/vault/parsing.rs index 975c74ed..29c8ea4c 100644 --- a/src-tauri/src/vault/parsing.rs +++ b/src-tauri/src/vault/parsing.rs @@ -195,6 +195,16 @@ pub(super) fn capitalize_first(s: &str) -> String { } } +/// Title-case a folder name: split on hyphens and underscores, capitalize each word, join with spaces. +/// Example: "monday-ideas" → "Monday Ideas", "key_result" → "Key Result" +pub(super) fn title_case_folder(s: &str) -> String { + s.split(|c| c == '-' || c == '_') + .filter(|w| !w.is_empty()) + .map(|w| capitalize_first(w)) + .collect::>() + .join(" ") +} + /// Parse an ISO 8601 date string to Unix timestamp (seconds since epoch). /// Handles "2025-05-23T14:35:00.000Z" and "2025-05-23" formats. pub(super) fn parse_iso_date(date_str: &str) -> Option { @@ -457,6 +467,29 @@ mod tests { assert_eq!(capitalize_first("a"), "A"); } + // --- title_case_folder tests --- + + #[test] + fn test_title_case_folder_hyphenated() { + assert_eq!(title_case_folder("monday-ideas"), "Monday Ideas"); + assert_eq!(title_case_folder("key-result"), "Key Result"); + } + + #[test] + fn test_title_case_folder_underscored() { + assert_eq!(title_case_folder("my_custom_type"), "My Custom Type"); + } + + #[test] + fn test_title_case_folder_single_word() { + assert_eq!(title_case_folder("recipe"), "Recipe"); + } + + #[test] + fn test_title_case_folder_empty() { + assert_eq!(title_case_folder(""), ""); + } + // --- without_h1_line tests --- #[test]