Browse Source

docs: apply documentation references model agents directive to notification system docs

- Remove duplicate sync checklists from notification-system-plan.md
- Fix markdown table formatting (remove double pipes) in executive summary
- Streamline cross-document references and eliminate redundant content
- Consolidate canonical ownership statements across all three docs
- Improve document structure and readability per directive guidelines

Files modified:
- doc/notification-system-executive-summary.md
- doc/notification-system-implementation.md
- doc/notification-system-plan.md

Compliance: Follows @docs/documentation_references_model_agents.mdc directive
for eliminating redundancy, centralizing context, and optimizing reference placement.
pull/196/head
Matthew Raymer 2 weeks ago
parent
commit
79b226e7d2
  1. 87
      .cursor/rules/docs/documentation_references_model_agents.mdc
  2. 19
      doc/notification-system-executive-summary.md
  3. 9
      doc/notification-system-implementation.md
  4. 487
      doc/notification-system-plan.md

87
.cursor/rules/docs/documentation_references_model_agents.mdc

@ -0,0 +1,87 @@
---
title: Documentation, References, and Model Agent Use
version: 1.1
alwaysApply: true
scope: code, project-plans
---
# Directive on Documentation, References, and Model Agent Use in Code and Project Plans
To ensure clarity, efficiency, and high-value documentation within code and project plans—and to leverage **model agents** (AI- or automation-based assistants) effectively—contributors must follow these rules:
---
## 1. Documentation and References Must Add Clear Value
- Only include documentation, comments, or reference links when they provide *new, meaningful information* that assists understanding or decision-making.
- Avoid duplicating content already obvious in the codebase, version history, or linked project documents.
---
## 2. Eliminate Redundant or Noisy References
- Remove references that serve no purpose beyond filling space.
- Model agents may automatically flag and suggest removal of trivial references (e.g., links to unchanged boilerplate or self-evident context).
---
## 3. Explicit Role of Model Agents
Model agents are **active participants** in documentation quality control. Their tasks include:
- **Relevance Evaluation**: Automatically analyze references for their substantive contribution before inclusion.
- **Redundancy Detection**: Flag duplicate or trivial references across commits, files, or tasks.
- **Context Linking**: Suggest appropriate higher-level docs (designs, ADRs, meeting notes) when a code change touches multi-stage or cross-team items.
- **Placement Optimization**: Recommend centralization of references (e.g., in plan overviews, ADRs, or merge commit messages) rather than scattered low-value inline references.
- **Consistency Monitoring**: Ensure references align with team standards (e.g., ADR template, architecture repo, or external policy documents).
Contributors must treat agent recommendations as **first-pass reviews** but remain accountable for final human judgment.
---
## 4. Contextual References for Complex Items
- Use **centralized references** for multi-stage features (e.g., architectural docs, research threads).
- Keep inline code comments light; push broader context into centralized documents.
- Model agents may auto-summarize complex chains of discussion and attach them as a single reference point.
---
## 5. Centralization of Broader Context
- Store overarching context (design docs, proposals, workflows) in accessible, well-indexed places.
- Model agents should assist by **generating reference maps** that track where docs are cited across the codebase.
---
## 6. Focused Documentation
- Documentation should explain **why** and **how** decisions are made, not just what was changed.
- Model agents can auto-generate first-pass explanations from commit metadata, diffs, and linked issues—but humans must refine them for accuracy and intent.
---
## 7. Review and Accountability
- Reviewers and team leads must reject submissions containing unnecessary or low-quality documentation.
- Model agent outputs are aids, not replacements—contributors remain responsible for **final clarity and relevance**.
---
## 8. Continuous Improvement and Agent Feedback Loops
- Encourage iterative development of model agents so their evaluations become more precise over time.
- Contributions should include **feedback on agent suggestions** (e.g., accepted, rejected, or corrected) to train better future outputs.
- Agents should log patterns of “rejected” suggestions for refinement.
---
## 9. Workflow Overview (Mermaid Diagram)
```mermaid
flowchart TD
A[Contributor] -->|Writes Code & Draft Docs| B[Model Agent]
B -->|Evaluates References| C{Relevant?}
C -->|Yes| D[Suggest Placement & Context Links]
C -->|No| E[Flag Redundancy / Noise]
D --> F[Contributor Refines Docs]
E --> F
F --> G[Reviewer]
G -->|Approves / Requests Revisions| H[Final Documentation]
G -->|Feedback on Agent Suggestions| B
```
---
✅ **Outcome:** By integrating disciplined contributor standards with **model agent augmentation**, the team achieves documentation that is consistently *relevant, concise, centralized, and decision-focused*. AI ensures coverage and noise reduction, while humans ensure precision and judgment.

19
doc/notification-system-executive-summary.md

@ -9,11 +9,7 @@
## Executive Overview ## Executive Overview
TimeSafari will implement a **multi-platform notification system** that TimeSafari will implement a **multi-platform notification system** that delivers **1-3 daily notifications** to keep users connected to gratitude, gifts, and collaborative projects. The system will work across **iOS, Android, Web, and Electron** platforms with **offline-first reliability** and **privacy-preserving architecture**.
delivers **1-3 daily notifications** to keep users connected to gratitude, gifts,
and collaborative projects. The system will work across **iOS, Android, Web,
and Electron** platforms with **offline-first reliability** and
**privacy-preserving architecture**.
### Business Value ### Business Value
@ -31,7 +27,6 @@ delivers **1-3 daily notifications** to keep users connected to gratitude, gifts
**Scope:** Multi-daily local notifications with online/offline flows **Scope:** Multi-daily local notifications with online/offline flows
**Key Capabilities:** **Key Capabilities:**
- **Local Notifications:** OS-level delivery on mobile/desktop - **Local Notifications:** OS-level delivery on mobile/desktop
- **Web Push:** Service Worker-based notifications for web - **Web Push:** Service Worker-based notifications for web
- **Offline Reliability:** Notifications fire even when app is closed - **Offline Reliability:** Notifications fire even when app is closed
@ -44,7 +39,6 @@ delivers **1-3 daily notifications** to keep users connected to gratitude, gifts
**Scope:** Native background scheduling and enhanced capabilities **Scope:** Native background scheduling and enhanced capabilities
**Key Capabilities:** **Key Capabilities:**
- **Native Background Work:** OS-level background tasks - **Native Background Work:** OS-level background tasks
- **Enhanced Scheduling:** More precise timing and reliability - **Enhanced Scheduling:** More precise timing and reliability
- **Advanced Features:** Rich media and complex actions - **Advanced Features:** Rich media and complex actions
@ -118,14 +112,9 @@ delivers **1-3 daily notifications** to keep users connected to gratitude, gifts
## Document References ## Document References
- **Strategic Plan:** `notification-system-plan.md` - Goals, tenets, platform - **Strategic Plan:** `notification-system-plan.md` - Goals, tenets, platform behaviors, acceptance criteria
behaviors, acceptance criteria - **Implementation Guide:** `notification-system-implementation.md` - Complete code, database schemas, integration specifics
- **Implementation Guide:** `notification-system-implementation.md` - Complete
code, database schemas, integration specifics
- **This Summary:** High-level overview for executive decision-making
--- ---
*This executive summary provides the essential business context and strategic *This executive summary provides the essential business context and strategic direction for TimeSafari's notification system. For detailed technical specifications and implementation guidance, refer to the referenced documents.*
direction for TimeSafari's notification system. For detailed technical
specifications and implementation guidance, refer to the referenced documents.*

9
doc/notification-system-implementation.md

@ -4,13 +4,10 @@
**Date:** 2025-09-05T05:09Z (UTC) **Date:** 2025-09-05T05:09Z (UTC)
**Author:** Matthew Raymer **Author:** Matthew Raymer
**Scope:** Detailed implementation for v1 (in‑app orchestrator) **Scope:** Detailed implementation for v1 (in‑app orchestrator)
**Goal:** Complete implementation guide with code, database schemas, and **Goal:** Complete implementation guide with code, database schemas, and integration specifics.
integration specifics.
> **Strategic Overview:** See `notification-system-plan.md` for high-level > **Strategic Overview:** See `notification-system-plan.md` for high-level strategy, architecture, and planning details.
> strategy, architecture, and planning details. > **Canonical Ownership:** This document owns API definitions, Database schemas, Adapter implementations, and Code examples.
> **Canonical Ownership:** This document owns API definitions, Database
> schemas, Adapter implementations, and Code examples.
--- ---

487
doc/notification-system-plan.md

@ -4,52 +4,31 @@
**Date:** 2025-09-05T05:09Z (UTC) **Date:** 2025-09-05T05:09Z (UTC)
**Author:** Matthew Raymer **Author:** Matthew Raymer
**Scope:** v1 (in‑app orchestrator) now; path to v2 (native plugin) next **Scope:** v1 (in‑app orchestrator) now; path to v2 (native plugin) next
**Goal:** We **will deliver** 1..M local notifications/day with content **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.
**prefetched** so messages **will display offline**. We **will support**
online‑first (API→DB→Schedule) with offline‑first fallback. The system > **Implementation Details:** See `notification-system-implementation.md` for detailed code, database schemas, and integration specifics.
**will enhance** TimeSafari's community-building mission by keeping users > **Canonical Ownership:** This document owns Goals, Tenets, Platform behaviors, Acceptance criteria, and Test cases.
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 ## 1) Versioning & Intent
- **v1 (In‑App Orchestrator):** We **will implement** multi‑daily local - **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.
notifications, online/offline flows, templating, SQLite persistence, and - **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**.
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 > 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.
> system **will add** reliable local scheduling on mobile and a unified API
> across platforms.
--- ---
## 2) Design Tenets ## 2) Design Tenets
- **Reliability:** OS‑level delivery once scheduled; no reliance on JS being alive - **Reliability:** OS‑level delivery once scheduled; no reliance on JS being alive at fire time.
at fire time. - **Freshness:** Prefer online‑first within a short prefetch window; degrade gracefully to cached content with TTL.
- **Freshness:** Prefer online‑first within a short prefetch window; degrade - **Extractable:** Clean interfaces (Scheduler, DataStore, Callbacks) so v2 **will swap** adapters without API changes.
gracefully to cached content with TTL. - **Simplicity:** One‑shot notifications per slot; rolling window scheduling to respect platform caps.
- **Extractable:** Clean interfaces (Scheduler, DataStore, Callbacks) so v2 - **Observability:** Persist deliveries and errors; surface minimal metrics; enable ACKs.
**will swap** adapters without API changes. - **Privacy-First:** Follow TimeSafari's privacy-preserving architecture; user-controlled visibility and data sovereignty.
- **Simplicity:** One‑shot notifications per slot; rolling window scheduling to - **Community-Focused:** Enhance TimeSafari's mission of connecting people through gratitude, gifts, and collaborative projects.
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.
--- ---
@ -77,90 +56,58 @@ Platform
**Execution modes (concise):** **Execution modes (concise):**
- **Online‑First:** wake near slot → fetch (ETag, timeout) → persist → schedule; - **Online‑First:** wake near slot → fetch (ETag, timeout) → persist → schedule; on failure → Offline‑First.
on failure → Offline‑First. - **Offline‑First:** read last good payload from SQLite; if beyond TTL → skip notification (no retry).
- **Offline‑First:** read last good payload from SQLite; if beyond TTL → skip
notification (no retry).
--- ---
## 4) Public API (Shared by v1 & v2) ## 4) Public API (Shared by v1 & v2)
**Core Types & Interface:** See Implementation document for complete API **Core Types & Interface:** See Implementation document for complete API definitions, type interfaces, and design decisions.
definitions, type interfaces, and design decisions.
> **Storage semantics:** `'shared'` = app DB; `'private'` = plugin-owned/native > **Storage semantics:** `'shared'` = app DB; `'private'` = plugin-owned/native DB (v2). (No functional difference in v1.)
> DB (v2). (No functional difference in v1.)
> **Slot Identity & Scheduling Policy** > **Slot Identity & Scheduling Policy**
> • **SlotId** uses canonical `HHmm` and remains stable across timezone > • **SlotId** uses canonical `HHmm` and remains stable across timezone changes.
> changes. > • **Lead window:** default `prefetchLeadMinutes = 20`; no retries once inside the lead.
> • **Lead window:** default `prefetchLeadMinutes = 20`; no retries once inside > • **TTL policy:** When offline and content is beyond TTL, **we will skip** the notification (no "(cached)" suffix).
the lead. > • **Idempotency:** Duplicate "scheduled" deliveries are prevented by a unique index on `(slot_id, fire_at, status='scheduled')`.
> • **TTL policy:** When offline and content is beyond TTL, **we will skip** the > • **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.
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) ## 5) Data Model & Retention (SQLite)
**Tables:** `notif_contents`, `notif_deliveries`, `notif_config` (see **Tables:** `notif_contents`, `notif_deliveries`, `notif_config` (see Implementation document for complete schema)
Implementation document for complete schema)
**Retention:** We **will keep** ~14 days of contents/deliveries (configurable) **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.
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; **Payload handling:** We **will template** `{title, body}` **before** scheduling; we **will not** mutate at delivery time.
we **will not** mutate at delivery time.
--- ---
## 6) Scheduling Policy & Slot Math ## 6) Scheduling Policy & Slot Math
- **One‑shot per slot** per day (non‑repeating). - **One‑shot per slot** per day (non‑repeating).
- **Rolling window:** today's remaining slots; seed tomorrow where platform - **Rolling window:** today's remaining slots; seed tomorrow where platform limits allow.
limits allow. - **TZ/DST safe:** We **will recompute** local wall‑times on app resume and whenever timezone/offset changes; then **reschedule**.
- **TZ/DST safe:** We **will recompute** local wall‑times on app resume and - **Android exactness:** If exact alarms are unavailable or denied, we **will use** `setWindow` semantics via the scheduler adapter.
whenever timezone/offset changes; then **reschedule**. - **iOS pending cap:** We **will keep** pending locals within typical caps (~64) by limiting the window and canceling/re‑arming as needed.
- **Android exactness:** If exact alarms are unavailable or denied, we - **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.
**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) Timing & Network Requirements ## 7) Timing & Network Requirements
**Summary:** The notification system uses lightweight, ETag-aware content **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.
fetching with single attempts inside lead windows. All timing constants and
detailed network policies are defined in the Implementation document.
**Key Policies:** **Key Policies:**
- **Lead policy:** The lead window governs **online-first fetch attempts**, not - **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.
arming. We **will arm** locals **whenever the app runs**, using the freshest - **TTL policy:** If offline and content is beyond TTL, we will **skip** the notification (no "cached" suffix).
available payload. - **Idempotency:** Duplicate "scheduled" rows are prevented by a unique index on `(slot_id, fire_at, status='scheduled')`.
- **TTL policy:** If offline and content is beyond TTL, we will **skip** the - **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.
notification (no "cached" suffix). - **Resume debounce:** On app resume/open we will **debounce** pipeline entry points by **30s** per app session to avoid burst fetches.
- **Idempotency:** Duplicate "scheduled" rows are prevented by a unique index on - **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.
`(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:** **Platform-Specific Network Access:**
@ -171,18 +118,9 @@ background schedulers are a **v2** capability.
**Optional Background Prefetch (v1):** **Optional Background Prefetch (v1):**
- **Background Runner (optional, v1):** We **will integrate** Capacitor's - **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.
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 **Implementation Details:** See Implementation document for complete timing constants table, network request profiles, and platform-specific enforcement.
constants table, network request profiles, and platform-specific enforcement.
--- ---
@ -190,112 +128,69 @@ constants table, network request profiles, and platform-specific enforcement.
**iOS** **iOS**
- Local notifications **will** fire without background runtime once scheduled. - Local notifications **will** fire without background runtime once scheduled. NSE **will not** mutate locals; delivery-time enrichment requires remote push (future).
NSE **will not** mutate locals; delivery-time enrichment requires remote push (future).
- **Category ID**: `TS_DAILY` with default `OPEN` action - **Category ID**: `TS_DAILY` with default `OPEN` action
- **Background budget** is short and OS‑managed; any prefetch work - **Background budget** is short and OS‑managed; any prefetch work **will complete** promptly.
**will complete** promptly.
- **Mobile local notifications will route via action listeners (not the service worker)**. - **Mobile local notifications will route via action listeners (not the service worker)**.
- Background Runner **will** offer **opportunistic** network wake (no guarantees; - 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.
short runtime; iOS will not run after force-quit). Locals **will** still deliver
offline once armed.
**Android** **Android**
- Exact alarms on **API 31+** may require `SCHEDULE_EXACT_ALARM`. If exact access - 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.
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.** - **We will deep-link users to the exact-alarm settings when we detect denials.**
- **Channel defaults**: ID `timesafari.daily`, name "TimeSafari Daily", - **Channel defaults**: ID `timesafari.daily`, name "TimeSafari Daily", importance=high (IDs never change)
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.
- 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)**. - **Mobile local notifications will route via action listeners (not the service worker)**.
- Background Runner **will** offer **opportunistic** network wake (no guarantees; - 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.
short runtime; iOS will not run after force-quit). Locals **will** still deliver
offline once armed.
**Web** **Web**
- Requires registered Service Worker + permission; can deliver with browser closed. - Requires registered Service Worker + permission; can deliver with browser closed. **Web will not offline-schedule**.
**Web will not offline-schedule**. - Service Worker click handlers are for **web push only**; **mobile locals bypass the SW**.
- Service Worker click handlers are for **web push only**; - 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).
**mobile locals bypass the SW**. - **Note**: Service workers are **intentionally disabled** in Electron (`src/main.electron.ts`) and web uses VitePWA plugin for minimal implementation.
- 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** **Electron**
- We **will use** native OS notifications with **best-effort scheduling while the - 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).
app is running**; true background scheduling will be addressed in v2 (native bridges).
**Electron delivery strategy (v1 reality + v2 path)** **Electron delivery strategy (v1 reality + v2 path)**
We **will deliver** desktop notifications while the Electron app is running. True 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):
**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 **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.
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 **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.
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 **UX notes:** On Windows we **will set** `appUserModelId` so toasts are attributed correctly; on macOS we **will request** notification permission on first use.
attributed correctly; on macOS we **will request** notification permission on
first use.
**Prerequisites:** We **will require** Node 18+ (global `fetch`) or we **Prerequisites:** We **will require** Node 18+ (global `fetch`) or we **will polyfill** via `undici` for content fetching in the main process.
**will polyfill** via `undici` for content fetching in the main process.
--- ---
## 8) Template Engine Contract ## 9) Template Engine Contract
**Supported tokens:** `{{headline}}`, `{{summary}}`, `{{date}}` (YYYY-MM-DD), **Supported tokens:** `{{headline}}`, `{{summary}}`, `{{date}}` (YYYY-MM-DD), `{{time}}` (HH:MM).
`{{time}}` (HH:MM).
**Escaping:** HTML-escape all injected values. **Escaping:** HTML-escape all injected values.
**Limits:** Title ≤ 50 chars; Body ≤ 200 chars; truncate with ellipsis. **Limits:** Title ≤ 50 chars; Body ≤ 200 chars; truncate with ellipsis.
**Fallback:** Missing token → `"[Content]"`. **Fallback:** Missing token → `"[Content]"`.
**Mutation:** We **will** render templates **before** scheduling; no mutation at **Mutation:** We **will** render templates **before** scheduling; no mutation at delivery time on iOS locals.
delivery time on iOS locals.
---
## 9) Integration with Existing TimeSafari Infrastructure
## 10) 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 **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 **Settings:** We **will extend** existing Settings type in `src/db/tables/settings.ts` following the established type extension pattern
`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
**Platform Service:** We **will leverage** existing PlatformServiceMixin database **Service Factory:** We **will follow** the existing `PlatformServiceFactory` singleton pattern for notification service creation
utilities following the established mixin pattern **Capacitor:** We **will integrate** with existing deep link system in `src/main.capacitor.ts` following the established initialization pattern
**Service Factory:** We **will follow** the existing `PlatformServiceFactory` **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)
singleton pattern for notification service creation **API:** We **will use** existing error handling from `src/services/api.ts` following the established `handleApiError` pattern
**Capacitor:** We **will integrate** with existing deep link system in **Logging:** We **will use** existing logger from `src/utils/logger` following the established logging patterns
`src/main.capacitor.ts` following the established initialization pattern **Platform Detection:** We **will use** existing `process.env.VITE_PLATFORM` patterns (`web`, `capacitor`, `electron`)
**Service Worker:** We **will extend** existing service worker infrastructure **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)
following the established `sw_scripts/` pattern (Note: Service workers are **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)
intentionally disabled in Electron and have minimal web implementation via **Identity System:** We **will integrate** with existing `did:ethr:` (Ethereum-based DID) system for user context
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 **Testing:** We **will follow** Playwright E2E testing patterns established in TimeSafari
**Database Architecture:** We **will support** platform-specific database backends: **Database Architecture:** We **will support** platform-specific database backends:
@ -305,87 +200,64 @@ TimeSafari uses PlatformServiceMixin for all state management, not Pinia stores)
--- ---
## 10) Error Taxonomy & Telemetry ## 11) Error Taxonomy & Telemetry
**Error Codes:** `FETCH_TIMEOUT`, `ETAG_NOT_MODIFIED`, `SCHEDULE_DENIED`, **Error Codes:** `FETCH_TIMEOUT`, `ETAG_NOT_MODIFIED`, `SCHEDULE_DENIED`, `EXACT_ALARM_MISSING`, `STORAGE_BUSY`, `TEMPLATE_MISSING_TOKEN`, `PERMISSION_DENIED`.
`EXACT_ALARM_MISSING`, `STORAGE_BUSY`, `TEMPLATE_MISSING_TOKEN`, `PERMISSION_DENIED`.
**Event Envelope:** `code, slotId, whenMs, attempt, networkState, tzOffset, **Event Envelope:** `code, slotId, whenMs, attempt, networkState, tzOffset, appState, timestamp`.
appState, timestamp`.
--- ---
## 11) Permission UX & Channels/Categories ## 12) Permission UX & Channels/Categories
- We **will request** notification permission **after** user intent (e.g., - We **will request** notification permission **after** user intent (e.g., settings screen), not on first render.
settings screen), not on first render. - **Android:** We **will create** a stable channel ID (e.g., `timesafari.daily`) and **will set** importance appropriately.
- **Android:** We **will create** a stable channel ID (e.g., `timesafari.daily`) - **iOS:** We **will register** categories for optional actions; grouping may use `threadIdentifier` per slot/day.
and **will set** importance appropriately.
- **iOS:** We **will register** categories for optional actions; grouping may
use `threadIdentifier` per slot/day.
--- ---
## 12) Eventing & Telemetry ## 13) Eventing & Telemetry
**Error Codes:** `FETCH_TIMEOUT`, `ETAG_NOT_MODIFIED`, `SCHEDULE_DENIED`, **Error Codes:** `FETCH_TIMEOUT`, `ETAG_NOT_MODIFIED`, `SCHEDULE_DENIED`, `EXACT_ALARM_MISSING`, `STORAGE_BUSY`, `TEMPLATE_MISSING_TOKEN`, `PERMISSION_DENIED`.
`EXACT_ALARM_MISSING`, `STORAGE_BUSY`, `TEMPLATE_MISSING_TOKEN`, `PERMISSION_DENIED`.
**Event Envelope:** `code, slotId, whenMs, attempt, networkState, tzOffset, **Event Envelope:** `code, slotId, whenMs, attempt, networkState, tzOffset, appState, timestamp`.
appState, timestamp`.
**Implementation:** See Implementation document for complete error taxonomy, **Implementation:** See Implementation document for complete error taxonomy, event logging envelope, ACK payload format, and telemetry events.
event logging envelope, ACK payload format, and telemetry events.
--- ---
## 13) Feature Flags & Config ## 14) Feature Flags & Config
**Key Flags:** `scheduler`, `mode`, `prefetchLeadMinutes`, `ttlSeconds`, **Key Flags:** `scheduler`, `mode`, `prefetchLeadMinutes`, `ttlSeconds`, `iosCategoryIdentifier`, `androidChannelId`, `prefetchRunner`, `runnerRearm`.
`iosCategoryIdentifier`, `androidChannelId`, `prefetchRunner`, `runnerRearm`.
**Storage:** Feature flags **will reside** in `notif_config` table as key-value **Storage:** Feature flags **will reside** in `notif_config` table as key-value pairs, separate from user settings.
pairs, separate from user settings.
**Implementation:** See Implementation document for complete feature flags table **Implementation:** See Implementation document for complete feature flags table with defaults and descriptions.
with defaults and descriptions.
--- ---
## 14) Acceptance (Definition of Done) → Test Cases ## 15) Acceptance (Definition of Done) → Test Cases
### Explicit Test Checks ### Explicit Test Checks
- **App killed → locals fire**: Configure slots at 8:00, 12:00, 18:00; kill - **App killed → locals fire**: Configure slots at 8:00, 12:00, 18:00; kill app; verify notifications fire at each slot on iOS/Android
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 304 path**: Server returns 304 → keep previous content; locals fire - **ETag 200 path**: Server returns 200 → update content and re-arm locals with fresh payload
with cached payload - **Offline + beyond TTL**: When offline and content > 24h old → skip notification (no "(cached)" suffix)
- **ETag 200 path**: Server returns 200 → update content and re-arm locals with - **iOS pending cap**: Respect ~64 pending limit; cancel/re-arm as needed within rolling window
fresh payload - **Exact-alarm denied**: Android permission absent → windowed schedule (±10m) activates; UI shows fallback hint
- **Offline + beyond TTL**: When offline and content > 24h old → skip notification - **Permissions disabled** → we will record `SCHEDULE_DENIED` and refrain from queuing locals.
(no "(cached)" suffix) - **Window fallback** → when exact alarm is absent on Android, verify target fires within **±10m** of slot time (document as an E2E expectation).
- **iOS pending cap**: Respect ~64 pending limit; cancel/re-arm as needed within - **Timezone change**: On TZ/DST change → recompute wall-clock times; cancel & re-arm all slots
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 - **Lead window respect**: No retries attempted once inside 20min lead window
- **Idempotency**: Multiple `runFullPipelineNow()` calls don't create duplicate - **Idempotency**: Multiple `runFullPipelineNow()` calls don't create duplicate scheduled deliveries
scheduled deliveries
- **Cooldown guard**: `deliverStoredNow()` has 60s cooldown to prevent double-firing - **Cooldown guard**: `deliverStoredNow()` has 60s cooldown to prevent double-firing
### Electron-Specific Test Checks ### Electron-Specific Test Checks
- **Electron running (tray or window) → notifications fire** at configured slots - **Electron running (tray or window) → notifications fire** at configured slots using Electron `Notification`
using Electron `Notification`
- **Electron not running →** no delivery (documented limitation for v1) - **Electron not running →** no delivery (documented limitation for v1)
- **Start on Login enabled →** after reboot + login, orchestrator **will re-arm** - **Start on Login enabled →** after reboot + login, orchestrator **will re-arm** slots and deliver
slots and deliver
- **Template limits honored** (Title ≤ 50, Body ≤ 200) on Electron notifications - **Template limits honored** (Title ≤ 50, Body ≤ 200) on Electron notifications
- **SW scope** not used for Electron (click handlers are **web only**) - **SW scope** not used for Electron (click handlers are **web only**)
- **Windows appUserModelId** set correctly for toast attribution - **Windows appUserModelId** set correctly for toast attribution
@ -393,76 +265,51 @@ slots and deliver
### Timing-Verifiable Test Checks ### Timing-Verifiable Test Checks
- **iOS/Android (app killed):** locals will fire at their slots; no network - **iOS/Android (app killed):** locals will fire at their slots; no network activity at delivery time.
activity at delivery time. - **iOS/Android (resume inside lead):** exactly **one** online-first attempt occurs; if fetch completes within **12s** → content updated; otherwise offline policy applies.
- **iOS/Android (resume inside lead):** exactly **one** online-first attempt - **Android (no exact access):** observed delivery is within **±10 min** of slot time.
occurs; if fetch completes within **12s** → content updated; otherwise offline - **Web push:** SW push event fetch runs once with **12s** timeout; if it times out, the push still displays (from payload).
policy applies. - **Electron (app running):** timer-based locals fire on time; on reboot with **Start on Login**, orchestrator re-arms on first run.
- **Android (no exact access):** observed delivery is within **±10 min** of slot
time.
- **Web push:** SW push event fetch runs once with **12s** timeout; if it times
out, the push still displays (from payload).
- **Electron (app running):** timer-based locals fire on time; on reboot with
**Start on Login**, orchestrator re-arms on first run.
- **TTL behavior:** offline & stale → **skip** (no notification posted). - **TTL behavior:** offline & stale → **skip** (no notification posted).
- **ETag path:** with `304`, last payload remains; no duplicate scheduling rows - **ETag path:** with `304`, last payload remains; no duplicate scheduling rows (unique index enforced).
(unique index enforced). - **Cooldown:** calling `deliverStoredNow` twice within **60s** for same slot doesn't produce two notifications.
- **Cooldown:** calling `deliverStoredNow` twice within **60s** for same slot - **Closed app, armed earlier** → locals fire at slot; title/body match last rendered content (proves "render at schedule time" + adapter API).
doesn't produce two notifications. - **Closed app, timezone change before slot** → on next resume, app recomputes and re-arms; already armed notifications will still fire on original wall-time
- **Closed app, armed earlier** → locals fire at slot; title/body match last - **Mobile closed-app, no background network:** Arm at T–hours; kill app; verify locals fire with last rendered text; confirm **no** network egress at delivery.
rendered content (proves "render at schedule time" + adapter API). - **Web push as network scheduler:** Send push with empty payload → SW fetches within 12s timeout → shows correct text; confirm behavior with browser closed.
- **Closed app, timezone change before slot** → on next resume, app recomputes - **Electron app not running:** No delivery; with **Start on Login**, after reboot first run fetches and re-arms; subsequent slots fire.
and re-arms; already armed notifications will still fire on original wall-time - **Runner fires in background (Android/iOS):** With Runner enabled and app backgrounded for ≥30 min, at least one prefetch **will** occur; content cache **will** update; already-armed locals **will** still fire on time.
- **Mobile closed-app, no background network:** Arm at T–hours; kill app; verify - **Runner re-arm (flagged):** If `runnerRearm=true` and Runner fires inside lead with fresh content + within TTL, the system **will** cancel & re-arm the next slot; delivered text **will** match fresh template.
locals fire with last rendered text; confirm **no** network egress at delivery.
- **Web push as network scheduler:** Send push with empty payload → SW fetches
within 12s timeout → shows correct text; confirm behavior with browser closed.
- **Electron app not running:** No delivery; with **Start on Login**, after
reboot first run fetches and re-arms; subsequent slots fire.
- **Runner fires in background (Android/iOS):** With Runner enabled and app
backgrounded for ≥30 min, at least one prefetch **will** occur; content cache
**will** update; already-armed locals **will** still fire on time.
- **Runner re-arm (flagged):** If `runnerRearm=true` and Runner fires inside lead
with fresh content + within TTL, the system **will** cancel & re-arm the next
slot; delivered text **will** match fresh template.
--- ---
## 15) Test Matrix (Essentials) ## 16) Test Matrix (Essentials)
- **Android:** exact vs inexact branch, Doze/App Standby behavior, reboot/time - **Android:** exact vs inexact branch, Doze/App Standby behavior, reboot/time change, permission denial path, deep‑link to exact‑alarm settings.
change, permission denial path, deep‑link to exact‑alarm settings. - **iOS:** BG fetch budget limits, pending cap windowing, local notification delivery with app terminated, category actions.
- **iOS:** BG fetch budget limits, pending cap windowing, local notification - **Web:** SW lifecycle, push delivery with app closed, click handling, no offline scheduling.
delivery with app terminated, category actions. - **Cross‑cutting:** ETag/304 behavior, TTL policy, templating correctness, event queue drain, SQLite retention job.
- **Web:** SW lifecycle, push delivery with app closed, click handling, no
offline scheduling.
- **Cross‑cutting:** ETag/304 behavior, TTL policy, templating correctness, event
queue drain, SQLite retention job.
--- ---
## 16) Migration & Rollout Notes ## 17) Migration & Rollout Notes
- We **will keep** existing web push flows intact. - We **will keep** existing web push flows intact.
- We **will introduce** the orchestrator behind a feature flag, initially with - We **will introduce** the orchestrator behind a feature flag, initially with a small number of slots.
a small number of slots.
- We **will migrate** settings to accept multiple times per day. - We **will migrate** settings to accept multiple times per day.
- We **will document** platform caveats inside user‑visible settings (e.g., - We **will document** platform caveats inside user‑visible settings (e.g., Android exact alarms, iOS cap).
Android exact alarms, iOS cap).
--- ---
## 17) Security & Privacy ## 18) Security & Privacy
- Tokens **will reside** in Keystore/Keychain (mobile) and **will be injected** - Tokens **will reside** in Keystore/Keychain (mobile) and **will be injected** at request time; they **will not** be stored in SQLite.
at request time; they **will not** be stored in SQLite. - Optionally, SQLCipher at rest for mobile; redaction of PII in logs; payload size caps.
- 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. - Content **will be** minimal (title/body); sensitive data **will not be** embedded.
--- ---
## 18) Non‑Goals (Now) ## 19) Non‑Goals (Now)
- Complex action sets and rich media on locals (kept minimal). - Complex action sets and rich media on locals (kept minimal).
- Delivery‑time mutation of local notifications on iOS (NSE is for remote). - Delivery‑time mutation of local notifications on iOS (NSE is for remote).
@ -470,69 +317,19 @@ size caps.
--- ---
## 19) Cross-Doc Sync Hygiene ## 20) Cross-Document Synchronization
### 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
- **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
---
## 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` | ✅ |
| Runner described as **opportunistic prefetch**, not scheduler | §7 | §9 | ✅ |
| Feature flag `prefetchRunner` (default `'none'`) | §13 | §15 | ✅ |
| Capabilities `networkWake: 'opportunistic' | 'none'` | §7 | Scheduler.capabilities | ✅ |
| Runner tick handler bounded to ≤12s | §7 | BackgroundRunnerPrefetch | ✅ |
| Optional `runnerRearm` flag & behavior | §7 | Orchestrator + Runner | ✅ |
--- **Canonical Ownership:**
- **This document (Plan):** Goals, Tenets, Platform behaviors, Acceptance criteria, Test cases
- **Implementation document:** API definitions, Database schemas, Adapter implementations, Code examples
## Sync Checklist **Synchronization Requirements:**
- API code blocks must be identical between Plan §4 and Implementation §3
| Sync item | Plan | Impl | Status | - Feature flags must match between Plan §13 and Implementation §15 defaults
| ------------------------------ | --------------------- | --------------------- | ------ | - Test cases must align between Plan §14 acceptance criteria and Implementation examples
| Public API block identical | §4 | §3 | ✅ | - Error codes must match between Plan §11 taxonomy and Implementation error handling
| `getState()` fields present | §4 | §8 Orchestrator | ✅ | - Slot/TTL/Lead policies must be identical between Plan §4 semantics and Implementation §9 logic
| 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` | ✅ |
| Runner described as **opportunistic prefetch**, not scheduler | §7 | §9 | ✅ |
| Feature flag `prefetchRunner` (default `'none'`) | §13 | §15 | ✅ |
| Capabilities `networkWake: 'opportunistic' | 'none'` | §7 | Scheduler.capabilities | ✅ |
| Runner tick handler bounded to ≤12s | §7 | BackgroundRunnerPrefetch | ✅ |
| Optional `runnerRearm` flag & behavior | §7 | Orchestrator + Runner | ✅ |
--- ---
*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`.* *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).*
Loading…
Cancel
Save