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
|
# 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. *Pool populated 2026-09-03 to 22 items (3 active +
19 dormant), so the rotation is real. The duplicate free-standing "Yard work"
recurring task is still there — deleting it ends its series, so it needs an
explicit call.*
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.
## Open questions
- Should a bucket's active items auto-commit at cycle start, or always be
pulled? Leaning pulled, for consistency with the one rule.
- Does the widget mirror TODAY exactly? Probably yes — the point is one list.
|