summaryrefslogtreecommitdiff
path: root/docs/superpowers/specs/2026-07-15-linear-task-chains-design.md
blob: 98198b242bb98180e291306b79cb630b07764fa1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# Linear Task Chains with WIP Limits — Design

**Status:** Implemented 2026-07-17. Backend (migration 026, store, HTTP API), web timeline position badge, and Android widget data layer + row badge all shipped with tests (`go test ./...` green). See `docs/superpowers/plans/2026-07-17-linear-task-chains.md` for the task breakdown. Not built: a dedicated Android checklist screen for browsing a full chain (locked + unlocked) -- the row-level badge and `GET /api/widget/chains/{id}` endpoint exist, but the standalone browsable view described in "Visibility" below is deferred, consistent with this spec's own "Web UI specifics for the chain/checklist view" being out of scope.

## Context

A chain is a fixed, ordered sequence of tasks where only one is ever actionable at a time — the next task appears only once its predecessor is finished. Use cases per the interview: training progressions, goal-oriented projects, test/cert prep. This is a curriculum/checklist model, not a general dependency graph.

**Depends on:** [[doot-future-task-scheduling-ideas]] item 2 (task labels/projects) — per the interview, a chain is "effectively a project with strict sequential unlocking," reusing the Projects concept rather than inventing a parallel grouping mechanism.

## Data Model

```sql
CREATE TABLE task_chains (
    id TEXT PRIMARY KEY,
    project_id TEXT NOT NULL,        -- one project per chain (see labels-and-projects spec)
    status TEXT DEFAULT 'active',    -- 'active', 'paused', 'abandoned', 'completed'
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

ALTER TABLE native_tasks ADD COLUMN chain_id TEXT DEFAULT '';
ALTER TABLE native_tasks ADD COLUMN chain_position INTEGER DEFAULT 0;   -- 0-indexed order within the chain
ALTER TABLE native_tasks ADD COLUMN chain_unlocked BOOLEAN DEFAULT 0;   -- true only for the single active task
```

WIP limit of exactly 1 (per the interview, not configurable): at most one `native_tasks` row per `chain_id` ever has `chain_unlocked = true`. Locked tasks (`chain_unlocked = false`) have no `due_date` — same "no date until it's actionable" convention as the maintenance-buckets spec's dormant items.

Strictly linear, no branching (per the interview): `chain_position` is a simple integer ordering, and advancement always moves to `position + 1`.

## Advancement Mechanism

Completing the currently-unlocked task (via the existing `CompleteNativeTask` path) checks `chain_id`: if set, it finds the task at `chain_position + 1` in the same chain and flips it from locked to unlocked, setting its `due_date` at that point (e.g. `due_date = now`, since a chain task becomes due the moment it's unlocked — there's no cycle-length concept here the way buckets have one). If there is no `chain_position + 1` (the completed task was the last), the chain's `status` flips to `'completed'` instead — chains always terminate, per the interview, no loop-back.

## Visibility

Per the interview: **hidden in timelines, visible in the tasks list**.
- `BuildTimeline`/`GetNativeTasksByDateRange`/the widget's `/api/widget` response exclude any task with `chain_id != '' AND chain_unlocked = 0` — locked tasks have no due date and aren't schedule-relevant, so they don't appear in any day-based view (web Today/Tomorrow, widget grid).
- A separate, new "chain view" (`GET /api/widget/chains/{id}`) lists every task in the chain in order, locked and unlocked, so the full checklist is browsable — this is the "visible in the tasks list" requirement, met via a dedicated surface rather than the normal timeline.

## Pause / Abandon

Per the interview, a chain can be paused or abandoned partway through:
- `'paused'`: the currently-unlocked task (if any) stays visible/actionable, but completing it does **not** auto-advance while paused — the chain must be explicitly resumed (`status` back to `'active'`) for the next task to unlock. This lets you finish what's in front of you without accidentally kicking off the next step of a chain you're stepping away from.
- `'abandoned'`: a terminal state (like `'completed'` for bookkeeping purposes, but distinguishable in queries/reporting as "didn't finish").

## API

- `POST /api/widget/chains` — create a chain: name (creates its backing project automatically) + an ordered list of task titles/descriptions to seed every position. Position 0 is created unlocked with `due_date = now`; the rest are created locked with no due date.
- `POST /api/widget/chains/{id}/pause`, `/resume`, `/abandon`.
- `GET /api/widget/chains/{id}` — full ordered list (locked + unlocked) for the checklist view.
- No new completion endpoint — `POST /api/widget/task/update`'s sibling `HandleWidgetComplete`/`CompleteNativeTask` gets extended to check `chain_id` and advance, the same way it already checks `recurrence_series_id` and creates a next iteration.

## Testing

Store-layer tests for: creating a chain seeds positions correctly (only position 0 unlocked/dated); completing an unlocked task advances to the next position; completing the last position marks the chain completed instead of advancing; pausing prevents auto-advance on completion, resuming re-enables it; locked tasks are excluded from date-range queries. Handler tests for the new endpoints mirroring existing widget-handler test style.

## Out of Scope

- Branching/non-linear chains, reordering after creation — strictly linear and fixed, per the interview.
- Configurable WIP limits above 1 — always exactly 1, per the interview.
- Looping chains for ongoing practice — always terminates, per the interview.
- Interaction with maintenance buckets ([[doot-future-task-scheduling-ideas]] item 3) — independent concepts, not designed to compose.
- Web UI specifics for the chain/checklist view.