chore: synch diretive before starting

This commit is contained in:
Matthew Raymer
2025-11-13 09:37:56 +00:00
parent d583b9103c
commit 2d84ae29ba

View File

@@ -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<void>` | `@objc func configure(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ✅ Partial |
| `scheduleDailyNotification()` | `scheduleDailyNotification(options: NotificationOptions): Promise<void>` | `@objc func scheduleDailyNotification(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ❌ Missing |
| `getLastNotification()` | `getLastNotification(): Promise<NotificationResponse \| null>` | `@objc func getLastNotification(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ❌ Missing |
| `cancelAllNotifications()` | `cancelAllNotifications(): Promise<void>` | `@objc func cancelAllNotifications(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ❌ Missing |
| `getNotificationStatus()` | `getNotificationStatus(): Promise<NotificationStatus>` | `@objc func getNotificationStatus(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ❌ Missing |
| `updateSettings()` | `updateSettings(settings: NotificationSettings): Promise<void>` | `@objc func updateSettings(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 1 | ❌ Missing |
| `getBatteryStatus()` | `getBatteryStatus(): Promise<BatteryStatus>` | `@objc func getBatteryStatus(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing |
| `requestBatteryOptimizationExemption()` | `requestBatteryOptimizationExemption(): Promise<void>` | `@objc func requestBatteryOptimizationExemption(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing |
| `setAdaptiveScheduling()` | `setAdaptiveScheduling(options: { enabled: boolean }): Promise<void>` | `@objc func setAdaptiveScheduling(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing |
| `getPowerState()` | `getPowerState(): Promise<PowerState>` | `@objc func getPowerState(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing |
| `maintainRollingWindow()` | `maintainRollingWindow(): Promise<void>` | `@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<void>` | `@objc func requestExactAlarmPermission(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 2 | ❌ Missing |
| `openExactAlarmSettings()` | `openExactAlarmSettings(): Promise<void>` | `@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<void>` | `@objc func setActiveDidFromHost(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 3 | ❌ Missing |
| `refreshAuthenticationForNewIdentity()` | `refreshAuthenticationForNewIdentity(options: {activeDid: string}): Promise<void>` | `@objc func refreshAuthenticationForNewIdentity(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 3 | ❌ Missing |
| `clearCacheForNewIdentity()` | `clearCacheForNewIdentity(): Promise<void>` | `@objc func clearCacheForNewIdentity(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 3 | ❌ Missing |
| `updateBackgroundTaskIdentity()` | `updateBackgroundTaskIdentity(options: {activeDid: string}): Promise<void>` | `@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<void>` | `@objc func coordinateBackgroundTasks(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | 4 | ❌ Missing |
| `handleAppLifecycleEvent()` | `handleAppLifecycleEvent(options: {lifecycleEvent: string}): Promise<void>` | `@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<void>` | `@objc func scheduleDailyReminder(_ call: CAPPluginCall)` | `DailyNotificationPlugin.swift` | - | ✅ Complete |
| `cancelDailyReminder()` | `cancelDailyReminder(options: {reminderId: string}): Promise<void>` | `@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<void>` | `@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
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
<string>fetch</string>
<string>processing</string>
</array>
```
2. **Background Task Identifiers**
```xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.fetch</string>
<string>com.timesafari.dailynotification.notify</string>
</array>
```
3. **Usage Descriptions**
```xml
<key>NSUserNotificationsUsageDescription</key>
<string>TimeSafari needs notification permissions to deliver daily updates</string>
```
**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
<!-- DebugView.vue -->
<template>
<div class="debug-view">
<h2>🔍 Debug Tools</h2>
<!-- Log Viewer -->
<LogViewer :logs="logs" />
<!-- Status Dashboard -->
<StatusDashboard :status="pluginStatus" />
<!-- Diagnostic Tools -->
<DiagnosticTools @test-method="testMethod" />
<!-- Error Display -->
<ErrorDisplay :errors="errors" />
</div>
</template>
```
#### 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