# 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. `DependsOnTaskId` is
  a single FK, so several tasks may share one predecessor → same depth → **same number**. Two
  rows showing `2` means "these two are both unblocked by step 1", which is the intended reading.
- **Chain head** = a task with no `DependsOnTaskId` that 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.SetDependsOnAsync` rejects 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`.
