4.1 KiB
Dependency chain display — Design
Date: 2026-08-11 Status: approved, not implemented
Problem
DependsOnTaskId is invisible in the UI. TaskRowViewModel has no DependsOnTaskId property
and TaskRowView.axaml renders nothing for it — a chained task looks exactly like an unrelated
one. The execution order the user declared is not readable anywhere in the list.
Target
A chain renders as a group: the head full width, its dependents indented behind a vertical rail, each carrying a small circular step badge on the rail.
┌──────────────────────────────────────────┐
│ chain head (full width, no badge) │
└──────────────────────────────────────────┘
╷ ┌────────────────────────────────────┐
(1)│ first dependent │
╷ └────────────────────────────────────┘
╷ ┌────────────────────────────────────┐
(2)│ second │
╷ └────────────────────────────────────┘
╷ ┌────────────────────────────────────┐
(2)│ parallel to the second │
╷ └────────────────────────────────────┘
╷ ┌────────────────────────────────────┐
(3)│ third │
└────────────────────────────────────┘
Model reading
- Step number = hop distance from the chain head, not a running counter.
DependsOnTaskIdis a single FK, so several tasks may share one predecessor → same depth → same number. Two rows showing2means "these two are both unblocked by step 1", which is the intended reading. - Chain head = a task with no
DependsOnTaskIdthat at least one other task points at. A task with no dependents and no dependency is not a chain and renders exactly as today. - Cycles are impossible (
TaskStateService.SetDependsOnAsyncrejects them), so the depth walk always terminates. Still cap the walk defensively.
Decisions
One indent level only — parent wins. The 24 px indent track already exists for planning
children (TaskRowView.axaml:22-28, gated on ShowAsChild). A planning child that also has a
DependsOnTaskId keeps the parent indent and shows its chain membership as a small inline
chip (after #123) instead of a second indent level. No nesting, no 48 px rows.
The group is pulled together; the head carries it. Chain members are re-ordered to sit
directly under their head regardless of SortOrder, so displayed order always equals execution
order. Dragging the head moves the whole group; members are not individually draggable.
Head not in view → no orphan rails. Exact precedent exists: ParentInView /ShowAsChild
(TaskRowViewModel.cs:75, computed in Regroup at TasksIslandViewModel.cs:543). Same
treatment — if the head is filtered out (other list, My Day, completed group), the row renders
flat with the after #123 chip instead of a dangling rail.
Badge has no #. If task numbers (2026-08-11-task-numbers-design.md) also land, a row
would show a rail badge and a #412 title prefix. The rail badge is a step position, not an
identity — render it bare (2), never #2.
Slices
| # | Slice | Depends on |
|---|---|---|
| 1 | VM: DependsOnTaskId + depth/head computation in Regroup, group pull-together, drag semantics |
— |
| 2 | View: rail, step badge, after … chip, locale keys |
1 |
Slice 1 exposes the contract Slice 2 binds to: ShowAsChainMember, ChainStep,
ChainAfterLabel.