From a427a9e66f2ba4eecdf42be3ad5cf462a98bf755 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Thu, 4 Sep 2025 13:09:40 +0000 Subject: [PATCH] chore: still tweaking plan --- ...notification-system-implementation-plan.md | 63 +++++++++++++++---- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/doc/notification-system-implementation-plan.md b/doc/notification-system-implementation-plan.md index c2cb51a6..592530a1 100644 --- a/doc/notification-system-implementation-plan.md +++ b/doc/notification-system-implementation-plan.md @@ -4,14 +4,35 @@ **Date**: 2025-01-27T15:00Z (UTC) **Status**: 🚀 **ACTIVE** - Surgical PR implementation for Capacitor platforms +## Purpose + +We **will deliver** 1..M daily local notifications with content fetched beforehand so they **will display offline**. We **will support** online-first (API→DB→Schedule) with offline-first fallback. New v1 ships in-app; New v2 extracts to a native Capacitor plugin with the same API. + ## What we'll ship (v1 in-app) -- Multi-daily **one-shot local notifications** (rolling window) -- **Online-first** (ETag, 10–15s timeout) with **offline-first** fallback -- **SQLite** persistence + **14-day** retention -- **Templating**: `{title, body}` with `{{var}}` -- **Event queue**: delivery/error/heartbeat (drained on foreground) -- Same TS API that we can swap to native (v2) later +- **Multi-daily one-shot local notifications** (rolling window; today + tomorrow within iOS pending limits ~64) +- **Online-first** content fetch (**ETag**, 10–15s timeout) with **offline-first** fallback and **TTL** handling ("(cached)" or skip) +- **SQLite persistence** (contents, deliveries, config) with **14-day retention** +- **Templating**: `{title, body}` with `{{var}}` substitution **before** scheduling +- **Event queue** in SQLite: `delivery`, `error`, `heartbeat`; **drain on foreground** +- **Resilience hooks**: re-arm on **app resume**, and when **timezone/offset** changes + +--- + +## New v2 (Plugin) — What It Adds + +- **Native schedulers** (WorkManager+AlarmManager / BGTask+UNUserNotificationCenter) incl. exact/inexact handling +- **Native HTTP** + **native SQLite** for reliability/perf +- **Server-assist (silent push)** to wake at T-lead and prefetch +- **Same TS API**, adapters swapped + +--- + +## Feature Flags + +- `scheduler: 'capacitor'|'native'` +- `mode: 'online-first'|'offline-first'|'auto'` +- `prefetchLeadMinutes`, `ttlSeconds` --- @@ -35,6 +56,24 @@ --- +## Changes (surgical) +/src/services/notifs/ + types.ts + NotificationOrchestrator.ts + adapters/ + DataStoreSqlite.ts + SchedulerCapacitor.ts + CallbacksHttp.ts +/migrations/ + 00XX_notifs.sql +/sw_scripts/ + notification-click.js # (or merge into sw_scripts-combined.js) +/app/bootstrap/ + notifications.ts # init + feature flags +``` + +--- + ## Changes (surgical) ### 1) Dependencies @@ -587,11 +626,11 @@ self.addEventListener('notificationclick', (event) => { ## Acceptance (v1) -- Locals fire at configured slots with app **killed** -- Online-first w/ **ETag** + **timeout**; falls back to offline-first (TTL respected) -- DB retains 14 days; retention job prunes -- Foreground drains queued events -- One-line adapter swap path ready for v2 +- Local notifications **fire at configured slots with the app killed** (iOS/Android) +- **Online-first** → 304/ETag respected; **offline-first** fallback; **TTL** policy respected +- **14-day retention** job prunes contents & deliveries +- **TZ/DST** change triggers **reschedule()**; rolling window adheres to iOS pending cap +- **Feature flags** present: `scheduler: 'capacitor'|'native'`, `mode: 'online-first'|'offline-first'|'auto'` --- @@ -601,3 +640,5 @@ self.addEventListener('notificationclick', (event) => { - **Web Fallback**: Web platform will use existing service worker + push notifications - **Electron**: Will need separate implementation using native notification APIs - **Feature Flags**: Can be toggled per platform in bootstrap configuration +- **Android UX**: Settings deep-link to grant exact-alarm special access on API 31+ +- **DST/TZ Guardrail**: On app resume, if offset/TZ changed → `reschedule()` and re-arm today's slots