Files
ClaudeDo/docs/superpowers/specs/2026-04-16-subtask-tree-view-design.md
mika kuns 4f25c3dd40 docs: add subtask tree view design spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 10:18:04 +02:00

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 expand
  • bool IsExpanded — observable, default false; toggles subtask visibility
  • bool HasSubtasks — observable, set during initial load from a count query
  • int SubtaskCount — observable, used for the indicator
  • ToggleExpandedCommand — flips IsExpanded; on first expand, loads subtasks from SubtaskRepository.GetByTaskIdAsync
  • ToggleSubtaskDoneCommand(string subtaskId) — toggles a subtask's Completed and persists via SubtaskRepository.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 Path data) before the status circle
  • Right-pointing when collapsed, down-pointing when expanded
  • Bound to ToggleExpandedCommand
  • Only visible when HasSubtasks is true (via IsVisible binding)
  • When HasSubtasks is false, the space is empty but reserved (fixed-width column) so all titles align

Row 1 — Subtask list:

  • ItemsControl bound to Subtasks
  • IsVisible bound to IsExpanded
  • Left margin ~40px for visual indentation
  • Each subtask item: CheckBox (bound to Completed) + TextBlock (bound to Title)
  • Subtask row has its own context menu flyout with "Edit Task" (opens parent task's editor modal via EditTaskCommand on root TaskListViewModel)
  • Checkbox toggle calls ToggleSubtaskDoneCommand on the parent TaskItemViewModel

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:

  1. Update the SubtaskItemViewModel.Completed property
  2. Call SubtaskRepository.UpdateAsync with the updated entity (same auto-save pattern as TaskDetailView)
  3. 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):

  1. Reload subtask count, update HasSubtasks and SubtaskCount
  2. 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, TextDimBrush color, no background, cursor=Hand
  • Subtask rows: smaller font (12px), TextDimBrush for 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

  1. src/ClaudeDo.Data/Repositories/SubtaskRepository.cs — add GetCountsByTaskIdsAsync
  2. src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs — add subtask collection, expand/collapse, toggle done
  3. src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs — batch-load counts, pass SubtaskRepository, extend refresh
  4. src/ClaudeDo.Ui/Views/TaskListView.axaml — restructure item template with chevron + nested ItemsControl
  5. src/ClaudeDo.Ui/Views/TaskListView.axaml.cs — handle subtask context menu pointer-pressed if needed
  6. src/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)