State Management
Rust AppState
The central application state is defined in tauri- and shared across all Tauri command handlers via tauri::State:
pub struct AppState {
pub file_searches: Mutex<HashMap<String, MdfindChild>>, // macOS only
pub watchers: Mutex<WatcherState>,
pub app_config_dir: PathBuf,
pub active_note_pointer_lock: Mutex<()>,
pub settings_cache: Mutex<Option<serde_json::Value>>,
pub settings_mtime: Mutex<u64>,
pub event_tx: broadcast::Sender<SseEvent>,
}Fields
file_searches— In-flight mdfind child processes keyed by search UUID (macOS only)watchers— Watchers for local development fallbacks, user skills, and files opened in External File Editor, plus mtime guards that suppress echo eventsapp_config_dir— Per-app config directory (e.g.~/.config/zudotext/<app-name>/). Fixed for the process lifetimeactive_note_pointer_lock— Serializes writes to the optional desktop active-note sidecarsettings_cache— Cached settings JSON to avoid re-reading from disk on every operationsettings_mtime— Last-known mtime (ms since UNIX epoch) of the settings file, used to invalidatesettings_cachewhen the file is modified externallyevent_tx— Broadcast sender for SSE events, used by the REST adapter to push events to browser clients
The BM25 similarity index used to live here as similarity_index, alongside an archives_list_cache. Both were deleted in S21 (#4225): the index moved to TypeScript over the workspace model, and the archives listing now comes from that same model — see File Search and Similarity.
Thread Safety and Workspace Binding
Mutable native state is wrapped in Mutex because Tauri command handlers may run on different threads. Account workspace selection and content access happen through the cloud-backed TypeScript bridge; they do not switch a local directory or restart a process-wide workspace watcher.
WatcherState
The WatcherState struct holds file watcher instances and self-write guards. All watcher handles are Option<notify::RecommendedWatcher> (not available on iOS — replaced by () placeholders so the rest of AppState still compiles):
pub struct WatcherState {
pub messages_watcher: Option<notify::RecommendedWatcher>,
pub messages_write_mtime: Arc<Mutex<HashMap<String, u64>>>,
pub active_draft_watcher: Option<notify::RecommendedWatcher>,
pub active_draft_watched_path: Option<String>,
pub draft_write_mtime: Arc<Mutex<u64>>,
pub notes_watchers: HashMap<String, NotesWatcherEntry>,
pub skills_watchers: HashMap<String, notify::RecommendedWatcher>,
pub external_file_watchers: HashMap<String, ExternalWatcherEntry>,
}The remaining local file watchers use mtime-based self-write detection: after the app writes a watched file it records the mtime, and the watcher compares against it to suppress self-triggered events. Message, active-draft, and Note Tray watchers support the development REST fallback; workspace changes in production arrive through the cloud bridge.
Frontend State
Settings Context
A React Context (renderer/) provides app settings to all components:
const appSettings = useSettings();
// Access: appSettings.editor.vimMode, appSettings.layout.sidebarPosition, etc.Settings are loaded from the backend on mount and passed through validateSettings() to enforce range checks and fill missing defaults. A useSettingsRefresh() hook allows any component to trigger a re-read from the backend.
Sync Context
The SyncContext (renderer/) manages cloud synchronization state:
const {
cloudStatus, // CloudSyncStatusInfo — current cloud sync status
syncNow, // () => Promise<void> — trigger manual sync
isConfigured, // boolean — live workspace binding (authenticated + workspace id + encryption armed)
isAuthenticated, // boolean — user is authenticated
authState, // AuthState — current auth state (user info)
isSyncing, // boolean — sync in progress
syncLog, // SyncLogEntry[] — recent sync history (max 10 entries)
canSync, // boolean — subscription active/trial AND configured
} = useSyncContext();The SyncProvider wraps the app and:
Listens for cloud sync status changes via
getBackend().cloudSync.onStatusChanged()Listens for auth state changes via
getBackend().auth.onStateChanged()Triggers sync via
getBackend().cloudSync.triggerSync()Maintains a rolling sync log (last 10 entries) with timestamps and error info
Pending Renames Context
The PendingRenamesContext (renderer/) tracks files currently being AI-summarized and renamed:
const { pendingFilenames, addPending, removePending } = usePendingRenames();When a message is archived, the filename is added to pendingFilenames while the heuristic rename derives a title. This prevents the messages list from briefly showing a "missing" file during the rename operation. Once the rename completes, the filename is removed.
Local UI State
Transient UI state is managed with React's useState and useRef:
Layout: active frame and frameset layout state
Command palette:
commandPaletteOpen,paletteModeShortcuts:
shortcuts(loaded from settings, updated when dialog closes)Chord indicator:
chordState(from the shortcut engine)
Settings Persistence Flow
User changes setting in dialog
→ SettingsDialog calls getBackend().settings.save(newSettings)
→ active adapter persists the synced settings document
→ Dialog closes
→ AppContent re-reads settings from backend
→ UI updates to reflect new values