Files
daily-notification-plugin/docs/progress/P3-EXECUTION-CHECKLIST-MECHANICAL.md
Matthew Raymer aea2a7f39d docs: Add ultra-mechanical P3 execution checklist
Created ultra-mechanical checklist with:
- Exact file paths and line numbers
- Exact search strings to find locations
- Exact code snippets with insertion points
- Before/after examples
- Import checks
- Verification steps

Ready for Cursor execution with minimal ambiguity.
2025-12-23 06:21:19 +00:00

30 KiB

P3 EXECUTION CHECKLIST — Mechanical (Cursor-Ready)

Purpose: Ultra-mechanical, file-by-file, function-by-function execution plan with exact search strings and insertion points.
Owner: Development Team
Last Updated: 2025-12-22
Status: execution-ready
Baseline: v1.0.11-p2.3-p1.5b-complete


0) P3 Invariants to Preserve (DO NOT VIOLATE)

Hard / enforced (policy-as-code):

  • CI authority: All gates must run ./ci/run.sh (never bypass with npm run build in gates)
  • Packaging invariants: npm pack --dry-run must not include forbidden files (enforced in scripts/verify.sh)
  • Export correctness: package.json.exports must match build artifacts (enforced in scripts/verify.sh)
  • Core purity: src/core/ must not import platform deps or Node builtins (enforced in scripts/verify.sh)

Process / repo discipline:

  • Docs structure: Index-first rule; progress docs are authoritative; archive goes under docs/_archive/
  • Baseline integrity: Keep baseline tags valid; don't "fix forward" by weakening checks

P3 scope constraints:

  • No new features, no architectural changes, no breaking API changes, no new platforms, no new dependencies

P3 Preflight (MUST DO BEFORE EACH BATCH)

  • Run ./ci/run.shSTOP IF FAILS
  • If changes touch docs: ensure drift-guard headers remain intact (Purpose/Owner/Last Updated/Status/Baseline Tag)
  • Update progress docs only after batch is green:
    • docs/progress/00-STATUS.md
    • docs/progress/01-CHANGELOG-WORK.md
    • docs/progress/03-TEST-RUNS.md
    • docs/progress/04-PARITY-MATRIX.md (only if parity items changed)

P3.1 — Performance Optimization & Metrics

Batch P3.1-A — Define Metrics Contract (Core-Only, Zero Behavior Change)

File: src/core/metrics.ts (NEW FILE)

Action: Create new file with exact content:

/**
 * Core Metrics
 * 
 * Performance metrics contract and lightweight collector.
 * Platform-agnostic, no dependencies.
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

/**
 * Performance metric entry
 */
export interface PerformanceMetric {
  /** Operation name (e.g., 'schedule.create', 'recovery.coldStart') */
  operation: string;
  /** Duration in milliseconds */
  duration: number;
  /** Timestamp when metric was recorded (milliseconds since epoch) */
  timestamp: number;
  /** Whether operation succeeded */
  success: boolean;
  /** Optional metadata */
  metadata?: Record<string, unknown>;
}

/**
 * Metrics collector interface
 */
export interface MetricsCollector {
  record(metric: PerformanceMetric): void;
  getMetrics(): PerformanceMetric[];
  clear(): void;
}

/**
 * Lightweight in-memory metrics collector
 * No dependencies, platform-agnostic
 */
export class InMemoryMetricsCollector implements MetricsCollector {
  private metrics: PerformanceMetric[] = [];
  private maxMetrics = 100;

  record(metric: PerformanceMetric): void {
    this.metrics.push(metric);
    if (this.metrics.length > this.maxMetrics) {
      this.metrics = this.metrics.slice(-this.maxMetrics);
    }
  }

  getMetrics(): PerformanceMetric[] {
    return [...this.metrics];
  }

  clear(): void {
    this.metrics = [];
  }
}

File: src/core/index.ts (UPDATE)

Search for: export * from './guards'; (should be near end of file)

Action: Add after existing exports:

export * from './metrics';

Verification:

  • npm run build succeeds
  • ./ci/run.sh passes
  • No new dependencies in package.json
  • grep -r "@capacitor\|react\|fs\|path\|os" src/core/metrics.ts returns empty

Batch P3.1-B — Instrument Critical Path: Scheduling + Delivery

File: src/web.ts (UPDATE)

Note: src/web.ts is a stub implementation (all methods call throwNotSupported()). Instrumentation here is for future-proofing. Real instrumentation happens in platform implementations.

Search for: async createSchedule(_schedule: CreateScheduleInput): Promise<Schedule> {

Current code (line ~334):

async createSchedule(_schedule: CreateScheduleInput): Promise<Schedule> {
  this.throwNotSupported();
}

Action: Add timing (even though it throws immediately):

async createSchedule(_schedule: CreateScheduleInput): Promise<Schedule> {
  const startTime = performance.now();
  try {
    this.throwNotSupported();
  } catch (error) {
    const duration = performance.now() - startTime;
    // Log timing even for unsupported operations (for consistency)
    if (this.observability) {
      this.observability.logEvent('INFO', EVENT_CODES.SCHEDULE_UPDATE, 
        'Schedule creation attempted (not supported on web)', 
        { duration, platform: 'web' });
    }
    throw error;
  }
}

Import check: Ensure EVENT_CODES is imported:

  • Search for: import.*EVENT_CODES.*from
  • If missing: Add import { EVENT_CODES } from './core/events'; near top of file

Repeat pattern for:

  • updateSchedule() (line ~338)
  • deleteSchedule() (line ~342)

Verification:

  • TypeScript compiles (npm run build)
  • ./ci/run.sh passes
  • No behavior changes (web still throws errors)

Batch P3.1-C — Instrument Recovery Path (Cold Start + Dedupe + Rollover)

File: android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt (UPDATE)

Search for: private suspend fun performColdStartRecovery(): RecoveryResult {

Current code (should be around line ~654):

private suspend fun performColdStartRecovery(): RecoveryResult {
  val db = DailyNotificationDatabase.getDatabase(context)
  val currentTime = System.currentTimeMillis()
  
  Log.i(TAG, "Cold start recovery: checking for missed notifications")
  // ... existing code ...
}

Action: Add timing at start and end:

private suspend fun performColdStartRecovery(): RecoveryResult {
  val startTime = System.currentTimeMillis()  // ADD THIS LINE
  val db = DailyNotificationDatabase.getDatabase(context)
  val currentTime = System.currentTimeMillis()
  
  Log.i(TAG, "Cold start recovery: checking for missed notifications")
  // ... existing code ...
  
  // Before return statement, add:
  val duration = System.currentTimeMillis() - startTime
  Log.i(TAG, "Cold start recovery completed: duration=${duration}ms, missed=$missedCount, rescheduled=$rescheduledCount, errors=$errors")
  
  return RecoveryResult(missedCount, rescheduledCount, verifiedCount, errors)
}

Search for: private suspend fun performForceStopRecovery(): RecoveryResult {

Action: Same pattern — add startTime at start, log duration before return

File: ios/Plugin/DailyNotificationReactivationManager.swift (UPDATE)

Search for: func performColdStartRecovery() async throws -> RecoveryResult {

Action: Add timing:

func performColdStartRecovery() async throws -> RecoveryResult {
  let startTime = Date()  // ADD THIS LINE
  // ... existing code ...
  
  // Before return, add:
  let duration = Date().timeIntervalSince(startTime) * 1000 // ms
  os_log("Cold start recovery completed: duration=%.0fms, missed=%d, rescheduled=%d", 
         log: .default, type: .info, duration, missedCount, rescheduledCount)
  
  return RecoveryResult(missedCount: missedCount, rescheduledCount: rescheduledCount, errors: errors)
}

Verification:

  • Android builds (cd test-apps/android-test-app && ./gradlew :daily-notification-plugin:build)
  • iOS builds (if macOS available)
  • ./ci/run.sh passes
  • Recovery tests still pass

Batch P3.1-D — Instrument Database Operations

File: android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt (UPDATE)

Search for: val enabledSchedules = try { db.scheduleDao().getEnabled() }

Current code (in runBootRecovery(), around line ~71):

val enabledSchedules = try {
  db.scheduleDao().getEnabled()
} catch (e: Exception) {
  Log.e(TAG, "Failed to load schedules from DB", e)
  emptyList()
}

Action: Add timing wrapper:

val dbStartTime = System.currentTimeMillis()
val enabledSchedules = try {
  db.scheduleDao().getEnabled()
} catch (e: Exception) {
  Log.e(TAG, "Failed to load schedules from DB", e)
  emptyList()
} finally {
  val dbDuration = System.currentTimeMillis() - dbStartTime
  if (dbDuration > 100) {  // Warn if > 100ms
    Log.w(TAG, "Database query slow: ${dbDuration}ms for getEnabled()")
  } else {
    Log.d(TAG, "Database query: ${dbDuration}ms, schedules=${enabledSchedules.size}")
  }
}

Verification:

  • Android builds
  • ./ci/run.sh passes
  • No database contract violations

Batch P3.1-E — Document Benchmarks + Regression Guard

File: docs/PERFORMANCE.md (NEW FILE)

Action: Create with exact content:

# Performance Characteristics

**Purpose:** Expected performance characteristics and benchmarks for Daily Notification Plugin operations.  
**Owner:** Development Team  
**Last Updated:** 2025-12-22  
**Status:** active

---

## Expected Operation Times

### Scheduling Operations
- **Schedule creation:** < 50ms (typical), < 100ms (p95)
- **Schedule update:** < 50ms (typical), < 100ms (p95)
- **Schedule deletion:** < 50ms (typical), < 100ms (p95)

### Recovery Operations
- **Cold start recovery:** < 500ms (typical), < 1000ms (p95)
- **Force stop recovery:** < 500ms (typical), < 1000ms (p95)
- **Boot recovery:** < 1000ms (typical), < 2000ms (p95)

### Database Operations
- **Query (getEnabled):** < 50ms (typical), < 100ms (p95)
- **Query (getById):** < 10ms (typical), < 20ms (p95)
- **Insert/Update:** < 50ms (typical), < 100ms (p95)

## Memory Footprint

- **In-memory metrics:** ~10KB per 100 metrics
- **Event logs:** ~5KB per 100 events
- **Total overhead:** < 100KB (development mode), < 10KB (production, metrics disabled)

## Platform-Specific Considerations

### iOS
- Background task time limits: ~30 seconds
- CoreData auto-migration: typically < 100ms

### Android
- WorkManager execution time limits: flexible (minutes)
- Room migrations: typically < 200ms

### Web
- No background execution limits
- No native database operations

## Measurement Methodology

Metrics are collected using:
- `performance.now()` (Web/TypeScript)
- `System.currentTimeMillis()` (Android)
- `Date.timeIntervalSince()` (iOS)

All timings are in milliseconds.

---

**See also:**
- [SYSTEM_INVARIANTS.md](./SYSTEM_INVARIANTS.md) — Enforced system invariants
- [docs/progress/03-TEST-RUNS.md](./progress/03-TEST-RUNS.md) — Test run history

File: docs/00-INDEX.md (UPDATE)

Search for: ## Policy & Contracts (Executable) section

Action: Add link in appropriate section:

- [PERFORMANCE.md](./PERFORMANCE.md) — Performance characteristics and benchmarks

Verification:

  • File created and linked
  • ./ci/run.sh passes
  • Drift guard headers present

P3.1 Acceptance Checklist

  • Metrics collection infrastructure exists (src/core/metrics.ts)
  • Hot paths instrumented (scheduling, recovery, DB operations)
  • Performance characteristics documented (docs/PERFORMANCE.md)
  • ./ci/run.sh green
  • No new dependencies
  • No behavior changes (tests pass)

P3.2 — Enhanced Observability

Batch P3.2-A — Expand Event Coverage

File: src/core/events.ts (UPDATE)

Search for: ELECTRON_NOTIFICATION: 'DNP-ELECTRON-NOTIFICATION', (line ~78)

Action: Add new event codes after existing ones:

  // Recovery events
  RECOVERY_START: 'DNP-RECOVERY-START',
  RECOVERY_COMPLETE: 'DNP-RECOVERY-COMPLETE',
  RECOVERY_ERROR: 'DNP-RECOVERY-ERROR',
  // Database events
  DB_QUERY_START: 'DNP-DB-QUERY-START',
  DB_QUERY_COMPLETE: 'DNP-DB-QUERY-COMPLETE',
  DB_QUERY_ERROR: 'DNP-DB-QUERY-ERROR',
  // State transition events
  STATE_TRANSITION: 'DNP-STATE-TRANSITION',
  // Background task events
  BACKGROUND_TASK_START: 'DNP-BG-TASK-START',
  BACKGROUND_TASK_COMPLETE: 'DNP-BG-TASK-COMPLETE',
  BACKGROUND_TASK_ERROR: 'DNP-BG-TASK-ERROR',
} as const;

Verification:

  • TypeScript compiles
  • ./ci/run.sh passes
  • Event codes exported from src/core/index.ts (should already be via export * from './events')

Batch P3.2-B — Structured Metrics Export

File: src/observability.ts (UPDATE)

Search for: getHealthStatus(): Promise<HealthStatus> { (around line ~166)

Action: Add new methods after getHealthStatus():

  /**
   * Export metrics as JSON
   * @returns JSON string of all metrics
   */
  exportMetrics(): string {
    return JSON.stringify({
      performance: this.performanceMetrics,
      user: this.userMetrics,
      platform: this.platformMetrics,
      events: this.eventLogs.slice(0, 100), // Last 100 events
      exportedAt: Date.now(),
      schemaVersion: 1
    }, null, 2);
  }

  /**
   * Get metrics summary (lightweight)
   * @returns Summary object
   */
  getMetricsSummary(): {
    eventCount: number;
    successRate: number;
    avgFetchTime: number;
    avgNotifyTime: number;
  } {
    const fetchTimes = this.performanceMetrics.fetchTimes;
    const notifyTimes = this.performanceMetrics.notifyTimes;
    const total = this.performanceMetrics.successCount + this.performanceMetrics.failureCount;
    
    return {
      eventCount: this.eventLogs.length,
      successRate: total > 0 ? this.performanceMetrics.successCount / total : 0,
      avgFetchTime: fetchTimes.length > 0 
        ? fetchTimes.reduce((a, b) => a + b, 0) / fetchTimes.length 
        : 0,
      avgNotifyTime: notifyTimes.length > 0
        ? notifyTimes.reduce((a, b) => a + b, 0) / notifyTimes.length
        : 0
    };
  }

Verification:

  • TypeScript compiles
  • ./ci/run.sh passes
  • JSON export is valid JSON (test with JSON.parse())

Batch P3.2-C — Improve Error Context

File: src/core/errors.ts (UPDATE)

Search for: class DailyNotificationError extends Error {

Action: Add toJSON() method to class:

  /**
   * Convert error to JSON for structured logging
   * @returns JSON-serializable error representation
   */
  toJSON(): Record<string, unknown> {
    return {
      code: this.code,
      message: this.message,
      cause: this.cause ? String(this.cause) : undefined,
      stack: this.stack,
      timestamp: Date.now()
    };
  }

File: src/observability.ts (UPDATE)

Search for: logEvent( (around line ~96)

Action: Add helper method after logEvent():

  /**
   * Log error with enhanced context
   * @param eventCode Event code
   * @param message Human-readable message
   * @param error Error object
   * @param context Additional context
   */
  logError(eventCode: string, message: string, error: Error, context?: Record<string, unknown>): void {
    const errorData: Record<string, unknown> = {
      error: error.message,
      errorCode: error instanceof DailyNotificationError ? error.code : undefined,
      stack: error.stack,
      ...context
    };
    this.logEvent('ERROR', eventCode, message, errorData);
  }

Import check: Ensure DailyNotificationError is imported:

  • Search for: import.*DailyNotificationError.*from
  • If missing: Add import { DailyNotificationError } from './core/errors';

Verification:

  • TypeScript compiles
  • ./ci/run.sh passes
  • Error context includes stack traces

Batch P3.2-D — Diagnostic Mode (Opt-In)

File: src/observability.ts (UPDATE)

Search for: private maxLogs = 1000; (around line ~90)

Action: Add diagnostic mode flag after maxMetrics:

  private maxLogs = 1000;
  private maxMetrics = 100;
  private diagnosticMode = false;  // ADD THIS LINE

Action: Add methods after generateEventId():

  /**
   * Enable diagnostic mode (verbose logging)
   */
  enableDiagnosticMode(): void {
    this.diagnosticMode = true;
    this.logEvent('INFO', EVENT_CODES.METRICS_RESET, 'Diagnostic mode enabled');
  }

  /**
   * Disable diagnostic mode
   */
  disableDiagnosticMode(): void {
    this.diagnosticMode = false;
  }

  /**
   * Check if diagnostic mode is enabled
   */
  isDiagnosticMode(): boolean {
    return this.diagnosticMode;
  }

  /**
   * Get diagnostic information
   * @returns Diagnostic info object
   */
  getDiagnosticInfo(): { metrics: string; eventCount: number; diagnosticMode: boolean } {
    return {
      metrics: this.exportMetrics(),
      eventCount: this.eventLogs.length,
      diagnosticMode: this.diagnosticMode
    };
  }

Verification:

  • TypeScript compiles
  • ./ci/run.sh passes
  • Diagnostic mode can be toggled

P3.2 Acceptance Checklist

  • Event logging coverage expanded (new event codes added)
  • Structured metrics export implemented (exportMetrics())
  • Error context improved (stack traces, toJSON())
  • Diagnostic mode added (toggle, info export)
  • ./ci/run.sh green
  • No new dependencies

P3.3 — Developer Experience Improvements

Batch P3.3-A — Error Messages Upgrade

File: src/core/errors.ts (UPDATE)

Search for: export enum ErrorCode {

Action: Enhance error messages with actionable guidance. For each error code, add guidance:

Example pattern:

export enum ErrorCode {
  PERMISSION_DENIED = 'PERMISSION_DENIED',
  // ... existing codes ...
}

/**
 * Error code metadata with actionable guidance
 */
export const ERROR_GUIDANCE: Record<ErrorCode, { message: string; guidance: string; platformHints?: Record<string, string> }> = {
  [ErrorCode.PERMISSION_DENIED]: {
    message: 'Notification permission denied',
    guidance: 'Request permission using requestPermission() before scheduling notifications',
    platformHints: {
      ios: 'Check Info.plist for notification permission description',
      android: 'Check AndroidManifest.xml for POST_NOTIFICATIONS permission'
    }
  },
  // ... add for other error codes ...
};

File: src/web.ts (UPDATE)

Search for: private throwNotSupported(): never {

Current code (around line ~38):

private throwNotSupported(): never {
  throw new Error(DailyNotificationWeb.WEB_NOT_SUPPORTED_ERROR);
}

Action: Enhance error:

private throwNotSupported(): never {
  throw new DailyNotificationError(
    ErrorCode.NOT_SUPPORTED,
    'This operation is not supported on the web platform',
    undefined,
    {
      guidance: 'Use native iOS or Android implementation for this feature',
      platform: 'web'
    }
  );
}

Import check: Ensure DailyNotificationError and ErrorCode are imported

Verification:

  • TypeScript compiles
  • ./ci/run.sh passes
  • Error messages are actionable

Batch P3.3-B — Debug Helpers (Read-Only)

File: src/web.ts (UPDATE)

Search for: async deleteSchedule(_id: string): Promise<void> { (around line ~342)

Action: Add debug method after deleteSchedule():

  /**
   * Get current plugin state (development only)
   * @internal
   */
  async getDebugState(): Promise<{
    schedules: Schedule[];
    configs: Config[];
    callbacks: Callback[];
    metrics: ReturnType<ObservabilityManager['getMetricsSummary']>;
  }> {
    if (process.env.NODE_ENV === 'production') {
      throw new DailyNotificationError(
        ErrorCode.NOT_SUPPORTED, 
        'Debug methods not available in production'
      );
    }
    
    // Web implementation returns empty (not supported)
    return {
      schedules: [],
      configs: [],
      callbacks: [],
      metrics: this.observability?.getMetricsSummary() || { 
        eventCount: 0, 
        successRate: 0, 
        avgFetchTime: 0, 
        avgNotifyTime: 0 
      }
    };
  }

Verification:

  • TypeScript compiles
  • ./ci/run.sh passes
  • Debug methods only work in development

Batch P3.3-C — Type Tightening

File: src/core/contracts.ts (UPDATE)

Search for: export interface ScheduleWithStatus extends Schedule {

Current code (around line ~51):

export interface ScheduleWithStatus extends Schedule {
  isActuallyScheduled: boolean;
}

Action: Enhance with discriminated union (optional, if it improves type safety):

export interface ScheduleWithStatus extends Schedule {
  isActuallyScheduled: boolean;
  status: 'active' | 'paused' | 'error';
}

// Optional: Add discriminated union type
export type ScheduleWithStatusUnion = ScheduleWithStatus & (
  | { status: 'active'; isActuallyScheduled: true }
  | { status: 'paused'; isActuallyScheduled: false }
  | { status: 'error'; isActuallyScheduled: false; error?: string }
);

Note: Only add discriminated union if it doesn't break existing code

Verification:

  • TypeScript compiles
  • IntelliSense shows improved types
  • ./ci/run.sh passes

Batch P3.3-D — Integration Examples

File: docs/examples/QUICK_START.md (NEW FILE)

Action: Create with exact content:

# Quick Start Guide

**Purpose:** Minimal working example for Daily Notification Plugin.  
**Owner:** Development Team  
**Last Updated:** 2025-12-22  
**Status:** active

---

## Minimal Working Example

\`\`\`typescript
import { DailyNotification } from '@timesafari/daily-notification-plugin';

// 1. Request permission
const { state } = await DailyNotification.requestPermission();
if (state !== 'granted') {
  console.error('Permission denied');
  return;
}

// 2. Create schedule
const { schedule } = await DailyNotification.createSchedule({
  id: 'daily-morning',
  kind: 'notify',
  clockTime: '09:00',
  enabled: true
});

// 3. Verify schedule
const { schedules } = await DailyNotification.getSchedules();
console.log('Active schedules:', schedules);
\`\`\`

## Platform Setup

### iOS
Add to `Info.plist`:
\`\`\`xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
  <string>com.timesafari.dailynotification.fetch</string>
</array>
\`\`\`

### Android
Add to `AndroidManifest.xml`:
\`\`\`xml
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
\`\`\`

---

**See also:**
- [Common Patterns](./COMMON_PATTERNS.md) — Common integration patterns
- [Integration Guide](../integration/INTEGRATION_GUIDE.md) — Full integration guide

File: docs/examples/COMMON_PATTERNS.md (NEW FILE)

Action: Create with common patterns (scheduling, recovery, error handling)

File: docs/00-INDEX.md (UPDATE)

Search for: ## Archive & Reference-only section

Action: Add before archive section:

## Examples

- [Quick Start](./examples/QUICK_START.md) — Minimal working example
- [Common Patterns](./examples/COMMON_PATTERNS.md) — Common integration patterns

Verification:

  • Examples are accurate and runnable
  • ./ci/run.sh passes
  • Examples linked in index

P3.3 Acceptance Checklist

  • Error messages improved (actionable, context-rich)
  • Development mode helpers added (getDebugState())
  • TypeScript types enhanced (discriminated unions, JSDoc)
  • Integration examples expanded (quick-start, patterns)
  • ./ci/run.sh green
  • No breaking changes

P3.4 — Documentation Polish

Batch P3.4-A — Public API JSDoc Completeness

File: src/definitions.ts (UPDATE)

Search for: createSchedule(schedule: CreateScheduleInput): Promise<Schedule>;

Current JSDoc (around line ~610):

/**
 * Create a new recurring schedule
 * 
 * @param schedule Schedule configuration
 * @returns Promise resolving to created Schedule object
 * 
 * @example
 * ```typescript
 * const schedule = await DailyNotification.createSchedule({
 *   kind: 'notify',
 *   cron: '0 9 * * *',  // Daily at 9 AM
 *   enabled: true
 * });
 * ```
 */
createSchedule(schedule: CreateScheduleInput): Promise<Schedule>;

Action: Enhance with complete JSDoc:

/**
 * Create a new recurring schedule
 * 
 * @param schedule Schedule configuration
 * @param schedule.id - Unique schedule identifier (required, must be unique)
 * @param schedule.kind - Schedule type: 'notify' for notifications, 'fetch' for content fetching
 * @param schedule.cron - Cron expression (e.g., '0 9 * * *' for daily at 9 AM). Mutually exclusive with clockTime
 * @param schedule.clockTime - Time of day in HH:mm format (e.g., '09:00'). Mutually exclusive with cron
 * @param schedule.enabled - Whether schedule is active (default: true)
 * @param schedule.jitterMs - Random jitter in milliseconds (default: 0)
 * @param schedule.backoffPolicy - Backoff policy for retries (default: 'exp')
 * @returns Promise resolving to created Schedule object
 * @throws {DailyNotificationError} If schedule creation fails (e.g., invalid cron, duplicate ID, permission denied)
 * 
 * @example
 * ```typescript
 * const schedule = await DailyNotification.createSchedule({
 *   id: 'morning-notification',
 *   kind: 'notify',
 *   clockTime: '09:00',
 *   enabled: true
 * });
 * ```
 * 
 * @example
 * ```typescript
 * // Using cron expression
 * const schedule = await DailyNotification.createSchedule({
 *   id: 'daily-fetch',
 *   kind: 'fetch',
 *   cron: '0 */6 * * *',  // Every 6 hours
 *   enabled: true
 * });
 * ```
 */
createSchedule(schedule: CreateScheduleInput): Promise<Schedule>;

Repeat for all public methods:

  • updateSchedule()
  • deleteSchedule()
  • getSchedules()
  • getSchedulesWithStatus()
  • createConfig()
  • updateConfig()
  • deleteConfig()
  • getConfigs()
  • createCallback()
  • updateCallback()
  • deleteCallback()
  • getCallbacks()
  • requestPermission()
  • checkPermission()

Verification:

  • All public APIs have complete JSDoc
  • JSDoc includes: params, returns, throws, examples
  • npm run build generates .d.ts files with JSDoc

Batch P3.4-B — Troubleshooting Guide

File: docs/TROUBLESHOOTING.md (NEW FILE)

Action: Create with exact content:

# Troubleshooting Guide

**Purpose:** Common issues, symptoms, causes, and solutions.  
**Owner:** Development Team  
**Last Updated:** 2025-12-22  
**Status:** active

---

## CI Failures

### Symptom: `./ci/run.sh` fails

**Causes:**
- Forbidden files in package
- Core module imports platform deps
- Export paths don't match artifacts

**Solutions:**
1. Check forbidden files: `npm pack --dry-run | grep -E "xcuserdata|xcuserstate|DerivedData|ios/App/"`
2. Check core purity: `grep -r "@capacitor\|react\|fs\|path\|os" src/core/`
3. Check exports: `node -e "const p=require('./package.json'); console.log(JSON.stringify(p.exports, null, 2))"`

---

## Packaging Failures

### Symptom: `npm pack` includes forbidden files

**Solution:** Update `package.json.files` whitelist

---

## Platform Test Failures

### Symptom: Android/iOS tests fail

**Solutions:**
- **Android:** Run from test-app: `cd test-apps/android-test-app && ./gradlew :daily-notification-plugin:test`
- **iOS:** Requires macOS + Xcode

---

## DST Issues

### Symptom: Notifications fire at wrong time during DST transitions

**Solution:** Plugin handles DST automatically. If issues persist, check timezone configuration.

---

## Duplicate Deliveries

### Symptom: Same notification delivered multiple times

**Solution:** Plugin includes deduplication. Check recovery logs for duplicate detection.

---

## Cold Start Recovery

### Symptom: Notifications not restored after app restart

**Solution:** Check recovery logs. Ensure schedules are persisted to database.

---

**See also:**
- [SYSTEM_INVARIANTS.md](./SYSTEM_INVARIANTS.md) — Enforced invariants
- [PERFORMANCE.md](./PERFORMANCE.md) — Performance characteristics

File: docs/00-INDEX.md (UPDATE)

Action: Add link in appropriate section

Verification:

  • Troubleshooting guide is comprehensive
  • ./ci/run.sh passes
  • Guide linked in index

Batch P3.4-C — Onboarding/Architecture Doc Pass

File: docs/GETTING_STARTED.md (NEW FILE or UPDATE existing)

Action: Create/update with:

  • Step-by-step installation
  • Platform setup
  • Basic usage
  • Links to authoritative docs (docs/00-INDEX.md, docs/SYSTEM_INVARIANTS.md, ./ci/run.sh)

File: docs/00-INDEX.md (UPDATE)

Action: Add link

Verification:

  • Getting started guide is clear
  • ./ci/run.sh passes
  • Guide linked in index

Batch P3.4-D — Migration Guide (Only If Needed)

Note: Only create if breaking changes exist. Current baseline has no breaking changes.

Verification:

  • Migration guide only created if needed
  • ./ci/run.sh passes

P3.4 Acceptance Checklist

  • API documentation complete (all public APIs have JSDoc)
  • Troubleshooting guides added (docs/TROUBLESHOOTING.md)
  • Onboarding documentation improved (docs/GETTING_STARTED.md)
  • Migration guides added (if needed)
  • Documentation index updated (docs/00-INDEX.md)
  • ./ci/run.sh green

P3 Close-out Checklist

When all P3 items are complete:

  • Parity matrix updated (if any parity-related items added)
  • Progress docs updated:
    • docs/progress/00-STATUS.md — Mark P3 complete
    • docs/progress/01-CHANGELOG-WORK.md — Add P3 completion entry
    • docs/progress/03-TEST-RUNS.md — Add performance test results (if applicable)
  • ./ci/run.sh green
  • Create baseline tag: v1.0.11-p3-complete
  • Push tag: git push --tags

Execution Notes

Batch Discipline:

  • Complete one batch at a time
  • Run ./ci/run.sh after each batch
  • Commit after each batch (if desired) or after completing a full P3.x item

No New Dependencies:

  • Use built-in APIs only (performance.now(), Date.now(), etc.)
  • No external metrics libraries
  • No external logging libraries

Testing:

  • Existing tests must continue to pass
  • No new test infrastructure required (unless explicitly in acceptance criteria)

Documentation:

  • All new docs must be linked in docs/00-INDEX.md
  • All docs must have drift guards (Purpose, Owner, Last Updated, Status)

Last Updated: 2025-12-22
Status: Execution-ready (awaiting approval to begin)