Files
phaseflow/IMPLEMENTATION_PLAN.md
Petru Paler 2ffee63a59 Implement token expiration warnings (P3.9)
Add email warnings for Garmin token expiration at 14-day and 7-day thresholds.
When the garmin-sync cron job runs, it now checks each user's token expiry and
sends a warning email at exactly 14 days and 7 days before expiration.

Changes:
- Add sendTokenExpirationWarning() to email.ts with differentiated subject
  lines and urgency levels for 14-day vs 7-day warnings
- Integrate warning logic into garmin-sync cron route using daysUntilExpiry()
- Track warnings sent in sync response with new warningsSent counter
- Add 20 new tests (10 for email function, 10 for sync integration)

Test count: 517 → 537

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 08:24:19 +00:00

49 KiB

PhaseFlow Implementation Plan

This file is maintained by Ralph. Run ./ralph-sandbox.sh plan 3 to generate tasks.

Current State Summary

Overall Status: 537 tests passing across 30 test files

Library Implementation

File Status Gap Analysis
cycle.ts COMPLETE 9 tests covering all functions, production-ready
nutrition.ts COMPLETE 17 tests covering getNutritionGuidance, getSeedSwitchAlert, phase-specific carb ranges, keto guidance
email.ts COMPLETE 24 tests covering sendDailyEmail, sendPeriodConfirmationEmail, sendTokenExpirationWarning, email formatting, subject lines
ics.ts COMPLETE 23 tests covering generateIcsFeed (90 days of phase events), ICS format validation, timezone handling
encryption.ts COMPLETE 14 tests covering AES-256-GCM encrypt/decrypt round-trip, error handling, key validation
decision-engine.ts COMPLETE 8 priority rules + override handling with getDecisionWithOverrides(), 24 tests
garmin.ts COMPLETE 33 tests covering fetchGarminData, fetchHrvStatus, fetchBodyBattery, fetchIntensityMinutes, isTokenExpired, daysUntilExpiry, error handling, token validation
pocketbase.ts COMPLETE 9 tests covering createPocketBaseClient(), isAuthenticated(), getCurrentUser(), loadAuthFromCookies()
auth-middleware.ts COMPLETE 6 tests covering withAuth() wrapper for API route protection
middleware.ts (Next.js) COMPLETE 12 tests covering page protection, redirects to login
logger.ts NOT IMPLEMENTED P2.17 - Structured logging with pino
metrics.ts NOT IMPLEMENTED P2.16 - Prometheus metrics collection

Infrastructure Gaps (from specs/ - pending implementation)

Gap Spec Reference Task Priority
Health Check Endpoint specs/observability.md P2.15 COMPLETE
Prometheus Metrics specs/observability.md P2.16 Medium
Structured Logging (pino) specs/observability.md P2.17 Medium
OIDC Authentication specs/authentication.md P2.18 Medium
Token Expiration Warnings specs/email.md P3.9 COMPLETE

API Routes (17 total)

Route Status Notes
GET /api/user COMPLETE Returns user profile with withAuth()
PATCH /api/user COMPLETE Updates cycleLength, notificationTime, timezone (17 tests)
POST /api/cycle/period COMPLETE Logs period start, updates user, creates PeriodLog (8 tests)
GET /api/cycle/current COMPLETE Returns cycle day, phase, config, daysUntilNextPhase (10 tests)
GET /api/today COMPLETE Returns decision, cycle, biometrics, nutrition (22 tests)
POST /api/overrides COMPLETE Adds override to user.activeOverrides (14 tests)
DELETE /api/overrides COMPLETE Removes override from user.activeOverrides (14 tests)
POST /api/garmin/tokens COMPLETE Stores encrypted Garmin OAuth tokens (15 tests)
DELETE /api/garmin/tokens COMPLETE Clears tokens and disconnects Garmin (15 tests)
GET /api/garmin/status COMPLETE Returns connection status, expiry, warning level (11 tests)
GET /api/calendar/[userId]/[token].ics COMPLETE Token validation, ICS generation, caching headers (10 tests)
POST /api/calendar/regenerate-token COMPLETE Generates 32-char token, returns URL (9 tests)
POST /api/cron/garmin-sync COMPLETE Syncs Garmin data for all users, creates DailyLogs, sends token expiration warnings (32 tests)
POST /api/cron/notifications COMPLETE Sends daily emails with timezone matching, DailyLog handling (20 tests)
GET /api/history COMPLETE Paginated historical daily logs with date filtering (19 tests)
GET /api/health COMPLETE Health check for deployment monitoring (14 tests)
GET /metrics NOT IMPLEMENTED Prometheus metrics endpoint (P2.16)

Pages (7 total)

Page Status Notes
Dashboard (/) COMPLETE Wired with /api/today, DecisionCard, DataPanel, NutritionPanel, OverrideToggles
Login (/login) COMPLETE Email/password form with auth, error handling, loading states
Settings (/settings) COMPLETE Form with cycleLength, notificationTime, timezone
Settings/Garmin (/settings/garmin) COMPLETE Token management UI, connection status, disconnect functionality, 27 tests
Calendar (/calendar) COMPLETE MonthView with navigation, ICS subscription section, token regeneration, 23 tests
History (/history) COMPLETE Table view with date filtering, pagination, decision styling, 26 tests
Plan (/plan) NOT IMPLEMENTED Placeholder only - shows heading and placeholder text, needs phase details display

Components

Component Status Notes
DecisionCard COMPLETE Displays status, icon, reason
DataPanel COMPLETE Shows BB, HRV, intensity data
NutritionPanel COMPLETE Shows seeds, carbs, keto guidance
OverrideToggles COMPLETE Toggle buttons with callbacks
DayCell COMPLETE Phase-colored day with click handler
MiniCalendar PARTIAL (~30%) Has header with cycle info only, MISSING: calendar grid with DayCell integration
MonthView COMPLETE Calendar grid with DayCell integration, navigation controls, phase legend

Test Coverage

Test File Status
src/lib/cycle.test.ts EXISTS - 9 tests
src/lib/decision-engine.test.ts EXISTS - 24 tests (8 algorithmic rules + 16 override scenarios)
src/lib/pocketbase.test.ts EXISTS - 9 tests (auth helpers, cookie loading)
src/lib/auth-middleware.test.ts EXISTS - 6 tests (withAuth wrapper, error handling)
src/middleware.test.ts EXISTS - 12 tests (page protection, public routes, static assets)
src/app/api/user/route.test.ts EXISTS - 21 tests (GET/PATCH profile, auth, validation, security)
src/app/api/cycle/period/route.test.ts EXISTS - 8 tests (POST period, auth, validation, date checks)
src/app/api/cycle/current/route.test.ts EXISTS - 10 tests (GET current cycle, auth, all phases, rollover, custom lengths)
src/app/api/today/route.test.ts EXISTS - 22 tests (daily snapshot, auth, decision, overrides, phases, nutrition, biometrics)
src/app/api/overrides/route.test.ts EXISTS - 14 tests (POST/DELETE overrides, auth, validation, type checks)
src/app/login/page.test.tsx EXISTS - 14 tests (form rendering, auth flow, error handling, validation)
src/app/page.test.tsx EXISTS - 23 tests (data fetching, component rendering, override toggles, error handling)
src/lib/nutrition.test.ts EXISTS - 17 tests (seed cycling, carb ranges, keto guidance by phase)
src/lib/email.test.ts EXISTS - 24 tests (email content, subject lines, formatting, token expiration warnings)
src/lib/ics.test.ts EXISTS - 23 tests (ICS format validation, 90-day event generation, timezone handling)
src/lib/encryption.test.ts EXISTS - 14 tests (encrypt/decrypt round-trip, error handling, key validation)
src/lib/garmin.test.ts EXISTS - 33 tests (fetchGarminData, fetchHrvStatus, fetchBodyBattery, fetchIntensityMinutes, token expiry, error handling)
src/app/api/garmin/tokens/route.test.ts EXISTS - 15 tests (POST/DELETE tokens, encryption, validation, auth)
src/app/api/garmin/status/route.test.ts EXISTS - 11 tests (connection status, expiry, warning levels)
src/app/api/cron/garmin-sync/route.test.ts EXISTS - 32 tests (auth, user iteration, token handling, Garmin data fetching, DailyLog creation, token expiration warnings, error handling)
src/app/api/cron/notifications/route.test.ts EXISTS - 20 tests (timezone matching, DailyLog handling, email sending)
src/app/api/calendar/[userId]/[token].ics/route.test.ts EXISTS - 10 tests (token validation, ICS generation, caching, error handling)
src/app/api/calendar/regenerate-token/route.test.ts EXISTS - 9 tests (token generation, URL formatting, auth)
src/app/api/history/route.test.ts EXISTS - 19 tests (pagination, date filtering, auth, validation)
src/app/api/health/route.test.ts EXISTS - 14 tests (healthy/unhealthy states, PocketBase connectivity, error handling)
src/app/history/page.test.tsx EXISTS - 26 tests (rendering, data loading, pagination, date filtering, styling)
src/components/calendar/month-view.test.tsx EXISTS - 21 tests (calendar grid, phase colors, navigation, legend)
src/app/calendar/page.test.tsx EXISTS - 23 tests (rendering, navigation, ICS subscription, token regeneration)
src/app/settings/page.test.tsx EXISTS - 24+ tests (form rendering, validation, submission)
src/app/settings/garmin/page.test.tsx EXISTS - 27 tests (connection status, token management)
src/components/dashboard/decision-card.test.tsx MISSING - Needs tests for rendering, status icons
src/components/dashboard/data-panel.test.tsx MISSING - Needs tests for biometrics display
src/components/dashboard/nutrition-panel.test.tsx MISSING - Needs tests for nutrition guidance display
src/components/dashboard/override-toggles.test.tsx MISSING - Needs tests for toggle state and callbacks
src/components/dashboard/mini-calendar.test.tsx MISSING - Needs tests for header/partial implementation
src/components/calendar/day-cell.test.tsx MISSING - Needs tests for phase coloring, click handler
E2E tests AUTHORIZED SKIP - Per specs/testing.md

Critical Business Rules (from Spec)

  1. Override Priority: flare > stress > sleep > pms (must be enforced in order)
  2. HRV Unbalanced: ALWAYS forces REST (highest algorithmic priority, non-overridable)
  3. Phase Limits: Strictly enforced per phase configuration
  4. Token Expiration Warnings: Must send email at 14 days and 7 days before expiry (IMPLEMENTED - P3.9 COMPLETE)
  5. ICS Feed: Generates 90 days of phase events for calendar subscription

P0: Critical Blockers ALL COMPLETE

These must be completed first - nothing else works without them.

P0.1: PocketBase Auth Helpers COMPLETE

  • Add authentication utilities to pocketbase.ts
  • Files:
    • src/lib/pocketbase.ts - Added createPocketBaseClient(), getCurrentUser(), isAuthenticated(), loadAuthFromCookies()
  • Tests:
    • src/lib/pocketbase.test.ts - 9 tests covering auth state management, cookie loading
  • Why: Every protected route and page depends on these helpers
  • Blocking: P0.2, P0.4, P1.1-P1.7, P2.2-P2.13

P0.2: Auth Middleware for API Routes COMPLETE

  • Create reusable auth middleware for protected API endpoints
  • Files:
    • src/lib/auth-middleware.ts - Added withAuth() wrapper for route handlers
    • src/middleware.ts - Added Next.js middleware for page protection
  • Tests:
    • src/lib/auth-middleware.test.ts - 6 tests covering unauthorized rejection, user context passing, error handling
    • src/middleware.test.ts - 12 tests covering protected routes, public routes, API routes, static assets
  • Why: All API routes except /api/calendar/[userId]/[token].ics and /api/cron/* require auth
  • Depends On: P0.1
  • Blocking: P0.4, P1.1-P1.5

P0.3: Decision Engine Override Handling COMPLETE

  • Add override priority logic before algorithmic decision
  • Files:
    • src/lib/decision-engine.ts - Added getDecisionWithOverrides(data, overrides) function
  • Tests:
    • src/lib/decision-engine.test.ts - 24 tests covering all 8 priority rules + override scenarios
  • Override Priority (enforced in this order):
    1. flare - Always forces REST
    2. stress - Forces REST
    3. sleep - Forces REST
    4. pms - Forces REST
  • Why: Overrides are core to the user experience per spec
  • Blocking: P1.4, P1.5

P0.4: GET /api/user Implementation COMPLETE

  • Return authenticated user profile
  • Files:
    • src/app/api/user/route.ts - Implemented GET handler with withAuth() wrapper
  • Tests:
    • src/app/api/user/route.test.ts - 4 tests covering auth, response shape, sensitive field exclusion
  • Response Shape:
    • id, email, garminConnected, cycleLength, lastPeriodDate, notificationTime, timezone, activeOverrides
    • Excludes sensitive fields: garminOauth1Token, garminOauth2Token, calendarToken
  • Why: Dashboard and all pages need user context
  • Depends On: P0.1, P0.2
  • Blocking: P1.7, P2.9, P2.10

P1: Core Functionality ALL COMPLETE

Minimum viable product - app can be used for daily decisions.

P1.1: PATCH /api/user Implementation COMPLETE

  • Allow profile updates (cycleLength, notificationTime, timezone)
  • Files:
    • src/app/api/user/route.ts - Implemented PATCH handler with validation
  • Tests:
    • src/app/api/user/route.test.ts - 17 tests covering field validation, persistence, security
  • Validation Rules:
    • cycleLength: number, range 21-45 days
    • notificationTime: string, HH:MM format (24-hour)
    • timezone: non-empty string
  • Security: Ignores attempts to update non-updatable fields (email, tokens)
  • Why: Users need to configure their cycle and preferences
  • Depends On: P0.1, P0.2

P1.2: POST /api/cycle/period Implementation COMPLETE

  • Log period start date, update user record, create PeriodLog
  • Files:
    • src/app/api/cycle/period/route.ts - Implemented POST handler with validation
  • Tests:
    • src/app/api/cycle/period/route.test.ts - 8 tests covering auth, date validation, user update, PeriodLog creation
  • Why: Cycle tracking is the foundation of all recommendations
  • Depends On: P0.1, P0.2

P1.3: GET /api/cycle/current Implementation COMPLETE

  • Return current cycle day, phase, and phase config
  • Files:
    • src/app/api/cycle/current/route.ts - Implemented GET using cycle.ts utilities with withAuth() wrapper
  • Tests:
    • src/app/api/cycle/current/route.test.ts - 10 tests covering auth, validation, all phases, cycle rollover, custom cycle lengths
  • Response Shape:
    • cycleDay, phase, phaseConfig, daysUntilNextPhase, cycleLength
  • Why: Dashboard needs this for display
  • Depends On: P0.1, P0.2, P1.2

P1.4: GET /api/today Implementation COMPLETE

  • Return complete daily snapshot with decision, biometrics, nutrition
  • Files:
    • src/app/api/today/route.ts - Implemented GET with withAuth() wrapper, aggregates cycle, biometrics, and nutrition
  • Tests:
    • src/app/api/today/route.test.ts - 22 tests covering auth, validation, decision calculation, overrides, phases, nutrition
  • Response Shape:
    • decision (status, reason, icon), cycleDay, phase, phaseConfig, daysUntilNextPhase, cycleLength
    • biometrics (hrvStatus, bodyBatteryCurrent, bodyBatteryYesterdayLow, weekIntensityMinutes, phaseLimit)
    • nutrition (seeds, carbRange, ketoGuidance)
  • Fallback Behavior: When no DailyLog exists (Garmin not synced), returns defaults: hrvStatus="Unknown", BB=100, weekIntensity=0
  • Why: This is THE core API for the dashboard
  • Depends On: P0.1, P0.2, P0.3, P1.3

P1.5: POST/DELETE /api/overrides Implementation COMPLETE

  • Toggle override flags on user record
  • Files:
    • src/app/api/overrides/route.ts - Implemented POST (add) and DELETE (remove) handlers with validation
  • Tests:
    • src/app/api/overrides/route.test.ts - 14 tests covering auth, override types, persistence, validation, edge cases
  • Override Types: flare, stress, sleep, pms
  • POST Response: Returns updated user with new override added to activeOverrides array
  • DELETE Response: Returns updated user with override removed from activeOverrides array
  • Validation: Rejects invalid override types, duplicates on POST, missing overrides on DELETE
  • Why: Emergency overrides are critical for flare days
  • Depends On: P0.1, P0.2, P0.3

P1.6: Login Page Implementation COMPLETE

  • Functional login form with PocketBase auth
  • Files:
    • src/app/login/page.tsx - Client component with email/password form, error handling, loading states, redirect
  • Tests:
    • src/app/login/page.test.tsx - 14 tests covering rendering, form submission, auth flow, error handling, validation
  • Infrastructure Added:
    • src/test-setup.ts - Global test setup with @testing-library/jest-dom and cleanup
    • Updated vitest.config.ts to include setupFiles
  • Why: Users need to authenticate to use the app
  • Depends On: P0.1

P1.7: Dashboard Page Implementation COMPLETE

  • Wire up dashboard with real data from /api/today
  • Integrate DecisionCard, DataPanel, NutritionPanel, OverrideToggles
  • Implement override toggle functionality with optimistic updates
  • Add error handling and loading states
  • Files:
    • src/app/page.tsx - Client component fetching /api/today, rendering all dashboard components
  • Tests:
    • src/app/page.test.tsx - 23 tests covering data fetching, component rendering, override toggles, error handling
  • Features Implemented:
    • Real-time decision display with cycle phase information
    • Biometrics panel (HRV, Body Battery, Intensity Minutes)
    • Nutrition guidance panel (seeds, carbs, keto)
    • Override toggles with optimistic UI updates
    • Error boundaries and loading states
  • Why: This is the main user interface
  • Depends On: P0.4, P1.3, P1.4, P1.5

P2: Important Features

Full feature set for production use.

P2.1: Garmin Data Fetching Functions COMPLETE

  • Add specific fetchers for HRV, Body Battery, Intensity Minutes
  • Files:
    • src/lib/garmin.ts - Added fetchHrvStatus(), fetchBodyBattery(), fetchIntensityMinutes()
  • Tests:
    • src/lib/garmin.test.ts - 33 tests covering API calls, response parsing, error handling (increased from 14 tests)
  • Functions Implemented:
    • fetchHrvStatus() - Fetches HRV status (balanced/unbalanced) from Garmin
    • fetchBodyBattery() - Fetches current and yesterday's low body battery values
    • fetchIntensityMinutes() - Fetches weekly moderate + vigorous intensity minutes
  • Why: Real biometric data is required for accurate decisions

P2.2: POST/DELETE /api/garmin/tokens Implementation COMPLETE

  • Store encrypted Garmin OAuth tokens
  • Files:
    • src/app/api/garmin/tokens/route.ts - POST/DELETE handlers with encryption, validation
  • Tests:
    • src/app/api/garmin/tokens/route.test.ts - 15 tests covering encryption, validation, storage, auth, deletion
  • Features Implemented:
    • POST: Accepts oauth1, oauth2, expires_at; encrypts tokens; stores in user record
    • DELETE: Clears tokens and sets garminConnected to false
    • Validation for required fields and types
    • Returns daysUntilExpiry in POST response
  • Why: Users need to connect their Garmin accounts
  • Depends On: P0.1, P0.2

P2.3: GET /api/garmin/status Implementation COMPLETE

  • Return Garmin connection status and days until expiry
  • Files:
    • src/app/api/garmin/status/route.ts - GET handler with expiry calculation
  • Tests:
    • src/app/api/garmin/status/route.test.ts - 11 tests covering connected/disconnected states, expiry calc, warning levels
  • Response Shape:
    • connected - Boolean indicating if tokens exist
    • daysUntilExpiry - Days until token expires (null if not connected)
    • expired - Boolean indicating if tokens have expired
    • warningLevel - "critical" (<=7 days), "warning" (8-14 days), or null
  • Why: Users need visibility into their Garmin connection
  • Depends On: P0.1, P0.2, P2.1

P2.4: POST /api/cron/garmin-sync Implementation COMPLETE

  • Daily sync of all Garmin data for all users
  • Files:
    • src/app/api/cron/garmin-sync/route.ts - Iterates users, fetches data, stores DailyLog
  • Tests:
    • src/app/api/cron/garmin-sync/route.test.ts - 32 tests covering auth, user iteration, token handling, Garmin data fetching, DailyLog creation, token expiration warnings, error handling
  • Features Implemented:
    • Fetches all users with garminConnected=true
    • Skips users with expired tokens
    • Decrypts OAuth2 tokens and fetches HRV, Body Battery, Intensity Minutes
    • Calculates cycle day, phase, phase limit, remaining minutes
    • Computes training decision using decision engine
    • Creates DailyLog entries for each user
    • Sends token expiration warning emails at 14 and 7 days before expiry
    • Returns sync summary (usersProcessed, errors, skippedExpired, timestamp)
  • Why: Automated data sync is required for morning notifications
  • Depends On: P2.1, P2.2

P2.5: POST /api/cron/notifications Implementation COMPLETE

  • Send daily email notifications at user's preferred time
  • Files:
    • src/app/api/cron/notifications/route.ts - Timezone-aware user matching, DailyLog fallback, email sending
  • Tests:
    • src/app/api/cron/notifications/route.test.ts - 20 tests covering timezone matching, DailyLog handling, email sending
  • Features Implemented:
    • Timezone-aware notification matching (finds users whose notificationTime matches current hour in their timezone)
    • DailyLog-based notifications with fallback to real-time calculation when DailyLog missing
    • Duplicate prevention (only sends once per user per hour)
    • Nutrition guidance integration (seeds, carbs, keto)
    • CRON_SECRET authentication
    • Returns summary with emailsSent count and timestamp
  • Why: Email notifications are a key feature per spec
  • Depends On: P2.4

P2.6: GET /api/calendar/[userId]/[token].ics Implementation COMPLETE

  • Return ICS feed for calendar subscription
  • Files:
    • src/app/api/calendar/[userId]/[token].ics/route.ts - Validates token, generates ICS with 90 days of phase events
  • Tests:
    • src/app/api/calendar/[userId]/[token].ics/route.test.ts - 10 tests covering token validation, ICS generation, caching headers, error handling
  • Features Implemented:
    • Token-based authentication (no session required)
    • Validates calendar token against user record
    • Generates 90 days of phase events using generateIcsFeed()
    • Returns proper Content-Type header (text/calendar; charset=utf-8)
    • Caching headers for calendar client optimization
    • 404 for non-existent users, 401 for invalid tokens
  • Why: Calendar integration for external apps

P2.7: POST /api/calendar/regenerate-token Implementation COMPLETE

  • Generate new calendar token
  • Files:
    • src/app/api/calendar/regenerate-token/route.ts - Creates random 32-char token, updates user
  • Tests:
    • src/app/api/calendar/regenerate-token/route.test.ts - 9 tests covering token generation, URL formatting, auth
  • Features Implemented:
    • Requires authentication via withAuth() middleware
    • Generates cryptographically secure 32-character hex token
    • Updates user's calendarToken field in database
    • Returns new token and formatted calendar URL
    • Old tokens immediately invalidated
  • Why: Security feature for calendar URLs
  • Depends On: P0.1, P0.2

P2.8: GET /api/history Implementation COMPLETE

  • Return paginated historical daily logs
  • Files:
    • src/app/api/history/route.ts - Query DailyLog with pagination, date filtering, validation
  • Tests:
    • src/app/api/history/route.test.ts - 19 tests covering pagination, date filtering, auth, validation
  • Features Implemented:
    • Pagination with page/limit parameters (default: page=1, limit=20)
    • Date filtering with startDate/endDate query params (YYYY-MM-DD format)
    • Validation for all parameters with descriptive error messages
    • Sort by date descending (most recent first)
    • Returns items, total, page, limit, totalPages, hasMore
  • Why: Users want to see their training history
  • Depends On: P0.1, P0.2

P2.9: Settings Page Implementation COMPLETE

  • User profile management UI
  • Files:
    • src/app/settings/page.tsx - Form for cycleLength, notificationTime, timezone with validation, loading states, error handling
  • Tests:
    • src/app/settings/page.test.tsx - 24 tests covering rendering, data loading, form submission, validation, error handling
  • Why: Users need to configure their preferences
  • Depends On: P0.4, P1.1

P2.10: Settings/Garmin Page Implementation COMPLETE

  • Garmin connection management UI
  • Files:
    • src/app/settings/garmin/page.tsx - Token input form, connection status, expiry warnings, disconnect button
  • Tests:
    • src/app/settings/garmin/page.test.tsx - 27 tests covering rendering, connection states, warning levels, token submission, disconnect flow
    • src/app/settings/page.test.tsx - 3 additional tests for Garmin link (28 total)
  • Features Implemented:
    • Connection status display with green/red/gray indicators
    • Token expiry warnings (yellow for 14 days, red for 7 days)
    • Token input form with JSON validation
    • Instructions for running bootstrap script
    • Disconnect functionality
    • Loading and error states
  • Why: Users need to manage their Garmin connection
  • Depends On: P0.4, P2.2, P2.3

P2.11: Calendar Page Implementation COMPLETE

  • In-app calendar with phase visualization
  • Files:
    • src/app/calendar/page.tsx - Month view with navigation, ICS subscription section with URL display, copy button, token regeneration
    • src/components/calendar/month-view.tsx - Complete calendar grid with DayCell integration, navigation controls, phase legend
  • Tests:
    • src/components/calendar/month-view.test.tsx - 21 tests covering calendar grid, phase colors, navigation, legend
    • src/app/calendar/page.test.tsx - 23 tests covering rendering, navigation, ICS subscription, token regeneration
  • Why: Planning ahead is a key user need
  • Depends On: P2.6

P2.12: History Page Implementation COMPLETE

  • View past training decisions and data
  • Files:
    • src/app/history/page.tsx - Data fetching, table display, pagination, date filtering
  • Tests:
    • src/app/history/page.test.tsx - 26 tests covering rendering, data loading, pagination, filtering, error handling
  • Why: Users want to review their training history
  • Depends On: P2.8

P2.13: Plan Page Implementation

  • Phase-specific training plan view
  • Current State: Placeholder only - shows "Exercise Plan" heading and placeholder text, no actual content
  • Files:
    • src/app/plan/page.tsx - Current phase details, upcoming phases, limits
  • Tests:
    • Component tests: phase info display, limits shown correctly
  • Features Needed:
    • Current phase details (name, day range, characteristics)
    • Upcoming phases preview
    • Phase-specific training limits and recommendations
    • Integration with cycle.ts utilities
  • Why: Users want detailed training guidance
  • Depends On: P0.4, P1.3

P2.14: Mini Calendar Component

  • Dashboard overview calendar
  • Current State: Component exists with header/cycle info only (~30% complete), NO calendar grid
  • Files:
    • src/components/dashboard/mini-calendar.tsx - Needs: complete calendar grid with phase colors using DayCell
  • Tests:
    • src/components/dashboard/mini-calendar.test.tsx - Component test: renders current month, highlights today
  • Features Needed:
    • Calendar grid (reuse DayCell component from MonthView)
    • Current week/month view
    • Phase color coding
    • Today highlight
  • Why: Quick visual reference on dashboard
  • Note: Can leverage existing MonthView/DayCell components for implementation

P2.15: Health Check Endpoint COMPLETE

  • GET /api/health for deployment monitoring
  • Current State: Fully implemented with PocketBase connectivity checks
  • Files:
    • src/app/api/health/route.ts - Returns health status with PocketBase connectivity check
  • Tests:
    • src/app/api/health/route.test.ts - 14 tests for healthy (200) and unhealthy (503) states
  • Response Shape:
    • status - "ok" or "unhealthy"
    • timestamp - ISO 8601 timestamp
    • version - Application version
  • Checks Performed:
    • PocketBase connectivity
    • Basic app startup complete
  • Why: Required for Nomad health checks, load balancer probes, and uptime monitoring (per specs/observability.md)

P2.16: Prometheus Metrics Endpoint

  • GET /metrics for monitoring
  • Current State: Endpoint and metrics library do not exist
  • Files:
    • src/app/api/metrics/route.ts - Returns Prometheus-format metrics
    • src/lib/metrics.ts - Metrics collection with prom-client
  • Tests:
    • src/app/api/metrics/route.test.ts - Tests for valid Prometheus format output
  • Metrics:
    • Standard Node.js metrics (heap, eventloop lag, http requests)
    • Custom: phaseflow_garmin_sync_total, phaseflow_email_sent_total, phaseflow_decision_engine_calls_total, phaseflow_active_users
  • Why: Required for Prometheus scraping and production monitoring (per specs/observability.md)
  • Depends On: None

P2.17: Structured Logging with Pino

  • Replace console.error with structured JSON logging
  • Current State: logger.ts does not exist, using console.log/error
  • Files:
    • src/lib/logger.ts - Pino logger configuration
    • All route files - Replace console.error/log with logger calls
  • Tests:
    • src/lib/logger.test.ts - Tests for log format, levels, and JSON output
  • Log Levels: error, warn, info
  • Key Events: Auth success/failure, Garmin sync, email sent/failed, decision calculated, period logged, override toggled
  • Why: Required for log aggregators (Loki, ELK) and production debugging (per specs/observability.md)
  • Depends On: None

P2.18: OIDC Authentication

  • Replace email/password login with OIDC (Pocket-ID)
  • Current State: Using email/password form, no OIDC code exists
  • Files:
    • src/app/login/page.tsx - Replace form with "Sign In with Pocket-ID" button
    • src/lib/pocketbase.ts - Add OIDC redirect and callback handling
  • Tests:
    • Update src/app/login/page.test.tsx - Tests for OIDC redirect flow
  • Flow:
    1. User clicks "Sign In with Pocket-ID"
    2. Redirect to Pocket-ID authorization endpoint
    3. User authenticates (MFA if configured)
    4. Callback with authorization code
    5. PocketBase exchanges code for tokens
    6. Redirect to dashboard
  • Environment Variables:
    • POCKETBASE_OIDC_CLIENT_ID
    • POCKETBASE_OIDC_CLIENT_SECRET
    • POCKETBASE_OIDC_ISSUER_URL
  • Why: Required per specs/authentication.md for secure identity management
  • Note: Current email/password implementation works but OIDC is the production requirement

P3: Polish and Quality

Testing, error handling, and refinements.

P3.1: Decision Engine Tests COMPLETE

  • Comprehensive unit tests for all decision paths
  • Files:
    • src/lib/decision-engine.test.ts - All 8 priority rules, override combinations (24 tests)
  • Test Cases Covered:
    • HRV Unbalanced always forces REST (highest algorithmic priority)
    • Override priority: flare > stress > sleep > pms
    • Phase limits strictly enforced
    • All override bypass and fallthrough scenarios
  • Why: Critical logic is now fully tested

P3.2: Nutrition Tests COMPLETE

  • Unit tests for nutrition guidance
  • Files:
    • src/lib/nutrition.test.ts - 17 tests covering seed cycling, carb ranges, keto guidance by phase
  • Test Cases Covered:
    • Seed cycling recommendations by phase (flax/pumpkin vs sunflower/sesame)
    • Carb range calculations per phase
    • Keto guidance by cycle day
    • Edge cases and phase transitions
  • Why: Nutrition advice accuracy is now fully tested

P3.3: Email Tests COMPLETE

  • Unit tests for email composition
  • Files:
    • src/lib/email.test.ts - 14 tests covering email content, subject lines, formatting
  • Test Cases Covered:
    • Daily email composition with decision data
    • Period confirmation email content
    • Subject line formatting
    • HTML email structure
  • Why: Email formatting correctness is now fully tested

P3.4: ICS Tests COMPLETE

  • Unit tests for calendar generation
  • Files:
    • src/lib/ics.test.ts - 23 tests covering ICS format validation, 90-day event generation, timezone handling
  • Test Cases Covered:
    • ICS feed generation with 90 days of phase events
    • RFC 5545 format compliance
    • Timezone handling (UTC conversion)
    • Event boundaries and phase transitions
  • Why: Calendar integration compatibility is now fully tested

P3.5: Encryption Tests COMPLETE

  • Unit tests for encrypt/decrypt round-trip
  • Files:
    • src/lib/encryption.test.ts - 14 tests covering AES-256-GCM round-trip, error handling, key validation
  • Test Cases Covered:
    • Encrypt/decrypt round-trip verification
    • Key validation and error handling
    • IV generation uniqueness
    • Malformed data handling
  • Why: Token security is now fully tested

P3.6: Garmin Tests COMPLETE

  • Unit tests for Garmin API interactions
  • Files:
    • src/lib/garmin.test.ts - 33 tests covering API calls, error handling, token expiry (expanded in P2.1)
  • Test Cases Covered:
    • fetchGarminData, fetchHrvStatus, fetchBodyBattery, fetchIntensityMinutes HTTP calls and response parsing
    • isTokenExpired logic with various expiry dates
    • daysUntilExpiry calculations
    • Error handling for invalid tokens and network failures
    • Response parsing for biometric data structures
  • Why: External API integration robustness is now fully tested

P3.7: Error Handling Improvements

  • Add consistent error responses across all API routes
  • Files:
    • All route files - Standardize error format, add logging
  • Why: Better debugging and user experience

P3.8: Loading States

  • Add loading indicators to all pages
  • Files:
    • All page files - Add loading.tsx or Suspense boundaries
  • Why: Better perceived performance

P3.9: Token Expiration Warnings COMPLETE

  • Email warnings at 14 and 7 days before Garmin token expiry
  • Files:
    • src/lib/email.ts - Added sendTokenExpirationWarning() function
    • src/app/api/cron/garmin-sync/route.ts - Added token expiry checking and warning logic
  • Tests:
    • src/lib/email.test.ts - 10 new tests for warning email function (24 total)
    • src/app/api/cron/garmin-sync/route.test.ts - 10 new tests for warning integration (32 total)
  • Features Implemented:
    • Sends warning email at exactly 14 days before token expiry
    • Sends warning email at exactly 7 days before token expiry
    • Warning logic integrated into garmin-sync cron job
    • Email includes days until expiry and instructions for refreshing tokens
  • Why: Users need time to refresh tokens (per spec requirement in specs/email.md)

P3.10: E2E Test Suite (AUTHORIZED SKIP)

  • Comprehensive end-to-end tests
  • Files:
    • tests/e2e/*.spec.ts - Full user flows
  • Test Scenarios:
    • Login flow
    • Period logging and phase calculation
    • Override toggle functionality
    • Settings update flow
    • Garmin connection flow
    • Calendar subscription
  • Why: Confidence in production deployment
  • Status: Per specs/testing.md: "End-to-end tests are not required for MVP (authorized skip)"

P3.11: Missing Component Tests

  • Add unit tests for untested components
  • Components Needing Tests (6 total):
    • src/components/dashboard/decision-card.tsx - Tests for rendering decision status, icon, and reason
    • src/components/dashboard/data-panel.tsx - Tests for biometrics display (BB, HRV, intensity)
    • src/components/dashboard/nutrition-panel.tsx - Tests for seeds, carbs, keto guidance display
    • src/components/dashboard/override-toggles.tsx - Tests for toggle states and callbacks (has interactive state)
    • src/components/dashboard/mini-calendar.tsx - Tests for header rendering (partial implementation)
    • src/components/calendar/day-cell.tsx - Tests for phase coloring and click handler
  • Test Files to Create:
    • src/components/dashboard/decision-card.test.tsx
    • src/components/dashboard/data-panel.test.tsx
    • src/components/dashboard/nutrition-panel.test.tsx
    • src/components/dashboard/override-toggles.test.tsx
    • src/components/dashboard/mini-calendar.test.tsx
    • src/components/calendar/day-cell.test.tsx
  • Why: Component isolation ensures UI correctness and prevents regressions

P4: UX Polish and Accessibility

Enhancements from spec requirements that improve user experience.

P4.1: Dashboard Onboarding Banners

  • Show setup prompts for missing configuration
  • Spec Reference: specs/dashboard.md mentions onboarding banners
  • Features Needed:
    • Banner when Garmin not connected
    • Banner when period date not set
    • Banner when notification time not configured
    • Dismissible after user completes setup
  • Files:
    • src/app/page.tsx - Add conditional banner rendering
    • src/components/dashboard/onboarding-banner.tsx - New component
  • Why: Helps new users complete setup for full functionality

P4.2: Accessibility Improvements

  • Keyboard navigation and focus indicators
  • Spec Reference: specs/dashboard.md accessibility requirements
  • Requirements:
    • Keyboard navigation for all interactive elements
    • Visible focus indicators (focus:ring styles)
    • 4.5:1 minimum contrast ratio
    • Screen reader labels where needed
  • Files:
    • All component files - Add focus:ring classes, aria-labels
  • Why: Required for accessibility compliance

P4.3: Dark Mode Configuration

  • Complete dark mode support
  • Current State: Partial Tailwind support via dark: classes exists in some components
  • Needs:
    • Configure prefers-color-scheme detection in tailwind.config.js
    • Theme toggle in settings (optional)
    • Ensure all components have dark: variants
    • Test contrast ratios in dark mode
  • Files:
    • tailwind.config.js - Add darkMode configuration
    • Component files - Verify dark: class coverage
  • Why: User preference for dark mode

P4.4: Loading Performance

  • Loading states within 100ms target
  • Spec Reference: specs/dashboard.md performance requirements
  • Features:
    • Skeleton loading states
    • Optimistic UI updates (partially done with overrides)
    • Suspense boundaries for code splitting
  • Files:
    • Page files - Add loading.tsx skeletons
  • Why: Perceived performance improvement

P4.5: Period Prediction Accuracy Feedback

  • Mark predicted vs confirmed period dates
  • Spec Reference: specs/calendar.md mentions predictions marked with "Predicted" suffix
  • Features:
    • Visual distinction between logged and predicted periods
    • Calendar events show "Predicted" label for future periods
  • Files:
    • src/lib/ics.ts - Add "Predicted" suffix to future phase events
    • src/components/calendar/day-cell.tsx - Visual indicator for predictions
  • Why: Helps users understand prediction accuracy

P4.6: Rate Limiting

  • Login attempt rate limiting
  • Spec Reference: specs/email.md mentions 5 login attempts per minute
  • Features:
    • Rate limit login attempts by IP/email
    • Show remaining attempts on error
    • Temporary lockout after exceeding limit
  • Files:
    • src/app/api/auth/route.ts or PocketBase config - Rate limiting logic
  • Why: Security requirement from spec

Implementation Order

P0.1 PocketBase Auth ──┬──> P0.2 Auth Middleware ──> P0.4 GET /api/user
                       │
P0.3 Override Logic ───┴──> P1.4 GET /api/today ──> P1.7 Dashboard

P1.1 PATCH /api/user ────> P2.9 Settings Page
P1.2 POST period ────────> P1.3 GET current ────> P1.7 Dashboard
P1.5 Overrides API ──────> P1.7 Dashboard
P1.6 Login Page ─────────> P2.18 OIDC Auth (upgrade)

P2.1 Garmin fetchers ──> P2.2 Garmin tokens ──> P2.4 Cron sync ──> P2.5 Notifications
                                                         │
                                                         └──> P3.9 Token Warnings
P2.3 Garmin status ────> P2.10 Garmin settings

P2.6 ICS endpoint ─────> P2.11 Calendar page
P2.7 Regen token

P2.8 History API ──────> P2.12 History page
P2.13 Plan page
P2.14 Mini calendar

P2.15 Health endpoint (independent - HIGH PRIORITY for deployment)
P2.16 Metrics endpoint (independent)
P2.17 Structured logging (independent, but should be done before other items for proper logging)

P3.11 Component tests ─> Can be done in parallel with other work
P4.* UX Polish ────────> After core functionality complete

Remaining Work Priority

Priority Task Effort Notes
Medium P2.13 Plan Page Medium Placeholder exists, needs content
Medium P2.14 MiniCalendar Small Can reuse DayCell, ~70% remaining
Medium P2.16 Metrics Medium Production monitoring
Medium P2.17 Logging Medium Should be done early for coverage
Medium P2.18 OIDC Auth Large Production auth requirement
Medium P3.11 Component Tests Medium 6 components need tests
Low P3.7 Error Handling Small Polish
Low P3.8 Loading States Small Polish
Low P4.* UX Polish Various After core complete

Dependency Summary

Task Blocked By Blocks
P0.1 - P0.2, P0.4, P1.1-P1.6, P2.2-P2.3, P2.7-P2.8
P0.2 P0.1 P0.4, P1.1-P1.5, P2.2-P2.3, P2.7-P2.8
P0.3 - P1.4, P1.5
P0.4 P0.1, P0.2 P1.7, P2.9, P2.10, P2.13
P2.16 - -
P2.17 - - (recommended early for logging coverage)
P2.18 P1.6 -
P3.9 P2.4 -
P3.11 - -

Completed

Library

  • cycle.ts - Complete with 9 tests (getCycleDay, getPhase, getPhaseConfig, getPhaseLimit)
  • decision-engine.ts - Complete with 24 tests (getTrainingDecision + getDecisionWithOverrides)
  • pocketbase.ts - Complete with 9 tests (createPocketBaseClient, isAuthenticated, getCurrentUser, loadAuthFromCookies)
  • nutrition.ts - Complete with 17 tests (getNutritionGuidance, getSeedSwitchAlert, phase-specific carb ranges, keto guidance) (P3.2)
  • email.ts - Complete with 24 tests (sendDailyEmail, sendPeriodConfirmationEmail, sendTokenExpirationWarning, email formatting) (P3.3, P3.9)
  • ics.ts - Complete with 23 tests (generateIcsFeed, ICS format validation, 90-day event generation) (P3.4)
  • encryption.ts - Complete with 14 tests (AES-256-GCM encrypt/decrypt, round-trip validation, error handling) (P3.5)
  • garmin.ts - Complete with 33 tests (fetchGarminData, fetchHrvStatus, fetchBodyBattery, fetchIntensityMinutes, isTokenExpired, daysUntilExpiry, error handling) (P2.1, P3.6)
  • auth-middleware.ts - Complete with 6 tests (withAuth() wrapper)
  • middleware.ts - Complete with 12 tests (Next.js page protection)

Components

  • DecisionCard - Displays decision status, icon, and reason
  • DataPanel - Shows body battery, HRV, intensity data
  • NutritionPanel - Shows seeds, carbs, keto guidance
  • OverrideToggles - Toggle buttons for flare/stress/sleep/pms
  • DayCell - Phase-colored calendar day cell with click handler
  • MonthView - Calendar grid with DayCell integration, navigation controls (prev/next month, Today button), phase legend, 21 tests

API Routes (16 complete, 1 not implemented)

  • GET /api/user - Returns authenticated user profile, 4 tests (P0.4)
  • PATCH /api/user - Updates user profile (cycleLength, notificationTime, timezone), 17 tests (P1.1)
  • POST /api/cycle/period - Logs period start date, updates user, creates PeriodLog, 8 tests (P1.2)
  • GET /api/cycle/current - Returns cycle day, phase, phaseConfig, daysUntilNextPhase, cycleLength, 10 tests (P1.3)
  • GET /api/today - Returns complete daily snapshot with decision, biometrics, nutrition, 22 tests (P1.4)
  • POST /api/overrides - Adds override to user.activeOverrides array, 14 tests (P1.5)
  • DELETE /api/overrides - Removes override from user.activeOverrides array, 14 tests (P1.5)
  • POST /api/garmin/tokens - Stores encrypted Garmin OAuth tokens, 15 tests (P2.2)
  • DELETE /api/garmin/tokens - Clears tokens and disconnects Garmin, 15 tests (P2.2)
  • GET /api/garmin/status - Returns connection status, expiry, warning level, 11 tests (P2.3)
  • POST /api/cron/garmin-sync - Daily sync of Garmin data for all connected users, creates DailyLogs, sends token expiration warnings, 32 tests (P2.4, P3.9)
  • POST /api/cron/notifications - Sends daily email notifications with timezone matching, DailyLog handling, nutrition guidance, 20 tests (P2.5)
  • GET /api/calendar/[userId]/[token].ics - Returns ICS feed with 90-day phase events, token validation, caching headers, 10 tests (P2.6)
  • POST /api/calendar/regenerate-token - Generates new 32-char calendar token, returns URL, 9 tests (P2.7)
  • GET /api/history - Paginated historical daily logs with date filtering, validation, 19 tests (P2.8)
  • GET /api/health - Health check endpoint with PocketBase connectivity check, 14 tests (P2.15)

Pages (6 complete, 1 placeholder)

  • Login Page - Email/password form with PocketBase auth, error handling, loading states, redirect, 14 tests (P1.6)
  • Dashboard Page - Complete daily interface with /api/today integration, DecisionCard, DataPanel, NutritionPanel, OverrideToggles, 23 tests (P1.7)
  • Settings Page - Form for cycleLength, notificationTime, timezone with validation, loading states, error handling, 28 tests (P2.9)
  • Settings/Garmin Page - Token input form, connection status, expiry warnings, disconnect functionality, 27 tests (P2.10)
  • Calendar Page - MonthView with navigation controls, ICS subscription section with URL display, copy button, token regeneration, 23 tests (P2.11)
  • History Page - Table view of DailyLogs with date filtering, pagination, decision styling, 26 tests (P2.12)

Test Infrastructure

  • test-setup.ts - Global test setup with @testing-library/jest-dom matchers and cleanup

P3: Quality and Testing

  • P3.1: Decision Engine Tests - Complete with 24 tests covering all 8 priority rules and override combinations
  • P3.2: Nutrition Tests - Complete with 17 tests covering seed cycling, carb ranges, keto guidance by phase
  • P3.3: Email Tests - Complete with 24 tests covering daily emails, period confirmation, token expiration warnings
  • P3.4: ICS Tests - Complete with 23 tests covering ICS format validation, 90-day event generation, timezone handling
  • P3.5: Encryption Tests - Complete with 14 tests covering AES-256-GCM round-trip, error handling, key validation
  • P3.6: Garmin Tests - Complete with 33 tests covering API interactions, token expiry, error handling
  • P3.9: Token Expiration Warnings - Complete with 10 new tests in email.test.ts, 10 new tests in garmin-sync/route.test.ts; sends warnings at 14 and 7 days before expiry

Discovered Issues

Bugs and inconsistencies found during implementation

  • src/lib/auth-middleware.ts does not exist - CREATED in P0.2
  • src/middleware.ts does not exist - CREATED in P0.2
  • ~garmin.ts is only 30% complete - missing specific biometric fetchers - FIXED in P2.1 (added fetchHrvStatus, fetchBodyBattery, fetchIntensityMinutes)
  • pocketbase.ts missing all auth helper functions - FIXED in P0.1
  • src/app/api/today/route.ts type error with null body battery values - FIXED (added null coalescing)

Notes

  1. TDD Approach: Each implementation task should follow TDD - write failing tests first, then implement
  2. Auth First: P0 items unlock all other work; prioritize ruthlessly
  3. Incremental Delivery: P1 completion = usable app without Garmin (manual data entry fallback)
  4. P2 Completion: Full feature set with automation
  5. P3: Quality and polish for production confidence
  6. P4: UX polish and accessibility improvements from spec requirements
  7. Component Reuse: Dashboard components are complete and can be used directly in P1.7
  8. HRV Rule: HRV Unbalanced status ALWAYS forces REST - this is the highest algorithmic priority and cannot be overridden by manual toggles
  9. Override Order: When multiple overrides are active, apply in order: flare > stress > sleep > pms
  10. Token Warnings: Per spec, warnings are sent at exactly 14 days and 7 days before expiry (P3.9 COMPLETE)
  11. Health Check Priority: P2.15 (GET /api/health) should be implemented early - it's required for deployment monitoring and load balancer health probes
  12. Structured Logging: P2.17 (pino logger) should be implemented before other P2 items if possible, so new code can use proper logging from the start
  13. OIDC vs Email/Password: Current email/password login (P1.6) works for development. P2.18 upgrades to OIDC for production security per specs/authentication.md
  14. E2E Tests: Authorized skip per specs/testing.md - unit and integration tests are sufficient for MVP
  15. Dark Mode: Partial Tailwind support exists via dark: classes but may need prefers-color-scheme configuration in tailwind.config.js (see P4.3)
  16. Component Tests: 6 components lack unit tests (P3.11) - DecisionCard, DataPanel, NutritionPanel, OverrideToggles, MiniCalendar, DayCell