From 92b32282d9d3d05030e5e17e939869f9eedd6477 Mon Sep 17 00:00:00 2001 From: Jake Date: Fri, 24 Apr 2026 20:13:28 +0100 Subject: [PATCH] feat(gamification): flag cascade-completed parents as auto_completed complete_subtask_and_check_parent now sets auto_completed = 1 on the parent when it closes via the cascade. The subtask UPDATE itself remains at the default 0, so explicit user taps still count toward the Phase 8 daily total. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/storage/src/database.rs | 71 +++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/crates/storage/src/database.rs b/crates/storage/src/database.rs index 25d0c66..eff5f14 100644 --- a/crates/storage/src/database.rs +++ b/crates/storage/src/database.rs @@ -451,11 +451,17 @@ pub async fn complete_subtask_and_check_parent(pool: &SqlitePool, subtask_id: &s })?; if pending == 0 { - sqlx::query("UPDATE tasks SET done = 1, done_at = datetime('now') WHERE id = ?") - .bind(&pid) - .execute(&mut *tx) - .await - .map_err(|e| KonError::StorageError(format!("Auto-complete parent failed: {e}")))?; + // Phase 8: flag the cascade so the daily-count query can exclude + // it. The subtask UPDATE (above) stays at auto_completed = 0 — the + // user explicitly ticked it, so it counts. + sqlx::query( + "UPDATE tasks SET done = 1, done_at = datetime('now'), auto_completed = 1 \ + WHERE id = ?", + ) + .bind(&pid) + .execute(&mut *tx) + .await + .map_err(|e| KonError::StorageError(format!("Auto-complete parent failed: {e}")))?; } } @@ -2016,4 +2022,59 @@ mod tests { let missing = get_setting(&pool, "nonexistent").await.unwrap(); assert!(missing.is_none()); } + + #[tokio::test] + async fn cascade_sets_auto_completed_on_parent_only() { + let pool = test_pool().await; + + // Parent + two subtasks. + insert_task( + &pool, + "parent", + "Parent task", + "inbox", + None, + None, + None, + None, + ) + .await + .unwrap(); + insert_subtask(&pool, "s1", "Step 1", "parent") + .await + .unwrap(); + insert_subtask(&pool, "s2", "Step 2", "parent") + .await + .unwrap(); + + complete_subtask_and_check_parent(&pool, "s1") + .await + .unwrap(); + complete_subtask_and_check_parent(&pool, "s2") + .await + .unwrap(); + + // Parent auto-closed. + let parent_auto: i64 = + sqlx::query_scalar("SELECT auto_completed FROM tasks WHERE id = 'parent'") + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!( + parent_auto, 1, + "parent closed by cascade must be auto_completed = 1" + ); + + // Subtasks themselves are manual. + let s1_auto: i64 = sqlx::query_scalar("SELECT auto_completed FROM tasks WHERE id = 's1'") + .fetch_one(&pool) + .await + .unwrap(); + let s2_auto: i64 = sqlx::query_scalar("SELECT auto_completed FROM tasks WHERE id = 's2'") + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(s1_auto, 0, "subtask completion is manual"); + assert_eq!(s2_auto, 0, "subtask completion is manual"); + } }