From f276233be57723d3fae521f33db147e5fbd2e392 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Apr 2026 16:19:46 +0000 Subject: [PATCH] fix(tauri): cascade delete must handle the full subtree, not just direct children MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit delete_task only collected direct children when a parent was deleted, so grandchildren (and deeper descendants — the data model allows any depth even though the UI is two-level today) would be left with a parent_id pointing at a deleted task. Walk the parent-child graph to collect the full descendant set and delete children before the parent so a mid-cascade failure can't strand descendants. --- apps/tauri/src-tauri/src/lib.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index 875a3e2..937afc4 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -420,14 +420,23 @@ fn delete_task( let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?; let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?; let repo = repo_mut(&mut s)?; - // Cascade-delete subtasks first + // Cascade-delete the full descendant subtree (not just direct children) + // so deleting a parent can't leave grandchildren orphaned with a + // parent_id pointing at a deleted task. let all_tasks = repo.list_tasks(lid).map_err(|e| e.to_string())?; - let child_ids: Vec = all_tasks - .iter() - .filter(|t| t.parent_id == Some(tid)) - .map(|t| t.id) - .collect(); - for child_id in child_ids { + let mut to_delete: Vec = Vec::new(); + let mut frontier: Vec = vec![tid]; + while let Some(parent) = frontier.pop() { + for t in &all_tasks { + if t.parent_id == Some(parent) && !to_delete.contains(&t.id) { + to_delete.push(t.id); + frontier.push(t.id); + } + } + } + // Delete children before the parent so a mid-cascade failure doesn't + // leave the parent removed but descendants stranded. + for child_id in to_delete { repo.delete_task(lid, child_id).map_err(|e| format!("Failed to delete subtask {}: {}", child_id, e))?; } repo.delete_task(lid, tid)