fix(ui): failureReason "usage_limit" nachziehen + Worktree-Gate prüft die Platte

Zwei liegengebliebene Consumer aus den letzten beiden Commits:

- TaskRunner klassifiziert seit 07dd7570 "usage_limit", aber weder
  TaskRowViewModel.FailureReasonLabel noch vm.failureReason (de/en) noch
  die get_task-Tool-Beschreibung kannten den Wert — die UI zeigte
  "Grund unbekannt", das MCP-Doc listete weiterhin max_turns|timeout|error.
- TaskRowViewModel.CanOpenWorktree prüfte nur auf einen nicht-leeren
  String. Die Zeile behält den Path eines gemergten/verworfenen Worktrees,
  also war der Menüpunkt aktiv und Process.Start warf in den Footer.
  Jetzt zusätzlich Directory.Exists — dieselbe Prüfung, die
  WorktreesOverviewModalViewModel und MergeSectionViewModel schon machen.
This commit is contained in:
mika kuns
2026-08-24 09:05:31 +02:00
parent 3dfae30fff
commit 29171b104b
5 changed files with 37 additions and 15 deletions
@@ -180,7 +180,11 @@ public sealed partial class TaskRowViewModel : ViewModelBase
// placeholder so the menu wiring is uniform; always null until a real gate exists.
public string? QuickSessionDisabledReason => null;
public bool CanOpenWorktree => !string.IsNullOrWhiteSpace(WorktreePath);
// The row keeps a merged/discarded worktree's recorded Path, so a non-empty string is not
// proof the folder is still there — same on-disk check the worktrees overview and the merge
// section do before offering "open". Only read when the context menu is built, not per row.
public bool CanOpenWorktree =>
!string.IsNullOrWhiteSpace(WorktreePath) && System.IO.Directory.Exists(WorktreePath);
public string? OpenWorktreeDisabledReason => CanOpenWorktree ? null : Loc.T("tasks.reasonNoWorktree");
public string? RefineDisabledReason
@@ -233,11 +237,12 @@ public sealed partial class TaskRowViewModel : ViewModelBase
public bool HasFailureReason => Status == TaskStatus.Failed;
public string FailureReasonLabel => FailureReason switch
{
"max_turns" => Loc.T("vm.failureReason.maxTurns"),
"timeout" => Loc.T("vm.failureReason.timeout"),
"cancelled" => Loc.T("vm.failureReason.cancelled"),
"error" => Loc.T("vm.failureReason.error"),
_ => Loc.T("vm.failureReason.unknown"),
"max_turns" => Loc.T("vm.failureReason.maxTurns"),
"timeout" => Loc.T("vm.failureReason.timeout"),
"usage_limit" => Loc.T("vm.failureReason.usageLimit"),
"cancelled" => Loc.T("vm.failureReason.cancelled"),
"error" => Loc.T("vm.failureReason.error"),
_ => Loc.T("vm.failureReason.unknown"),
};
// max_turns gets the actionable detail (turns used/configured) since that's the one case
// where the fix is "raise maxTurns and continue_task", not "reset and re-run".