Merge branch 'claudedo/c209d54cef5642278673d41e8b98fc43'
This commit is contained in:
@@ -15,7 +15,6 @@ Checkliste lebt in `docs/verification-handoff.md`.
|
||||
|
||||
- **Planning-aktiver Parent zeigt weiter „Idle":** Parent in `planning_phase=active` hat `Status=Idle` (korrekt im Modell), aber `TaskRowViewModel.StatusLabel` (Zeile 132) kennt nur `HasInteractiveSession`/`IsParked` als Overrides — der `PlanningBadge` ersetzt den Chip nicht sichtbar → liest sich wie ein normaler Idle-Task. Wunsch: klarer „Planning/Draft aktiv"-Zustand, der Idle überschreibt. (Kinder zeigen korrekt „Draft".)
|
||||
- **Blocked-by-Kette nicht sichtbar:** Nach Finalize ist die sequentielle Kette korrekt gesetzt (child[i] blocked-by child[i-1]), aber die UI stellt Reihenfolge/Abhängigkeit nicht dar — es gibt nur den „waiting"-Chip. Wunsch: Kette visualisieren (z.B. „wartet auf <Vorgänger>").
|
||||
- **Dequeue-„X" fehlt auf wartenden (blockierten) Kettengliedern:** `CanRemoveFromQueue = IsQueued || HasQueuedSubtasks` (TaskRowViewModel.cs:99), `IsQueued` verlangt leeres `blocked_by`. Ein gequeuetes, aber blockiertes Kind (`IsWaiting`) bekommt daher kein Remove-from-queue-X — nur Parent + erstes (entsperrtes) Kind.
|
||||
- **Modal-Bodies unten abgeschnitten — gefixt, Sichtprüfung offen** (Ursache 2026-09-02 verifiziert: `ScrollViewer.Padding` zählt in Avalonia 12 nicht zum Extent; Fix ist das Padding auf ein `Margin` des inneren Inhalts umzuziehen). Betraf `AboutModalView`, `MergeHelperSelectionModal`, `MergeModalView`, `RepoImportModalView`, `WorktreesOverviewModalView` — alle fünf umgestellt. `ListSettingsModalView`, `LogVisualizerView` und `WorkConsole` hatten zu diesem Zeitpunkt bereits kein ScrollViewer-Padding mehr.
|
||||
- **Usage-Monitor-Modal: Analyse-Tabellen ausbaufähig** (Sichtprüfung 2026-08-06; Gauges, Info-Bänder, Presets/Custom-Range und Refresh-Button sind in Ordnung). Models-Tab: die Spaltenköpfe „OTHER CACHE" und „SHARE" kollidieren zu `OTHER CACHISHARE`; `claude-haiku-4-5-20251001` läuft in die IN-Spalte; alle Zahlen linksbündig und ohne Tausendertrenner (`506830708`). Tasks-Tab: Task-Titel wird ohne Ellipse hart an der LIST-Spalte abgeschnitten, der LIST-Text ebenfalls; MODEL ist bei Runs von vor der `task_runs.model`-Spalte leer (besser „—"). Wunsch: Zahlen rechtsbündig + `#,##0` (oder k/M), Spaltenbreiten/Truncation fixen.
|
||||
- **Conflict-Resolver: mehrere Konfliktdateien schlecht erkennbar:** Beim 2-Datei-Konflikt schwer zu sehen, dass zwei Dateien betroffen sind (File-Switcher/Anzahl zu unauffällig). Prominentere Datei-Liste / „x von y Dateien".
|
||||
|
||||
@@ -112,6 +112,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
||||
[NotifyPropertyChangedFor(nameof(IsQueued))]
|
||||
[NotifyPropertyChangedFor(nameof(IsWaiting))]
|
||||
[NotifyPropertyChangedFor(nameof(StatusChipClass))]
|
||||
[NotifyPropertyChangedFor(nameof(CanRemoveFromQueue))]
|
||||
private string? _blockedByTaskId;
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(DependsOnTaskId))]
|
||||
@@ -276,7 +277,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
||||
&& !HasInteractiveSession;
|
||||
public bool IsQueued => Status == TaskStatus.Queued && string.IsNullOrEmpty(BlockedByTaskId);
|
||||
public bool IsWaiting => Status == TaskStatus.Queued && !string.IsNullOrEmpty(BlockedByTaskId);
|
||||
public bool CanRemoveFromQueue => IsQueued || HasQueuedSubtasks;
|
||||
public bool CanRemoveFromQueue => IsQueued || IsWaiting || HasQueuedSubtasks;
|
||||
// "Send to queue" is the single queue entry. On a finalized planning parent it queues the
|
||||
// plan (children) via CanQueuePlan; an Active (not-yet-finalized) planning parent is hidden —
|
||||
// it must be finalized first. The worker never sees a UI-hosted ConPTY session (it never
|
||||
|
||||
@@ -430,6 +430,17 @@ public sealed class TaskStateService : ITaskStateService
|
||||
if (affected == 0)
|
||||
return new TransitionResult(false, "Task is not queued; cannot remove from queue.");
|
||||
|
||||
// A dequeued task may have been a planning-chain predecessor. Without unblocking its
|
||||
// successor here, the successor is left permanently wedged on a BlockedByTaskId link to
|
||||
// a task that has left the queue and will never reach the terminal state that would
|
||||
// otherwise resolve it (PlanningChainCoordinator.OnChildFinishedAsync).
|
||||
var successorId = await ctx.Tasks.AsNoTracking()
|
||||
.Where(t => t.BlockedByTaskId == taskId)
|
||||
.Select(t => t.Id)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
if (successorId is not null)
|
||||
await UnblockAsync(successorId, ct);
|
||||
|
||||
await NotifyAsync(taskId);
|
||||
return new TransitionResult(true, null);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,23 @@ public class TaskRowContextMenuTests
|
||||
Assert.NotNull(row.SendToQueueDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRemoveFromQueue_True_When_Queued_And_Blocked()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Queued;
|
||||
row.BlockedByTaskId = "predecessor-id";
|
||||
Assert.True(row.CanRemoveFromQueue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRemoveFromQueue_False_When_Idle()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
Assert.False(row.CanRemoveFromQueue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelDisabledReason_Null_While_Running()
|
||||
{
|
||||
|
||||
@@ -585,6 +585,23 @@ public sealed class TaskStateServiceTests : IDisposable
|
||||
Assert.Equal(TaskStatus.Running, await GetStatusAsync(id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DequeueToIdleAsync_MidChain_UnblocksSuccessor()
|
||||
{
|
||||
// pred (queued, blocked) is removed from the queue in the middle of a chain;
|
||||
// successor was blocked on pred and must not be left wedged forever.
|
||||
var pred = await SeedTaskAsync(TaskStatus.Queued);
|
||||
var id = await SeedTaskAsync(TaskStatus.Queued, blockedBy: pred);
|
||||
var successor = await SeedTaskAsync(TaskStatus.Queued, blockedBy: id);
|
||||
|
||||
var result = await _sut.DequeueToIdleAsync(id, default);
|
||||
|
||||
Assert.True(result.Ok);
|
||||
var successorTask = await GetTaskAsync(successor);
|
||||
Assert.Equal(TaskStatus.Queued, successorTask.Status);
|
||||
Assert.Null(successorTask.BlockedByTaskId);
|
||||
}
|
||||
|
||||
// ─── StartPlanningAsync ───────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -281,6 +281,7 @@ public class TaskRowViewModelNotificationsTests
|
||||
nameof(TaskRowViewModel.IsQueued),
|
||||
nameof(TaskRowViewModel.IsWaiting),
|
||||
nameof(TaskRowViewModel.StatusChipClass),
|
||||
nameof(TaskRowViewModel.CanRemoveFromQueue),
|
||||
}, raised);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user