# DailyNotification Plugin - Stack Improvement Plan

**Author**: Matthew Raymer  
**Date**: October 20, 2025  
**Version**: 1.0.0  
**Status**: Implementation Roadmap

## Executive Summary

This document outlines high-impact improvements for the DailyNotification plugin based on comprehensive analysis of current capabilities, strengths, and areas for enhancement. The improvements focus on reliability, user experience, and production readiness.

## Current State Analysis

### ✅ What's Working Well

#### **Core Capabilities**
- **Daily & reminder scheduling**: Plugin API supports simple daily notifications, advanced reminders (IDs, repeat, vibration, priority, timezone), and combined flows (content fetch + user-facing notification)
- **API-driven change alerts**: Planned `TimeSafariApiService` with DID/JWT authentication, configurable polling, history, and preferences
- **WorkManager offload on Android**: Heavy lifting in `DailyNotificationWorker` with JIT freshness checks, soft prefetch, DST-aware rescheduling, duplicate suppression, and action buttons

#### **Architectural Strengths**
- **Background resilience**: WorkManager provides battery/network constraints and retry semantics
- **Freshness strategy**: Pragmatic "borderline" soft refetch and "stale" hard refresh approach
- **DST safety & dedupe**: Next-day scheduling with DST awareness and duplicate prevention
- **End-to-end planning**: Comprehensive UX, store integration, testing, and deployment coverage

## High-Impact Improvements

### 🔧 Android / Native Side Improvements

#### **1. Exact-Time Reliability (Doze & Android 12+)**
- [ ] **Priority**: Critical  
- [ ] **Impact**: Notification delivery accuracy

- [ ] **Current Issue**: Notifications may not fire at exact times due to Doze mode and Android 12+ restrictions

- [ ] **Implementation**:
```java
// In DailyNotificationScheduler.java
public void scheduleExactNotification(NotificationContent content) {
    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    
    // Use setExactAndAllowWhileIdle for precise timing
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        alarmManager.setExactAndAllowWhileIdle(
            AlarmManager.RTC_WAKEUP,
            content.getScheduledTime(),
            createNotificationPendingIntent(content)
        );
    } else {
        alarmManager.setExact(AlarmManager.RTC_WAKEUP, content.getScheduledTime(), createNotificationPendingIntent(content));
    }
}

// Add exact alarm permission request
public void requestExactAlarmPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        if (!alarmManager.canScheduleExactAlarms()) {
            Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);
        }
    }
}
```

- [ ] **Testing**: Verify notifications fire within 1 minute of scheduled time across Doze cycles

#### **2. DST-Safe Time Calculation**
- [ ] **Priority**: High  
- [ ] **Impact**: Prevents notification drift across DST boundaries

- [ ] **Current Issue**: `plusHours(24)` can drift across DST boundaries

- [ ] **Implementation**:
```java
// In DailyNotificationScheduler.java
public long calculateNextScheduledTime(int hour, int minute, String timezone) {
    ZoneId zone = ZoneId.of(timezone);
    LocalTime targetTime = LocalTime.of(hour, minute);
    
    // Get current time in user's timezone
    ZonedDateTime now = ZonedDateTime.now(zone);
    LocalDate today = now.toLocalDate();
    
    // Calculate next occurrence at same local time
    ZonedDateTime nextScheduled = ZonedDateTime.of(today, targetTime, zone);
    
    // If time has passed today, schedule for tomorrow
    if (nextScheduled.isBefore(now)) {
        nextScheduled = nextScheduled.plusDays(1);
    }
    
    return nextScheduled.toInstant().toEpochMilli();
}
```

- [ ] **Testing**: Test across DST transitions (spring forward/fall back)

#### **3. Work Deduplication & Idempotence**
- [ ] **Priority**: High  
- [ ] **Impact**: Prevents duplicate notifications and race conditions

- [ ] **Current Issue**: Repeat enqueues can cause race conditions

- [ ] **Implementation**:
```java
// In DailyNotificationWorker.java
public static void enqueueDisplayWork(Context context, String notificationId) {
    String workName = "display_notification_" + notificationId;
    
    OneTimeWorkRequest displayWork = new OneTimeWorkRequest.Builder(DailyNotificationWorker.class)
        .setInputData(createNotificationData(notificationId))
        .setConstraints(getNotificationConstraints())
        .build();
    
    WorkManager.getInstance(context)
        .enqueueUniqueWork(workName, ExistingWorkPolicy.REPLACE, displayWork);
}

public static void enqueueSoftRefetchWork(Context context, String notificationId) {
    String workName = "soft_refetch_" + notificationId;
    
    OneTimeWorkRequest refetchWork = new OneTimeWorkRequest.Builder(SoftRefetchWorker.class)
        .setInputData(createRefetchData(notificationId))
        .setConstraints(getRefetchConstraints())
        .build();
    
    WorkManager.getInstance(context)
        .enqueueUniqueWork(workName, ExistingWorkPolicy.KEEP, refetchWork);
}
```

#### **4. Notification Channel Discipline**
- [ ] **Priority**: Medium  
- [ ] **Impact**: Consistent notification behavior and user control

**Implementation**:
```java
// New class: NotificationChannelManager.java
public class NotificationChannelManager {
    private static final String DAILY_NOTIFICATIONS_CHANNEL = "daily_notifications";
    private static final String CHANGE_NOTIFICATIONS_CHANNEL = "change_notifications";
    private static final String SYSTEM_NOTIFICATIONS_CHANNEL = "system_notifications";
    
    public static void createChannels(Context context) {
        NotificationManager notificationManager = 
            (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        
        // Daily notifications channel
        NotificationChannel dailyChannel = new NotificationChannel(
            DAILY_NOTIFICATIONS_CHANNEL,
            "Daily Notifications",
            NotificationManager.IMPORTANCE_DEFAULT
        );
        dailyChannel.setDescription("Scheduled daily reminders and updates");
        dailyChannel.enableLights(true);
        dailyChannel.enableVibration(true);
        dailyChannel.setShowBadge(true);
        
        // Change notifications channel
        NotificationChannel changeChannel = new NotificationChannel(
            CHANGE_NOTIFICATIONS_CHANNEL,
            "Change Notifications",
            NotificationManager.IMPORTANCE_HIGH
        );
        changeChannel.setDescription("Notifications about project changes and updates");
        changeChannel.enableLights(true);
        changeChannel.enableVibration(true);
        changeChannel.setShowBadge(true);
        
        notificationManager.createNotificationChannels(Arrays.asList(dailyChannel, changeChannel));
    }
    
    public static NotificationCompat.Builder createNotificationBuilder(
        Context context, 
        String channelId, 
        String title, 
        String body
    ) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle(title)
            .setContentText(body)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setAutoCancel(true);
        
        // Add BigTextStyle for long content
        if (body.length() > 100) {
            builder.setStyle(new NotificationCompat.BigTextStyle().bigText(body));
        }
        
        return builder;
    }
}
```

#### **5. Click Analytics & Deep-Link Safety**
- [ ] **Priority**: Medium  
- [ ] **Impact**: User engagement tracking and security

**Implementation**:
```java
// New class: NotificationClickReceiver.java
public class NotificationClickReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getStringExtra("action");
        String notificationId = intent.getStringExtra("notification_id");
        String deepLink = intent.getStringExtra("deep_link");
        
        // Record analytics
        recordClickAnalytics(notificationId, action);
        
        // Handle deep link safely
        if (deepLink != null && isValidDeepLink(deepLink)) {
            handleDeepLink(context, deepLink);
        } else {
            // Fallback to main activity
            openMainActivity(context);
        }
    }
    
    private void recordClickAnalytics(String notificationId, String action) {
        // Record click-through rate and user engagement
        AnalyticsService.recordEvent("notification_clicked", Map.of(
            "notification_id", notificationId,
            "action", action,
            "timestamp", System.currentTimeMillis()
        ));
    }
    
    private boolean isValidDeepLink(String deepLink) {
        // Validate deep link format and domain
        return deepLink.startsWith("timesafari://") || 
               deepLink.startsWith("https://endorser.ch/");
    }
}
```

#### **6. Storage Hardening**
- [ ] **Priority**: High  
- [ ] **Impact**: Data integrity and performance

**Implementation**:
```java
// Migrate to Room database
@Entity(tableName = "notification_content")
public class NotificationContentEntity {
    @PrimaryKey
    public String id;
    
    public String title;
    public String body;
    public long scheduledTime;
    public String mediaUrl;
    public long fetchTime;
    public long ttlSeconds;
    public boolean encrypted;
    
    @ColumnInfo(name = "created_at")
    public long createdAt;
    
    @ColumnInfo(name = "updated_at")
    public long updatedAt;
}

@Dao
public interface NotificationContentDao {
    @Query("SELECT * FROM notification_content WHERE scheduledTime > :currentTime ORDER BY scheduledTime ASC")
    List<NotificationContentEntity> getUpcomingNotifications(long currentTime);
    
    @Query("DELETE FROM notification_content WHERE createdAt < :cutoffTime")
    void deleteOldNotifications(long cutoffTime);
    
    @Query("SELECT COUNT(*) FROM notification_content")
    int getNotificationCount();
}

// Add encryption for sensitive content
public class NotificationContentEncryption {
    private static final String ALGORITHM = "AES/GCM/NoPadding";
    
    public String encrypt(String content, String key) {
        // Implementation for encrypting sensitive notification content
    }
    
    public String decrypt(String encryptedContent, String key) {
        // Implementation for decrypting sensitive notification content
    }
}
```

#### **7. Permission UX (Android 13+)**
- [ ] **Priority**: High  
- [ ] **Impact**: User experience and notification delivery

**Implementation**:
```java
// Enhanced permission handling
public class NotificationPermissionManager {
    public static boolean hasPostNotificationsPermission(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            return ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) 
                == PackageManager.PERMISSION_GRANTED;
        }
        return true; // Permission not required for older versions
    }
    
    public static void requestPostNotificationsPermission(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (!hasPostNotificationsPermission(activity)) {
                ActivityCompat.requestPermissions(activity, 
                    new String[]{Manifest.permission.POST_NOTIFICATIONS}, 
                    REQUEST_POST_NOTIFICATIONS);
            }
        }
    }
    
    public static void showPermissionEducationDialog(Context context) {
        new AlertDialog.Builder(context)
            .setTitle("Enable Notifications")
            .setMessage("To receive daily updates and project change notifications, please enable notifications in your device settings.")
            .setPositiveButton("Open Settings", (dialog, which) -> {
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                intent.setData(Uri.parse("package:" + context.getPackageName()));
                context.startActivity(intent);
            })
            .setNegativeButton("Later", null)
            .show();
    }
}
```

### 🔧 Plugin / JS Side Improvements

#### **8. Schema-Validated Inputs**
- [ ] **Priority**: High  
- [ ] **Impact**: Data integrity and error prevention

**Implementation**:
```typescript
// Add Zod schema validation
import { z } from 'zod';

const NotificationOptionsSchema = z.object({
  time: z.string().regex(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/, 'Invalid time format. Use HH:MM'),
  title: z.string().min(1).max(100, 'Title must be less than 100 characters'),
  body: z.string().min(1).max(500, 'Body must be less than 500 characters'),
  sound: z.boolean().optional().default(true),
  priority: z.enum(['low', 'default', 'high']).optional().default('default'),
  url: z.string().url().optional()
});

const ReminderOptionsSchema = z.object({
  id: z.string().min(1),
  title: z.string().min(1).max(100),
  body: z.string().min(1).max(500),
  time: z.string().regex(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/),
  sound: z.boolean().optional().default(true),
  vibration: z.boolean().optional().default(true),
  priority: z.enum(['low', 'normal', 'high']).optional().default('normal'),
  repeatDaily: z.boolean().optional().default(true),
  timezone: z.string().optional().default('UTC')
});

// Enhanced plugin methods with validation
export class DailyNotificationPlugin {
  async scheduleDailyNotification(options: unknown): Promise<void> {
    try {
      const validatedOptions = NotificationOptionsSchema.parse(options);
      return await this.nativeScheduleDailyNotification(validatedOptions);
    } catch (error) {
      if (error instanceof z.ZodError) {
        throw new Error(`Validation failed: ${error.errors.map(e => e.message).join(', ')}`);
      }
      throw error;
    }
  }
  
  async scheduleDailyReminder(options: unknown): Promise<void> {
    try {
      const validatedOptions = ReminderOptionsSchema.parse(options);
      return await this.nativeScheduleDailyReminder(validatedOptions);
    } catch (error) {
      if (error instanceof z.ZodError) {
        throw new Error(`Validation failed: ${error.errors.map(e => e.message).join(', ')}`);
      }
      throw error;
    }
  }
}
```

#### **9. Quiet Hours Enforcement**
- [ ] **Priority**: Medium  
- [ ] **Impact**: User experience and notification respect

**Implementation**:
```typescript
// Enhanced quiet hours handling
export class QuietHoursManager {
  private quietHoursStart: string = '22:00';
  private quietHoursEnd: string = '08:00';
  
  isQuietHours(time: string): boolean {
    const currentTime = new Date();
    const currentTimeString = currentTime.toTimeString().slice(0, 5);
    
    const start = this.quietHoursStart;
    const end = this.quietHoursEnd;
    
    if (start <= end) {
      // Same day quiet hours (e.g., 22:00 to 08:00)
      return currentTimeString >= start || currentTimeString <= end;
    } else {
      // Overnight quiet hours (e.g., 22:00 to 08:00)
      return currentTimeString >= start || currentTimeString <= end;
    }
  }
  
  getNextAllowedTime(time: string): string {
    if (!this.isQuietHours(time)) {
      return time;
    }
    
    // Calculate next allowed time
    const [hours, minutes] = this.quietHoursEnd.split(':').map(Number);
    const nextAllowed = new Date();
    nextAllowed.setHours(hours, minutes, 0, 0);
    
    // If quiet hours end is tomorrow
    if (this.quietHoursStart > this.quietHoursEnd) {
      nextAllowed.setDate(nextAllowed.getDate() + 1);
    }
    
    return nextAllowed.toTimeString().slice(0, 5);
  }
}

// Enhanced scheduling with quiet hours
export class EnhancedNotificationScheduler {
  private quietHoursManager = new QuietHoursManager();
  
  async scheduleNotification(options: NotificationOptions): Promise<void> {
    if (this.quietHoursManager.isQuietHours(options.time)) {
      const nextAllowedTime = this.quietHoursManager.getNextAllowedTime(options.time);
      console.log(`Scheduling notification for ${nextAllowedTime} due to quiet hours`);
      options.time = nextAllowedTime;
    }
    
    return await DailyNotification.scheduleDailyNotification(options);
  }
}
```

#### **10. Backoff & Jitter for API Polling**
- [ ] **Priority**: Medium  
- [ ] **Impact**: API efficiency and server load reduction

**Implementation**:
```typescript
// Enhanced API polling with backoff
export class SmartApiPoller {
  private baseInterval: number = 300000; // 5 minutes
  private maxInterval: number = 1800000; // 30 minutes
  private backoffMultiplier: number = 1.5;
  private jitterRange: number = 0.1; // 10% jitter
  private currentInterval: number = this.baseInterval;
  private consecutiveErrors: number = 0;
  private lastModified?: string;
  private etag?: string;
  
  async pollWithBackoff(): Promise<ChangeNotification[]> {
    try {
      const changes = await this.fetchChanges();
      this.onSuccess();
      return changes;
    } catch (error) {
      this.onError();
      throw error;
    }
  }
  
  private async fetchChanges(): Promise<ChangeNotification[]> {
    const headers: Record<string, string> = {
      'Content-Type': 'application/json'
    };
    
    // Add conditional headers for efficient polling
    if (this.lastModified) {
      headers['If-Modified-Since'] = this.lastModified;
    }
    if (this.etag) {
      headers['If-None-Match'] = this.etag;
    }
    
    const response = await fetch('/api/v2/report/plansLastUpdatedBetween', {
      method: 'POST',
      headers,
      body: JSON.stringify({
        planIds: this.starredPlanHandleIds,
        afterId: this.lastAckedJwtId
      })
    });
    
    if (response.status === 304) {
      // No changes
      return [];
    }
    
    // Update conditional headers
    this.lastModified = response.headers.get('Last-Modified') || undefined;
    this.etag = response.headers.get('ETag') || undefined;
    
    const data = await response.json();
    return this.mapToChangeNotifications(data);
  }
  
  private onSuccess(): void {
    this.consecutiveErrors = 0;
    this.currentInterval = this.baseInterval;
  }
  
  private onError(): void {
    this.consecutiveErrors++;
    this.currentInterval = Math.min(
      this.currentInterval * this.backoffMultiplier,
      this.maxInterval
    );
  }
  
  private getNextPollInterval(): number {
    const jitter = this.currentInterval * this.jitterRange * (Math.random() - 0.5);
    return Math.max(this.currentInterval + jitter, 60000); // Minimum 1 minute
  }
}
```

#### **11. Action Handling End-to-End**
- [ ] **Priority**: High  
- [ ] **Impact**: User engagement and data consistency

**Implementation**:
```typescript
// Complete action handling system
export class NotificationActionHandler {
  async handleAction(action: string, notificationId: string, data?: any): Promise<void> {
    switch (action) {
      case 'view':
        await this.handleViewAction(notificationId, data);
        break;
      case 'dismiss':
        await this.handleDismissAction(notificationId);
        break;
      case 'snooze':
        await this.handleSnoozeAction(notificationId, data?.snoozeMinutes);
        break;
      default:
        console.warn(`Unknown action: ${action}`);
    }
    
    // Record analytics
    await this.recordActionAnalytics(action, notificationId);
  }
  
  private async handleViewAction(notificationId: string, data?: any): Promise<void> {
    // Navigate to relevant content
    if (data?.deepLink) {
      await this.navigateToDeepLink(data.deepLink);
    } else {
      await this.navigateToMainApp();
    }
    
    // Mark as read on server
    await this.markAsReadOnServer(notificationId);
  }
  
  private async handleDismissAction(notificationId: string): Promise<void> {
    // Mark as dismissed locally
    await this.markAsDismissedLocally(notificationId);
    
    // Mark as dismissed on server
    await this.markAsDismissedOnServer(notificationId);
  }
  
  private async handleSnoozeAction(notificationId: string, snoozeMinutes: number): Promise<void> {
    // Reschedule notification
    const newTime = new Date(Date.now() + snoozeMinutes * 60000);
    await DailyNotification.scheduleDailyNotification({
      time: newTime.toTimeString().slice(0, 5),
      title: 'Snoozed Notification',
      body: 'This notification was snoozed',
      id: `${notificationId}_snoozed_${Date.now()}`
    });
  }
  
  private async recordActionAnalytics(action: string, notificationId: string): Promise<void> {
    // Record click-through rate and user engagement
    await AnalyticsService.recordEvent('notification_action', {
      action,
      notificationId,
      timestamp: Date.now()
    });
  }
}
```

#### **12. Battery & Network Budgets**
- [ ] **Priority**: Medium  
- [ ] **Impact**: Battery life and network efficiency

**Implementation**:
```typescript
// Job coalescing and budget management
export class NotificationJobManager {
  private pendingJobs: Map<string, NotificationJob> = new Map();
  private coalescingWindow: number = 300000; // 5 minutes
  
  async scheduleNotificationJob(job: NotificationJob): Promise<void> {
    this.pendingJobs.set(job.id, job);
    
    // Check if we should coalesce jobs
    if (this.shouldCoalesceJobs()) {
      await this.coalesceJobs();
    } else {
      // Schedule individual job
      await this.scheduleIndividualJob(job);
    }
  }
  
  private shouldCoalesceJobs(): boolean {
    const now = Date.now();
    const jobsInWindow = Array.from(this.pendingJobs.values())
      .filter(job => now - job.createdAt < this.coalescingWindow);
    
    return jobsInWindow.length >= 3; // Coalesce if 3+ jobs in window
  }
  
  private async coalesceJobs(): Promise<void> {
    const jobsToCoalesce = Array.from(this.pendingJobs.values())
      .filter(job => Date.now() - job.createdAt < this.coalescingWindow);
    
    if (jobsToCoalesce.length === 0) return;
    
    // Create coalesced job
    const coalescedJob: CoalescedNotificationJob = {
      id: `coalesced_${Date.now()}`,
      jobs: jobsToCoalesce,
      createdAt: Date.now()
    };
    
    // Schedule coalesced job with WorkManager constraints
    await this.scheduleCoalescedJob(coalescedJob);
    
    // Clear pending jobs
    jobsToCoalesce.forEach(job => this.pendingJobs.delete(job.id));
  }
  
  private async scheduleCoalescedJob(job: CoalescedNotificationJob): Promise<void> {
    // Use WorkManager with battery and network constraints
    const constraints = new WorkConstraints.Builder()
      .setRequiredNetworkType(NetworkType.UNMETERED) // Use unmetered network
      .setRequiresBatteryNotLow(true) // Don't run on low battery
      .setRequiresCharging(false) // Allow running while not charging
      .build();
    
    const workRequest = new OneTimeWorkRequest.Builder(CoalescedNotificationWorker.class)
      .setInputData(createCoalescedJobData(job))
      .setConstraints(constraints)
      .build();
    
    await WorkManager.getInstance().enqueue(workRequest);
  }
}
```

#### **13. Internationalization & Theming**
- [ ] **Priority**: Low  
- [ ] **Impact**: User experience and accessibility

**Implementation**:
```typescript
// i18n support for notifications
export class NotificationI18n {
  private locale: string = 'en';
  private translations: Map<string, Map<string, string>> = new Map();
  
  async loadTranslations(locale: string): Promise<void> {
    this.locale = locale;
    
    try {
      const translations = await import(`./locales/${locale}.json`);
      this.translations.set(locale, translations.default);
    } catch (error) {
      console.warn(`Failed to load translations for ${locale}:`, error);
      // Fallback to English
      this.locale = 'en';
    }
  }
  
  t(key: string, params?: Record<string, string>): string {
    const localeTranslations = this.translations.get(this.locale);
    if (!localeTranslations) {
      return key; // Fallback to key
    }
    
    let translation = localeTranslations.get(key) || key;
    
    // Replace parameters
    if (params) {
      Object.entries(params).forEach(([param, value]) => {
        translation = translation.replace(`{{${param}}}`, value);
      });
    }
    
    return translation;
  }
  
  getNotificationTitle(type: string, params?: Record<string, string>): string {
    return this.t(`notifications.${type}.title`, params);
  }
  
  getNotificationBody(type: string, params?: Record<string, string>): string {
    return this.t(`notifications.${type}.body`, params);
  }
}

// Enhanced notification preferences
export interface NotificationPreferences {
  enableScheduledReminders: boolean;
  enableChangeNotifications: boolean;
  enableSystemNotifications: boolean;
  quietHoursStart: string;
  quietHoursEnd: string;
  preferredNotificationTimes: string[];
  changeTypes: string[];
  locale: string;
  theme: 'light' | 'dark' | 'system';
  soundEnabled: boolean;
  vibrationEnabled: boolean;
  badgeEnabled: boolean;
}
```

#### **14. Test Harness & Golden Scenarios**
- [ ] **Priority**: High  
- [ ] **Impact**: Reliability and confidence in production

**Implementation**:
```typescript
// Comprehensive test scenarios
export class NotificationTestHarness {
  async runGoldenScenarios(): Promise<TestResults> {
    const results: TestResults = {
      clockSkew: await this.testClockSkew(),
      dstJump: await this.testDstJump(),
      dozeIdle: await this.testDozeIdle(),
      permissionDenied: await this.testPermissionDenied(),
      exactAlarmDenied: await this.testExactAlarmDenied(),
      oemBackgroundKill: await this.testOemBackgroundKill()
    };
    
    return results;
  }
  
  private async testClockSkew(): Promise<TestResult> {
    // Test notification scheduling with clock skew
    const originalTime = Date.now();
    const skewedTime = originalTime + 300000; // 5 minutes ahead
    
    // Mock clock skew
    jest.spyOn(Date, 'now').mockReturnValue(skewedTime);
    
    try {
      await DailyNotification.scheduleDailyNotification({
        time: '09:00',
        title: 'Clock Skew Test',
        body: 'Testing clock skew handling'
      });
      
      return { success: true, message: 'Clock skew handled correctly' };
    } catch (error) {
      return { success: false, message: `Clock skew test failed: ${error.message}` };
    } finally {
      jest.restoreAllMocks();
    }
  }
  
  private async testDstJump(): Promise<TestResult> {
    // Test DST transition handling
    const dstTransitionDate = new Date('2025-03-09T07:00:00Z'); // Spring forward
    const beforeDst = new Date('2025-03-09T06:59:00Z');
    const afterDst = new Date('2025-03-09T08:01:00Z');
    
    // Test scheduling before DST
    jest.useFakeTimers();
    jest.setSystemTime(beforeDst);
    
    try {
      await DailyNotification.scheduleDailyNotification({
        time: '08:00',
        title: 'DST Test',
        body: 'Testing DST transition'
      });
      
      // Fast forward past DST
      jest.setSystemTime(afterDst);
      
      // Verify notification still scheduled correctly
      const status = await DailyNotification.getNotificationStatus();
      
      return { success: true, message: 'DST transition handled correctly' };
    } catch (error) {
      return { success: false, message: `DST test failed: ${error.message}` };
    } finally {
      jest.useRealTimers();
    }
  }
  
  private async testDozeIdle(): Promise<TestResult> {
    // Test Doze mode handling
    try {
      // Simulate Doze mode
      await this.simulateDozeMode();
      
      // Schedule notification
      await DailyNotification.scheduleDailyNotification({
        time: '09:00',
        title: 'Doze Test',
        body: 'Testing Doze mode handling'
      });
      
      // Verify notification scheduled
      const status = await DailyNotification.getNotificationStatus();
      
      return { success: true, message: 'Doze mode handled correctly' };
    } catch (error) {
      return { success: false, message: `Doze test failed: ${error.message}` };
    }
  }
  
  private async testPermissionDenied(): Promise<TestResult> {
    // Test permission denied scenarios
    try {
      // Mock permission denied
      jest.spyOn(DailyNotification, 'checkPermissions').mockResolvedValue({
        notifications: 'denied',
        exactAlarms: 'denied'
      });
      
      // Attempt to schedule notification
      await DailyNotification.scheduleDailyNotification({
        time: '09:00',
        title: 'Permission Test',
        body: 'Testing permission denied handling'
      });
      
      return { success: true, message: 'Permission denied handled correctly' };
    } catch (error) {
      return { success: false, message: `Permission test failed: ${error.message}` };
    } finally {
      jest.restoreAllMocks();
    }
  }
  
  private async testExactAlarmDenied(): Promise<TestResult> {
    // Test exact alarm permission denied
    try {
      // Mock exact alarm denied
      jest.spyOn(DailyNotification, 'getExactAlarmStatus').mockResolvedValue({
        supported: true,
        enabled: false,
        canSchedule: false,
        fallbackWindow: '±10 minutes'
      });
      
      // Attempt to schedule exact notification
      await DailyNotification.scheduleDailyNotification({
        time: '09:00',
        title: 'Exact Alarm Test',
        body: 'Testing exact alarm denied handling'
      });
      
      return { success: true, message: 'Exact alarm denied handled correctly' };
    } catch (error) {
      return { success: false, message: `Exact alarm test failed: ${error.message}` };
    } finally {
      jest.restoreAllMocks();
    }
  }
  
  private async testOemBackgroundKill(): Promise<TestResult> {
    // Test OEM background kill scenarios
    try {
      // Simulate background kill
      await this.simulateBackgroundKill();
      
      // Verify recovery
      const status = await DailyNotification.getNotificationStatus();
      
      return { success: true, message: 'OEM background kill handled correctly' };
    } catch (error) {
      return { success: false, message: `OEM background kill test failed: ${error.message}` };
    }
  }
}
```

## Implementation Priority Matrix

### **Critical Priority (Implement First)**
- [ ] 1. **Exact-time reliability** - Core functionality
- [ ] 2. **DST-safe time calculation** - Prevents user-facing bugs
- [ ] 3. **Schema-validated inputs** - Data integrity
- [ ] 4. **Permission UX** - User experience

### **High Priority (Implement Second)**
- [ ] 5. **Work deduplication** - Prevents race conditions
- [ ] 6. **Storage hardening** - Data integrity and performance
- [ ] 7. **Action handling end-to-end** - User engagement
- [ ] 8. **Test harness** - Reliability and confidence

### **Medium Priority (Implement Third)**
- [ ] 9. **Notification channel discipline** - User control
- [ ] 10. **Quiet hours enforcement** - User experience
- [ ] 11. **Backoff & jitter** - API efficiency
- [ ] 12. **Battery & network budgets** - Performance

### **Low Priority (Implement Last)**
- [ ] 13. **Click analytics** - User insights
- [ ] 14. **Internationalization** - Accessibility

## Success Metrics

### **Reliability Metrics**
- [ ] **Notification delivery rate**: >95% of scheduled notifications delivered
- [ ] **Timing accuracy**: Notifications delivered within 1 minute of scheduled time
- [ ] **DST transition success**: 100% success rate across DST boundaries
- [ ] **Permission handling**: Graceful degradation when permissions denied

### **Performance Metrics**
- [ ] **Battery impact**: <1% battery drain per day
- [ ] **Network efficiency**: <1MB data usage per day
- [ ] **Storage usage**: <10MB local storage
- [ ] **Memory usage**: <50MB RAM usage

### **User Experience Metrics**
- [ ] **Click-through rate**: >20% of notifications clicked
- [ ] **Dismissal rate**: <30% of notifications dismissed
- [ ] **User satisfaction**: >4.0/5.0 rating
- [ ] **Permission grant rate**: >80% of users grant permissions

## Conclusion

This improvement plan addresses the critical areas identified in the analysis while maintaining the existing strengths of the DailyNotification plugin. The phased approach ensures that the most impactful improvements are implemented first, providing immediate value while building toward a robust, production-ready notification system.

The improvements focus on:
- **Reliability**: Ensuring notifications fire at the right time, every time
- **User Experience**: Providing intuitive controls and graceful error handling
- **Performance**: Minimizing battery and network impact
- **Maintainability**: Building a robust, testable system

By implementing these improvements, the DailyNotification plugin will become a production-ready, enterprise-grade notification system that provides reliable, efficient, and user-friendly notifications across all supported platforms.
