diff options
| -rw-r--r-- | docs/superpowers/specs/2026-07-12-widget-quick-add-design.md | 35 |
1 files changed, 35 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-07-12-widget-quick-add-design.md b/docs/superpowers/specs/2026-07-12-widget-quick-add-design.md new file mode 100644 index 0000000..a025554 --- /dev/null +++ b/docs/superpowers/specs/2026-07-12-widget-quick-add-design.md @@ -0,0 +1,35 @@ +# Widget Quick Add — Design + +## Context + +Fourth of five widget features (refresh → overdue badge → clickable reschedule → **quick add** → recurrence). Decided without a user Q&A round (per explicit instruction to proceed autonomously). + +## Key constraint from grounding + +Jetpack Glance / RemoteViews widgets do not support a reliable in-widget text-entry field across launchers and Android versions. Every existing interactive flow in this widget that needs more than a tap (reschedule's date picker) already follows the same pattern: tap a widget element → launch a full `ComponentActivity` with a Compose `ModalBottomSheet` → do the real work there → close. Quick add follows the identical pattern rather than attempting in-widget text input. + +A reusable server-side creation path already exists for the web UI: `HandleUnifiedAdd` (`internal/handlers/handlers.go:608`) creates a `models.Task` via `h.store.CreateNativeTask`. It's form-encoded and behind session/cookie auth, not the widget's bearer-token JSON API, so it isn't directly reusable from the widget client — but the underlying `CreateNativeTask` call is the same one this feature will use, just from a new bearer-token-protected JSON endpoint that mirrors the existing `/api/widget/complete` and `/api/widget/reschedule` handlers. + +## Design + +**Server (Go):** +- New handler `HandleWidgetAdd` in `internal/handlers/widget.go`, matching the shape of `HandleWidgetComplete`: decode a JSON body `{"title": "..."}`, reject empty/whitespace-only titles with 400, create an undated doot task (`models.Task{ID: newID(), Content: title, Priority: 1}` — no due date; a quick-add task starts in the same "undated/floating" bucket as any other undated doot task), call `h.store.CreateNativeTask`, return 200 on success or 500 on a store error. +- New route in `cmd/dashboard/main.go`, alongside the other widget routes: `r.With(widgetAuth).Post("/api/widget/add", h.HandleWidgetAdd)`. + +**Android — data layer:** +- `WidgetRepository` gains `suspend fun addTask(title: String): Result<Unit>`, POSTing to `/api/widget/add`. +- Unlike `reschedule`/`complete` (which manually string-interpolate their JSON bodies from internal IDs that can't contain special characters), `addTask` takes arbitrary free-text user input, so it must not use manual string interpolation — a title containing a `"` or `\` would produce invalid or unsafe JSON. It uses a small `@Serializable data class WidgetAddRequest(val title: String)` encoded via the file's existing `json` (`kotlinx.serialization.json.Json`) instance instead. + +**Android — UI:** +- New composable `QuickAddButton()` in `DootWidget.kt`, same 24dp tap-target pattern as `RefreshButton`, placed in the "TODAY" header row next to the refresh button (order: `Text("TODAY")` — `defaultWeight()` — `QuickAddButton()` — `RefreshButton(isRefreshing)`). Uses a new `ic_add.xml` drawable (24dp plus-sign vector, same stroke style as the other icons). +- Tapping it launches a new `QuickAddActivity : ComponentActivity`, structurally a near-twin of `TaskDetailActivity`: a `ModalBottomSheet` containing a `TextField` (title input, autofocus) and an "Add" `Button`. Submitting: calls `WidgetRepository.addTask(title)`, and on success, `fetchAndPersist` + `DootWidget().updateAll()` + `finish()`, matching the existing reschedule success flow exactly. Empty/blank titles disable the "Add" button (no server round-trip for obviously-invalid input — the server still validates independently as the source of truth). +- `QuickAddActivity` is a separate class from `TaskDetailActivity` (not a mode flag on the existing one) — they have different triggers (widget button vs. task row tap), different required inputs (no id/source/completable for creation), and keeping them separate avoids a sprawling "does five different things" activity, consistent with this codebase's existing per-purpose-Activity pattern. + +## Testing + +- Server: unit test for `HandleWidgetAdd` covering (a) success — valid title creates a task, verified via the store, (b) empty title → 400, matching the existing `TestHandleWidgetComplete_*` test style in `widget_test.go` (uses `setupTestDB`). +- 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 + +Recurrence display gets its own spec (the fifth and last feature). |
