- Add POST /api/auth/logout endpoint with tests (5 tests) - Add logout button to settings page (5 tests) - Add structured logging to garmin-sync cron (sync start/complete/failure) - Update IMPLEMENTATION_PLAN.md with spec gap analysis findings - Total: 835 tests passing across 44 test files Closes spec gaps from authentication.md (logout) and observability.md (logging) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
62 KiB
62 KiB
PhaseFlow Implementation Plan
This file is maintained by Ralph. Run ./ralph-sandbox.sh plan 3 to generate tasks.
Current State Summary
Overall Status: 835 tests passing across 44 test files
Library Implementation
| File | Status | Gap Analysis |
|---|---|---|
cycle.ts |
COMPLETE | 22 tests covering all functions including dynamic phase boundaries for variable cycle lengths |
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 | 33 tests covering generateIcsFeed (90 days of phase events), ICS format validation, timezone handling, period prediction feedback, CATEGORIES for calendar colors |
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 | 9 tests covering withAuth() wrapper for API route protection, structured logging for auth failures |
middleware.ts (Next.js) |
COMPLETE | 12 tests covering page protection, redirects to login |
logger.ts |
COMPLETE | 16 tests covering JSON output, log levels, error stack traces, child loggers |
metrics.ts |
COMPLETE | 33 tests covering metrics collection, counters, gauges, histograms, Prometheus format |
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 | COMPLETE |
| Structured Logging (pino) | specs/observability.md | P2.17 | COMPLETE |
| OIDC Authentication | specs/authentication.md | P2.18 | COMPLETE |
| Token Expiration Warnings | specs/email.md | P3.9 | COMPLETE |
API Routes (18 total)
| Route | Status | Notes |
|---|---|---|
| POST /api/auth/logout | COMPLETE | Clears pb_auth cookie, logs out user (5 tests) |
| 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 with prediction tracking (13 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 with period prediction feedback, caching headers (11 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 | COMPLETE | 33 tests (18 lib + 15 route) |
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, rate limiting |
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) |
COMPLETE | Phase overview, training guidelines, rebounding techniques, 16 tests |
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 |
COMPLETE | Compact calendar widget with phase colors, navigation, legend (23 tests) |
OnboardingBanner |
COMPLETE | Setup prompts for new users with icons and action buttons, 16 tests |
MonthView |
COMPLETE | Calendar grid with DayCell integration, navigation controls, phase legend, keyboard navigation |
Test Coverage
| Test File | Status |
|---|---|
src/lib/cycle.test.ts |
EXISTS - 22 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 - 9 tests (withAuth wrapper, error handling, structured logging) |
src/lib/logger.test.ts |
EXISTS - 16 tests (JSON format, log levels, error serialization, child loggers) |
src/lib/metrics.test.ts |
EXISTS - 18 tests (metrics collection, counters, gauges, histograms, Prometheus format) |
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 - 13 tests (POST period, auth, validation, date checks, prediction tracking) |
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 - 32 tests (form rendering, auth flow, error handling, validation, accessibility, rate limiting) |
src/app/page.test.tsx |
EXISTS - 28 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 - 33 tests (ICS format validation, 90-day event generation, timezone handling, period prediction feedback, CATEGORIES for colors) |
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 - 11 tests (token validation, ICS generation with period prediction feedback, 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/app/api/metrics/route.test.ts |
EXISTS - 15 tests (Prometheus format validation, metric types, route handling) |
src/components/calendar/month-view.test.tsx |
EXISTS - 30 tests (calendar grid, phase colors, navigation, legend, keyboard navigation) |
src/app/calendar/page.test.tsx |
EXISTS - 23 tests (rendering, navigation, ICS subscription, token regeneration) |
src/app/settings/page.test.tsx |
EXISTS - 34 tests (form rendering, validation, submission, accessibility, logout functionality) |
src/app/api/auth/logout/route.test.ts |
EXISTS - 5 tests (cookie clearing, success response, error handling) |
src/app/settings/garmin/page.test.tsx |
EXISTS - 27 tests (connection status, token management) |
src/components/dashboard/decision-card.test.tsx |
EXISTS - 11 tests (rendering, status icons, styling) |
src/components/dashboard/data-panel.test.tsx |
EXISTS - 18 tests (biometrics display, null handling, styling) |
src/components/dashboard/nutrition-panel.test.tsx |
EXISTS - 12 tests (seeds, carbs, keto guidance) |
src/components/dashboard/override-toggles.test.tsx |
EXISTS - 18 tests (toggle states, callbacks, styling) |
src/components/dashboard/mini-calendar.test.tsx |
EXISTS - 23 tests (calendar grid, phase colors, navigation, legend) |
src/components/dashboard/onboarding-banner.test.tsx |
EXISTS - 16 tests (setup prompts, icons, action buttons, interactions, dismissal) |
src/components/calendar/day-cell.test.tsx |
EXISTS - 27 tests (phase coloring, today highlighting, click handling, accessibility) |
src/app/plan/page.test.tsx |
EXISTS - 16 tests (loading states, error handling, phase display, exercise reference, rebounding techniques) |
src/app/layout.test.tsx |
EXISTS - 3 tests (skip navigation link rendering, accessibility) |
| E2E tests | AUTHORIZED SKIP - Per specs/testing.md |
Critical Business Rules (from Spec)
- Override Priority: flare > stress > sleep > pms (must be enforced in order)
- HRV Unbalanced: ALWAYS forces REST (highest algorithmic priority, non-overridable)
- Phase Limits: Strictly enforced per phase configuration
- Token Expiration Warnings: Must send email at 14 days and 7 days before expiry (IMPLEMENTED - P3.9 COMPLETE)
- 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- AddedcreatePocketBaseClient(),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- AddedwithAuth()wrapper for route handlerssrc/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 handlingsrc/middleware.test.ts- 12 tests covering protected routes, public routes, API routes, static assets
- Why: All API routes except
/api/calendar/[userId]/[token].icsand/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- AddedgetDecisionWithOverrides(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):
flare- Always forces RESTstress- Forces RESTsleep- Forces RESTpms- 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 withwithAuth()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 daysnotificationTime: 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 and prediction tracking
- Tests:
src/app/api/cycle/period/route.test.ts- 13 tests covering auth, date validation, user update, PeriodLog creation, prediction tracking
- 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 withwithAuth()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 withwithAuth()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,cycleLengthbiometrics(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.tsto 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- AddedfetchHrvStatus(),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 GarminfetchBodyBattery()- Fetches current and yesterday's low body battery valuesfetchIntensityMinutes()- 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 existdaysUntilExpiry- Days until token expires (null if not connected)expired- Boolean indicating if tokens have expiredwarningLevel- "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 and period prediction feedback
- Tests:
src/app/api/calendar/[userId]/[token].ics/route.test.ts- 11 tests covering token validation, ICS generation with period predictions, caching headers, error handling
- Features Implemented:
- Token-based authentication (no session required)
- Validates calendar token against user record
- Fetches period logs and passes them to ICS generator for prediction feedback
- 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
calendarTokenfield in database - Returns new token and formatted calendar URL
- Old tokens immediately invalidated
- Requires authentication via
- 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 flowsrc/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 regenerationsrc/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, legendsrc/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 ✅ COMPLETE
- Phase-specific training plan view
- Files:
src/app/plan/page.tsx- Phase overview, training guidelines, exercise reference, rebounding techniques
- Tests:
src/app/plan/page.test.tsx- 16 tests covering loading states, error handling, phase display, exercise reference, rebounding techniques
- Features Implemented:
- Current phase status display (day, phase name, training type, weekly limit)
- Phase overview cards for all 5 phases with weekly intensity minute limits
- Strength training exercises reference with descriptions
- Rebounding techniques organized by phase (follicular and luteal)
- Weekly guidelines for each phase with training goals
- Why: Users want detailed training guidance
- Depends On: P0.4, P1.3
P2.14: Mini Calendar Component ✅ COMPLETE
- Dashboard overview calendar
- Current State: COMPLETE - Compact calendar grid with phase colors, navigation buttons, today highlighting, phase legend
- Files:
src/components/dashboard/mini-calendar.tsx- Complete calendar grid with DayCell integration
- Tests:
src/components/dashboard/mini-calendar.test.tsx- 23 tests (calendar grid, phase colors, navigation, legend)
- Features Implemented:
- Calendar grid using DayCell component
- Current week/month view
- Phase color coding
- Today highlight
- Navigation buttons (prev/next month)
- Phase legend
- Why: Quick visual reference on dashboard
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 timestampversion- 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 ✅ COMPLETE
- GET /metrics for monitoring
- Current State: Fully implemented with prom-client
- Files:
src/app/api/metrics/route.ts- Returns Prometheus-format metrics (15 tests)src/lib/metrics.ts- Metrics collection with prom-client (18 tests)
- Tests:
src/lib/metrics.test.ts- 18 tests covering metrics collection, counters, gauges, histograms, Prometheus formatsrc/app/api/metrics/route.test.ts- 15 tests for Prometheus format output, metric types, route handling
- Metrics Implemented:
- Custom counters:
phaseflow_garmin_sync_total,phaseflow_email_sent_total,phaseflow_decision_engine_calls_total - Custom gauge:
phaseflow_active_users - Custom histogram:
phaseflow_garmin_sync_duration_seconds
- Custom counters:
- Integrations:
- garmin-sync route: garminSyncTotal, garminSyncDuration, activeUsersGauge
- email.ts: emailSentTotal (daily and warning types)
- decision-engine.ts: decisionEngineCallsTotal
- Why: Required for Prometheus scraping and production monitoring (per specs/observability.md)
P2.17: Structured Logging with Pino ✅ COMPLETE
- Create pino-based logger with JSON output
- Files:
src/lib/logger.ts- Pino logger configuration with LOG_LEVEL env var support
- Tests:
src/lib/logger.test.ts- 16 tests covering JSON format, log levels, error stack traces, child loggers
- Features Implemented:
- JSON output to stdout for log aggregators (Loki, ELK)
- Log levels: error, warn, info, debug
- LOG_LEVEL environment variable configuration (defaults to "info")
- Error objects serialized with type, message, and stack trace
- Child logger support for bound context
- ISO 8601 timestamps
- Why: Required for log aggregators and production debugging (per specs/observability.md)
- Next Step: Integrate logger into API routes (can be done incrementally)
P2.18: OIDC Authentication ✅ COMPLETE
- Replace email/password login with OIDC (Pocket-ID)
- Files:
src/app/login/page.tsx- OIDC button with email/password fallback
- Tests:
src/app/login/page.test.tsx- 24 tests (10 new OIDC tests)
- Features Implemented:
- Auto-detection of OIDC provider via
listAuthMethods()API - "Sign In with Pocket-ID" button when OIDC provider is configured
- Email/password form fallback when OIDC is not available
- PocketBase
authWithOAuth2()popup-based OAuth2 flow - Loading states during authentication
- Error handling with user-friendly messages
- Auto-detection of OIDC provider via
- Flow:
- Page checks for available auth methods on mount
- If OIDC provider configured, shows "Sign In with Pocket-ID" button
- User clicks button, PocketBase handles OAuth2 popup flow
- On success, user redirected to dashboard
- Falls back to email/password when OIDC not available
- Environment Variables (configured in PocketBase Admin):
- Client ID, Client Secret, Issuer URL configured in PocketBase
- Why: Required per specs/authentication.md for secure identity management
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- 28 tests covering ICS format validation, 90-day event generation, timezone handling, period prediction feedback
- 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
- Period prediction accuracy feedback ("Predicted" labels)
- 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 ✅ COMPLETE
- Add consistent error responses across all API routes
- Replace console.error with structured pino logger
- Add logging for key events per observability spec
- Files:
src/lib/auth-middleware.ts- Replaced console.error with structured logger, added auth failure loggingsrc/app/api/cycle/period/route.ts- Added "Period logged" event logging, structured error loggingsrc/app/api/calendar/[userId]/[token].ics/route.ts- Replaced console.error with structured loggersrc/app/api/overrides/route.ts- Added "Override toggled" event loggingsrc/app/api/today/route.ts- Added "Decision calculated" event loggingsrc/app/api/cron/garmin-sync/route.ts- Added "Garmin sync start", "Garmin sync complete", "Garmin sync failure" loggingsrc/app/api/auth/logout/route.ts- Added "User logged out" logging
- Tests:
src/lib/auth-middleware.test.ts- Added 3 tests for structured logging (9 total)
- Events Logged (per observability spec):
- Auth failure (warn): reason
- Period logged (info): userId, date
- Override toggled (info): userId, override, enabled
- Decision calculated (info): userId, decision, reason
- Garmin sync start (info): userId
- Garmin sync complete (info): userId, duration_ms, metrics (bodyBattery, hrvStatus)
- Garmin sync failure (error): userId, err object
- User logged out (info)
- Error events (error): err object with stack trace
- Why: Better debugging and user experience with structured JSON logs
P3.8: Loading States ✅ COMPLETE
- Add loading indicators to all pages
- Files:
src/components/dashboard/skeletons.tsx- Skeleton components (DecisionCardSkeleton, DataPanelSkeleton, NutritionPanelSkeleton, MiniCalendarSkeleton, OverrideTogglesSkeleton, CycleInfoSkeleton, DashboardSkeleton)src/components/dashboard/skeletons.test.tsx- 29 testssrc/app/loading.tsx- Dashboard route loading statesrc/app/calendar/loading.tsx- Calendar route loading statesrc/app/history/loading.tsx- History route loading statesrc/app/plan/loading.tsx- Plan route loading statesrc/app/settings/loading.tsx- Settings route loading state
- Features: Skeleton placeholders with shimmer animations matching spec requirements, updated dashboard page to use skeleton components
- 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- AddedsendTokenExpirationWarning()functionsrc/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 ✅ COMPLETE
- Add unit tests for untested components
- Components Tested (5 total):
src/components/dashboard/decision-card.tsx- 11 tests for rendering decision status, icon, reason, stylingsrc/components/dashboard/data-panel.tsx- 18 tests for biometrics display (BB, HRV, intensity), null handling, stylingsrc/components/dashboard/nutrition-panel.tsx- 12 tests for seeds, carbs, keto guidance displaysrc/components/dashboard/override-toggles.tsx- 18 tests for toggle states, callbacks, stylingsrc/components/calendar/day-cell.tsx- 23 tests for phase coloring, today highlighting, click handling
- Test Files Created:
src/components/dashboard/decision-card.test.tsx- 11 testssrc/components/dashboard/data-panel.test.tsx- 18 testssrc/components/dashboard/nutrition-panel.test.tsx- 12 testssrc/components/dashboard/override-toggles.test.tsx- 18 testssrc/components/calendar/day-cell.test.tsx- 23 tests
- Total Tests Added: 82 tests across 5 files
- 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 ✅ COMPLETE
- Show setup prompts for missing configuration
- Spec Reference: specs/dashboard.md mentions onboarding banners
- Implementation Details:
OnboardingBannercomponent created atsrc/components/dashboard/onboarding-banner.tsx- Tests at
src/components/dashboard/onboarding-banner.test.tsx- 16 tests - Dashboard integration at
src/app/page.tsx- 5 new tests added to page.test.tsx (28 total) - Features: Garmin connection banner, period date banner with callback, notification time banner
- Banners render at top of dashboard when setup incomplete
- Files:
src/app/page.tsx- Added conditional banner rendering at top of dashboardsrc/components/dashboard/onboarding-banner.tsx- New component with icons, action buttons, stylingsrc/components/dashboard/onboarding-banner.test.tsx- 16 tests covering rendering, interactions, dismissal
- Why: Helps new users complete setup for full functionality
P4.2: Accessibility Improvements ✅ COMPLETE
- Skip navigation link
- Semantic HTML landmarks (main elements)
- Screen reader labels for calendar buttons
- Keyboard navigation for calendar
- Spec Reference: specs/dashboard.md accessibility requirements
- Implementation Details:
- Skip navigation link added to layout with sr-only styling
- Semantic HTML landmarks (main element) added to login and settings pages
- Aria-labels added to DayCell calendar buttons with date and phase information
- Keyboard navigation: ArrowLeft/Right for prev/next day, ArrowUp/Down for prev/next week, Home/End for first/last day, boundary navigation triggers month change
- Files:
src/app/layout.tsx- Added skip navigation link with sr-only stylingsrc/app/layout.test.tsx- 3 tests for skip link rendering and accessibilitysrc/app/login/page.tsx- Wrapped content in main elementsrc/app/login/page.test.tsx- Added 2 accessibility tests (26 total)src/app/settings/page.tsx- Wrapped content in main elementsrc/app/settings/page.test.tsx- Added 2 accessibility tests (29 total)src/app/page.tsx- Added id="main-content" to existing main elementsrc/components/calendar/day-cell.tsx- Added aria-label with date/phase info, dataDay propsrc/components/calendar/day-cell.test.tsx- Added 4 accessibility tests (27 total)src/components/calendar/month-view.tsx- Added role="grid", keyboard navigation handlersrc/components/calendar/month-view.test.tsx- Added 9 keyboard navigation tests (30 total)
- Why: Required for accessibility compliance
P4.3: Dark Mode Configuration ✅ COMPLETE
- Complete dark mode support
- Current State: COMPLETE - Dark mode auto-detects system preference via
prefers-color-scheme - Implementation Details:
- Changed
@custom-variant darkfrom class-based to@media (prefers-color-scheme: dark)in globals.css - Changed
.darkCSS selector to@media (prefers-color-scheme: dark) { :root { ... } }for CSS variables - Dark mode now auto-detects system preference (no manual toggle needed per spec)
- No component changes needed - existing CSS variables handle the theming
- Changed
- Files:
src/app/globals.css- Updated dark mode detection to use media query instead of class
- Why: User preference for dark mode
P4.4: Loading Performance ✅ COMPLETE
- Loading states within 100ms target
- Spec Reference: specs/dashboard.md performance requirements
- Implementation Details:
- Skeleton loading states exist for all routes (P3.8 COMPLETE)
- Optimistic UI updates implemented for override toggles
- Suspense boundaries provided by Next.js App Router via loading.tsx files
- Next.js loading.tsx files render immediately during navigation (< 100ms)
- Files:
src/app/loading.tsx- Dashboard route loading (uses DashboardSkeleton)src/app/calendar/loading.tsx- Calendar route loadingsrc/app/history/loading.tsx- History route loadingsrc/app/plan/loading.tsx- Plan route loadingsrc/app/settings/loading.tsx- Settings route loadingsrc/components/dashboard/skeletons.tsx- Skeleton components (29 tests)
- Why: Perceived performance improvement
- Verification: Build succeeds, all 825 tests pass, Next.js handles 100ms target via automatic loading.tsx rendering
P4.5: Period Prediction Accuracy Feedback ✅ COMPLETE
- Mark predicted vs confirmed period dates
- Spec Reference: specs/calendar.md mentions predictions marked with "Predicted" suffix
- Files Modified:
src/types/index.ts- AddedpredictedDatefield to PeriodLog typesrc/lib/ics.ts- ModifiedgenerateIcsFeed()to accept period logs and mark events with "(Predicted)" when actual differs from predictedsrc/app/api/cycle/period/route.ts- POST handler calculates predicted date (lastPeriodDate + cycleLength), stores in PeriodLog, returns daysEarly/daysLatesrc/app/api/calendar/[userId]/[token].ics/route.ts- Fetches period logs and passes them to ICS generator
- Tests Added:
src/app/api/cycle/period/route.test.ts- 5 new tests (13 total): predictedDate storage, daysEarly/daysLate calculationssrc/lib/ics.test.ts- 5 new tests (28 total): "(Predicted)" label on events when actual differs from predictedsrc/app/api/calendar/[userId]/[token].ics/route.test.ts- 1 new test (11 total): period logs fetching and passing to ICS generator
- Features Implemented:
- PeriodLog stores predictedDate calculated from previous period (lastPeriodDate + cycleLength)
- POST /api/cycle/period calculates predicted vs actual date, returns daysEarly (negative) or daysLate (positive)
- ICS feed shows "(Predicted)" suffix on menstruation events when actual date differs from predicted date
- Calendar route fetches all period logs and passes them to ICS generator for prediction feedback
- Why: Creates feedback loop for understanding cycle prediction accuracy per calendar.md spec
P4.6: Rate Limiting ✅ COMPLETE
- Login attempt rate limiting
- Spec Reference: specs/email.md mentions 5 login attempts per minute
- Files:
src/app/login/page.tsx- Added client-side rate limiting with 5 attempts per minute
- Tests:
src/app/login/page.test.tsx- 6 new tests (32 total) covering rate limiting
- Features Implemented:
- Client-side rate limiting tracking login attempts with timestamps
- Rate limit: 5 attempts per minute (RATE_LIMIT_WINDOW_MS = 60000)
- Shows "Too many login attempts. Please try again in 1 minute." error when rate limited
- Shows remaining attempts warning after 3 failures
- Disables form/button when rate limited
- Auto-clears after cooldown period
- Works for both email/password and OIDC login
- 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 |
|---|---|---|---|
| Done | P4.4 Loading Performance | Complete | Next.js loading.tsx provides 100ms target |
| Done | P4.5 Period Prediction | Complete | Prediction tracking with feedback loop |
| Done | P4.6 Rate Limiting | Complete | Client-side rate limiting implemented |
All P4 UX Polish items are now 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 |
| P3.9 | P2.4 | - |
Completed
Library
- cycle.ts - Complete with 22 tests (
getCycleDay,getPhasewith dynamic boundaries for variable cycle lengths,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 33 tests (
generateIcsFeed, ICS format validation, 90-day event generation, period prediction feedback, CATEGORIES for calendar colors) (P3.4, P4.5) - 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)
- logger.ts - Complete with 16 tests (JSON output, log levels, error serialization, child loggers) (P2.17)
- metrics.ts - Complete with 18 tests (metrics collection, counters, gauges, histograms, Prometheus format) (P2.16)
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
- MiniCalendar - Compact calendar widget with phase colors, navigation, legend, 23 tests (P2.14)
API Routes (18 complete)
- POST /api/auth/logout - Clears session cookie, logs user out, 5 tests
- 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 with prediction tracking, 13 tests (P1.2, P4.5)
- 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 and period prediction feedback, token validation, caching headers, 11 tests (P2.6, P4.5)
- 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)
- GET /metrics - Prometheus metrics endpoint with counters, gauges, histograms, 33 tests (18 lib + 15 route) (P2.16)
Pages (7 complete)
- Login Page - OIDC (Pocket-ID) with email/password fallback, error handling, loading states, redirect, rate limiting, 32 tests (P1.6, P2.18, P4.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, logout button, 34 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)
- Plan Page - Phase overview, training guidance, exercise reference, rebounding techniques, 16 tests (P2.13)
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 28 tests covering ICS format validation, 90-day event generation, timezone handling, period prediction feedback
- 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.7: Error Handling Improvements - Replaced console.error with structured pino logger across API routes, added key event logging (Period logged, Override toggled, Decision calculated, Auth failure), 3 new tests in auth-middleware.test.ts
- P3.8: Loading States - Complete with skeleton components (DecisionCardSkeleton, DataPanelSkeleton, NutritionPanelSkeleton, MiniCalendarSkeleton, OverrideTogglesSkeleton, CycleInfoSkeleton, DashboardSkeleton), 29 tests in skeletons.test.tsx; loading.tsx files for all routes (dashboard, calendar, history, plan, settings); shimmer animations matching spec requirements
- 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
- P3.11: Missing Component Tests - Complete with 82 tests across 5 component test files (DecisionCard: 11, DataPanel: 18, NutritionPanel: 12, OverrideToggles: 18, DayCell: 23)
P4: UX Polish and Accessibility
- P4.1: Dashboard Onboarding Banners - Complete with OnboardingBanner component (16 tests), dashboard integration (5 new tests)
- P4.2: Accessibility Improvements - Complete with skip navigation, semantic landmarks, calendar screen reader labels, keyboard navigation (9 new tests)
- P4.3: Dark Mode Configuration - Complete with automatic dark mode via prefers-color-scheme media query
- P4.4: Loading Performance - Complete with Next.js loading.tsx providing 100ms target, skeleton components, and optimistic UI for overrides
- P4.5: Period Prediction Accuracy Feedback - Complete with predictedDate tracking in PeriodLog, daysEarly/daysLate calculations, "(Predicted)" labels in ICS feed (11 new tests across 3 files)
- P4.6: Rate Limiting - Complete with client-side login rate limiting (5 attempts per minute, 6 new tests)
Discovered Issues
Bugs and inconsistencies found during implementation
Spec Gaps Discovered (2025-01-11)
Analysis of all specs vs implementation revealed these gaps:
| Gap | Spec | Status | Notes |
|---|---|---|---|
| Logout functionality | authentication.md | COMPLETE | Added POST /api/auth/logout + settings button |
| Garmin sync structured logging | observability.md | COMPLETE | Added sync start/complete/failure logging |
| Email sent/failed logging | observability.md | PENDING | Email events should be logged |
| Period history UI | cycle-tracking.md | PENDING | UI for viewing/editing past periods |
| Dashboard color-coded backgrounds | dashboard.md | PENDING | Phase-based background colors |
| Toast notifications | dashboard.md | PENDING | Success/error toasts for user actions |
| CI pipeline | testing.md | PENDING | GitHub Actions for test/lint/build |
Previously Fixed Issues
CRITICAL: Cycle phase boundaries hardcoded for 31-day cycle- FIXED. Phase boundaries were not scaling with cycle length. The spec (cycle-tracking.md) defines formulas: MENSTRUAL 1-3, FOLLICULAR 4-(cycleLength-16), OVULATION (cycleLength-15)-(cycleLength-14), EARLY_LUTEAL (cycleLength-13)-(cycleLength-7), LATE_LUTEAL (cycleLength-6)-cycleLength. AddedgetPhaseBoundaries(cycleLength)function and updatedgetPhase()to accept cycleLength parameter. Updated all callers (API routes, components) to pass cycleLength. Added 13 new tests.ICS emojis did not match calendar.md spec- FIXED. Changed from colored circles (🔵🟢🟣🟡🔴) to thematic emojis (🩸🌱🌸🌙🌑) per spec.ICS missing CATEGORIES field for calendar app colors- FIXED. Added CATEGORIES field per calendar.md spec: MENSTRUAL=Red, FOLLICULAR=Green, OVULATION=Pink, EARLY_LUTEAL=Yellow, LATE_LUTEAL=Orange. Added 5 new tests.- CREATED in P0.2src/lib/auth-middleware.tsdoes not exist- CREATED in P0.2src/middleware.tsdoes not exist- ~
- FIXED in P2.1 (added fetchHrvStatus, fetchBodyBattery, fetchIntensityMinutes)garmin.tsis only30% complete - missing specific biometric fetchers - FIXED in P0.1pocketbase.tsmissing all auth helper functions- FIXED (added null coalescing)src/app/api/today/route.tstype error with null body battery values- Build requires RESEND_API_KEY and ENCRYPTION_KEY environment variables - works with
pnpm test:runbutpnpm buildfails without them. This is expected behavior for production builds.
Notes
- TDD Approach: Each implementation task should follow TDD - write failing tests first, then implement
- Auth First: P0 items unlock all other work; prioritize ruthlessly
- Incremental Delivery: P1 completion = usable app without Garmin (manual data entry fallback)
- P2 Completion: Full feature set with automation
- P3: Quality and polish for production confidence
- P4: UX polish and accessibility improvements from spec requirements
- Component Reuse: Dashboard components are complete and can be used directly in P1.7
- HRV Rule: HRV Unbalanced status ALWAYS forces REST - this is the highest algorithmic priority and cannot be overridden by manual toggles
- Override Order: When multiple overrides are active, apply in order: flare > stress > sleep > pms
- Token Warnings: Per spec, warnings are sent at exactly 14 days and 7 days before expiry (P3.9 COMPLETE)
- Health Check Priority: P2.15 (GET /api/health) should be implemented early - it's required for deployment monitoring and load balancer health probes
- Structured Logging: P2.17 (pino logger) is COMPLETE - new code should use
import { logger } from "@/lib/logger"for all logging - OIDC Authentication: P2.18 COMPLETE - Login page auto-detects OIDC via
listAuthMethods()and shows "Sign In with Pocket-ID" button when configured. Falls back to email/password when OIDC not available. Configure OIDC provider in PocketBase Admin under Settings → Auth providers → OpenID Connect - E2E Tests: Authorized skip per specs/testing.md - unit and integration tests are sufficient for MVP
- Dark Mode: Partial Tailwind support exists via dark: classes but may need prefers-color-scheme configuration in tailwind.config.js (see P4.3)
- Component Tests: P3.11 COMPLETE - All 5 dashboard and calendar components now have comprehensive unit tests (82 tests total)