fix: improve error handling across Rust and TypeScript
- Log swallowed errors in file watcher and window operations (lib.rs, watcher.rs) - Propagate recovery errors from bridge::recover_pending to SyncResult.errors so the frontend can display them instead of silently dropping failures - Fix useTauriEvent/useTauriEvents race condition where cleanup fires before async listen() resolves, leaking the listener (cancelled flag pattern) - Guard computeStaleness against invalid date strings (NaN -> 'normal' instead of incorrectly returning 'urgent') - Strengthen isMcError type guard to check field types, not just presence - Log warning when data directory resolution falls back to '.' (state.rs, bridge.rs) - Add test for computeStaleness with invalid date inputs
This commit is contained in:
@@ -157,9 +157,14 @@ fn sync_now_inner(
|
||||
let mut map = bridge.load_map()?;
|
||||
|
||||
// Recover any pending entries from a previous crash
|
||||
bridge.recover_pending(&mut map)?;
|
||||
let (_recovered, recovery_errors) = bridge.recover_pending(&mut map)?;
|
||||
|
||||
let result = bridge.incremental_sync(&mut map)?;
|
||||
let mut result = bridge.incremental_sync(&mut map)?;
|
||||
|
||||
// Surface recovery errors alongside sync errors
|
||||
if !recovery_errors.is_empty() {
|
||||
result.errors.extend(recovery_errors);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -182,9 +187,14 @@ fn reconcile_inner(
|
||||
let mut map = bridge.load_map()?;
|
||||
|
||||
// Recover pending first
|
||||
bridge.recover_pending(&mut map)?;
|
||||
let (_recovered, recovery_errors) = bridge.recover_pending(&mut map)?;
|
||||
|
||||
let result = bridge.full_reconciliation(&mut map)?;
|
||||
let mut result = bridge.full_reconciliation(&mut map)?;
|
||||
|
||||
// Surface recovery errors alongside reconciliation errors
|
||||
if !recovery_errors.is_empty() {
|
||||
result.errors.extend(recovery_errors);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -152,7 +152,12 @@ impl<L: LoreCli, B: BeadsCli> Bridge<L, B> {
|
||||
/// Create a new bridge with the given CLI implementations
|
||||
pub fn new(lore: L, beads: B) -> Self {
|
||||
let data_dir = dirs::data_local_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.unwrap_or_else(|| {
|
||||
tracing::warn!(
|
||||
"Could not determine local data directory ($HOME may be unset), falling back to '.'"
|
||||
);
|
||||
PathBuf::from(".")
|
||||
})
|
||||
.join("mc");
|
||||
|
||||
Self {
|
||||
@@ -331,7 +336,9 @@ impl<L: LoreCli, B: BeadsCli> Bridge<L, B> {
|
||||
/// On startup, scan for entries with pending=true:
|
||||
/// - If bead_id is None -> retry creation
|
||||
/// - If bead_id exists -> clear pending flag
|
||||
pub fn recover_pending(&self, map: &mut GitLabBeadMap) -> Result<usize, BridgeError> {
|
||||
///
|
||||
/// Returns (recovered_count, error_messages) so callers can surface failures.
|
||||
pub fn recover_pending(&self, map: &mut GitLabBeadMap) -> Result<(usize, Vec<String>), BridgeError> {
|
||||
let pending_keys: Vec<String> = map
|
||||
.mappings
|
||||
.iter()
|
||||
@@ -340,6 +347,7 @@ impl<L: LoreCli, B: BeadsCli> Bridge<L, B> {
|
||||
.collect();
|
||||
|
||||
let mut recovered = 0;
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for key in &pending_keys {
|
||||
if let Some(entry) = map.mappings.get_mut(key) {
|
||||
@@ -356,6 +364,7 @@ impl<L: LoreCli, B: BeadsCli> Bridge<L, B> {
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to recover bead for {}: {}", key, e);
|
||||
errors.push(format!("Failed to recover pending bead for {}: {}", key, e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -369,7 +378,7 @@ impl<L: LoreCli, B: BeadsCli> Bridge<L, B> {
|
||||
self.save_map(map)?;
|
||||
}
|
||||
|
||||
Ok(recovered)
|
||||
Ok((recovered, errors))
|
||||
}
|
||||
|
||||
/// Incremental sync: process `since_last_check` events from lore.
|
||||
@@ -904,9 +913,10 @@ mod tests {
|
||||
);
|
||||
|
||||
bridge.save_map(&map).unwrap();
|
||||
let recovered = bridge.recover_pending(&mut map).unwrap();
|
||||
let (recovered, errors) = bridge.recover_pending(&mut map).unwrap();
|
||||
|
||||
assert_eq!(recovered, 1);
|
||||
assert!(errors.is_empty());
|
||||
let entry = &map.mappings["issue:g/p:42"];
|
||||
assert_eq!(entry.bead_id, Some("bd-recovered".to_string()));
|
||||
assert!(!entry.pending);
|
||||
@@ -931,9 +941,10 @@ mod tests {
|
||||
);
|
||||
|
||||
bridge.save_map(&map).unwrap();
|
||||
let recovered = bridge.recover_pending(&mut map).unwrap();
|
||||
let (recovered, errors) = bridge.recover_pending(&mut map).unwrap();
|
||||
|
||||
assert_eq!(recovered, 1);
|
||||
assert!(errors.is_empty());
|
||||
assert!(!map.mappings["issue:g/p:42"].pending);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,12 @@ use std::path::PathBuf;
|
||||
/// Get the Mission Control data directory
|
||||
pub fn mc_data_dir() -> PathBuf {
|
||||
dirs::data_local_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.unwrap_or_else(|| {
|
||||
tracing::warn!(
|
||||
"Could not determine local data directory ($HOME may be unset), falling back to '.'"
|
||||
);
|
||||
PathBuf::from(".")
|
||||
})
|
||||
.join("mc")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user