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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
# 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.
|