summaryrefslogtreecommitdiff
path: root/internal/store/native_tasks.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-18 00:14:45 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-18 00:14:45 +0000
commitf08f06bef47aac2c9effb4cec650d99c2deb2dd7 (patch)
treeae06bd2e140c678e67f2879f21b07a4114a41f73 /internal/store/native_tasks.go
parentbe4d606e9a1f5b068abcc21bbac58d1e4705ea1f (diff)
Rework Tasks tab: Chains section, checklist modal, project visibility
The flat Tasks-tab atom list was silently dumping every chain step (locked and unlocked) and dormant bucket-pool items in as ordinary undated cards, with no chain/project context and no protection against completing a locked step out of order. - CompleteNativeTask now rejects completing a locked chain task (ErrChainTaskLocked), mapped to 400 in both the widget and web complete-atom handlers. - Chain tasks and dormant bucket items are excluded from the flat atom list; a new "Chains" section shows one card per active/paused chain with the current step and N/M progress. - New chain checklist modal (GET /chains/{id}) lists every position in order with pause/resume/abandon -- the web view originally deferred as Android-only. - Fixed a real bug this surfaced: resuming a paused chain only flipped the status flag, never unlocking the deferred successor, so a chain paused right after a completion stayed stuck forever. SetChainStatus now catches up the deferred advancement on resume, idempotently. - Atom cards gained a project-name chip for general visibility. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
Diffstat (limited to 'internal/store/native_tasks.go')
-rw-r--r--internal/store/native_tasks.go26
1 files changed, 24 insertions, 2 deletions
diff --git a/internal/store/native_tasks.go b/internal/store/native_tasks.go
index 6f8ba7a..11a9197 100644
--- a/internal/store/native_tasks.go
+++ b/internal/store/native_tasks.go
@@ -161,15 +161,27 @@ func (s *Store) UpdateNativeTaskDescription(id, description string) error {
return err
}
+// ErrChainTaskLocked is returned by CompleteNativeTask when the task
+// belongs to a chain but isn't the currently-unlocked position -- without
+// this guard, completing a locked task directly by id (bypassing the UI,
+// which never renders a checkbox for locked chain tasks) would still run
+// advanceChain against the wrong position, breaking the chain's WIP-1
+// invariant.
+var ErrChainTaskLocked = errors.New("task is locked in its chain")
+
// CompleteNativeTask marks a task as completed. If it's the latest
// occurrence of a recurring series (no newer row exists yet), it also
// creates the next iteration. Returns ErrNativeTaskNotFound if id doesn't
-// match any row.
+// match any row, or ErrChainTaskLocked if it's a locked (not yet
+// actionable) chain task.
func (s *Store) CompleteNativeTask(id string) error {
task, err := s.GetNativeTaskByID(id)
if err != nil {
return err
}
+ if task.ChainID != "" && !task.ChainUnlocked {
+ return ErrChainTaskLocked
+ }
result, err := s.db.Exec(`
UPDATE native_tasks SET completed = 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?
@@ -182,9 +194,19 @@ func (s *Store) CompleteNativeTask(id string) error {
}
if task.ChainID != "" {
- if err := s.advanceChain(task.ChainID, task.ChainPosition); err != nil {
+ chain, err := s.GetChain(task.ChainID)
+ if err != nil {
return err
}
+ // A paused chain does not auto-advance -- completing its unlocked
+ // task is still allowed (it's the one actionable step), but the
+ // successor stays locked until the chain is explicitly resumed
+ // (see SetChainStatus's resume catch-up).
+ if chain.Status != "paused" {
+ if err := s.advanceChain(task.ChainID, task.ChainPosition); err != nil {
+ return err
+ }
+ }
}
if task.BucketID != "" {