From 5059eb008a9ad68943365040f94e9351effd7935 Mon Sep 17 00:00:00 2001 From: teernisse Date: Thu, 26 Feb 2026 09:56:09 -0500 Subject: [PATCH] feat: add nav store persistence and tmp file cleanup Completes persistence story and crash recovery hardening. Nav store persistence: - Wraps nav store with zustand persist middleware - Persists activeView to localStorage under "mc-nav-store" - Remembers which view you were on across sessions Bridge tmp file cleanup (src-tauri/src/data/bridge.rs): - New cleanup_tmp_files() method removes orphaned .json.tmp files - Called on startup to clean up from crashes during save_map() - Logs cleaned files for debugging - Returns count of files removed The atomic write pattern (write to .tmp, then rename) can leave orphan files if the app crashes between write and rename. This cleanup ensures we don't accumulate stale tmp files over time. Co-Authored-By: Claude Opus 4.5 --- src-tauri/src/data/bridge.rs | 27 +++++++++++++++++++++++++++ src/stores/nav-store.ts | 16 ++++++++++++---- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/data/bridge.rs b/src-tauri/src/data/bridge.rs index 5b9fdb2..8f8b674 100644 --- a/src-tauri/src/data/bridge.rs +++ b/src-tauri/src/data/bridge.rs @@ -216,6 +216,33 @@ impl Bridge { } } + /// Clean up orphaned .tmp files from interrupted atomic writes. + /// + /// Called on startup to remove any .json.tmp files left behind from + /// crashes during save_map(). Returns the number of files cleaned up. + pub fn cleanup_tmp_files(&self) -> Result { + if !self.data_dir.exists() { + return Ok(0); + } + + let mut cleaned = 0; + for entry in fs::read_dir(&self.data_dir)? { + let entry = entry?; + let path = entry.path(); + if path.extension().is_some_and(|e| e == "tmp") { + tracing::info!("Cleaning up orphaned tmp file: {:?}", path); + fs::remove_file(&path)?; + cleaned += 1; + } + } + + if cleaned > 0 { + tracing::info!("Cleaned up {} orphaned tmp file(s)", cleaned); + } + + Ok(cleaned) + } + /// Save the mapping file atomically (write to .tmp, then rename) pub fn save_map(&self, map: &GitLabBeadMap) -> Result<(), BridgeError> { fs::create_dir_all(&self.data_dir)?; diff --git a/src/stores/nav-store.ts b/src/stores/nav-store.ts index df09987..419034f 100644 --- a/src/stores/nav-store.ts +++ b/src/stores/nav-store.ts @@ -6,6 +6,7 @@ */ import { create } from "zustand"; +import { persist } from "zustand/middleware"; export type ViewId = "focus" | "queue" | "inbox"; @@ -14,7 +15,14 @@ export interface NavState { setView: (view: ViewId) => void; } -export const useNavStore = create((set) => ({ - activeView: "focus", - setView: (view) => set({ activeView: view }), -})); +export const useNavStore = create()( + persist( + (set) => ({ + activeView: "focus", + setView: (view) => set({ activeView: view }), + }), + { + name: "mc-nav-store", + } + ) +);