5.8 KiB
Subtask Tree View in Task List
Problem
Subtasks are invisible in the task list — users only see them after opening the detail pane or editor modal. This makes it hard to get an overview of task progress without clicking into each task individually.
Solution
Show subtasks indented below their parent task in the task list, with expand/collapse. Tasks start collapsed with a visual indicator when subtasks exist.
Scope
Pure UI/ViewModel change. No data model changes, no new migrations, no repository schema changes.
Design
ViewModel Changes
TaskItemViewModel — add:
ObservableCollection<SubtaskItemViewModel> Subtasks— populated on first expandbool IsExpanded— observable, defaultfalse; toggles subtask visibilitybool HasSubtasks— observable, set during initial load from a count queryint SubtaskCount— observable, used for the indicatorToggleExpandedCommand— flipsIsExpanded; on first expand, loads subtasks fromSubtaskRepository.GetByTaskIdAsyncToggleSubtaskDoneCommand(string subtaskId)— toggles a subtask'sCompletedand persists viaSubtaskRepository.UpdateAsync
Constructor gains SubtaskRepository and initial subtaskCount parameter.
TaskListViewModel.LoadAsync — after fetching tasks, run a single batch query to get subtask counts per task. Pass counts into each TaskItemViewModel. This avoids N+1 queries on load.
TaskListViewModel.RefreshSingleAsync — if the refreshed task's IsExpanded is true, also reload its subtasks from DB and update the collection.
Repository Changes
SubtaskRepository — add one method:
Task<Dictionary<string, int>> GetCountsByTaskIdsAsync(IEnumerable<string> taskIds, CancellationToken ct = default)
Single query: SELECT task_id, COUNT(*) FROM subtasks WHERE task_id IN (...) GROUP BY task_id. Returns a map of taskId -> count. Tasks with no subtasks won't appear in the result (count defaults to 0).
XAML Changes
TaskListView.axaml — the DataTemplate for TaskItemViewModel becomes a 2-row grid:
Row 0: [ExpandChevron] [StatusCircle] [Title + Tags/Status subtitle]
Row 1: [SubtaskItemsControl, margin-left ~40px, visible when IsExpanded]
Row 0 — Expand chevron:
- Column 0 gets a small chevron button (12x12
Pathdata) before the status circle - Right-pointing when collapsed, down-pointing when expanded
- Bound to
ToggleExpandedCommand - Only visible when
HasSubtasksis true (viaIsVisiblebinding) - When
HasSubtasksis false, the space is empty but reserved (fixed-width column) so all titles align
Row 1 — Subtask list:
ItemsControlbound toSubtasksIsVisiblebound toIsExpanded- Left margin ~40px for visual indentation
- Each subtask item:
CheckBox(bound toCompleted) +TextBlock(bound toTitle) - Subtask row has its own context menu flyout with "Edit Task" (opens parent task's editor modal via
EditTaskCommandon rootTaskListViewModel) - Checkbox toggle calls
ToggleSubtaskDoneCommandon the parentTaskItemViewModel
Column layout change: The existing 2-column Grid (Auto, *) gets a third column prepended: Auto, Auto, *. The chevron goes in column 0, status circle in column 1, title stack in column 2. Row 1 spans all 3 columns.
Subtask Checkbox Interaction
When a subtask checkbox is toggled in the list:
- Update the
SubtaskItemViewModel.Completedproperty - Call
SubtaskRepository.UpdateAsyncwith the updated entity (same auto-save pattern asTaskDetailView) - No need to refresh the parent task — subtask completion doesn't affect task status
Subtask Context Menu
Right-click on a subtask row shows:
- "Edit Task" — opens the parent task's editor modal (same flow as
EditTaskCommand)
This reuses the existing editor which already has full subtask editing (add/remove/reorder/rename).
Real-time Updates
When RefreshSingleAsync fires (via SignalR TaskUpdatedEvent):
- Reload subtask count, update
HasSubtasksandSubtaskCount - If
IsExpanded, reload subtask list from DB and reconcile with the observable collection
Detail Pane Sync
When the user edits subtasks in TaskDetailView (auto-save) or TaskEditorView (batch-save), the list view's subtask state may become stale. Two options:
Chosen approach: The detail pane and editor already trigger TaskUpdatedEvent (or the editor's save path calls RefreshSingleAsync via SelectedTask.Refresh). Extend Refresh on TaskItemViewModel to also reload subtasks if expanded, and update HasSubtasks/SubtaskCount.
Visual Style
- Chevron: 10x10 path,
TextDimBrushcolor, no background, cursor=Hand - Subtask rows: smaller font (12px),
TextDimBrushfor unchecked title, strikethrough + dimmed for completed - Subtask checkbox: standard Avalonia
CheckBox(no custom circular border), small size - Subtask row vertical padding: 2px (compact)
- Indent: 40px left margin on the subtask
ItemsControl
Files to Modify
src/ClaudeDo.Data/Repositories/SubtaskRepository.cs— addGetCountsByTaskIdsAsyncsrc/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs— add subtask collection, expand/collapse, toggle donesrc/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs— batch-load counts, pass SubtaskRepository, extend refreshsrc/ClaudeDo.Ui/Views/TaskListView.axaml— restructure item template with chevron + nested ItemsControlsrc/ClaudeDo.Ui/Views/TaskListView.axaml.cs— handle subtask context menu pointer-pressed if neededsrc/ClaudeDo.App/Program.cs— pass SubtaskRepository to TaskListViewModel (if not already available via DI)
Out of Scope
- Drag-to-reorder subtasks in the list view
- Add subtask directly from the list view
- Subtask progress indicator (e.g., "2/5 done") on collapsed tasks
- Recursive task nesting (tasks containing tasks)