summaryrefslogtreecommitdiff
path: root/internal/store/native_tasks.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-08-12 23:35:47 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-08-12 23:35:47 +0000
commit2509dde6aa372a505b186657706f4d21bd391807 (patch)
treee4147991747b8dd14d0b8654b2b8e50717b666bd /internal/store/native_tasks.go
parent3e8ad60431d6cc783f9f7c555bfde5db54ebec75 (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.go61
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,