summaryrefslogtreecommitdiff
path: root/internal/store
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-17 22:46:18 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-17 22:46:18 +0000
commitbe4d606e9a1f5b068abcc21bbac58d1e4705ea1f (patch)
tree220aea277125aee4aa35a10201ab0a8a6c1caf89 /internal/store
parent9460b768335dcaf7f89020caf00f1a125de14b20 (diff)
Extend chain creation to accept per-task description and priority
CreateChain and POST /api/widget/chains previously only took bare title strings, with priority hardcoded to 1 -- narrower than the spec's "an ordered list of task titles/descriptions" API line. Now accepts []models.ChainTaskInput{Content, Description, Priority} per position, priority defaulting to 1 when omitted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
Diffstat (limited to 'internal/store')
-rw-r--r--internal/store/chains.go22
-rw-r--r--internal/store/chains_test.go44
2 files changed, 52 insertions, 14 deletions
diff --git a/internal/store/chains.go b/internal/store/chains.go
index 4f51b3c..f3bd0d7 100644
--- a/internal/store/chains.go
+++ b/internal/store/chains.go
@@ -16,11 +16,11 @@ const defaultChainProjectColor = "#8B5CF6"
// CreateChain creates a backing project (per the design's "a chain is
// effectively a project with strict sequential unlocking"), a task_chains
-// row, and one native_tasks row per title in taskTitles. Position 0 is
-// created unlocked with due_date = now; the rest are locked with no due
-// date. All in one transaction so a partial chain never exists.
-func (s *Store) CreateChain(name string, taskTitles []string) (*models.Chain, error) {
- if len(taskTitles) == 0 {
+// row, and one native_tasks row per entry in tasks. Position 0 is created
+// unlocked with due_date = now; the rest are locked with no due date. All
+// in one transaction so a partial chain never exists.
+func (s *Store) CreateChain(name string, tasks []models.ChainTaskInput) (*models.Chain, error) {
+ if len(tasks) == 0 {
return nil, fmt.Errorf("chain must have at least one task")
}
@@ -44,16 +44,20 @@ func (s *Store) CreateChain(name string, taskTitles []string) (*models.Chain, er
return nil, err
}
- for i, title := range taskTitles {
+ for i, t := range tasks {
unlocked := i == 0
var dueDate *time.Time
if unlocked {
dueDate = &now
}
+ priority := t.Priority
+ if priority == 0 {
+ priority = 1
+ }
if _, err := tx.Exec(`
- INSERT INTO native_tasks (id, content, project_name, project_id, priority, due_date, chain_id, chain_position, chain_unlocked, created_at, updated_at)
- VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)
- `, newTaskID(), title, project.Name, project.ID, dueDate, chainID, i, unlocked, now, now); err != nil {
+ INSERT INTO native_tasks (id, content, description, project_name, project_id, priority, due_date, chain_id, chain_position, chain_unlocked, created_at, updated_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ `, newTaskID(), t.Content, t.Description, project.Name, project.ID, priority, dueDate, chainID, i, unlocked, now, now); err != nil {
return nil, err
}
}
diff --git a/internal/store/chains_test.go b/internal/store/chains_test.go
index 6d3e450..1d16508 100644
--- a/internal/store/chains_test.go
+++ b/internal/store/chains_test.go
@@ -2,12 +2,24 @@ package store
import (
"testing"
+
+ "task-dashboard/internal/models"
)
+// chainTasks builds bare-content ChainTaskInputs (no description/priority)
+// for tests that only care about title ordering and lock state.
+func chainTasks(titles ...string) []models.ChainTaskInput {
+ tasks := make([]models.ChainTaskInput, len(titles))
+ for i, t := range titles {
+ tasks[i] = models.ChainTaskInput{Content: t}
+ }
+ return tasks
+}
+
func TestCreateChain_SeedsPositionsCorrectly(t *testing.T) {
s := newNativeTasksTestStore(t)
- chain, err := s.CreateChain("Ham Radio Track", []string{"Study Technician", "Pass Technician exam", "Study General"})
+ chain, err := s.CreateChain("Ham Radio Track", chainTasks("Study Technician", "Pass Technician exam", "Study General"))
if err != nil {
t.Fatalf("CreateChain: %v", err)
}
@@ -38,7 +50,7 @@ func TestCreateChain_SeedsPositionsCorrectly(t *testing.T) {
func TestCompleteNativeTask_AdvancesChain(t *testing.T) {
s := newNativeTasksTestStore(t)
- chain, err := s.CreateChain("Track", []string{"Step 1", "Step 2"})
+ chain, err := s.CreateChain("Track", chainTasks("Step 1", "Step 2"))
if err != nil {
t.Fatal(err)
}
@@ -71,7 +83,7 @@ func TestCompleteNativeTask_AdvancesChain(t *testing.T) {
func TestCompleteNativeTask_LastPosition_MarksChainCompleted(t *testing.T) {
s := newNativeTasksTestStore(t)
- chain, err := s.CreateChain("Track", []string{"Only step"})
+ chain, err := s.CreateChain("Track", chainTasks("Only step"))
if err != nil {
t.Fatal(err)
}
@@ -96,7 +108,7 @@ func TestCompleteNativeTask_LastPosition_MarksChainCompleted(t *testing.T) {
func TestCompleteNativeTask_PausedChain_DoesNotAdvance(t *testing.T) {
s := newNativeTasksTestStore(t)
- chain, err := s.CreateChain("Track", []string{"Step 1", "Step 2"})
+ chain, err := s.CreateChain("Track", chainTasks("Step 1", "Step 2"))
if err != nil {
t.Fatal(err)
}
@@ -139,7 +151,7 @@ func TestCompleteNativeTask_PausedChain_DoesNotAdvance(t *testing.T) {
func TestGetUndatedNativeTasks_ExcludesLockedChainTasks(t *testing.T) {
s := newNativeTasksTestStore(t)
- chain, err := s.CreateChain("Track", []string{"Step 1", "Step 2", "Step 3"})
+ chain, err := s.CreateChain("Track", chainTasks("Step 1", "Step 2", "Step 3"))
if err != nil {
t.Fatal(err)
}
@@ -156,6 +168,28 @@ func TestGetUndatedNativeTasks_ExcludesLockedChainTasks(t *testing.T) {
}
}
+func TestCreateChain_PersistsDescriptionAndPriority(t *testing.T) {
+ s := newNativeTasksTestStore(t)
+
+ chain, err := s.CreateChain("Track", []models.ChainTaskInput{
+ {Content: "Step 1", Description: "do the thing", Priority: 4},
+ {Content: "Step 2"}, // no priority given -- should default to 1
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ tasks, err := s.GetChainTasks(chain.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if tasks[0].Description != "do the thing" || tasks[0].Priority != 4 {
+ t.Errorf("tasks[0] = %+v, want description=%q priority=4", tasks[0], "do the thing")
+ }
+ if tasks[1].Priority != 1 {
+ t.Errorf("tasks[1].Priority = %d, want default of 1", tasks[1].Priority)
+ }
+}
+
func TestGetChain_UnknownID_ReturnsErrNotFound(t *testing.T) {
s := newNativeTasksTestStore(t)