From 1bc65bd774dc00c4db95fa2065ebe1374406aae3 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Wed, 15 Apr 2026 15:43:56 +1000 Subject: [PATCH 1/2] feat(staged): adopt and resume interrupted autoreviews Interrupted autoreviews (cancelled for new commits, app quit, or crashed) were either deleted or adopted incomplete with no way to resume them. - Stop deleting review records when cancelling in-flight autoreviews so they survive for potential adoption - Remove frontend deleteReview call from cancelAutoReview() - Resume incomplete autoreviews (completedAt is null) during adoption by calling resumeSession with the existing session - Add completedAt field to the shared Review TypeScript interface Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/staged/src-tauri/src/session_commands.rs | 19 +++++++++---------- .../BranchCardSessionManager.svelte.ts | 16 +++++++++++++--- packages/diff-viewer/src/lib/types.ts | 2 ++ 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/staged/src-tauri/src/session_commands.rs b/apps/staged/src-tauri/src/session_commands.rs index 8aa324a0b..8ab6b3c4d 100644 --- a/apps/staged/src-tauri/src/session_commands.rs +++ b/apps/staged/src-tauri/src/session_commands.rs @@ -1466,15 +1466,10 @@ fn cancel_in_flight_auto_review_for_branch( if !cancelled { let current = store.get_session(session_id).map_err(|e| e.to_string())?; return match current.map(|session| session.status) { - None => Ok(true), - Some(store::SessionStatus::Cancelled) => { - store.delete_review(&review.id).map_err(|e| e.to_string())?; - Ok(true) - } + None | Some(store::SessionStatus::Cancelled) => Ok(true), _ => Ok(false), }; } - store.delete_review(&review.id).map_err(|e| e.to_string())?; Ok(true) } @@ -2680,8 +2675,10 @@ mod tests { cancel_in_flight_auto_review_for_branch(&store, ®istry, &branch.id).unwrap(); assert!(cancelled); - assert!(store.get_session(&session.id).unwrap().is_none()); - assert!(store.get_review(&review.id).unwrap().is_none()); + // Session transitions to Cancelled but both records survive for potential adoption + let session = store.get_session(&session.id).unwrap().unwrap(); + assert_eq!(session.status, store::SessionStatus::Cancelled); + assert!(store.get_review(&review.id).unwrap().is_some()); } #[test] @@ -2695,8 +2692,10 @@ mod tests { cancel_in_flight_auto_review_for_branch(&store, ®istry, &branch.id).unwrap(); assert!(cancelled); - assert!(store.get_session(&session.id).unwrap().is_none()); - assert!(store.get_review(&review.id).unwrap().is_none()); + // Session transitions to Cancelled but both records survive for potential adoption + let session = store.get_session(&session.id).unwrap().unwrap(); + assert_eq!(session.status, store::SessionStatus::Cancelled); + assert!(store.get_review(&review.id).unwrap().is_some()); } #[test] diff --git a/apps/staged/src/lib/features/branches/BranchCardSessionManager.svelte.ts b/apps/staged/src/lib/features/branches/BranchCardSessionManager.svelte.ts index 11b7ec322..eb62b2803 100644 --- a/apps/staged/src/lib/features/branches/BranchCardSessionManager.svelte.ts +++ b/apps/staged/src/lib/features/branches/BranchCardSessionManager.svelte.ts @@ -170,9 +170,6 @@ export default class BranchCardSessionManager { if (this.autoReviewSessionId) { commands.cancelSession(this.autoReviewSessionId).catch(() => {}); } - if (this.autoReviewId) { - commands.deleteReview(this.autoReviewId).catch(() => {}); - } this.autoReviewSessionId = null; this.autoReviewId = null; } @@ -193,6 +190,19 @@ export default class BranchCardSessionManager { projectStateStore.addRunningSession(branch.projectId, this.autoReviewSessionId); } + // If the autoreview was interrupted before completing, resume it + const needsResume = !review.completedAt && review.sessionId && !this.autoReviewSessionId; + if (needsResume) { + await commands.resumeSession( + review.sessionId!, + 'Continue reviewing the code changes on this branch.', + undefined, + branch.id + ); + sessionRegistry.register(review.sessionId!, branch.projectId, 'review', branch.id); + projectStateStore.addRunningSession(branch.projectId, review.sessionId!); + } + this.autoReviewSessionId = null; this.autoReviewId = null; diff --git a/packages/diff-viewer/src/lib/types.ts b/packages/diff-viewer/src/lib/types.ts index adfec950b..555609951 100644 --- a/packages/diff-viewer/src/lib/types.ts +++ b/packages/diff-viewer/src/lib/types.ts @@ -96,6 +96,8 @@ export interface Review { referenceFiles: string[]; createdAt: number; updatedAt: number; + /** When the AI session finished producing this review. `null` while running. */ + completedAt: number | null; } // ============================================================================= From b99b488c398b3643e70ccd8e750a5481076bed0c Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Wed, 15 Apr 2026 16:35:30 +1000 Subject: [PATCH 2/2] fix(staged): restore review deletion on normal autoreview cancellation Only orphaned reviews (from crashes/app quit) should survive for resume. When explicitly cancelling an autoreview to start a new session, delete the review record as before to avoid accumulating cancelled reviews. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../lib/features/branches/BranchCardSessionManager.svelte.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/staged/src/lib/features/branches/BranchCardSessionManager.svelte.ts b/apps/staged/src/lib/features/branches/BranchCardSessionManager.svelte.ts index eb62b2803..fa4ffdbc2 100644 --- a/apps/staged/src/lib/features/branches/BranchCardSessionManager.svelte.ts +++ b/apps/staged/src/lib/features/branches/BranchCardSessionManager.svelte.ts @@ -170,6 +170,9 @@ export default class BranchCardSessionManager { if (this.autoReviewSessionId) { commands.cancelSession(this.autoReviewSessionId).catch(() => {}); } + if (this.autoReviewId) { + commands.deleteReview(this.autoReviewId).catch(() => {}); + } this.autoReviewSessionId = null; this.autoReviewId = null; }