docs/plans/2026-07-03-android-home-screen-widget.md
Implements #3818. Based on ilvez's POC (PR #7124, closed) plus the 2026-06-08 review punch-list. Re-implemented on current master (the PR's April base has drifted heavily).
KeyValStore (SQLite) under key widget_data. The native
RemoteViewsFactory reads it. No new storage mechanism.SharedPreferences-backed WidgetDoneQueue (mirrors
WidgetTaskQueue / ReminderDoneQueue patterns), drained by Angular.setPendingIntentTemplate;
the POC's second fill-in intent silently fell into the done-template. Fix: one
broadcast template, fill-ins carry either EXTRA_TASK_ID (→ done) or
EXTRA_OPEN_APP (→ startActivity), branch in onReceive.APPWIDGET_UPDATE remains.getWidgetDoneQueue()
(get-and-clear). Single delivery path; no double setDone(); retires the
'$sanitizedId' string-interpolated JS call.widget_data. For instant checkbox feedback while the app is dead, the factory
overlays isDone=true for IDs currently in WidgetDoneQueue at render time
(peek(), non-clearing). The Angular-vs-native write race disappears structurally;
markDoneInWidgetData is deleted.selectAndroidWidgetData projects
todayIds + task entities + project colors into the exact blob shape. Project color
changes now propagate (POC pulled colors in but excluded them from the distinct key).
Dedupe happens in WidgetDataService via a last-pushed-JSON cache, so all trigger
paths share it.isSyncInProgress$ falling edge. (Push-on-pause kept as belt-and-braces.)AndroidWidgetData interface (TS) + WidgetData.kt parser as
the two named ends of the v:1 contract, locked by a golden-shape unit test on the
serializer and a JVM parse test on the Kotlin side. (typia writer-side assert was
considered and skipped: the object is constructed from typed state, so an assert is
tautological, and a throw would leave the widget stale — worse than pushing.)T/en.json) instead of one hardcoded snack per task.db.close() removal (correct SQLiteOpenHelper pattern,
avoids churn from widget reads) — redone against the current chunked-read code.
All access is @Synchronized on the App-level singleton, so widget-vs-bridge
concurrency stays serialized. Backup-ring round trip must be manually verified on
device (no Robolectric in the project).WidgetData.kt (single parse site, JVM-testable
via org.json:json test dep). optString("projectId", null) JSON-NULL→"null"
footgun avoided via isNull() checks; Angular omits projectId when absent.strings.xml (English) — accepted for v1.Native: widget/{TaskListWidgetProvider,TaskListWidgetService,WidgetData,WidgetDoneQueue}.kt,
CapacitorMainActivity.kt (drain receiver), webview/JavaScriptInterface.kt
(getWidgetDoneQueue, updateWidget), app/KeyValStore.kt, manifest, layouts,
xml/appwidget_info.xml, values/strings.xml, build.gradle (test dep),
test/.../widget/WidgetDataTest.kt.
Angular: features/android/android-widget.model.ts,
features/android/store/android-widget.selectors.ts (+spec),
features/android/widget-data.service.ts (+spec),
features/android/store/android-widget.effects.ts (+spec),
android-interface.ts, root-store/feature-stores.module.ts, en.json (+npm run int).