# 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.