fix(claude-do): merge ScheduledFor als UTC speichern — geplante Tasks starten aktu

ClaudeDo-Task: ea2b5950-baff-4631-ad0d-6c156a01267e
This commit is contained in:
Mika Kuns
2026-08-26 15:46:26 +02:00
4 changed files with 31 additions and 5 deletions
@@ -121,7 +121,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase
public bool HasBranch => !string.IsNullOrWhiteSpace(Branch);
public bool HasDiff => DiffAdditions > 0 || DiffDeletions > 0;
public bool HasSteps => StepsCount > 0;
public bool IsOverdue => ScheduledFor is { } d && d.Date < DateTime.Today && !Done;
public bool IsOverdue => ScheduledFor is { } d && d.ToLocalTime().Date < DateTime.Today && !Done;
public bool IsRunning => Status == TaskStatus.Running;
public bool IsWaitingForReview => Status == TaskStatus.WaitingForReview;
// Parked = set aside from review: Idle but still holding its Active worktree (vs a plain Idle
@@ -637,7 +637,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
if (r.Done && !underOpenPlanningParent)
completed.Add(r);
else if (r.ScheduledFor is { } d && d.Date < today)
else if (r.ScheduledFor is { } d && d.ToLocalTime().Date < today)
overdue.Add(r);
else
open.Add(r);
@@ -1361,12 +1361,18 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
public async Task SetScheduledForAsync(TaskRowViewModel row, DateTime? when)
{
if (row is null) return;
// ThemedDatePicker builds Unspecified/Local wall-clock values; the DB's UtcConverter only
// tags DateTimes as Utc on read, it never converts on write, so this is the one place that
// must turn "local wall clock" into a real UTC instant before it's persisted.
var whenUtc = when is { } w
? (w.Kind == DateTimeKind.Utc ? w : DateTime.SpecifyKind(w, DateTimeKind.Local).ToUniversalTime())
: (DateTime?)null;
await using var db = await _dbFactory.CreateDbContextAsync();
var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
if (entity is null) return;
entity.ScheduledFor = when;
entity.ScheduledFor = whenUtc;
await db.SaveChangesAsync();
row.ScheduledFor = when;
row.ScheduledFor = whenUtc;
Regroup();
UpdateSubtitle();
TasksChanged?.Invoke(this, EventArgs.Empty);
@@ -217,7 +217,7 @@ public partial class TaskRowView : UserControl
{
if (DataContext is not TaskRowViewModel row) return;
_pendingScheduleRow = row;
ScheduleDate.SelectedDate = row.ScheduledFor ?? DateTime.Now.AddHours(1);
ScheduleDate.SelectedDate = row.ScheduledFor?.ToLocalTime() ?? DateTime.Now.AddHours(1);
ScheduleAnchor.Flyout?.ShowAt(ScheduleAnchor);
}
@@ -346,4 +346,24 @@ public class TasksIslandRegroupTests : IDisposable
Assert.Equal(new[] { "t2", "t3", "t1" }, vm.Items.Select(r => r.Id));
Assert.Equal(new[] { "t2", "t3", "t1" }, vm.Rows.OfType<TaskRowViewModel>().Select(r => r.Id));
}
[Fact]
public async Task SetScheduledForAsync_LocalDateTime_PersistsAsUtc()
{
await SeedTasksAsync(("t1", TaskStatus.Idle, null, 0));
var vm = BuildViewModel();
await LoadAndWaitAsync(vm, UserList("list1", "Default"));
var row = vm.Items.First(r => r.Id == "t1");
// ThemedDatePicker hands SetScheduledForAsync Local/Unspecified wall-clock values.
var localWhen = DateTime.SpecifyKind(new DateTime(2026, 8, 26, 15, 0, 0), DateTimeKind.Local);
await vm.SetScheduledForAsync(row, localWhen);
await using var db = NewContext();
var entity = await db.Tasks.FirstAsync(t => t.Id == "t1");
Assert.Equal(DateTimeKind.Utc, entity.ScheduledFor!.Value.Kind);
Assert.Equal(localWhen.ToUniversalTime(), entity.ScheduledFor!.Value);
}
}