fix(ui): silent no-ops raise ErrorReported instead of swallowing failures (UX-Audit #1)
Stop/Enqueue/Dequeue/Reset&Retry (DetailsIslandViewModel), status/cancel/reject
commands (TasksIslandViewModel), Mission Control's drag-enqueue and queue
refresh, and "Open findings folder" (ListsIslandViewModel) used to catch {}
or silently return on a blocked precondition. They now report through the
existing ErrorReported -> FlashFooterError path, with new en/de locale keys
and Ui.Tests covering each converted command.
This commit is contained in:
@@ -1129,9 +1129,13 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
private async System.Threading.Tasks.Task StopAsync()
|
||||
{
|
||||
if (Task == null || !IsRunning) return;
|
||||
if (!_worker.IsConnected) return;
|
||||
if (!_worker.IsConnected)
|
||||
{
|
||||
ErrorReported?.Invoke(Loc.T("vm.detailsIsland.stopOffline"));
|
||||
return;
|
||||
}
|
||||
try { await _worker.CancelTaskAsync(Task.Id); }
|
||||
catch { /* offline */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.detailsIsland.stopFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanEnqueue))]
|
||||
@@ -1144,7 +1148,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
AgentState = "queued";
|
||||
ReportBaseDirty(baseDirty);
|
||||
}
|
||||
catch { /* offline */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.detailsIsland.enqueueFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
private void ReportBaseDirty(BaseDirtyWarningDto? warning)
|
||||
@@ -1167,7 +1171,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
await _worker.SetTaskStatusAsync(Task.Id, ClaudeDo.Data.Models.TaskStatus.Idle);
|
||||
AgentState = "idle";
|
||||
}
|
||||
catch { /* offline */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.detailsIsland.dequeueFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
private bool CanDequeue() =>
|
||||
@@ -1223,7 +1227,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
AgentState = "queued";
|
||||
ReportBaseDirty(baseDirty);
|
||||
}
|
||||
catch { /* offline */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.detailsIsland.resetAndRetryFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
// Reset & Retry discards the branch/uncommitted work and queues an autonomous run into the
|
||||
|
||||
@@ -126,7 +126,11 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
|
||||
var dir = row?.WorkingDir;
|
||||
if (string.IsNullOrWhiteSpace(dir)) return;
|
||||
var findings = System.IO.Path.Combine(dir, ".claudedo");
|
||||
if (!System.IO.Directory.Exists(findings)) return;
|
||||
if (!System.IO.Directory.Exists(findings))
|
||||
{
|
||||
ErrorReported?.Invoke(Loc.T("vm.lists.findingsNotFound"));
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
@@ -135,7 +139,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
catch { /* best-effort */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.lists.findingsOpenFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -1136,7 +1136,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
|
||||
var baseDirty = await _worker.SetTaskStatusAsync(row.Id, status);
|
||||
ReportBaseDirty(baseDirty);
|
||||
}
|
||||
catch { /* offline; broadcast won't fire */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.tasksIsland.setStatusFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
private void ReportBaseDirty(BaseDirtyWarningDto? warning)
|
||||
@@ -1227,7 +1227,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
if (row is null || !row.IsRunning || _worker is null) return;
|
||||
try { await _worker.CancelTaskAsync(row.Id); }
|
||||
catch { /* worker offline; the broadcast will reconcile when it returns */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.tasksIsland.cancelFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
// ── Review actions (visible when a task is WaitingForReview) ─────────────
|
||||
@@ -1247,7 +1247,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
|
||||
if (!row.IsWaitingForReview || _worker is null) return;
|
||||
if (string.IsNullOrWhiteSpace(feedback)) return;
|
||||
try { await _worker.RejectReviewToQueueAsync(row.Id, feedback); }
|
||||
catch { /* offline; broadcast reconciles on return */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.tasksIsland.rejectToQueueFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -1255,7 +1255,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
if (row is null || !row.IsWaitingForReview || _worker is null) return;
|
||||
try { await _worker.RejectReviewToIdleAsync(row.Id); }
|
||||
catch { /* offline; broadcast reconciles on return */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.tasksIsland.rejectToIdleFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -119,7 +119,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
}
|
||||
OnPropertyChanged(nameof(HasQueued));
|
||||
}
|
||||
catch { /* best-effort queue refresh */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("missionControl.queueRefreshFailed", ex.Message)); }
|
||||
}
|
||||
|
||||
// Drop-to-queue: a task dragged from the main app onto Mission Control gets queued. Goes
|
||||
@@ -130,7 +130,11 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
public async System.Threading.Tasks.Task EnqueueTaskAsync(string taskId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(taskId)) return;
|
||||
if (ConPtySessions.Any(s => s.TaskId == taskId)) return;
|
||||
if (ConPtySessions.Any(s => s.TaskId == taskId))
|
||||
{
|
||||
ErrorReported?.Invoke(Loc.T("missionControl.enqueueAlreadyOpen"));
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var baseDirty = await _worker.SetTaskStatusAsync(taskId, ClaudeDo.Data.Models.TaskStatus.Queued);
|
||||
@@ -138,7 +142,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
ErrorReported?.Invoke(Loc.T(
|
||||
"vm.queue.baseDirtyWarning", baseDirty.ModifiedCount, baseDirty.UntrackedCount));
|
||||
}
|
||||
catch { /* best-effort enqueue */ }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("missionControl.enqueueFailed", ex.Message)); }
|
||||
await RefreshQueueAsync();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user