Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion internal/db/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ func (db *DB) ListTasks(opts ListTasksOptions) ([]*Task, error) {
query += " AND status != 'done'"
}

query += " ORDER BY created_at DESC"
// Sort done/blocked tasks by completed_at (most recently closed first),
// other tasks by created_at (newest first).
// Use id DESC as secondary sort for consistent ordering when timestamps are equal.
query += " ORDER BY CASE WHEN status IN ('done', 'blocked') THEN completed_at ELSE created_at END DESC, id DESC"

if opts.Limit > 0 {
query += fmt.Sprintf(" LIMIT %d", opts.Limit)
Expand Down
84 changes: 84 additions & 0 deletions internal/db/tasks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,90 @@ func TestLastTaskTypeForProject(t *testing.T) {
}
}

func TestListTasksClosedSortedByCompletedAt(t *testing.T) {
// Create temporary database
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "test.db")

database, err := Open(dbPath)
if err != nil {
t.Fatalf("failed to open database: %v", err)
}
defer database.Close()
defer os.Remove(dbPath)

// Create tasks in specific order - older tasks first
task1 := &Task{
Title: "First task - closed early",
Status: StatusBacklog,
Type: TypeCode,
Project: "test",
}
task2 := &Task{
Title: "Second task - closed late",
Status: StatusBacklog,
Type: TypeCode,
Project: "test",
}
task3 := &Task{
Title: "Third task - not closed",
Status: StatusBacklog,
Type: TypeCode,
Project: "test",
}

// Create tasks
if err := database.CreateTask(task1); err != nil {
t.Fatalf("failed to create task1: %v", err)
}
if err := database.CreateTask(task2); err != nil {
t.Fatalf("failed to create task2: %v", err)
}
if err := database.CreateTask(task3); err != nil {
t.Fatalf("failed to create task3: %v", err)
}

// Close task1 first
if err := database.UpdateTaskStatus(task1.ID, StatusDone); err != nil {
t.Fatalf("failed to close task1: %v", err)
}

// Then close task2 (so task2 has later completed_at)
if err := database.UpdateTaskStatus(task2.ID, StatusDone); err != nil {
t.Fatalf("failed to close task2: %v", err)
}

// List all tasks including closed
tasks, err := database.ListTasks(ListTasksOptions{IncludeClosed: true})
if err != nil {
t.Fatalf("failed to list tasks: %v", err)
}

if len(tasks) != 3 {
t.Fatalf("expected 3 tasks, got %d", len(tasks))
}

// Find done tasks - the one closed most recently (task2) should appear before task1
var doneTasks []*Task
for _, task := range tasks {
if task.Status == StatusDone {
doneTasks = append(doneTasks, task)
}
}

if len(doneTasks) != 2 {
t.Fatalf("expected 2 done tasks, got %d", len(doneTasks))
}

// task2 (closed later) should be first
if doneTasks[0].ID != task2.ID {
t.Errorf("expected task2 (most recently closed) to be first, got task%d", doneTasks[0].ID)
}
if doneTasks[1].ID != task1.ID {
t.Errorf("expected task1 (closed earlier) to be second, got task%d", doneTasks[1].ID)
}
}

func TestCreateTaskSavesLastType(t *testing.T) {
// Create temporary database
tmpDir := t.TempDir()
Expand Down