feat: add Tauri state persistence and BvCli trait

- Add Tauri storage adapter for Zustand (tauri-storage.ts)
- Add read_state, write_state, clear_state Tauri commands
- Wire focus-store and nav-store to use Tauri persistence
- Add BvCli trait for bv CLI mocking with response types
- Add BvError and McError conversion for bv errors
- Add cleanup_tmp_files tests for bridge
- Fix linter-introduced tauri_specta::command issues

Closes bd-2x6, bd-gil, bd-3px

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
teernisse
2026-02-26 10:05:53 -05:00
parent 443db24fb3
commit 087b588d71
14 changed files with 877 additions and 20 deletions

View File

@@ -4,12 +4,13 @@
//! to handle errors programmatically rather than parsing strings.
use serde::Serialize;
use specta::Type;
/// Structured error type for Tauri IPC commands.
///
/// This replaces string-based errors (`Result<T, String>`) with typed errors
/// that the frontend can handle programmatically.
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Serialize, Type)]
pub struct McError {
/// Machine-readable error code (e.g., "LORE_UNAVAILABLE", "BRIDGE_LOCKED")
pub code: McErrorCode,
@@ -20,7 +21,7 @@ pub struct McError {
}
/// Error codes for frontend handling
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Type)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum McErrorCode {
// Lore errors
@@ -38,6 +39,10 @@ pub enum McErrorCode {
BeadsCreateFailed,
BeadsCloseFailed,
// Bv errors
BvUnavailable,
BvTriageFailed,
// General errors
IoError,
InternalError,
@@ -50,6 +55,7 @@ impl McError {
code,
McErrorCode::LoreUnavailable
| McErrorCode::BeadsUnavailable
| McErrorCode::BvUnavailable
| McErrorCode::BridgeLocked
| McErrorCode::IoError
);
@@ -97,6 +103,19 @@ impl McError {
"br CLI not found -- is beads installed?",
)
}
/// Create an IO error with context
pub fn io_error(context: impl Into<String>) -> Self {
Self::new(McErrorCode::IoError, context)
}
/// Create a bv unavailable error
pub fn bv_unavailable() -> Self {
Self::new(
McErrorCode::BvUnavailable,
"bv CLI not found -- is beads installed?",
)
}
}
impl std::fmt::Display for McError {
@@ -169,6 +188,25 @@ impl From<crate::data::beads::BeadsError> for McError {
}
}
// Conversion from bv errors
impl From<crate::data::bv::BvError> for McError {
fn from(err: crate::data::bv::BvError) -> Self {
use crate::data::bv::BvError;
match err {
BvError::ExecutionFailed(_) => Self::bv_unavailable(),
BvError::CommandFailed(msg) => Self::new(
McErrorCode::BvTriageFailed,
format!("bv command failed: {}", msg),
),
BvError::ParseFailed(msg) => Self::new(
McErrorCode::BvTriageFailed,
format!("Failed to parse bv response: {}", msg),
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -261,4 +299,24 @@ mod tests {
assert_eq!(err.code, McErrorCode::BridgeMapCorrupted);
assert!(!err.recoverable);
}
#[test]
fn test_bv_error_conversion() {
use crate::data::bv::BvError;
// ExecutionFailed -> BvUnavailable (recoverable)
let err: McError = BvError::ExecutionFailed("not found".to_string()).into();
assert_eq!(err.code, McErrorCode::BvUnavailable);
assert!(err.recoverable);
// CommandFailed -> BvTriageFailed (not recoverable)
let err: McError = BvError::CommandFailed("failed".to_string()).into();
assert_eq!(err.code, McErrorCode::BvTriageFailed);
assert!(!err.recoverable);
// ParseFailed -> BvTriageFailed (not recoverable)
let err: McError = BvError::ParseFailed("bad json".to_string()).into();
assert_eq!(err.code, McErrorCode::BvTriageFailed);
assert!(!err.recoverable);
}
}