From 46e9303da6d1aa6f0230f781c8e345e5830ff65b Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 1 Oct 2026 14:50:55 +0200 Subject: [PATCH] fix(mission-control): show post-merge verify as Verifying instead of In review Derived from the VerifyMerge entry in RunningOperations (no new TaskStatus). The Running now row stays, reworded to 'Verifying after merge'. Co-Authored-By: Claude Sonnet 5.5 --- src/ClaudeDo.Localization/locales/de.json | 3 ++- src/ClaudeDo.Localization/locales/en.json | 3 ++- .../ViewModels/MissionControlViewModel.cs | 25 +++++++++++++++--- .../MissionControlRunningOperationsTests.cs | 26 +++++++++++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json index ffd67203..073bb51d 100644 --- a/src/ClaudeDo.Localization/locales/de.json +++ b/src/ClaudeDo.Localization/locales/de.json @@ -384,7 +384,7 @@ "settings": "Einstellungen", "activity": "Aktivität", "runningNow": "Läuft gerade", - "opVerifyMerge": "Verify (Merge)", + "opVerifyMerge": "Prüfung nach Merge", "opVerifyBatch": "Verify (Batch)", "opMerging": "Merge", "opRebasing": "Rebase", @@ -393,6 +393,7 @@ "blocked": "Blockiert", "running": "Läuft", "review": "Im Review", + "verifying": "Wird geprüft", "failed": "Fehlgeschlagen", "roadblock": "Blockade gemeldet", "openSession": "Sitzung öffnen", diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json index c12a6eb8..d5314640 100644 --- a/src/ClaudeDo.Localization/locales/en.json +++ b/src/ClaudeDo.Localization/locales/en.json @@ -384,7 +384,7 @@ "settings": "Settings", "activity": "Activity", "runningNow": "Running now", - "opVerifyMerge": "Verify (merge)", + "opVerifyMerge": "Verifying after merge", "opVerifyBatch": "Verify (batch)", "opMerging": "Merging", "opRebasing": "Rebasing", @@ -393,6 +393,7 @@ "blocked": "Blocked", "running": "Running", "review": "In review", + "verifying": "Verifying", "failed": "Failed", "roadblock": "Roadblock reported", "openSession": "Open session", diff --git a/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs b/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs index cc7d4dfc..a690b500 100644 --- a/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs @@ -157,10 +157,12 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable try { var snapshot = await _worker.GetActiveOperationsAsync(); + var hadVerify = RunningOperations.Any(IsVerifyMerge); RunningOperations.Clear(); foreach (var op in snapshot.Where(o => !_endedOperationIds.Contains(o.Id)).OrderBy(o => o.StartedAt)) RunningOperations.Add(new RunningOperationViewModel(op)); NotifyOperationsChanged(); + if (hadVerify || RunningOperations.Any(IsVerifyMerge)) _ = RefreshQueueAsync(); } catch (Exception ex) { ErrorReported?.Invoke(Loc.T("missionControl.operationsRefreshFailed", ex.Message)); } } @@ -171,8 +173,10 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable private void AddOperation(RunningOperationDto op) { if (_endedOperationIds.Contains(op.Id) || RunningOperations.Any(r => r.Id == op.Id)) return; - RunningOperations.Add(new RunningOperationViewModel(op)); + var row = new RunningOperationViewModel(op); + RunningOperations.Add(row); NotifyOperationsChanged(); + if (IsVerifyMerge(row)) _ = RefreshQueueAsync(); } private void RemoveOperation(string id) @@ -182,8 +186,12 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable if (row is null) return; RunningOperations.Remove(row); NotifyOperationsChanged(); + if (IsVerifyMerge(row)) _ = RefreshQueueAsync(); } + /// A post-merge verify keeps its task in WaitingForReview, so only this op marks the activity row "Verifying". + private static bool IsVerifyMerge(RunningOperationViewModel r) => r.Kind == OperationKinds.VerifyMerge; + private void NotifyOperationsChanged() { OnPropertyChanged(nameof(HasRunningOperations)); @@ -220,7 +228,8 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable { var id = r.Id; var running = r.Status == ClaudeDo.Data.Models.TaskStatus.Running; - var (tint, colorKey, labelKey) = Decorate(r.Status, r.RoadblockCount, r.BlockedByTaskId != null); + var (tint, colorKey, labelKey) = Decorate(r.Status, r.RoadblockCount, r.BlockedByTaskId != null, + RunningOperations.Any(o => o.Kind == OperationKinds.VerifyMerge && o.TaskId == id)); Queued.Add(new QueuedTaskViewModel { Id = id, @@ -243,12 +252,14 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable // Row tint + status label for the activity strip. Keys are resolved to brushes by // DotBrushConverter ("Brush"), so they must match tokens in Tokens.axaml. private static (string? Tint, string? ColorKey, string? LabelKey) Decorate( - ClaudeDo.Data.Models.TaskStatus status, int roadblockCount, bool blocked) + ClaudeDo.Data.Models.TaskStatus status, int roadblockCount, bool blocked, bool verifying) => status switch { ClaudeDo.Data.Models.TaskStatus.Failed => ("ErrorTint", "StatusError", "missionControl.failed"), ClaudeDo.Data.Models.TaskStatus.WaitingForReview when roadblockCount > 0 => ("RoadblockTint", "Amber", "missionControl.roadblock"), + ClaudeDo.Data.Models.TaskStatus.WaitingForReview when verifying + => ("RunningTint", "StatusRunning", "missionControl.verifying"), ClaudeDo.Data.Models.TaskStatus.WaitingForReview => ("DoneTint", "StatusReview", "missionControl.review"), ClaudeDo.Data.Models.TaskStatus.Running => ("RunningTint", "StatusRunning", "missionControl.running"), _ when blocked => (null, "Amber", "missionControl.blocked"), @@ -890,7 +901,11 @@ public sealed partial class RunningOperationViewModel : ObservableObject private readonly DateTime _startedAtUtc; public string Id { get; } - /// Localized operation kind ("Verify (merge)", "Rebasing", ...). + /// Wire kind token (). + public string Kind { get; } + /// The task the operation runs for; null for a batch verify. + public string? TaskId { get; } + /// Localized operation kind ("Verifying after merge", "Rebasing", ...). public string KindLabel { get; } /// #Number Title for a task operation, the list name for a batch verify. public string Subject { get; } @@ -900,6 +915,8 @@ public sealed partial class RunningOperationViewModel : ObservableObject public RunningOperationViewModel(RunningOperationDto op) { Id = op.Id; + Kind = op.Kind; + TaskId = op.TaskId; _startedAtUtc = op.StartedAt; KindLabel = op.Kind switch { diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlRunningOperationsTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlRunningOperationsTests.cs index e1304b25..47919d6d 100644 --- a/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlRunningOperationsTests.cs +++ b/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlRunningOperationsTests.cs @@ -1,4 +1,6 @@ using ClaudeDo.Data; +using ClaudeDo.Data.Models; +using ClaudeDo.Ui.Localization; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels; using Microsoft.EntityFrameworkCore; @@ -126,6 +128,30 @@ public class MissionControlRunningOperationsTests : IDisposable Assert.Equal("c", Assert.Single(vm.RunningOperations).Id); } + [Fact] + public async Task VerifyMergeOperation_ShowsReviewTaskAsVerifying_UntilItEnds() + { + await using (var db = NewContext()) + { + db.Lists.Add(new ListEntity { Id = "L1", Name = "Work", CreatedAt = DateTime.UtcNow }); + db.Tasks.Add(new TaskEntity { Number = TestTaskNumbers.Next(), Id = "rev1", ListId = "L1", Title = "review", Status = ClaudeDo.Data.Models.TaskStatus.WaitingForReview, CreatedAt = DateTime.UtcNow }); + await db.SaveChangesAsync(); + } + var worker = new FakeWorker(); + using var vm = BuildVm(worker); + await vm.RefreshQueueAsync(); + Assert.Equal(Loc.T("missionControl.review"), vm.Queued.Single().StatusLabel); + + worker.RaiseOperationStarted(new RunningOperationDto("v1", OperationKinds.VerifyMerge, "rev1", 1, "L1", "review", DateTime.UtcNow)); + await vm.RefreshQueueAsync(); + Assert.Equal(Loc.T("missionControl.verifying"), vm.Queued.Single().StatusLabel); + Assert.Equal("StatusRunning", vm.Queued.Single().StatusColorKey); + + worker.RaiseOperationEnded("v1"); + await vm.RefreshQueueAsync(); + Assert.Equal(Loc.T("missionControl.review"), vm.Queued.Single().StatusLabel); + } + [Fact] public async Task TickOperations_UpdatesElapsed() {