diff --git a/demo-vault-v2/config/ui.config.md b/demo-vault-v2/config/ui.config.md deleted file mode 100644 index 71acb9dd..00000000 --- a/demo-vault-v2/config/ui.config.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: config -zoom: 1.3 -view_mode: all ---- diff --git a/demo-vault-v2/ui.config.md b/demo-vault-v2/ui.config.md deleted file mode 100644 index 71acb9dd..00000000 --- a/demo-vault-v2/ui.config.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: config -zoom: 1.3 -view_mode: all ---- diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 8764414c..55ead010 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -14,9 +14,8 @@ use crate::indexing::{IndexStatus, IndexingProgress}; use crate::search::SearchResponse; use crate::settings::Settings; use crate::vault::{RenameResult, VaultEntry}; -use crate::vault_config::VaultConfig; use crate::vault_list::VaultList; -use crate::{frontmatter, git, github, indexing, menu, search, vault, vault_config, vault_list}; +use crate::{frontmatter, git, github, indexing, menu, search, vault, vault_list}; /// Expand a leading `~` or `~/` in a path string to the user's home directory. /// Returns the original string unchanged if it doesn't start with `~` or if the @@ -524,8 +523,6 @@ pub fn repair_vault(vault_path: String) -> Result { vault::migrate_is_a_to_type(&vault_path)?; // Flatten vault: move notes from type-based subfolders to root vault::flatten_vault(&vault_path)?; - // Migrate legacy config/ui.config.md → root ui.config.md - vault_config::migrate_ui_config_to_root(&vault_path); // Repair config files (AGENTS.md at root, config.md type def) vault::repair_config_files(&vault_path)?; // Ensure .gitignore with sensible defaults exists @@ -578,18 +575,6 @@ pub fn save_vault_list(list: VaultList) -> Result<(), String> { vault_list::save_vault_list(&list) } -#[tauri::command] -pub fn get_vault_config(vault_path: String) -> Result { - let vault_path = expand_tilde(&vault_path); - vault_config::get_vault_config(&vault_path) -} - -#[tauri::command] -pub fn save_vault_config(vault_path: String, config: VaultConfig) -> Result<(), String> { - let vault_path = expand_tilde(&vault_path); - vault_config::save_vault_config(&vault_path, config) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7145ea10..18b5f664 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -10,7 +10,6 @@ pub mod menu; pub mod search; pub mod settings; pub mod vault; -pub mod vault_config; pub mod vault_list; use std::process::Child; @@ -43,13 +42,6 @@ fn run_startup_tasks() { "Migrated is_a to type on startup", vault::migrate_is_a_to_type(vp_str), ); - // Migrate legacy config/ui.config.md → root ui.config.md - vault_config::migrate_ui_config_to_root(vp_str); - log_startup_result( - "Migrated hidden_sections to visible property", - vault_config::migrate_hidden_sections_to_visible(vp_str), - ); - // Migrate legacy config/agents.md → root AGENTS.md (one-time, idempotent) vault::migrate_agents_md(vp_str); // Seed AGENTS.md and config.md at vault root if missing @@ -165,9 +157,7 @@ pub fn run() { commands::get_default_vault_path, commands::register_mcp_tools, commands::check_mcp_status, - commands::repair_vault, - commands::get_vault_config, - commands::save_vault_config + commands::repair_vault ]) .build(tauri::generate_context!()) .expect("error while building tauri application") diff --git a/src-tauri/src/vault_config.rs b/src-tauri/src/vault_config.rs deleted file mode 100644 index 2b9dfb7d..00000000 --- a/src-tauri/src/vault_config.rs +++ /dev/null @@ -1,579 +0,0 @@ -use gray_matter::engine::YAML; -use gray_matter::Matter; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::Path; - -/// Vault-wide UI configuration stored in `ui.config.md` at vault root. -/// -/// This file is a regular vault note with YAML frontmatter, visible in the -/// sidebar under the "Config" section and editable like any note. -#[derive(Debug, Serialize, Deserialize, Default, Clone)] -pub struct VaultConfig { - pub zoom: Option, - pub view_mode: Option, - pub editor_mode: Option, - #[serde(default)] - pub tag_colors: Option>, - #[serde(default)] - pub status_colors: Option>, - #[serde(default)] - pub property_display_modes: Option>, -} - -const CONFIG_FILENAME: &str = "ui.config.md"; - -fn config_path(vault_path: &str) -> std::path::PathBuf { - Path::new(vault_path).join(CONFIG_FILENAME) -} - -/// Read the vault-wide UI config from `ui.config.md` at vault root. -/// Returns default values if the file doesn't exist. -pub fn get_vault_config(vault_path: &str) -> Result { - let path = config_path(vault_path); - if !path.exists() { - return Ok(VaultConfig::default()); - } - - let content = - std::fs::read_to_string(&path).map_err(|e| format!("Failed to read config: {e}"))?; - - parse_vault_config(&content) -} - -/// Parse VaultConfig from markdown content with YAML frontmatter. -fn parse_vault_config(content: &str) -> Result { - let matter = Matter::::new(); - let parsed = matter.parse(content); - - let hash = match parsed.data { - Some(gray_matter::Pod::Hash(map)) => map, - _ => return Ok(VaultConfig::default()), - }; - - let json_map: serde_json::Map = - hash.into_iter().map(|(k, v)| (k, pod_to_json(v))).collect(); - let json = serde_json::Value::Object(json_map); - - serde_json::from_value(json.clone()) - .or_else(|_| { - // If direct deserialization fails, strip the `type` field and retry - let mut map = match json { - serde_json::Value::Object(m) => m, - _ => return Ok(VaultConfig::default()), - }; - map.remove("type"); - serde_json::from_value(serde_json::Value::Object(map)) - }) - .map_err(|e| format!("Failed to parse config: {e}")) -} - -/// Save the vault-wide UI config to `ui.config.md` at vault root. -/// Creates the file if it doesn't exist. -pub fn save_vault_config(vault_path: &str, config: VaultConfig) -> Result<(), String> { - let path = config_path(vault_path); - let content = serialize_config(&config); - std::fs::write(&path, content).map_err(|e| format!("Failed to write config: {e}")) -} - -/// Migrate legacy `config/ui.config.md` → root `ui.config.md`. -/// If the root file already exists, the legacy file is simply removed. -/// Cleans up empty `config/` directory after migration. -pub fn migrate_ui_config_to_root(vault_path: &str) { - let vault = Path::new(vault_path); - let legacy = vault.join("config").join(CONFIG_FILENAME); - let root = vault.join(CONFIG_FILENAME); - - if legacy.exists() { - if !root.exists() { - // Move legacy content to root - if let Ok(content) = std::fs::read_to_string(&legacy) { - let _ = std::fs::write(&root, content); - } - } - let _ = std::fs::remove_file(&legacy); - } - - // Clean up empty config/ directory - let config_dir = vault.join("config"); - if config_dir.is_dir() { - let is_empty = std::fs::read_dir(&config_dir).map_or(true, |mut d| d.next().is_none()); - if is_empty { - let _ = std::fs::remove_dir(&config_dir); - } - } -} - -/// Serialize VaultConfig to a markdown file with YAML frontmatter. -fn serialize_config(config: &VaultConfig) -> String { - let mut lines = vec!["---".to_string(), "type: config".to_string()]; - - if let Some(zoom) = config.zoom { - lines.push(format!("zoom: {zoom}")); - } - if let Some(ref mode) = config.view_mode { - lines.push(format!("view_mode: {mode}")); - } - if let Some(ref mode) = config.editor_mode { - lines.push(format!("editor_mode: {mode}")); - } - append_string_map(&mut lines, "tag_colors", config.tag_colors.as_ref()); - append_string_map(&mut lines, "status_colors", config.status_colors.as_ref()); - append_string_map( - &mut lines, - "property_display_modes", - config.property_display_modes.as_ref(), - ); - lines.push("---".to_string()); - lines.join("\n") + "\n" -} - -/// Append a YAML map section with sorted keys for stable output. -fn append_string_map(lines: &mut Vec, key: &str, map: Option<&HashMap>) { - if let Some(m) = map { - if !m.is_empty() { - lines.push(format!("{key}:")); - let mut entries: Vec<_> = m.iter().collect(); - entries.sort_by_key(|(k, _)| k.to_owned()); - for (k, v) in entries { - lines.push(format!(" {}: {}", yaml_safe_key(k), yaml_safe_value(v))); - } - } - } -} - -/// Quote a YAML key if it contains special characters. -fn yaml_safe_key(key: &str) -> String { - if key.contains(':') || key.contains('#') || key.contains(' ') { - format!("\"{}\"", key.replace('"', "\\\"")) - } else { - key.to_string() - } -} - -/// Quote a YAML value if it contains special characters. -fn yaml_safe_value(value: &str) -> String { - if value.contains(':') - || value.contains('#') - || value.starts_with('"') - || value.starts_with('\'') - { - format!("\"{}\"", value.replace('"', "\\\"")) - } else { - value.to_string() - } -} - -/// Migrate `hidden_sections` from `ui.config.md` to `visible: false` -/// on Type notes. Returns the number of Type notes updated. -/// -/// For each type name in `hidden_sections`: -/// - If `.md` exists at vault root, adds `visible: false` to its frontmatter -/// - If it doesn't exist, creates it with `type: Type`, `title: `, `visible: false` -/// - Re-saves the config without `hidden_sections` -pub fn migrate_hidden_sections_to_visible(vault_path: &str) -> Result { - let path = config_path(vault_path); - if !path.exists() { - return Ok(0); - } - - let content = - std::fs::read_to_string(&path).map_err(|e| format!("Failed to read config: {e}"))?; - - let hidden = extract_hidden_sections(&content); - if hidden.is_empty() { - return Ok(0); - } - - let vault = Path::new(vault_path); - - let mut migrated = 0; - for type_name in &hidden { - let slug = type_name_to_slug(type_name); - let type_path = vault.join(format!("{slug}.md")); - - if type_path.exists() { - let type_content = std::fs::read_to_string(&type_path) - .map_err(|e| format!("Failed to read {}: {e}", type_path.display()))?; - if !type_content.contains("visible:") { - let updated = crate::frontmatter::update_frontmatter_content( - &type_content, - "visible", - Some(crate::frontmatter::FrontmatterValue::Bool(false)), - ) - .map_err(|e| format!("Failed to update {}: {e}", type_path.display()))?; - std::fs::write(&type_path, updated) - .map_err(|e| format!("Failed to write {}: {e}", type_path.display()))?; - } - } else { - let new_content = format!( - "---\ntype: Type\ntitle: {}\nvisible: false\n---\n\n# {}\n", - type_name, type_name - ); - std::fs::write(&type_path, new_content) - .map_err(|e| format!("Failed to write {}: {e}", type_path.display()))?; - } - migrated += 1; - } - - // Re-save config without hidden_sections - let config = parse_vault_config(&content)?; - save_vault_config(vault_path, config)?; - - Ok(migrated) -} - -/// Extract `hidden_sections` from raw YAML frontmatter. -fn extract_hidden_sections(content: &str) -> Vec { - let matter = Matter::::new(); - let parsed = matter.parse(content); - - let hash = match parsed.data { - Some(gray_matter::Pod::Hash(map)) => map, - _ => return vec![], - }; - - match hash.get("hidden_sections") { - Some(gray_matter::Pod::Array(arr)) => arr - .iter() - .filter_map(|v| match v { - gray_matter::Pod::String(s) => Some(s.clone()), - _ => None, - }) - .collect(), - _ => vec![], - } -} - -/// Convert a Type name to a filesystem slug. -fn type_name_to_slug(name: &str) -> String { - name.chars() - .map(|c| { - if c.is_ascii_alphanumeric() { - c.to_ascii_lowercase() - } else { - '-' - } - }) - .collect::() - .split('-') - .filter(|s| !s.is_empty()) - .collect::>() - .join("-") -} - -/// Convert gray_matter::Pod to serde_json::Value. -fn pod_to_json(pod: gray_matter::Pod) -> serde_json::Value { - match pod { - gray_matter::Pod::String(s) => serde_json::Value::String(s), - gray_matter::Pod::Integer(i) => serde_json::json!(i), - gray_matter::Pod::Float(f) => serde_json::json!(f), - gray_matter::Pod::Boolean(b) => serde_json::Value::Bool(b), - gray_matter::Pod::Array(arr) => { - serde_json::Value::Array(arr.into_iter().map(pod_to_json).collect()) - } - gray_matter::Pod::Hash(map) => { - let obj: serde_json::Map = - map.into_iter().map(|(k, v)| (k, pod_to_json(v))).collect(); - serde_json::Value::Object(obj) - } - gray_matter::Pod::Null => serde_json::Value::Null, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_empty_returns_defaults() { - let config = parse_vault_config("").unwrap(); - assert!(config.zoom.is_none()); - assert!(config.view_mode.is_none()); - assert!(config.tag_colors.is_none()); - } - - #[test] - fn parse_full_config() { - let content = r#"--- -type: config -zoom: 1.1 -view_mode: all -tag_colors: - engineering: blue - personal: green -status_colors: - Active: green - Done: blue -property_display_modes: - deadline: date ---- -"#; - let config = parse_vault_config(content).unwrap(); - assert_eq!(config.zoom, Some(1.1)); - assert_eq!(config.view_mode.as_deref(), Some("all")); - let tags = config.tag_colors.unwrap(); - assert_eq!(tags.get("engineering").unwrap(), "blue"); - assert_eq!(tags.get("personal").unwrap(), "green"); - let statuses = config.status_colors.unwrap(); - assert_eq!(statuses.get("Active").unwrap(), "green"); - let props = config.property_display_modes.unwrap(); - assert_eq!(props.get("deadline").unwrap(), "date"); - } - - #[test] - fn roundtrip_serialization() { - let mut tag_colors = HashMap::new(); - tag_colors.insert("work".to_string(), "blue".to_string()); - let config = VaultConfig { - zoom: Some(1.2), - view_mode: Some("editor-only".to_string()), - editor_mode: None, - tag_colors: Some(tag_colors), - status_colors: None, - property_display_modes: None, - }; - let serialized = serialize_config(&config); - let parsed = parse_vault_config(&serialized).unwrap(); - assert_eq!(parsed.zoom, Some(1.2)); - assert_eq!(parsed.view_mode.as_deref(), Some("editor-only")); - assert_eq!(parsed.tag_colors.unwrap().get("work").unwrap(), "blue"); - } - - #[test] - fn get_config_missing_file() { - let dir = tempfile::TempDir::new().unwrap(); - let config = get_vault_config(dir.path().to_str().unwrap()).unwrap(); - assert!(config.zoom.is_none()); - } - - #[test] - fn save_and_read_config() { - let dir = tempfile::TempDir::new().unwrap(); - let vault_path = dir.path().to_str().unwrap(); - let mut status_colors = HashMap::new(); - status_colors.insert("Active".to_string(), "green".to_string()); - let config = VaultConfig { - zoom: Some(0.9), - view_mode: None, - editor_mode: None, - tag_colors: None, - status_colors: Some(status_colors), - property_display_modes: None, - }; - save_vault_config(vault_path, config).unwrap(); - - let loaded = get_vault_config(vault_path).unwrap(); - assert_eq!(loaded.zoom, Some(0.9)); - assert_eq!( - loaded.status_colors.unwrap().get("Active").unwrap(), - "green" - ); - } - - #[test] - fn migrate_hidden_sections_creates_type_notes_with_visible_false() { - let dir = tempfile::TempDir::new().unwrap(); - let vault_path = dir.path().to_str().unwrap(); - - // Create config with hidden_sections at vault root - std::fs::write( - dir.path().join("ui.config.md"), - "---\ntype: config\nhidden_sections:\n - Bookmark\n - Recipe\n---\n", - ) - .unwrap(); - - let count = migrate_hidden_sections_to_visible(vault_path).unwrap(); - assert_eq!(count, 2); - - // Check type notes were created - let bookmark = std::fs::read_to_string(dir.path().join("bookmark.md")).unwrap(); - assert!(bookmark.contains("visible: false")); - assert!(bookmark.contains("title: Bookmark")); - - let recipe = std::fs::read_to_string(dir.path().join("recipe.md")).unwrap(); - assert!(recipe.contains("visible: false")); - - // Config should no longer have hidden_sections - let config_content = std::fs::read_to_string(dir.path().join("ui.config.md")).unwrap(); - assert!(!config_content.contains("hidden_sections")); - } - - #[test] - fn migrate_hidden_sections_updates_existing_type_note() { - let dir = tempfile::TempDir::new().unwrap(); - let vault_path = dir.path().to_str().unwrap(); - - // Create config with hidden_sections at vault root - std::fs::write( - dir.path().join("ui.config.md"), - "---\ntype: config\nhidden_sections:\n - Project\n---\n", - ) - .unwrap(); - - // Create existing type note at vault root without visible - std::fs::write( - dir.path().join("project.md"), - "---\ntype: Type\ntitle: Project\nicon: briefcase\n---\n\n# Project\n", - ) - .unwrap(); - - let count = migrate_hidden_sections_to_visible(vault_path).unwrap(); - assert_eq!(count, 1); - - let content = std::fs::read_to_string(dir.path().join("project.md")).unwrap(); - assert!(content.contains("visible: false")); - assert!( - content.contains("icon: briefcase"), - "should preserve existing fields" - ); - } - - #[test] - fn migrate_hidden_sections_skips_when_no_hidden_sections() { - let dir = tempfile::TempDir::new().unwrap(); - let vault_path = dir.path().to_str().unwrap(); - - std::fs::write( - dir.path().join("ui.config.md"), - "---\ntype: config\nzoom: 1.0\n---\n", - ) - .unwrap(); - - let count = migrate_hidden_sections_to_visible(vault_path).unwrap(); - assert_eq!(count, 0); - } - - #[test] - fn migrate_hidden_sections_skips_when_no_config_file() { - let dir = tempfile::TempDir::new().unwrap(); - let count = migrate_hidden_sections_to_visible(dir.path().to_str().unwrap()).unwrap(); - assert_eq!(count, 0); - } - - #[test] - fn migrate_hidden_sections_does_not_duplicate_visible() { - let dir = tempfile::TempDir::new().unwrap(); - let vault_path = dir.path().to_str().unwrap(); - - std::fs::write( - dir.path().join("ui.config.md"), - "---\ntype: config\nhidden_sections:\n - Note\n---\n", - ) - .unwrap(); - - // Type note already has visible: false at vault root - std::fs::write( - dir.path().join("note.md"), - "---\ntype: Type\ntitle: Note\nvisible: false\n---\n", - ) - .unwrap(); - - let count = migrate_hidden_sections_to_visible(vault_path).unwrap(); - assert_eq!(count, 1); - - let content = std::fs::read_to_string(dir.path().join("note.md")).unwrap(); - // Should have exactly one visible: false, not two - assert_eq!(content.matches("visible:").count(), 1); - } - - #[test] - fn type_name_to_slug_converts_names() { - assert_eq!(type_name_to_slug("Project"), "project"); - assert_eq!(type_name_to_slug("Weekly Review"), "weekly-review"); - assert_eq!(type_name_to_slug("My Note!"), "my-note"); - } - - #[test] - fn yaml_safe_key_quoting() { - assert_eq!(yaml_safe_key("simple"), "simple"); - assert_eq!(yaml_safe_key("has space"), "\"has space\""); - assert_eq!(yaml_safe_key("has:colon"), "\"has:colon\""); - } - - #[test] - fn migrate_ui_config_moves_legacy_to_root() { - let dir = tempfile::TempDir::new().unwrap(); - let vault_path = dir.path().to_str().unwrap(); - - let config_dir = dir.path().join("config"); - std::fs::create_dir_all(&config_dir).unwrap(); - std::fs::write( - config_dir.join("ui.config.md"), - "---\ntype: config\nzoom: 1.5\n---\n", - ) - .unwrap(); - - migrate_ui_config_to_root(vault_path); - - // Root file should exist with migrated content - let content = std::fs::read_to_string(dir.path().join("ui.config.md")).unwrap(); - assert!(content.contains("zoom: 1.5")); - // Legacy file and empty config dir should be gone - assert!(!config_dir.join("ui.config.md").exists()); - assert!(!config_dir.exists()); - } - - #[test] - fn migrate_ui_config_preserves_existing_root() { - let dir = tempfile::TempDir::new().unwrap(); - let vault_path = dir.path().to_str().unwrap(); - - // Root already has content - std::fs::write( - dir.path().join("ui.config.md"), - "---\ntype: config\nzoom: 2.0\n---\n", - ) - .unwrap(); - - // Legacy also exists - let config_dir = dir.path().join("config"); - std::fs::create_dir_all(&config_dir).unwrap(); - std::fs::write( - config_dir.join("ui.config.md"), - "---\ntype: config\nzoom: 1.0\n---\n", - ) - .unwrap(); - - migrate_ui_config_to_root(vault_path); - - // Root should keep its original content - let content = std::fs::read_to_string(dir.path().join("ui.config.md")).unwrap(); - assert!(content.contains("zoom: 2.0")); - // Legacy file should be removed - assert!(!config_dir.join("ui.config.md").exists()); - } - - #[test] - fn migrate_ui_config_keeps_nonempty_config_dir() { - let dir = tempfile::TempDir::new().unwrap(); - let vault_path = dir.path().to_str().unwrap(); - - let config_dir = dir.path().join("config"); - std::fs::create_dir_all(&config_dir).unwrap(); - std::fs::write(config_dir.join("ui.config.md"), "---\ntype: config\n---\n").unwrap(); - std::fs::write(config_dir.join("other.md"), "Other file").unwrap(); - - migrate_ui_config_to_root(vault_path); - - // config/ should still exist because it has other files - assert!(config_dir.exists()); - assert!(config_dir.join("other.md").exists()); - assert!(!config_dir.join("ui.config.md").exists()); - } - - #[test] - fn save_config_writes_to_vault_root() { - let dir = tempfile::TempDir::new().unwrap(); - let vault_path = dir.path().to_str().unwrap(); - let config = VaultConfig { - zoom: Some(1.0), - ..Default::default() - }; - save_vault_config(vault_path, config).unwrap(); - - // File should be at vault root, not in config/ - assert!(dir.path().join("ui.config.md").exists()); - assert!(!dir.path().join("config").exists()); - } -} diff --git a/src/hooks/useVaultConfig.ts b/src/hooks/useVaultConfig.ts index 6325aa76..b1517e7c 100644 --- a/src/hooks/useVaultConfig.ts +++ b/src/hooks/useVaultConfig.ts @@ -1,6 +1,4 @@ import { useEffect, useCallback, useSyncExternalStore } from 'react' -import { invoke } from '@tauri-apps/api/core' -import { isTauri, mockInvoke } from '../mock-tauri' import type { VaultConfig } from '../types' import { initStatusColors } from '../utils/statusStyles' import { initTagColors } from '../utils/tagStyles' @@ -14,8 +12,32 @@ import { } from '../utils/vaultConfigStore' import { migrateLocalStorageToVaultConfig } from '../utils/configMigration' -function tauriCall(command: string, tauriArgs: Record, mockArgs?: Record): Promise { - return isTauri() ? invoke(command, tauriArgs) : mockInvoke(command, mockArgs ?? tauriArgs) +const STORAGE_PREFIX = 'laputa:vault-config:' + +function storageKey(vaultPath: string): string { + return `${STORAGE_PREFIX}${vaultPath}` +} + +function loadFromStorage(vaultPath: string): VaultConfig { + const DEFAULT: VaultConfig = { + zoom: null, view_mode: null, editor_mode: null, + tag_colors: null, status_colors: null, property_display_modes: null, + } + try { + const raw = localStorage.getItem(storageKey(vaultPath)) + if (!raw) return DEFAULT + return { ...DEFAULT, ...JSON.parse(raw) } + } catch { + return DEFAULT + } +} + +function saveToStorage(vaultPath: string, config: VaultConfig): void { + try { + localStorage.setItem(storageKey(vaultPath), JSON.stringify(config)) + } catch (err) { + console.warn('Failed to save vault config:', err) + } } function applyToModules(c: VaultConfig): void { @@ -24,34 +46,18 @@ function applyToModules(c: VaultConfig): void { initDisplayModeOverrides(c.property_display_modes ?? {}) } -function persistConfig(vaultPath: string, config: VaultConfig): void { - tauriCall('save_vault_config', { vaultPath, config }) - .catch((err) => console.warn('Failed to save vault config:', err)) -} - export function useVaultConfig(vaultPath: string) { const config = useSyncExternalStore(subscribeVaultConfig, getVaultConfig) useEffect(() => { resetVaultConfigStore() - tauriCall('get_vault_config', { vaultPath }) - .then((loaded) => { - const migrated = migrateLocalStorageToVaultConfig(loaded) - const needsSave = migrated !== loaded - bindVaultConfigStore(migrated, (c) => persistConfig(vaultPath, c)) - applyToModules(migrated) - if (needsSave) persistConfig(vaultPath, migrated) - }) - .catch((err) => { - console.warn('Failed to load vault config:', err) - const migrated = migrateLocalStorageToVaultConfig(null) - bindVaultConfigStore(migrated, (c) => persistConfig(vaultPath, c)) - applyToModules(migrated) - if (migrated.zoom !== null || migrated.tag_colors !== null || migrated.status_colors !== null) { - persistConfig(vaultPath, migrated) - } - }) + const loaded = loadFromStorage(vaultPath) + const migrated = migrateLocalStorageToVaultConfig(loaded) + const needsSave = migrated !== loaded + bindVaultConfigStore(migrated, (c) => saveToStorage(vaultPath, c)) + applyToModules(migrated) + if (needsSave) saveToStorage(vaultPath, migrated) return () => resetVaultConfigStore() }, [vaultPath]) diff --git a/src/mock-tauri/mock-handlers.ts b/src/mock-tauri/mock-handlers.ts index 0909386b..0502de7d 100644 --- a/src/mock-tauri/mock-handlers.ts +++ b/src/mock-tauri/mock-handlers.ts @@ -3,7 +3,7 @@ * Each handler simulates a Tauri backend command. */ -import type { VaultEntry, VaultConfig, ModifiedFile, Settings, DeviceFlowStart, DeviceFlowPollResult, GitHubUser, GitPullResult, GitPushResult, GitRemoteStatus, LastCommitInfo, PulseCommit } from '../types' +import type { VaultEntry, ModifiedFile, Settings, DeviceFlowStart, DeviceFlowPollResult, GitHubUser, GitPullResult, GitPushResult, GitRemoteStatus, LastCommitInfo, PulseCommit } from '../types' import { MOCK_CONTENT } from './mock-content' import { MOCK_ENTRIES } from './mock-entries' @@ -269,8 +269,6 @@ export const mockHandlers: Record any> = { start_indexing: () => null, trigger_incremental_index: () => null, repair_vault: (): string => 'Vault repaired', - get_vault_config: (): VaultConfig => ({ zoom: null, view_mode: null, editor_mode: null, tag_colors: null, status_colors: null, property_display_modes: null }), - save_vault_config: (): null => null, } export function addMockEntry(_entry: VaultEntry, content: string): void {