diff options
Diffstat (limited to 'docs/superpowers/specs/2026-07-15-linear-task-chains-design.md')
| -rw-r--r-- | docs/superpowers/specs/2026-07-15-linear-task-chains-design.md | 63 |
1 files changed, 63 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-07-15-linear-task-chains-design.md b/docs/superpowers/specs/2026-07-15-linear-task-chains-design.md new file mode 100644 index 0000000..a66d828 --- /dev/null +++ b/docs/superpowers/specs/2026-07-15-linear-task-chains-design.md @@ -0,0 +1,63 @@ +# Linear Task Chains with WIP Limits — Design + +**Status:** Backlog idea, refined via interview 2026-07-15. Not yet approved for implementation. + +## 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. |
