feat(tasks): add TaskStatus and Motivation types

Amolith created

Replace raw strings with typed constants for better discoverability and
compile-time safety.

- TaskStatus: StatusLater, StatusNext, StatusStarted, StatusWaiting,
  StatusCompleted
- Motivation: MotivationMust, MotivationShould, MotivationWant

Assisted-by: Claude Sonnet 4 via Crush

Change summary

builders.go | 24 +++++++++------
tasks.go    | 82 +++++++++++++++++++++++++++---------------------------
types.go    | 22 ++++++++++++++
3 files changed, 77 insertions(+), 51 deletions(-)

Detailed changes

builders.go 🔗

@@ -10,7 +10,7 @@ import "time"
 //
 //	req := lunatask.NewTask("Review PR").
 //		InArea(areaID).
-//		WithStatus("next").
+//		WithStatus(lunatask.StatusNext).
 //		WithEstimate(30).
 //		Build()
 //	task, err := client.CreateTask(ctx, req)
@@ -44,15 +44,17 @@ func (b *TaskBuilder) WithNote(note string) *TaskBuilder {
 	return b
 }
 
-// WithStatus sets the workflow status: later, next, started, waiting, or completed.
-func (b *TaskBuilder) WithStatus(status string) *TaskBuilder {
+// WithStatus sets the workflow status.
+// Use one of the Status* constants (e.g., [StatusNext]).
+func (b *TaskBuilder) WithStatus(status TaskStatus) *TaskBuilder {
 	b.req.Status = &status
 
 	return b
 }
 
-// WithMotivation sets why this task matters: must, should, or want.
-func (b *TaskBuilder) WithMotivation(motivation string) *TaskBuilder {
+// WithMotivation sets why this task matters.
+// Use one of the Motivation* constants (e.g., [MotivationMust]).
+func (b *TaskBuilder) WithMotivation(motivation Motivation) *TaskBuilder {
 	b.req.Motivation = &motivation
 
 	return b
@@ -111,7 +113,7 @@ func (b *TaskBuilder) Build() *CreateTaskRequest {
 // Only fields you set will be modified; others remain unchanged.
 //
 //	req := lunatask.NewTaskUpdate().
-//		WithStatus("completed").
+//		WithStatus(lunatask.StatusCompleted).
 //		CompletedAt(time.Now()).
 //		Build()
 //	task, err := client.UpdateTask(ctx, taskID, req)
@@ -152,15 +154,17 @@ func (b *TaskUpdateBuilder) WithNote(note string) *TaskUpdateBuilder {
 	return b
 }
 
-// WithStatus sets the workflow status: later, next, started, waiting, or completed.
-func (b *TaskUpdateBuilder) WithStatus(status string) *TaskUpdateBuilder {
+// WithStatus sets the workflow status.
+// Use one of the Status* constants (e.g., [StatusNext]).
+func (b *TaskUpdateBuilder) WithStatus(status TaskStatus) *TaskUpdateBuilder {
 	b.req.Status = &status
 
 	return b
 }
 
-// WithMotivation sets why this task matters: must, should, or want.
-func (b *TaskUpdateBuilder) WithMotivation(motivation string) *TaskUpdateBuilder {
+// WithMotivation sets why this task matters.
+// Use one of the Motivation* constants (e.g., [MotivationMust]).
+func (b *TaskUpdateBuilder) WithMotivation(motivation Motivation) *TaskUpdateBuilder {
 	b.req.Motivation = &motivation
 
 	return b

tasks.go 🔗

@@ -15,57 +15,57 @@ import (
 // Task is a task in Lunatask. Name and Note are encrypted client-side
 // and will be null when read back from the API.
 type Task struct {
-	ID             string     `json:"id"`
-	AreaID         *string    `json:"area_id"`
-	GoalID         *string    `json:"goal_id"`
-	Name           *string    `json:"name"`
-	Note           *string    `json:"note"`
-	Status         *string    `json:"status"`
-	PreviousStatus *string    `json:"previous_status"`
-	Estimate       *int       `json:"estimate"`
-	Priority       *int       `json:"priority"`
-	Progress       *int       `json:"progress"`
-	Motivation     *string    `json:"motivation"`
-	Eisenhower     *int       `json:"eisenhower"`
-	Sources        []Source   `json:"sources"`
-	ScheduledOn    *Date      `json:"scheduled_on"`
-	CompletedAt    *time.Time `json:"completed_at"`
-	CreatedAt      time.Time  `json:"created_at"`
-	UpdatedAt      time.Time  `json:"updated_at"`
+	ID             string      `json:"id"`
+	AreaID         *string     `json:"area_id"`
+	GoalID         *string     `json:"goal_id"`
+	Name           *string     `json:"name"`
+	Note           *string     `json:"note"`
+	Status         *TaskStatus `json:"status"`
+	PreviousStatus *TaskStatus `json:"previous_status"`
+	Estimate       *int        `json:"estimate"`
+	Priority       *int        `json:"priority"`
+	Progress       *int        `json:"progress"`
+	Motivation     *Motivation `json:"motivation"`
+	Eisenhower     *int        `json:"eisenhower"`
+	Sources        []Source    `json:"sources"`
+	ScheduledOn    *Date       `json:"scheduled_on"`
+	CompletedAt    *time.Time  `json:"completed_at"`
+	CreatedAt      time.Time   `json:"created_at"`
+	UpdatedAt      time.Time   `json:"updated_at"`
 }
 
 // CreateTaskRequest defines a new task.
 // Use [TaskBuilder] for a fluent construction API.
 type CreateTaskRequest struct {
-	Name        string     `json:"name"`
-	AreaID      *string    `json:"area_id,omitempty"`
-	GoalID      *string    `json:"goal_id,omitempty"`
-	Note        *string    `json:"note,omitempty"`
-	Status      *string    `json:"status,omitempty"`
-	Motivation  *string    `json:"motivation,omitempty"`
-	Estimate    *int       `json:"estimate,omitempty"`
-	Priority    *int       `json:"priority,omitempty"`
-	Eisenhower  *int       `json:"eisenhower,omitempty"`
-	ScheduledOn *Date      `json:"scheduled_on,omitempty"`
-	CompletedAt *time.Time `json:"completed_at,omitempty"`
-	Source      *string    `json:"source,omitempty"`
-	SourceID    *string    `json:"source_id,omitempty"`
+	Name        string      `json:"name"`
+	AreaID      *string     `json:"area_id,omitempty"`
+	GoalID      *string     `json:"goal_id,omitempty"`
+	Note        *string     `json:"note,omitempty"`
+	Status      *TaskStatus `json:"status,omitempty"`
+	Motivation  *Motivation `json:"motivation,omitempty"`
+	Estimate    *int        `json:"estimate,omitempty"`
+	Priority    *int        `json:"priority,omitempty"`
+	Eisenhower  *int        `json:"eisenhower,omitempty"`
+	ScheduledOn *Date       `json:"scheduled_on,omitempty"`
+	CompletedAt *time.Time  `json:"completed_at,omitempty"`
+	Source      *string     `json:"source,omitempty"`
+	SourceID    *string     `json:"source_id,omitempty"`
 }
 
 // UpdateTaskRequest specifies which fields to change on a task.
 // Only non-nil fields are updated. Use [TaskUpdateBuilder] for fluent construction.
 type UpdateTaskRequest struct {
-	Name        *string    `json:"name,omitempty"`
-	AreaID      *string    `json:"area_id,omitempty"`
-	GoalID      *string    `json:"goal_id,omitempty"`
-	Note        *string    `json:"note,omitempty"`
-	Status      *string    `json:"status,omitempty"`
-	Motivation  *string    `json:"motivation,omitempty"`
-	Estimate    *int       `json:"estimate,omitempty"`
-	Priority    *int       `json:"priority,omitempty"`
-	Eisenhower  *int       `json:"eisenhower,omitempty"`
-	ScheduledOn *Date      `json:"scheduled_on,omitempty"`
-	CompletedAt *time.Time `json:"completed_at,omitempty"`
+	Name        *string     `json:"name,omitempty"`
+	AreaID      *string     `json:"area_id,omitempty"`
+	GoalID      *string     `json:"goal_id,omitempty"`
+	Note        *string     `json:"note,omitempty"`
+	Status      *TaskStatus `json:"status,omitempty"`
+	Motivation  *Motivation `json:"motivation,omitempty"`
+	Estimate    *int        `json:"estimate,omitempty"`
+	Priority    *int        `json:"priority,omitempty"`
+	Eisenhower  *int        `json:"eisenhower,omitempty"`
+	ScheduledOn *Date       `json:"scheduled_on,omitempty"`
+	CompletedAt *time.Time  `json:"completed_at,omitempty"`
 }
 
 // taskResponse wraps a single task from the API.

types.go 🔗

@@ -101,3 +101,25 @@ const (
 	RelationshipBusiness       RelationshipStrength = "business-contacts"
 	RelationshipAlmostStranger RelationshipStrength = "almost-strangers"
 )
+
+// TaskStatus represents the workflow state of a task.
+type TaskStatus string
+
+// Valid task status values.
+const (
+	StatusLater     TaskStatus = "later"
+	StatusNext      TaskStatus = "next"
+	StatusStarted   TaskStatus = "started"
+	StatusWaiting   TaskStatus = "waiting"
+	StatusCompleted TaskStatus = "completed"
+)
+
+// Motivation represents why a task matters.
+type Motivation string
+
+// Valid motivation values.
+const (
+	MotivationMust   Motivation = "must"
+	MotivationShould Motivation = "should"
+	MotivationWant   Motivation = "want"
+)