From 3c5cdc9cc238189bb7557c32607f020433425a92 Mon Sep 17 00:00:00 2001 From: Dustin Hilgaertner Date: Wed, 22 Apr 2026 14:37:53 -0500 Subject: [PATCH] Add Move to Active button on completed sessions Closes #186. When a session is `completed`, the session detail view now shows a Move to Active button next to Open in VS Code that flips the status back to `active`. Mirrors the existing Mark as Completed pattern (closure on AppState, helper on SessionService, wired in AppDelegate, button gated on session.status). Co-Authored-By: Claude Opus 4.7 (1M context) --- Packages/CrowCore/Sources/CrowCore/AppState.swift | 3 +++ .../CrowUI/Sources/CrowUI/SessionDetailView.swift | 12 ++++++++++++ Sources/Crow/App/AppDelegate.swift | 3 +++ Sources/Crow/App/SessionService.swift | 4 ++++ 4 files changed, 22 insertions(+) diff --git a/Packages/CrowCore/Sources/CrowCore/AppState.swift b/Packages/CrowCore/Sources/CrowCore/AppState.swift index 93653a3..64f5b6f 100644 --- a/Packages/CrowCore/Sources/CrowCore/AppState.swift +++ b/Packages/CrowCore/Sources/CrowCore/AppState.swift @@ -190,6 +190,9 @@ public final class AppState { /// Called to update session status to .inReview (persists to store). public var onSetSessionInReview: ((UUID) -> Void)? + /// Called to update session status back to .active (persists to store). + public var onSetSessionActive: ((UUID) -> Void)? + /// Whether a given session is currently being marked as "In Review" (loading state). /// Must be cleaned up when a session is deleted (see `SessionService.deleteSession`). public var isMarkingInReview: [UUID: Bool] = [:] diff --git a/Packages/CrowUI/Sources/CrowUI/SessionDetailView.swift b/Packages/CrowUI/Sources/CrowUI/SessionDetailView.swift index 0715b68..ae4ed4e 100644 --- a/Packages/CrowUI/Sources/CrowUI/SessionDetailView.swift +++ b/Packages/CrowUI/Sources/CrowUI/SessionDetailView.swift @@ -176,6 +176,18 @@ public struct SessionDetailView: View { .tint(CorveilTheme.gold) } + if session.status == .completed { + Button { + appState.onSetSessionActive?(session.id) + } label: { + Label("Move to Active", systemImage: "arrow.uturn.backward.circle") + .font(.caption) + } + .buttonStyle(.bordered) + .controlSize(.small) + .tint(CorveilTheme.gold) + } + Button(role: .destructive) { sessionToDelete = session } label: { diff --git a/Sources/Crow/App/AppDelegate.swift b/Sources/Crow/App/AppDelegate.swift index 9b258e9..fa1a5cc 100644 --- a/Sources/Crow/App/AppDelegate.swift +++ b/Sources/Crow/App/AppDelegate.swift @@ -154,6 +154,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { appState.onSetSessionInReview = { [weak service] id in service?.setSessionInReview(id: id) } + appState.onSetSessionActive = { [weak service] id in + service?.setSessionActive(id: id) + } appState.onLaunchClaude = { [weak service] terminalID in service?.launchClaude(terminalID: terminalID) diff --git a/Sources/Crow/App/SessionService.swift b/Sources/Crow/App/SessionService.swift index 0813cb0..09636b9 100644 --- a/Sources/Crow/App/SessionService.swift +++ b/Sources/Crow/App/SessionService.swift @@ -911,6 +911,10 @@ final class SessionService { updateSessionStatus(id, to: .inReview) } + func setSessionActive(id: UUID) { + updateSessionStatus(id, to: .active) + } + // MARK: - Persist Current State /// Sync all in-memory state back to the JSON store on disk.