Files
daily-notification-plugin/docs/progress/P3.1-CURSOR-TASK-BLOCK.md
Matthew Raymer 6297281d2d docs: Add P3.1 compressed Cursor task block
Created ultra-compressed task block for P3.1 only (batches A-E).

Contains:
- Exact file paths
- Exact search strings
- Exact code replacements
- Verification commands
- Progress doc update checklist

Ready for incremental execution - one batch at a time.
2025-12-23 06:28:41 +00:00

7.1 KiB

P3.1 Cursor Task Block — Performance Optimization & Metrics

Purpose: Ultra-compressed, mechanical execution steps for P3.1 only.
Baseline: v1.0.11-p2.3-p1.5b-complete
Invariants: See docs/progress/P3-EXECUTION-CHECKLIST-MECHANICAL.md section 0


Preflight (Before Each Batch)

./ci/run.sh
# STOP IF FAILS

Batch P3.1-A: Metrics Contract

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

Action: Create file with exact content:

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

export interface PerformanceMetric {
  operation: string;
  duration: number;
  timestamp: number;
  success: boolean;
  metadata?: Record<string, unknown>;
}

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

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: export * from './guards';

Action: Add after: export * from './metrics';

Verify:

npm run build
./ci/run.sh
grep -r "@capacitor\|react\|fs\|path\|os" src/core/metrics.ts  # Must be empty

Batch P3.1-B: Instrument Scheduling

File: src/web.ts (UPDATE)

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

Current:

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

Replace with:

async createSchedule(_schedule: CreateScheduleInput): Promise<Schedule> {
  const startTime = performance.now();
  try {
    this.throwNotSupported();
  } catch (error) {
    const duration = performance.now() - startTime;
    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 imported:

import { EVENT_CODES } from './core/events';

Repeat for:

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

Verify:

npm run build
./ci/run.sh

Batch P3.1-C: Instrument Recovery

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

Search: private suspend fun performColdStartRecovery(): RecoveryResult {

Add at start:

val startTime = System.currentTimeMillis()

Find return statement, add before:

val duration = System.currentTimeMillis() - startTime
Log.i(TAG, "Cold start recovery completed: duration=${duration}ms, missed=$missedCount, rescheduled=$rescheduledCount, errors=$errors")

Repeat for: performForceStopRecovery()

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

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

Add at start:

let startTime = Date()

Find return, add before:

let duration = Date().timeIntervalSince(startTime) * 1000
os_log("Cold start recovery completed: duration=%.0fms, missed=%d, rescheduled=%d", 
       log: .default, type: .info, duration, missedCount, rescheduledCount)

Verify:

cd test-apps/android-test-app && ./gradlew :daily-notification-plugin:build
./ci/run.sh

Batch P3.1-D: Instrument Database

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

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

Replace with:

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) {
    Log.w(TAG, "Database query slow: ${dbDuration}ms for getEnabled()")
  } else {
    Log.d(TAG, "Database query: ${dbDuration}ms, schedules=${enabledSchedules.size}")
  }
}

Verify:

cd test-apps/android-test-app && ./gradlew :daily-notification-plugin:build
./ci/run.sh

Batch P3.1-E: Performance Documentation

File: docs/PERFORMANCE.md (NEW)

Action: Create with exact content:

# Performance Characteristics

**Purpose:** Expected performance characteristics and benchmarks.  
**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), < 10KB (production)

## Platform-Specific Considerations

### iOS
- Background task limits: ~30 seconds
- CoreData migrations: typically < 100ms

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

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

## Measurement Methodology

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

All timings in milliseconds.

---

**See also:**
- [SYSTEM_INVARIANTS.md](./SYSTEM_INVARIANTS.md)
- [docs/progress/03-TEST-RUNS.md](./progress/03-TEST-RUNS.md)

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

Search: ## Policy & Contracts (Executable)

Add link:

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

Verify:

./ci/run.sh

P3.1 Acceptance

  • src/core/metrics.ts exists
  • Scheduling methods instrumented
  • Recovery methods instrumented
  • Database operations instrumented
  • docs/PERFORMANCE.md created and linked
  • ./ci/run.sh green
  • No new dependencies
  • Tests pass

Update Progress Docs (After P3.1 Complete)

Files:

  • docs/progress/00-STATUS.md — Mark P3.1 complete
  • docs/progress/01-CHANGELOG-WORK.md — Add P3.1 entry
  • docs/progress/03-TEST-RUNS.md — Add performance metrics note

Verify:

./ci/run.sh

Next: Proceed to P3.2 (Observability) after P3.1 is green.