diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-09-03 00:45:53 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-09-03 00:45:53 +0000 |
| commit | f295fd12e6ffc8a7c023a83b3ac1223664255e90 (patch) | |
| tree | b4e9a3e3f28591524a78f456940b4798aa92623b /internal/store/native_tasks.go | |
| parent | 96518adb706b65670508b170b50f4bd5e1aeeed8 (diff) | |
Abandon recurring occurrences superseded by their next iteration
A weekly task not done before the next one lands is stale, not still
owed. The scheduler already created the successor regardless of
completion (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. Live: "Bills" and
"Bones's meds" each sat in the list twice.
AdvanceDueRecurringTasks now marks the superseded occurrence abandoned
when it creates the successor. A separate flag rather than `completed`,
because these were never done and crediting them would corrupt
completion history. All four open-task queries filter it out; migration
030 adds the column and backfills the occurrences already superseded
(exactly the 2 above, verified against production before deploying).
Supersedes the earlier view-level collapse (10f9fcd, reverted in
96518ad): that kept the *earliest* open instance, which is backwards --
the stale one should go and the current one stand -- and it only hid the
row in the flat list while leaving it overdue in the timeline and widget,
which is where it was actually being seen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'internal/store/native_tasks.go')
| -rw-r--r-- | internal/store/native_tasks.go | 20 |
1 files changed, 16 insertions, 4 deletions
diff --git a/internal/store/native_tasks.go b/internal/store/native_tasks.go index ea5212c..955b7ac 100644 --- a/internal/store/native_tasks.go +++ b/internal/store/native_tasks.go @@ -43,7 +43,7 @@ func (s *Store) GetNativeTasks() ([]models.Task, error) { chain_id, chain_position, chain_unlocked, bucket_id, bucket_state, bucket_last_active_at FROM native_tasks - WHERE completed = 0 + WHERE completed = 0 AND abandoned = 0 ORDER BY CASE WHEN due_date IS NULL THEN 1 ELSE 0 END, due_date ASC, priority DESC `) if err != nil { @@ -64,7 +64,7 @@ func (s *Store) GetNativeTasksByDateRange(start, end time.Time) ([]models.Task, chain_id, chain_position, chain_unlocked, bucket_id, bucket_state, bucket_last_active_at FROM native_tasks t - WHERE completed = 0 AND due_date IS NOT NULL AND due_date >= ? AND due_date < ? + WHERE completed = 0 AND abandoned = 0 AND due_date IS NOT NULL AND due_date >= ? AND due_date < ? AND `+visibleChainTaskClause+` ORDER BY due_date ASC, priority DESC `, start, end) @@ -88,7 +88,7 @@ func (s *Store) GetOverdueNativeTasks(before time.Time) ([]models.Task, error) { chain_id, chain_position, chain_unlocked, bucket_id, bucket_state, bucket_last_active_at FROM native_tasks t - WHERE completed = 0 AND due_date IS NOT NULL AND due_date < ? + WHERE completed = 0 AND abandoned = 0 AND due_date IS NOT NULL AND due_date < ? AND `+visibleChainTaskClause+` ORDER BY due_date ASC, priority DESC `, before) @@ -107,7 +107,7 @@ func (s *Store) GetUndatedNativeTasks() ([]models.Task, error) { chain_id, chain_position, chain_unlocked, bucket_id, bucket_state, bucket_last_active_at FROM native_tasks t - WHERE completed = 0 AND due_date IS NULL + WHERE completed = 0 AND abandoned = 0 AND due_date IS NULL AND `+visibleChainTaskClause+` AND bucket_state != 'dormant' ORDER BY priority DESC, created_at ASC @@ -493,6 +493,18 @@ func (s *Store) AdvanceDueRecurringTasks(now time.Time) (int, error) { if err := s.CreateNextIteration(task); err != nil { return 0, err } + // The occurrence being superseded was never done, and now its + // replacement is here -- it's stale, not still owed. Left open it would + // sit in the list as a permanent overdue item and every skipped + // occurrence would add another. Not marked `completed`, since that would + // falsely credit work that never happened. + if !task.Completed { + if _, err := s.db.Exec(` + UPDATE native_tasks SET abandoned = 1, updated_at = ? WHERE id = ? + `, config.Now(), task.ID); err != nil { + return 0, err + } + } } return len(series), nil } |
