From 229d4bbb2bf3ba648844b2968054a0914da3f0b6 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 23 Apr 2026 18:36:55 +0200 Subject: [PATCH] feat(ui): TaskRowViewModel gains planning hierarchy flags Adds ParentTaskId, IsExpanded, IsChild, IsPlanningParent, IsDraft, and PlanningBadge to TaskRowViewModel with property-changed notifications. Co-Authored-By: Claude Sonnet 4.6 --- .../ViewModels/Islands/TaskRowViewModel.cs | 19 ++++++++ .../UiVm/TaskRowViewModelPlanningTests.cs | 45 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 tests/ClaudeDo.Worker.Tests/UiVm/TaskRowViewModelPlanningTests.cs diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs index 2fe8ddc..f147261 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs @@ -23,6 +23,8 @@ public sealed partial class TaskRowViewModel : ViewModelBase [ObservableProperty] private int _diffDeletions; [ObservableProperty] private bool _dropHintAbove; [ObservableProperty] private bool _dropHintBelow; + [ObservableProperty] private string? _parentTaskId; + [ObservableProperty] private bool _isExpanded = true; public DateTime CreatedAt { get; init; } public string CreatedAtFormatted => CreatedAt == default ? "—" : $"Created {CreatedAt:MMM d}"; @@ -31,6 +33,17 @@ public sealed partial class TaskRowViewModel : ViewModelBase public int StepsCount { get; init; } public int StepsCompleted { get; init; } + public bool IsChild => !string.IsNullOrEmpty(ParentTaskId); + public bool IsPlanningParent => Status == TaskStatus.Planning || Status == TaskStatus.Planned; + public bool IsDraft => Status == TaskStatus.Draft; + + public string? PlanningBadge => Status switch + { + TaskStatus.Planning => "PLANNING", + TaskStatus.Planned => "PLANNED", + _ => null, + }; + public bool HasBranch => !string.IsNullOrWhiteSpace(Branch); public bool HasDiff => DiffAdditions > 0 || DiffDeletions > 0; public bool HasTags => Tags.Count > 0; @@ -60,8 +73,13 @@ public sealed partial class TaskRowViewModel : ViewModelBase OnPropertyChanged(nameof(IsRunning)); OnPropertyChanged(nameof(IsQueued)); OnPropertyChanged(nameof(HasLiveTail)); + OnPropertyChanged(nameof(IsPlanningParent)); + OnPropertyChanged(nameof(PlanningBadge)); + OnPropertyChanged(nameof(IsDraft)); } + partial void OnParentTaskIdChanged(string? value) => OnPropertyChanged(nameof(IsChild)); + partial void OnBranchChanged(string? value) => OnPropertyChanged(nameof(HasBranch)); partial void OnLiveTailChanged(string? value) => OnPropertyChanged(nameof(HasLiveTail)); partial void OnDoneChanged(bool value) => OnPropertyChanged(nameof(IsOverdue)); @@ -91,6 +109,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase DiffAdditions = add, DiffDeletions = del, CreatedAt = t.CreatedAt, + ParentTaskId = t.ParentTaskId, }; } diff --git a/tests/ClaudeDo.Worker.Tests/UiVm/TaskRowViewModelPlanningTests.cs b/tests/ClaudeDo.Worker.Tests/UiVm/TaskRowViewModelPlanningTests.cs new file mode 100644 index 0000000..8ac6d38 --- /dev/null +++ b/tests/ClaudeDo.Worker.Tests/UiVm/TaskRowViewModelPlanningTests.cs @@ -0,0 +1,45 @@ +using ClaudeDo.Data.Models; +using ClaudeDo.Ui.ViewModels.Islands; +using Xunit; +using TaskStatus = ClaudeDo.Data.Models.TaskStatus; + +namespace ClaudeDo.Worker.Tests.UiVm; + +public class TaskRowViewModelPlanningTests +{ + private static TaskRowViewModel MakeRow(TaskStatus status, string? parentTaskId = null) + => new TaskRowViewModel { Id = "t", Status = status, ParentTaskId = parentTaskId }; + + [Fact] + public void Draft_Status_SetsIsChildFlag_WhenParentIdIsNotNull() + { + var vm = MakeRow(TaskStatus.Draft, "parent-id"); + Assert.True(vm.IsChild); + Assert.False(vm.IsPlanningParent); + } + + [Fact] + public void Planning_Status_SetsIsPlanningParent() + { + var vm = MakeRow(TaskStatus.Planning); + Assert.True(vm.IsPlanningParent); + Assert.False(vm.IsChild); + Assert.Equal("PLANNING", vm.PlanningBadge); + } + + [Fact] + public void Planned_Status_ShowsPlannedBadge() + { + var vm = MakeRow(TaskStatus.Planned); + Assert.True(vm.IsPlanningParent); + Assert.Equal("PLANNED", vm.PlanningBadge); + } + + [Fact] + public void NonPlanningStatus_NoBadge() + { + var vm = MakeRow(TaskStatus.Manual); + Assert.False(vm.IsPlanningParent); + Assert.Null(vm.PlanningBadge); + } +}