diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-07-13 07:11:04 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-07-13 07:11:04 +0000 |
| commit | 44abf42ed45aa8f285e7ce031cbb9ef1ade667ea (patch) | |
| tree | dbc21cadc2750c6e39e7f7613be0b3f73790c445 /docs/superpowers/specs/2026-07-12-widget-clickable-reschedule-design.md | |
| parent | 8310f802dd9fc6ef5dff0be7f640f79c5b39987f (diff) | |
| parent | 4126fe4f56a6eb9703a084d4793a597f37bf2867 (diff) | |
Merge github/master: reconcile with parallel widget work
Another session pushed 27 commits in parallel covering quick-add, event
detail popups, recurrence display, overdue badges, a manual refresh button,
and its own fix for the same overdue-tasks bug (via a separate
GetOverdueNativeTasks fetch folded into BuildTimeline, rather than widening
GetNativeTasksByDateRange's bound directly). Reconciled rather than blindly
taking one side:
- Reverted GetNativeTasksByDateRange to its original bounded query and kept
upstream's GetOverdueNativeTasks + BuildTimeline fold-in as the sole
overdue mechanism for native tasks, to avoid double-counting overdue
items (my widened query + their separate fetch would have both returned
them). Re-pointed the regression test at the now-correct contract and
added a store-level test for GetOverdueNativeTasks directly.
- Kept my GetGoogleTasksByDateRange fix as-is (single unbounded query) --
upstream never touched Google Tasks overdue handling, so there's no
duplication risk there.
- Rewove WidgetRoot's LazyColumn structure (added for scrolling) around
upstream's new header buttons, pinned all-day event rows, and the
enhanced TomorrowSection, none of which were written LazyColumn-aware
since that work landed on this side only.
- Combined both sides' additions to TaskDetailActivity/TaskDetailSheet
(description-edit detail popup + due-date reschedule label) and
WidgetRepository/Actions (optimistic local removal + refresh button
wiring) -- these were independent, non-overlapping features that both
needed to survive.
- Renumbered the migration collision: both sides independently added a
migration numbered 022. Card-description was already applied to the live
production DB under that filename earlier this session (migrations are
tracked by filename), so it keeps 022; the recurring-event-id migration,
never deployed under any name here, moves to 023.
Verified: go build clean, full test suite passes (only the two
pre-existing agent-handler failures and the pre-existing models package
build error remain, both confirmed unrelated via git stash before this
session began), and a dry run against a copy of the live production
database applies both migrations cleanly with no re-run conflicts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
Diffstat (limited to 'docs/superpowers/specs/2026-07-12-widget-clickable-reschedule-design.md')
| -rw-r--r-- | docs/superpowers/specs/2026-07-12-widget-clickable-reschedule-design.md | 41 |
1 files changed, 41 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-07-12-widget-clickable-reschedule-design.md b/docs/superpowers/specs/2026-07-12-widget-clickable-reschedule-design.md new file mode 100644 index 0000000..c456908 --- /dev/null +++ b/docs/superpowers/specs/2026-07-12-widget-clickable-reschedule-design.md @@ -0,0 +1,41 @@ +# Widget Clickable Date/Time Reschedule — Design + +## Context + +Third of five widget features (refresh → overdue badge → **clickable reschedule** → quick add → recurrence). Today `TaskDetailSheet` (the bottom-sheet popup opened by tapping a task row) shows a separate "Reschedule" `OutlinedButton` for doot tasks, which opens a `DatePickerDialog` — but the popup never displays the task's *current* due date anywhere. The user wants the due-date value itself to be the clickable element, replacing the standalone button. + +Decided without a user Q&A round (per explicit instruction to proceed autonomously). + +## Key finding from grounding + +`WidgetItem.start` is deliberately `null` for every doot task regardless of whether it has a real due date — this is intentional, existing behavior from the 2026-07-12 all-day-pinning fix (`TestTimelineItemToWidgetItem_AllDayTask_KeepsFloatingBehavior`): doot tasks are "floating" items positioned by the client's `SlotPacker`, and `start` is reserved for that scheduling/positioning role. There is currently **no field carrying a doot task's raw due date to the Android client at all** — `TaskRow`'s `Intent` extras don't include it either. This must be added; it doesn't already exist under a different name. + +## Design + +**Server (Go):** add a new field, independent of `Start`/`IsAllDay` semantics, so this change cannot regress the floating-task positioning behavior the prior fix protects: +- `models.WidgetItem` gains `DueDate *time.Time `json:"due_date,omitempty"``. +- `TimelineItemToWidgetItem`: when `item.Type == TimelineItemTypeTask && item.Source == "doot" && !item.Time.IsZero()`, set `wi.DueDate = &item.Time`. This is the *only* new condition — it does not touch the existing `Start`/`End` logic at all. + +**Android — data layer:** +- `WidgetItem.kt` gains `@SerialName("due_date") val dueDate: String? = null`. + +**Android — passing the value into the popup:** +- `TaskRow`'s `detailIntent` (`DootWidget.kt`) gains `putExtra(TaskDetailActivity.EXTRA_DUE_DATE, task.dueDate)` (nullable string extra). +- `TaskDetailActivity.onCreate` reads it: `val dueDate = intent.getStringExtra(EXTRA_DUE_DATE)`, passes to `TaskDetailSheet`. + +**Android — UI:** +- `TaskDetailSheet` gains a `dueDate: String?` parameter. +- The existing "Reschedule" `OutlinedButton` (only ever shown for `source == "doot"`) is replaced by a tappable row: an outlined `Row` styled like the current button (same border/shape/padding) containing formatted text — + - If `dueDate != null`: parse and format as `"Due " + MMM d` (e.g. "Due Jul 15"), using `java.time.LocalDate`/`DateTimeFormatter` (already available; `TaskDetailActivity.kt` already imports `java.util.Calendar`/`TimeZone` for the existing picker, this adds the modern `java.time` formatter alongside it). + - If `dueDate == null`: show `"No due date · tap to schedule"`. + - Tapping the row opens the same `DatePickerDialog` that exists today (unchanged picker logic) — only the trigger element changes from a button labeled "Reschedule" to this date-display row. +- No time-of-day editing: the server's reschedule endpoint (`HandleWidgetReschedule`) only accepts a `YYYY-MM-DD` date and sets the task to midnight — this was already true before this feature and stays true. "date/time" in the request is read as "the current due-date value that's displayed," not a request for new time-granularity editing, since doot tasks don't carry time-of-day today (confirmed: every existing `due_date` in the production DB is midnight-valued). + +## Testing + +- Server: unit test for `TimelineItemToWidgetItem` confirming `DueDate` is set for a doot task with a real due date, and confirming it's still `nil` for (a) a doot task with no due date and (b) a non-doot item (e.g. a calendar event), so this can't leak into other item types. +- Android: no unit-test surface (consistent with the rest of this session's widget UI work) — verified by build + manual on-device check. + +## Out of scope + +Quick add and recurrence display — each gets its own spec. |
