diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json
index 8753be16..5c806957 100644
--- a/src/ClaudeDo.Localization/locales/de.json
+++ b/src/ClaudeDo.Localization/locales/de.json
@@ -266,6 +266,9 @@
"overviewMode": "Übersicht",
"closeSession": "Sitzung schließen",
"conptyLaunchFailed": "ConPTY-Sitzung konnte nicht geöffnet werden: {0}",
+ "submitForReviewFailed": "Einreichen zum Review fehlgeschlagen: {0}",
+ "submitForReview": "Zum Review einreichen",
+ "submitForReviewTip": "Diesen Worktree committen und den Task ins Review bringen, damit er gemergt werden kann",
"planningTitleSuffix": " (Planung)",
"question": {
"title": "Claude fragt nach",
diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json
index 9a82ee24..5e1c1b7e 100644
--- a/src/ClaudeDo.Localization/locales/en.json
+++ b/src/ClaudeDo.Localization/locales/en.json
@@ -266,6 +266,9 @@
"overviewMode": "Overview",
"closeSession": "Close session",
"conptyLaunchFailed": "Couldn't open ConPTY session: {0}",
+ "submitForReviewFailed": "Couldn't submit for review: {0}",
+ "submitForReview": "Submit for review",
+ "submitForReviewTip": "Commit this worktree and move the task to review so it can be merged",
"planningTitleSuffix": " (Planning)",
"question": {
"title": "Claude is asking",
diff --git a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs
index b0e32e93..3e0f8ebf 100644
--- a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs
+++ b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs
@@ -76,6 +76,9 @@ public interface IWorkerClient : INotifyPropertyChanged
Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default);
// Picks up a task's Claude session in a real terminal window (--resume).
Task ResumeTaskInTerminalAsync(string taskId, CancellationToken ct = default);
+ /// Commits an interactively-worked task's worktree and moves it to WaitingForReview
+ /// (the only path that flips a hand-driven ConPTY session into the review/merge pipeline).
+ Task SubmitTaskForReviewAsync(string taskId, CancellationToken ct = default);
/// Launch spec for an embedded ConPTY terminal to open an interactive session
/// in a task's worktree (same worktree prep as an autonomous run).
Task GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default);
diff --git a/src/ClaudeDo.Ui/Services/WorkerClient.cs b/src/ClaudeDo.Ui/Services/WorkerClient.cs
index 56768cc4..5ce7adf6 100644
--- a/src/ClaudeDo.Ui/Services/WorkerClient.cs
+++ b/src/ClaudeDo.Ui/Services/WorkerClient.cs
@@ -513,6 +513,9 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
public async Task ResumeTaskInTerminalAsync(string taskId, CancellationToken ct = default)
=> await _hub.InvokeAsync("ResumeTaskInTerminal", taskId, ct);
+ public async Task SubmitTaskForReviewAsync(string taskId, CancellationToken ct = default)
+ => await _hub.InvokeAsync("SubmitTaskForReview", taskId, ct);
+
public async Task GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default)
=> await _hub.InvokeAsync("GetInteractiveLaunchSpec", taskId, ct);
diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs
index 7c6b7647..604ed891 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs
@@ -413,6 +413,8 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
ReviewDiffViewed = false;
ApproveReviewCommand.NotifyCanExecuteChanged();
OnPropertyChanged(nameof(ShowReviewDiffHint));
+ OnPropertyChanged(nameof(CanSubmitForReview));
+ SubmitForReviewCommand.NotifyCanExecuteChanged();
AgentSettings.IsRunning = IsRunning;
NotifySessionSections();
OnPropertyChanged(nameof(CanAcceptDrop));
@@ -758,6 +760,8 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
Merge.SyncWorktree(WorktreePath, WorktreeBaseCommit, WorktreeHeadCommit,
WorktreeStateLabel, _listWorkingDir);
NotifySessionSections();
+ OnPropertyChanged(nameof(CanSubmitForReview));
+ SubmitForReviewCommand.NotifyCanExecuteChanged();
}
partial void OnWorktreeHeadCommitChanged(string? value) =>
@@ -1034,6 +1038,23 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
Task != null && _worker.IsConnected && IsWaitingForReview
&& (!Merge.HasReviewableDiff || ReviewDiffViewed);
+ // An interactive (ConPTY) session leaves its worktree changed but never flips the task
+ // status. Offer "Submit for review" for an Idle/Failed task that still has a worktree, so
+ // its hand-driven work can enter the normal review/merge flow (commits first, server-side).
+ public bool CanSubmitForReview =>
+ Task != null && _worker.IsConnected && (IsIdle || IsFailed) && !string.IsNullOrEmpty(WorktreePath);
+
+ [RelayCommand(CanExecute = nameof(CanSubmitForReview))]
+ private async System.Threading.Tasks.Task SubmitForReviewAsync()
+ {
+ if (Task is null || !_worker.IsConnected) return;
+ try { await _worker.SubmitTaskForReviewAsync(Task.Id); }
+ catch (Exception ex)
+ {
+ if (ShowErrorAsync != null) await ShowErrorAsync(ex.Message);
+ }
+ }
+
[RelayCommand(CanExecute = nameof(HasReviewFeedback))]
private async System.Threading.Tasks.Task RejectReviewAsync()
{
diff --git a/src/ClaudeDo.Ui/ViewModels/MissionControl/ConPtyPaneViewModel.cs b/src/ClaudeDo.Ui/ViewModels/MissionControl/ConPtyPaneViewModel.cs
index 3ae6ebef..40125658 100644
--- a/src/ClaudeDo.Ui/ViewModels/MissionControl/ConPtyPaneViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/MissionControl/ConPtyPaneViewModel.cs
@@ -17,6 +17,9 @@ public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControl
{
public string? TaskId { get; }
+ // Only a task-based pane can be submitted for review (an ad-hoc directory session has no task).
+ public bool IsTaskBased => TaskId is not null;
+
[ObservableProperty] private string _displayTitle;
public InteractiveTerminalViewModel Terminal { get; } = new();
@@ -27,6 +30,10 @@ public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControl
/// Set by the host (Mission Control) to remove this pane from its collection.
public Action? CloseRequested { get; set; }
+ /// Raised when the user submits this task's hand-driven work for review; the host
+ /// commits the worktree and moves the task to WaitingForReview.
+ public event Action? SubmitForReviewRequested;
+
/// Task-based pane — dedup'd by . Pass null for an ad-hoc pane
/// (no task, never deduped); prefer at ad-hoc call sites.
public ConPtyPaneViewModel(string? taskId, string displayTitle, TerminalLaunchDescriptor descriptor)
@@ -50,6 +57,14 @@ public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControl
[RelayCommand]
private void Close() => CloseRequested?.Invoke(this);
+ private bool CanSubmitForReview() => IsTaskBased;
+
+ [RelayCommand(CanExecute = nameof(CanSubmitForReview))]
+ private void SubmitForReview()
+ {
+ if (TaskId is { } id) SubmitForReviewRequested?.Invoke(id);
+ }
+
public void Dispose()
{
Terminal.PropertyChanged -= OnTerminalPropertyChanged;
diff --git a/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs b/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs
index b7a3966e..6ed5ee7d 100644
--- a/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs
@@ -248,6 +248,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
var pane = new ConPtyPaneViewModel(taskId, title, descriptor);
pane.ErrorReported += OnConPtyPaneError;
pane.CloseRequested += CloseConPtySession;
+ pane.SubmitForReviewRequested += OnPaneSubmitForReview;
ConPtySessions.Add(pane);
}
catch (Exception ex)
@@ -287,6 +288,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
var pane = new ConPtyPaneViewModel(taskId, title, descriptor);
pane.ErrorReported += OnConPtyPaneError;
pane.CloseRequested += CloseConPtySession;
+ pane.SubmitForReviewRequested += OnPaneSubmitForReview;
ConPtySessions.Add(pane);
}
catch (Exception ex)
@@ -311,6 +313,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
var pane = ConPtyPaneViewModel.CreateAdHoc(title, descriptor);
pane.ErrorReported += OnConPtyPaneError;
pane.CloseRequested += CloseConPtySession;
+ pane.SubmitForReviewRequested += OnPaneSubmitForReview;
ConPtySessions.Add(pane);
}
catch (Exception ex)
@@ -321,6 +324,22 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
private void OnConPtyPaneError(string message) => ErrorReported?.Invoke(message);
+ // Submit a task's hand-driven ConPTY work for review, then close the pane (the interactive
+ // session is finished). The worker commits the worktree and moves the task to WaitingForReview.
+ private async void OnPaneSubmitForReview(string taskId)
+ {
+ try
+ {
+ await _worker.SubmitTaskForReviewAsync(taskId);
+ if (ConPtySessions.FirstOrDefault(s => s.TaskId == taskId) is { } pane)
+ CloseConPtySession(pane);
+ }
+ catch (Exception ex)
+ {
+ ErrorReported?.Invoke(Loc.T("missionControl.submitForReviewFailed", ex.Message));
+ }
+ }
+
private void CloseConPtySession(ConPtyPaneViewModel pane)
{
if (!ConPtySessions.Contains(pane)) return;
diff --git a/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml b/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml
index 2eb3bbdf..0110347a 100644
--- a/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml
+++ b/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml
@@ -372,6 +372,17 @@
ToolTip.Tip="{loc:Tr session.reviewResetTip}"
Command="{Binding ResetReviewCommand}" />
+
+
+
+
+
+
+
diff --git a/src/ClaudeDo.Ui/Views/MissionControl/ConPtyPaneView.axaml b/src/ClaudeDo.Ui/Views/MissionControl/ConPtyPaneView.axaml
index b8e18362..7b1eeef6 100644
--- a/src/ClaudeDo.Ui/Views/MissionControl/ConPtyPaneView.axaml
+++ b/src/ClaudeDo.Ui/Views/MissionControl/ConPtyPaneView.axaml
@@ -14,13 +14,18 @@
Background="{DynamicResource Surface2Brush}"
BorderBrush="{DynamicResource LineBrush}"
BorderThickness="0,0,0,1" Padding="8,3">
-
+
-
+