diff --git a/apps/staged/src/lib/features/branches/BranchCard.svelte b/apps/staged/src/lib/features/branches/BranchCard.svelte index d3c5dc81e..90c756a2f 100644 --- a/apps/staged/src/lib/features/branches/BranchCard.svelte +++ b/apps/staged/src/lib/features/branches/BranchCard.svelte @@ -995,6 +995,20 @@ deletingItems={timelineDeletingItems} reviewCommentBreakdown={timelineReviewDetailsById} onSessionClick={(sid) => sessionMgr.handleTimelineSessionClick(sid)} + onResumeClick={(sid) => { + commands + .resumeSession(sid, 'Continue where you left off.', undefined, branch.id) + .then(() => loadTimeline()) + .catch((e) => { + console.error('Failed to resume session:', e); + alerts.error( + e instanceof Error + ? e.message + : 'Could not resume the session. Please try again.', + 'Resume failed' + ); + }); + }} onCommitClick={handleCommitClick} onNoteClick={handleNoteClick} onReviewClick={handleReviewClick} diff --git a/apps/staged/src/lib/features/sessions/SessionModal.svelte b/apps/staged/src/lib/features/sessions/SessionModal.svelte index 8e376b9d2..23d2d0bde 100644 --- a/apps/staged/src/lib/features/sessions/SessionModal.svelte +++ b/apps/staged/src/lib/features/sessions/SessionModal.svelte @@ -45,6 +45,7 @@ import Spinner from '../../shared/Spinner.svelte'; import { marked } from 'marked'; import { sanitize } from '../../shared/sanitize'; + import { isResumableReason } from '../../types'; import type { Session, SessionMessage, HashtagItem } from '../../types'; import { cancelSession, @@ -1215,7 +1216,7 @@ {session.errorMessage} {:else if session && session.status !== 'running' && session.status !== 'queued'} - {#if session.completionReason === 'crashed' || session.completionReason === 'app_quit' || session.completionReason === 'interrupted'} + {#if isResumableReason(session.completionReason)}
void; + onResumeClick?: (sessionId: string) => void; onCommitClick?: (sha: string) => void; onNoteClick?: (noteId: string, title: string, content: string, sessionId?: string) => void; onReviewClick?: (reviewId: string) => void; @@ -95,6 +97,7 @@ prunedSessionIds = new Set(), deletingItems = [], onSessionClick, + onResumeClick, onCommitClick, onNoteClick, onReviewClick, @@ -199,6 +202,7 @@ badges?: TimelineBadge[]; /** When set, delete button is shown but disabled with this tooltip. */ deleteDisabledReason?: string; + completionReason?: string | null; }; let runningSessionIds = $derived.by(() => collectRunningSessionIds(timeline, pendingItems)); @@ -285,6 +289,7 @@ commitSha: commit.sha || undefined, commitId: commit.id ?? undefined, deleteDisabledReason: isDeleting ? 'Deleting...' : undefined, + completionReason: commit.completionReason, }); } @@ -326,6 +331,7 @@ noteTitle: stripXmlTags(note.title), noteContent: note.content, deleteDisabledReason: isDeleting ? 'Deleting...' : undefined, + completionReason: note.completionReason, }); } @@ -388,6 +394,7 @@ sessionId: review.sessionId ?? undefined, reviewId: review.id, deleteDisabledReason: isDeleting ? 'Deleting...' : undefined, + completionReason: review.completionReason, }); } @@ -488,6 +495,10 @@ } } + function isResumable(item: DisplayItem): boolean { + return !!item.sessionId && isResumableReason(item.completionReason) && !item.deleting; + } + function handleDeleteClick(item: DisplayItem, opts?: { altKey: boolean }) { if (item.type === 'commit' && item.commitSha && onDeleteCommit) { onDeleteCommit(item.commitSha, item.sessionId, opts); @@ -555,6 +566,9 @@ onStartClick={item.type.startsWith('queued-') && !hasActiveSession ? onStartQueued : undefined} + onResumeClick={isResumable(item) && onResumeClick && item.sessionId && !hasActiveSession + ? () => onResumeClick!(item.sessionId!) + : undefined} />
{/each} diff --git a/apps/staged/src/lib/features/timeline/TimelineRow.svelte b/apps/staged/src/lib/features/timeline/TimelineRow.svelte index 2f825ea90..7a3dfbef2 100644 --- a/apps/staged/src/lib/features/timeline/TimelineRow.svelte +++ b/apps/staged/src/lib/features/timeline/TimelineRow.svelte @@ -58,6 +58,7 @@ deleteDisabledReason?: string; onRetryClick?: () => void; onStartClick?: () => void; + onResumeClick?: () => void; } let { @@ -76,6 +77,7 @@ deleteDisabledReason, onRetryClick, onStartClick, + onResumeClick, }: Props = $props(); let isNote = $derived( @@ -140,6 +142,11 @@ e.stopPropagation(); onStartClick?.(); } + + function handleResumeClick(e: MouseEvent) { + e.stopPropagation(); + onResumeClick?.(); + } @@ -217,7 +224,10 @@ {/if} -
+
{#if onStartClick} {/if} + {#if onResumeClick} + + {/if} {#if hasSession && !onStartClick && !isQueued}