summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/handlers/timeline_logic.go28
-rw-r--r--internal/handlers/timeline_logic_test.go50
-rw-r--r--internal/store/native_tasks.go20
3 files changed, 97 insertions, 1 deletions
diff --git a/internal/handlers/timeline_logic.go b/internal/handlers/timeline_logic.go
index 67ba09d..e7c1279 100644
--- a/internal/handlers/timeline_logic.go
+++ b/internal/handlers/timeline_logic.go
@@ -125,7 +125,10 @@ func BuildTimeline(ctx context.Context, s *store.Store, start, end time.Time) ([
}
}
- // 6. Fetch native (doot-owned) tasks
+ // 6. Fetch native (doot-owned) tasks due within the range, plus any
+ // overdue tasks (due before the range's start) so they still surface --
+ // GetNativeTasksByDateRange's lower bound would otherwise drop them
+ // before ComputeDaySection ever gets a chance to mark them IsOverdue.
nativeDated, err := s.GetNativeTasksByDateRange(start, end)
if err != nil {
log.Printf("Warning: failed to read native tasks: %v", err)
@@ -149,6 +152,29 @@ func BuildTimeline(ctx context.Context, s *store.Store, start, end time.Time) ([
}
}
+ nativeOverdue, err := s.GetOverdueNativeTasks(start)
+ if err != nil {
+ log.Printf("Warning: failed to read overdue native tasks: %v", err)
+ } else {
+ for _, task := range nativeOverdue {
+ if task.DueDate == nil {
+ continue
+ }
+ item := models.TimelineItem{
+ ID: task.ID,
+ Type: models.TimelineItemTypeTask,
+ Title: task.Content,
+ Time: *task.DueDate,
+ Description: task.Description,
+ OriginalItem: task,
+ IsCompleted: task.Completed,
+ Source: "doot",
+ }
+ item.ComputeDaySection(now)
+ items = append(items, item)
+ }
+ }
+
nativeUndated, err := s.GetUndatedNativeTasks()
if err != nil {
log.Printf("Warning: failed to read undated native tasks: %v", err)
diff --git a/internal/handlers/timeline_logic_test.go b/internal/handlers/timeline_logic_test.go
index 0b7bc37..7d3f6b5 100644
--- a/internal/handlers/timeline_logic_test.go
+++ b/internal/handlers/timeline_logic_test.go
@@ -293,6 +293,56 @@ func TestBuildTimeline_ReadsCalendarEventsFromStore(t *testing.T) {
}
}
+// TestBuildTimeline_IncludesOverdueNativeTasks proves the 2026-07-12 fix: a
+// native task whose due_date is BEFORE the requested range's start must
+// still appear in the timeline, marked IsOverdue, instead of being silently
+// dropped. Root cause: GetNativeTasksByDateRange's SQL bound (due_date >=
+// start) excluded overdue tasks from ever being fetched, so ComputeDaySection
+// never got a chance to set IsOverdue -- both the web Timeline view and the
+// widget API (which both call BuildTimeline with a start of "today") showed
+// zero overdue tasks even though the Tasks tab (which calls the unbounded
+// GetNativeTasks) showed them fine.
+func TestBuildTimeline_IncludesOverdueNativeTasks(t *testing.T) {
+ db, cleanup := setupTestDB(t)
+ defer cleanup()
+
+ now := time.Now()
+ today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+ overdueDate := today.AddDate(0, 0, -12) // 12 days in the past
+
+ if err := db.CreateNativeTask(models.Task{
+ ID: "overdue-1",
+ Content: "Pay the water bill",
+ DueDate: &overdueDate,
+ }); err != nil {
+ t.Fatalf("Failed to create native task: %v", err)
+ }
+
+ start := today
+ end := today.AddDate(0, 0, 2)
+
+ items, err := BuildTimeline(context.Background(), db, start, end)
+ if err != nil {
+ t.Fatalf("BuildTimeline failed: %v", err)
+ }
+
+ var found *models.TimelineItem
+ for i := range items {
+ if items[i].ID == "overdue-1" {
+ found = &items[i]
+ }
+ }
+ if found == nil {
+ t.Fatal("expected overdue native task to appear in timeline, but it was missing")
+ }
+ if !found.IsOverdue {
+ t.Error("expected overdue native task to have IsOverdue = true")
+ }
+ if found.Title != "Pay the water bill" {
+ t.Errorf("Title: got %q, want %q", found.Title, "Pay the water bill")
+ }
+}
+
func TestFetchCalendarEvents_CacheFallbackOnAPIError(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()
diff --git a/internal/store/native_tasks.go b/internal/store/native_tasks.go
index 6f8f999..f43a160 100644
--- a/internal/store/native_tasks.go
+++ b/internal/store/native_tasks.go
@@ -46,6 +46,26 @@ func (s *Store) GetNativeTasksByDateRange(start, end time.Time) ([]models.Task,
return scanNativeTasks(rows)
}
+// GetOverdueNativeTasks returns non-completed native tasks whose due date is
+// before the given time. BuildTimeline calls this alongside
+// GetNativeTasksByDateRange, whose lower bound excludes anything due before
+// the requested range's start -- without this, a task overdue from a
+// previous day never gets fetched at all, so it never reaches
+// ComputeDaySection to be marked IsOverdue.
+func (s *Store) GetOverdueNativeTasks(before time.Time) ([]models.Task, error) {
+ rows, err := s.db.Query(`
+ SELECT id, content, description, project_name, due_date, priority, completed, labels, created_at
+ FROM native_tasks
+ WHERE completed = 0 AND due_date IS NOT NULL AND due_date < ?
+ ORDER BY due_date ASC, priority DESC
+ `, before)
+ if err != nil {
+ return nil, err
+ }
+ defer func() { _ = rows.Close() }()
+ return scanNativeTasks(rows)
+}
+
// GetUndatedNativeTasks returns non-completed native tasks with no due date.
func (s *Store) GetUndatedNativeTasks() ([]models.Task, error) {
rows, err := s.db.Query(`