summaryrefslogtreecommitdiff
path: root/internal/store/native_tasks.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-09-02 23:47:56 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-09-02 23:47:56 +0000
commit3e26cd5ad7a0cbd1445f535ccd63c2e79d3580a4 (patch)
tree41b6dd8b79f4d078cda1c91570af2f9240ccdce4 /internal/store/native_tasks.go
parent74bf58335d3542d88b32d447b47342a76f5e5449 (diff)
Hide paused chains' tasks from the task list
Pausing a chain is supposed to take its work off the list -- that's the entire point of the control. But the three task-fetching queries only ever checked `chain_unlocked`, never the owning chain's status, so a paused chain's head task kept appearing exactly like an active one. Verified against production: 4 of the 8 visible chain tasks came from chains that had been explicitly paused ("Will", "ABYC Marine Electrical certification", "Standard services agreement", "Find a broker who writes marine trades"). Half the chain clutter on the list was work the user had already told doot to put away. Fixed the class, not just the instances: the predicate now lives in one shared visibleChainTaskClause constant used by all three queries, with a structural test asserting no query spells it out inline and that every query still references it. The previous shape -- the same predicate copy-pasted into three separate query strings -- is exactly why a fourth query would have gotten it wrong too. Also added task_chains to sqlite_test.go's leaner test schema, since the queries now join against it even for chain-less tasks. 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.go26
1 files changed, 20 insertions, 6 deletions
diff --git a/internal/store/native_tasks.go b/internal/store/native_tasks.go
index 9d9af2c..ea5212c 100644
--- a/internal/store/native_tasks.go
+++ b/internal/store/native_tasks.go
@@ -21,6 +21,20 @@ import (
// completion: the HTTP response was 200, but nothing in the database changed.
var ErrNativeTaskNotFound = errors.New("native task not found")
+// visibleChainTaskClause is the single source of truth for "which chain tasks
+// belong on the list." Every task query that hides locked chain work must use
+// this rather than spelling the predicate out inline -- see
+// TestChainVisibilityPredicate_UsedEverywhereChainUnlockedIsFiltered.
+//
+// The chain's own status matters as much as the task's lock state: pausing a
+// chain is meant to take its work off the list entirely, but for a long time
+// these queries checked only chain_unlocked, so a paused chain's head task kept
+// appearing exactly like an active one -- the pause control did nothing to the
+// list it was supposed to affect.
+const visibleChainTaskClause = `(t.chain_id = '' OR (t.chain_unlocked = 1 AND EXISTS (
+ SELECT 1 FROM task_chains c WHERE c.id = t.chain_id AND c.status = 'active'
+ )))`
+
// GetNativeTasks returns all non-completed native tasks.
func (s *Store) GetNativeTasks() ([]models.Task, error) {
rows, err := s.db.Query(`
@@ -49,9 +63,9 @@ func (s *Store) GetNativeTasksByDateRange(start, end time.Time) ([]models.Task,
recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id, next_occurrence_override, estimated_minutes,
chain_id, chain_position, chain_unlocked,
bucket_id, bucket_state, bucket_last_active_at
- FROM native_tasks
+ FROM native_tasks t
WHERE completed = 0 AND due_date IS NOT NULL AND due_date >= ? AND due_date < ?
- AND (chain_id = '' OR chain_unlocked = 1)
+ AND `+visibleChainTaskClause+`
ORDER BY due_date ASC, priority DESC
`, start, end)
if err != nil {
@@ -73,9 +87,9 @@ func (s *Store) GetOverdueNativeTasks(before time.Time) ([]models.Task, error) {
recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id, next_occurrence_override, estimated_minutes,
chain_id, chain_position, chain_unlocked,
bucket_id, bucket_state, bucket_last_active_at
- FROM native_tasks
+ FROM native_tasks t
WHERE completed = 0 AND due_date IS NOT NULL AND due_date < ?
- AND (chain_id = '' OR chain_unlocked = 1)
+ AND `+visibleChainTaskClause+`
ORDER BY due_date ASC, priority DESC
`, before)
if err != nil {
@@ -92,9 +106,9 @@ func (s *Store) GetUndatedNativeTasks() ([]models.Task, error) {
recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id, next_occurrence_override, estimated_minutes,
chain_id, chain_position, chain_unlocked,
bucket_id, bucket_state, bucket_last_active_at
- FROM native_tasks
+ FROM native_tasks t
WHERE completed = 0 AND due_date IS NULL
- AND (chain_id = '' OR chain_unlocked = 1)
+ AND `+visibleChainTaskClause+`
AND bucket_state != 'dormant'
ORDER BY priority DESC, created_at ASC
`)