**Scope:** v1 (in‑app 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** online‑first (API→DB→Schedule) with offline‑first fallback. The system **will enhance** TimeSafari's community-building mission by keeping users connected to gratitude, gifts, and collaborative projects through timely, relevant notifications.
**Status:** 🚀 Active plan
**Date:** 2025-09-05T05:09Z (UTC)
**Author:** Matthew Raymer
**Scope:** v1 (in‑app 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**
online‑first (API→DB→Schedule) with offline‑first 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.
> **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 (In‑App Orchestrator):** We **will implement** multi‑daily 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**.
- **v1 (In‑App Orchestrator):** We **will implement** multi‑daily 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.
> 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:** OS‑level delivery once scheduled; no reliance on JS being alive at fire time.
- **Freshness:** Prefer online‑first 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:** One‑shot notifications per slot; rolling window scheduling to respect platform caps.
**Scheduler Adapter:** All notification arming must go through the Scheduler adapter to honor platform timing semantics (exact alarms vs. windowed fallback).
> • **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.
> • **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.
**Tables:**`notif_contents`, `notif_deliveries`, `notif_config` (see
Implementation document for complete schema)
**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.
**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.
**Payload handling:** We **will template**`{title, body}`**before** scheduling;
we **will not** mutate at delivery time.
---
## 6) Scheduling Policy & Slot Math
- **One‑shot per slot** per day (non‑repeating).
- **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 wall‑times 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/re‑arming 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.
- **One‑shot per slot** per day (non‑repeating).
- **Rolling window:** today's remaining slots; seed tomorrow where platform
limits allow.
- **TZ/DST safe:** We **will recompute** local wall‑times 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/re‑arming 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
## 7) Timing & Network Requirements
**iOS**
- Local notifications **will** fire without background runtime once scheduled. NSE **will not** mutate locals; delivery-time enrichment requires remote push (future).
**Summary:** The notification system uses lightweight, ETag-aware content
fetching with single attempts inside lead windows. All timing constants and
detailed network policies are defined in the Implementation document.
**Key Policies:**
- **Lead policy:** The lead window governs **online-first fetch attempts**, not
arming. We **will arm** locals **whenever the app runs**, using the freshest
available payload.
- **TTL policy:** If offline and content is beyond TTL, we will **skip** the
notification (no "cached" suffix).
- **Idempotency:** Duplicate "scheduled" rows are prevented by a unique index on
`(slot_id, fire_at, status='scheduled')`.
- **Wall-clock rule:** Slots will follow **local wall-clock** across TZ/DST;
`slotId=HHmm` stays constant and we will **recompute fire times** on offset change.
- **Resume debounce:** On app resume/open we will **debounce** pipeline
entry points by **30s** per app session to avoid burst fetches.
- **No scheduled background network in v1 (mobile):** Local notifications
**will deliver offline once armed**, but **we will not** run timed network jobs
when the app is terminated. **Network prefetch will occur only while the app is
running** (launch/resume/inside lead). Server-driven push (Web SW) and OS
background schedulers are a **v2** capability.
**Platform-Specific Network Access:**
- **iOS:** Foreground/recently backgrounded only; no JS wake when app is killed
- **Android:** Exact alarms vs. windowed triggers based on permissions
- **Web:** Service Worker for push notifications only
- **Electron:** App-running only; no background network access
**Optional Background Prefetch (v1):**
- **Background Runner (optional, v1):** We **will integrate** Capacitor's
Background Runner to **opportunistically prefetch** content on iOS/Android when
the OS grants background time. This **will not** provide clock-precise execution
and **will not** run after user-terminate on iOS. It **will not** be treated as
a scheduler. We **will continue** to *arm* local notifications via our rolling
window regardless of Runner availability. When Runner fires near a slot (inside
`prefetchLeadMinutes`), it **will** refresh content (ETag, 12s timeout) and,
behind a flag, **may** cancel & re-arm that slot with the fresh template if within
TTL. If no budget or failure, the previously armed local **will** still deliver.
**Implementation Details:** See Implementation document for complete timing
constants table, network request profiles, and platform-specific enforcement.
---
## 8) Platform Essentials
**iOS**
- Local notifications **will** fire without background runtime once scheduled.
- **Category ID**: `TS_DAILY` with default `OPEN` action
- **Background budget** is short and OS‑managed; any prefetch work**will complete** promptly.
- **Background budget** is short and OS‑managed; any prefetch work
**will complete** promptly.
- **Mobile local notifications will route via action listeners (not the service worker)**.
- Background Runner **will** offer **opportunistic** network wake (no guarantees;
short runtime; iOS will not run after force-quit). Locals **will** still deliver
offline once armed.
**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.
**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, re‑arming **will occur** on app start/resume.
- **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,
re‑arming **will occur** on app start/resume.
- **Mobile local notifications will route via action listeners (not the service worker)**.
- Background Runner **will** offer **opportunistic** network wake (no guarantees;
short runtime; iOS will not run after force-quit). Locals **will** still deliver
offline once armed.
**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.
**Web**
**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).
- Requires registered Service Worker + permission; can deliver with browser closed.
**Web will not offline-schedule**.
- Service Worker click handlers are for **web push only**;
**mobile locals bypass 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.
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):
**Electron**
**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.
- 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).
**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.
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):
**UX notes:** On Windows we **will set**`appUserModelId` so toasts are attributed correctly; on macOS we **will request** notification permission on first use.
**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.
**Prerequisites:** We **will require** Node 18+ (global `fetch`) or we **will polyfill** via `undici` for content fetching in the main process.
**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.
**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
**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:** 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)
- **Capacitor**: Native SQLite via `CapacitorPlatformService`
- **Electron**: Native SQLite via `ElectronPlatformService` (extends CapacitorPlatformService)
- **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.
**Implementation:** See Implementation document for complete error taxonomy,
event logging envelope, ACK payload format, and telemetry events.
*This strategic plan focuses on features and future‑tense deliverables, avoids implementation details, and preserves a clear path from the in‑app implementation (v1) to the native plugin (v2). For detailed implementation specifics, see `notification-system-implementation.md`.*
*This strategic plan focuses on features and future‑tense deliverables, avoids implementation details, and preserves a clear path from the in‑app orchestrator (v1) to native plugin (v2). For executive overview, see `notification-system-executive-summary.md`. For complete implementation details, see `notification-system-implementation.md`.*
Establish consistent z-index values across the TimeSafari application to ensure proper layering of UI elements.
## Result
This document defines the z-index hierarchy for all UI components.
## Use/Run
Reference these values when implementing new components or modifying existing ones to maintain consistent layering.
## Z-Index Hierarchy
@@ -51,7 +54,7 @@ Reference these values when implementing new components or modifying existing on
## Collaboration Hooks
- **Reviewers**: Frontend team, UI/UX designers
- **Sign-off checklist**:
- **Sign-off checklist**:
- [ ] All new components follow z-index guidelines
- [ ] Existing components updated to use defined values
- [ ] Cross-browser testing completed
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.