summaryrefslogtreecommitdiff
path: root/internal/handlers
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-12 07:40:30 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-12 07:40:30 +0000
commitf01b2924bf0b62fc20af0d09581d101418455b1c (patch)
tree703bed9c8286fa4f48c5085c2a95268f9c6b07b7 /internal/handlers
parentaf0d59d78524127f6cc76e43a7e397da01788d97 (diff)
fix(timeline): include overdue native tasks in timeline and widget
GetNativeTasksByDateRange's SQL bound (due_date >= start) excluded any task overdue from a previous day before ComputeDaySection ever got a chance to mark it IsOverdue, so both the web Timeline view and the widget API (which both call BuildTimeline with start = today) silently dropped overdue tasks entirely -- only the Tasks tab (unbounded GetNativeTasks) showed them. Added GetOverdueNativeTasks and folded its results into BuildTimeline alongside the ranged fetch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
Diffstat (limited to 'internal/handlers')
-rw-r--r--internal/handlers/timeline_logic.go28
-rw-r--r--internal/handlers/timeline_logic_test.go50
2 files changed, 77 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()