# iOS Android Parity Directive — iOS Implementation Upgrade **Status:** ✅ **IN PROGRESS** - Plugin discovery resolved, implementation continuing **Date:** 2025-11-13 **Author:** Matthew Raymer **Branch:** `ios-2` **Objective:** Upgrade iOS implementation to match Android functionality while preserving Android code and TypeScript interface --- ## Executive Summary This directive outlines the plan to upgrade the iOS implementation of the Daily Notification Plugin to achieve feature parity with the functioning Android implementation. The Android codebase and TypeScript interface definitions will remain **completely untouched** during this work. ### Key Constraints - ✅ **Android code must remain unchanged** - No modifications to `src/android/` directory - ✅ **TypeScript interface must remain unchanged** - No modifications to `src/definitions.ts` - ✅ **iOS implementation must match Android functionality** - All Android `@PluginMethod` methods must have iOS equivalents - ✅ **Platform-specific adaptations allowed** - iOS can use Swift/iOS APIs but must provide same functionality ### Testing & Scope - ✅ **Primary Testing Target:** iOS Simulator - ✅ **Initial Scope:** One scheduled prefetch + one scheduled notification per day - **Prefetch Timing:** 5 minutes before notification time - **Notification Timing:** User-specified daily time - ✅ **Test App:** `ios-test-app` (equivalent to `android-test-app`) with same HTML/JS UI - ✅ **Rolling Window:** Deferred to Phase 2 (initial implementation supports single daily schedule) ### Readiness Milestones - **Phase 1:** Test app ready for basic testing (one prefetch + one notification per day) - **Phase 3:** Library ready for TimeSafari integration - **Phase 4:** Full library ready for production TimeSafari integration --- ## Plugin Discovery Issue - Systematic Analysis **Date:** 2025-11-13 **Status:** ✅ **RESOLVED** ### Problem Statement Capacitor iOS is not discovering the `DailyNotificationPlugin` class, preventing plugin methods from being accessible from JavaScript. **Symptoms:** - JavaScript reports: `DailyNotification plugin NOT found` - Available plugins only show: `["WebView","Console","CapacitorHttp","CapacitorCookies"]` - Plugin's `load()` method is never called - Plugin class exists and is properly linked (verified in build settings) ### Current Configuration **Verified Working:** - ✅ Plugin class correctly annotated: `@objc(DailyNotificationPlugin)` - ✅ Plugin inherits from `CAPPlugin` - ✅ Framework is linked: `-framework "DailyNotificationPlugin"` in build settings - ✅ `capacitor.plugins.json` exists with entry: `{"name":"DailyNotification","class":"DailyNotificationPlugin"}` - ✅ Podfile references plugin: `pod 'DailyNotificationPlugin', :path => '../../../../ios'` - ✅ Podspec configured: `static_framework = true` **Potential Issues to Investigate:** 1. **Static Framework Discovery:** - Capacitor iOS may not scan static frameworks for plugins - Static frameworks are linked but may not be in scanned bundle - **Test:** Remove `static_framework = true`, run `pod install`, rebuild 2. **Framework Loading:** - Framework may not be loaded into Objective-C runtime - Classes in static frameworks may not be discoverable via `objc_getClassList` - **Test:** Add explicit framework load in AppDelegate 3. **Module/Namespace Issues:** - Plugin may be in separate module that Capacitor doesn't scan - `use_frameworks!` creates separate framework modules - **Test:** Check if plugin class is accessible via `NSClassFromString` 4. **Capacitor Discovery Mechanism:** - iOS may use different discovery than Android - May require explicit registration vs. automatic discovery - **Test:** Check Capacitor source code for discovery mechanism 5. **Timing Issues:** - Plugin discovery may happen before framework is loaded - AppDelegate may need to force-load framework before Capacitor initializes - **Test:** Add framework load in `application(_:didFinishLaunchingWithOptions:)` ### Investigation Plan **Step 1: Verify Plugin Class Accessibility** ✅ **IN PROGRESS** - ✅ Added diagnostic logging to check if `NSClassFromString("DailyNotificationPlugin")` can find the class - ✅ Added logging to verify CAPPlugin inheritance - ✅ Added logging to check NSObjectProtocol conformance - ✅ Added logging to check framework bundle loading - ✅ Added logging in plugin's `load()` method to verify if Capacitor calls it - **Next:** Rebuild app and check Xcode console for diagnostic output **Step 2: Test Static Framework Hypothesis** - Temporarily remove `static_framework = true` from podspec - Run `pod install` to regenerate Pods - Rebuild and test if plugin is discovered - If discovered, static framework is the issue **Step 3: Test Framework Loading** - Add explicit framework load in AppDelegate before Capacitor initializes - Use `Bundle.load()` or similar to force-load framework - Check if this enables discovery **Step 4: Check Capacitor Discovery Code** - Review Capacitor iOS source for plugin discovery mechanism - Verify if it scans static frameworks or only dynamic frameworks - Check if there's a registration API we should use **Step 5: Alternative Solutions** - If static framework is the issue, consider: - Removing `static_framework = true` (makes it dynamic) - Adding plugin files directly to app target (bypasses CocoaPods framework) - Using explicit plugin registration API (if available) ### Diagnostic Logging Added (2025-11-13) **Files Modified:** - `test-apps/ios-test-app/ios/App/App/AppDelegate.swift` - Added comprehensive diagnostic tests - `ios/Plugin/DailyNotificationPlugin.swift` - Added logging in `load()` method **Diagnostic Tests:** 1. **NSClassFromString Test:** Checks if Objective-C runtime can find the plugin class 2. **Swift Type Access:** Verifies plugin class is accessible via Swift 3. **CAPPlugin Inheritance:** Confirms plugin is a CAPPlugin subclass 4. **NSObjectProtocol Conformance:** Verifies Objective-C runtime compatibility 5. **Class Name Retrieval:** Gets class name via `NSStringFromClass()` 6. **Framework Bundle Check:** Verifies if framework bundle is loaded 7. **Plugin Load Method:** Logs when Capacitor calls `load()` (indicates discovery) **Expected Output:** When app launches, Xcode console should show: - `DNP-DEBUG: AppDelegate.application(_:didFinishLaunchingWithOptions:) called` - `DNP-DEBUG: Testing plugin class accessibility...` - Either `✅ NSClassFromString found plugin class` or `❌ NSClassFromString could NOT find plugin class` - Either `✅ Plugin is a CAPPlugin subclass` or `❌ Plugin is NOT a CAPPlugin subclass` - `DNP-DEBUG: Plugin class name (NSStringFromClass): DailyNotificationPlugin` - Framework bundle status - **If plugin is discovered:** `DNP-DEBUG: DailyNotificationPlugin.load() called - Capacitor discovered the plugin!` - **If plugin is NOT discovered:** No `load()` log message ### Diagnostic Results (2025-11-13) **Test Results:** - ✅ **NSClassFromString found plugin class:** `DailyNotificationPlugin` - Class IS accessible to Objective-C runtime - ✅ **Plugin is a CAPPlugin subclass:** Inheritance confirmed - ✅ **Plugin conforms to NSObjectProtocol:** Objective-C runtime compatibility verified - ✅ **Plugin class name (NSStringFromClass):** `DailyNotificationPlugin` - Class name correct - ⚠️ **Plugin framework bundle not found via identifier:** Expected for static frameworks (not a separate bundle) - ⚠️ **Plugin framework not found in main bundle:** Expected for CocoaPods frameworks - ❌ **NO `load()` method called:** Capacitor is NOT discovering the plugin **Critical Finding (Initial):** The plugin class **IS accessible** to the Objective-C runtime (`NSClassFromString` works), but Capacitor's `load()` method is **NEVER called**. This confirmed: 1. **Plugin is properly linked** - Class exists and is accessible 2. **Plugin is correctly configured** - Inheritance and runtime compatibility verified 3. **Capacitor discovery is failing** - Plugin is not being discovered despite being accessible **Root Cause Discovered:** The plugin class did NOT conform to `CAPBridgedPlugin` protocol, which is required for Capacitor's discovery mechanism. Even though the class was in `objc_getClassList()`, `class_conformsToProtocol(aClass, CAPBridgedPlugin.self)` returned `NO`. **Solution Implemented (2025-11-13):** 1. **Added `CAPBridgedPlugin` conformance** via `@objc` extension: - Implemented `identifier` property (returns `"com.timesafari.dailynotification"`) - Implemented `jsName` property (returns `"DailyNotification"`) - Implemented `pluginMethods` property (returns array of all `@objc` methods) 2. **Force-load framework** in AppDelegate before Capacitor initializes: - Added `_ = DailyNotificationPlugin.self` to ensure class is in `objc_getClassList()` 3. **Removed duplicate BGTaskScheduler registration** from AppDelegate (plugin handles it) **Files Modified:** - `ios/Plugin/DailyNotificationPlugin.swift` - Added `@objc extension DailyNotificationPlugin: CAPBridgedPlugin` - `test-apps/ios-test-app/ios/App/App/AppDelegate.swift` - Added force-load, removed duplicate registration ### Final Test Results (2025-11-13) **Status:** ✅ **SUCCESS** - Plugin is now discovered and working **Verification Logs:** - ✅ `Class conforms to CAPBridgedPlugin (using .self): YES` - ✅ `DailyNotificationPlugin.load() called - Capacitor discovered the plugin!` - ✅ `DailyNotification plugin found` (JavaScript) - ✅ All methods accessible: `["addListener","configure","scheduleDailyNotification","getLastNotification","cancelAllNotifications","getNotificationStatus","updateSettings","checkPermissionStatus","requestNotificationPermissions",...]` **Why This Works:** - Built-in Capacitor plugins use the `CAP_PLUGIN` macro in Objective-C to add `CAPBridgedPlugin` conformance - Swift plugins must implement it manually via an `@objc` extension - The extension makes the protocol conformance visible to Objective-C runtime's `class_conformsToProtocol()` - Force-loading ensures the class is in `objc_getClassList()` when Capacitor scans ### Root Cause Discovery (2025-11-13) **Critical Finding:** Capacitor iOS uses `objc_getClassList()` to discover plugins, not `NSClassFromString()`. **How Capacitor Discovers Plugins:** 1. Calls `objc_getClassList()` to get all loaded classes 2. Checks if class conforms to `CAPBridgedPlugin` protocol 3. Checks if class is a `CapacitorPlugin` type (CAPPlugin & CAPBridgedPlugin) 4. Calls `registerPlugin()` for each matching class **The Problem:** - `objc_getClassList()` only includes classes from **loaded** frameworks - Even though `NSClassFromString("DailyNotificationPlugin")` works (class is accessible), the class may not be in `objc_getClassList()` if the framework isn't loaded yet - **More critically:** The plugin class did NOT conform to `CAPBridgedPlugin` protocol, which is required for discovery **Solution Implemented:** 1. **Force-load framework** in AppDelegate before Capacitor initializes: - Added `_ = DailyNotificationPlugin.self` in AppDelegate to force class load - Added diagnostic check to verify class is in `objc_getClassList()` 2. **Add CAPBridgedPlugin conformance** via `@objc` extension: - Implemented `identifier` property (returns `"com.timesafari.dailynotification"`) - Implemented `jsName` property (returns `"DailyNotification"`) - Implemented `pluginMethods` property (returns array of all `@objc` methods) **Why This Works:** - Built-in Capacitor plugins use the `CAP_PLUGIN` macro in Objective-C to add `CAPBridgedPlugin` conformance - Swift plugins must implement it manually via an `@objc` extension - The extension makes the protocol conformance visible to Objective-C runtime's `class_conformsToProtocol()` **Result:** - ✅ Plugin class is in `objc_getClassList()` - ✅ Plugin conforms to `CAPBridgedPlugin` protocol - ✅ Plugin is discovered by Capacitor - ✅ `DailyNotificationPlugin.load()` is called - ✅ Plugin appears in `window.Capacitor.Plugins.DailyNotification` - ✅ All methods are accessible from JavaScript **Files Modified:** - `ios/Plugin/DailyNotificationPlugin.swift` - Added `@objc extension DailyNotificationPlugin: CAPBridgedPlugin` with required properties - `test-apps/ios-test-app/ios/App/App/AppDelegate.swift` - Added force-load and diagnostic checks, removed duplicate BGTaskScheduler registration --- ## Minimal Viable Parity Definition **Objective:** Define the exact minimal scope iOS must meet before Android parity is considered achieved for Phase 1. ### Required Method Implementations (Phase 1) - `configure()` - Plugin configuration with database/storage options - `scheduleDailyNotification()` - Schedule single daily notification (one prefetch + one notification) - `getLastNotification()` - Get last delivered notification - `cancelAllNotifications()` - Cancel all scheduled notifications - `getNotificationStatus()` - Get current notification status - `updateSettings()` - Update notification settings ### Required Background Task Behavior - **Prefetch Task:** Scheduled 5 minutes before notification time via BGTaskScheduler - **Notification:** Scheduled at user-specified time via UNUserNotificationCenter - **Fallback:** If BGTaskScheduler task not run within 15 minutes of `earliestBeginDate`, trigger reschedule - **Error Handling:** Return same error codes as Android (see Error Code Matching section) ### Required Durability Guarantees - **Storage:** Content cached in UserDefaults or CoreData - **Persistence:** Schedule survives app termination - **Recovery:** On app launch, reschedule today's schedule from persisted settings - Full reboot detection and status reporting is introduced in **Phase 2** - Phase 1 uses simple "reschedule on launch" approach ### Required Error Handling Surface - All methods must return same error keys as Android - All methods must return same JSON shape as Android - All methods must log using same categories as Android ### Required JSON Return Structure Each method must match Android's return structure exactly: - Same field names - Same data types - Same nullability rules - Same error response format --- ## Current State Assessment ### Android Implementation (Reference) **Location:** `src/android/DailyNotificationPlugin.java` **Key Components:** - ✅ `DailyNotificationPlugin.java` - Main plugin class (1936 lines) - ✅ `DailyNotificationStorage.java` - Storage abstraction - ✅ `DailyNotificationScheduler.java` - AlarmManager-based scheduling - ✅ `DailyNotificationFetcher.java` - Background content fetching - ✅ `DailyNotificationDatabase.java` - SQLite database management - ✅ `DailyNotificationRollingWindow.java` - Rolling window safety - ✅ `DailyNotificationExactAlarmManager.java` - Exact alarm management - ✅ `DailyNotificationRebootRecoveryManager.java` - Reboot recovery - ✅ `DailyNotificationTTLEnforcer.java` - TTL enforcement - ✅ `DailyNotificationETagManager.java` - ETag caching - ✅ `DailyNotificationJWTManager.java` - JWT authentication - ✅ `EnhancedDailyNotificationFetcher.java` - TimeSafari API integration - ✅ `DailyNotificationFetchWorker.java` - WorkManager background worker - ✅ `DailyNotificationReceiver.java` - Broadcast receiver for alarms **Implemented Methods (from Android):** 1. `configure()` - Plugin configuration with database/storage options 2. `scheduleDailyNotification()` - Schedule daily notification 3. `getLastNotification()` - Get last delivered notification 4. `cancelAllNotifications()` - Cancel all scheduled notifications 5. `getNotificationStatus()` - Get current notification status 6. `updateSettings()` - Update notification settings 7. `getBatteryStatus()` - Get battery status information 8. `requestBatteryOptimizationExemption()` - Request battery optimization exemption 9. `setAdaptiveScheduling()` - Enable/disable adaptive scheduling 10. `getPowerState()` - Get current power state 11. `maintainRollingWindow()` - Manual rolling window maintenance 12. `getRollingWindowStats()` - Get rolling window statistics 13. `getExactAlarmStatus()` - Get exact alarm status 14. `requestExactAlarmPermission()` - Request exact alarm permission 15. `openExactAlarmSettings()` - Open exact alarm settings 16. `getRebootRecoveryStatus()` - Get reboot recovery status 17. `setActiveDidFromHost()` - Set activeDid from host (Phase 1) 18. `refreshAuthenticationForNewIdentity()` - Refresh authentication (Phase 1) 19. `clearCacheForNewIdentity()` - Clear cache for new identity (Phase 1) 20. `updateBackgroundTaskIdentity()` - Update background task identity (Phase 1) 21. `testJWTGeneration()` - Test JWT generation (Phase 1) 22. `testEndorserAPI()` - Test Endorser.ch API (Phase 1) 23. `coordinateBackgroundTasks()` - Coordinate background tasks (Phase 3) 24. `handleAppLifecycleEvent()` - Handle app lifecycle events (Phase 3) 25. `getCoordinationStatus()` - Get coordination status (Phase 3) 26. `scheduleDailyReminder()` - Schedule static daily reminder 27. `cancelDailyReminder()` - Cancel daily reminder 28. `getScheduledReminders()` - Get all scheduled reminders 29. `updateDailyReminder()` - Update existing daily reminder ### iOS Implementation (Current) **Location:** `ios/Plugin/DailyNotificationPlugin.swift` **Current Components:** - ✅ `DailyNotificationPlugin.swift` - Main plugin class (480 lines, partial) - ✅ `DailyNotificationDatabase.swift` - CoreData database (exists) - ✅ `DailyNotificationRollingWindow.swift` - Rolling window (exists) - ✅ `DailyNotificationTTLEnforcer.swift` - TTL enforcer (exists) - ✅ `DailyNotificationETagManager.swift` - ETag manager (exists) - ✅ `DailyNotificationBackgroundTaskManager.swift` - Background task manager (exists) - ✅ `DailyNotificationConstants.swift` - Constants (exists) - ✅ `DailyNotificationErrorHandler.swift` - Error handling (exists) - ✅ `DailyNotificationLogger.swift` - Logging (exists) - ✅ `DailyNotificationPowerManager.swift` - Power management (exists) - ✅ `DailyNotificationPerformanceOptimizer.swift` - Performance (exists) - ✅ `DailyNotificationCallbacks.swift` - Callbacks (exists) - ✅ `DailyNotificationConfig.swift` - Configuration (exists) - ✅ `DailyNotificationModel.swift` - Data models (exists) - ✅ `DailyNotificationMaintenanceWorker.swift` - Maintenance (exists) **Currently Implemented Methods:** 1. ✅ `configure()` - Basic configuration (needs enhancement) 2. ✅ `scheduleContentFetch()` - Basic BGTaskScheduler (needs enhancement) 3. ✅ `scheduleUserNotification()` - Basic UNUserNotificationCenter (needs enhancement) 4. ✅ `scheduleDualNotification()` - Dual scheduling (needs enhancement) 5. ✅ `getDualScheduleStatus()` - Status retrieval (needs enhancement) 6. ✅ `scheduleDailyReminder()` - Static reminders (implemented) 7. ✅ `cancelDailyReminder()` - Cancel reminders (implemented) 8. ✅ `getScheduledReminders()` - Get reminders (implemented) 9. ✅ `updateDailyReminder()` - Update reminders (implemented) **Missing Methods (Need Implementation):** 1. ❌ `scheduleDailyNotification()` - Main scheduling method 2. ❌ `getLastNotification()` - Last notification retrieval 3. ❌ `cancelAllNotifications()` - Cancel all notifications 4. ❌ `getNotificationStatus()` - Status retrieval 5. ❌ `updateSettings()` - Settings update 6. ❌ `getBatteryStatus()` - Battery status 7. ❌ `requestBatteryOptimizationExemption()` - Battery optimization 8. ❌ `setAdaptiveScheduling()` - Adaptive scheduling 9. ❌ `getPowerState()` - Power state 10. ❌ `maintainRollingWindow()` - Rolling window maintenance 11. ❌ `getRollingWindowStats()` - Rolling window stats 12. ❌ `getExactAlarmStatus()` - Exact alarm status (iOS equivalent) 13. ❌ `requestExactAlarmPermission()` - Alarm permission (iOS equivalent) 14. ❌ `openExactAlarmSettings()` - Alarm settings (iOS equivalent) 15. ❌ `getRebootRecoveryStatus()` - Reboot recovery status 16. ❌ `setActiveDidFromHost()` - ActiveDid management (Phase 1) 17. ❌ `refreshAuthenticationForNewIdentity()` - Auth refresh (Phase 1) 18. ❌ `clearCacheForNewIdentity()` - Cache clearing (Phase 1) 19. ❌ `updateBackgroundTaskIdentity()` - Background task identity (Phase 1) 20. ❌ `testJWTGeneration()` - JWT testing (Phase 1) 21. ❌ `testEndorserAPI()` - API testing (Phase 1) 22. ❌ `coordinateBackgroundTasks()` - Background coordination (Phase 3) 23. ❌ `handleAppLifecycleEvent()` - Lifecycle events (Phase 3) 24. ❌ `getCoordinationStatus()` - Coordination status (Phase 3) --- ## Implementation Strategy **Testing Environment:** iOS Simulator (primary testing target) **Initial Scope:** Simplified daily schedule - **One scheduled prefetch per day** (via BGTaskScheduler) - **Timing:** 5 minutes before notification time - **One scheduled notification per day** (via UNUserNotificationCenter) - **Timing:** User-specified daily time - Full rolling window and advanced features deferred to later phases ### Phase 1: Core Infrastructure Parity **Objective:** Implement core notification scheduling with single daily schedule (one prefetch + one notification). **Deliverables:** 1. Storage layer (UserDefaults/CoreData) with content caching 2. Scheduler (UNUserNotificationCenter) with timing tolerance (±180s) and permission auto-healing 3. Background fetching (BGTaskScheduler) with fallback rescheduling (15 min window) 4. Core methods: `configure()`, `scheduleDailyNotification()`, `getLastNotification()`, `cancelAllNotifications()`, `getNotificationStatus()`, `updateSettings()` **Key Constraints:** - Phase 1 uses dummy/static content fetcher (no JWT/ETag - deferred to Phase 3) - Timing tolerance: iOS notifications may drift up to 180 seconds - BGTaskScheduler fallback: Auto-reschedule if task missed within 15 min window (see BGTask Miss Detection below) - Concurrency: Use Swift `actor` or serial queue for DB/cache access (see Concurrency & Reentrancy Rules section) **Permission Auto-Healing:** On every call to `scheduleDailyNotification()`: 1. Read current `UNUserNotificationCenter.getNotificationSettings()` 2. If `authorizationStatus == .denied`: - Return error: `{ error: "notifications_denied", message: "Notification permissions denied" }` - Log: `[DNP-PLUGIN] notifications_denied for scheduleDailyNotification` - **DO NOT schedule** (prevents ghost schedules) 3. If `authorizationStatus == .notDetermined`: - **Option A (test app):** Call `requestAuthorization()` then retry once - **Option B (library):** Fail fast with `notifications_denied` and let host request permissions 4. **Never silently "succeed"** when notifications are denied **BGTask Miss Detection:** On every app launch and every plugin entry point that touches scheduling: 1. Read the last scheduled BGTask `earliestBeginDate` from storage 2. Read the last successful run timestamp from storage 3. If `now > earliestBeginDate + 15 minutes` **and no successful run timestamp is recorded**, treat the task as "missed": - Log: `[DNP-FETCH] BGTask missed window; rescheduling` - Schedule a new BGTask with `earliestBeginDate = now + 1 minute` - Update stored `earliestBeginDate` in storage **Successful Run Definition:** A "successful run" is defined as: BGTask handler invoked, content fetch completed (even if 304 Not Modified), and next schedule successfully submitted; only then update `lastSuccessfulRunTimestamp`. Do not set the timestamp on handler entry or partial completion. **Completion Criteria:** - All Phase 1 methods implemented and tested - Single daily schedule working (prefetch 5 min before notification) - Test app ready for iOS Simulator testing ### Phase 2: Advanced Features Parity **Objective:** Implement rolling window, TTL enforcement, exact alarm equivalent, reboot recovery, and power management. **Deliverables:** 1. Rolling window (expand beyond single daily schedule, enforce iOS 64 limit) 2. TTL enforcement (check at notification fire time, discard stale content) 3. Exact alarm equivalent (UNCalendarNotificationTrigger with tolerance, document iOS constraints) 4. Reboot recovery (uptime comparison strategy, auto-reschedule on app launch) 5. Power management (battery status, Background App Refresh status) **Key Constraints:** - **iOS cannot guarantee exact delivery** like Android's `setExact()`; TimeSafari must treat ±180s as the design target, not a bug - iOS notifications are "best effort" not "exact" (may drift, batch, or delay) - Rolling window must respect iOS 64 pending notification limit - Reboot detection via system uptime comparison (no BOOT_COMPLETED equivalent) **Completion Criteria:** - Rolling window functional with iOS limits enforced - TTL validation working at notification delivery - Reboot recovery tested and working - Test app ready for advanced feature testing ### Phase 3: TimeSafari Integration Parity **Objective:** Implement JWT authentication, ETag caching, and TimeSafari API integration. **Deliverables:** 1. JWT management (ES256K signing, token generation/caching, refresh logic) 2. ETag management (conditional requests, caching, invalidation) 3. Enhanced fetcher (TimeSafari API integration, activeDid support) 4. Integration methods: `setActiveDidFromHost()`, `refreshAuthenticationForNewIdentity()`, `clearCacheForNewIdentity()`, `updateBackgroundTaskIdentity()`, `testJWTGeneration()`, `testEndorserAPI()` **Key Constraints:** - Replace Phase 1 dummy fetcher with JWT-signed fetcher - Use same JWT algorithm as Android (ES256K) - ETag validation must match Android behavior **Completion Criteria:** - JWT authentication working with TimeSafari API - ETag caching reducing unnecessary network requests - Library ready for TimeSafari integration - Test app ready for integration testing ### Phase 4: Background Coordination Parity **Objective:** Implement background task coordination with TimeSafari PlatformServiceMixin. **Deliverables:** 1. Background coordination (`coordinateBackgroundTasks()` method) 2. Lifecycle management (`handleAppLifecycleEvent()` for app background/foreground) 3. Coordination status (`getCoordinationStatus()` for debugging) **Key Constraints:** - Coordinate BGTaskScheduler with app lifecycle events - Sync state between plugin and TimeSafari PlatformServiceMixin - Track coordination state for debugging **Completion Criteria:** - Background tasks coordinated with app lifecycle - Full library ready for production TimeSafari integration - Test app ready for full coordination testing --- ## Cross-Platform Method Equivalence Table **Canonical source of truth for all @PluginMethod implementations** **Note:** TypeScript signatures in this table are illustrative; the **source of truth** is `src/definitions.ts`. Any changes in `definitions.ts` must be reflected here and in the iOS implementation. Verify signatures match `definitions.ts` before implementation. | Android Method | TypeScript Interface | iOS Swift Method | iOS File | Phase | Status | |----------------|---------------------|------------------|----------|-------|--------| | `configure()` | `configure(options: ConfigureOptions): Promise` | `@objc func configure(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Complete | | `scheduleDailyNotification()` | `scheduleDailyNotification(options: NotificationOptions): Promise` | `@objc func scheduleDailyNotification(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Complete | | `getLastNotification()` | `getLastNotification(): Promise` | `@objc func getLastNotification(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Complete | | `cancelAllNotifications()` | `cancelAllNotifications(): Promise` | `@objc func cancelAllNotifications(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Complete | | `getNotificationStatus()` | `getNotificationStatus(): Promise` | `@objc func getNotificationStatus(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Complete | | `updateSettings()` | `updateSettings(settings: NotificationSettings): Promise` | `@objc func updateSettings(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Complete | | `checkPermissionStatus()` | `checkPermissionStatus(): Promise` | `@objc func checkPermissionStatus(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Complete | | `requestNotificationPermissions()` | `requestNotificationPermissions(): Promise` | `@objc func requestNotificationPermissions(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Complete | | `isChannelEnabled()` | `isChannelEnabled(channelId?: string): Promise<{ enabled: boolean; channelId: string }>` | `@objc func isChannelEnabled(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Complete | | `openChannelSettings()` | `openChannelSettings(channelId?: string): Promise` | `@objc func openChannelSettings(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Complete | | `getBatteryStatus()` | `getBatteryStatus(): Promise` | `@objc func getBatteryStatus(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing | | `requestBatteryOptimizationExemption()` | `requestBatteryOptimizationExemption(): Promise` | `@objc func requestBatteryOptimizationExemption(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing | | `setAdaptiveScheduling()` | `setAdaptiveScheduling(options: { enabled: boolean }): Promise` | `@objc func setAdaptiveScheduling(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing | | `getPowerState()` | `getPowerState(): Promise` | `@objc func getPowerState(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing | | `maintainRollingWindow()` | `maintainRollingWindow(): Promise` | `@objc func maintainRollingWindow(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing | | `getRollingWindowStats()` | `getRollingWindowStats(): Promise<{stats: string, ...}>` | `@objc func getRollingWindowStats(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing | | `getExactAlarmStatus()` | `getExactAlarmStatus(): Promise<{supported: boolean, ...}>` | `@objc func getExactAlarmStatus(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing | | `requestExactAlarmPermission()` | `requestExactAlarmPermission(): Promise` | `@objc func requestExactAlarmPermission(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing | | `openExactAlarmSettings()` | `openExactAlarmSettings(): Promise` | `@objc func openExactAlarmSettings(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing | | `getRebootRecoveryStatus()` | `getRebootRecoveryStatus(): Promise<{inProgress: boolean, ...}>` | `@objc func getRebootRecoveryStatus(_ call: CAPPluginCall)` | `DailyNotificationRebootRecoveryManager.swift` | 2 | ❌ Missing | | `setActiveDidFromHost()` | `setActiveDidFromHost(options: {activeDid: string}): Promise` | `@objc func setActiveDidFromHost(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 3 | ❌ Missing | | `refreshAuthenticationForNewIdentity()` | `refreshAuthenticationForNewIdentity(options: {activeDid: string}): Promise` | `@objc func refreshAuthenticationForNewIdentity(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 3 | ❌ Missing | | `clearCacheForNewIdentity()` | `clearCacheForNewIdentity(): Promise` | `@objc func clearCacheForNewIdentity(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 3 | ❌ Missing | | `updateBackgroundTaskIdentity()` | `updateBackgroundTaskIdentity(options: {activeDid: string}): Promise` | `@objc func updateBackgroundTaskIdentity(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 3 | ❌ Missing | | `testJWTGeneration()` | `testJWTGeneration(options?: {activeDid?: string}): Promise<{...}>` | `@objc func testJWTGeneration(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 3 | ❌ Missing | | `testEndorserAPI()` | `testEndorserAPI(options?: {activeDid?: string, apiServer?: string}): Promise<{...}>` | `@objc func testEndorserAPI(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 3 | ❌ Missing | | `coordinateBackgroundTasks()` | `coordinateBackgroundTasks(): Promise` | `@objc func coordinateBackgroundTasks(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 4 | ❌ Missing | | `handleAppLifecycleEvent()` | `handleAppLifecycleEvent(options: {lifecycleEvent: string}): Promise` | `@objc func handleAppLifecycleEvent(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 4 | ❌ Missing | | `getCoordinationStatus()` | `getCoordinationStatus(): Promise<{...}>` | `@objc func getCoordinationStatus(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 4 | ❌ Missing | | `scheduleDailyReminder()` | `scheduleDailyReminder(options: ReminderOptions): Promise` | `@objc func scheduleDailyReminder(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | - | ✅ Complete | | `cancelDailyReminder()` | `cancelDailyReminder(options: {reminderId: string}): Promise` | `@objc func cancelDailyReminder(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | - | ✅ Complete | | `getScheduledReminders()` | `getScheduledReminders(): Promise<{reminders: ReminderInfo[]}>` | `@objc func getScheduledReminders(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | - | ✅ Complete | | `updateDailyReminder()` | `updateDailyReminder(options: ReminderOptions): Promise` | `@objc func updateDailyReminder(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | - | ✅ Complete | --- ## Component Mapping: Android → iOS ### Core Components | Android Component | iOS Equivalent | Status | Notes | |------------------|----------------|--------|-------| | `DailyNotificationPlugin.java` | `DailyNotificationPlugin.swift` | ✅ Complete | Phase 1 methods implemented | | `DailyNotificationStorage.java` | `DailyNotificationStorage.swift` | ✅ Complete | Created with Phase 1 | | `DailyNotificationScheduler.java` | `DailyNotificationScheduler.swift` | ✅ Complete | Created with Phase 1 | | `DailyNotificationFetcher.java` | `DailyNotificationBackgroundTaskManager.swift` | ✅ Exists | Needs enhancement | | `DailyNotificationDatabase.java` | `DailyNotificationDatabase.swift` | ✅ Exists | CoreData-based | | `DailyNotificationRollingWindow.java` | `DailyNotificationRollingWindow.swift` | ✅ Exists | Needs enhancement | | `DailyNotificationExactAlarmManager.java` | iOS equivalent needed | ❌ Missing | Use UNUserNotificationCenter | | `DailyNotificationRebootRecoveryManager.java` | iOS equivalent needed | ❌ Missing | Use app launch detection | | `DailyNotificationTTLEnforcer.java` | `DailyNotificationTTLEnforcer.swift` | ✅ Exists | Needs enhancement | | `DailyNotificationETagManager.java` | `DailyNotificationETagManager.swift` | ✅ Exists | Needs enhancement | | `DailyNotificationJWTManager.java` | iOS equivalent needed | ❌ Missing | Create new file | | `EnhancedDailyNotificationFetcher.java` | iOS equivalent needed | ❌ Missing | Create new file | | `DailyNotificationFetchWorker.java` | BGTaskScheduler handler | ❌ Missing | Implement in plugin | | `DailyNotificationReceiver.java` | Notification delegate | ❌ Missing | Implement in plugin | ### Platform API Mapping | Android API | iOS Equivalent | Implementation Notes | |-------------|----------------|---------------------| | `AlarmManager.setExact()` | `UNUserNotificationCenter.add()` with `UNCalendarNotificationTrigger` | Use calendar-based triggers for daily scheduling | | `WorkManager.enqueue()` | `BGTaskScheduler.submit()` | Use BGAppRefreshTaskRequest for background fetching | | `SharedPreferences` | `UserDefaults` | For simple key-value storage | | `SQLiteDatabase` | `CoreData` / `SQLite.swift` | For complex data storage | | `NotificationManager` | `UNUserNotificationCenter` | For notification delivery | | `PowerManager` | `UIDevice.batteryState` | For battery status | | `BroadcastReceiver` | App lifecycle notifications | For reboot recovery | | `PendingIntent` | `UNNotificationRequest` | For scheduled notifications | --- ## Method Implementation Checklist ### Core Methods (Phase 1) - [x] `configure(options: ConfigureOptions)` - Enhanced configuration ✅ - [x] `scheduleDailyNotification(options: NotificationOptions)` - Main scheduling ✅ - [x] `getLastNotification()` - Last notification retrieval ✅ - [x] `cancelAllNotifications()` - Cancel all notifications ✅ - [x] `getNotificationStatus()` - Status retrieval ✅ - [x] `updateSettings(settings: NotificationSettings)` - Settings update ✅ - [x] `checkPermissionStatus()` - Permission status check ✅ - [x] `requestNotificationPermissions()` - Request permissions ✅ - [x] `isChannelEnabled(channelId?)` - Channel enabled check (iOS: app-wide) ✅ - [x] `openChannelSettings(channelId?)` - Open channel settings (iOS: app Settings) ✅ ### Power Management Methods (Phase 2) - [ ] `getBatteryStatus()` - Battery status - [ ] `requestBatteryOptimizationExemption()` - Battery optimization (iOS: Background App Refresh) - [ ] `setAdaptiveScheduling(options: { enabled: boolean })` - Adaptive scheduling - [ ] `getPowerState()` - Power state ### Rolling Window Methods (Phase 2) - [ ] `maintainRollingWindow()` - Manual maintenance - [ ] `getRollingWindowStats()` - Statistics retrieval ### Exact Alarm Methods (Phase 2) - iOS Equivalents - [ ] `getExactAlarmStatus()` - Notification delivery status - [ ] `requestExactAlarmPermission()` - Notification permissions - [ ] `openExactAlarmSettings()` - Open notification settings ### Reboot Recovery Methods (Phase 2) - [ ] `getRebootRecoveryStatus()` - Recovery status ### TimeSafari Integration Methods (Phase 3) - [ ] `setActiveDidFromHost(options: { activeDid: string })` - ActiveDid management - [ ] `refreshAuthenticationForNewIdentity(options: { activeDid: string })` - Auth refresh - [ ] `clearCacheForNewIdentity()` - Cache clearing - [ ] `updateBackgroundTaskIdentity(options: { activeDid: string })` - Background task identity - [ ] `testJWTGeneration(options?: { activeDid?: string })` - JWT testing - [ ] `testEndorserAPI(options?: { activeDid?: string, apiServer?: string })` - API testing ### Background Coordination Methods (Phase 4) - [ ] `coordinateBackgroundTasks()` - Background coordination - [ ] `handleAppLifecycleEvent(options: { lifecycleEvent: string })` - Lifecycle events - [ ] `getCoordinationStatus()` - Coordination status ### Enhanced Dual Scheduling Methods (Already Partially Implemented) - [x] `scheduleContentFetch(config: ContentFetchConfig)` - Needs enhancement - [x] `scheduleUserNotification(config: UserNotificationConfig)` - Needs enhancement - [x] `scheduleDualNotification(config: DualScheduleConfiguration)` - Needs enhancement - [x] `getDualScheduleStatus()` - Needs enhancement ### Reminder Methods (Already Implemented) - [x] `scheduleDailyReminder(options: ReminderOptions)` - ✅ Complete - [x] `cancelDailyReminder(options: { reminderId: string })` - ✅ Complete - [x] `getScheduledReminders()` - ✅ Complete - [x] `updateDailyReminder(options: ReminderOptions)` - ✅ Complete --- ## Platform-Specific Considerations ### Testing Environment - **Primary Testing Target:** iOS Simulator - **Simulator Testing:** All features should be testable on iOS Simulator - **Physical Device Testing:** Optional for final validation ### iOS Simulator vs Real Device Differences | Behavior | Simulator | Real Device | |----------|-----------|-------------| | BGTaskScheduler | Runs instantly when forced | Often delayed, may not run at all | | Notifications | Always delivered immediately | Delivery may be delayed or batched | | Battery state | Always "unplugged" simulation | Real battery impacts scheduling | | App kill behavior | More predictable | iOS kills background tasks aggressively | | Background App Refresh | Always enabled | User may disable in Settings | | Low Power Mode | Not available | May delay/disable background tasks | **Testing Strategy:** - Develop and test primarily on iOS Simulator - Validate critical paths on physical device before release - Document simulator limitations in test results ### iOS Notification Limits - **Maximum Pending Notifications:** 64 (system limit) - **Maximum Daily Notifications:** ~20 (recommended) - **Initial Scope:** One prefetch + one notification per day - **Rolling Window Strategy:** Always arm today's notifications, arm tomorrow's if within limits (Phase 2+) ### iOS Background Execution - **BGTaskScheduler Limits:** - Background refresh tasks: ~30 seconds execution time - Processing tasks: Variable, depends on system resources - **Strategy:** Efficient processing, immediate next-schedule after completion - **Simulator Limitation:** BGTaskScheduler doesn't work reliably on simulator (Code=1: notPermitted) - This is **expected behavior** - background fetch scheduling will fail on simulator - Notifications will still be delivered, just without prefetch - Real device testing required to verify background fetch works - Error handling logs clear message that simulator limitation is expected ### iOS Storage Options - **UserDefaults:** For simple configuration and preferences - **CoreData:** For complex relational data (schedules, content cache) - **File System:** For large content (images, media) ### iOS Permission Model - **Notification Permissions:** Required for all notification delivery - **Background App Refresh:** Required for background fetching - **Strategy:** Request permissions early, provide clear explanations ### Concurrency & Reentrancy Rules **Critical:** All access to shared state must be serialized to prevent race conditions. **Implementation Requirements:** 1. **Single Actor/Queue for State Access:** - All access to `DailyNotificationDatabase`, `DailyNotificationStorage`, `DailyNotificationRollingWindow`, and `DailyNotificationTTLEnforcer` **must go through** a single `actor` (e.g., `DailyNotificationStateActor`) or a dedicated serial `DispatchQueue` - No direct CoreData access from CAP bridge methods; they must delegate into the actor/queue - Background tasks and plugin method calls must never touch the DB concurrently outside this actor/queue 2. **Actor Pattern (Recommended):** ```swift actor DailyNotificationStateActor { private let database: DailyNotificationDatabase // All DB operations here } ``` 3. **Serial Queue Pattern (Alternative):** ```swift private let stateQueue = DispatchQueue(label: "com.timesafari.dailynotification.state", attributes: .serial) ``` 4. **Enforcement:** - All plugin methods must call through actor/queue - All background task handlers must call through actor/queue - No direct property access to shared state from multiple threads ### Error Code Matching with Android **Requirement:** iOS must return the same error codes and JSON structure as Android. **Authoritative Source:** The **authoritative list of error codes** is defined in Android's `DailyNotificationErrorHandler` (or equivalent error handling class). iOS must mirror that list exactly, including semantics. **✅ COMPLETE:** Error code mapping verified. See `doc/IOS_ANDROID_ERROR_CODE_MAPPING.md` for comprehensive mapping table. **Status:** ✅ **VERIFIED** - All Phase 1 error codes semantically match Android error messages. iOS provides structured error responses as required by directive. **Error Response Format:** ```json { "error": "error_code", "message": "Human-readable error message" } ``` **Common Error Codes (examples - verify against Android source):** - `notifications_denied` - Notification permissions not granted - `invalid_time_format` - Time parameter format invalid - `scheduling_failed` - Failed to schedule notification - `background_refresh_disabled` - Background App Refresh disabled - `task_scheduling_failed` - BGTaskScheduler submission failed **Implementation:** - Use same error keys as Android implementation (from authoritative source) - Use same JSON shape for all error responses - Log using same categories as Android (`DNP-PLUGIN`, `DNP-SCHEDULER`, etc.) --- ## Validation & QA Plan ### Testing Strategy **Comprehensive testing guide:** See `doc/test-app-ios/IOS_PREFETCH_TESTING.md` for detailed testing procedures, log checklists, and behavior classification. **Key Testing Principles:** - **Simulator:** Test logic correctness (deterministic behavior) - **Real Device:** Test timing and reliability (heuristic behavior) - **Log-Driven:** Use structured logging to trace full execution paths - **Failure Modes:** Test negative paths (network failures, permission denials) **Behavior Classification:** - **🟢 Deterministic:** Code logic, error handling, data processing (test in simulator) - **🟡 Partially Deterministic:** Scheduling logic works, but timing is heuristic (test flow in simulator, timing on device) - **🔴 Heuristic:** Background execution timing, notification delivery timing (test on real device only) ### Method Behavior Classification **🟢 Deterministic Methods (Test in Simulator):** - `configure()` - Configuration logic - `getLastNotification()` - Data retrieval - `cancelAllNotifications()` - Cancellation logic - `getNotificationStatus()` - Status calculation - `updateSettings()` - Settings update logic - `getBatteryStatus()` - Battery state reading - `getPowerState()` - Power state reading - `getRollingWindowStats()` - Statistics calculation - `testJWTGeneration()` - JWT generation logic - `testEndorserAPI()` - API call logic **🟡 Partially Deterministic Methods (Test Flow in Simulator, Timing on Device):** - `scheduleDailyNotification()` - Scheduling logic is deterministic, but iOS delivery timing is heuristic - `maintainRollingWindow()` - Logic is deterministic, but when iOS allows execution is heuristic **🔴 Heuristic Methods (Test on Real Device Only):** - Background task execution timing (BGTaskScheduler) - iOS controls when tasks run - Notification delivery timing (UNUserNotificationCenter) - iOS controls delivery timing - Reboot recovery detection (uptime comparison) - May vary based on system state **See `doc/test-app-ios/IOS_PREFETCH_TESTING.md` for detailed testing procedures for each behavior class.** ### Unit Tests - [ ] Test all new methods match Android behavior - [ ] Test storage layer (UserDefaults and CoreData) - [ ] Test scheduler with iOS notification limits - [ ] Test background task execution - [ ] Test TTL enforcement - [ ] Test rolling window maintenance ### Integration Tests - [ ] Test full notification pipeline (fetch → cache → schedule → display) - [ ] Test offline scenarios - [ ] Test background execution reliability - [ ] Test reboot recovery - [ ] Test activeDid integration ### Manual Testing - [ ] Test on iOS Simulator (primary) - Logic correctness - [ ] Test on physical iOS devices (validation) - Timing and reliability - [ ] Test Background App Refresh settings - [ ] Test notification permissions - [ ] Test battery optimization scenarios - [ ] Test app lifecycle events - [ ] Test prefetch execution (see `doc/test-app-ios/IOS_PREFETCH_TESTING.md`) --- ## File Organization ### New Files to Create ``` ios/Plugin/ ├── DailyNotificationStorage.swift # Storage abstraction (new) ├── DailyNotificationScheduler.swift # Scheduler implementation (new) ├── DailyNotificationJWTManager.swift # JWT management (new) ├── EnhancedDailyNotificationFetcher.swift # Enhanced fetcher (new) └── DailyNotificationRebootRecoveryManager.swift # Reboot recovery (new) ``` ### Files to Enhance ``` ios/Plugin/ ├── DailyNotificationPlugin.swift # Add missing methods ├── DailyNotificationBackgroundTaskManager.swift # Enhance background tasks ├── DailyNotificationRollingWindow.swift # Enhance rolling window ├── DailyNotificationTTLEnforcer.swift # Enhance TTL enforcement └── DailyNotificationETagManager.swift # Enhance ETag management ``` ### Test App Files to Create ``` test-apps/ios-test-app/ # New iOS test app (equivalent to android-test-app) ├── App/ │ ├── AppDelegate.swift # App delegate with plugin setup │ ├── Info.plist # iOS permissions and config │ ├── SceneDelegate.swift # Scene management (if needed) │ └── Main.storyboard # Main storyboard (if needed) ├── App.xcodeproj/ # Xcode project ├── App.xcworkspace/ # Xcode workspace (if using CocoaPods) ├── Podfile # CocoaPods dependencies └── www/ # Web assets (same as android-test-app) ├── index.html # Same UI as android-test-app ├── capacitor.config.json # Capacitor config └── capacitor.plugins.json # Plugin registration ``` ### Build Scripts to Create ``` scripts/ └── build-ios-test-app.sh # iOS test app build script (new) # Location: Root-level script (scripts/build-ios-test-app.sh) # Pattern: Similar to scripts/build-native.sh # Features: # - Check environment (xcodebuild, pod) # - cd into test-apps/ios-test-app internally # - Install dependencies (pod install) # - Build for simulator or device # - Clear error messages and logging ``` --- ## Success Criteria ### Functional Parity - [ ] All Android `@PluginMethod` methods have iOS equivalents - [ ] All methods return same data structures as Android - [ ] All methods handle errors consistently with Android - [ ] All methods log consistently with Android ### Platform Adaptations - [ ] iOS uses appropriate iOS APIs (UNUserNotificationCenter, BGTaskScheduler) - [ ] iOS respects iOS limits (64 notification limit, background execution limits) - [ ] iOS provides iOS-specific features where appropriate (Background App Refresh) ### Code Quality - [ ] All code follows Swift best practices - [ ] All code is documented with file-level and method-level comments - [ ] All code includes error handling and logging - [ ] All code is type-safe ### Testing - [ ] Unit tests cover all new methods - [ ] Integration tests verify full pipeline - [ ] Manual testing confirms real-world behavior ### Test App - [ ] `ios-test-app` created with same UI as `android-test-app` - [ ] Test app uses iOS plugin implementation - [ ] All permissions properly configured in Info.plist - [ ] Build script `build-ios-test-app.sh` created and working - [ ] Build works via both Xcode GUI and command line - [ ] Comprehensive logging accessible via Console.app - [ ] Background task debugging tools available --- ## Risks & Mitigations ### Risk 1: iOS Background Execution Limitations **Risk:** iOS background execution is more limited than Android **Mitigation:** - Use efficient processing algorithms - Schedule next task immediately after completion - Provide clear user guidance on Background App Refresh ### Risk 2: iOS Notification Limits **Risk:** iOS 64 notification limit may conflict with rolling window strategy **Mitigation:** - Implement smart rolling window that respects limits - Prioritize today's notifications over tomorrow's - Provide clear error messages when limits are reached ### Risk 3: TypeScript Interface Compatibility **Risk:** iOS implementation may not match TypeScript interface exactly **Mitigation:** - Test all methods against TypeScript interface - Ensure return types match exactly - Ensure error handling matches Android behavior ### Risk 4: Platform-Specific Behavior Differences **Risk:** iOS and Android may behave differently for same operations **Mitigation:** - Document all platform differences - Provide platform-specific error messages where appropriate - Test cross-platform compatibility --- ## Timeline Estimate ### Phase 1: Core Infrastructure - Storage layer implementation - Scheduler implementation (single daily schedule: one prefetch + one notification) - Prefetch scheduled 5 minutes before notification time - Background fetching enhancement (single daily prefetch, 5 minutes before notification) - Core methods implementation - iOS test app creation (ios-test-app) - Build script creation (build-ios-test-app.sh) - Testing on iOS Simulator **Test App Readiness:** ✅ Ready for testing after Phase 1 completion - Basic scheduling (one prefetch 5 minutes before + one notification per day) - Core plugin methods functional - Testable on iOS Simulator ### Phase 2: Advanced Features - Rolling window enhancement (expands beyond single daily schedule) - TTL enforcement enhancement - Exact alarm equivalent implementation - Reboot recovery implementation - Power management enhancement - Testing on iOS Simulator **Test App Readiness:** ✅ Ready for testing after Phase 2 completion - Rolling window and advanced features functional - Testable on iOS Simulator ### Phase 3: TimeSafari Integration - JWT management implementation - ETag management enhancement - Enhanced fetcher implementation - Phase 1 methods implementation - Testing on iOS Simulator **TimeSafari Integration Readiness:** ✅ Library ready for TimeSafari integration after Phase 3 completion **Test App Readiness:** ✅ Ready for testing after Phase 3 completion - TimeSafari integration features functional - Testable on iOS Simulator ### Phase 4: Background Coordination - Background coordination implementation - Lifecycle management implementation - Coordination status implementation - Testing on iOS Simulator **TimeSafari Integration Readiness:** ✅ Full library ready for production TimeSafari integration after Phase 4 completion **Test App Readiness:** ✅ Ready for testing after Phase 4 completion - Full background coordination functional - Testable on iOS Simulator --- ## Dependencies ### External Dependencies - **Capacitor:** iOS plugin framework - **UserNotifications:** iOS notification framework - **BackgroundTasks:** iOS background execution framework - **CoreData:** iOS data persistence framework ### Internal Dependencies - **TypeScript Interface:** Must remain unchanged (`src/definitions.ts`) - **Android Implementation:** Reference implementation (read-only) - **Existing iOS Components:** Build upon existing Swift files --- ## References ### Android Implementation Files - `src/android/DailyNotificationPlugin.java` - Main plugin class - `src/android/DailyNotificationStorage.java` - Storage layer - `src/android/DailyNotificationScheduler.java` - Scheduler - `src/android/DailyNotificationFetcher.java` - Background fetching - `src/android/DailyNotificationDatabase.java` - Database management - `src/android/DailyNotificationRollingWindow.java` - Rolling window - `src/android/DailyNotificationExactAlarmManager.java` - Exact alarms - `src/android/DailyNotificationRebootRecoveryManager.java` - Reboot recovery - `src/android/DailyNotificationTTLEnforcer.java` - TTL enforcement - `src/android/DailyNotificationETagManager.java` - ETag management - `src/android/DailyNotificationJWTManager.java` - JWT management - `src/android/EnhancedDailyNotificationFetcher.java` - Enhanced fetcher ### iOS Implementation Files - `ios/Plugin/DailyNotificationPlugin.swift` - Main plugin class - `ios/Plugin/DailyNotificationDatabase.swift` - Database (CoreData) - `ios/Plugin/DailyNotificationRollingWindow.swift` - Rolling window - `ios/Plugin/DailyNotificationTTLEnforcer.swift` - TTL enforcement - `ios/Plugin/DailyNotificationBackgroundTaskTestHarness.swift` - BGTaskScheduler test harness (reference implementation) - `ios/Plugin/DailyNotificationETagManager.swift` - ETag management - `ios/Plugin/DailyNotificationBackgroundTaskManager.swift` - Background tasks ### TypeScript Interface - `src/definitions.ts` - Complete TypeScript interface definition ### Documentation - `doc/implementation-roadmap.md` - Implementation roadmap - `doc/INTEGRATION_CHECKLIST.md` - Integration checklist - `doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.md` - iOS test app detailed requirements (must be created in Phase 1) - `doc/test-app-ios/IOS_PREFETCH_TESTING.md` - Comprehensive iOS prefetch testing guide (testing procedures, log checklists, behavior classification) - `README.md` - Project documentation --- ## Competence Hooks ### Why This Works - **Platform Parity:** Matching Android functionality ensures consistent user experience across platforms - **Incremental Approach:** Phased implementation reduces risk and allows for iterative testing - **Platform Adaptations:** Using iOS-native APIs ensures optimal performance and reliability ### Common Pitfalls - **Background Execution Limits:** iOS background execution is more limited than Android - must design for efficiency - **Notification Limits:** iOS 64 notification limit requires careful rolling window management - **Permission Model:** iOS requires explicit permission requests - must handle gracefully ### Next Skill Unlock - **iOS Background Execution:** Understanding BGTaskScheduler and background execution patterns - **iOS Notification System:** Deep dive into UNUserNotificationCenter and notification delivery guarantees ### Teach-Back **Question:** "How does iOS background execution differ from Android, and how does this affect the notification scheduling strategy?" **Expected Answer:** iOS background execution is more limited (30 seconds for refresh tasks) and less reliable than Android WorkManager. This requires efficient processing, immediate next-schedule after completion, and fallback strategies for when background execution fails. --- ## Collaboration Hooks ### Discussion Points 1. **Platform Differences:** How should we handle platform-specific behavior differences in error messages and user guidance? 2. **Testing Strategy:** What level of cross-platform testing is needed to ensure parity? 3. **Performance:** How do we ensure iOS implementation performs as well as Android? ### Review Points - **Code Review:** All new iOS code should be reviewed against Android implementation for functional parity - **Architecture Review:** iOS component architecture should be reviewed for maintainability - **Testing Review:** Test coverage should be reviewed for completeness ### Stakeholders - **iOS Developer:** Primary implementer - **Android Developer:** Reference implementation owner (consultation) - **TypeScript Developer:** Interface maintainer (consultation) - **QA:** Testing and validation --- ## Decision Log ### 2025-11-13: Directive Created **Decision:** Create comprehensive directive for iOS Android parity implementation **Rationale:** Need clear plan for upgrading iOS while preserving Android and TypeScript interface **Status:** ✅ Approved for implementation ### 2025-11-13: Build Compilation Fixes **Decision:** Fix all Swift compilation errors to enable test app building **Rationale:** Test app must build successfully before testing can begin **Status:** ✅ Complete **Lessons Learned:** 1. **Type System Mismatches:** - **Issue:** `NotificationContent` properties `scheduledTime` and `fetchedAt` were `Int64` (matching Android `long`), but Swift `Date(timeIntervalSince1970:)` expects `Double` - **Fix:** Explicitly convert `Int64` to `Double` when creating `Date` objects: `Date(timeIntervalSince1970: Double(value) / 1000.0)` - **Files Affected:** `DailyNotificationTTLEnforcer.swift`, `DailyNotificationRollingWindow.swift` - **Lesson:** Always verify type compatibility when bridging between platforms, even when types appear similar 2. **Logger API Inconsistency:** - **Issue:** Code called `logger.debug()`, `logger.error()`, etc., but `DailyNotificationLogger` only provides `log(level:message:)` - **Fix:** Updated all logger calls to use `logger.log(.debug, "\(TAG): message")` format - **Files Affected:** `DailyNotificationErrorHandler.swift`, `DailyNotificationPerformanceOptimizer.swift`, `DailyNotificationETagManager.swift` - **Lesson:** Verify API contracts before using helper classes; document expected usage patterns 3. **Immutable Property Assignment:** - **Issue:** `NotificationContent` properties are `let` constants, but code attempted to mutate them - **Fix:** Create new `NotificationContent` instances instead of mutating existing ones - **Files Affected:** `DailyNotificationBackgroundTaskManager.swift` - **Lesson:** Swift value types with `let` properties require creating new instances for updates 4. **Missing Import Statements:** - **Issue:** `DailyNotificationCallbacks.swift` used `CAPPluginCall` without importing `Capacitor` - **Fix:** Added `import Capacitor` to file - **Files Affected:** `DailyNotificationCallbacks.swift` - **Lesson:** Always verify imports when using types from external frameworks 5. **Access Control Issues:** - **Issue:** `storage`, `stateActor`, and `notificationCenter` were `private` but needed by extension methods - **Fix:** Changed access level to `internal` (default) or explicitly `var` without `private` - **Files Affected:** `DailyNotificationPlugin.swift` - **Lesson:** Extension methods in separate files need appropriate access levels for shared state 6. **Phase 2 Features in Phase 1 Code:** - **Issue:** Code referenced `persistenceController` (CoreData) which doesn't exist in Phase 1 - **Fix:** Stubbed out Phase 2 methods with TODO comments and early returns - **Files Affected:** `DailyNotificationBackgroundTasks.swift`, `DailyNotificationCallbacks.swift` - **Lesson:** Clearly separate Phase 1 and Phase 2 implementations; stub Phase 2 methods rather than leaving broken references 7. **iOS API Availability:** - **Issue:** `interruptionLevel` property requires iOS 15.0+, but deployment target is iOS 13.0 - **Fix:** Wrapped usage in `if #available(iOS 15.0, *)` checks - **Files Affected:** `DailyNotificationPlugin.swift` - **Lesson:** Always check API availability for iOS versions below the feature's minimum requirement 8. **Switch Statement Exhaustiveness:** - **Issue:** Swift requires exhaustive switch statements; missing `.scheduling` case in `ErrorCategory` switch - **Fix:** Added missing case to switch statement - **Files Affected:** `DailyNotificationErrorHandler.swift` - **Lesson:** Swift's exhaustive switch requirement helps catch missing enum cases at compile time 9. **Variable Initialization in Closures:** - **Issue:** Variables captured by closures must be initialized before closure execution - **Fix:** Extract values from closures into local variables before use - **Files Affected:** `DailyNotificationErrorHandler.swift` - **Lesson:** Swift's closure capture semantics require careful initialization order 10. **Capacitor Plugin Call Reject Signature:** - **Issue:** `call.reject()` signature differs from expected; doesn't accept dictionary as error parameter - **Fix:** Use `call.reject(message, code)` format instead of passing error dictionary - **Files Affected:** `DailyNotificationPlugin.swift` - **Lesson:** Verify Capacitor API signatures; don't assume parameter types match Android patterns 11. **Database Method Naming:** - **Issue:** Code called `database.execSQL()` but method is named `executeSQL()` - **Fix:** Updated all calls to use correct method name - **Files Affected:** `DailyNotificationPerformanceOptimizer.swift` - **Lesson:** Consistent naming conventions help prevent these errors; verify method names match declarations 12. **Async/Await in Synchronous Context:** - **Issue:** `URLSession.shared.data(for:)` is async but called in non-async function - **Fix:** Made function `async throws` and used `await` for async calls - **Files Affected:** `DailyNotificationETagManager.swift` - **Lesson:** Modern Swift async/await requires proper function signatures; can't mix sync and async patterns 13. **Codable Conformance:** - **Issue:** `NotificationContent` needed to conform to `Codable` for JSON encoding/decoding - **Fix:** Added `Codable` conformance to class declaration - **Files Affected:** `NotificationContent.swift` - **Lesson:** Verify protocol conformance when using encoding/decoding APIs **Build Status:** ✅ **BUILD SUCCEEDED** (2025-11-13) **Total Errors Fixed:** 13 categories, ~50+ individual compilation errors ### 2025-11-13: Build Script Improvements **Decision:** Improve iOS test app build script with auto-detection and better error handling **Rationale:** Build script should work reliably across different development environments **Status:** ✅ Complete **Improvements Made:** 1. **Simulator Auto-Detection:** - **Before:** Hardcoded "iPhone 15" simulator name (not available on all systems) - **After:** Auto-detects available iPhone simulators using device ID (UUID) - **Implementation:** Extracts device ID from `xcrun simctl list devices available` - **Fallback:** Uses device name if ID extraction fails, then generic destination - **Files Affected:** `scripts/build-ios-test-app.sh` - **Lesson:** Auto-detection improves portability across different Xcode versions and simulator configurations 2. **Workspace Path Correction:** - **Issue:** Build script looked for workspace in wrong directory - **Fix:** Updated to look in `test-apps/ios-test-app/ios/App/App.xcworkspace` - **Files Affected:** `scripts/build-ios-test-app.sh` - **Lesson:** Verify file paths match actual project structure 3. **CocoaPods Path Handling:** - **Issue:** Script needed to handle rbenv CocoaPods installation path - **Fix:** Detects CocoaPods via `which pod` or `~/.rbenv/shims/pod` - **Files Affected:** `scripts/build-ios-test-app.sh` - **Lesson:** Support multiple installation methods for better developer experience **Build Script Features:** - ✅ Auto-detects available iPhone simulators - ✅ Handles CocoaPods installation paths (system, rbenv) - ✅ Clear error messages and logging - ✅ Supports both simulator and device builds - ✅ Verifies environment before building - ✅ Finds built app in DerivedData (not local build folder) - ✅ Automatically boots simulator if not running - ✅ Automatically installs and launches app on simulator ### 2025-11-13: Build Script Build Folder and Simulator Launch Fixes **Decision:** Fix build folder detection and add automatic simulator boot/launch **Rationale:** Script was looking in wrong location and not launching simulator automatically **Status:** ✅ Complete **Issues Fixed:** 1. **Missing Build Folder:** - **Issue:** Script searched `find build -name "*.app"` but Xcode builds to `DerivedData` - **Fix:** Updated to search `~/Library/Developer/Xcode/DerivedData` for built app - **Files Affected:** `scripts/build-ios-test-app.sh` - **Lesson:** Xcode command-line builds go to DerivedData, not a local `build` folder 2. **Simulator Not Launching:** - **Issue:** Script only built app, didn't boot simulator or launch app - **Fix:** Added automatic simulator boot detection, booting, app installation, and launch - **Implementation:** - Detects if simulator is booted - Boots simulator if needed - Opens Simulator.app if not running - Waits up to 60 seconds for boot completion (with progress feedback) - Installs app automatically - Launches app with fallback methods - **Files Affected:** `scripts/build-ios-test-app.sh` - **Lesson:** Full automation requires boot detection, waiting for readiness, and multiple launch attempts **Build Script Now:** - ✅ Finds app in correct location (DerivedData) - ✅ Boots simulator automatically - ✅ Installs app automatically - ✅ Launches app automatically - ✅ Provides clear feedback and fallback instructions ### 2025-11-13: App Launch Verification Improvements **Decision:** Improve app launch detection and error reporting **Rationale:** Script was reporting success even when app launch failed **Status:** ✅ Complete **Issues Fixed:** 1. **False Success Reporting:** - **Issue:** Script reported "✅ App launched successfully!" even when launch command failed - **Fix:** Capture actual launch output and exit code; verify launch actually succeeded - **Implementation:** Check if `simctl launch` returns a PID (success) vs error message - **Files Affected:** `scripts/build-ios-test-app.sh` - **Lesson:** Always verify command success, not just check exit code; capture error output 2. **Simulator Readiness:** - **Issue:** Simulator may be "Booted" but not ready to launch apps (takes time to fully initialize) - **Fix:** Added readiness check using `simctl get_app_container` to verify simulator is responsive - **Implementation:** Wait up to 10 seconds for simulator to be ready after boot - **Files Affected:** `scripts/build-ios-test-app.sh` - **Lesson:** "Booted" state doesn't mean "ready"; need to verify simulator is actually responsive 3. **Launch Error Visibility:** - **Issue:** Launch errors were hidden by redirecting stderr to /dev/null - **Fix:** Capture full error output and display it to user - **Implementation:** Store launch output in variable, check for errors, display if launch fails - **Files Affected:** `scripts/build-ios-test-app.sh` - **Lesson:** Always capture and display errors to help diagnose issues 4. **Launch Verification:** - **Issue:** No verification that app actually launched after command succeeded - **Fix:** Added verification step using `simctl get_app_container` to confirm app is accessible - **Implementation:** After launch, verify app container can be accessed - **Files Affected:** `scripts/build-ios-test-app.sh` - **Lesson:** Verify actual state, not just command success 5. **Bundle Identifier Mismatch:** - **Issue:** Script was using `com.timesafari.dailynotification.test` but actual bundle ID is `com.timesafari.dailynotification` - **Fix:** Updated all launch commands to use correct bundle ID `com.timesafari.dailynotification` - **Root Cause:** Project file has `.test` suffix but Info.plist resolves to base bundle ID - **Files Affected:** `scripts/build-ios-test-app.sh` - **Lesson:** Always verify actual bundle ID from installed app, not just project settings; bundle ID resolution can differ from project settings **Known Limitations:** - Simulator boot can take 60+ seconds on slower systems - App launch may fail if simulator isn't fully ready (even if "Booted") - Manual launch may be required if automatic launch fails - Bundle identifier may differ between project settings and actual installed app **Workarounds:** - If automatic launch fails, script provides clear manual instructions - User can manually tap app icon in Simulator - User can run launch command manually with displayed command - Verify bundle ID from installed app: `xcrun simctl listapps booted | grep -i "appname"` ### 2025-11-13: Permission Request Implementation **Decision:** Implement `requestNotificationPermissions` and `checkPermissionStatus` methods for iOS plugin **Rationale:** Test app needs permission management functionality to match Android behavior **Status:** ✅ Complete **Implementation Details:** 1. **Method Exposure:** - **Issue:** Methods must be marked with `@objc` to be exposed to JavaScript via Capacitor bridge - **Fix:** Both `checkPermissionStatus` and `requestNotificationPermissions` marked with `@objc func` - **Files Affected:** `ios/Plugin/DailyNotificationPlugin.swift` - **Lesson:** Capacitor automatically exposes `@objc` methods to JavaScript; method names must match exactly 2. **Async Permission Handling:** - **Issue:** `UNUserNotificationCenter.requestAuthorization()` is async and must be awaited - **Fix:** Used Swift `Task` with `await` for async permission checks and requests - **Files Affected:** `ios/Plugin/DailyNotificationPlugin.swift`, `ios/Plugin/DailyNotificationScheduler.swift` - **Lesson:** iOS permission APIs are async; must use Swift concurrency (`Task`, `await`) properly 3. **Permission State Management:** - **Issue:** iOS only shows permission dialog once; if denied, user must go to Settings - **Fix:** Check current status first; if `.authorized`, return immediately; if `.denied`, return error with Settings guidance - **Files Affected:** `ios/Plugin/DailyNotificationPlugin.swift` - **Lesson:** iOS permission model is stricter than Android; must handle `.notDetermined`, `.authorized`, and `.denied` states explicitly 4. **Logging Visibility:** - **Issue:** `print()` statements may not appear in simulator logs; `NSLog()` is more reliable - **Fix:** Changed from `print()` to `NSLog()` for better console visibility - **Files Affected:** `ios/Plugin/DailyNotificationPlugin.swift` - **Lesson:** Use `NSLog()` for debugging iOS plugins; appears in both Xcode console and `simctl log` output 5. **Main Thread Dispatch:** - **Issue:** `call.resolve()` and `call.reject()` must be called on main thread - **Fix:** Wrapped all `call.resolve()` and `call.reject()` calls in `DispatchQueue.main.async` - **Files Affected:** `ios/Plugin/DailyNotificationPlugin.swift` - **Lesson:** Capacitor plugin callbacks must execute on main thread; use `DispatchQueue.main.async` when calling from background tasks 6. **Permission Reset for Testing:** - **Issue:** Simulator permissions persist across app launches; need to reset for testing - **Fix:** Use `xcrun simctl privacy booted reset all ` to reset permissions - **Command:** `xcrun simctl privacy booted reset all com.timesafari.dailynotification` - **Lesson:** Simulator permissions don't reset automatically; must manually reset for testing different permission states 7. **JavaScript Method Existence Check:** - **Issue:** JavaScript may call methods that don't exist yet, causing silent failures - **Fix:** Added method existence checks in HTML before calling plugin methods - **Files Affected:** `test-apps/ios-test-app/App/App/Public/index.html` - **Lesson:** Always check for method existence in JavaScript before calling; provides better error messages **Debugging Tips:** - Check Xcode console (not browser console) for `NSLog()` output - Use `xcrun simctl spawn booted log stream --predicate 'process == "App"'` for real-time logs - Verify methods are exposed: `console.log(Object.keys(window.DailyNotification))` - Reset permissions between tests: `xcrun simctl privacy booted reset all ` - Rebuild app after adding new `@objc` methods (Capacitor needs to regenerate bridge) **Status:** ✅ **METHODS IMPLEMENTED** (2025-11-13) - `checkPermissionStatus()` - Returns current notification permission status - `requestNotificationPermissions()` - Requests notification permissions (shows system dialog if `.notDetermined`) - `isChannelEnabled(channelId?)` - Checks if notifications are enabled (iOS: app-wide check, not per-channel) - `openChannelSettings(channelId?)` - Opens notification settings (iOS: opens app Settings, not per-channel) - `scheduleDailyNotification(options)` - Schedules daily notification (✅ working, BGTaskScheduler prefetch fails on simulator - expected) ### 2025-11-13: scheduleDailyNotification Implementation **Decision:** Implement `scheduleDailyNotification` with proper parameter handling **Rationale:** Test app needs to schedule notifications for testing **Status:** ✅ Complete **Implementation Details:** 1. **Parameter Reading Fix:** - **Issue:** iOS code was looking for `call.getObject("options")` but Capacitor passes parameters directly - **Fix:** Changed to read parameters directly from `call` (matching Android pattern) - **Files Affected:** `ios/Plugin/DailyNotificationPlugin.swift` - **Lesson:** Capacitor passes the options object directly as call data, not wrapped in "options" key 2. **BGTaskScheduler Simulator Limitation:** - **Issue:** Background fetch scheduling fails on simulator with `BGTaskSchedulerErrorDomain Code=1` (notPermitted) - **Behavior:** This is **expected** on simulator - BGTaskScheduler doesn't work reliably in simulators - **Solution:** Improved error handling to log clear message that this is expected on simulator - **Impact:** Notification scheduling still works; prefetch won't run on simulator but will work on real devices - **Files Affected:** `ios/Plugin/DailyNotificationPlugin.swift` - **Lesson:** BGTaskScheduler requires real device or Background App Refresh enabled; simulator limitations are normal 3. **Notification Scheduling:** - ✅ Notification scheduling works correctly - ✅ Storage saves notification content - ✅ Background fetch scheduling attempted (fails on simulator, works on device) - ✅ Error handling doesn't fail notification scheduling if background fetch fails **Testing Notes:** - Notification scheduling verified working on simulator - Background fetch error is expected and doesn't prevent notification delivery - Real device testing required to verify background fetch works --- ## Validation Matrix **Cross-platform feature validation checklist** | Feature | Android Behavior | iOS Equivalent | Verified By | Phase | |---------|-----------------|----------------|-------------|-------| | Daily notification | Exact ±0s | ±180s tolerance¹ | Test case: verify **observed** delivery within 3 min window | 1 | | Daily prefetch | Worker at exact time | BGTask within 15 min window | Logs + UI: check fetch timestamp | 1 | | Prefetch execution | Deterministic logic | Heuristic timing (device-only) | Simulator: logic correctness; Device: timing verification (see `IOS_PREFETCH_TESTING.md`) | 1 | | Rolling window | 2 days ahead | iOS 64 limit enforced | Stats call: verify pending count ≤ 64 | 2 | | TTL enforcement | Discard stale at fire | Same: discard stale at fire | Deliver-time check: verify TTL validation | 2 | | Reboot recovery | BOOT_COMPLETED | Uptime comparison | App launch: verify reschedule after reboot | 2 | | JWT authentication | ES256K signing | Same algorithm | Test method: verify token generation | 3 | | ETag caching | Conditional requests | Same: If-None-Match headers | Network logs: verify 304 responses | 3 | | Background coordination | WorkManager sync | BGTaskScheduler sync | Lifecycle events: verify coordination | 4 | ¹ **Note:** If drift exceeds 180s, log `[DNP-SCHEDULER] notification drift > 180s` and capture in diagnostics; retry logic is not required for v1. --- ## Glossary **ETag:** HTTP entity tag used for conditional requests. Allows server to return 304 Not Modified if content unchanged. **Rolling Window:** Strategy of scheduling notifications for today and tomorrow to ensure delivery even if system delays occur. **Prefetch:** Background task that fetches notification content before the notification is scheduled to fire. **ActiveDID:** Decentralized identifier (DID) representing the currently active user identity in TimeSafari. **Exact Alarm:** Android feature allowing precise scheduling at exact time. iOS equivalent uses calendar triggers with tolerance. **TTL (Time To Live):** Maximum age of cached content. Content older than TTL is considered stale and discarded. **BGTaskScheduler:** iOS framework for scheduling background tasks (fetch, processing) that run when system conditions allow. **UNUserNotificationCenter:** iOS framework for scheduling and delivering local notifications to users. --- ## Developer Onboarding ### Required Environment - **Xcode Version:** 15.0 or later (for iOS 17+ features) - **macOS Version:** 13.0 (Ventura) or later - **Swift Version:** 5.9 or later - **iOS Deployment Target:** iOS 15.0 or later ### Running the Plugin from Test App 1. **Build Test App:** ```bash # From repo root ./scripts/build-ios-test-app.sh --simulator ``` Note: The build script handles `cd` into `test-apps/ios-test-app` internally. 2. **Open in Xcode:** ```bash cd test-apps/ios-test-app open App.xcworkspace # or App.xcodeproj ``` 3. **Run on Simulator:** - Select target device (iPhone 15, etc.) - Press Cmd+R to build and run ### Common Failure Modes 1. **BGTaskScheduler Not Running:** - Check Info.plist has `BGTaskSchedulerPermittedIdentifiers` - Verify task registered in AppDelegate before app finishes launching - **Simulator-only debugging trick:** Use LLDB command to manually trigger: `e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]` - Note: This is for simulator testing only, not available in production - **Testing Guide:** See `doc/test-app-ios/IOS_PREFETCH_TESTING.md` for comprehensive testing procedures 2. **Notifications Not Delivering:** - Check notification permissions: `UNUserNotificationCenter.current().getNotificationSettings()` - Verify notification scheduled: `UNUserNotificationCenter.current().getPendingNotificationRequests()` - Check notification category registered 3. **Prefetch Not Executing:** - Verify BGTask scheduled: Check logs for `[DNP-FETCH] BGAppRefreshTask scheduled` - Check Background App Refresh enabled in Settings - In Simulator: Use Xcode → Debug → Simulate Background Fetch - On Device: Check logs via Xcode Devices console - **Testing Guide:** See `doc/test-app-ios/IOS_PREFETCH_TESTING.md` for detailed troubleshooting 3. **Build Failures:** - Ensure CocoaPods installed: `pod install` - Check Capacitor plugin path in `capacitor.plugins.json` - Verify Xcode scheme matches workspace 4. **Background Tasks Expiring:** - BGTaskScheduler tasks have ~30 second execution window - Must call `task.setTaskCompleted(success:)` before expiration - Schedule next task immediately after completion --- ## iOS Test App Requirements **Note:** Detailed test app requirements have been moved to a separate document for clarity. **Reference:** See `doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.md` for: - UI parity requirements - iOS permissions configuration - Build options (Xcode GUI and command-line) - Debugging strategy - Test app implementation checklist **Important:** If `doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.md` does not yet exist, it **MUST be created as part of Phase 1** before implementation starts. **Quick Reference:** - Test app location: `test-apps/ios-test-app/` - Build script: `scripts/build-ios-test-app.sh` (root-level) - Same UI as `android-test-app` (HTML/JS) --- **Status:** ✅ **PHASE 1 COMPLETE** - Build Compilation Fixed **Next Steps:** Test app ready for iOS Simulator testing **Phase 1 Completion Summary:** See `doc/PHASE1_COMPLETION_SUMMARY.md` for detailed implementation status. **Build Status:** ✅ **BUILD SUCCEEDED** (2025-11-13) - All Swift compilation errors resolved - Test app builds successfully for iOS Simulator - Ready for functional testing **Lessons Learned:** See Decision Log section above for compilation error fixes and patterns.