# P3 Execution Checklist — Mechanical Step-by-Step **Purpose:** Exact, file-by-file, function-by-function execution plan for P3 work. **Owner:** Development Team **Last Updated:** 2025-12-22 **Status:** execution-ready **Baseline:** `v1.0.11-p2.3-p1.5b-complete` --- ## 0) Non-Negotiable Invariants (DO NOT BREAK) **Before every batch:** - [ ] Run `./ci/run.sh` and verify all checks pass - [ ] Verify `npm pack --dry-run | grep -E "xcuserdata|xcuserstate|DerivedData|ios/App/"` returns empty - [ ] Verify no platform imports in `src/core/` (grep for `@capacitor|react|fs|path|os`) - [ ] Verify `package.json.exports` matches build artifacts - [ ] Verify new docs are linked in `docs/00-INDEX.md` or placed in `docs/_archive/` **After every batch:** - [ ] Run `./ci/run.sh` — **STOP IF FAILS** - [ ] Update progress docs if applicable - [ ] Commit with clear message --- ## P3.1 — Performance Optimization & Metrics ### Batch 1: Add Metrics Collection Infrastructure **Files to create/modify:** 1. **`src/core/metrics.ts`** (NEW FILE) ```typescript // Exact structure: export interface PerformanceMetric { operation: string; duration: number; timestamp: number; success: boolean; metadata?: Record; } export interface MetricsCollector { record(metric: PerformanceMetric): void; getMetrics(): PerformanceMetric[]; clear(): void; } // Lightweight in-memory collector (no deps) 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 = []; } } ``` 2. **`src/core/index.ts`** (UPDATE) - Add export: `export * from './metrics';` **Verification:** - [ ] `npm run build` succeeds - [ ] `./ci/run.sh` passes - [ ] No new dependencies added --- ### Batch 2: Instrument Hot Paths — Scheduling **Files to modify:** 1. **`src/web.ts`** — `createSchedule()` method - **Location:** Find `async createSchedule(input: CreateScheduleInput)` - **Add before method:** ```typescript const startTime = performance.now(); ``` - **Add after success (before return):** ```typescript const duration = performance.now() - startTime; this.observability?.logEvent('INFO', EVENT_CODES.SCHEDULE_UPDATE, `Schedule created: ${result.schedule.id}`, { scheduleId: result.schedule.id, duration }); ``` - **Add after error (in catch):** ```typescript const duration = performance.now() - startTime; this.observability?.logEvent('ERROR', EVENT_CODES.SCHEDULE_UPDATE, `Schedule creation failed`, { error: error.message, duration }); ``` 2. **`src/web.ts`** — `updateSchedule()` method - **Same pattern:** Add timing before, log after (success/error) 3. **`src/web.ts`** — `deleteSchedule()` method - **Same pattern:** Add timing before, log after (success/error) **Verification:** - [ ] TypeScript compiles (`npm run build`) - [ ] `./ci/run.sh` passes - [ ] No behavior changes (tests still pass) --- ### Batch 3: Instrument Hot Paths — Recovery **Files to modify:** 1. **Android: `android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt`** - **Function:** `performColdStartRecovery()` - **Add at start:** ```kotlin val startTime = System.currentTimeMillis() ``` - **Add before return:** ```kotlin val duration = System.currentTimeMillis() - startTime Log.i(TAG, "Cold start recovery completed: duration=${duration}ms, missed=$missedCount, rescheduled=$rescheduledCount") ``` 2. **Android: `android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt`** - **Function:** `performForceStopRecovery()` - **Same pattern:** Add timing, log duration 3. **iOS: `ios/Plugin/DailyNotificationReactivationManager.swift`** - **Function:** `performColdStartRecovery()` - **Add at start:** ```swift let startTime = Date() ``` - **Add before return:** ```swift 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) ``` **Verification:** - [ ] Android builds (`cd test-apps/android-test-app && ./gradlew :daily-notification-plugin:build`) - [ ] iOS builds (if macOS available) - [ ] `./ci/run.sh` passes --- ### Batch 4: Instrument Hot Paths — Database Operations **Files to modify:** 1. **Android: `android/src/main/java/com/timesafari/dailynotification/DatabaseSchema.kt`** - **Find:** Room DAO methods (e.g., `getEnabled()`, `getById()`) - **Add timing wrapper** (if possible without breaking Room contracts): ```kotlin // For critical queries, add timing in calling code, not DAO // Document in comments: "Timing measured in ReactivationManager" ``` 2. **Android: `android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt`** - **Function:** `runBootRecovery()` - **Add timing around DB query:** ```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 Log.d(TAG, "Database query duration: ${dbDuration}ms, schedules=${enabledSchedules.size}") } ``` **Verification:** - [ ] Android builds - [ ] `./ci/run.sh` passes - [ ] No database contract violations --- ### Batch 5: Document Performance Characteristics **Files to create:** 1. **`docs/PERFORMANCE.md`** (NEW FILE) ```markdown # Performance Characteristics ## Expected Operation Times - Schedule creation: < 50ms (typical), < 100ms (p95) - Schedule update: < 50ms (typical), < 100ms (p95) - Cold start recovery: < 500ms (typical), < 1000ms (p95) - Database query (getEnabled): < 50ms (typical), < 100ms (p95) ## Memory Footprint - In-memory metrics: ~10KB per 100 metrics - Event logs: ~5KB per 100 events - Total overhead: < 100KB (development mode) ## Platform-Specific Considerations - iOS: Background task time limits (~30 seconds) - Android: WorkManager execution time limits (flexible) - Web: No background execution limits ``` 2. **`docs/00-INDEX.md`** (UPDATE) - Add link: `- [PERFORMANCE.md](./PERFORMANCE.md) — Performance characteristics and benchmarks` **Verification:** - [ ] File created and linked - [ ] `./ci/run.sh` passes --- ### 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 1: Expand Event Logging Coverage **Files to modify:** 1. **`src/core/events.ts`** — Add missing event codes ```typescript // Add to EVENT_CODES object: RECOVERY_START: 'DNP-RECOVERY-START', RECOVERY_COMPLETE: 'DNP-RECOVERY-COMPLETE', RECOVERY_ERROR: 'DNP-RECOVERY-ERROR', DB_QUERY_START: 'DNP-DB-QUERY-START', DB_QUERY_COMPLETE: 'DNP-DB-QUERY-COMPLETE', DB_QUERY_ERROR: 'DNP-DB-QUERY-ERROR', STATE_TRANSITION: 'DNP-STATE-TRANSITION', BACKGROUND_TASK_START: 'DNP-BG-TASK-START', BACKGROUND_TASK_COMPLETE: 'DNP-BG-TASK-COMPLETE', ``` 2. **`src/observability.ts`** — Add recovery event logging - **Function:** `logEvent()` (already exists, no changes needed) - **Add helper method:** ```typescript logRecovery(operation: string, result: { success: boolean; missed?: number; rescheduled?: number; errors?: number; duration?: number }): void { const level = result.success ? 'INFO' : 'ERROR'; this.logEvent(level, EVENT_CODES.RECOVERY_COMPLETE, `Recovery ${operation} completed`, { operation, ...result }); } ``` **Verification:** - [ ] TypeScript compiles - [ ] `./ci/run.sh` passes - [ ] Event codes are exported from `src/core/index.ts` --- ### Batch 2: Add Structured Metrics Export **Files to modify:** 1. **`src/observability.ts`** — Add export method ```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() }, 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 }; } ``` 2. **`src/definitions.ts`** — Add to plugin interface (if needed) - Check if `DailyNotificationPlugin` interface needs `exportMetrics()` method - If yes, add: `exportMetrics(): Promise<{ metrics: string }>;` **Verification:** - [ ] TypeScript compiles - [ ] `./ci/run.sh` passes - [ ] JSON export is valid JSON --- ### Batch 3: Improve Error Context **Files to modify:** 1. **`src/core/errors.ts`** — Enhance error class ```typescript // Find DailyNotificationError class // Add method: toJSON(): Record { return { code: this.code, message: this.message, cause: this.cause ? String(this.cause) : undefined, stack: this.stack, timestamp: Date.now() }; } ``` 2. **`src/observability.ts`** — Enhance error logging ```typescript // In logEvent(), if level === 'ERROR' and data contains error: 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); } ``` **Verification:** - [ ] TypeScript compiles - [ ] `./ci/run.sh` passes - [ ] Error context includes stack traces --- ### Batch 4: Add Diagnostic Mode **Files to modify:** 1. **`src/observability.ts`** — Add diagnostic flag ```typescript private diagnosticMode = false; /** * 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; } ``` 2. **`src/definitions.ts`** — Add to plugin interface ```typescript // Add to DailyNotificationPlugin: enableDiagnosticMode(): Promise; disableDiagnosticMode(): Promise; getDiagnosticInfo(): Promise<{ metrics: string; eventCount: number }>; ``` **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, state snapshots) - [ ] Diagnostic mode added (toggle, info export) - [ ] `./ci/run.sh` green - [ ] No new dependencies --- ## P3.3 — Developer Experience Improvements ### Batch 1: Improve Error Messages **Files to modify:** 1. **`src/core/errors.ts`** — Enhance error messages ```typescript // For each error code, add actionable guidance: // Example: PERMISSION_DENIED: { code: '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' } } ``` 2. **`src/web.ts`** — Improve error handling - **Find:** `throwNotSupported()` method - **Enhance:** ```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' } ); } ``` **Verification:** - [ ] TypeScript compiles - [ ] `./ci/run.sh` passes - [ ] Error messages are actionable --- ### Batch 2: Add Development Mode Helpers **Files to modify:** 1. **`src/web.ts`** — Add debug helpers ```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'); } const schedules = await this.getSchedules(); const configs = await this.getConfigs(); const callbacks = await this.getCallbacks(); const metrics = this.observability?.getMetricsSummary() || { eventCount: 0, successRate: 0, avgFetchTime: 0, avgNotifyTime: 0 }; return { schedules: schedules.schedules, configs: configs.configs, callbacks: callbacks.callbacks, metrics }; } ``` **Verification:** - [ ] TypeScript compiles - [ ] `./ci/run.sh` passes - [ ] Debug methods only work in development --- ### Batch 3: Enhance TypeScript Types **Files to modify:** 1. **`src/core/contracts.ts`** — Add discriminated unions where appropriate ```typescript // Example: Enhance ScheduleWithStatus export type ScheduleWithStatus = Schedule & { status: 'active' | 'paused' | 'error'; nextRunAt: number | null; lastRunAt: number | null; } & ( | { status: 'active'; nextRunAt: number } | { status: 'paused'; nextRunAt: null } | { status: 'error'; nextRunAt: null; error: string } ); ``` 2. **`src/definitions.ts`** — Add JSDoc improvements ```typescript /** * Create a new notification schedule * * @param input - Schedule configuration * @param input.id - Unique schedule identifier (required) * @param input.kind - Schedule type: 'notify' for notifications, 'fetch' for content fetching * @param input.cron - Cron expression (e.g., '0 9 * * *' for daily at 9 AM) * @param input.clockTime - Time of day in HH:mm format (alternative to cron) * @param input.enabled - Whether schedule is active (default: true) * @returns Created schedule with status * @throws {DailyNotificationError} If schedule creation fails * * @example * ```typescript * const schedule = await DailyNotification.createSchedule({ * id: 'morning-notification', * kind: 'notify', * clockTime: '09:00', * enabled: true * }); * ``` */ createSchedule(input: CreateScheduleInput): Promise<{ schedule: ScheduleWithStatus }>; ``` **Verification:** - [ ] TypeScript compiles - [ ] IntelliSense shows improved types - [ ] `./ci/run.sh` passes --- ### Batch 4: Expand Integration Examples **Files to create:** 1. **`docs/examples/QUICK_START.md`** (NEW FILE) ```markdown # Quick Start Guide ## 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); \`\`\` ``` 2. **`docs/examples/COMMON_PATTERNS.md`** (NEW FILE) - Add patterns for: scheduling, recovery, error handling, platform-specific 3. **`docs/00-INDEX.md`** (UPDATE) - Add section: `## Examples` - Link: `- [Quick Start](./examples/QUICK_START.md)` - Link: `- [Common Patterns](./examples/COMMON_PATTERNS.md)` **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 1: Complete API Documentation (JSDoc) **Files to modify (exact list):** 1. **`src/definitions.ts`** — All public methods - `createSchedule()` — Add JSDoc (see P3.3 Batch 3 example) - `updateSchedule()` — Add JSDoc - `deleteSchedule()` — Add JSDoc - `getSchedules()` — Add JSDoc - `createConfig()` — Add JSDoc - `updateConfig()` — Add JSDoc - `deleteConfig()` — Add JSDoc - `getConfigs()` — Add JSDoc - `createCallback()` — Add JSDoc - `updateCallback()` — Add JSDoc - `deleteCallback()` — Add JSDoc - `getCallbacks()` — Add JSDoc - `requestPermission()` — Add JSDoc - `checkPermission()` — Add JSDoc 2. **`src/core/contracts.ts`** — All interfaces - Add JSDoc to: `Schedule`, `Config`, `Callback`, `History`, `ContentCache` 3. **`src/core/errors.ts`** — Error codes - Add JSDoc to `ErrorCode` enum values - Add JSDoc to `DailyNotificationError` class **Verification:** - [ ] All public APIs have JSDoc - [ ] JSDoc includes: params, returns, throws, examples - [ ] `npm run build` generates `.d.ts` files with JSDoc --- ### Batch 2: Add Troubleshooting Guides **Files to create:** 1. **`docs/TROUBLESHOOTING.md`** (NEW FILE) ```markdown # Troubleshooting Guide ## Common Issues ### CI Failures **Problem:** `./ci/run.sh` fails **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" src/core/` 3. Check exports: `node -e "const p=require('./package.json'); console.log(p.exports)"` ### Packaging Failures **Problem:** `npm pack` includes forbidden files **Solution:** Update `package.json.files` whitelist ### Platform Test Failures **Problem:** 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 ``` 2. **`docs/00-INDEX.md`** (UPDATE) - Add link: `- [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) — Common issues and solutions` **Verification:** - [ ] Troubleshooting guide is comprehensive - [ ] `./ci/run.sh` passes - [ ] Guide linked in index --- ### Batch 3: Improve Onboarding Documentation **Files to create/modify:** 1. **`docs/GETTING_STARTED.md`** (NEW FILE or UPDATE existing) ```markdown # Getting Started ## Step 1: Installation \`\`\`bash npm install @timesafari/daily-notification-plugin \`\`\` ## Step 2: Platform Setup - iOS: Add to `Info.plist` (see integration guide) - Android: Add to `AndroidManifest.xml` (see integration guide) ## Step 3: Basic Usage See [Quick Start](./examples/QUICK_START.md) ## Architecture Overview See [ARCHITECTURE.md](./ARCHITECTURE.md) ## Key Concepts - **Scheduling**: Recurring notification patterns - **Recovery**: Automatic rescheduling after app restart - **Persistence**: State survives app/OS restarts ``` 2. **`docs/00-INDEX.md`** (UPDATE) - Add link: `- [GETTING_STARTED.md](./GETTING_STARTED.md) — Step-by-step onboarding` **Verification:** - [ ] Getting started guide is clear - [ ] `./ci/run.sh` passes - [ ] Guide linked in index --- ### Batch 4: Add Migration Guides (if needed) **Files to create (only if breaking changes exist):** 1. **`docs/MIGRATION.md`** (NEW FILE, only if needed) ```markdown # Migration Guide ## Version Upgrades ### v1.0.11 → v1.0.12 No breaking changes. ### v1.0.10 → v1.0.11 - Core module introduced: Use `@timesafari/daily-notification-plugin/core` for core types ``` **Verification:** - [ ] Migration guide only created if needed - [ ] `./ci/run.sh` passes - [ ] Guide linked in index (if created) --- ### 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)