# 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 `doc/_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.sh` — **STOP 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: - `doc/progress/00-STATUS.md` - `doc/progress/01-CHANGELOG-WORK.md` - `doc/progress/03-TEST-RUNS.md` - `doc/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: ```typescript /** * 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; } /** * 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: ```typescript 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 {` **Current code (line ~334):** ```typescript async createSchedule(_schedule: CreateScheduleInput): Promise { this.throwNotSupported(); } ``` **Action:** Add timing (even though it throws immediately): ```typescript async createSchedule(_schedule: CreateScheduleInput): Promise { 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):** ```kotlin 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: ```kotlin 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: ```swift 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):** ```kotlin val enabledSchedules = try { db.scheduleDao().getEnabled() } catch (e: Exception) { Log.e(TAG, "Failed to load schedules from DB", e) emptyList() } ``` **Action:** Add timing wrapper: ```kotlin 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: `doc/PERFORMANCE.md` (NEW FILE) **Action:** Create with exact content: ```markdown # 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 - [doc/progress/03-TEST-RUNS.md](./progress/03-TEST-RUNS.md) — Test run history ``` ### File: `doc/00-INDEX.md` (UPDATE) **Search for:** `## Policy & Contracts (Executable)` section **Action:** Add link in appropriate section: ```markdown - [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 (`doc/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: ```typescript // 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 {` (around line ~166) **Action:** Add new methods after `getHealthStatus()`: ```typescript /** * 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: ```typescript /** * Convert error to JSON for structured logging * @returns JSON-serializable error representation */ toJSON(): Record { 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()`: ```typescript /** * 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): void { const errorData: Record = { 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`: ```typescript private maxLogs = 1000; private maxMetrics = 100; private diagnosticMode = false; // ADD THIS LINE ``` **Action:** Add methods after `generateEventId()`: ```typescript /** * 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:** ```typescript export enum ErrorCode { PERMISSION_DENIED = 'PERMISSION_DENIED', // ... existing codes ... } /** * Error code metadata with actionable guidance */ export const ERROR_GUIDANCE: Record }> = { [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):** ```typescript private throwNotSupported(): never { throw new Error(DailyNotificationWeb.WEB_NOT_SUPPORTED_ERROR); } ``` **Action:** Enhance error: ```typescript 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 {` (around line ~342) **Action:** Add debug method after `deleteSchedule()`: ```typescript /** * Get current plugin state (development only) * @internal */ async getDebugState(): Promise<{ schedules: Schedule[]; configs: Config[]; callbacks: Callback[]; metrics: ReturnType; }> { 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):** ```typescript export interface ScheduleWithStatus extends Schedule { isActuallyScheduled: boolean; } ``` **Action:** Enhance with discriminated union (optional, if it improves type safety): ```typescript 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: `doc/examples/QUICK_START.md` (NEW FILE) **Action:** Create with exact content: ```markdown # 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 BGTaskSchedulerPermittedIdentifiers org.timesafari.dailynotification.fetch \`\`\` ### Android Add to `AndroidManifest.xml`: \`\`\`xml \`\`\` --- **See also:** - [Common Patterns](./COMMON_PATTERNS.md) — Common integration patterns - [Integration Guide](../integration/INTEGRATION_GUIDE.md) — Full integration guide ``` ### File: `doc/examples/COMMON_PATTERNS.md` (NEW FILE) **Action:** Create with common patterns (scheduling, recovery, error handling) ### File: `doc/00-INDEX.md` (UPDATE) **Search for:** `## Archive & Reference-only` section **Action:** Add before archive section: ```markdown ## 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;` **Current JSDoc (around line ~610):** ```typescript /** * 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; ``` **Action:** Enhance with complete JSDoc: ```typescript /** * 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; ``` **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: `doc/TROUBLESHOOTING.md` (NEW FILE) **Action:** Create with exact content: ```markdown # 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: `doc/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: `doc/GETTING_STARTED.md` (NEW FILE or UPDATE existing) **Action:** Create/update with: - Step-by-step installation - Platform setup - Basic usage - Links to authoritative docs (`doc/00-INDEX.md`, `doc/SYSTEM_INVARIANTS.md`, `./ci/run.sh`) ### File: `doc/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 (`doc/TROUBLESHOOTING.md`) - [ ] Onboarding documentation improved (`doc/GETTING_STARTED.md`) - [ ] Migration guides added (if needed) - [ ] Documentation index updated (`doc/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: - [ ] `doc/progress/00-STATUS.md` — Mark P3 complete - [ ] `doc/progress/01-CHANGELOG-WORK.md` — Add P3 completion entry - [ ] `doc/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 `doc/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)