--- name: project-doot-isallday-midnight-bug description: "FIXED 2026-08-10 (commit 9a3ece4): dated doot-native/Google tasks landed in the widget's undated/floating bucket (rendering as due 'today') instead of their correct day, because every task due-date is midnight-anchored and trips the IsAllDay 'is this all-day' heuristic" metadata: node_type: memory type: project originSessionId: 647d8c60-e602-440e-b8b3-d8422fcbcb1d --- **Status: FIXED and deployed**, commit `9a3ece4` on 2026-08-10. Originally diagnosed 2026-07-29 and deliberately set aside; recurred as a fresh report ("tasks scheduled for tomorrow show up right on the webview but on today in the widget") which prompted implementing it. **Root cause:** doot-native and Google Tasks due dates are always midnight-anchored (even with a real due date), which trips `TimelineItem.ComputeDaySection`'s "midnight means no specific time" heuristic and sets `IsAllDay = true` on them. `TimelineItemToWidgetItem` then left `wi.Start` nil for any Task/GTask with `IsAllDay == true`, and the Android client's undated/floating pool (`DootWidget.kt`'s `floating` list) has zero per-day awareness — it just packs items forward from "now" — so a task due tomorrow rendered as if due today. **Important correction to the original 2026-07-29 fix plan:** that plan proposed gating `ComputeDaySection`'s midnight heuristic to `Event`/`Meal` types only. Re-verifying it against the live code (rather than implementing on trust) surfaced a real problem: it would also flip `IsAllDay` to `false` for *same-day* dated tasks, moving them on the **web** from the untimed-item strip into the hourly calendar grid with a fabricated "12:00 AM" time label — fixing the widget by breaking the web's already-correct rendering. `IsAllDay`/`ComputeDaySection` is shared by both clients; the web buckets by `DaySection` (computed from `Time`, unaffected by `IsAllDay`) and only uses `IsAllDay` for the same-day untimed-vs-grid visual choice, which the naive fix would have broken. **Actual fix implemented:** added `TimelineItem.Undated bool` — a signal genuinely independent of `IsAllDay`, true only for the caller's two genuinely-dateless constructions in `internal/handlers/timeline_logic.go` (nativeUndated tasks, gtasks with no due date), both of which use `Time = now` as a layout placeholder rather than a real date. `ComputeDaySection`/`IsAllDay` were left completely untouched (zero web-side risk). `TimelineItemToWidgetItem` routes on `Undated` instead: dated Task/GTask items now get `wi.Start` populated (so the Android client's existing, already-correct Start-based day bucketing — `todayScheduledEvents`/`tomorrowItems` in `DootWidget.kt` — runs for them), while genuinely undated ones keep the original nil-Start floating treatment. Also fixed the identical latent bug in `wi.DueDate`'s guard in the same pass: it only checked `!Time.IsZero()`, but `nativeUndated`'s `Time=now` placeholder is non-zero, so an undated task's Android detail popup would show a fabricated due date. Now guards on `!Undated` too. **Verification:** new tests `TestTimelineItemToWidgetItem_DatedGTaskGetsStart` and `_UndatedTaskWithPlaceholderTime_NilDueDate`, plus three existing tests updated because they had encoded the bug's "Start stays nil for any IsAllDay task" as expected behavior. Proved the fix by reverting `widget.go` to the pre-fix condition against a real backup and confirming exactly the three tests exercising the new behavior failed, then restored and confirmed byte-identical. `go build`/`vet`/`test` all clean; deployed to tungsten and health-checked. **Takeaway for future similar reports:** when a proposed fix and its own diagnosis are more than a session old, re-derive the trace against current code before implementing rather than trusting the old plan verbatim — the plan itself had a real, would-have-shipped bug (web-side regression) that only surfaced from re-checking, not from the original diagnosis.