diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json
index 2dd53a4..043c575 100644
--- a/src/ClaudeDo.Localization/locales/de.json
+++ b/src/ClaudeDo.Localization/locales/de.json
@@ -384,7 +384,7 @@
"vm": {
"connection": { "online": "Online", "connecting": "Verbinden…", "offline": "Offline" },
"shell": { "restartingWorker": "Worker wird neu gestartet…" },
- "agentStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen" },
+ "agentStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "review": "Prüfung", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen" },
"taskStatus": { "idle": "Leerlauf", "queued": "In Warteschlange", "running": "Läuft", "waitingForReview": "Wartet auf Prüfung", "done": "Fertig", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen" },
"planningBadge": { "active": "PLANUNG", "finalized": "GEPLANT" },
"taskRow": { "createdPrefix": "Erstellt {0}", "stepsText": "{0}/{1} Schritte" },
diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json
index c9f228a..9046651 100644
--- a/src/ClaudeDo.Localization/locales/en.json
+++ b/src/ClaudeDo.Localization/locales/en.json
@@ -384,7 +384,7 @@
"vm": {
"connection": { "online": "Online", "connecting": "Connecting…", "offline": "Offline" },
"shell": { "restartingWorker": "Restarting worker…" },
- "agentStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "done": "Done", "failed": "Failed", "cancelled": "Cancelled" },
+ "agentStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "review": "Review", "done": "Done", "failed": "Failed", "cancelled": "Cancelled" },
"taskStatus": { "idle": "Idle", "queued": "Queued", "running": "Running", "waitingForReview": "Waiting for Review", "done": "Done", "failed": "Failed", "cancelled": "Cancelled" },
"planningBadge": { "active": "PLANNING", "finalized": "PLANNED" },
"taskRow": { "createdPrefix": "Created {0}", "stepsText": "{0}/{1} steps" },
diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs
index f77d7f1..b57d239 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs
@@ -110,12 +110,13 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
[NotifyCanExecuteChangedFor(nameof(ContinueCommand))]
private string _agentState = "idle";
public string AgentStatusLabel => Loc.T($"vm.agentStatus.{AgentState}");
- public bool IsIdle => AgentState == "idle";
- public bool IsQueued => AgentState == "queued";
- public bool IsRunning => AgentState == "running";
- public bool IsDone => AgentState == "done";
- public bool IsFailed => AgentState == "failed";
- public bool IsCancelled => AgentState == "cancelled";
+ public bool IsIdle => AgentState == "idle";
+ public bool IsQueued => AgentState == "queued";
+ public bool IsRunning => AgentState == "running";
+ public bool IsWaitingForReview => AgentState == "review";
+ public bool IsDone => AgentState == "done";
+ public bool IsFailed => AgentState == "failed";
+ public bool IsCancelled => AgentState == "cancelled";
// Recovery actions: Continue (resume session) for Failed/Cancelled.
public bool ShowContinue => IsFailed || IsCancelled;
@@ -132,6 +133,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
OnPropertyChanged(nameof(IsIdle));
OnPropertyChanged(nameof(IsQueued));
OnPropertyChanged(nameof(IsRunning));
+ OnPropertyChanged(nameof(IsWaitingForReview));
OnPropertyChanged(nameof(IsDone));
OnPropertyChanged(nameof(IsFailed));
OnPropertyChanged(nameof(IsCancelled));
@@ -279,7 +281,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
{
ClaudeDo.Data.Models.TaskStatus.Queued => "queued",
ClaudeDo.Data.Models.TaskStatus.Running => "running",
- ClaudeDo.Data.Models.TaskStatus.WaitingForReview => "running",
+ ClaudeDo.Data.Models.TaskStatus.WaitingForReview => "review",
ClaudeDo.Data.Models.TaskStatus.Done => "done",
ClaudeDo.Data.Models.TaskStatus.Failed => "failed",
ClaudeDo.Data.Models.TaskStatus.Cancelled => "cancelled",
@@ -291,7 +293,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
"done" => "done",
"failed" => "failed",
"cancelled" => "cancelled",
- "waiting_for_review" => "running",
+ "waiting_for_review" => "review",
_ => status.ToLowerInvariant(),
};
@@ -880,6 +882,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
AgentState = StatusToStateKey(entity.Status);
if (Task is { } row && entity.Worktree?.DiffStat is { } stat)
row.DiffStat = stat;
+ var (add, del) = ParseDiffStat(entity.Worktree?.DiffStat);
+ DiffAdditions = add;
+ DiffDeletions = del;
}
catch { /* best-effort refresh */ }
}
@@ -1131,6 +1136,52 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
[RelayCommand] private void ResetTaskModel() => TaskModelSelection = null;
[RelayCommand] private void ResetTaskTurns() => TaskMaxTurns = null;
[RelayCommand] private void ResetTaskAgent() => TaskSelectedAgent = TaskAgentOptions.Count > 0 ? TaskAgentOptions[0] : null;
+
+ // ── Review actions ──────────────────────────────────────────────────────────
+ [ObservableProperty] private string _reviewFeedback = "";
+
+ [RelayCommand]
+ private async System.Threading.Tasks.Task ApproveReviewAsync()
+ {
+ if (Task is null || !_worker.IsConnected) return;
+ await _worker.ApproveReviewAsync(Task.Id);
+ }
+
+ [RelayCommand]
+ private async System.Threading.Tasks.Task RejectReviewAsync()
+ {
+ if (Task is null || !_worker.IsConnected) return;
+ var feedback = ReviewFeedback;
+ if (string.IsNullOrWhiteSpace(feedback)) return;
+ await _worker.RejectReviewToQueueAsync(Task.Id, feedback);
+ ReviewFeedback = "";
+ }
+
+ [RelayCommand]
+ private async System.Threading.Tasks.Task ParkReviewAsync()
+ {
+ if (Task is null || !_worker.IsConnected) return;
+ await _worker.RejectReviewToIdleAsync(Task.Id);
+ }
+
+ [RelayCommand]
+ private async System.Threading.Tasks.Task CancelReviewAsync()
+ {
+ if (Task is null || !_worker.IsConnected) return;
+ await _worker.CancelReviewAsync(Task.Id);
+ }
+
+ // ── Diff meter parser ───────────────────────────────────────────────────────
+ internal static (int Additions, int Deletions) ParseDiffStat(string? stat)
+ {
+ if (string.IsNullOrEmpty(stat)) return (0, 0);
+ int add = 0, del = 0;
+ var m1 = System.Text.RegularExpressions.Regex.Match(stat, @"(\d+)\s+insertion");
+ var m2 = System.Text.RegularExpressions.Regex.Match(stat, @"(\d+)\s+deletion");
+ if (m1.Success) int.TryParse(m1.Groups[1].Value, out add);
+ if (m2.Success) int.TryParse(m2.Groups[1].Value, out del);
+ return (add, del);
+ }
}
public sealed partial class SubtaskRowViewModel : ViewModelBase
diff --git a/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml b/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml
index a836fc0..e8c93d5 100644
--- a/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml
+++ b/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml
@@ -185,6 +185,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/ClaudeDo.Worker.Tests/UiVm/ParseDiffStatTests.cs b/tests/ClaudeDo.Worker.Tests/UiVm/ParseDiffStatTests.cs
new file mode 100644
index 0000000..b41a4bf
--- /dev/null
+++ b/tests/ClaudeDo.Worker.Tests/UiVm/ParseDiffStatTests.cs
@@ -0,0 +1,38 @@
+using ClaudeDo.Ui.ViewModels.Islands;
+
+namespace ClaudeDo.Worker.Tests.UiVm;
+
+public class ParseDiffStatTests
+{
+ [Fact]
+ public void Null_Returns_Zero()
+ {
+ var (add, del) = DetailsIslandViewModel.ParseDiffStat(null);
+ Assert.Equal(0, add);
+ Assert.Equal(0, del);
+ }
+
+ [Fact]
+ public void Empty_Returns_Zero()
+ {
+ var (add, del) = DetailsIslandViewModel.ParseDiffStat("");
+ Assert.Equal(0, add);
+ Assert.Equal(0, del);
+ }
+
+ [Fact]
+ public void Full_Stat_Parses_Both()
+ {
+ var (add, del) = DetailsIslandViewModel.ParseDiffStat("2 files changed, 10 insertions(+), 3 deletions(-)");
+ Assert.Equal(10, add);
+ Assert.Equal(3, del);
+ }
+
+ [Fact]
+ public void Insertions_Only_Returns_Zero_Deletions()
+ {
+ var (add, del) = DetailsIslandViewModel.ParseDiffStat("1 file changed, 5 insertions(+)");
+ Assert.Equal(5, add);
+ Assert.Equal(0, del);
+ }
+}