From 2d84ae29ba33273c45cfde2d82fbb390a7f0423a Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Thu, 13 Nov 2025 09:37:56 +0000 Subject: [PATCH] chore: synch diretive before starting --- .../0003-iOS-Android-Parity-Directive.md | 901 +++++++----------- 1 file changed, 366 insertions(+), 535 deletions(-) diff --git a/doc/directives/0003-iOS-Android-Parity-Directive.md b/doc/directives/0003-iOS-Android-Parity-Directive.md index 2f5a48d..6bc366a 100644 --- a/doc/directives/0003-iOS-Android-Parity-Directive.md +++ b/doc/directives/0003-iOS-Android-Parity-Directive.md @@ -36,6 +36,50 @@ This directive outlines the plan to upgrade the iOS implementation of the Daily --- +## 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) @@ -162,140 +206,156 @@ This directive outlines the plan to upgrade the iOS implementation of the Daily ### Phase 1: Core Infrastructure Parity -**Objective:** Implement core notification scheduling and management methods with simplified daily schedule +**Objective:** Implement core notification scheduling with single daily schedule (one prefetch + one notification). -**Tasks:** -1. **Storage Layer Enhancement** - - Enhance `DailyNotificationStorage` (Swift equivalent) to match Android `DailyNotificationStorage.java` - - Support both UserDefaults and SQLite (CoreData) storage modes - - Implement content caching and retrieval +**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()` -2. **Scheduler Implementation** - - Create `DailyNotificationScheduler.swift` equivalent to `DailyNotificationScheduler.java` - - Use `UNUserNotificationCenter` for notification scheduling - - **Initial:** Support single daily notification (one prefetch + one notification per day) - - Rolling window logic deferred to Phase 2 +**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) -3. **Background Fetching** - - Enhance `DailyNotificationBackgroundTaskManager.swift` to match Android `DailyNotificationFetcher.java` - - Implement `BGTaskScheduler` registration and execution - - **Initial:** Support single daily prefetch task (scheduled 5 minutes before notification) - - Add content fetching with ETag support +**Permission Auto-Healing:** -4. **Core Methods Implementation** - - `scheduleDailyNotification()` - Main scheduling method (single daily schedule) - - `getLastNotification()` - Last notification retrieval - - `cancelAllNotifications()` - Cancel all - - `getNotificationStatus()` - Status retrieval - - `updateSettings()` - Settings update +On every call to `scheduleDailyNotification()`: -**Platform Adaptations:** -- Android `AlarmManager` → iOS `UNUserNotificationCenter` with `UNCalendarNotificationTrigger` -- Android `WorkManager` → iOS `BGTaskScheduler` with `BGAppRefreshTaskRequest` -- Android `SharedPreferences` → iOS `UserDefaults` (with SQLite option via CoreData) +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 -**Test App Readiness:** Phase 1 test app should be ready for testing on iOS Simulator after core methods are implemented +**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 advanced features matching Android capabilities +**Objective:** Implement rolling window, TTL enforcement, exact alarm equivalent, reboot recovery, and power management. -**Tasks:** -1. **Rolling Window Management** - - Enhance `DailyNotificationRollingWindow.swift` to match Android implementation - - Implement iOS-specific limits (64 pending notifications) - - Add maintenance scheduling - - **Note:** Expands beyond single daily schedule to support rolling window +**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) -2. **TTL Enforcement** - - Enhance `DailyNotificationTTLEnforcer.swift` to match Android implementation - - Implement TTL checking at notification fire time - - Add stale content handling +**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) -3. **Exact Alarm Equivalent** - - Implement iOS equivalent of Android exact alarm management - - Use `UNTimeIntervalNotificationTrigger` for precise timing - - Handle iOS notification delivery guarantees - -4. **Reboot Recovery** - - Create `DailyNotificationRebootRecoveryManager.swift` equivalent - - Use iOS app launch detection to reschedule notifications - - Implement recovery status tracking - -5. **Power Management** - - Enhance `DailyNotificationPowerManager.swift` to match Android capabilities - - Implement battery status checking - - Add background refresh status checking - -**Platform Adaptations:** -- Android exact alarms → iOS notification delivery with time-based triggers -- Android battery optimization → iOS Background App Refresh settings -- Android reboot recovery → iOS app launch detection and rescheduling - -**Test App Readiness:** Phase 2 test app should be ready for testing rolling window and advanced features on iOS Simulator +**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 TimeSafari-specific integration features +**Objective:** Implement JWT authentication, ETag caching, and TimeSafari API integration. -**Tasks:** -1. **JWT Management** - - Create `DailyNotificationJWTManager.swift` equivalent - - Implement JWT token generation and caching - - Add token refresh logic +**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()` -2. **ETag Management** - - Enhance `DailyNotificationETagManager.swift` to match Android - - Implement ETag caching and conditional requests - - Add ETag invalidation logic +**Key Constraints:** +- Replace Phase 1 dummy fetcher with JWT-signed fetcher +- Use same JWT algorithm as Android (ES256K) +- ETag validation must match Android behavior -3. **Enhanced Fetcher** - - Create `EnhancedDailyNotificationFetcher.swift` equivalent - - Implement TimeSafari API integration - - Add activeDid support - -4. **Phase 1 Methods** - - `setActiveDidFromHost()` - ActiveDid management - - `refreshAuthenticationForNewIdentity()` - Auth refresh - - `clearCacheForNewIdentity()` - Cache clearing - - `updateBackgroundTaskIdentity()` - Background task identity - - `testJWTGeneration()` - JWT testing - - `testEndorserAPI()` - API testing - -**Platform Adaptations:** -- Android WorkManager background tasks → iOS BGTaskScheduler background tasks -- Android SharedPreferences for config → iOS UserDefaults for config -- Android JWT signing → iOS JWT signing (same algorithm, different implementation) - -**TimeSafari Integration Readiness:** Library ready for TimeSafari integration after Phase 3 completion -**Test App Readiness:** Phase 3 test app should be ready for testing TimeSafari integration features on iOS Simulator +**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 features +**Objective:** Implement background task coordination with TimeSafari PlatformServiceMixin. -**Tasks:** -1. **Background Coordination** - - Implement `coordinateBackgroundTasks()` method - - Add TimeSafari PlatformServiceMixin integration - - Coordinate BGTaskScheduler with app lifecycle +**Deliverables:** +1. Background coordination (`coordinateBackgroundTasks()` method) +2. Lifecycle management (`handleAppLifecycleEvent()` for app background/foreground) +3. Coordination status (`getCoordinationStatus()` for debugging) -2. **Lifecycle Management** - - Implement `handleAppLifecycleEvent()` method - - Handle app background/foreground events - - Sync state with app lifecycle +**Key Constraints:** +- Coordinate BGTaskScheduler with app lifecycle events +- Sync state between plugin and TimeSafari PlatformServiceMixin +- Track coordination state for debugging -3. **Coordination Status** - - Implement `getCoordinationStatus()` method - - Track coordination state - - Provide debugging information +**Completion Criteria:** +- Background tasks coordinated with app lifecycle +- Full library ready for production TimeSafari integration +- Test app ready for full coordination testing -**Platform Adaptations:** -- Android app lifecycle callbacks → iOS app lifecycle notifications -- Android WorkManager coordination → iOS BGTaskScheduler coordination +--- -**TimeSafari Integration Readiness:** Full library ready for production TimeSafari integration after Phase 4 completion -**Test App Readiness:** Phase 4 test app should be ready for testing full background coordination on iOS Simulator +## 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 | ✅ Partial | +| `scheduleDailyNotification()` | `scheduleDailyNotification(options: NotificationOptions): Promise` | `@objc func scheduleDailyNotification(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ❌ Missing | +| `getLastNotification()` | `getLastNotification(): Promise` | `@objc func getLastNotification(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ❌ Missing | +| `cancelAllNotifications()` | `cancelAllNotifications(): Promise` | `@objc func cancelAllNotifications(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ❌ Missing | +| `getNotificationStatus()` | `getNotificationStatus(): Promise` | `@objc func getNotificationStatus(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ❌ Missing | +| `updateSettings()` | `updateSettings(settings: NotificationSettings): Promise` | `@objc func updateSettings(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ❌ Missing | +| `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 | --- @@ -345,6 +405,9 @@ This directive outlines the plan to upgrade the iOS implementation of the Daily - [ ] `cancelAllNotifications()` - Cancel all notifications - [ ] `getNotificationStatus()` - Status retrieval - [ ] `updateSettings(settings: NotificationSettings)` - Settings update + +### Power Management Methods (Phase 2) + - [ ] `getBatteryStatus()` - Battery status - [ ] `requestBatteryOptimizationExemption()` - Battery optimization (iOS: Background App Refresh) - [ ] `setAdaptiveScheduling(options: { enabled: boolean })` - Adaptive scheduling @@ -404,6 +467,22 @@ This directive outlines the plan to upgrade the iOS implementation of the Daily - **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) @@ -430,9 +509,68 @@ This directive outlines the plan to upgrade the iOS implementation of the Daily - **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. + +**TODO:** Extract full error code table from Android implementation (`src/android/DailyNotificationErrorHandler.java` or equivalent) and paste here as a normative reference. + +**Note:** This TODO is **blocking for Phase 1**: iOS error handling must not be considered complete until the table is extracted and mirrored. Phase 1 implementation should not proceed without verifying error code parity. + +**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.) + --- -## Testing Strategy +## Validation & QA Plan ### Unit Tests @@ -453,7 +591,8 @@ This directive outlines the plan to upgrade the iOS implementation of the Daily ### Manual Testing -- [ ] Test on physical iOS devices +- [ ] Test on iOS Simulator (primary) +- [ ] Test on physical iOS devices (validation) - [ ] Test Background App Refresh settings - [ ] Test notification permissions - [ ] Test battery optimization scenarios @@ -461,431 +600,6 @@ This directive outlines the plan to upgrade the iOS implementation of the Daily --- -## iOS Test App Requirements - -### UI Parity with Android - -**Objective:** Create iOS test app with identical UI/UX to Android test app, using iOS plugin implementation - -**Reference App:** `test-apps/android-test-app/` (Standalone Android with HTML/JS UI) - -**Requirements:** -1. **Same UI as Android Test App** - - Reuse exact HTML/JavaScript from `android-test-app/app/src/main/assets/public/index.html` - - Same button layout and functionality - - Same status display area - - Same visual design and styling - - Platform detection to use iOS plugin methods instead of Android - -2. **Platform Detection** - - Detect iOS platform and use iOS plugin methods - - Show iOS-specific status indicators - - Display iOS-specific permission states - - Adapt Android-specific methods to iOS equivalents (e.g., `openExactAlarmSettings` → `openNotificationSettings`) - -3. **iOS Plugin Integration** - - Use iOS `DailyNotificationPlugin` instead of Android version - - All method calls route to iOS native implementation - - Error handling adapted for iOS-specific errors - -**File Structure:** -``` -test-apps/ios-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 (HTML/JS/CSS) - ├── index.html # Same UI as android-test-app - ├── capacitor.config.json # Capacitor config - └── capacitor.plugins.json # Plugin registration -``` - -### iOS Permissions and Requirements - -**Required Permissions (Info.plist):** - -1. **Notification Permissions** - ```xml - UIBackgroundModes - - remote-notification - fetch - processing - - ``` - -2. **Background Task Identifiers** - ```xml - BGTaskSchedulerPermittedIdentifiers - - com.timesafari.dailynotification.fetch - com.timesafari.dailynotification.notify - - ``` - -3. **Usage Descriptions** - ```xml - NSUserNotificationsUsageDescription - TimeSafari needs notification permissions to deliver daily updates - ``` - -**Runtime Permission Handling:** - -1. **Notification Permissions** - - Request via `UNUserNotificationCenter.requestAuthorization()` - - Check status via `UNUserNotificationCenter.getNotificationSettings()` - - Display permission status in test app UI - - Provide "Open Settings" button if denied - -2. **Background App Refresh** - - Check status via `UIApplication.shared.backgroundRefreshStatus` - - Display status in test app UI - - Provide guidance for enabling in Settings - -3. **Permission Status Display** - - Show notification permission state (authorized/denied/not determined) - - Show background refresh state (available/restricted/denied) - - Show battery optimization status (iOS equivalent) - - Provide actionable buttons to request/enable permissions - -**iOS-Specific Requirements:** - -1. **Background Task Registration** - - Register BGTaskScheduler tasks in `AppDelegate.application(_:didFinishLaunchingWithOptions:)` - - Register handlers before app finishes launching - - Handle task expiration gracefully - -2. **Notification Categories** - - Define notification categories for actions - - Register categories with UNUserNotificationCenter - - Handle notification actions in delegate - -3. **App Lifecycle Integration** - - Handle app background/foreground events - - Reschedule notifications on app launch - - Sync state on app resume - -### Build Options - -**Objective:** Support both Xcode GUI and command-line builds with straightforward setup - -#### Xcode Build (GUI) - -**Setup:** -1. Open `test-apps/ios-test-app/App.xcworkspace` (if using CocoaPods) or `App.xcodeproj` in Xcode -2. Select target device (simulator or physical device) -3. Build and run via Xcode interface - -**Xcode Configuration:** -- **Scheme:** ios-test-app (or similar) -- **Configuration:** Debug (development) / Release (production) -- **Signing:** Automatic signing with development team -- **Capabilities:** - - Push Notifications - - Background Modes (Remote notifications, Background fetch, Background processing) - -**Xcode Build Steps:** -1. Clean build folder (Cmd+Shift+K) -2. Build project (Cmd+B) -3. Run on device/simulator (Cmd+R) -4. Attach debugger for debugging - -#### Console Build (Command Line) - -**Objective:** Enable straightforward command-line builds matching Android test app pattern - -**Build Script Pattern:** -- Follow pattern from `scripts/build-native.sh` -- Create `scripts/build-ios-test-app.sh` for iOS test app -- Support both simulator and device builds -- Include dependency installation (CocoaPods) -- Include code signing configuration - -**Build Commands (via Script):** - -1. **Build iOS Test App (Debug - Simulator)** - ```bash - ./scripts/build-ios-test-app.sh --simulator - ``` - -2. **Build iOS Test App (Debug - Device)** - ```bash - ./scripts/build-ios-test-app.sh --device - ``` - -3. **Build iOS Test App (Release)** - ```bash - ./scripts/build-ios-test-app.sh --release - ``` - -**Direct xcodebuild Commands (if needed):** - -1. **Install Dependencies** - ```bash - cd test-apps/ios-test-app - pod install # If using CocoaPods - ``` - -2. **Build iOS App (Debug - Simulator)** - ```bash - xcodebuild -workspace App.xcworkspace \ - -scheme ios-test-app \ - -configuration Debug \ - -sdk iphonesimulator \ - -destination 'platform=iOS Simulator,name=iPhone 15' \ - build - ``` - -3. **Build iOS App (Debug - Device)** - ```bash - xcodebuild -workspace App.xcworkspace \ - -scheme ios-test-app \ - -configuration Debug \ - -sdk iphoneos \ - -destination 'generic/platform=iOS' \ - build - ``` - -4. **Build iOS App (Release)** - ```bash - xcodebuild -workspace App.xcworkspace \ - -scheme ios-test-app \ - -configuration Release \ - -sdk iphoneos \ - -archivePath build/ios-test-app.xcarchive \ - archive - ``` - -**Build Script Requirements:** -- Check for required tools (xcodebuild, pod if using CocoaPods) -- Install dependencies automatically -- Support both simulator and device builds -- Provide clear error messages -- Follow same pattern as `scripts/build-native.sh` (colors, logging, error handling) - -### Debugging Strategy - -**Objective:** Make debugging easily accessible and comprehensive - -#### Console Logging - -**Structured Logging:** -1. **Log Levels** - - `DEBUG`: Detailed diagnostic information - - `INFO`: General informational messages - - `WARN`: Warning messages for potential issues - - `ERROR`: Error messages for failures - -2. **Log Format** - ``` - [DNP-PLUGIN] [LEVEL] [TIMESTAMP] [METHOD] Message - ``` - Example: - ``` - [DNP-PLUGIN] [INFO] [2025-11-13 10:30:45 UTC] [scheduleDailyNotification] Scheduling notification for 09:00 - ``` - -3. **Log Categories** - - `DNP-PLUGIN`: Main plugin operations - - `DNP-SCHEDULER`: Scheduling operations - - `DNP-FETCHER`: Background fetching - - `DNP-STORAGE`: Storage operations - - `DNP-TTL`: TTL enforcement - - `DNP-ROLLING-WINDOW`: Rolling window maintenance - -**Logging Implementation:** -- Use `os_log` for system-level logging (visible in Console.app) -- Use `print()` for Xcode console output -- Include file/line information in debug builds -- Support log level filtering - -#### Xcode Debugging - -**Breakpoints:** -1. **Strategic Breakpoints** - - Plugin method entry points - - Background task handlers - - Notification scheduling logic - - Error handling paths - -2. **Conditional Breakpoints** - - Break on specific notification IDs - - Break on error conditions - - Break on specific activeDid values - -3. **Logging Breakpoints** - - Log method entry/exit without stopping - - Log variable values - - Log call stack - -**Debugging Tools:** -1. **LLDB Commands** - - Inspect Swift objects - - Print variable values - - Execute Swift expressions - - View call stack - -2. **View Hierarchy Debugger** - - Inspect UI components - - Debug layout issues - - Verify UI state - -3. **Network Debugging** - - Monitor API calls - - Inspect request/response - - Debug authentication issues - -#### Test App Debugging UI - -**Debug Panel in Test App:** -1. **Log Viewer** - - Real-time log display - - Filter by log level - - Filter by category - - Search functionality - - Export logs to file - -2. **Status Dashboard** - - Plugin initialization status - - Permission status - - Background task status - - Notification schedule status - - Storage status - -3. **Diagnostic Tools** - - Test individual plugin methods - - View stored data (UserDefaults, CoreData) - - Trigger manual background tasks - - Force notification delivery - - Clear caches - -4. **Error Display** - - Show recent errors - - Error details and stack traces - - Error reporting functionality - -**Debug View Implementation:** -```vue - - -``` - -#### Console.app Integration - -**System Logs:** -- Use `os_log` for system-level logging -- View logs in Console.app on macOS -- Filter by subsystem: `com.timesafari.dailynotification` -- Export logs for analysis - -**Log Export:** -- Export logs from test app UI -- Export via Xcode Organizer -- Export via Console.app -- Include timestamps and log levels - -#### Background Task Debugging - -**BGTaskScheduler Debugging:** -1. **Task Registration Verification** - - Log task registration in AppDelegate - - Verify task identifiers match Info.plist - - Check task handler registration - -2. **Task Execution Monitoring** - - Log task start/completion - - Log task expiration - - Monitor task execution time - - Track task success/failure - -3. **Task Scheduling Debugging** - - Log scheduled task times - - Verify earliestBeginDate - - Check task submission success - - Monitor task cancellation - -**Debug Commands:** -```swift -// In test app or debug build -func debugBackgroundTasks() { - // List registered tasks - // Show scheduled tasks - // Trigger test tasks - // View task history -} -``` - -#### Notification Debugging - -**Notification Delivery Debugging:** -1. **Scheduled Notifications** - - List all pending notifications - - Show notification content - - Display trigger times - - Verify notification limits - -2. **Notification Delivery** - - Log notification delivery - - Track delivery success/failure - - Monitor notification actions - - Debug notification display - -3. **Permission Debugging** - - Check permission status - - Log permission requests - - Track permission changes - - Debug permission denials - -**Debug Methods:** -```swift -// Debug notification state -func debugNotifications() { - // Get pending notifications - // Get delivered notifications - // Check notification settings - // Verify notification categories -} -``` - -### Test App Implementation Checklist - -- [ ] Create `ios-test-app` directory structure (equivalent to `android-test-app`) -- [ ] Copy HTML/JS UI from `android-test-app/app/src/main/assets/public/index.html` -- [ ] Adapt JavaScript to use iOS plugin methods -- [ ] Configure Info.plist with required permissions -- [ ] Implement AppDelegate with plugin setup -- [ ] Set up Xcode project/workspace -- [ ] Configure CocoaPods (if needed) or direct dependencies -- [ ] Create build script `scripts/build-ios-test-app.sh` -- [ ] Test Xcode GUI builds -- [ ] Test command-line builds via script -- [ ] Implement comprehensive logging -- [ ] Add Console.app integration -- [ ] Create debugging documentation - ---- - ## File Organization ### New Files to Create @@ -933,9 +647,11 @@ test-apps/ios-test-app/ # New iOS test app (equivalent to an ``` 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 @@ -1124,6 +840,7 @@ scripts/ - `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) - `README.md` - Project documentation --- @@ -1188,6 +905,120 @@ scripts/ --- +--- + +## 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 | +| 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 + +2. **Notifications Not Delivering:** + - Check notification permissions: `UNUserNotificationCenter.current().getNotificationSettings()` + - Verify notification scheduled: `UNUserNotificationCenter.current().getPendingNotificationRequests()` + - Check notification category registered + +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:** 🎯 **READY FOR IMPLEMENTATION** **Next Steps:** Begin Phase 1 implementation after directive approval