diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-08-12 23:35:47 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-08-12 23:35:47 +0000 |
| commit | 2509dde6aa372a505b186657706f4d21bd391807 (patch) | |
| tree | e4147991747b8dd14d0b8654b2b8e50717b666bd /internal/store/native_tasks.go | |
| parent | 3e8ad60431d6cc783f9f7c555bfde5db54ebec75 (diff) | |
Add task title editing/deletion, timeline click-to-open, widget app launch
Task-detail modal was description-only with no delete affordance;
HandleUpdateTask now saves the title too and a Delete button hits a new
DELETE /tasks/{id} route backed by store.DeleteNativeTask, which repairs
chain_position/unlocks the successor when the deleted task belongs to a
chain. Timeline tab task/card/gtask rows now open the same detail modal
as the Tasks tab. Android widget's "TODAY" header is now a tap target
that launches DashboardActivity, since nothing previously opened the
full app from the widget.
Diffstat (limited to 'internal/store/native_tasks.go')
| -rw-r--r-- | internal/store/native_tasks.go | 61 |
1 files changed, 61 insertions, 0 deletions
diff --git a/internal/store/native_tasks.go b/internal/store/native_tasks.go index 11a9197..9d9af2c 100644 --- a/internal/store/native_tasks.go +++ b/internal/store/native_tasks.go @@ -161,6 +161,67 @@ func (s *Store) UpdateNativeTaskDescription(id, description string) error { return err } +// DeleteNativeTask permanently removes a task. Returns ErrNativeTaskNotFound +// if id doesn't match any row. If the task belongs to a chain, this also +// closes the resulting gap in chain_position (advanceChain looks up +// chain_position+1, so a gap would either strand the chain mid-sequence or, +// if the deleted task was the unlocked one, silently stop it from ever +// advancing) and, if the deleted task was itself the unlocked position, +// unlocks whatever now occupies that position -- or marks the chain +// completed if nothing does (the deleted task was the last one left). +func (s *Store) DeleteNativeTask(id string) error { + task, err := s.GetNativeTaskByID(id) + if err != nil { + return err + } + + if task.ChainID == "" { + result, err := s.db.Exec(`DELETE FROM native_tasks WHERE id = ?`, id) + if err != nil { + return err + } + return checkRowsAffected(result) + } + + tx, err := s.db.Begin() + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + + if _, err := tx.Exec(`DELETE FROM native_tasks WHERE id = ?`, id); err != nil { + return err + } + if _, err := tx.Exec(` + UPDATE native_tasks SET chain_position = chain_position - 1 + WHERE chain_id = ? AND chain_position > ? + `, task.ChainID, task.ChainPosition); err != nil { + return err + } + + if task.ChainUnlocked { + now := config.Now() + result, err := tx.Exec(` + UPDATE native_tasks SET chain_unlocked = 1, due_date = ?, updated_at = ? + WHERE chain_id = ? AND chain_position = ? + `, now, now, task.ChainID, task.ChainPosition) + if err != nil { + return err + } + successorPromoted, err := result.RowsAffected() + if err != nil { + return err + } + if successorPromoted == 0 { + if _, err := tx.Exec(`UPDATE task_chains SET status = 'completed' WHERE id = ?`, task.ChainID); err != nil { + return err + } + } + } + + return tx.Commit() +} + // 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, |
