# Pull-based punch list — design **Status:** core implemented and deployed 2026-09-03 (1477ee5); remaining work at the bottom **Goal:** the daily list should hit zero most days. Today it structurally cannot. ## Problem Measured against production on 2026-09-02: 113 open native tasks. 83 are correctly-hidden locked chain steps. Of the ~30 visible, 19 are free-standing and genuinely today-sized ("Take out recycling", "Charge speaker", "Clean bathroom"). The punch list itself is fine. Everything around it is not. Three defects and one design flaw: 1. **Paused chains leaked.** Task queries checked `chain_unlocked` but never the owning chain's status, so 4 of 8 visible chain tasks came from explicitly paused chains. *Fixed 2026-09-02 (3e26cd5).* 2. **Skipped recurrences never retired.** The scheduler materializes the next instance once the current one comes due, whether or not it was completed — deliberately, so skipping one doesn't stall the series. But nothing retired the occurrence it replaced, so every skipped occurrence stayed open forever as a permanent overdue item ("Bills" and "Bones's meds" each sat in the list twice). Per the intended semantics: an occurrence not done before its successor arrives is *stale/abandoned*, not still owed — the newer one stands. *Fixed 2026-09-03 (f295fd1): an `abandoned` flag, set when the successor is created. Deliberately not `completed`, which would credit work never done.* 3. **Buckets never got a pool.** One bucket exists ("Yard Work", cycle 14d, pick 3) holding exactly 3 items. "Pick 3 of 3" makes the rotation a no-op — everything is always active. A duplicate free-standing "Yard work" task does the same job in parallel. *Fixed 2026-09-03: pool populated to 22 items, cycle retuned to pick 2 every 4 days, and the duplicate free-standing "Yard work" recurring task deleted.* 4. **`due_date` is doing two incompatible jobs** — the design flaw underneath the complaint. It means both *"this is a hard deadline"* (Bills, meds, T-ball signup) and *"this is when I intend to work on it."* Chains lean hard on the second meaning: `CreateChain` stamps `due_date = now` on the head, and `advanceChain` does the same on every unlock. So a 5-minute phone call and a 6-month certification are both born "due today," and the only way to make them stop shouting is to hand-reschedule them — which is exactly what has been happening. The deeper structural issue: the list is a **push** of everything not-blocked, not a **pull** of what was committed to. Zero is unreachable by construction when the list contains things that cannot be finished today. ## Design Split the two meanings of `due_date` apart. - **`due_date`** keeps only its real meaning: an externally-imposed deadline. Bills, meds, signups, appointments. Rare, and always meaningful. - **`committed_date`** (new, nullable) means "I chose to do this today." ### What TODAY shows - tasks with `committed_date = today` - tasks with `due_date <= today` (real deadlines still push — they're deadlines) - calendar events (unchanged) Nothing else. Chain steps, bucket-active items, undated backlog, and future-dated work are all absent until pulled. ### What chains do - Chain steps **no longer get a due date on unlock.** A milestone is not a deadline. (`CreateChain` and `advanceChain` both stop stamping `due_date`.) - The unlocked step per chain is visible in a **Projects** surface, not the daily list. - Pulling a step sets `committed_date = today` and leaves `due_date` alone. This also means "pausing" stops being load-bearing for noise control — a chain you're not working on simply never pushes anything at you, paused or not. ### What buckets do Unchanged mechanically, but populated properly so the rotation is real: a pool substantially larger than `pick_n`. Active items appear in the review surface as pull candidates rather than pushing themselves onto the list. The duplicate free-standing "Yard work" task gets deleted. ### Review One screen, the pull surface. Sections: **Projects** (next step per active chain) · **Bucket active** · **Undated backlog** · **Due soon**. Each row has a "+ Today" action. Intended to take well under a minute. Note this formalizes something already happening by hand — "Review week plan" and "Sailing Readiness Review" currently exist as literal tasks. ### Carry-over A committed task not finished by end of day stays visible, flagged as carried, rather than silently vanishing or silently accumulating. Un-committing drops it back to the backlog. Carry-over count is the honest signal for over-committing. ## Migration - Add `committed_date` (nullable) to `native_tasks`. New numbered migration — never renumber existing ones. - Existing chain steps: clear the auto-stamped `due_date` on unlocked steps that were never hand-set. Ambiguous cases stay as-is; they surface in the first review and get triaged by hand. - Existing undated free-standing tasks: unchanged, land in backlog. - Existing dated free-standing tasks: unchanged, still push. Most are real deadlines already. ## What shipped (1477ee5, 2026-09-03) - `committed_date` on `native_tasks` (migration 031), with Set/Clear/GetCommittedTasks in the store. - Chain steps no longer stamped with a due date on unlock, and excluded from the undated list so they don't fall through to it instead. Migration 031 cleared the auto-stamped dates from the 8 open steps. - `BuildTimeline` includes tasks committed to today, deduped against rows already present for another reason. The widget inherits this — it renders from `BuildTimeline`. - "+ today" / "on today ×" controls on chain cards and active bucket items. Verified against production after deploy: 0 open chain steps still carry a due date; an uncommitted chain head is absent from the widget payload; committing it makes it appear; clearing it removes it again. ## Still open - **Carry-over is unbuilt.** A task committed yesterday and not finished simply stops appearing rather than showing as carried. Needs a decision: auto-roll to today, or surface a "yesterday's unfinished" prompt during review. - **No dedicated review surface.** Pulling happens from the Chains and Buckets sections of the Tasks tab, which works but isn't the single "plan the day" screen described above. - **Buckets still push.** Active bucket items keep `due_date = now + cycle_days`, so they arrive on the day's list on their own rather than by being pulled — inconsistent with the one rule, though mild at 2 items per 4 days. - **No commit control on ordinary undated backlog tasks** — only chain steps and active bucket items have one so far.