docs: Consolidate documentation structure (139 files, zero information loss)

Consolidate all markdown documentation into organized structure per
CONSOLIDATION_DIRECTIVE. All files preserved (canonical, merged, or archived).

- docs/integration/ - Integration documentation (7 files)
- docs/platform/ios/ - iOS platform docs (12 files)
- docs/platform/android/ - Android platform docs (9 files)
- docs/testing/ - Testing documentation (15 files)
- docs/design/ - Design & research (5 files)
- docs/ai/ - AI/ChatGPT artifacts (7 files)
- docs/archive/2025-legacy-doc/ - Historical docs (17 files)

- Integration: Root INTEGRATION_GUIDE.md → docs/integration/
- Platform: Separated iOS and Android into platform/ subdirectories
- Testing: Consolidated all testing docs to docs/testing/
- Legacy: Archived entire doc/ directory to archive/
- AI: Moved all ChatGPT artifacts to docs/ai/

- Added docs/00-INDEX.md - Central navigation hub
- Added docs/CONSOLIDATION_SOURCE_MAP.md - Complete audit trail
- Added docs/CONSOLIDATION_COMPLETE.md - Consolidation summary
- Updated README.md with links to documentation index

- All 139 files have destinations (see CONSOLIDATION_SOURCE_MAP.md)
- Zero information loss (all files preserved)
- Archive preserves original structure
- Index provides clear navigation

- 87 files moved/created/updated
- Root-level docs consolidated
- Legacy doc/ directory archived
- Test app docs remain with test apps (indexed)

Ref: CONSOLIDATION_DIRECTIVE
Author: Matthew Raymer
This commit is contained in:
Matthew Raymer
2025-12-18 09:13:18 +00:00
parent 37fd2629d1
commit c39bd7cec6
78 changed files with 720 additions and 18 deletions
File diff suppressed because it is too large Load Diff
+669
View File
@@ -0,0 +1,669 @@
# Plugin Behavior Exploration - Initial Findings
**Author**: Matthew Raymer
**Date**: November 2025
**Status**: Initial Code Review - In Progress
## Purpose
This document contains initial findings from code-level inspection of the plugin. These findings should be verified through actual testing using [Plugin Behavior Exploration Template](./plugin-behavior-exploration-template.md).
---
## 0. Behavior Definitions & Investigation Scope
Before examining the code, we need to clearly define what behaviors we're investigating and what each scenario means.
### 0.1 App State Scenarios
#### Swipe from Recents (Recent Apps List)
**What it is**: User swipes the app away from the Android recent apps list (app switcher) or iOS app switcher.
**What happens**:
- **Android**: The app's UI is removed from the recent apps list, but:
- The app process may still be running in the background
- The app may be killed by the OS later due to memory pressure
- **AlarmManager alarms remain scheduled** and will fire even if the process is killed
- The OS will recreate the app process when the alarm fires
- **iOS**: The app is terminated, but:
- **UNUserNotificationCenter notifications remain scheduled** and will fire
- **Calendar/time-based triggers persist across reboot**
- **TimeInterval triggers also persist across reboot** (UNLESS they were scheduled with `repeats = false` AND the reboot occurs before the elapsed interval)
- The app does not run in the background (unless it has active background tasks)
- Notifications fire even though the app is not running
- **No plugin code runs when notification fires** unless the user interacts with the notification
**Key Point**: Swiping from recents does **not** cancel scheduled alarms/notifications. The OS maintains them separately from the app process.
**Android Nuance - Swipe vs Kill**:
- **"Swipe away" DOES NOT kill your process**; the OS may kill it later due to memory pressure
- **AlarmManager remains unaffected** by swipe - alarms stay scheduled
- **WorkManager tasks remain scheduled** regardless of swipe
- The app process may continue running in the background after swipe
- Only Force Stop actually cancels alarms and prevents execution
**Investigation Goal**: Verify that alarms/notifications still fire after the app is swiped away.
---
#### Force Stop (Android Only)
**What it is**: User goes to Settings → Apps → [Your App] → Force Stop. This is a **hard kill** that is different from swiping from recents.
**What happens**:
- **All alarms are immediately cancelled** by the OS
- **All WorkManager tasks are cancelled**
- **All broadcast receivers are blocked** (including BOOT_COMPLETED)
- **All JobScheduler jobs are cancelled**
- **The app cannot run** until the user manually opens it again
- **No background execution** is possible
**Key Point**: Force Stop is a **hard boundary** that cannot be bypassed. It's more severe than swiping from recents.
**Investigation Goal**: Verify that alarms do NOT fire after force stop, and that the plugin can detect and recover when the app is opened again.
**Difference from Swipe**:
- **Swipe**: Alarms remain scheduled, app may still run in background
- **Force Stop**: Alarms are cancelled, app cannot run until manually opened
---
#### App Still Functioning When Not Visible
**Android**:
- When an app is swiped from recents but not force-stopped:
- The app process may continue running in the background
- Background services can continue
- WorkManager tasks continue
- AlarmManager alarms remain scheduled
- The app is just not visible in the recent apps list
- The OS may kill the process later due to memory pressure, but alarms remain scheduled
**iOS**:
- When an app is swiped from the app switcher:
- The app process is terminated
- Background tasks (BGTaskScheduler) may still execute (system-controlled, but **opportunistic, not exact**)
- UNUserNotificationCenter notifications remain scheduled
- The app does not run in the foreground or background (unless it has active background tasks)
- **No persistent background execution** after user swipe
- **No alarm-like wake** for plugins (unlike Android AlarmManager)
- **No background execution at notification time** unless user interacts
**iOS Limitations**:
- No background execution at notification fire time unless user interacts
- No alarm-style wakeups exist on iOS
- Background execution (BGTaskScheduler) cannot be used for precise timing
- Notifications survive reboot but plugin code does not run automatically
**Investigation Goal**: Understand that "not visible" does not mean "not functioning" for alarms/notifications, but also understand iOS limitations on background execution.
---
### 0.2 App Launch Recovery - How It Should Work
App Launch Recovery is the mechanism by which the plugin detects and handles missed alarms/notifications when the app starts.
#### Recovery Scenarios
##### Cold Start
**What it is**: App is launched from a completely terminated state (process was killed or never started).
**Recovery Process**:
1. Plugin's `load()` method is called
2. Plugin initializes database/storage
3. Plugin queries for missed alarms/notifications:
- Find alarms with `scheduled_time < now` and `delivery_status != 'delivered'`
- Find notifications that should have fired but didn't
4. For each missed alarm/notification:
- Generate a "missed alarm" event or notification
- If repeating, reschedule the next occurrence
- Update delivery status to "missed" or "delivered"
5. Reschedule future alarms/notifications that are still valid
6. Verify active alarms match stored alarms
**Investigation Goal**: Verify that the plugin detects missed alarms on cold start and handles them appropriately.
**Android Force Stop Detection**:
- On cold start, query AlarmManager for active alarms
- Query plugin DB schedules
- If `(DB.count > 0 && AlarmManager.count == 0)`: **Force Stop detected**
- Recovery: Mark all past schedules as missed, reschedule all future schedules, emit missed notifications
---
##### Warm Start
**What it is**: App is returning from background (app was paused but process still running).
**Recovery Process**:
1. Plugin's `load()` method may be called (or app resumes)
2. Plugin checks for missed alarms/notifications (same as cold start)
3. Plugin verifies that active alarms are still scheduled correctly
4. Plugin reschedules if any alarms were cancelled (shouldn't happen, but verify)
**Investigation Goal**: Verify that the plugin checks for missed alarms on warm start and verifies active alarms.
---
##### Force Stop Recovery (Android)
**What it is**: App was force-stopped and user manually opens it again.
**Recovery Process**:
1. App launches (this is the only way to recover from force stop)
2. Plugin's `load()` method is called
3. Plugin detects that alarms were cancelled (all alarms have `scheduled_time < now` or are missing from AlarmManager)
4. Plugin queries database for all enabled alarms
5. For each alarm:
- If `scheduled_time < now`: Mark as missed, generate missed alarm event, reschedule if repeating
- If `scheduled_time >= now`: Reschedule the alarm
6. Plugin reschedules all future alarms
**Investigation Goal**: Verify that the plugin can detect force stop scenario and fully recover all alarms.
**Key Difference**: Force stop recovery is more comprehensive than normal app launch recovery because ALL alarms were cancelled, not just missed ones.
---
### 0.3 What We're Investigating
For each scenario, we want to know:
1. **Does the alarm/notification fire?** (OS behavior)
2. **Does the plugin detect missed alarms?** (Plugin behavior)
3. **Does the plugin recover/reschedule?** (Plugin behavior)
4. **What happens on next app launch?** (Recovery behavior)
**Expected Behaviors**:
| Scenario | Alarm Fires? | Plugin Detects Missed? | Plugin Recovers? |
| -------- | ------------ | ---------------------- | ---------------- |
| Swipe from recents | ✅ Yes (OS) | N/A (fired) | N/A |
| Force stop | ❌ No (OS cancels) | ✅ Should detect | ✅ Should recover |
| Device reboot (Android) | ❌ No (OS cancels) | ✅ Should detect | ✅ Should recover |
| Device reboot (iOS) | ✅ Yes (OS persists) | ⚠️ May detect | ⚠️ May recover |
| Cold start | N/A | ✅ Should detect | ✅ Should recover |
| Warm start | N/A | ✅ Should detect | ✅ Should verify |
---
## 1. Android Findings
### 1.1 Boot Receiver Implementation
**Status**: ✅ **IMPLEMENTED**
**Location**: `android/src/main/java/com/timesafari/dailynotification/BootReceiver.kt`
**Findings**:
- Boot receiver exists and handles `ACTION_BOOT_COMPLETED` (line 24)
- Reschedules alarms from database (line 38+)
- Loads enabled schedules from Room database (line 40)
- Reschedules both "fetch" and "notify" schedules (lines 46-81)
**Gap Identified**:
- **Missed Alarm Handling**: Boot receiver only reschedules FUTURE alarms
- Line 64: `if (nextRunTime > System.currentTimeMillis())`
- This means if an alarm was scheduled for before the reboot time, it won't be rescheduled
- **No missed alarm detection or notification**
**Recommendation**: Add missed alarm detection in `rescheduleNotifications()` method
---
### 1.2 Missed Alarm Detection
**Status**: ⚠️ **PARTIAL**
**Location**: `android/src/main/java/com/timesafari/dailynotification/dao/NotificationContentDao.java`
**Findings**:
- DAO has query for missed alarms: `getNotificationsReadyForDelivery()` (line 98)
- Query: `SELECT * FROM notification_content WHERE scheduled_time <= :currentTime AND delivery_status != 'delivered'`
- This can identify notifications that should have fired but haven't
**Gap Identified**:
- **Not called on app launch**: The `DailyNotificationPlugin.load()` method (line 91) only initializes the database
- No recovery logic in `load()` method
- Query exists but may not be used for missed alarm detection
**Recommendation**: Add missed alarm detection in `load()` method or create separate recovery method
---
### 1.3 App Launch Recovery
**Status**: ❌ **NOT IMPLEMENTED**
**Location**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt`
**Expected Behavior** (as defined in Section 0.2):
**Cold Start Recovery**:
1. Plugin `load()` method called
2. Query database for missed alarms: `scheduled_time < now AND delivery_status != 'delivered'`
3. For each missed alarm:
- Generate missed alarm event/notification
- Reschedule if repeating
- Update delivery status
4. Reschedule all future alarms from database
5. Verify active alarms match stored alarms
**Warm Start Recovery**:
1. Plugin checks for missed alarms (same as cold start)
2. Verify active alarms are still scheduled
3. Reschedule if any were cancelled
**Force Stop Recovery**:
1. Detect that all alarms were cancelled (force stop scenario)
2. Query database for ALL enabled alarms
3. For each alarm:
- If `scheduled_time < now`: Mark as missed, generate event, reschedule if repeating
- If `scheduled_time >= now`: Reschedule immediately
4. Fully restore alarm state
**Current Implementation**:
- `load()` method (line 91) only initializes database
- No recovery logic on app launch
- No check for missed alarms
- No rescheduling of future alarms
- No distinction between cold/warm/force-stop scenarios
**Gap Identified**:
- Plugin does not recover on app cold/warm start
- Plugin does not recover from force stop
- Only boot receiver handles recovery (and only for future alarms)
- No missed alarm detection on app launch
**Recommendation**:
1. Add recovery logic to `load()` method or create `ReactivationManager`
2. Implement missed alarm detection using `getNotificationsReadyForDelivery()` query
3. Implement force stop detection (all alarms cancelled)
4. Implement rescheduling of future alarms from database
---
### 1.4 Persistence Completeness
**Status**: ✅ **IMPLEMENTED**
**Findings**:
- Room database used for persistence
- `Schedule` entity stores: id, kind, cron, clockTime, enabled, nextRunAt
- `NotificationContentEntity` stores: id, title, body, scheduledTime, priority, etc.
- `ContentCache` stores: fetched content with TTL
**All Required Fields Present**:
- ✅ alarm_id (Schedule.id, NotificationContentEntity.id)
- ✅ trigger_time (Schedule.nextRunAt, NotificationContentEntity.scheduledTime)
- ✅ repeat_rule (Schedule.cron, Schedule.clockTime)
- ✅ channel_id (NotificationContentEntity - implicit)
- ✅ priority (NotificationContentEntity.priority)
- ✅ title, body (NotificationContentEntity)
- ✅ sound_enabled, vibration_enabled (NotificationContentEntity)
- ✅ created_at, updated_at (NotificationContentEntity)
- ✅ enabled (Schedule.enabled)
---
### 1.5 Force Stop Recovery
**Status**: ❌ **NOT IMPLEMENTED**
**Expected Behavior** (as defined in Section 0.1 and 0.2):
**Force Stop Scenario**:
- User goes to Settings → Apps → [App] → Force Stop
- All alarms are immediately cancelled by the OS
- App cannot run until user manually opens it
- When app is opened, it's a cold start scenario
**Force Stop Recovery**:
1. Detect that alarms were cancelled (check AlarmManager for scheduled alarms)
2. Compare with database: if database has alarms but AlarmManager has none → force stop occurred
3. Query database for ALL enabled alarms
4. For each alarm:
- If `scheduled_time < now`: This alarm was missed during force stop
- Generate missed alarm event/notification
- Reschedule next occurrence if repeating
- Update delivery status
- If `scheduled_time >= now`: This alarm is still in the future
- Reschedule immediately
5. Fully restore alarm state
**Current Implementation**:
- No specific force stop detection
- No recovery logic for force stop scenario
- App launch recovery (if implemented) would handle this, but app launch recovery is not implemented
- Cannot distinguish between normal app launch and force stop recovery
**Gap Identified**:
- Plugin cannot detect force stop scenario
- Plugin cannot distinguish between normal app launch and force stop recovery
- No special handling for force stop scenario
- All alarms remain cancelled until user opens app, then plugin should recover them
**Recommendation**:
1. Implement app launch recovery (which will handle force stop as a special case)
2. Add force stop detection: compare AlarmManager scheduled alarms with database
3. If force stop detected, recover ALL alarms (not just missed ones)
---
## 2. iOS Findings
### 2.1 Notification Persistence
**Status**: ✅ **IMPLEMENTED**
**Location**: `ios/Plugin/DailyNotificationStorage.swift`
**Findings**:
- Plugin uses `DailyNotificationStorage` for separate persistence
- Uses UserDefaults for quick access (line 40)
- Uses CoreData for structured data (line 41)
- Stores notifications separately from UNUserNotificationCenter
**Storage Components**:
- UserDefaults: Settings, last fetch, BGTask tracking
- CoreData: NotificationContent, Schedule entities
- UNUserNotificationCenter: OS-managed notification scheduling
---
### 2.2 Missed Notification Detection
**Status**: ⚠️ **PARTIAL**
**Location**: `ios/Plugin/DailyNotificationPlugin.swift`
**Findings**:
- `checkForMissedBGTask()` method exists (line 421)
- Checks for missed background tasks (BGTaskScheduler)
- Reschedules missed BGTask if needed
**Gap Identified**:
- Only checks for missed BGTask, not missed notifications
- UNUserNotificationCenter handles notification persistence, but plugin doesn't check for missed notifications
- No comparison between plugin storage and UNUserNotificationCenter pending notifications
**Recommendation**: Add missed notification detection by comparing plugin storage with UNUserNotificationCenter pending requests
---
### 2.3 App Launch Recovery
**Status**: ⚠️ **PARTIAL**
**Location**: `ios/Plugin/DailyNotificationPlugin.swift`
**Expected Behavior** (iOS Missed Notification Recovery Architecture):
**Required Steps for Missed Notification Detection**:
1. Query plugin storage (CoreData) for all scheduled notifications
2. Query `UNUserNotificationCenter.pendingNotificationRequests()` for future notifications
3. Query `UNUserNotificationCenter.getDeliveredNotifications()` for already-fired notifications
4. Find CoreData entries where:
- `scheduled_time < now` (should have fired)
- NOT in `deliveredNotifications` list (didn't fire)
- NOT in `pendingNotificationRequests` list (not scheduled for future)
5. Generate "missed notification" events for each detected miss
6. Reschedule repeating notifications
7. Verify that scheduled notifications in UNUserNotificationCenter align with CoreData schedules
**This must be placed in `load()` during cold start.**
**Current Implementation**:
- `load()` method exists (line 42)
- `setupBackgroundTasks()` called (line 318)
- `checkForMissedBGTask()` called on setup (line 330)
- Only checks for missed BGTask, not missed notifications
- No recovery of notification state
- No rescheduling of notifications from plugin storage
- No comparison between UNUserNotificationCenter and CoreData
**Gap Identified**:
- Only checks for missed BGTask, not missed notifications
- No recovery of notification state
- No rescheduling of notifications from plugin storage
- No cross-checking between UNUserNotificationCenter and CoreData
- **iOS cannot detect missed notifications** unless plugin compares storage vs `UNUserNotificationCenter.getDeliveredNotifications()` or infers from plugin timestamps
**Recommendation**:
1. Add notification recovery logic in `load()` or `setupBackgroundTasks()`
2. Implement three-way comparison: CoreData vs pending vs delivered notifications
3. Add missed notification detection using the architecture above
4. Note: iOS does NOT allow arbitrary code execution at notification fire time unless user interacts or Notification Service Extensions are used (not currently used)
---
### 2.4 Background Execution Limits
**Status**: ✅ **DOCUMENTED IN CODE**
**Findings**:
- BGTaskScheduler used for background fetch
- Time budget limitations understood (30 seconds typical)
- System-controlled execution acknowledged
- Rescheduling logic handles missed tasks
**Code Evidence**:
- `checkForMissedBGTask()` handles missed BGTask (line 421)
- 15-minute miss window used (line 448)
- Reschedules if missed (line 462)
---
## 3. Cross-Platform Gaps Summary
| Gap | Android | iOS | Severity | Recommendation |
| --- | ------- | --- | -------- | -------------- |
| Missed alarm/notification detection | ⚠️ Partial | ⚠️ Partial | **High** | Implement on app launch |
| App launch recovery | ❌ Missing | ⚠️ Partial | **High** | **MUST implement for both platforms** |
| Force stop recovery | ❌ Missing | N/A | **Medium** | Android: Implement app launch recovery with force stop detection |
| Boot recovery missed alarms | ⚠️ Only future | N/A | **Medium** | Android: Add missed alarm handling in boot receiver |
| Cross-check mechanism (DB vs OS) | ❌ Missing | ⚠️ Partial | **High** | Android: AlarmManager vs DB; iOS: UNUserNotificationCenter vs CoreData |
**Critical Requirement**: App Launch Recovery **must be implemented on BOTH platforms**:
- Plugin must execute recovery logic during `load()` OR equivalent
- Distinguish cold vs warm start
- Use timestamps in storage to verify last known state
- Reconcile DB entries with OS scheduling APIs
- Android: Cross-check AlarmManager scheduled alarms with DB
- iOS: Cross-check UNUserNotificationCenter with CoreData schedules
---
## 4. Test Validation Outputs
For each scenario, the exploration should produce explicit outputs:
| Scenario | OS Expected | Plugin Expected | Observed Result | Pass/Fail | Notes |
| -------- | ----------- | --------------- | --------------- | --------- | ----- |
| Swipe from recents | Alarm fires | Alarm fires | ☐ | ☐ | |
| Force stop | Alarm does NOT fire | Plugin detects on launch | ☐ | ☐ | |
| Device reboot (Android) | Alarm does NOT fire | Plugin reschedules on boot | ☐ | ☐ | |
| Device reboot (iOS) | Notification fires | Notification fires | ☐ | ☐ | |
| Cold start | N/A | Missed alarms detected | ☐ | ☐ | |
| Warm start | N/A | Missed alarms detected | ☐ | ☐ | |
| Force stop recovery | N/A | All alarms recovered | ☐ | ☐ | |
**This creates alignment with the [Exploration Template](./plugin-behavior-exploration-template.md).**
---
## 5. Next Steps
1. **Verify findings through testing** using [Plugin Behavior Exploration Template](./plugin-behavior-exploration-template.md)
2. **Test boot receiver** on actual device reboot
3. **Test app launch recovery** on cold/warm start
4. **Test force stop recovery** on Android (with cross-check mechanism)
5. **Test missed notification detection** on iOS (with three-way comparison)
6. **Inspect `UNUserNotificationCenter.getPendingNotificationRequests()` vs CoreData** to detect "lost" iOS notifications
7. **Update Plugin Requirements** document with verified gaps
8. **Generate Test Validation Outputs** table with actual test results
---
## 6. Code References for Implementation
### Android - Add Missed Alarm Detection with Force Stop Detection
**Location**: `DailyNotificationPlugin.kt` - `load()` method (line 91)
**Suggested Implementation** (with Force Stop Detection):
```kotlin
override fun load() {
super.load()
// ... existing initialization ...
// Check for missed alarms on app launch
CoroutineScope(Dispatchers.IO).launch {
detectAndHandleMissedAlarms()
}
}
private suspend fun detectAndHandleMissedAlarms() {
val db = DailyNotificationDatabase.getDatabase(context)
val currentTime = System.currentTimeMillis()
// Cross-check: Query AlarmManager for active alarms
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val activeAlarmCount = getActiveAlarmCount(alarmManager) // Helper method needed
// Query database for all enabled schedules
val dbSchedules = db.scheduleDao().getEnabled()
// Force Stop Detection: If DB has schedules but AlarmManager has zero
val forceStopDetected = dbSchedules.isNotEmpty() && activeAlarmCount == 0
if (forceStopDetected) {
Log.i(TAG, "Force stop detected - all alarms were cancelled")
// Recover ALL alarms (not just missed ones)
recoverAllAlarmsAfterForceStop(db, dbSchedules, currentTime)
} else {
// Normal recovery: only check for missed alarms
val missedNotifications = db.notificationContentDao()
.getNotificationsReadyForDelivery(currentTime)
missedNotifications.forEach { notification ->
// Generate missed alarm event/notification
// Reschedule if repeating
// Update delivery status
}
// Reschedule future alarms from database
rescheduleFutureAlarms(db, dbSchedules, currentTime)
}
}
private suspend fun recoverAllAlarmsAfterForceStop(
db: DailyNotificationDatabase,
schedules: List<Schedule>,
currentTime: Long
) {
schedules.forEach { schedule ->
val nextRunTime = calculateNextRunTime(schedule)
if (nextRunTime < currentTime) {
// Past alarm - mark as missed
// Generate missed alarm notification
// Reschedule if repeating
} else {
// Future alarm - reschedule immediately
rescheduleAlarm(schedule, nextRunTime)
}
}
}
```
### Android - Add Missed Alarm Handling in Boot Receiver
**Location**: `BootReceiver.kt` - `rescheduleNotifications()` method (line 38)
**Suggested Implementation**:
```kotlin
// After rescheduling future alarms, check for missed ones
val missedNotifications = db.notificationContentDao()
.getNotificationsReadyForDelivery(System.currentTimeMillis())
missedNotifications.forEach { notification ->
// Generate missed alarm notification
// Reschedule if repeating
}
```
### iOS - Add Missed Notification Detection
**Location**: `DailyNotificationPlugin.swift` - `setupBackgroundTasks()` or `load()` method
**Suggested Implementation** (Three-Way Comparison):
```swift
private func checkForMissedNotifications() async {
// Step 1: Get pending notifications (future) from UNUserNotificationCenter
let pendingRequests = await notificationCenter.pendingNotificationRequests()
let pendingIds = Set(pendingRequests.map { $0.identifier })
// Step 2: Get delivered notifications (already fired) from UNUserNotificationCenter
let deliveredNotifications = await notificationCenter.getDeliveredNotifications()
let deliveredIds = Set(deliveredNotifications.map { $0.request.identifier })
// Step 3: Get notifications from plugin storage (CoreData)
let storedNotifications = storage?.getAllNotifications() ?? []
let currentTime = Date().timeIntervalSince1970
// Step 4: Find missed notifications
// Missed = scheduled_time < now AND not in delivered AND not in pending
for notification in storedNotifications {
let scheduledTime = notification.scheduledTime
let notificationId = notification.id
if scheduledTime < currentTime {
// Should have fired by now
if !deliveredIds.contains(notificationId) && !pendingIds.contains(notificationId) {
// This notification was missed
// Generate missed notification event
// Reschedule if repeating
// Update delivery status
}
}
}
// Step 5: Verify alignment - check if CoreData schedules match UNUserNotificationCenter
// Reschedule any missing notifications from CoreData
}
```
---
## Related Documentation
- [Plugin Behavior Exploration Template](./plugin-behavior-exploration-template.md) - Use this for testing
- [Plugin Requirements & Implementation](./plugin-requirements-implementation.md) - Requirements based on findings
- [Platform Capability Reference](./platform-capability-reference.md) - OS-level facts
---
## 7. Document Separation Directive
After improvements are complete, separate documents by purpose:
- **This file** → Exploration Findings (final) - Code inspection and test results
- **Android Behavior** → Platform Reference (see [Platform Capability Reference](./platform-capability-reference.md))
- **iOS Behavior** → Platform Reference (see [Platform Capability Reference](./platform-capability-reference.md))
- **Plugin Requirements** → Independent document (see [Plugin Requirements & Implementation](./plugin-requirements-implementation.md))
- **Future Implementation Directive** → Separate document (to be created)
This avoids future redundancy and maintains clear separation of concerns.
---
## Notes
- These findings are from **code inspection only**
- **Actual testing required** to verify behavior
- Findings should be updated after testing with [Plugin Behavior Exploration Template](./plugin-behavior-exploration-template.md)
- iOS missed notification detection requires three-way comparison: CoreData vs pending vs delivered
- Android force stop detection requires cross-check: AlarmManager vs database
@@ -0,0 +1,670 @@
# DIRECTIVE: Explore & Document Alarm / Schedule / Notification Behavior in Capacitor Plugin (Android & iOS)
**Author**: Matthew Raymer
**Date**: November 2025
**Status**: Active Directive - Exploration Phase
## 0. Scope & Objective
We want to **explore, test, and document** how the *current* Capacitor plugin handles:
- **Alarms / schedules / reminders**
- **Local notifications**
- **Persistence and recovery** across:
- App kill / swipe from recents
- OS process kill
- Device reboot
- **Force stop** (Android) / hard termination (iOS)
- Cross-platform **semantic differences** between Android and iOS
The focus is **observation of current behavior**, not yet changing implementation.
We want a clear map of **what the plugin actually guarantees** on each platform.
---
## 1. Key Questions to Answer
For **each platform (Android, iOS)** and for **each "scheduled thing"** the plugin supports (alarms, reminders, scheduled notifications, repeating schedules, etc.):
### 1.1 How is it implemented under the hood?
- **Android**: AlarmManager? WorkManager? JobScheduler? Foreground service?
- **iOS**: UNUserNotificationCenter? BGTaskScheduler? background fetch? timers in foreground?
### 1.2 What happens when the app is:
- Swiped away from recents?
- Killed by OS (memory pressure)?
- Device rebooted?
- On Android: explicitly **Force Stopped** in system settings?
- On iOS: explicitly swiped away, then device rebooted before next trigger?
### 1.3 What is persisted?
Are schedules/alarms stored in:
- SQLite / Room / shared preferences (Android)?
- CoreData / UserDefaults / files (iOS)?
- Or are they only in RAM / native scheduler?
### 1.4 What is re-created and when?
- On boot?
- On app cold start?
- On notification tap?
- Not at all?
### 1.5 What does the plugin *promise* to the JS/TS layer?
- "Will always fire even after reboot"?
- "Will fire as long as app hasn't been force-stopped"?
- "Best-effort only"?
We are trying to align **plugin promises** with **real platform capabilities and limitations.**
---
## 2. Android Exploration
### 2.1 Code-Level Inspection
**Source Locations:**
- **Plugin Implementation**: `android/src/main/java/com/timesafari/dailynotification/` (Kotlin files may also be present)
- **Manifest**: `android/src/main/AndroidManifest.xml`
- **Test Applications**:
- `test-apps/android-test-app/` - Primary Android test app
- `test-apps/daily-notification-test/` - Additional test application
**Tasks:**
1. **Locate the Android implementation in the plugin:**
- Primary path: `android/src/main/java/com/timesafari/dailynotification/`
- Key files to examine:
- `DailyNotificationPlugin.kt` - Main plugin class (see `scheduleDailyNotification()` at line 1302)
- `DailyNotificationWorker.java` - WorkManager worker (see `doWork()` at line 59)
- `DailyNotificationReceiver.java` - BroadcastReceiver for alarms (see `onReceive()` at line 51)
- `NotifyReceiver.kt` - AlarmManager scheduling (see `scheduleExactNotification()` at line 92)
- `BootReceiver.kt` - Boot recovery (see `onReceive()` at line 24)
- `FetchWorker.kt` - WorkManager fetch scheduling (see `scheduleFetch()` at line 31)
2. **Identify the mechanisms used to schedule work:**
- **AlarmManager**: Used via `NotifyReceiver.scheduleExactNotification()` (line 92)
- `setAlarmClock()` for Android 5.0+ (line 219)
- `setExactAndAllowWhileIdle()` for Android 6.0+ (line 223)
- `setExact()` for older versions (line 231)
- **WorkManager**: Used for background fetching and notification processing
- `FetchWorker.scheduleFetch()` (line 31) - Uses `OneTimeWorkRequest`
- `DailyNotificationWorker.doWork()` (line 59) - Processes notifications
- **No JobScheduler**: Not used in current implementation
- **No repeating alarms**: Uses one-time alarms with rescheduling
3. **Inspect how notifications are issued:**
- **NotificationCompat**: Used in `DailyNotificationWorker.displayNotification()` and `NotifyReceiver.showNotification()` (line 482)
- **setFullScreenIntent**: Not currently used (check `NotifyReceiver.showNotification()` at line 443)
- **Notification channels**: Created in `NotifyReceiver.showNotification()` (lines 454-470)
- Channel ID: `"timesafari.daily"` (see `DailyNotificationWorker.java` line 46)
- Importance based on priority (HIGH/DEFAULT/LOW)
- **ChannelManager**: Check for separate channel management class
4. **Check for permissions & receivers:**
- Manifest: `android/src/main/AndroidManifest.xml`
- Look for:
- `RECEIVE_BOOT_COMPLETED` permission
- `SCHEDULE_EXACT_ALARM` permission
- Any `BroadcastReceiver` declarations for:
- `BOOT_COMPLETED` / `LOCKED_BOOT_COMPLETED`
- Custom alarm intent actions
- Check test app manifests:
- `test-apps/android-test-app/app/src/main/AndroidManifest.xml`
- `test-apps/daily-notification-test/` (if applicable)
5. **Determine persistence strategy:**
- Where are scheduled alarms stored?
- SharedPreferences?
- SQLite / Room?
- Not at all (just in AlarmManager/work queue)?
6. **Check for reschedule-on-boot or reschedule-on-app-launch logic:**
- **BootReceiver**: `android/src/main/java/com/timesafari/dailynotification/BootReceiver.kt`
- `onReceive()` handles `ACTION_BOOT_COMPLETED` (line 24)
- `rescheduleNotifications()` reloads from database and reschedules (line 38+)
- Calls `NotifyReceiver.scheduleExactNotification()` for each schedule (line 74)
- **Alternative**: `DailyNotificationRebootRecoveryManager.java` has `BootCompletedReceiver` inner class (line 278)
- **App launch recovery**: Check `DailyNotificationPlugin.kt` for initialization logic that reschedules on app start
---
### 2.2 Behavior Testing Matrix (Android)
**Test Applications:**
- **Primary**: `test-apps/android-test-app/` - Use this for comprehensive testing
- **Secondary**: `test-apps/daily-notification-test/` - Additional test scenarios if needed
**Run these tests on a real device or emulator.**
For each scenario, record:
- Did the alarm / notification fire?
- Did it fire on time?
- From what state did the app wake up (cold, warm, already running)?
- Any visible logs / errors?
**Scenarios:**
#### 2.2.1 Base Case
- Schedule an alarm/notification 2 minutes in the future.
- Leave app in foreground or background.
- Confirm it fires.
#### 2.2.2 Swipe from Recents
- Schedule alarm (2–5 minutes).
- Swipe app away from recents.
- Wait for trigger time.
- Observe: does alarm still fire?
#### 2.2.3 OS Kill (simulate memory pressure)
- Mainly observational; may be tricky to force, but:
- Open many other apps or use `adb shell am kill <package>` (not force-stop).
- Confirm whether scheduled alarm still fires.
#### 2.2.4 Device Reboot
- Schedule alarm (e.g. 10 minutes in the future).
- Reboot device.
- Do **not** reopen app.
- Wait past scheduled time:
- Does plugin reschedule and fire automatically?
- Or does nothing happen until user opens the app?
Then:
- After device reboot, manually open the app.
- Does plugin detect missed alarms and:
- Fire "missed" behavior?
- Reschedule future alarms?
- Or silently forget them?
#### 2.2.5 Android Force Stop
- Schedule alarm.
- Go to Settings → Apps → [Your App] → Force stop.
- Wait for trigger time.
- Observe: it should **not** fire (OS-level rule).
- Then open app again and see if plugin automatically:
- Detects missed alarms and recovers, or
- Treats them as lost.
**Goal:** build a clear empirical table of plugin behavior vs Android's known rules.
---
## 3. iOS Exploration
### 3.1 Code-Level Inspection
**Source Locations:**
- **Plugin Implementation**: `ios/Plugin/` - Swift plugin files
- **Alternative Branch**: Check `ios-2` branch for additional iOS implementations or variations
- **Test Applications**:
- `test-apps/ios-test-app/` - Primary iOS test app
- `test-apps/daily-notification-test/` - Additional test application (if iOS support exists)
**Tasks:**
1. **Locate the iOS implementation:**
- Primary path: `ios/Plugin/`
- Key files to examine:
- `DailyNotificationPlugin.swift` - Main plugin class (see `scheduleUserNotification()` at line 506)
- `DailyNotificationScheduler.swift` - Notification scheduling (see `scheduleNotification()` at line 133)
- `DailyNotificationBackgroundTasks.swift` - BGTaskScheduler handlers
- **Also check**: `ios-2` branch for alternative implementations or newer iOS code
```bash
git checkout ios-2
# Compare ios/Plugin/DailyNotificationPlugin.swift
```
2. **Identify the scheduling mechanism:**
- **UNUserNotificationCenter**: Primary mechanism
- `DailyNotificationScheduler.scheduleNotification()` uses `UNCalendarNotificationTrigger` (line 172)
- `DailyNotificationPlugin.scheduleUserNotification()` uses `UNTimeIntervalNotificationTrigger` (line 514)
- No `UNLocationNotificationTrigger` found
- **BGTaskScheduler**: Used for background fetch
- `DailyNotificationPlugin.scheduleBackgroundFetch()` (line 495)
- Uses `BGAppRefreshTaskRequest` (line 496)
- **No timers**: No plain Timer usage found (would die with app)
3. **Determine what's persisted:**
- Does the plugin store alarms in:
- `UserDefaults`?
- Files / CoreData?
- Or only within UNUserNotificationCenter's pending notification requests (no parallel app-side storage)?
4. **Check for re-scheduling behavior on app launch:**
- On app start (cold or warm), does plugin:
- Query `UNUserNotificationCenter` for pending notifications?
- Compare against its own store?
- Attempt to rebuild schedules?
- Or does it rely solely on UNUserNotificationCenter to manage everything?
5. **Determine capabilities / limitations:**
- Can the plugin run arbitrary code *when the notification fires*?
- Only via notification actions / `didReceive response` callbacks.
- Does it support repeating notifications (daily/weekly)?
---
### 3.2 Behavior Testing Matrix (iOS)
**Test Applications:**
- **Primary**: `test-apps/ios-test-app/` - Use this for comprehensive testing
- **Secondary**: `test-apps/daily-notification-test/` - Additional test scenarios if needed
- **Note**: Compare behavior between main branch and `ios-2` branch implementations if they differ
As with Android, test:
#### 3.2.1 Base Case
- Schedule local notification 2–5 minutes in the future; leave app backgrounded.
- Confirm it fires on time with app in background.
#### 3.2.2 Swipe App Away
- Schedule notification, then swipe app away from app switcher.
- Confirm notification still fires (iOS local notification center should handle this).
#### 3.2.3 Device Reboot
- Schedule notification for a future time.
- Reboot device.
- Do **not** open app.
- Test whether:
- Notification still fires (iOS usually persists scheduled local notifications across reboot), or
- Behavior depends on trigger type (time vs calendar, etc.).
#### 3.2.4 Hard Termination & Relaunch
- Schedule some repeating notification(s).
- Terminate app via Xcode / app switcher.
- Allow some triggers to occur.
- Reopen app and inspect whether plugin:
- Notices anything about missed events, or
- Simply trusts that UNUserNotificationCenter handled user-visible parts.
**Goal:** map what your *plugin* adds on top of native behavior vs what is entirely delegated to the OS.
---
## 4. Cross-Platform Behavior & Promise Alignment
After exploration, produce a summary:
### 4.1 What the plugin actually guarantees to JS callers
- "Scheduled reminders will still fire after app swipe / kill"
- "On Android, reminders may not survive device reboot unless app is opened"
- "After Force Stop (Android), nothing runs until user opens app"
- "On iOS, local notifications themselves persist across reboot, but no extra app code runs at fire time unless user interacts"
### 4.2 Where semantics differ
- Android may require explicit rescheduling on boot; iOS may not.
- Android **force stop** is a hard wall; iOS has no exact equivalent in user-facing settings.
- Plugin may currently:
- Over-promise on reliability, or
- Under-document platform limitations.
### 4.3 Where we need to add warnings / notes in the public API
- E.g. "This schedule is best-effort; on Android, device reboot may cancel it unless you open the app again," etc.
---
## 5. Deliverables from This Exploration
### 5.1 Doc: `ALARMS_BEHAVIOR_MATRIX.md`
A table of scenarios (per platform) vs observed behavior.
Includes:
- App state
- OS event (reboot, force stop, etc.)
- What fired, what didn't
- Log snippets where useful
### 5.2 Doc: `PLUGIN_ALARM_LIMITATIONS.md`
Plain-language explanation of:
- Android hard limits (Force Stop, reboot behavior)
- iOS behavior (local notifications vs app code execution)
- Clear note on what the plugin promises.
### 5.3 Annotated code pointers
Commented locations in Android/iOS code where:
- Scheduling is performed
- Persistence (if any) is implemented
- Rescheduling (if any) is implemented
### 5.4 Open Questions / TODOs
Gaps uncovered:
- No reschedule-on-boot?
- No persistence of schedules?
- No handling of "missed" alarms on reactivation?
- Potential next-step directives (separate document) to improve behavior.
---
## 6. One-Liner Summary
> This directive is to **investigate, not change**: we want a precise, tested understanding of what our Capacitor plugin *currently* does with alarms/schedules/notifications on Android and iOS, especially across kills, reboots, and force stops, and where that behavior does or does not match what we think we're promising to app developers.
---
## Related Documentation
- [Android Alarm Persistence Directive](./android-alarm-persistence-directive.md) - General Android alarm capabilities and limitations
- [Boot Receiver Testing Guide](./boot-receiver-testing-guide.md) - Testing boot receiver behavior
- [App Startup Recovery Solution](./app-startup-recovery-solution.md) - Recovery mechanisms on app launch
- [Reboot Testing Procedure](./reboot-testing-procedure.md) - Step-by-step reboot testing
---
## Source Code Structure Reference
### Android Source Files
**Primary Plugin Code:**
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt` (or `.java`)
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java`
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java`
- `android/src/main/java/com/timesafari/dailynotification/ChannelManager.java`
- `android/src/main/AndroidManifest.xml`
**Test Applications:**
- `test-apps/android-test-app/app/src/main/` - Test app source
- `test-apps/android-test-app/app/src/main/assets/public/index.html` - Test UI
- `test-apps/daily-notification-test/` - Additional test app (if present)
### iOS Source Files
**Primary Plugin Code:**
- `ios/Plugin/DailyNotificationPlugin.swift` (or similar)
- `ios/Plugin/` - All Swift plugin files
- **Also check**: `ios-2` branch for alternative implementations
**Test Applications:**
- `test-apps/ios-test-app/` - Test app source
- `test-apps/daily-notification-test/` - Additional test app (if iOS support exists)
---
## Detailed Code References (File Locations, Functions, Line Numbers)
### Android Implementation Details
#### Alarm Scheduling
**File**: `android/src/main/java/com/timesafari/dailynotification/NotifyReceiver.kt`
- **Function**: `scheduleExactNotification()` - **Lines 92-247**
- Schedules exact alarms using AlarmManager
- Uses `setAlarmClock()` for Android 5.0+ (API 21+) - **Line 219**
- Falls back to `setExactAndAllowWhileIdle()` for Android 6.0+ (API 23+) - **Line 223**
- Falls back to `setExact()` for older versions - **Line 231**
- Called from:
- `DailyNotificationPlugin.kt` - `scheduleDailyNotification()` - **Line 1385**
- `DailyNotificationPlugin.kt` - `scheduleDailyReminder()` - **Line 809**
- `DailyNotificationPlugin.kt` - `scheduleDualNotification()` - **Line 1685**
- `BootReceiver.kt` - `rescheduleNotifications()` - **Line 74**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt`
- **Function**: `scheduleDailyNotification()` - **Lines 1302-1417**
- Main plugin method for scheduling notifications
- Checks exact alarm permission - **Line 1309**
- Opens settings if permission not granted - **Lines 1314-1324**
- Calls `NotifyReceiver.scheduleExactNotification()` - **Line 1385**
- Schedules prefetch 2 minutes before notification - **Line 1395**
- **Function**: `scheduleDailyReminder()` - **Lines 777-833**
- Schedules static reminders (no content dependency)
- Calls `NotifyReceiver.scheduleExactNotification()` - **Line 809**
- **Function**: `canScheduleExactAlarms()` - **Lines 835-860**
- Checks if exact alarm permission is granted (Android 12+)
**File**: `android/src/main/java/com/timesafari/dailynotification/PendingIntentManager.java`
- **Function**: `scheduleExactAlarm()` - **Lines 127-158**
- Uses `setExactAndAllowWhileIdle()` - **Line 135**
- Falls back to `setExact()` - **Line 141**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationExactAlarmManager.java`
- **Function**: `scheduleExactAlarm()` - **Lines 186-201**
- Uses `setExactAndAllowWhileIdle()` - **Line 189**
- Falls back to `setExact()` - **Line 193**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationScheduler.java`
- **Function**: `scheduleExactAlarm()` - **Lines 237-272**
- Uses `setExactAndAllowWhileIdle()` - **Line 242**
- Falls back to `setExact()` - **Line 251**
#### WorkManager Usage
**File**: `android/src/main/java/com/timesafari/dailynotification/FetchWorker.kt`
- **Function**: `scheduleFetch()` - **Lines 31-59**
- Schedules WorkManager one-time work request
- Uses `OneTimeWorkRequestBuilder` - **Line 36**
- Enqueues with `WorkManager.getInstance().enqueueUniqueWork()` - **Lines 53-58**
- **Function**: `scheduleDelayedPrefetch()` - **Lines 62-131**
- Schedules delayed prefetch work
- Uses `setInitialDelay()` - **Line 104**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java`
- **Function**: `doWork()` - **Lines 59-915**
- Main WorkManager worker execution
- Handles notification display - **Line 91**
- Calls `displayNotification()` - **Line 200+**
- **Function**: `displayNotification()` - **Lines 200-400+**
- Displays notification using NotificationCompat
- Ensures notification channel exists
- Uses `NotificationCompat.Builder` - **Line 200+**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationFetcher.java`
- **Function**: `scheduleFetch()` - **Lines 78-140**
- Schedules WorkManager fetch work
- Uses `OneTimeWorkRequest.Builder` - **Line 106**
- Enqueues with `workManager.enqueueUniqueWork()` - **Lines 115-119**
#### Boot Recovery
**File**: `android/src/main/java/com/timesafari/dailynotification/BootReceiver.kt`
- **Class**: `BootReceiver` - **Lines 18-100+**
- BroadcastReceiver for BOOT_COMPLETED
- `onReceive()` - **Line 24** - Handles boot intent
- `rescheduleNotifications()` - **Line 38+** - Reschedules all notifications from database
- Calls `NotifyReceiver.scheduleExactNotification()` - **Line 74**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationRebootRecoveryManager.java`
- **Class**: `BootCompletedReceiver` - **Lines 278-297**
- Inner BroadcastReceiver for boot events
- `onReceive()` - **Line 280** - Handles BOOT_COMPLETED action
- Calls `handleSystemReboot()` - **Line 290**
#### Notification Display
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java`
- **Function**: `onReceive()` - **Lines 51-485**
- Lightweight BroadcastReceiver triggered by AlarmManager
- Enqueues WorkManager work for heavy operations - **Line 100+**
- Extracts notification ID and action from intent
**File**: `android/src/main/java/com/timesafari/dailynotification/NotifyReceiver.kt`
- **Function**: `showNotification()` - **Lines 443-500**
- Displays notification using NotificationCompat
- Creates notification channel if needed - **Lines 454-470**
- Uses `NotificationCompat.Builder` - **Line 482**
#### Persistence
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt`
- Database operations use Room database:
- `getDatabase()` - Returns DailyNotificationDatabase instance
- Schedule storage in `scheduleDailyNotification()` - **Lines 1393-1410**
- Schedule storage in `scheduleDailyReminder()` - **Lines 813-821**
#### Permissions & Manifest
**File**: `android/src/main/AndroidManifest.xml`
- **Note**: Plugin manifest is minimal; receivers declared in consuming app manifest
- Check test app manifest: `test-apps/android-test-app/app/src/main/AndroidManifest.xml`
- Look for `RECEIVE_BOOT_COMPLETED` permission
- Look for `SCHEDULE_EXACT_ALARM` permission
- Look for `BootReceiver` registration
- Look for `DailyNotificationReceiver` registration
### iOS Implementation Details
#### Notification Scheduling
**File**: `ios/Plugin/DailyNotificationPlugin.swift`
- **Class**: `DailyNotificationPlugin` - **Lines 24-532**
- Main Capacitor plugin class
- Uses `UNUserNotificationCenter.current()` - **Line 26**
- Uses `BGTaskScheduler.shared` - **Line 27**
- **Function**: `scheduleUserNotification()` - **Lines 506-529**
- Schedules notification using UNUserNotificationCenter
- Creates `UNMutableNotificationContent` - **Line 507**
- Creates `UNTimeIntervalNotificationTrigger` - **Line 514**
- Adds request via `notificationCenter.add()` - **Line 522**
- **Function**: `scheduleBackgroundFetch()` - **Lines 495-504**
- Schedules BGTaskScheduler background fetch
- Creates `BGAppRefreshTaskRequest` - **Line 496**
- Submits via `backgroundTaskScheduler.submit()` - **Line 502**
**File**: `ios/Plugin/DailyNotificationScheduler.swift`
- **Class**: `DailyNotificationScheduler` - **Lines 20-236+**
- Manages UNUserNotificationCenter scheduling
- **Function**: `scheduleNotification()` - **Lines 133-198**
- Schedules notification with calendar trigger
- Creates `UNCalendarNotificationTrigger` - **Lines 172-175**
- Creates `UNNotificationRequest` - **Lines 178-182**
- Adds via `notificationCenter.add()` - **Line 185**
- **Function**: `cancelNotification()` - **Lines 205-213**
- Cancels notification by ID
- Uses `notificationCenter.removePendingNotificationRequests()` - **Line 206**
#### Background Tasks
**File**: `ios/Plugin/DailyNotificationBackgroundTasks.swift`
- Background task handling for BGTaskScheduler
- Register background task identifiers
- Handle background fetch execution
#### Persistence
**File**: `ios/Plugin/DailyNotificationPlugin.swift**
- **Note**: Check for UserDefaults, CoreData, or file-based storage
- Storage component: `var storage: DailyNotificationStorage?` - **Line 35**
- Scheduler component: `var scheduler: DailyNotificationScheduler?` - **Line 36**
#### iOS-2 Branch
- **Note**: Check `ios-2` branch for alternative implementations:
```bash
git checkout ios-2
# Compare ios/Plugin/ implementations
```
---
## Testing Tools & Commands
### Android Testing
```bash
# Check scheduled alarms
adb shell dumpsys alarm | grep -i timesafari
# Force kill (not force-stop) - adjust package name based on test app
adb shell am kill com.timesafari.dailynotification
# Or for test apps:
# adb shell am kill com.timesafari.androidtestapp
# adb shell am kill <package-name-from-test-app-manifest>
# View logs
adb logcat | grep -i "DN\|DailyNotification"
# Check WorkManager tasks
adb shell dumpsys jobscheduler | grep -i timesafari
# Build and install test app
cd test-apps/android-test-app
./gradlew installDebug
```
### iOS Testing
```bash
# View device logs (requires Xcode)
xcrun simctl spawn booted log stream --predicate 'processImagePath contains "DailyNotification"'
# List pending notifications (requires app code)
# Use UNUserNotificationCenter.getPendingNotificationRequests()
# Build test app (from test-apps/ios-test-app)
# Use Xcode or:
cd test-apps/ios-test-app
# Follow build instructions in test app README
# Check ios-2 branch for alternative implementations
git checkout ios-2
# Compare ios/Plugin/ implementations between branches
```
---
## Next Steps After Exploration
Once this exploration is complete:
1. **Document findings** in the deliverables listed above
2. **Identify gaps** between current behavior and desired behavior
3. **Create implementation directives** to address gaps (if needed)
4. **Update plugin documentation** to accurately reflect platform limitations
5. **Update API documentation** with appropriate warnings and caveats
+296
View File
@@ -0,0 +1,296 @@
# ✅ DIRECTIVE: Improvements to Alarm/Schedule/Notification Directives for Capacitor Plugin
**Author**: Matthew Raymer
**Date**: November 2025
**Status**: Active Improvement Directive
## 0. Goal of This Improvement Directive
Unify, refine, and strengthen the existing alarm-behavior directives so they:
1. **Eliminate duplication** between the Android Alarm Persistence Directive and the Plugin Exploration Directive.
2. **Clarify scope and purpose** (one is exploration/investigation; the other is platform mechanics).
3. **Produce a single cohesive standard** to guide future plugin improvements, testing, and documentation.
4. **Streamline testing expectations** into executable, check-box-style matrices.
5. **Map OS-level limitations directly to plugin-level behavior** so the JS/TS API contract is unambiguous.
6. **Add missing iOS-specific limitations, guarantees, and required recovery patterns**.
7. **Provide an upgrade path from exploration → design decisions → implementation directives**.
---
## 1. Structural Improvements to Apply
### 1.1 Split responsibilities into three documents (clear roles)
Your current directives mix *exploration*, *reference*, and *design rules*.
Improve by creating three clearly separated docs:
#### **Document A — Platform Capability Reference (Android/iOS)**
* Pure OS-level facts (no plugin logic).
* Use the Android Alarm Persistence Directive as the baseline.
* Add an equivalent iOS capability matrix.
* Keep strictly normative, minimal, stable.
#### **Document B — Plugin Behavior Exploration (Android/iOS)**
* Use the uploaded exploration directive as the baseline.
* Remove platform-mechanics explanations (moved to Document A).
* Replace vague descriptions with concrete, line-number-linked tasks.
* Add "expected vs actual" checklists to each test item.
#### **Document C — Plugin Requirements & Improvements**
* Generated after exploration.
* Defines the rules the plugin *must follow* to behave predictably.
* Defines recovery strategy (boot, reboot, missed alarms, force stop behavior).
* Defines JS API caveats and warnings.
**This file you're asking for now (improvement directive) becomes the origin of Document C.**
---
## 2. Improvements Needed to Existing Directives
### 2.1 Reduce duplication in Android section
The exploration directive currently repeats much of the alarm persistence directive.
**Improve by:**
* Referencing the Android alarm document instead of replicating content.
* Summarizing Android limitations in 5–7 lines in the exploration document.
* Keeping full explanation *only* in the Android alarm persistence reference file.
---
### 2.2 Add missing iOS counterpart to Android's capability matrix
You have a complete matrix for Android, but not iOS.
Add a **parallel iOS matrix**, including:
* Notification survives swipe → yes
* Notification survives reboot → yes (for calendar/time triggers)
* App logic runs in background → no
* Arbitrary code on trigger → no
* Recovery required → only if plugin has its own DB
This fixes asymmetry in current directives.
---
### 2.3 Clarify when plugin behavior depends on OS behavior
The exploration directive needs clearer labeling:
**Label each behavior as:**
* **OS-guaranteed** (iOS will fire pending notifications even when the app is dead)
* **Plugin-guaranteed** (plugin must reschedule alarms from DB)
* **Not allowed** (Android force-stop)
This removes ambiguity for plugin developers.
---
### 2.4 Introduce "Observed Behavior Table" in the exploration doc
Currently tests describe how to test but do not include space for results.
Add a table like:
| Scenario | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ------------------ | --------------------------- | ------------------------------ | ------------- | ----- |
| Swipe from recents | Fires | Fires | | |
| Device reboot | Does NOT fire automatically | Plugin must reschedule at boot | | |
This allows the exploration document to be executable.
---
### 2.5 Add "JS/TS API Contract" section to both directives
A critical missing piece.
Define what JavaScript developers can assume:
Examples:
* "Notification will still fire if the app is swiped from recents."
* "Notifications WILL NOT fire after Android Force Stop until the app is opened again."
* "Reboot behavior depends on platform: iOS preserves, Android destroys."
This section makes plugin behavior developer-friendly and predictable.
---
### 2.6 Strengthen direction on persistence strategy
The exploration directive asks "what is persisted?" but does not specify what *should* be persisted.
Add:
#### **Required Persistence Items**
* alarm_id
* trigger time
* repeat rule
* channel/priority
* payload
* time created, last modified
And:
#### **Required Recovery Points**
* Boot event
* App cold start
* App warm start
* App returning from background fetch
* User tapping notification
---
### 2.7 Add iOS Background Execution Limits section
Currently missing.
Add:
* No repeating background execution APIs except BGTaskScheduler
* BGTaskScheduler requires minimum intervals
* Plugin cannot rely on background execution to reconstruct alarms
* Only notification center persists notifications
This is critical for plugin parity.
---
### 2.8 Integrate "missed alarm recovery" requirements
The exploration directive asks whether plugin detects missed alarms.
The improvement directive must assert that it **must**.
Add requirement:
* If alarm time < now, and plugin is activated by reboot or user opening the app → plugin must generate a "missed alarm" event or notification.
---
## 3. Rewrite Testing Protocols into Standardized Formats
### Replace long paragraphs with clear test tables, e.g.:
#### Android Reboot Test
| Step | Action |
| ---- | ------------------------------------------------ |
| 1 | Schedule alarm for 10 minutes later |
| 2 | Reboot device |
| 3 | Do not open app |
| 4 | Does alarm fire? (Expected: NO) |
| 5 | Open app |
| 6 | Does plugin detect missed alarm? (Expected: YES) |
Same for iOS, force-stop, etc.
---
## 4. Add Missing Directive Sections
### 4.1 Policy Section
Define:
* Unsupported features
* Required permissions
* Required manifest entries
* Required notification channels
### 4.2 Versioning Requirements
Each change to alarm behavior is **breaking** and must have:
* MAJOR version bump
* Migration guide
---
## 5. Final Improvement Directive (What to Produce Next)
Here is your actionable deliverable list:
### **Produce Three New Documents**
1. **Platform Reference Document**
* Android alarm rules
* iOS notification rules
* Both in tabular form
* (Rewrites + merges the two uploaded directives)
2. **Exploration Results Template**
* Table format for results
* Expected vs actual
* Direct code references
* Remove platform explanation duplication
3. **Plugin Requirements + Future Implementation Directive**
* Persistence spec
* Recovery spec
* JS/TS API contract
* Parity rules
* Android/iOS caveats
* Required test harness
### **Implement Major Improvements**
* Strengthen separation of concerns
* Add iOS parity
* Integrate plugin-level persistence + recovery
* Add test matrices
* Add clear developer contracts
* Add missed-alarm handling requirements
* Add design rules for exact alarms and background restrictions
---
## 6. One-Sentence Summary
> **Rewrite the existing directives into three clear documents—platform reference, plugin exploration, and plugin implementation requirements—while adding iOS parity, recovery rules, persistence requirements, and standardized testing matrices, removing duplicated Android content, and specifying a clear JS/TS API contract.**
---
## Related Documentation
- [Android Alarm Persistence Directive](./android-alarm-persistence-directive.md) - Source material for Document A
- [Explore Alarm Behavior Directive](./explore-alarm-behavior-directive.md) - Source material for Document B
- [Boot Receiver Testing Guide](./boot-receiver-testing-guide.md) - Testing procedures
- [App Startup Recovery Solution](./app-startup-recovery-solution.md) - Recovery mechanisms
---
## Next Steps
1. **Create Document A**: Platform Capability Reference (Android/iOS)
2. **Create Document B**: Plugin Behavior Exploration Template
3. **Create Document C**: Plugin Requirements & Implementation Directive
4. **Execute exploration** using Document B
5. **Update Document C** with findings from exploration
6. **Implement improvements** based on Document C
---
## Status Tracking
- [ ] Document A created
- [ ] Document B created
- [ ] Document C created
- [ ] Exploration executed
- [ ] Findings documented
- [ ] Improvements implemented
@@ -0,0 +1,363 @@
# Plugin Behavior Exploration Template
**Author**: Matthew Raymer
**Date**: November 2025
**Status**: Active Exploration Template
## Purpose
This document provides an **executable template** for exploring and documenting the current plugin's alarm/schedule/notification behavior on Android and iOS.
**Use this template to**:
1. Test plugin behavior across different scenarios
2. Document expected vs actual results
3. Identify gaps between current behavior and platform capabilities
4. Generate findings for the Plugin Requirements document
**Reference**: See [Platform Capability Reference](./platform-capability-reference.md) for OS-level facts.
---
## 0. Quick Reference: Platform Capabilities
**Android**: See [Platform Capability Reference - Android Section](./platform-capability-reference.md#2-android-alarm-capability-matrix)
**iOS**: See [Platform Capability Reference - iOS Section](./platform-capability-reference.md#3-ios-notification-capability-matrix)
**Key Differences**:
* Android: Alarms wiped on reboot; must reschedule
* iOS: Notifications persist across reboot automatically
* Android: App code runs when alarm fires
* iOS: App code does NOT run when notification fires (unless user interacts)
---
## 1. Android Exploration
### 1.1 Code-Level Inspection Checklist
**Source Locations**:
- Plugin: `android/src/main/java/com/timesafari/dailynotification/`
- Test App: `test-apps/android-test-app/`
- Manifest: `test-apps/android-test-app/app/src/main/AndroidManifest.xml`
| Task | File/Function | Line | Status | Notes |
| ---- | ------------- | ---- | ------ | ----- |
| Locate main plugin class | `DailyNotificationPlugin.kt` | 1302 | ☐ | `scheduleDailyNotification()` |
| Identify alarm scheduling | `NotifyReceiver.kt` | 92 | ☐ | `scheduleExactNotification()` |
| Check AlarmManager usage | `NotifyReceiver.kt` | 219, 223, 231 | ☐ | `setAlarmClock()`, `setExactAndAllowWhileIdle()`, `setExact()` |
| Check WorkManager usage | `FetchWorker.kt` | 31 | ☐ | `scheduleFetch()` |
| Check notification display | `DailyNotificationWorker.java` | 200+ | ☐ | `displayNotification()` |
| Check boot receiver | `BootReceiver.kt` | 24 | ☐ | `onReceive()` handles `BOOT_COMPLETED` |
| Check persistence | `DailyNotificationPlugin.kt` | 1393+ | ☐ | Room database storage |
| Check exact alarm permission | `DailyNotificationPlugin.kt` | 1309 | ☐ | `canScheduleExactAlarms()` |
| Check manifest permissions | `AndroidManifest.xml` | - | ☐ | `RECEIVE_BOOT_COMPLETED`, `SCHEDULE_EXACT_ALARM` |
### 1.2 Behavior Testing Matrix
#### Test 1: Base Case
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Schedule alarm 2 minutes in future | - | Alarm scheduled | ☐ | |
| 2 | Leave app in foreground/background | - | - | ☐ | |
| 3 | Wait for trigger time | Alarm fires | Notification displayed | ☐ | |
| 4 | Check logs | - | No errors | ☐ | |
**Code Reference**: `NotifyReceiver.scheduleExactNotification()` line 92
---
#### Test 2: Swipe from Recents
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Schedule alarm 2-5 minutes in future | - | Alarm scheduled | ☐ | |
| 2 | Swipe app away from recents | - | - | ☐ | |
| 3 | Wait for trigger time | ✅ Alarm fires (OS resurrects process) | ✅ Notification displayed | ☐ | |
| 4 | Check app state on wake | Cold start | App process recreated | ☐ | |
| 5 | Check logs | - | No errors | ☐ | |
**Code Reference**: `NotifyReceiver.scheduleExactNotification()` uses `setAlarmClock()` line 219
**Platform Behavior**: OS-guaranteed (Android AlarmManager)
---
#### Test 3: OS Kill (Memory Pressure)
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Schedule alarm 2-5 minutes in future | - | Alarm scheduled | ☐ | |
| 2 | Force kill via `adb shell am kill <package>` | - | - | ☐ | |
| 3 | Wait for trigger time | ✅ Alarm fires | ✅ Notification displayed | ☐ | |
| 4 | Check logs | - | No errors | ☐ | |
**Platform Behavior**: OS-guaranteed (Android AlarmManager)
---
#### Test 4: Device Reboot
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Schedule alarm 10 minutes in future | - | Alarm scheduled | ☐ | |
| 2 | Reboot device | - | - | ☐ | |
| 3 | Do NOT open app | ❌ Alarm does NOT fire | ❌ No notification | ☐ | |
| 4 | Wait past scheduled time | ❌ No automatic firing | ❌ No notification | ☐ | |
| 5 | Open app manually | - | Plugin detects missed alarm | ☐ | |
| 6 | Check missed alarm handling | - | ✅ Missed alarm detected | ☐ | |
| 7 | Check rescheduling | - | ✅ Future alarms rescheduled | ☐ | |
**Code Reference**:
- Boot receiver: `BootReceiver.kt` line 24
- Rescheduling: `BootReceiver.kt` line 38+
**Platform Behavior**: Plugin-guaranteed (must implement boot receiver)
**Expected Plugin Behavior**: Plugin must reschedule from database on boot
---
#### Test 5: Android Force Stop
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Schedule alarm | - | Alarm scheduled | ☐ | |
| 2 | Go to Settings → Apps → [App] → Force Stop | ❌ All alarms removed | ❌ All alarms removed | ☐ | |
| 3 | Wait for trigger time | ❌ Alarm does NOT fire | ❌ No notification | ☐ | |
| 4 | Open app again | - | Plugin detects missed alarm | ☐ | |
| 5 | Check recovery | - | ✅ Missed alarm detected | ☐ | |
| 6 | Check rescheduling | - | ✅ Future alarms rescheduled | ☐ | |
**Platform Behavior**: Not allowed (Android hard kill)
**Expected Plugin Behavior**: Plugin must detect and recover on app restart
---
#### Test 6: Exact Alarm Permission (Android 12+)
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Revoke exact alarm permission | - | - | ☐ | |
| 2 | Attempt to schedule alarm | - | Plugin requests permission | ☐ | |
| 3 | Check settings opened | - | ✅ Settings opened | ☐ | |
| 4 | Grant permission | - | - | ☐ | |
| 5 | Schedule alarm | - | ✅ Alarm scheduled | ☐ | |
| 6 | Verify alarm fires | ✅ Alarm fires | ✅ Notification displayed | ☐ | |
**Code Reference**: `DailyNotificationPlugin.kt` line 1309, 1314-1324
---
### 1.3 Persistence Investigation
| Item | Expected | Actual | Code Reference | Notes |
| ---- | -------- | ------ | -------------- | ----- |
| Alarm ID stored | ✅ Yes | ☐ | `DailyNotificationPlugin.kt` line 1393+ | |
| Trigger time stored | ✅ Yes | ☐ | Room database | |
| Repeat rule stored | ✅ Yes | ☐ | Schedule entity | |
| Channel/priority stored | ✅ Yes | ☐ | NotificationContentEntity | |
| Payload stored | ✅ Yes | ☐ | ContentCache | |
| Time created/modified | ✅ Yes | ☐ | Entity timestamps | |
**Storage Location**: Room database (`DailyNotificationDatabase`)
---
### 1.4 Recovery Points Investigation
| Recovery Point | Expected Behavior | Actual Behavior | Code Reference | Notes |
| -------------- | ----------------- | --------------- | -------------- | ----- |
| Boot event | ✅ Reschedule all alarms | ☐ | `BootReceiver.kt` line 24 | |
| App cold start | ✅ Detect missed alarms | ☐ | Check plugin initialization | |
| App warm start | ✅ Verify active alarms | ☐ | Check plugin initialization | |
| Background fetch return | ⚠️ May reschedule | ☐ | `FetchWorker.kt` | |
| User taps notification | ✅ Launch app | ☐ | Notification intent | |
---
## 2. iOS Exploration
### 2.1 Code-Level Inspection Checklist
**Source Locations**:
- Plugin: `ios/Plugin/`
- Test App: `test-apps/ios-test-app/`
- Alternative: Check `ios-2` branch
| Task | File/Function | Line | Status | Notes |
| ---- | ------------- | ---- | ------ | ----- |
| Locate main plugin class | `DailyNotificationPlugin.swift` | 506 | ☐ | `scheduleUserNotification()` |
| Identify notification scheduling | `DailyNotificationScheduler.swift` | 133 | ☐ | `scheduleNotification()` |
| Check UNUserNotificationCenter usage | `DailyNotificationScheduler.swift` | 185 | ☐ | `notificationCenter.add()` |
| Check trigger types | `DailyNotificationScheduler.swift` | 172 | ☐ | `UNCalendarNotificationTrigger` |
| Check BGTaskScheduler usage | `DailyNotificationPlugin.swift` | 495 | ☐ | `scheduleBackgroundFetch()` |
| Check persistence | `DailyNotificationPlugin.swift` | 35 | ☐ | `storage: DailyNotificationStorage?` |
| Check app launch recovery | `DailyNotificationPlugin.swift` | 42 | ☐ | `load()` method |
### 2.2 Behavior Testing Matrix
#### Test 1: Base Case
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Schedule notification 2-5 minutes in future | - | Notification scheduled | ☐ | |
| 2 | Leave app backgrounded | - | - | ☐ | |
| 3 | Wait for trigger time | ✅ Notification fires | ✅ Notification displayed | ☐ | |
| 4 | Check logs | - | No errors | ☐ | |
**Code Reference**: `DailyNotificationScheduler.scheduleNotification()` line 133
---
#### Test 2: Swipe App Away
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Schedule notification 2-5 minutes in future | - | Notification scheduled | ☐ | |
| 2 | Swipe app away from app switcher | - | - | ☐ | |
| 3 | Wait for trigger time | ✅ Notification fires (OS handles) | ✅ Notification displayed | ☐ | |
| 4 | Check app state | App terminated | App not running | ☐ | |
**Platform Behavior**: OS-guaranteed (iOS UNUserNotificationCenter)
---
#### Test 3: Device Reboot
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Schedule notification for future time | - | Notification scheduled | ☐ | |
| 2 | Reboot device | - | - | ☐ | |
| 3 | Do NOT open app | ✅ Notification fires (OS persists) | ✅ Notification displayed | ☐ | |
| 4 | Check notification timing | ✅ On time (±180s tolerance) | ✅ On time | ☐ | |
**Platform Behavior**: OS-guaranteed (iOS persists calendar/time triggers)
**Note**: Only calendar and time-based triggers persist. Location triggers do not.
---
#### Test 4: Hard Termination & Relaunch
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Schedule repeating notifications | - | Notifications scheduled | ☐ | |
| 2 | Terminate app via Xcode/switcher | - | - | ☐ | |
| 3 | Allow some triggers to occur | ✅ Notifications fire | ✅ Notifications displayed | ☐ | |
| 4 | Reopen app | - | Plugin checks for missed events | ☐ | |
| 5 | Check missed event detection | ⚠️ May detect | ☐ | Plugin-specific |
| 6 | Check state recovery | ⚠️ May recover | ☐ | Plugin-specific |
**Platform Behavior**: OS-guaranteed for notifications; Plugin-guaranteed for missed event detection
---
#### Test 5: Background Execution Limits
| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes |
| ---- | ------ | ------------- | ------------------ | ------------- | ----- |
| 1 | Schedule BGTaskScheduler task | - | Task scheduled | ☐ | |
| 2 | Wait for system to execute | ⚠️ System-controlled | ⚠️ May not execute | ☐ | |
| 3 | Check execution timing | ⚠️ Not guaranteed | ⚠️ Not guaranteed | ☐ | |
| 4 | Check time budget | ⚠️ ~30 seconds | ⚠️ Limited time | ☐ | |
**Code Reference**: `DailyNotificationPlugin.scheduleBackgroundFetch()` line 495
**Platform Behavior**: System-controlled (not guaranteed)
---
### 2.3 Persistence Investigation
| Item | Expected | Actual | Code Reference | Notes |
| ---- | -------- | ------ | -------------- | ----- |
| Notification ID stored | ✅ Yes (in UNUserNotificationCenter) | ☐ | `UNNotificationRequest` | |
| Plugin-side storage | ⚠️ May not exist | ☐ | `DailyNotificationStorage?` | |
| Trigger time stored | ✅ Yes (in trigger) | ☐ | `UNCalendarNotificationTrigger` | |
| Repeat rule stored | ✅ Yes (in trigger) | ☐ | `repeats: true/false` | |
| Payload stored | ✅ Yes (in userInfo) | ☐ | `notificationContent.userInfo` | |
**Storage Location**:
- Primary: UNUserNotificationCenter (OS-managed)
- Secondary: Plugin storage (if implemented)
---
### 2.4 Recovery Points Investigation
| Recovery Point | Expected Behavior | Actual Behavior | Code Reference | Notes |
| -------------- | ----------------- | --------------- | -------------- | ----- |
| Boot event | ✅ Notifications fire automatically | ☐ | OS handles | |
| App cold start | ⚠️ May detect missed notifications | ☐ | Check `load()` method | |
| App warm start | ⚠️ May verify pending notifications | ☐ | Check plugin initialization | |
| Background fetch | ⚠️ May reschedule | ☐ | `BGTaskScheduler` | |
| User taps notification | ✅ App launched | ☐ | Notification action | |
---
## 3. Cross-Platform Comparison
### 3.1 Observed Behavior Summary
| Scenario | Android (Observed) | iOS (Observed) | Platform Difference |
| -------- | ------------------ | -------------- | ------------------- |
| Swipe/termination | ☐ | ☐ | Both should work |
| Reboot | ☐ | ☐ | iOS auto, Android manual |
| Force stop | ☐ | N/A | Android only |
| App code on trigger | ☐ | ☐ | Android yes, iOS no |
| Background execution | ☐ | ☐ | Android more flexible |
---
## 4. Findings & Gaps
### 4.1 Android Gaps
| Gap | Severity | Description | Recommendation |
| --- | -------- | ----------- | -------------- |
| Boot recovery | ☐ High/Medium/Low | Does plugin reschedule on boot? | Implement if missing |
| Missed alarm detection | ☐ High/Medium/Low | Does plugin detect missed alarms? | Implement if missing |
| Force stop recovery | ☐ High/Medium/Low | Does plugin recover after force stop? | Implement if missing |
| Persistence completeness | ☐ High/Medium/Low | Are all required fields persisted? | Verify and add if missing |
### 4.2 iOS Gaps
| Gap | Severity | Description | Recommendation |
| --- | -------- | ----------- | -------------- |
| Missed notification detection | ☐ High/Medium/Low | Does plugin detect missed notifications? | Implement if missing |
| Plugin-side persistence | ☐ High/Medium/Low | Does plugin persist state separately? | Consider if needed |
| Background task reliability | ☐ High/Medium/Low | Can plugin rely on BGTaskScheduler? | Document limitations |
---
## 5. Deliverables from This Exploration
After completing this exploration, generate:
1. **ALARMS_BEHAVIOR_MATRIX.md** - Completed test results
2. **PLUGIN_ALARM_LIMITATIONS.md** - Documented limitations and gaps
3. **Annotated code pointers** - Code locations with findings
4. **Open Questions / TODOs** - Unresolved issues
---
## Related Documentation
- [Platform Capability Reference](./platform-capability-reference.md) - OS-level facts
- [Plugin Requirements & Implementation](./plugin-requirements-implementation.md) - Requirements based on findings
- [Improve Alarm Directives](./improve-alarm-directives.md) - Improvement directive
---
## Notes for Explorers
* Fill in checkboxes (☐) as you complete each test
* Document actual results in "Actual Result" columns
* Add notes for any unexpected behavior
* Reference code locations when documenting findings
* Update "Findings & Gaps" section as you discover issues
* Use platform capability reference to understand expected OS behavior