docs: Consolidate documentation structure (139 files, zero information loss)
Consolidate all markdown documentation into organized structure per CONSOLIDATION_DIRECTIVE. All files preserved (canonical, merged, or archived). - docs/integration/ - Integration documentation (7 files) - docs/platform/ios/ - iOS platform docs (12 files) - docs/platform/android/ - Android platform docs (9 files) - docs/testing/ - Testing documentation (15 files) - docs/design/ - Design & research (5 files) - docs/ai/ - AI/ChatGPT artifacts (7 files) - docs/archive/2025-legacy-doc/ - Historical docs (17 files) - Integration: Root INTEGRATION_GUIDE.md → docs/integration/ - Platform: Separated iOS and Android into platform/ subdirectories - Testing: Consolidated all testing docs to docs/testing/ - Legacy: Archived entire doc/ directory to archive/ - AI: Moved all ChatGPT artifacts to docs/ai/ - Added docs/00-INDEX.md - Central navigation hub - Added docs/CONSOLIDATION_SOURCE_MAP.md - Complete audit trail - Added docs/CONSOLIDATION_COMPLETE.md - Consolidation summary - Updated README.md with links to documentation index - All 139 files have destinations (see CONSOLIDATION_SOURCE_MAP.md) - Zero information loss (all files preserved) - Archive preserves original structure - Index provides clear navigation - 87 files moved/created/updated - Root-level docs consolidated - Legacy doc/ directory archived - Test app docs remain with test apps (indexed) Ref: CONSOLIDATION_DIRECTIVE Author: Matthew Raymer
This commit is contained in:
649
docs/platform/ios/ROLLOVER_EDGE_CASES.md
Normal file
649
docs/platform/ios/ROLLOVER_EDGE_CASES.md
Normal file
@@ -0,0 +1,649 @@
|
||||
# iOS Rollover Implementation — Edge Case Handling Plan
|
||||
|
||||
**Status**: Planning Phase
|
||||
**Priority**: Reliability-First
|
||||
**Author**: AI Assistant
|
||||
**Date**: 2025-01-27
|
||||
|
||||
## Objective
|
||||
|
||||
Implement Android-like automatic rollover for iOS notifications with comprehensive edge case handling to ensure reliability across all scenarios, including time changes, timezone changes, DST transitions, and race conditions.
|
||||
|
||||
---
|
||||
|
||||
## Edge Case Categories
|
||||
|
||||
### 1. **Time Changes**
|
||||
- Manual clock adjustments (user changes device time)
|
||||
- System clock corrections (NTP sync)
|
||||
- Clock drift corrections
|
||||
- Time jumps (forward/backward)
|
||||
|
||||
### 2. **Timezone Changes**
|
||||
- User changes device timezone
|
||||
- Automatic timezone detection changes
|
||||
- Travel across timezones
|
||||
- Timezone database updates
|
||||
|
||||
### 3. **DST Transitions**
|
||||
- Spring forward (lose 1 hour)
|
||||
- Fall back (gain 1 hour)
|
||||
- DST rule changes
|
||||
- Regions that don't observe DST
|
||||
|
||||
### 4. **Race Conditions**
|
||||
- Multiple rollover attempts for same notification
|
||||
- Concurrent scheduling operations
|
||||
- App state transitions during rollover
|
||||
- Background task conflicts
|
||||
|
||||
### 5. **System Events**
|
||||
- Device reboots
|
||||
- App termination
|
||||
- Low memory conditions
|
||||
- Background execution limits
|
||||
|
||||
### 6. **Notification System Edge Cases**
|
||||
- Notification limit reached (64 pending)
|
||||
- Notification delivery failures
|
||||
- System notification queue issues
|
||||
- Permission changes
|
||||
|
||||
---
|
||||
|
||||
## Detection Mechanisms
|
||||
|
||||
### A. Time Change Detection
|
||||
|
||||
**iOS Limitation**: iOS doesn't provide direct time change notifications like Android's `ACTION_TIME_CHANGED` broadcast.
|
||||
|
||||
**Solution**: Multi-layered detection:
|
||||
|
||||
1. **App Launch Detection**
|
||||
- Store last known system time on app exit
|
||||
- Compare on app launch
|
||||
- Detect significant time jumps (>5 minutes)
|
||||
|
||||
2. **Background Task Detection**
|
||||
- Store timestamp when scheduling notification
|
||||
- Compare with current time when background task runs
|
||||
- Detect time discrepancies
|
||||
|
||||
3. **Notification Delivery Detection**
|
||||
- Compare scheduled time with actual delivery time
|
||||
- Flag if delivery time is significantly different
|
||||
|
||||
4. **Periodic Validation**
|
||||
- Background task validates scheduled notifications
|
||||
- Checks if notification times are still valid
|
||||
- Adjusts if time change detected
|
||||
|
||||
### B. Timezone Change Detection
|
||||
|
||||
**iOS Limitation**: No direct timezone change notification.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. **Store Timezone on Schedule**
|
||||
- Save timezone identifier when scheduling
|
||||
- Store as part of notification metadata
|
||||
|
||||
2. **Compare on Access**
|
||||
- Check current timezone vs stored timezone
|
||||
- Detect changes on app launch, background tasks, rollover
|
||||
|
||||
3. **Recalculate on Change**
|
||||
- If timezone changed, recalculate all scheduled times
|
||||
- Maintain same local time (e.g., 9:00 AM stays 9:00 AM)
|
||||
|
||||
### C. DST Transition Detection
|
||||
|
||||
**Solution**: Use Calendar API for DST-aware calculations:
|
||||
|
||||
1. **Calendar-Based Calculation**
|
||||
- Use `Calendar.date(byAdding: .hour, value: 24, to:)`
|
||||
- Automatically handles DST transitions
|
||||
- No manual DST detection needed
|
||||
|
||||
2. **Validation After Calculation**
|
||||
- Verify calculated time is exactly 24 hours later in local time
|
||||
- Log DST transitions for debugging
|
||||
- Handle edge cases (e.g., 2:00 AM → 3:00 AM spring forward)
|
||||
|
||||
### D. Duplicate Prevention
|
||||
|
||||
**Solution**: Multi-level idempotence checks:
|
||||
|
||||
1. **Database-Level Check**
|
||||
- Store rollover state per notification ID
|
||||
- Track last processed rollover time
|
||||
- Prevent duplicate rollover attempts
|
||||
|
||||
2. **Storage-Level Check**
|
||||
- Check for existing notifications at same scheduled time
|
||||
- Use tolerance window (1 minute) for DST shifts
|
||||
- Compare notification IDs and scheduled times
|
||||
|
||||
3. **System-Level Check**
|
||||
- Query `UNUserNotificationCenter` for pending notifications
|
||||
- Check if notification already scheduled
|
||||
- Cancel and reschedule if needed
|
||||
|
||||
4. **Request-Level Check**
|
||||
- Use unique notification IDs
|
||||
- Include timestamp in ID generation
|
||||
- Prevent ID collisions
|
||||
|
||||
---
|
||||
|
||||
## Handling Strategies
|
||||
|
||||
### Strategy 1: Time Change Handling
|
||||
|
||||
**When Detected**:
|
||||
1. **Validate All Scheduled Notifications**
|
||||
- Check if scheduled times are still valid
|
||||
- Recalculate if time change was significant
|
||||
- Cancel invalid notifications
|
||||
|
||||
2. **Recalculate Rollover Times**
|
||||
- If time changed, recalculate next notification time
|
||||
- Use DST-safe calculation
|
||||
- Maintain same local time (e.g., 9:00 AM)
|
||||
|
||||
3. **Reschedule Affected Notifications**
|
||||
- Cancel old notifications
|
||||
- Schedule with corrected times
|
||||
- Update storage with new times
|
||||
|
||||
4. **Log Time Change Event**
|
||||
- Record time change in history
|
||||
- Log old time, new time, delta
|
||||
- Track which notifications were affected
|
||||
|
||||
**Implementation**:
|
||||
```swift
|
||||
func handleTimeChange(
|
||||
lastKnownTime: Int64,
|
||||
currentTime: Int64,
|
||||
scheduledNotifications: [NotificationContent]
|
||||
) async {
|
||||
let timeDelta = abs(currentTime - lastKnownTime)
|
||||
|
||||
// Only handle significant time changes (>5 minutes)
|
||||
guard timeDelta > (5 * 60 * 1000) else {
|
||||
return // Ignore small clock adjustments
|
||||
}
|
||||
|
||||
// Recalculate all scheduled notifications
|
||||
for notification in scheduledNotifications {
|
||||
// Recalculate using original scheduled time
|
||||
let originalScheduledTime = notification.scheduledTime
|
||||
let newScheduledTime = recalculateScheduledTime(
|
||||
originalTime: originalScheduledTime,
|
||||
timeDelta: timeDelta
|
||||
)
|
||||
|
||||
// Cancel old notification
|
||||
await scheduler.cancelNotification(id: notification.id)
|
||||
|
||||
// Reschedule with corrected time
|
||||
let updatedNotification = NotificationContent(
|
||||
id: notification.id,
|
||||
title: notification.title,
|
||||
body: notification.body,
|
||||
scheduledTime: newScheduledTime,
|
||||
fetchedAt: notification.fetchedAt,
|
||||
url: notification.url,
|
||||
payload: notification.payload,
|
||||
etag: notification.etag
|
||||
)
|
||||
|
||||
await scheduler.scheduleNotification(updatedNotification)
|
||||
}
|
||||
|
||||
// Record time change in history
|
||||
await recordTimeChangeEvent(
|
||||
oldTime: lastKnownTime,
|
||||
newTime: currentTime,
|
||||
delta: timeDelta
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Strategy 2: Timezone Change Handling
|
||||
|
||||
**When Detected**:
|
||||
1. **Detect Timezone Change**
|
||||
- Compare current timezone with stored timezone
|
||||
- Detect on app launch, background tasks, rollover
|
||||
|
||||
2. **Recalculate All Scheduled Times**
|
||||
- Maintain same local time (e.g., 9:00 AM)
|
||||
- Convert to new timezone
|
||||
- Update scheduled times
|
||||
|
||||
3. **Reschedule All Notifications**
|
||||
- Cancel existing notifications
|
||||
- Schedule with new times
|
||||
- Update storage
|
||||
|
||||
**Implementation**:
|
||||
```swift
|
||||
func handleTimezoneChange(
|
||||
oldTimezone: TimeZone,
|
||||
newTimezone: TimeZone,
|
||||
scheduledNotifications: [NotificationContent]
|
||||
) async {
|
||||
// Extract local time from each notification
|
||||
for notification in scheduledNotifications {
|
||||
// Get local time components (hour, minute)
|
||||
let scheduledDate = notification.getScheduledTimeAsDate()
|
||||
let calendar = Calendar.current
|
||||
let hour = calendar.component(.hour, from: scheduledDate)
|
||||
let minute = calendar.component(.minute, from: scheduledDate)
|
||||
|
||||
// Recalculate in new timezone
|
||||
let newScheduledTime = calculateNextOccurrence(
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
timezone: newTimezone
|
||||
)
|
||||
|
||||
// Cancel old notification
|
||||
await scheduler.cancelNotification(id: notification.id)
|
||||
|
||||
// Reschedule with new time
|
||||
let updatedNotification = NotificationContent(
|
||||
id: notification.id,
|
||||
title: notification.title,
|
||||
body: notification.body,
|
||||
scheduledTime: newScheduledTime,
|
||||
fetchedAt: notification.fetchedAt,
|
||||
url: notification.url,
|
||||
payload: notification.payload,
|
||||
etag: notification.etag
|
||||
)
|
||||
|
||||
await scheduler.scheduleNotification(updatedNotification)
|
||||
}
|
||||
|
||||
// Update stored timezone
|
||||
await storage.saveTimezone(newTimezone.identifier)
|
||||
}
|
||||
```
|
||||
|
||||
### Strategy 3: DST Transition Handling
|
||||
|
||||
**When Detected**:
|
||||
1. **Use Calendar API**
|
||||
- `Calendar.date(byAdding: .hour, value: 24, to:)` handles DST automatically
|
||||
- No manual DST detection needed
|
||||
|
||||
2. **Validate Calculation**
|
||||
- Verify 24-hour addition results in correct local time
|
||||
- Log DST transitions for debugging
|
||||
- Handle edge cases (2:00 AM → 3:00 AM)
|
||||
|
||||
3. **Handle Edge Cases**
|
||||
- Spring forward: Notification might be scheduled for 2:00 AM (doesn't exist)
|
||||
- Fall back: Notification might be scheduled for 2:00 AM (occurs twice)
|
||||
- Use system's automatic handling
|
||||
|
||||
**Implementation**:
|
||||
```swift
|
||||
func calculateNextScheduledTime(_ currentScheduledTime: Int64) -> Int64 {
|
||||
let calendar = Calendar.current
|
||||
let currentDate = Date(timeIntervalSince1970: Double(currentScheduledTime) / 1000.0)
|
||||
|
||||
// Add 24 hours (handles DST automatically)
|
||||
guard let nextDate = calendar.date(byAdding: .hour, value: 24, to: currentDate) else {
|
||||
// Fallback to simple addition
|
||||
return currentScheduledTime + (24 * 60 * 60 * 1000)
|
||||
}
|
||||
|
||||
// Validate: Ensure it's exactly 24 hours later in local time
|
||||
let currentHour = calendar.component(.hour, from: currentDate)
|
||||
let currentMinute = calendar.component(.minute, from: currentDate)
|
||||
let nextHour = calendar.component(.hour, from: nextDate)
|
||||
let nextMinute = calendar.component(.minute, from: nextDate)
|
||||
|
||||
// Log DST transitions
|
||||
if currentHour != nextHour || currentMinute != nextMinute {
|
||||
print("\(Self.TAG): DST transition detected: \(currentHour):\(currentMinute) -> \(nextHour):\(nextMinute)")
|
||||
}
|
||||
|
||||
return Int64(nextDate.timeIntervalSince1970 * 1000)
|
||||
}
|
||||
```
|
||||
|
||||
### Strategy 4: Duplicate Prevention
|
||||
|
||||
**Multi-Level Checks**:
|
||||
|
||||
1. **Rollover State Tracking**
|
||||
- Store rollover state in database
|
||||
- Track last processed notification ID
|
||||
- Prevent duplicate rollover attempts
|
||||
|
||||
2. **Time-Based Deduplication**
|
||||
- Check for existing notifications at same scheduled time
|
||||
- Use tolerance window (1 minute) for DST shifts
|
||||
- Compare notification IDs
|
||||
|
||||
3. **System-Level Verification**
|
||||
- Query `UNUserNotificationCenter` for pending notifications
|
||||
- Check if notification already scheduled
|
||||
- Cancel and reschedule if needed
|
||||
|
||||
**Implementation**:
|
||||
```swift
|
||||
func scheduleNextNotification(
|
||||
_ content: NotificationContent,
|
||||
storage: DailyNotificationStorage?,
|
||||
fetcher: DailyNotificationFetcher? = nil
|
||||
) async -> Bool {
|
||||
// Check 1: Rollover state tracking
|
||||
if let storage = storage {
|
||||
let lastRolloverTime = await storage.getLastRolloverTime(for: content.id)
|
||||
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
|
||||
// If rollover was processed recently (< 1 hour ago), skip
|
||||
if let lastTime = lastRolloverTime,
|
||||
(currentTime - lastTime) < (60 * 60 * 1000) {
|
||||
print("\(Self.TAG): RESCHEDULE_SKIP id=\(content.id) already_processed")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate next time
|
||||
let nextScheduledTime = calculateNextScheduledTime(content.scheduledTime)
|
||||
|
||||
// Check 2: Storage-level duplicate check
|
||||
if let storage = storage {
|
||||
let existingNotifications = storage.getAllNotifications()
|
||||
let toleranceMs: Int64 = 60 * 1000 // 1 minute tolerance
|
||||
|
||||
for existing in existingNotifications {
|
||||
if abs(existing.scheduledTime - nextScheduledTime) <= toleranceMs {
|
||||
print("\(Self.TAG): RESCHEDULE_DUPLICATE id=\(content.id) existing_id=\(existing.id)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check 3: System-level duplicate check
|
||||
let pendingNotifications = await notificationCenter.pendingNotificationRequests()
|
||||
for pending in pendingNotifications {
|
||||
if let trigger = pending.trigger as? UNCalendarNotificationTrigger,
|
||||
let nextDate = trigger.nextTriggerDate() {
|
||||
let pendingTime = Int64(nextDate.timeIntervalSince1970 * 1000)
|
||||
let toleranceMs: Int64 = 60 * 1000
|
||||
|
||||
if abs(pendingTime - nextScheduledTime) <= toleranceMs {
|
||||
print("\(Self.TAG): RESCHEDULE_DUPLICATE id=\(content.id) system_pending_id=\(pending.identifier)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All checks passed, proceed with scheduling
|
||||
// ... (rest of scheduling logic)
|
||||
|
||||
// Mark rollover as processed
|
||||
await storage?.saveLastRolloverTime(for: content.id, time: Int64(Date().timeIntervalSince1970 * 1000))
|
||||
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
### Strategy 5: Race Condition Prevention
|
||||
|
||||
**Solution**: Use serial queue + state tracking
|
||||
|
||||
1. **Serial Queue for Rollover**
|
||||
- Use dedicated serial queue for rollover operations
|
||||
- Prevent concurrent rollover attempts
|
||||
- Ensure atomic operations
|
||||
|
||||
2. **State Machine**
|
||||
- Track rollover state (pending, processing, completed)
|
||||
- Prevent duplicate processing
|
||||
- Handle failures gracefully
|
||||
|
||||
3. **Locking Mechanism**
|
||||
- Use actor or serial queue for thread safety
|
||||
- Prevent race conditions
|
||||
- Ensure atomic updates
|
||||
|
||||
**Implementation**:
|
||||
```swift
|
||||
actor RolloverCoordinator {
|
||||
private var processingNotifications: Set<String> = []
|
||||
private let scheduler: DailyNotificationScheduler
|
||||
private let storage: DailyNotificationStorage
|
||||
|
||||
func processRollover(for notificationId: String) async -> Bool {
|
||||
// Check if already processing
|
||||
if processingNotifications.contains(notificationId) {
|
||||
print("RolloverCoordinator: Already processing \(notificationId)")
|
||||
return false
|
||||
}
|
||||
|
||||
// Mark as processing
|
||||
processingNotifications.insert(notificationId)
|
||||
defer {
|
||||
processingNotifications.remove(notificationId)
|
||||
}
|
||||
|
||||
// Perform rollover
|
||||
// ... (rollover logic)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Architecture
|
||||
|
||||
### Component 1: TimeChangeDetector
|
||||
|
||||
**Purpose**: Detect time changes and trigger recovery
|
||||
|
||||
**Responsibilities**:
|
||||
- Store last known system time
|
||||
- Compare on app launch/background tasks
|
||||
- Detect significant time jumps
|
||||
- Trigger time change recovery
|
||||
|
||||
**Location**: `ios/Plugin/DailyNotificationTimeChangeDetector.swift`
|
||||
|
||||
### Component 2: TimezoneChangeDetector
|
||||
|
||||
**Purpose**: Detect timezone changes and trigger recalculation
|
||||
|
||||
**Responsibilities**:
|
||||
- Store current timezone
|
||||
- Compare on access
|
||||
- Detect timezone changes
|
||||
- Trigger timezone change recovery
|
||||
|
||||
**Location**: `ios/Plugin/DailyNotificationTimezoneChangeDetector.swift`
|
||||
|
||||
### Component 3: RolloverCoordinator
|
||||
|
||||
**Purpose**: Coordinate rollover operations with duplicate prevention
|
||||
|
||||
**Responsibilities**:
|
||||
- Manage rollover state
|
||||
- Prevent duplicate rollovers
|
||||
- Coordinate multiple detection mechanisms
|
||||
- Handle race conditions
|
||||
|
||||
**Location**: `ios/Plugin/DailyNotificationRolloverCoordinator.swift`
|
||||
|
||||
### Component 4: Enhanced Recovery Manager
|
||||
|
||||
**Purpose**: Extend existing recovery manager with time/timezone change handling
|
||||
|
||||
**Responsibilities**:
|
||||
- Integrate time change detection
|
||||
- Integrate timezone change detection
|
||||
- Coordinate with rollover coordinator
|
||||
- Handle all edge cases
|
||||
|
||||
**Location**: `ios/Plugin/DailyNotificationReactivationManager.swift` (enhance existing)
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Test Category 1: Time Changes
|
||||
|
||||
1. **Manual Clock Adjustment**
|
||||
- Set device time forward 1 hour
|
||||
- Verify notifications rescheduled correctly
|
||||
- Verify rollover still works
|
||||
|
||||
2. **Clock Jump Forward**
|
||||
- Set device time forward 24 hours
|
||||
- Verify all notifications recalculated
|
||||
- Verify no duplicates created
|
||||
|
||||
3. **Clock Jump Backward**
|
||||
- Set device time backward 1 hour
|
||||
- Verify notifications still valid
|
||||
- Verify rollover works correctly
|
||||
|
||||
### Test Category 2: Timezone Changes
|
||||
|
||||
1. **Timezone Change**
|
||||
- Change device timezone
|
||||
- Verify notifications rescheduled to same local time
|
||||
- Verify rollover maintains local time
|
||||
|
||||
2. **Travel Simulation**
|
||||
- Change timezone multiple times
|
||||
- Verify notifications always at correct local time
|
||||
- Verify no duplicates
|
||||
|
||||
### Test Category 3: DST Transitions
|
||||
|
||||
1. **Spring Forward**
|
||||
- Test on DST spring forward day
|
||||
- Verify 24-hour calculation handles correctly
|
||||
- Verify notification fires at correct time
|
||||
|
||||
2. **Fall Back**
|
||||
- Test on DST fall back day
|
||||
- Verify 24-hour calculation handles correctly
|
||||
- Verify no duplicate notifications
|
||||
|
||||
### Test Category 4: Race Conditions
|
||||
|
||||
1. **Concurrent Rollover**
|
||||
- Trigger multiple rollover attempts simultaneously
|
||||
- Verify only one succeeds
|
||||
- Verify no duplicates
|
||||
|
||||
2. **App State Transitions**
|
||||
- Trigger rollover during app state changes
|
||||
- Verify rollover completes correctly
|
||||
- Verify no data corruption
|
||||
|
||||
### Test Category 5: Edge Cases
|
||||
|
||||
1. **Notification Limit**
|
||||
- Schedule 64 notifications
|
||||
- Verify rollover still works
|
||||
- Verify proper error handling
|
||||
|
||||
2. **Permission Changes**
|
||||
- Revoke notification permission
|
||||
- Verify graceful failure
|
||||
- Verify recovery when permission restored
|
||||
|
||||
---
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Core Rollover (Week 1)
|
||||
- ✅ DST-safe time calculation
|
||||
- ✅ Basic rollover scheduling
|
||||
- ✅ Duplicate prevention (storage + system level)
|
||||
- ✅ AppDelegate integration
|
||||
|
||||
### Phase 2: Edge Case Detection (Week 2)
|
||||
- ✅ Time change detection
|
||||
- ✅ Timezone change detection
|
||||
- ✅ Rollover state tracking
|
||||
- ✅ Race condition prevention
|
||||
|
||||
### Phase 3: Recovery Integration (Week 3)
|
||||
- ✅ Time change recovery
|
||||
- ✅ Timezone change recovery
|
||||
- ✅ Enhanced recovery manager
|
||||
- ✅ Background task integration
|
||||
|
||||
### Phase 4: Testing & Validation (Week 4)
|
||||
- ✅ Comprehensive edge case testing
|
||||
- ✅ Real device testing
|
||||
- ✅ DST transition testing
|
||||
- ✅ Performance optimization
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. **Reliability**: 99%+ rollover success rate across all edge cases
|
||||
2. **No Duplicates**: Zero duplicate notifications in any scenario
|
||||
3. **Time Accuracy**: Notifications fire within 1 minute of scheduled time
|
||||
4. **Recovery**: All edge cases handled gracefully with recovery
|
||||
5. **Performance**: Rollover completes in <1 second
|
||||
6. **Logging**: Comprehensive logging for debugging
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
### Risk 1: iOS Background Execution Limits
|
||||
**Mitigation**: Multiple detection mechanisms (delegate + background + recovery)
|
||||
|
||||
### Risk 2: Time Change Detection Reliability
|
||||
**Mitigation**: Store timestamps, compare on every access, validate scheduled times
|
||||
|
||||
### Risk 3: Race Conditions
|
||||
**Mitigation**: Serial queue, state machine, actor-based coordination
|
||||
|
||||
### Risk 4: DST Edge Cases
|
||||
**Mitigation**: Use Calendar API, validate calculations, comprehensive testing
|
||||
|
||||
### Risk 5: Notification System Limits
|
||||
**Mitigation**: Check pending count, handle gracefully, provide user feedback
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Review & Approve Plan** (This document)
|
||||
2. **Create Implementation Tasks** (Break down into specific tasks)
|
||||
3. **Implement Phase 1** (Core rollover functionality)
|
||||
4. **Test Phase 1** (Basic functionality)
|
||||
5. **Implement Phase 2** (Edge case detection)
|
||||
6. **Test Phase 2** (Edge case scenarios)
|
||||
7. **Implement Phase 3** (Recovery integration)
|
||||
8. **Test Phase 3** (Recovery scenarios)
|
||||
9. **Final Testing** (Comprehensive validation)
|
||||
10. **Documentation** (Update docs with edge case handling)
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- Android Implementation: `DailyNotificationWorker.java` (scheduleNextNotification)
|
||||
- Android Time Change Handling: `DailyNotificationRebootRecoveryManager.java`
|
||||
- iOS Calendar API: `Calendar.date(byAdding:to:)` documentation
|
||||
- iOS Background Tasks: `BGTaskScheduler` documentation
|
||||
- iOS Notifications: `UNUserNotificationCenter` documentation
|
||||
Reference in New Issue
Block a user