summaryrefslogtreecommitdiff
path: root/internal/handlers/widget.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers/widget.go')
-rw-r--r--internal/handlers/widget.go36
1 files changed, 27 insertions, 9 deletions
diff --git a/internal/handlers/widget.go b/internal/handlers/widget.go
index 915909e..1f6911b 100644
--- a/internal/handlers/widget.go
+++ b/internal/handlers/widget.go
@@ -70,10 +70,24 @@ func TimelineItemToWidgetItem(item models.TimelineItem) models.WidgetItem {
// client can pin them to the top of the correct day's section instead of
// losing them in the floating-task hourly-slot packer, which has no
// concept of "all day" and can push a slot past the visible grid range
- // entirely. Tasks keep the existing nil-Start "floating" treatment
- // regardless of IsAllDay -- only Start is set for them (never End), and
- // only when they have a real time.
- if !item.Time.IsZero() && (!item.IsAllDay || item.Type == models.TimelineItemTypeEvent) {
+ // entirely.
+ //
+ // Dated Task/GTask items (item.Undated == false) get the same Start
+ // treatment as Events, for the same reason -- their client-side day
+ // bucketing (Android's WidgetRoot todayScheduledEvents/tomorrowItems
+ // split) is keyed entirely off Start, so a nil Start drops them into the
+ // undated/floating pool regardless of their actual due date. That pool
+ // has zero day-awareness (see DootWidget.kt's `floating` list), so a
+ // task due tomorrow rendered as if due today (2026-08-10 report). This
+ // is deliberately gated on Undated, not IsAllDay -- IsAllDay stays true
+ // for these tasks (doot-native/Google Tasks due dates are ALWAYS
+ // midnight-anchored even with a real due date, see
+ // reference_google_tasks_due_date_utc_quirk), which is why the simpler
+ // "just check IsAllDay" fix doesn't work here: Card and truly-undated
+ // Task/GTask items must keep the exact opposite behavior (nil Start),
+ // and only Undated tells them apart.
+ isDatedTask := (item.Type == models.TimelineItemTypeTask || item.Type == models.TimelineItemTypeGTask) && !item.Undated
+ if !item.Time.IsZero() && (!item.IsAllDay || item.Type == models.TimelineItemTypeEvent || isDatedTask) {
t := item.Time
wi.Start = &t
if item.Type == models.TimelineItemTypeEvent {
@@ -94,11 +108,15 @@ func TimelineItemToWidgetItem(item models.TimelineItem) models.WidgetItem {
}
}
- // DueDate is independent of Start/IsAllDay -- doot tasks deliberately
- // keep Start nil (see the "floating task" doc comment above) so the
- // client's SlotPacker positions them, but the Android detail popup
- // still needs to know the real due date to display and reschedule it.
- if item.Type == models.TimelineItemTypeTask && item.Source == "doot" && !item.Time.IsZero() {
+ // DueDate is a separate signal from Start/IsAllDay -- populated whenever
+ // a doot task genuinely has a due date (Undated == false), regardless of
+ // whether Start also got set above, so the Android detail popup can
+ // display/reschedule it. Guarding on !item.Undated (not just
+ // !item.Time.IsZero()) matters because nativeUndated tasks use Time=now
+ // as a layout placeholder, not a zero value -- without this guard,
+ // an undated task's detail popup would show a fabricated "due" date of
+ // whatever moment the request happened to run.
+ if item.Type == models.TimelineItemTypeTask && item.Source == "doot" && !item.Time.IsZero() && !item.Undated {
due := item.Time
wi.DueDate = &due
}