diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-07-15 10:15:56 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-07-16 02:54:17 +0000 |
| commit | 56cd7195060e7ac304cba946087d5f56d5a5f187 (patch) | |
| tree | 5ed98edd83c500cf0db1c8b343e80d3b2d3e3cb4 /docs | |
| parent | f48eae22ec7510e662e0ad6a3fa02781e951af4d (diff) | |
Add design docs for four backlog task-scheduling ideas
Labels/projects, budgets/availability, recurring maintenance buckets,
and linear task chains -- refined via a 24-question interview
(2026-07-15) into concrete data models, mechanisms, and API shapes.
None approved for implementation yet; each spec notes its
dependencies on the others (labels/projects is foundational to two
of the other three).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
Diffstat (limited to 'docs')
4 files changed, 266 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. diff --git a/docs/superpowers/specs/2026-07-15-recurring-maintenance-buckets-design.md b/docs/superpowers/specs/2026-07-15-recurring-maintenance-buckets-design.md new file mode 100644 index 0000000..5072643 --- /dev/null +++ b/docs/superpowers/specs/2026-07-15-recurring-maintenance-buckets-design.md @@ -0,0 +1,70 @@ +# Recurring Maintenance Buckets — Design + +**Status:** Backlog idea, refined via interview 2026-07-15. Not yet approved for implementation. + +## Context + +For longer-term items that need doing "every so often" but don't each deserve their own due date (e.g. "clean the gutters," "check the water heater," "review the emergency kit") — a bucket holds a pool of such items, and every M days/weeks the system activates N of them as real, actionable tasks. Completing (or deferring) an active item returns it to the pool; the interview's own framing calls for "room for fanciness" in the selection. + +**Depends on:** the task-recurrence-and-detail-editing feature (already shipped) for its periodic-scheduler infrastructure, and optionally on [[doot-future-task-scheduling-ideas]] items 1 (availability) and 2 (labels) for scoring inputs, per the interview. + +## Architecture Note: Different Shape Than Due-Date Recurrence + +The existing recurrence system (shipped 2026-07-14) creates a **new row per iteration** — each occurrence is a distinct, permanent history entry. That model fits a task that repeats indefinitely on its own schedule. + +A bucket is different: it's a **finite, fixed pool of items** that get **repeatedly activated and deactivated** — the item itself doesn't need a fresh row each cycle, it needs a state flip. So this feature reuses the *scheduler infrastructure* (a periodic background check, same pattern as `scheduler.RunRecurrenceCheck`) but not the *new-row-per-iteration* mechanism. This is the "new recurrence variant layered onto the existing system" from the interview — same periodic-check shape, different iteration semantics. + +## Data Model + +```sql +CREATE TABLE maintenance_buckets ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + cycle_days INTEGER NOT NULL, -- M, expressed in days (a "weekly" bucket = 7, "every 3 weeks" = 21) + pick_n INTEGER NOT NULL, -- N items activated per cycle + last_cycle_at DATETIME, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE native_tasks ADD COLUMN bucket_id TEXT DEFAULT ''; +ALTER TABLE native_tasks ADD COLUMN bucket_state TEXT DEFAULT ''; -- '', 'dormant', 'active' +ALTER TABLE native_tasks ADD COLUMN bucket_last_active_at DATETIME; -- drives staleness scoring +``` + +Bucket items are ordinary `native_tasks` rows (so they appear in existing lookups/UI naturally) with `bucket_id` set and `bucket_state` cycling between `'dormant'` (in the pool, no `due_date`, invisible to normal task views) and `'active'` (selected this cycle, has a computed `due_date`, shows up everywhere a normal task would). + +Per the interview: no fixed due dates on dormant items — the cycle length is the only timing concept. Exactly one bucket per item (`bucket_id` is a single value, not a list). + +## Selection Mechanism + +Every `cycle_days`, a periodic check (extending the existing scheduler) for each bucket: +1. Finds all `'dormant'` items with that `bucket_id`. +2. Scores each — staleness (time since `bucket_last_active_at`; never-activated items score highest), task priority, and (if the item has a label/project with an availability budget, per [[doot-future-task-scheduling-ideas]] item 1) how well it fits current capacity. Exact scoring formula is an implementation-time decision, not fixed here — the interview confirmed "weighted/scored," not a specific weighting. +3. Selects the top `pick_n`, flips them to `'active'`, sets `due_date = now + cycle_days` (giving the full cycle window to complete it), sets `bucket_last_active_at = now`. +4. Updates `last_cycle_at` on the bucket. + +**Deferring** an active item (a new "Defer" action distinct from complete/reschedule): flips it back to `'dormant'` with `bucket_last_active_at` left at its prior value (so it's still relatively stale and likely to be reselected soon), then **immediately triggers a fresh selection** for that bucket to backfill the freed slot — matching the interview's "defer one and get a new selection" requirement. + +**Completing** an active item: flips it back to `'dormant'` too, but sets `bucket_last_active_at = now` (just done, so it naturally cools down in the staleness scoring before being picked again) — distinct from deferring, where nothing was actually accomplished. This distinction wasn't explicitly asked in the interview; it's a reasonable design choice to revisit if it doesn't match intuition in practice. + +## API + +- `GET/POST /api/widget/buckets` — CRUD for buckets (name, cycle_days, pick_n). +- `POST /api/widget/buckets/{id}/items` — add an existing or new task to a bucket's pool (sets `bucket_id`, `bucket_state = 'dormant'`). +- `POST /api/widget/task/defer` — `{id}`, the new defer action (distinct from complete/reschedule) for active bucket items. +- No dedicated "trigger selection" endpoint needed for normal operation (the periodic check handles it) — but the defer action needs to synchronously trigger a re-selection for its bucket, reusing the same selection logic as a library function, not a new job. + +## UI + +Not designed in detail here — the interview didn't cover UI specifics for buckets. At minimum: a bucket needs a management view (create bucket, add/remove pool items, see cycle settings) and active bucket items need a visible "Defer" action alongside the existing Complete action, likely in the task-detail popup ([[doot-future-task-scheduling-ideas]] item 2's popup, or a bucket-specific variant). + +## Testing + +Store-layer tests for the selection algorithm (given a pool with known staleness/priority values, confirm the expected N are selected), the defer-triggers-reselection flow, and complete-vs-defer's differing `bucket_last_active_at` handling. Scheduler test for the periodic per-bucket cycle check, mirroring the existing `AdvanceDueRecurringTasks` test style. + +## Out of Scope + +- Exact scoring formula/weights — left as an implementation-time decision. +- UI design. +- Multi-bucket item membership (exactly one bucket per item, per the interview). +- Any interaction with the linear-task-chains feature ([[doot-future-task-scheduling-ideas]] item 4) — buckets and chains are independent concepts, not designed to compose. diff --git a/docs/superpowers/specs/2026-07-15-task-budgets-and-availability-design.md b/docs/superpowers/specs/2026-07-15-task-budgets-and-availability-design.md new file mode 100644 index 0000000..923ef54 --- /dev/null +++ b/docs/superpowers/specs/2026-07-15-task-budgets-and-availability-design.md @@ -0,0 +1,64 @@ +# Task Budgets and Availability Times — Design + +**Status:** Backlog idea, refined via interview 2026-07-15. Not yet approved for implementation. + +## Context + +The goal is to know whether what's on your plate actually fits in the time you realistically have, without doot trying to auto-schedule your day for you. This is explicitly a **visibility** feature, not a **scheduling** one, per the interview. + +**Depends on:** [[doot-future-task-scheduling-ideas]] item 2 (task labels/projects) — availability tracking is opt-in per project/label, so that feature needs to exist first. + +## Scope + +doot-native tasks only. Opt-in: a project or label is marked as "budget-tracked," and only tasks under a tracked project/label count against any budget calculation. Untracked tasks are invisible to this whole feature — the default experience is unchanged. + +## Data Model + +**Availability** — a manual recurring weekly template, reduced by real calendar events: +```sql +CREATE TABLE availability_blocks ( + id TEXT PRIMARY KEY, + weekday INTEGER NOT NULL, -- 0-6, Sun-Sat + start_time TEXT NOT NULL, -- "18:00" + end_time TEXT NOT NULL, -- "20:00" + label TEXT DEFAULT '' -- optional, e.g. "evening focus time" +); +``` +At computation time (see below), a day's available minutes = sum of that weekday's `availability_blocks` minus any overlapping existing calendar events (already fetched via `BuildTimeline` — no new calendar integration needed, just a subtraction pass over blocks the timeline already has). + +**Budget** — both per-task estimate and a period pool, per the interview: +- `native_tasks` gets `estimated_minutes INTEGER DEFAULT 0` — user-entered or inferred (see below). +- A period pool isn't a separate stored number — it's *computed* as the sum of `availability_blocks` for the period (today, this week), and compared against the sum of `estimated_minutes` for all budget-tracked, incomplete, due-in-period tasks. No new "pool" table; the pool is availability itself. + +**Estimate inference ("learned from history"):** requires tracking actual completion duration, which doot doesn't currently record (tasks have `due_date`/`completed_at` but no "started working on this" timestamp). The simplest version that doesn't require a new "time tracking" UI: when a task completes, if it had no `estimated_minutes` set, backfill nothing (no signal to learn from) — but if a *label* or *project* accumulates several tasks with user-entered `estimated_minutes`, use the average of the same-label/project tasks' user-entered estimates as the default for a *new* task under that label/project, rather than inferring from actual elapsed time. This sidesteps needing real time-tracking infrastructure while still delivering "the system learns typical durations for this kind of task." True elapsed-time-based learning is a larger, separate feature not designed here. + +## Computation and Surfacing + +No new background job — this is computed on read, at the same points `BuildTimeline`/`HandleWidgetGet` already run: +1. For "today" and "this week," sum `estimated_minutes` across budget-tracked incomplete tasks due in that window. +2. Sum available minutes from `availability_blocks` for the matching weekdays, minus overlapping calendar events already in the timeline. +3. If load > availability, surface a flag — not a hard block, not a reorder. Likely surfaced as a small indicator in the web timeline (e.g. "6.5h scheduled / 4h available today") and, if there's room, a small badge on the widget's TODAY header. Exact placement is a UI-design decision for the implementation plan, not fixed here. + +Per the interview: **no auto-scheduling, no auto-reprioritization** — the flag is purely informational. + +## API + +- `GET/POST /api/widget/availability` — CRUD for `availability_blocks`. +- `POST /api/widget/task/estimate` — `{id, estimated_minutes}`. +- The overflow flag itself doesn't need a dedicated endpoint — it's computed as part of the existing `GET /api/widget` and the web timeline's existing data-building path, added as a new field on the response (e.g. `budget_status: {scheduled_minutes, available_minutes}` alongside `items`). + +## Recurrence Interaction + +`estimated_minutes` is task-level, not series-level — the interview didn't ask about this directly, but since it's meant to reflect "how long does *this kind* of task take," and `CreateNextIteration` already copies most fields forward, the natural default is to carry it forward like content/description/project/labels. Re-estimating a specific occurrence would just mean editing that occurrence's value directly (no override mechanism needed beyond the normal edit UI). + +## Testing + +Store-layer tests for availability CRUD and the availability-minus-calendar-events computation (a pure function, easy to unit test with fixture events). Handler tests for the new endpoints and for the `budget_status` field appearing correctly in `/api/widget`'s response only when budget-tracked tasks exist. + +## Out of Scope + +- Auto-scheduling tasks into specific time slots. +- Auto-reprioritization or auto-deferral on overflow. +- Real elapsed-time tracking (start/stop timers) — estimate inference uses same-label/project averaging of user-entered values instead. +- Any UI beyond a basic indicator — exact visual treatment is an implementation-time decision. +- Non-project/label-scoped (global) budgets — everything is opt-in per the interview. diff --git a/docs/superpowers/specs/2026-07-15-task-labels-and-projects-design.md b/docs/superpowers/specs/2026-07-15-task-labels-and-projects-design.md new file mode 100644 index 0000000..4144d5b --- /dev/null +++ b/docs/superpowers/specs/2026-07-15-task-labels-and-projects-design.md @@ -0,0 +1,69 @@ +# Task Labels and Projects — Design + +**Status:** Backlog idea, refined via interview 2026-07-15. Not yet approved for implementation. + +## Context + +doot-native tasks have had a `Labels []string` field on `models.Task` (`internal/models/types.go`) since early in the project — it's fully wired at the store layer (persisted as a JSON column, scanned back out by `scanNativeTasks`) but never surfaced in any UI, web or widget. There's no editing, no display, no filtering. + +There's also no real "project" concept for doot-native tasks today. `Task.ProjectID`/`ProjectName` exist on the struct and `ProjectName` is a real column in `native_tasks`, but a repo-wide search turns up **zero** places that ever set `ProjectName` to a non-empty value when constructing a native task — it's vestigial, always empty in practice. This is likely a leftover from the Todoist-sync era (Todoist tasks did have real project names; the field just never got repurposed after native tasks replaced Todoist in commit `945c345`). So this feature isn't "finish wiring up projects" — it's building projects from scratch, alongside finally wiring up labels. + +This is a foundational feature: [[doot-future-task-scheduling-ideas]] items 1 (budgets/availability) and 4 (task chains) both reference "projects" as a dependency. Building this first unblocks both. + +## Scope + +doot-native tasks only, matching every other feature built this session. Trello/Google Tasks cards keep whatever labeling concept they already have server-side (Trello lists, etc.) — this feature does not touch them. + +## Data Model + +**Labels** — reuse the existing field, no schema change needed: +- `Task.Labels []string`, already a JSON column on `native_tasks`. +- Free-form text tags, many-to-many with tasks (a task can have any number of labels). +- User-assignable color per label. Since labels are currently just strings with no identity beyond their text, color needs a home: a new small `labels` table (`name TEXT PRIMARY KEY, color TEXT`) mapping label text → color hex. A label typed on a task that doesn't yet exist in this table gets a default color (or prompts for one) the first time it's colored. + +**Projects** — new, lightweight: +```sql +CREATE TABLE projects ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + color TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + archived BOOLEAN DEFAULT 0 +); +``` +- `native_tasks` gets a new `project_id TEXT DEFAULT ''` column (a real FK-shaped reference, not the vestigial `project_name` string column — that column stays as dead weight for now since dropping SQLite columns needs a table-rebuild migration, out of scope for this feature; it just stops being read). +- Exactly one project per task (`project_id` is a single string, empty = unassigned), per the interview answer. Labels handle the cross-cutting many-to-many need instead. +- A project is not itself an atom/task — it's pure metadata (name + color) that a task points to. + +## Recurrence Interaction + +Both `project_id` and `Labels` are **series-level metadata**: `CreateNextIteration` (in `internal/store/native_tasks.go`) already copies `content`, `description`, `project_name`, `priority`, and `labels` onto every new occurrence — `project_id` joins that same copy-forward list, no new mechanism needed. This matches the interview's "inherit automatically" answer exactly. + +## API + +New endpoints, doot-only, matching the existing `/api/widget/task/*` pattern: +- `GET /api/widget/projects` — list all non-archived projects (id, name, color), for populating a picker. +- `POST /api/widget/projects` — create a project (name, color). +- `POST /api/widget/task/project` — `{id, project_id}`, sets or clears a task's project. +- `POST /api/widget/task/labels` — `{id, labels: []string}`, replaces a task's label set (matches `UpdateNativeTask`'s replace-whole-value style rather than an add/remove-single-label API). +- Labels don't need their own CRUD endpoint in the same way projects do — a label is just a string until someone assigns it a color, so `POST /api/widget/task/labels` implicitly "creates" any new label text. A separate `POST /api/widget/labels/color` — `{name, color}` — sets a label's color. + +## UI + +**Widget (Android):** per the interview, minimal — a small color mark on the time-grid row (project color takes priority for the row's accent; if labels need visual representation too, a second smaller dot, but start with just project color to avoid clutter). No filtering UI on the widget itself. + +**Task detail popup (`TaskDetailActivity`):** full display and editing — a project picker (chip showing current project, tap to open a picker/create-new dialog) and a label editor (chip-entry, similar in spirit to the recurrence weekday chips but free-text with autocomplete against existing label names), both using the color-coding. + +**Web:** out of scope for this spec's UI section — the interview didn't ask about web display specifically; assume parity with the widget's popup-level editing is desirable but not designed here. + +## Testing + +Standard TDD per this project's convention: store-layer tests for `CreateProject`, `GetProjects`, `SetTaskProject`, `SetTaskLabels`, `SetLabelColor`; handler tests for the new endpoints (mirroring `widget_test.go`'s style); a `CreateNextIteration` test confirming `project_id` and `Labels` both carry forward to the next occurrence. + +## Out of Scope + +- Project hierarchies (sub-projects, nesting) — flat list only. +- Multi-project tasks — exactly one project per task, per the interview. +- Archived-project handling in the UI beyond hiding them from the picker (no dedicated archive-management screen designed here). +- Web UI specifics (noted above). +- Migrating/backfilling the vestigial `project_name` column — it's simply superseded, not cleaned up, in this spec. |
