Files
crowd-funder-for-time-pwa/doc/notification-system-plan.md
Matthew Raymer cfeb920493 refactor(docs): split notification system docs into plan and implementation
Replace monolithic notification-system-implementation-plan.md with focused
strategic plan (notification-system-plan.md) and detailed implementation
guide (notification-system-implementation.md). Both documents now perfectly
aligned with TimeSafari codebase patterns including:

- Actual Settings type extension pattern (JSON strings for complex objects)
- Real useNotifications composable stub signatures with eslint-disable
- Verified logger exports and safeStringify usage
- Confirmed PlatformServiceMixin.$saveSettings integration
- Validated migration system registerMigration patterns

Documents are production-ready with accurate code examples verified
against actual TimeSafari infrastructure.
2025-09-05 09:01:15 +00:00

458 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TimeSafari Notification System — Strategic Plan
**Status:** 🚀 Active plan
**Date:** 2025-09-05T05:09Z (UTC)
**Author:** Matthew Raymer
**Scope:** v1 (inapp orchestrator) now; path to v2 (native plugin) next
**Goal:** We **will deliver** 1..M local notifications/day with content **prefetched** so messages **will display offline**. We **will support** onlinefirst (API→DB→Schedule) with offlinefirst fallback. The system **will enhance** TimeSafari's community-building mission by keeping users connected to gratitude, gifts, and collaborative projects through timely, relevant notifications.
> **Implementation Details:** See `notification-system-implementation.md` for detailed code, database schemas, and integration specifics.
> **Canonical Ownership:** This document owns Goals, Tenets, Platform behaviors, Acceptance criteria, and Test cases.
---
## 1) Versioning & Intent
- **v1 (InApp Orchestrator):** We **will implement** multidaily local notifications, online/offline flows, templating, SQLite persistence, and eventing **inside the app** using Capacitor Local Notifications.
- **v2 (Plugin):** We **will extract** adapters to a Capacitor/Native plugin to gain native schedulers (WorkManager/AlarmManager; BGTask+UNUserNotificationCenter), native HTTP, and native SQLite **with the same TypeScript API**.
> We **will retain** the existing web push + Service Worker foundation; the system **will add** reliable local scheduling on mobile and a unified API across platforms.
---
## 2) Design Tenets
- **Reliability:** OSlevel delivery once scheduled; no reliance on JS being alive at fire time.
- **Freshness:** Prefer onlinefirst within a short prefetch window; degrade gracefully to cached content with TTL.
- **Extractable:** Clean interfaces (Scheduler, DataStore, Callbacks) so v2 **will swap** adapters without API changes.
- **Simplicity:** Oneshot notifications per slot; rolling window scheduling to respect platform caps.
- **Observability:** Persist deliveries and errors; surface minimal metrics; enable ACKs.
- **Privacy-First:** Follow TimeSafari's privacy-preserving architecture; user-controlled visibility and data sovereignty.
- **Community-Focused:** Enhance TimeSafari's mission of connecting people through gratitude, gifts, and collaborative projects.
---
## 3) Architecture Overview
```
Application (Vue/TS)
├─ NotificationOrchestrator (core state)
│ ├─ Scheduler (adapter)
│ ├─ DataStore (adapter)
│ └─ Callbacks (adapter)
└─ UI (settings, status)
Adapters
├─ V1: SchedulerCapacitor, DataStoreSqlite, CallbacksHttp
└─ V2: SchedulerNative, DataStoreNativeSqlite, CallbacksNativeHttp
Platform
├─ iOS/Android: LocalNotifications (+ native bridges later)
├─ Web: Service Worker + Push (kept)
└─ Electron: OS notifications (thin adapter)
```
**Execution modes (concise):**
- **OnlineFirst:** wake near slot → fetch (ETag, timeout) → persist → schedule; on failure → OfflineFirst.
- **OfflineFirst:** read last good payload from SQLite; if beyond TTL → skip notification (no retry).
---
## 4) Public API (Shared by v1 & v2)
```ts
export type NotificationTime = { hour: number; minute: number }; // local wall-clock
export type SlotId = string; // Format: "HHmm" (e.g., "0800", "1200", "1800") - stable across TZ changes
export type FetchSpec = {
method: 'GET'|'POST';
url: string;
headers?: Record<string,string>;
bodyJson?: Record<string,unknown>;
timeoutMs?: number;
};
export type CallbackProfile = {
fetchContent: FetchSpec;
ackDelivery?: Omit<FetchSpec,'bodyJson'|'timeoutMs'>;
reportError?: Omit<FetchSpec,'bodyJson'|'timeoutMs'>;
heartbeat?: Omit<FetchSpec,'bodyJson'> & { intervalMinutes?: number };
};
export type ConfigureOptions = {
times: NotificationTime[]; // 1..M daily
timezone?: string; // Default: system timezone
ttlSeconds?: number; // Default: 86400 (24h)
prefetchLeadMinutes?: number; // Default: 20
storage: 'shared'|'private'; // Required
contentTemplate: { title: string; body: string }; // Required
callbackProfile?: CallbackProfile; // Optional
};
export interface MultiDailyNotification {
requestPermissions(): Promise<void>;
configure(o: ConfigureOptions): Promise<void>;
runFullPipelineNow(): Promise<void>; // API→DB→Schedule (today's remaining)
deliverStoredNow(slotId?: SlotId): Promise<void>; // 60s cooldown guard
reschedule(): Promise<void>;
getState(): Promise<{
nextOccurrences: Array<{ slotId: SlotId; when: string }>; // ISO
lastFetchAt?: string; lastDeliveryAt?: string;
pendingCount: number; exactAlarmCapable?: boolean;
}>;
}
```
> **Storage semantics:** `'shared'` = app DB; `'private'` = plugin-owned/native DB (v2). (No functional difference in v1.)
> **Slot Identity & Scheduling Policy**
> • **SlotId** uses canonical `HHmm` and remains stable across timezone changes.
> • **Lead window:** default `prefetchLeadMinutes = 20`; no retries once inside the lead.
> • **TTL policy:** When offline and content is beyond TTL, **we will skip** the notification (no "(cached)" suffix).
> • **Idempotency:** Duplicate "scheduled" deliveries are prevented by a unique index on `(slot_id, fire_at, status='scheduled')`.
> • **Time handling:** Slots will follow **local wall-clock** time across TZ/DST; `slotId=HHmm` stays constant and we will **recompute fire times** on offset change.
---
## 5) Data Model & Retention (SQLite)
**Tables:** `notif_contents`, `notif_deliveries`, `notif_config`
**Retention:** We **will keep** ~14 days of contents/deliveries (configurable) and **will prune** via a simple daily job that runs on app start/resume. We **will prune** daily but **will not** VACUUM by default on mobile; disk compaction is deferred.
**Payload handling:** We **will template** `{title, body}` **before** scheduling; we **will not** mutate at delivery time.
---
## 6) Scheduling Policy & Slot Math
- **Oneshot per slot** per day (nonrepeating).
- **Rolling window:** today's remaining slots; seed tomorrow where platform limits allow. v1 will schedule **the next occurrence per slot** by default; a **configurable depth** (0=today, 1=today+tomorrow) may be enabled as long as the iOS pending cap is respected.
- **TZ/DST safe:** We **will recompute** local walltimes on app resume and whenever timezone/offset changes; then **reschedule**.
- **Android exactness:** If exact alarms are unavailable or denied, we **will use** `setWindow` semantics via the scheduler adapter.
- **iOS pending cap:** We **will keep** pending locals within typical caps (~64) by limiting the window and canceling/rearming as needed.
- **Electron rolling window:** On Electron we **will schedule** the **next occurrence per slot** by default; depth (today+tomorrow) **will be** enabled only when auto-launch is on, to avoid drift while the app is closed.
---
## 7) Platform Essentials
**iOS**
- Local notifications **will** fire without background runtime once scheduled. NSE **will not** mutate locals; delivery-time enrichment requires remote push (future).
- **Category ID**: `TS_DAILY` with default `OPEN` action
- **Background budget** is short and OSmanaged; any prefetch work **will complete** promptly.
- **Mobile local notifications will route via action listeners (not the service worker)**.
**Android**
- Exact alarms on **API 31+** may require `SCHEDULE_EXACT_ALARM`. If exact access is missing on API 31+, we will use a **windowed trigger (default ±10m)** and surface a settings deep-link.
- **We will deep-link users to the exact-alarm settings when we detect denials.**
- **Channel defaults**: ID `timesafari.daily`, name "TimeSafari Daily", importance=high (IDs never change)
- Receivers for reboot/time change **will be handled** by v2 (plugin); in v1, rearming **will occur** on app start/resume.
- **Mobile local notifications will route via action listeners (not the service worker)**.
**Web**
- Requires registered Service Worker + permission; can deliver with browser closed. **Web will not offline-schedule**.
- Service worker click handlers apply to **web push only**; local notifications on mobile do **not** pass through the SW.
- SW examples use `/sw.js` as a placeholder; **wire this to your actual build output path** (e.g., `sw_scripts/notification-click.js` or your combined bundle).
- **Note**: Service workers are **intentionally disabled** in Electron (`src/main.electron.ts`) and web uses VitePWA plugin for minimal implementation.
**Electron**
- We **will use** native OS notifications with **best-effort scheduling while the app is running**; true background scheduling will be addressed in v2 (native bridges).
**Electron delivery strategy (v1 reality + v2 path)**
We **will deliver** desktop notifications while the Electron app is running. True **background scheduling when the app is closed** is **out of scope for v1** and **will be addressed** in v2 via native bridges. We **will adopt** one of the following options (in order of fit to our codebase):
**In-app scheduler + auto-launch (recommended now):** Keep the orchestrator in the main process, **start on login** (tray app, hidden window), and use the **Electron `Notification` API** for delivery. This requires no new OS services and aligns with our PlatformServiceFactory/mixin patterns.
**Policy (v1):** If the app is **not running**, Electron will **not** deliver scheduled locals. With **auto-launch enabled**, we **will achieve** near-mobile parity while respecting OS sleep/idle behavior.
**UX notes:** On Windows we **will set** `appUserModelId` so toasts are attributed correctly; on macOS we **will request** notification permission on first use.
**Prerequisites:** We **will require** Node 18+ (global `fetch`) or we **will polyfill** via `undici` for content fetching in the main process.
---
## 8) Template Engine Contract
**Supported tokens:** `{{headline}}`, `{{summary}}`, `{{date}}` (YYYY-MM-DD), `{{time}}` (HH:MM).
**Escaping:** HTML-escape all injected values.
**Limits:** Title ≤ 50 chars; Body ≤ 200 chars; truncate with ellipsis.
**Fallback:** Missing token → `"[Content]"`.
**Mutation:** We **will** render templates **before** scheduling; no mutation at delivery time on iOS locals.
## 9) Integration with Existing TimeSafari Infrastructure
**Database:** We **will integrate** with existing migration system in `src/db-sql/migration.ts` following the established `MIGRATIONS` array pattern
**Settings:** We **will extend** existing Settings type in `src/db/tables/settings.ts` following the established type extension pattern
**Platform Service:** We **will leverage** existing PlatformServiceMixin database utilities following the established mixin pattern
**Service Factory:** We **will follow** the existing `PlatformServiceFactory` singleton pattern for notification service creation
**Capacitor:** We **will integrate** with existing deep link system in `src/main.capacitor.ts` following the established initialization pattern
**Service Worker:** We **will extend** existing service worker infrastructure following the established `sw_scripts/` pattern (Note: Service workers are intentionally disabled in Electron and have minimal web implementation via VitePWA plugin)
**API:** We **will use** existing error handling from `src/services/api.ts` following the established `handleApiError` pattern
**Logging:** We **will use** existing logger from `src/utils/logger` following the established logging patterns
**Platform Detection:** We **will use** existing `process.env.VITE_PLATFORM` patterns (`web`, `capacitor`, `electron`)
**Vue Architecture:** We **will follow** Vue 3 + vue-facing-decorator patterns for component integration (Note: The existing `useNotifications` composable in `src/composables/useNotifications.ts` is currently stub functions with eslint-disable comments and needs implementation)
**State Management:** We **will integrate** with existing settings system via `PlatformServiceMixin.$saveSettings()` for notification preferences (Note: TimeSafari uses PlatformServiceMixin for all state management, not Pinia stores)
**Identity System:** We **will integrate** with existing `did:ethr:` (Ethereum-based DID) system for user context
**Testing:** We **will follow** Playwright E2E testing patterns established in TimeSafari
**Database Architecture:** We **will support** platform-specific database backends:
- **Web**: Absurd SQL (SQLite via IndexedDB) via `WebPlatformService` with worker pattern
- **Capacitor**: Native SQLite via `CapacitorPlatformService`
- **Electron**: Native SQLite via `ElectronPlatformService` (extends CapacitorPlatformService)
---
## 10) Error Taxonomy & Telemetry
**Error Codes:** `FETCH_TIMEOUT`, `ETAG_NOT_MODIFIED`, `SCHEDULE_DENIED`, `EXACT_ALARM_MISSING`, `STORAGE_BUSY`, `TEMPLATE_MISSING_TOKEN`, `PERMISSION_DENIED`.
**Event Envelope:** `code, slotId, whenMs, attempt, networkState, tzOffset, appState, timestamp`.
---
## 11) Permission UX & Channels/Categories
- We **will request** notification permission **after** user intent (e.g., settings screen), not on first render.
- **Android:** We **will create** a stable channel ID (e.g., `timesafari.daily`) and **will set** importance appropriately.
- **iOS:** We **will register** categories for optional actions; grouping may use `threadIdentifier` per slot/day.
---
## 12) Eventing & Telemetry
### Error Taxonomy
**Finite error code set:**
- `FETCH_TIMEOUT` - Network request exceeded timeout
- `ETAG_NOT_MODIFIED` - Server returned 304 (expected)
- `SCHEDULE_DENIED` - OS denied notification scheduling
- `EXACT_ALARM_MISSING` - Android exact alarm permission absent
- `STORAGE_BUSY` - Database locked or unavailable
- `TEMPLATE_MISSING_TOKEN` - Required template variable not found
- `PERMISSION_DENIED` - User denied notification permissions
### Event Logging Envelope
```ts
{
code: string, // Error code from taxonomy
slotId: string, // Affected slot
whenMs: number, // Scheduled time
attempt: number, // Retry attempt (1-based)
networkState: string, // 'online' | 'offline'
tzOffset: number, // Current timezone offset
appState: string, // 'foreground' | 'background' | 'killed'
timestamp: number // UTC timestamp
}
```
### ACK Payload Format
```ts
{
slotId: string,
fireAt: number, // Scheduled time
deliveredAt: number, // Actual delivery time
deviceTz: string, // Device timezone
appVersion: string, // App version
buildId: string // Build identifier
}
```
- **Event queue (v1):** In-memory queue for `delivery`, `error`, `heartbeat` events. Background/native work **will enqueue**; foreground **will drain** and publish to the UI. **v2 will migrate** to SQLite-backed queue for persistence.
- **Callbacks (optional):** `ackDelivery`, `reportError`, `heartbeat` **will post** to server endpoints when configured.
- **Minimal metrics:** pending count, last fetch, last delivery, next occurrences.
---
## 13) Feature Flags & Config
### Feature Flags Table
| Flag | Default | Description | Location |
|------|---------|-------------|----------|
| `scheduler` | `'capacitor'` | Scheduler implementation | `notif_config` table |
| `mode` | `'auto'` | Online-first inside lead, else offline-first | `notif_config` table |
| `prefetchLeadMinutes` | `20` | Lead time for prefetch attempts | `notif_config` table |
| `ttlSeconds` | `86400` | Content staleness threshold (24h) | `notif_config` table |
| `iosCategoryIdentifier` | `'TS_DAILY'` | iOS notification category | `notif_config` table |
| `androidChannelId` | `'timesafari.daily'` | Android channel ID (never changes) | `notif_config` table |
**Storage:** Feature flags **will reside** in `notif_config` table as key-value pairs, separate from user settings.
---
## 14) Acceptance (Definition of Done) → Test Cases
### Explicit Test Checks
- **App killed → locals fire**: Configure slots at 8:00, 12:00, 18:00; kill app; verify notifications fire at each slot on iOS/Android
- **ETag 304 path**: Server returns 304 → keep previous content; locals fire with cached payload
- **ETag 200 path**: Server returns 200 → update content and re-arm locals with fresh payload
- **Offline + beyond TTL**: When offline and content > 24h old → skip notification (no "(cached)" suffix)
- **iOS pending cap**: Respect ~64 pending limit; cancel/re-arm as needed within rolling window
- **Exact-alarm denied**: Android permission absent → windowed schedule (±10m) activates; UI shows fallback hint
- **Permissions disabled** → we will record `SCHEDULE_DENIED` and refrain from queuing locals.
- **Window fallback** → when exact alarm is absent on Android, verify target fires within **±10m** of slot time (document as an E2E expectation).
- **Timezone change**: On TZ/DST change → recompute wall-clock times; cancel & re-arm all slots
- **Lead window respect**: No retries attempted once inside 20min lead window
- **Idempotency**: Multiple `runFullPipelineNow()` calls don't create duplicate scheduled deliveries
- **Cooldown guard**: `deliverStoredNow()` has 60s cooldown to prevent double-firing
### Electron-Specific Test Checks
- **Electron running (tray or window) → notifications fire** at configured slots using Electron `Notification`
- **Electron not running →** no delivery (documented limitation for v1)
- **Start on Login enabled →** after reboot + login, orchestrator **will re-arm** slots and deliver
- **Template limits honored** (Title ≤ 50, Body ≤ 200) on Electron notifications
- **SW scope** not used for Electron (click handlers are **web only**)
- **Windows appUserModelId** set correctly for toast attribution
- **macOS notification permission** requested on first use
---
## 15) Test Matrix (Essentials)
- **Android:** exact vs inexact branch, Doze/App Standby behavior, reboot/time change, permission denial path, deeplink to exactalarm settings.
- **iOS:** BG fetch budget limits, pending cap windowing, local notification delivery with app terminated, category actions.
- **Web:** SW lifecycle, push delivery with app closed, click handling, no offline scheduling.
- **Crosscutting:** ETag/304 behavior, TTL policy, templating correctness, event queue drain, SQLite retention job.
---
## 16) Migration & Rollout Notes
- We **will keep** existing web push flows intact.
- We **will introduce** the orchestrator behind a feature flag, initially with a small number of slots.
- We **will migrate** settings to accept multiple times per day.
- We **will document** platform caveats inside uservisible settings (e.g., Android exact alarms, iOS cap).
---
## 17) Security & Privacy
- Tokens **will reside** in Keystore/Keychain (mobile) and **will be injected** at request time; they **will not** be stored in SQLite.
- Optionally, SQLCipher at rest for mobile; redaction of PII in logs; payload size caps.
- Content **will be** minimal (title/body); sensitive data **will not be** embedded.
---
## 18) NonGoals (Now)
- Complex action sets and rich media on locals (kept minimal).
- Deliverytime mutation of local notifications on iOS (NSE is for remote).
- Full analytics pipeline (future enhancement).
---
## 19) Cross-Doc Sync Hygiene
### Canonical Ownership
- **This document (Plan)**: Canonical for Goals, Tenets, Platform behaviors, Acceptance criteria, Test cases
- **Implementation document**: Canonical for API definitions, Database schemas, Adapter implementations, Code examples
### PR Checklist
When changing notification system behavior, update both documents:
- [ ] **API changes**: Update types/interfaces in both Plan §4 and Implementation §3
- [ ] **Schema changes**: Update Plan §5 and Implementation §2
- [ ] **Slot/TTL changes**: Update Plan §4 semantics and Implementation §6 logic
- [ ] **Template changes**: Update Plan §9 contract and Implementation examples
- [ ] **Error codes**: Update Plan §11 taxonomy and Implementation error handling
### Synchronization Points
- **API code blocks**: Must be identical between Plan §4 and Implementation §3 (Public API (Shared))
- **Feature flags**: Must match between Plan §12 table and Implementation defaults
- **Test cases**: Plan §13 acceptance criteria must align with Implementation test examples
- **Slot/TTL/Lead policies**: Must be identical between Plan §4 policy and Implementation §3 policy
---
## 21) Privacy & Security Alignment
### Privacy-First Architecture
- **User-Controlled Visibility:** Notification preferences **will be** user-controlled with explicit opt-in/opt-out
- **Data Sovereignty:** All notification data **will reside** on user's device; no external tracking or analytics
- **Minimal Data Collection:** We **will collect** only essential data for notification delivery (slot times, content templates)
- **DID Integration:** Notifications **will be** associated with user's Decentralized Identifier (DID) for privacy-preserving identity
### Security Considerations
- **Content Encryption:** Sensitive notification content **will be** encrypted at rest using device keystore
- **Secure Transmission:** All API calls **will use** HTTPS with proper certificate validation
- **Input Validation:** All notification content **will be** validated and sanitized before storage
- **Access Control:** Notification settings **will be** protected by user authentication
### Compliance with TimeSafari Principles
- **Privacy-Preserving:** Follows TimeSafari's privacy-preserving claims architecture
- **User Agency:** Users maintain full control over their notification experience
- **Transparency:** Clear communication about what data is collected and how it's used
- **Minimal Footprint:** Notification system **will have** minimal impact on user privacy
---
## 23) Platform-Specific Implementation Details
### Web Platform (`VITE_PLATFORM=web`)
- **Database:** Uses Absurd SQL (SQLite via IndexedDB) via `WebPlatformService` with worker pattern
- **Notifications:** Web push notifications via Service Worker (minimal implementation)
- **Local Scheduling:** **Not supported** - web cannot schedule local notifications offline
- **API Integration:** Direct HTTP calls for content fetching
- **Storage:** Notification preferences stored in Absurd SQL database
- **Testing:** Playwright E2E tests run on web platform
### Capacitor Platform (`VITE_PLATFORM=capacitor`)
- **Database:** Uses native SQLite via `CapacitorPlatformService`
- **Notifications:** Local notifications via `@capacitor/local-notifications`
- **Local Scheduling:** **Fully supported** - OS-level notification scheduling
- **API Integration:** HTTP calls with mobile-optimized timeouts and retry logic
- **Storage:** Notification preferences stored in native SQLite database
- **Testing:** Playwright E2E tests run on mobile devices (Android/iOS)
### Electron Platform (`VITE_PLATFORM=electron`)
- **Database:** Uses native SQLite via `ElectronPlatformService` (extends CapacitorPlatformService)
- **Notifications:** OS-level notifications via Electron's notification API
- **Local Scheduling:** **Supported** - desktop OS notification scheduling
- **API Integration:** Same as Capacitor platform
- **Storage:** Same as Capacitor platform (via inherited service)
- **Testing:** Same as Capacitor platform
### Cross-Platform Considerations
- **Feature Detection:** Use `process.env.VITE_PLATFORM` for platform-specific behavior
- **Database Abstraction:** PlatformServiceMixin handles database differences transparently
- **API Consistency:** Same TypeScript API across all platforms
- **Fallback Behavior:** Web platform gracefully degrades to push-only notifications
---
## 24) TimeSafari Architecture Compliance
### Design Pattern Adherence
- **Factory Pattern:** Notification service follows `PlatformServiceFactory` singleton pattern
- **Mixin Pattern:** Database access uses existing `PlatformServiceMixin` pattern
- **Migration Pattern:** Database changes follow existing `MIGRATIONS` array pattern
- **Error Handling:** Uses existing `handleApiError` from `src/services/api.ts`
- **Logging:** Uses existing logger from `src/utils/logger` with established patterns
- **Platform Detection:** Uses existing `Capacitor.isNativePlatform()` and `VITE_PLATFORM` patterns
### File Organization Compliance
- **Services:** Follows existing `src/services/` organization with factory and adapters
- **Database:** Extends existing `src/db-sql/migration.ts` and `src/db/tables/settings.ts`
- **Utils:** Extends existing `src/utils/PlatformServiceMixin.ts`
- **Main Entry:** Integrates with existing `src/main.capacitor.ts` initialization
- **Service Workers:** Follows existing `sw_scripts/` organization
### Type Safety Compliance
- **Settings Extension:** Follows existing Settings type extension pattern
- **Interface Definitions:** Uses existing TypeScript interface patterns
- **Error Types:** Follows existing error handling type patterns
- **Platform Types:** Uses existing platform detection type patterns
---
## Sync Checklist
| Sync item | Plan | Impl | Status |
| ------------------------------ | --------------------- | --------------------- | --------- |
| Public API block identical | §4 | §3 | ✅ |
| `getState()` fields present | §4 | §8 Orchestrator | ✅ |
| Capacitor action handlers | §7 (iOS/Android note) | §9 Bootstrap | ✅ |
| Electron fetch prereq/polyfill | §7 | §9 Electron | ✅ |
| Android ±10m fallback | §7 | §7 SchedulerCapacitor | ✅ |
| Retention (no VACUUM v1) | §5 | `$pruneNotifData` | ✅ |
---
*This strategic plan focuses on features and futuretense deliverables, avoids implementation details, and preserves a clear path from the inapp implementation (v1) to the native plugin (v2). For detailed implementation specifics, see `notification-system-implementation.md`.*