You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

41 KiB

Daily Notification Plugin Integration Plan

Author: Matthew Raymer
Date: 2025-11-03
Status: 🎯 PLANNING - Feature planning phase
Feature: Daily Notification Plugin Integration
Platform Scope: Capacitor-only (Android/iOS)


Executive Summary

This plan outlines the integration of @timesafari/daily-notification-plugin into the TimeSafari application using the PlatformService interface pattern. The feature is implemented on all platforms via PlatformService, but only Capacitor platforms provide full functionality. Web and Electron platforms return null for unsupported operations.

Key Requirements

  • Platform: All platforms (Capacitor provides full functionality, Web/Electron return null)
  • Architecture: PlatformService interface integration (all platforms implement, unsupported return null)
  • Components: AccountViewView integration (settings UI) with optional supporting components
  • Store: No store needed - state managed locally in AccountViewView

Complexity Assessment

Technical Complexity: Medium

Code Changes

  • Medium: AccountViewView modification, optional supporting components
  • Pattern: Following PlatformService interface pattern (like camera, filesystem methods) - all platforms implement, unsupported return null
  • Integration: Plugin API integration with error handling
  • UI Integration: AccountViewView modification with new notification section and optional supporting components

Platform Impact

  • Single Platform: Capacitor-only (Android/iOS)
  • Conditional Loading: Feature only loads on Capacitor platforms
  • Graceful Degradation: Web/Electron builds should not break when plugin unavailable

Testing Requirements

  • Comprehensive:
    • Plugin availability detection
    • Permission request flows
    • Notification scheduling
    • Status checking
    • Cross-platform validation (ensure web/electron unaffected)
    • AccountViewView UI integration

Dependency Complexity

Internal Dependencies

  • Medium:
    • Optional supporting components (only if AccountViewView exceeds length limits)
    • Logger integration (replace console.* with project logger)
    • AccountViewView modifications
    • Settings schema updates

External Dependencies

  • Medium:
    • @timesafari/daily-notification-plugin (external package)
    • @capacitor/core (already in project)
    • Capacitor core APIs
    • Platform detection utilities

Infrastructure Dependencies

  • Low:
    • Package.json update (add plugin dependency)
    • Vite conditional imports for Capacitor builds only
    • No infrastructure changes required

Risk Factors

  1. Plugin Availability: Plugin may not be available in package registry

    • Mitigation: Verify package availability, consider local development setup
  2. Platform Implementation: All platforms must implement interface methods

    • Mitigation: Follow PlatformService pattern - Capacitor provides full implementation, Web/Electron return null or throw errors
  3. Web/Electron Compatibility: Feature must not break non-Capacitor builds

    • Mitigation: Use dynamic imports with platform checks, graceful fallbacks
  4. Store State Management: Notification state persistence

    • Mitigation: State managed locally in AccountViewView - no store needed

Platform Analysis

Target Platform: Capacitor Only

Capacitor Requirements

  • Android: API 21+ (already supported)
  • iOS: 13+ (already supported)
  • Native platform detection: Capacitor.isNativePlatform()

Build Configuration

  • Vite Config: vite.config.capacitor.mts (already exists)
  • Build Command: npm run build:capacitor
  • Conditional Import Pattern: Dynamic import based on process.env.VITE_PLATFORM === 'capacitor'

Platform Detection Strategy

Pattern: PlatformService interface - all platforms implement methods

CRITICAL REQUIREMENT: All notification scheduling components MUST hide themselves if the current device does not support scheduling.

Components check PlatformService capabilities by calling methods and checking for null returns:

// Components check capability via PlatformService
const platformService = PlatformServiceFactory.getInstance();
const status = await platformService.getDailyNotificationStatus();

if (status === null) {
  // Notifications not supported on this platform - hide UI
  return;
}
// Continue with notification features

Why PlatformService Pattern?

  • Consistent with existing platform capabilities (camera, filesystem)
  • All platforms implement the interface (contract compliance)
  • Unsupported platforms return null or throw clear errors
  • Components handle capability detection via method results, not environment variables

Component Visibility Requirements:

  • AccountViewView notification section: Must use v-if="notificationsSupported" to hide section
  • Supporting components (if created): Must check platform support before rendering
  • Any component providing scheduling UI: Must verify getDailyNotificationStatus() !== null before showing scheduling controls

Web/Electron Implementation Strategy

Web Platform

  • Implementation: All notification methods implemented in WebPlatformService
  • Behavior: Methods return null for status/permissions, throw errors for scheduling
  • UI: Components check for null responses to hide notification UI
  • Plugin Import: No plugin imports - methods return null/throw errors directly

Electron Platform

  • Implementation: All notification methods implemented in ElectronPlatformService
  • Behavior: Methods return null for status/permissions, throw errors for scheduling
  • UI: Components check for null responses to hide notification UI
  • Plugin Import: No plugin imports - methods return null/throw errors directly

Architecture Design

PlatformService Integration

Key Pattern: Add notification methods directly to PlatformService interface, implemented on all platforms. Unsupported platforms return null or empty results.

This follows the same pattern as other platform capabilities (camera, filesystem) where all platforms implement the interface, but unsupported platforms return null/empty results.

// src/services/PlatformService.ts - Add to interface
export interface PlatformService {
  // ... existing methods ...

  // Daily notification operations
  /**
   * Get the status of scheduled daily notifications
   * @returns Promise resolving to notification status, or null if not supported
   */
  getDailyNotificationStatus(): Promise<NotificationStatus | null>;

  /**
   * Check notification permissions
   * @returns Promise resolving to permission status, or null if not supported
   */
  checkNotificationPermissions(): Promise<PermissionStatus | null>;

  /**
   * Request notification permissions
   * @returns Promise resolving to permission result, or null if not supported
   */
  requestNotificationPermissions(): Promise<PermissionResult | null>;

  /**
   * Schedule a daily notification
   * @param options - Notification scheduling options
   * @returns Promise that resolves when scheduled, or rejects if not supported
   */
  scheduleDailyNotification(options: ScheduleOptions): Promise<void>;

  /**
   * Cancel scheduled daily notification
   * @returns Promise that resolves when cancelled, or rejects if not supported
   */
  cancelDailyNotification(): Promise<void>;

  /**
   * Configure native fetcher for background operations
   * @param config - Native fetcher configuration
   * @returns Promise that resolves when configured, or null if not supported
   */
  configureNativeFetcher(config: NativeFetcherConfig): Promise<void | null>;

  /**
   * Update starred plans for background fetcher
   * @param plans - Starred plan IDs
   * @returns Promise that resolves when updated, or null if not supported
   */
  updateStarredPlans(plans: { planIds: string[] }): Promise<void | null>;
}

Implementation Pattern:

  • CapacitorPlatformService: Full implementation using @timesafari/daily-notification-plugin
  • WebPlatformService: Returns null for status/permissions, throws errors for scheduling operations
  • ElectronPlatformService: Returns null for status/permissions, throws errors for scheduling operations

PlatformService Interface Extensions

// Types/interfaces for notification operations
export interface NotificationStatus {
  isScheduled: boolean;
  scheduledTime?: string; // "HH:mm" format
  lastTriggered?: string;
  permissions: PermissionStatus;
}

export interface PermissionStatus {
  notifications: 'granted' | 'denied' | 'prompt';
  exactAlarms?: 'granted' | 'denied' | 'prompt'; // Android only
}

export interface PermissionResult {
  notifications: boolean;
  exactAlarms?: boolean; // Android only
}

export interface ScheduleOptions {
  time: string; // "HH:mm" format in local time
  title: string;
  body: string;
  sound?: boolean;
  priority?: 'high' | 'normal' | 'low';
}

export interface NativeFetcherConfig {
  apiServer: string;
  jwt: string; // Ignored - generated automatically by configureNativeFetcher
  starredPlanHandleIds: string[];
}

Implementation Behavior:

  • Capacitor: Full implementation, all methods functional
  • Web/Electron: Status/permission methods return null, scheduling methods throw errors with clear messages

Authentication & Token Management

Background Prefetch Authentication

The daily notification plugin requires authentication tokens for background prefetch operations. The implementation uses a hybrid token refresh strategy that balances security with offline capability.

Token Generation (src/libs/crypto/index.ts):

  • Function: accessTokenForBackground(did, expirationMinutes?)
  • Default expiration: 72 hours (4320 minutes)
  • Token type: JWT with ES256K signing
  • Payload: { exp, iat, iss: did }

Why 72 Hours?

  • Balances security (read-only prefetch operations) with offline capability
  • Reduces need for app to wake itself for token refresh
  • Allows plugin to work offline for extended periods (e.g., weekend trips)
  • Longer than typical prefetch windows (5 minutes before notification)

Token Refresh Strategy (Hybrid Approach):

  1. Proactive Refresh Triggers:

    • Component mount (DailyNotificationSection.mounted())
    • App resume (Capacitor resume event)
    • Notification enabled (when user enables daily notifications)
  2. Refresh Implementation (DailyNotificationSection.refreshNativeFetcherConfig()):

    • Checks if notifications are supported and enabled
    • Retrieves API server URL from settings
    • Retrieves starred plans from settings
    • Calls configureNativeFetcher() to generate fresh token
    • Errors are logged but don't interrupt user experience
  3. Offline Behavior:

    • If token expires while offline → plugin uses cached content
    • Next time app opens → token automatically refreshed
    • No app wake-up required (refresh happens when app is already open)

Configuration Flow (CapacitorPlatformService.configureNativeFetcher()):

  1. Retrieves active DID from active_identity table (single source of truth)
  2. Generates JWT token with 72-hour expiration using accessTokenForBackground()
  3. Configures plugin with API server URL, active DID, and JWT token
  4. Plugin stores token in its Room database for background workers

Security Considerations:

  • Tokens are used only for read-only prefetch operations
  • Tokens are stored securely in plugin's Room database
  • Tokens are refreshed proactively to minimize exposure window
  • No private keys are exposed to native code
  • Token generation happens in TypeScript (no Java crypto compatibility issues)

Error Handling:

  • Returns null if active DID not found (no user logged in)
  • Returns null if JWT generation fails
  • Logs errors but doesn't throw (allows graceful degradation)
  • Refresh failures don't interrupt user experience (plugin uses cached content)

Component Architecture

Views Structure

src/views/
  └── AccountViewView.vue (existing - add DailyNotificationSection component)

Supporting Components

src/components/notifications/
  └── DailyNotificationSection.vue (required - extracted section component)

Component Structure: DailyNotificationSection.vue will use vue-facing-decorator with ES6 classes

<template>
  <section
    v-if="notificationsSupported"
    id="sectionDailyNotifications"
    class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
    aria-labelledby="dailyNotificationsHeading"
  >
    <!-- Daily Notifications UI -->
  </section>
</template>

<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { logger } from "@/utils/logger";

/**
 * DailyNotificationSection Component
 *
 * A self-contained component for managing daily notification scheduling
 * in AccountViewView. This component handles platform detection, permission
 * requests, scheduling, and state management for daily notifications.
 *
 * Features:
 * - Platform capability detection (hides on unsupported platforms)
 * - Permission request flow
 * - Schedule/cancel notifications
 * - Time editing with HTML5 time input
 * - Settings persistence
 * - Plugin state synchronization
 */
@Component({
  name: "DailyNotificationSection",
  mixins: [PlatformServiceMixin],
})
export default class DailyNotificationSection extends Vue {
  // Component implementation here
}
</script>

AccountViewView Integration Strategy

Overview

Integrate daily notification scheduling into AccountViewView.vue, allowing users to configure notification times directly from their account settings.

Integration Approach ACCEPTED

Decision: Create a separate "Daily Notifications" section

This approach adds a dedicated "Daily Notifications" section that checks PlatformService capabilities. On Capacitor platforms, it provides full functionality. On other platforms, the UI is hidden when PlatformService returns null for notification methods.

Key Benefits:

  • Uses PlatformService interface pattern (consistent with camera, filesystem)
  • Platform-specific features properly isolated
  • Can use native time picker (better UX on mobile)
  • Future-proof: Easy to extend with additional notification features
  • Graceful degradation on unsupported platforms

UI Component Design

1. Platform Capability Detection

// In AccountViewView component
async checkNotificationSupport(): Promise<boolean> {
  const platformService = PlatformServiceFactory.getInstance();
  const status = await platformService.getDailyNotificationStatus();
  return status !== null; // null means not supported
}

2. State Management

// Component properties
nativeNotificationEnabled: boolean = false;
nativeNotificationTime: string = ""; // Display format: "9:00 AM"
nativeNotificationTimeStorage: string = ""; // Plugin format: "09:00"
nativeNotificationTitle: string = "Daily Update";
nativeNotificationMessage: string = "Your daily notification is ready!";
notificationsSupported: boolean = false; // Computed from PlatformService

3. Template Section

<!-- New Daily Notifications section -->
<section
  v-if="notificationsSupported"
  id="sectionDailyNotifications"
  class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
  aria-labelledby="dailyNotificationsHeading"
>
  <h2 id="dailyNotificationsHeading" class="mb-2 font-bold">
    Daily Notifications
    <button
      class="text-slate-400 fa-fw cursor-pointer"
      aria-label="Learn more about native notifications"
      @click.stop="showNativeNotificationInfo"
    >
      <font-awesome icon="question-circle" aria-hidden="true" />
    </button>
  </h2>
  
  <div class="flex items-center justify-between">
    <div>Daily Notification</div>
    <!-- Toggle switch -->
    <div
      class="relative ml-2 cursor-pointer"
      role="switch"
      :aria-checked="nativeNotificationEnabled"
      @click="toggleNativeNotification()"
    >
      <!-- Custom toggle UI -->
    </div>
  </div>
  
  <!-- Show current time when enabled -->
  <div v-if="nativeNotificationEnabled" class="mt-2">
    <div class="flex items-center justify-between">
      <span>Scheduled for: {{ nativeNotificationTime }}</span>
      <button
        class="text-blue-500 text-sm"
        @click="editNativeNotificationTime()"
      >
        Edit Time
      </button>
    </div>
  </div>
</section>

4. Time Input SELECTED: HTML5 Time Input

Decision: Use HTML5 <input type="time"> for native mobile experience

<input
  type="time"
  v-model="nativeNotificationTimeStorage"
  class="rounded border border-slate-400 px-2 py-2"
/>

Benefits:

  • Native mobile time picker UI on Capacitor platforms
  • Simpler implementation (no custom time parsing needed)
  • Automatic 24-hour format output (compatible with plugin)
  • System handles locale-specific time formatting
  • Better UX on mobile devices

Note: HTML5 time input provides time in "HH:mm" format (24-hour) which matches the plugin's expected format perfectly.

5. Time Format Conversion (Using System Time)

Key Principle: Use device's local system time - no timezone conversions needed. The plugin handles system time natively.

// Convert "09:00" (plugin storage format) to "9:00 AM" (display)
function formatTimeForDisplay(time24: string): string {
  const [hours, minutes] = time24.split(':');
  const hourNum = parseInt(hours);
  const isPM = hourNum >= 12;
  const displayHour = hourNum === 0 ? 12 : hourNum > 12 ? hourNum - 12 : hourNum;
  return `${displayHour}:${minutes} ${isPM ? 'PM' : 'AM'}`;
}

// HTML5 time input provides "HH:mm" in local time - use directly
// No UTC conversion needed - plugin handles local timezone
function getTimeFromInput(timeInput: string): string {
  // timeInput is already in "HH:mm" format from <input type="time">
  // This is in the user's local timezone - pass directly to plugin
  return timeInput; // e.g., "09:00" in user's local time
}

Time Handling:

  • PlatformService Integration: Uses device's local system time directly - NO UTC conversion needed. The plugin schedules notifications on the device itself, using the device's timezone.

Implementation Principles:

  • HTML5 <input type="time"> provides time in device's local timezone
  • Plugin receives time in "HH:mm" format and schedules relative to device's local time
  • No manual timezone conversion or UTC calculations needed
  • System automatically handles:
    • Timezone changes
    • Daylight saving time transitions
    • Device timezone updates
  • User sets "9:00 AM" in their local time → plugin schedules for 9:00 AM local time every day

Data Flow

1. Initialization (Sync with Plugin State)

async initializeState() {
  // ... existing initialization ...
  
  const platformService = PlatformServiceFactory.getInstance();
  
  // Check if notifications are supported on this platform
  const status = await platformService.getDailyNotificationStatus();
  if (status === null) {
    // Notifications not supported - don't initialize
    this.notificationsSupported = false;
    return;
  }
  
  this.notificationsSupported = true;
  
  // CRITICAL: Sync with plugin state first (source of truth)
  // Plugin may have an existing schedule even if settings don't
  if (status.isScheduled && status.scheduledTime) {
    // Plugin has a scheduled notification - sync UI to match
    this.nativeNotificationEnabled = true;
    this.nativeNotificationTimeStorage = status.scheduledTime;
    this.nativeNotificationTime = formatTimeForDisplay(status.scheduledTime);
    
    // Also sync settings to match plugin state
    const settings = await this.$accountSettings();
    if (settings.nativeNotificationTime !== status.scheduledTime) {
      await this.$saveSettings({
        nativeNotificationTime: status.scheduledTime,
        nativeNotificationTitle: settings.nativeNotificationTitle || this.nativeNotificationTitle,
        nativeNotificationMessage: settings.nativeNotificationMessage || this.nativeNotificationMessage,
      });
    }
  } else {
    // No plugin schedule - check settings for user preference
    const settings = await this.$accountSettings();
    const nativeNotificationTime = settings.nativeNotificationTime || "";
    this.nativeNotificationEnabled = !!nativeNotificationTime;
    this.nativeNotificationTimeStorage = nativeNotificationTime;
    
    if (nativeNotificationTime) {
      this.nativeNotificationTime = formatTimeForDisplay(nativeNotificationTime);
    }
  }
  
  // Update UI with current status
  this.notificationStatus = status;
}

Key Points:

  • getDailyNotificationStatus() is called on mount to check for pre-existing schedules
  • Plugin state is the source of truth - if plugin has a schedule, UI syncs to match
  • Settings are synced with plugin state if they differ
  • If no plugin schedule exists, fall back to settings

2. Enable Notification

async enableNativeNotification() {
  try {
    const platformService = PlatformServiceFactory.getInstance();
    
    // 1. Request permissions if needed
    const permissions = await platformService.checkNotificationPermissions();
    if (permissions === null || permissions.notifications !== 'granted') {
      const result = await platformService.requestNotificationPermissions();
      if (result === null || !result.notifications) {
        throw new Error("Notification permissions denied");
      }
    }
    
    // 2. Schedule notification via PlatformService
    // Time is in device's local system time (from HTML5 time input)
    // PlatformService handles timezone and scheduling internally
    await platformService.scheduleDailyNotification({
      time: this.nativeNotificationTimeStorage, // "09:00" in local time
      title: this.nativeNotificationTitle,
      body: this.nativeNotificationMessage,
      sound: true,
      priority: 'high'
    });
    
    // 3. Save to settings
    await this.$saveSettings({
      nativeNotificationTime: this.nativeNotificationTimeStorage,
      nativeNotificationTitle: this.nativeNotificationTitle,
      nativeNotificationMessage: this.nativeNotificationMessage,
    });
    
    // 4. Update UI state
    this.nativeNotificationEnabled = true;
    
    this.notify.success("Daily notification scheduled successfully", TIMEOUTS.SHORT);
  } catch (error) {
    logger.error("Failed to enable notification:", error);
    this.notify.error("Failed to schedule notification. Please try again.", TIMEOUTS.LONG);
  }
}

3. Disable Notification

async disableNativeNotification() {
  try {
    const platformService = PlatformServiceFactory.getInstance();
    
    // 1. Cancel notification via PlatformService
    await platformService.cancelDailyNotification();
    
    // 2. Clear settings
    await this.$saveSettings({
      nativeNotificationTime: "",
      nativeNotificationTitle: "",
      nativeNotificationMessage: "",
    });
    
    // 3. Update UI state
    this.nativeNotificationEnabled = false;
    this.nativeNotificationTime = "";
    this.nativeNotificationTimeStorage = "";
    
    this.notify.success("Daily notification disabled", TIMEOUTS.SHORT);
  } catch (error) {
    logger.error("Failed to disable native notification:", error);
    this.notify.error("Failed to disable notification. Please try again.", TIMEOUTS.LONG);
  }
}

4. Edit Time (Update Schedule)

Approach: When time changes, immediately update the scheduled notification

async editNativeNotificationTime() {
  // Show inline HTML5 time input for quick changes
  this.showTimeEdit = true;
}

async updateNotificationTime(newTime: string) {
  // newTime is in "HH:mm" format from HTML5 time input
  if (!this.nativeNotificationEnabled) {
    // If notification is disabled, just save the time preference
    this.nativeNotificationTimeStorage = newTime;
    this.nativeNotificationTime = formatTimeForDisplay(newTime);
    await this.$saveSettings({
      nativeNotificationTime: newTime,
    });
    return;
  }
  
  // Notification is enabled - update the schedule
  try {
    const platformService = PlatformServiceFactory.getInstance();
    
    // 1. Cancel existing notification
    await platformService.cancelDailyNotification();
    
    // 2. Schedule with new time
    await platformService.scheduleDailyNotification({
      time: newTime, // "09:00" in local time
      title: this.nativeNotificationTitle,
      body: this.nativeNotificationMessage,
      sound: true,
      priority: 'high'
    });
    
    // 3. Update local state
    this.nativeNotificationTimeStorage = newTime;
    this.nativeNotificationTime = formatTimeForDisplay(newTime);
    
    // 4. Save to settings
    await this.$saveSettings({
      nativeNotificationTime: newTime,
    });
    
    this.notify.success("Notification time updated successfully", TIMEOUTS.SHORT);
    this.showTimeEdit = false;
  } catch (error) {
    logger.error("Failed to update notification time:", error);
    this.notify.error("Failed to update notification time. Please try again.", TIMEOUTS.LONG);
  }
}

Implementation Note: HTML5 time input provides native mobile picker experience when shown inline, making it ideal for quick time adjustments. When the time changes, the notification schedule is immediately updated via PlatformService.

Settings Schema

New Settings Fields

// Add to Settings interface in src/db/tables/settings.ts
interface Settings {
  // ... existing fields ...
  
  // Native notification settings (Capacitor only)
  nativeNotificationTime?: string; // "09:00" format (24-hour)
  nativeNotificationTitle?: string; // Default: "Daily Update"
  nativeNotificationMessage?: string; // Default message
}

Settings Persistence

  • Store in settings table via $saveSettings()
  • Use same pattern as notifyingNewActivityTime
  • Persist across app restarts
  • Sync with plugin state on component mount

UI/UX Considerations

Visual Design

  • Section Style: Match existing notification section (bg-slate-100 rounded-md)
  • Toggle Switch: Reuse existing custom toggle pattern
  • Time Display: Show in user-friendly format ("9:00 AM")
  • Edit Button: Small, subtle link/button to edit time

User Feedback

  • Success: Toast notification when scheduled successfully
  • Error: Clear error message with troubleshooting guidance
  • Loading: Show loading state during plugin operations
  • Permission Request: Handle gracefully if denied

Accessibility

  • ARIA Labels: Proper labels for all interactive elements
  • Keyboard Navigation: Full keyboard support
  • Screen Reader: Clear announcements for state changes

Implementation Decisions

Time Input Format

  • Selected: HTML5 <input type="time"> for Capacitor platforms
  • Rationale: Native mobile experience, simpler code, automatic 24-hour format

Edit Approach

  • Selected: Inline HTML5 time input for quick edits in AccountViewView
  • Note: All editing happens within AccountViewView - no separate views needed

Settings Field Names

  • Selected: nativeNotificationTime, nativeNotificationTitle, nativeNotificationMessage
  • Rationale: Clear distinction from web push notification fields

Notification Title/Message

  • Selected: Allow customization, default to "Daily Update" / "Your daily notification is ready!"
  • Rationale: Flexibility for users, sensible defaults

Phase Breakdown

Phase 1: Foundation & Infrastructure

Complexity: Low-Medium
Goals: Set up factory architecture, store, and conditional loading

Tasks

  1. Package Dependency

    • Add @timesafari/daily-notification-plugin to package.json
    • Verify package availability/version
    • Document in dependencies section
  2. PlatformService Interface Extension

    • Add notification methods to PlatformService interface
    • Define notification types/interfaces (NotificationStatus, ScheduleOptions, etc.)
    • Implement in CapacitorPlatformService using @timesafari/daily-notification-plugin
    • Implement in WebPlatformService with null returns / error throws
    • Implement in ElectronPlatformService with null returns / error throws
  3. Settings Schema Extension

    • Add notification settings fields to Settings interface
    • Update settings persistence methods if needed

Acceptance Criteria

  • PlatformService interface extended with notification methods
  • CapacitorPlatformService implements notification methods using plugin
  • WebPlatformService and ElectronPlatformService return null/throw errors appropriately
  • Settings schema extended with notification fields
  • No build errors in web/electron builds

Phase 2: AccountViewView Integration

Complexity: Medium
Goals: Integrate notification scheduling into AccountViewView with optional supporting components

Tasks

  1. DailyNotificationSection Component

    • Create src/components/notifications/DailyNotificationSection.vue
    • Use vue-facing-decorator with ES6 class extending Vue
    • Add PlatformServiceMixin to component
    • Implement platform capability detection on mount
    • Implement initialization that syncs with plugin state (checks for pre-existing schedules)
    • Add toggle switch for enabling/disabling notifications
    • Add HTML5 time input for scheduling time
    • Integrate with PlatformService via PlatformServiceFactory
    • Implement time format conversion (display vs storage)
    • Add enable/disable notification methods
    • Add edit time functionality with schedule update (cancel old, schedule new)
    • Add permission request flow
    • Add error handling and user feedback
    • Save/load settings from settings table
    • Follow project styling patterns
    • Add TypeScript interfaces
    • Add file-level documentation
  2. AccountViewView Integration

    • Import DailyNotificationSection component
    • Add component to template (minimal integration)
    • Verify component renders correctly
    • Test component hiding on unsupported platforms

Acceptance Criteria

  • DailyNotificationSection component created using vue-facing-decorator
  • Component extends Vue class with PlatformServiceMixin
  • Component checks platform support on mount via getDailyNotificationStatus()
  • Component syncs with plugin state on initialization (checks for pre-existing schedules)
  • Component hidden on unsupported platforms (v-if="notificationsSupported")
  • Toggle and time input functional
  • Time changes update notification schedule immediately (cancel old, schedule new)
  • Settings persist across app restarts
  • Plugin state syncs with settings on mount
  • All logging uses project logger
  • Error handling implemented
  • Loading states visible
  • UI matches existing design patterns
  • AccountViewView integration is minimal (just imports and uses component)

Phase 3: Polish & Testing

Complexity: Medium
Goals: Complete AccountViewView integration, error handling, and testing

Tasks

  1. Permission Management

    • Implement permission request flow in AccountViewView
    • Handle permission denial gracefully
    • Update status after permission changes
    • Show appropriate user feedback
  2. Error Handling & User Feedback

    • Add comprehensive error handling for all plugin operations
    • Implement loading states during async operations
    • Add success/error toast notifications
    • Handle edge cases (permission denied, plugin unavailable, etc.)
  3. Testing & Validation

    • Test AccountViewView integration on Capacitor platforms
    • Verify component hiding on Web/Electron
    • Test all user workflows (enable, disable, edit time)
    • Verify settings persistence

Acceptance Criteria

  • Permission requests handled properly
  • Status updates after permission changes
  • Error handling for all failure cases
  • User feedback (toasts, loading states) implemented
  • AccountViewView tested on all platforms
  • Component hiding verified on unsupported platforms


Milestones

Milestone 1: Foundation Complete

Success Criteria:

  • PlatformService interface extended
  • Settings schema extended
  • No build regressions

Milestone 2: AccountViewView Integration Complete

Success Criteria:

  • AccountViewView notification section functional
  • Plugin integration working
  • Settings persistence working
  • Component hiding verified on unsupported platforms

Milestone 3: Production Ready

Success Criteria:

  • All tests passing
  • Cross-platform validation complete
  • Error handling robust
  • User feedback implemented
  • Documentation complete

Testing Strategy

Unit Tests

  • PlatformService platform detection
  • AccountViewView notification section rendering
  • Supporting component rendering (if created)

Integration Tests

  • Plugin API calls from AccountViewView
  • Permission flows
  • Status updates
  • AccountViewView enable/disable/edit workflows
  • Settings persistence

Platform Tests

  • Capacitor Android: Notification scheduling, permissions, status, AccountViewView UI
  • Capacitor iOS: Notification scheduling, permissions, status, AccountViewView UI
  • Web: Feature hidden, no errors, AccountViewView section hidden
  • Electron: Feature hidden, no errors, AccountViewView section hidden

E2E Tests (Playwright)

  • AccountViewView notification configuration workflow
  • Permission request flow
  • Enable/disable notification workflow
  • Edit notification time workflow
  • Error handling scenarios

Dependencies

External Dependencies

  • @timesafari/daily-notification-plugin (to be added)
  • @capacitor/core (already in project)
  • vue (already in project)

Internal Dependencies

  • Logger service (@/utils/logger)
  • Platform detection utilities
  • Existing component patterns
  • AccountViewView component
  • Settings schema and persistence

Configuration Dependencies

  • Settings Access: Use $accountSettings() and $saveSettings() for persistence (existing)

Implementation Notes

Component Visibility Requirements

CRITICAL: All components that provide notification scheduling UI MUST hide themselves if the current device does not support scheduling.

Required Pattern for All Scheduling Components

// In component mounted/created lifecycle
async mounted() {
  const platformService = PlatformServiceFactory.getInstance();
  const status = await platformService.getDailyNotificationStatus();
  
  if (status === null) {
    // Device does not support scheduling - hide component
    this.notificationsSupported = false;
    return;
  }
  
  // Device supports scheduling - proceed with initialization
  this.notificationsSupported = true;
  // ... rest of initialization
}

Template Pattern

<!-- Option 1: Conditional rendering with v-if -->
<section v-if="notificationsSupported">
  <!-- Scheduling UI -->
</section>

<!-- Option 2: Early return in component logic -->
<template>
  <div v-if="notificationsSupported">
    <!-- Scheduling UI -->
  </div>
  <div v-else>
    <!-- Optional: Show unsupported message -->
    <p>Notifications are not supported on this platform.</p>
  </div>
</template>

Components That Must Implement This Pattern

  1. DailyNotificationSection.vue: Daily Notifications section uses v-if="notificationsSupported" and checks getDailyNotificationStatus() on mount
  2. Any component providing scheduling UI: Must verify getDailyNotificationStatus() !== null before showing scheduling controls

Verification Checklist

  • DailyNotificationSection checks platform support on mount and hides on unsupported platforms
  • DailyNotificationSection syncs with plugin state on initialization (checks for pre-existing schedules)
  • Component tested on Web/Electron to verify hiding works
  • No console errors when components are hidden
  • Time changes properly update notification schedule

Code Quality Standards

  • Logging: Use logger from @/utils/logger, not console.*
  • File Documentation: Add file-level documentation headers
  • Method Documentation: Rich method-level documentation
  • Type Safety: Full TypeScript typing
  • PEP8/Prettier: Follow code style guidelines
  • Line Length: Keep methods < 80 columns when possible

Architecture Patterns to Follow

  • Service Interface: Abstract interface with platform implementations
  • Component Organization: Keep AccountViewView concise - extract supporting components if needed to maintain < 200 lines
  • PlatformService Pattern: Check capabilities via method results, not environment variables

PlatformService Integration Strategy

Pattern: Direct integration into PlatformService interface (like camera, filesystem methods)

// In components - use PlatformServiceFactory pattern
import { PlatformServiceFactory } from '@/services/PlatformServiceFactory';

const platformService = PlatformServiceFactory.getInstance();

// Check if notifications are supported
const status = await platformService.getDailyNotificationStatus();
if (status === null) {
  // Notifications not supported on this platform - hide UI
  return;
}

// Schedule notification
await platformService.scheduleDailyNotification({
  time: "09:00",
  title: "Daily Update",
  body: "Your daily notification is ready!",
});

Key Points:

  • Methods available on all PlatformService implementations
  • CapacitorPlatformService provides full implementation
  • WebPlatformService/ElectronPlatformService return null or throw errors
  • Components check for null responses to hide/show UI appropriately
  • No separate factory needed - uses existing PlatformServiceFactory pattern

Risk Mitigation

Risk 1: Plugin Package Unavailable

Mitigation:

  • Verify package exists and is accessible
  • Consider local development setup if needed
  • Document package installation requirements

Risk 2: Platform Detection Failures

Mitigation:

  • Use proven patterns from QRScannerFactory
  • Test on all platforms
  • Add fallback logic

Risk 3: Web/Electron Build Breaks

Mitigation:

  • Use dynamic imports exclusively
  • Test web/electron builds after each phase
  • Ensure no static plugin imports
  • Verify AccountViewView section properly hidden

Risk 4: AccountViewView Integration Issues

Mitigation:

  • Use platform capability detection before showing UI
  • Test on all platforms to ensure proper hiding
  • Follow existing UI patterns for consistency
  • Add comprehensive error handling

Risk 5: Components Visible on Unsupported Platforms

Mitigation:

  • REQUIRED: All scheduling components must check getDailyNotificationStatus() and hide if null
  • Use v-if="notificationsSupported" pattern consistently
  • Add explicit verification in acceptance criteria
  • Test on Web/Electron builds to verify hiding works
  • Document required pattern in Implementation Notes section

Success Criteria Summary

  • Plugin integrated using PlatformService architecture
  • Feature works on Capacitor (Android/iOS)
  • Feature hidden/graceful on Web/Electron
  • DailyNotificationSection component created and functional
  • DailyNotificationSection hides itself on unsupported platforms
  • Component syncs with plugin state on mount (checks for pre-existing schedules)
  • Time changes update notification schedule immediately
  • AccountViewView integration minimal (just imports component)
  • Settings persist across app restarts
  • Logging standardized (no console.*)
  • Error handling robust
  • Cross-platform testing complete
  • Verified component hiding on Web/Electron platforms
  • Documentation updated
  • No build regressions

Next Steps

  • Verify Plugin Package: Confirm @timesafari/daily-notification-plugin availability
  • Extend PlatformService: Add notification methods to PlatformService interface and implement in all platform services
  • Extend Settings Schema: Add notification fields to Settings interface
  • Begin Phase 1 Implementation: Start with foundation tasks
  • AccountViewView Integration: Implement Daily Notifications section in Phase 2

See also:

  • .cursor/rules/meta_feature_planning.mdc - Feature planning workflow
  • .cursor/rules/app/architectural_patterns.mdc - Architecture patterns
  • .cursor/rules/app/timesafari_platforms.mdc - Platform requirements
  • src/services/QRScanner/QRScannerFactory.ts - Factory pattern reference
  • src/views/AccountViewView.vue - Target component for integration