summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-15 18:30:16 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-16 02:54:17 +0000
commit37c323481d2e124e75bf3480dbc3bf53cad915e0 (patch)
tree18dcd09b2d9114fc7e849a52f7409d6750999cd0
parentb55c01eb9c48d2af754ff22c55deab6f0832e358 (diff)
feat(tasks): thread project color from BuildTimeline to WidgetItem
Projects are fetched once per BuildTimeline call into a color lookup map, not queried per-task -- same pattern as RecurringEventID's existing TimelineItem->WidgetItem threading.
-rw-r--r--internal/handlers/timeline_logic.go13
-rw-r--r--internal/handlers/widget.go4
-rw-r--r--internal/models/timeline.go1
-rw-r--r--internal/models/widget.go1
4 files changed, 19 insertions, 0 deletions
diff --git a/internal/handlers/timeline_logic.go b/internal/handlers/timeline_logic.go
index 6fddd1d..b90b61e 100644
--- a/internal/handlers/timeline_logic.go
+++ b/internal/handlers/timeline_logic.go
@@ -17,6 +17,16 @@ func BuildTimeline(ctx context.Context, s *store.Store, start, end time.Time) ([
var items []models.TimelineItem
now := config.Now()
+ // Fetch all projects once into a color lookup map
+ projectColors := make(map[string]string)
+ if projects, err := s.GetProjects(); err != nil {
+ log.Printf("Warning: failed to read projects: %v", err)
+ } else {
+ for _, p := range projects {
+ projectColors[p.ID] = p.Color
+ }
+ }
+
// 1. Fetch Meals - combine multiple items for same date+mealType
meals, err := s.GetMealsByDateRange(start, end)
if err != nil {
@@ -147,6 +157,7 @@ func BuildTimeline(ctx context.Context, s *store.Store, start, end time.Time) ([
OriginalItem: task,
IsCompleted: task.Completed,
Source: "doot",
+ ProjectColor: projectColors[task.ProjectID],
}
item.ComputeDaySection(now)
items = append(items, item)
@@ -170,6 +181,7 @@ func BuildTimeline(ctx context.Context, s *store.Store, start, end time.Time) ([
OriginalItem: task,
IsCompleted: task.Completed,
Source: "doot",
+ ProjectColor: projectColors[task.ProjectID],
}
item.ComputeDaySection(now)
items = append(items, item)
@@ -191,6 +203,7 @@ func BuildTimeline(ctx context.Context, s *store.Store, start, end time.Time) ([
IsCompleted: task.Completed,
Source: "doot",
IsAllDay: true,
+ ProjectColor: projectColors[task.ProjectID],
}
item.ComputeDaySection(now)
items = append(items, item)
diff --git a/internal/handlers/widget.go b/internal/handlers/widget.go
index 1bb706e..05c0bdb 100644
--- a/internal/handlers/widget.go
+++ b/internal/handlers/widget.go
@@ -97,6 +97,10 @@ func TimelineItemToWidgetItem(item models.TimelineItem) models.WidgetItem {
wi.DueDate = &due
}
+ if item.ProjectColor != "" {
+ wi.ProjectColor = &item.ProjectColor
+ }
+
return wi
}
diff --git a/internal/models/timeline.go b/internal/models/timeline.go
index 6e48f91..1313712 100644
--- a/internal/models/timeline.go
+++ b/internal/models/timeline.go
@@ -44,6 +44,7 @@ type TimelineItem struct {
// Source-specific metadata
ListID string `json:"list_id,omitempty"` // For Google Tasks
RecurringEventID string `json:"recurring_event_id,omitempty"` // For calendar events
+ ProjectColor string `json:"project_color,omitempty"` // For doot-native tasks with a project assigned
}
// ComputeDaySection sets the DaySection, IsOverdue, and IsAllDay based on the item's time
diff --git a/internal/models/widget.go b/internal/models/widget.go
index cbaf19c..da13008 100644
--- a/internal/models/widget.go
+++ b/internal/models/widget.go
@@ -18,6 +18,7 @@ type WidgetItem struct {
URL string `json:"url,omitempty"`
Completable bool `json:"completable"` // true = doot task (checkbox shown)
RecurringEventID string `json:"recurring_event_id,omitempty"`
+ ProjectColor *string `json:"project_color,omitempty"`
}
// WidgetResponse is the full /api/widget response body.