diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-07-18 00:14:45 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-07-18 00:14:45 +0000 |
| commit | f08f06bef47aac2c9effb4cec650d99c2deb2dd7 (patch) | |
| tree | ae06bd2e140c678e67f2879f21b07a4114a41f73 /internal/store/native_tasks.go | |
| parent | be4d606e9a1f5b068abcc21bbac58d1e4705ea1f (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.go | 26 |
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 != "" { |
