feat(ui): planning commands and expand/collapse in TasksIslandViewModel

- Add IWorkerClient interface; WorkerClient implements it
- TasksIslandViewModel accepts IWorkerClient? and gains OpenPlanningSession,
  ResumePlanningSession, DiscardPlanningSession, FinalizePlanningSession,
  and ToggleExpand commands
- Regroup() is hierarchy-aware: children of collapsed planning parents are hidden
- InternalsVisibleTo ClaudeDo.Worker.Tests for Regroup()
- 4 new unit tests covering collapse/expand and guard logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-04-23 18:51:22 +02:00
parent 00608401aa
commit 309f84b388
5 changed files with 219 additions and 5 deletions

View File

@@ -13,7 +13,8 @@ namespace ClaudeDo.Ui.ViewModels.Islands;
public sealed partial class TasksIslandViewModel : ViewModelBase
{
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
private readonly WorkerClient? _worker;
private readonly IWorkerClient? _worker;
private readonly Dictionary<string, bool> _expandedState = new();
private ListNavItemViewModel? _currentList;
private CancellationTokenSource? _loadCts;
@@ -41,7 +42,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
[ObservableProperty] private bool _showOpenLabel;
[ObservableProperty] private string _completedHeader = "COMPLETED";
public TasksIslandViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, WorkerClient? worker = null)
public TasksIslandViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, IWorkerClient? worker = null)
{
_dbFactory = dbFactory;
_worker = worker;
@@ -105,14 +106,35 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
catch (OperationCanceledException) { }
}
private void Regroup()
internal void Regroup()
{
OverdueItems.Clear();
OpenItems.Clear();
CompletedItems.Clear();
var today = DateTime.Today;
// Restore IsExpanded from saved state
foreach (var r in Items)
{
if (_expandedState.TryGetValue(r.Id, out var saved))
r.IsExpanded = saved;
}
// Build hierarchy-aware flat list: top-level rows interleaved with visible children.
// Items is already ordered by SortOrder from the DB query.
var topLevel = Items.Where(r => !r.IsChild);
var flat = new List<TaskRowViewModel>();
foreach (var parent in topLevel)
{
flat.Add(parent);
if (parent.IsPlanningParent && parent.IsExpanded)
{
var children = Items.Where(r => r.ParentTaskId == parent.Id);
flat.AddRange(children);
}
}
var today = DateTime.Today;
foreach (var r in flat)
{
if (r.Done)
CompletedItems.Add(r);
@@ -356,6 +378,48 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
[RelayCommand]
private void OpenListSettings() => OpenListSettingsRequested?.Invoke(this, EventArgs.Empty);
[RelayCommand]
private async Task OpenPlanningSessionAsync(TaskRowViewModel? row)
{
if (row is null || row.Status != TaskStatus.Manual) return;
try { await _worker!.StartPlanningSessionAsync(row.Id); }
catch { }
}
[RelayCommand]
private async Task ResumePlanningSessionAsync(TaskRowViewModel? row)
{
if (row is null || !row.IsPlanningParent) return;
try { await _worker!.ResumePlanningSessionAsync(row.Id); }
catch { }
}
[RelayCommand]
private async Task DiscardPlanningSessionAsync(TaskRowViewModel? row)
{
if (row is null) return;
try { await _worker!.DiscardPlanningSessionAsync(row.Id); }
catch { }
}
[RelayCommand]
private async Task FinalizePlanningSessionAsync(TaskRowViewModel? row)
{
if (row is null) return;
try { await _worker!.FinalizePlanningSessionAsync(row.Id, queueAgentTasks: true); }
catch { }
}
[RelayCommand]
private void ToggleExpand(TaskRowViewModel? row)
{
if (row is null) return;
var next = !(_expandedState.TryGetValue(row.Id, out var current) ? current : row.IsExpanded);
_expandedState[row.Id] = next;
row.IsExpanded = next;
Regroup();
}
partial void OnSelectedTaskChanged(TaskRowViewModel? value)
{
foreach (var i in Items) i.IsSelected = ReferenceEquals(i, value);