fix(ui): prevent dragging completed tasks in the task list

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-10-01 11:44:17 +02:00
co-authored by Claude Sonnet 5.5
parent 2b7b58be37
commit b776fd28d8
3 changed files with 27 additions and 5 deletions
@@ -1009,6 +1009,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
{
if (!CanReorder || _currentList is null) return;
if (source.IsRunning || target.IsRunning) return;
if (source.Done || target.Done) return;
if (ReferenceEquals(source, target)) return;
// Master Items: single Move event (no Reset) so ItemsControls animate, not rebuild.
@@ -94,8 +94,8 @@ public partial class TasksIslandView : UserControl
&& parentVisual.FindAncestorOfType<Button>() is not null;
if (nestedInsideButton) return;
// Running tasks can be neither reordered nor re-queued.
if (row.IsRunning) return;
// Running and completed tasks can be neither reordered, moved nor re-queued.
if (row.IsRunning || row.Done) return;
// Arm the drag for ANY list kind so drag-to-queue works everywhere; reorder-on-drop is
// still gated on CanReorder (user lists only).
@@ -173,7 +173,7 @@ public partial class TasksIslandView : UserControl
{
var targetButton = RowButtonAt(e, topLevel);
if (targetButton?.DataContext is TaskRowViewModel target
&& !ReferenceEquals(target, row) && !target.IsRunning)
&& !ReferenceEquals(target, row) && !target.IsRunning && !target.Done)
{
var placeBelow = e.GetPosition(targetButton).Y > targetButton.Bounds.Height / 2;
await vm.ReorderAsync(row, target, placeBelow);
@@ -223,7 +223,7 @@ public partial class TasksIslandView : UserControl
var targetButton = RowButtonAt(e, topLevel);
if (targetButton?.DataContext is not TaskRowViewModel target
|| target.IsRunning || ReferenceEquals(target, _pressRow))
|| target.IsRunning || target.Done || ReferenceEquals(target, _pressRow))
{
vm.ClearDropHints();
return;
@@ -238,7 +238,7 @@ public partial class TasksIslandView : UserControl
if (placeBelow)
{
var next = FindNextInSameSection(vm, target);
if (next is not null && !next.IsRunning) { hintRow = next; hintBelow = false; }
if (next is not null && !next.IsRunning && !next.Done) { hintRow = next; hintBelow = false; }
else { hintRow = target; hintBelow = true; }
}
@@ -347,6 +347,27 @@ public class TasksIslandRegroupTests : IDisposable
Assert.Equal(new[] { "t2", "t3", "t1" }, vm.Rows.OfType<TaskRowViewModel>().Select(r => r.Id));
}
[Fact]
public async Task ReorderAsync_WithDoneSourceOrTarget_IsNoOp()
{
await SeedTasksAsync(
("t1", TaskStatus.Idle, null, 0),
("t2", TaskStatus.Idle, null, 1),
("t3", TaskStatus.Done, null, 2));
var vm = BuildViewModel();
await LoadAndWaitAsync(vm, UserList("list1", "Default"));
var t1 = vm.Items.First(r => r.Id == "t1");
var t3 = vm.Items.First(r => r.Id == "t3");
var before = vm.Items.Select(r => r.Id).ToList();
await vm.ReorderAsync(t1, t3, placeBelow: true);
await vm.ReorderAsync(t3, t1, placeBelow: false);
Assert.Equal(before, vm.Items.Select(r => r.Id));
}
[Fact]
public async Task SetScheduledForAsync_LocalDateTime_PersistsAsUtc()
{