feat(ui): My Day actions, orphan-aware grouping, menu restructure

Pending UI work:
- My Day add/remove context actions on task rows (parent removal cascades to children)
- orphan-aware grouping: a child whose parent isn't in view renders as a top-level row, not an indented draft
- shell menu restructure (Worker / Repositories submenus); 'Finalize plan' action, drop 'Queue subtasks sequentially'
- notes editor refinements
- subtask-row hover tweak (Surface3, no transition)
- bump Avalonia 12.0.0 -> 12.0.4
This commit is contained in:
Mika Kuns
2026-06-18 16:22:29 +02:00
parent 43fb506e87
commit 4847c5c0a4
19 changed files with 384 additions and 58 deletions
@@ -31,6 +31,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase
[ObservableProperty] private bool _hasQueuedSubtasks;
[ObservableProperty] private bool _showListChip = true;
[ObservableProperty] private bool _parentFinalized;
[ObservableProperty] private bool _parentInView = true;
[ObservableProperty] private int _roadblockCount;
[ObservableProperty] private bool _isRefining;
@@ -46,9 +47,13 @@ public sealed partial class TaskRowViewModel : ViewModelBase
public bool IsAgentSuggested => IsChild && !string.IsNullOrEmpty(CreatedBy) && CreatedBy == ParentTaskId;
public bool IsPlanningParent => PlanningPhase != PlanningPhase.None
|| HasPlanningChildren;
// A child only reads as a child while its parent shares the current view. When the parent is
// absent (removed from My Day, or daily-prep placed a lone child there), the row renders as a
// normal top-level task instead of an orphaned, indented Draft.
public bool ShowAsChild => IsChild && ParentInView;
// A subtask is Draft until its planning parent is finalized, then Planned (queueable).
public bool IsDraft => IsChild && Status == TaskStatus.Idle && !ParentFinalized;
public bool IsPlanned => IsChild && Status == TaskStatus.Idle && ParentFinalized;
public bool IsDraft => ShowAsChild && Status == TaskStatus.Idle && !ParentFinalized;
public bool IsPlanned => ShowAsChild && Status == TaskStatus.Idle && ParentFinalized;
public bool CanOpenPlanningSession => Status == TaskStatus.Idle
&& PlanningPhase == PlanningPhase.None
@@ -74,13 +79,23 @@ public sealed partial class TaskRowViewModel : ViewModelBase
public bool IsQueued => Status == TaskStatus.Queued && string.IsNullOrEmpty(BlockedByTaskId);
public bool IsWaiting => Status == TaskStatus.Queued && !string.IsNullOrEmpty(BlockedByTaskId);
public bool CanRemoveFromQueue => IsQueued || 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.
public bool CanSendToQueue => !IsRunning && !IsQueued && !IsWaitingForReview && !HasQueuedSubtasks
&& (!IsChild || ParentFinalized);
&& (!IsChild || ParentFinalized)
&& PlanningPhase != PlanningPhase.Active;
// Parent-level "send plan to queue" — only once the plan is finalized (children Planned).
// Drives the routing inside SendToQueue, not a separate menu entry.
public bool CanQueuePlan => !IsChild && HasPlanningChildren
&& PlanningPhase == PlanningPhase.Finalized
&& !HasQueuedSubtasks;
// User-triggered finalize for a planning parent whose session was closed before finalizing.
public bool CanFinalizePlanning => PlanningPhase == PlanningPhase.Active && !IsChild;
public bool HasSchedule => ScheduledFor.HasValue;
// "Add to My Day" — shown on any task not already in My Day; a Done task has no place in
// today's focus list. The mirror of "Remove from My Day" (gated on IsMyDay).
public bool CanAddToMyDay => !IsMyDay && !Done;
public bool HasRoadblock => RoadblockCount > 0;
public string RoadblockTooltip => RoadblockCount == 1
? "1 roadblock reported during the run — see details"
@@ -135,12 +150,20 @@ public sealed partial class TaskRowViewModel : ViewModelBase
{
OnPropertyChanged(nameof(IsChild));
OnPropertyChanged(nameof(IsAgentSuggested));
OnPropertyChanged(nameof(ShowAsChild));
OnPropertyChanged(nameof(IsDraft));
OnPropertyChanged(nameof(IsPlanned));
OnPropertyChanged(nameof(CanSendToQueue));
OnPropertyChanged(nameof(CanOpenPlanningSession));
}
partial void OnParentInViewChanged(bool value)
{
OnPropertyChanged(nameof(ShowAsChild));
OnPropertyChanged(nameof(IsDraft));
OnPropertyChanged(nameof(IsPlanned));
}
partial void OnCreatedByChanged(string? value) => OnPropertyChanged(nameof(IsAgentSuggested));
partial void OnParentFinalizedChanged(bool value)
@@ -159,6 +182,8 @@ public sealed partial class TaskRowViewModel : ViewModelBase
OnPropertyChanged(nameof(CanOpenPlanningSession));
OnPropertyChanged(nameof(CanResumeOrDiscardPlanning));
OnPropertyChanged(nameof(CanQueuePlan));
OnPropertyChanged(nameof(CanSendToQueue));
OnPropertyChanged(nameof(CanFinalizePlanning));
OnPropertyChanged(nameof(CanRefine));
}
@@ -185,7 +210,12 @@ public sealed partial class TaskRowViewModel : ViewModelBase
}
partial void OnBranchChanged(string? value) => OnPropertyChanged(nameof(HasBranch));
partial void OnDoneChanged(bool value) => OnPropertyChanged(nameof(IsOverdue));
partial void OnDoneChanged(bool value)
{
OnPropertyChanged(nameof(IsOverdue));
OnPropertyChanged(nameof(CanAddToMyDay));
}
partial void OnIsMyDayChanged(bool value) => OnPropertyChanged(nameof(CanAddToMyDay));
partial void OnScheduledForChanged(DateTime? value)
{
OnPropertyChanged(nameof(IsOverdue));