Add override management API for the training decision system: - POST /api/overrides adds an override (flare, stress, sleep, pms) - DELETE /api/overrides removes an override - Both endpoints use withAuth middleware - Validation for override types, idempotent operations - 14 tests covering auth, validation, and persistence Also fix type error in today/route.ts where DailyLog body battery fields could be null but biometrics object expected numbers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
24 KiB
24 KiB
PhaseFlow Implementation Plan
This file is maintained by Ralph. Run ./ralph-sandbox.sh plan 3 to generate tasks.
Current State Summary
Library Implementation
| File | Status | Gap Analysis |
|---|---|---|
cycle.ts |
COMPLETE | 9 tests covering all functions, production-ready |
nutrition.ts |
Complete | getNutritionGuidance, getSeedSwitchAlert implemented. MISSING: tests |
email.ts |
Complete | sendDailyEmail, sendPeriodConfirmationEmail implemented. MISSING: tests |
ics.ts |
Complete | generateIcsFeed implemented (90 days of phase events). MISSING: tests |
encryption.ts |
Complete | AES-256-GCM encrypt/decrypt implemented. MISSING: tests |
decision-engine.ts |
COMPLETE | 8 priority rules + override handling with getDecisionWithOverrides(), 24 tests |
garmin.ts |
Minimal (~30%) | Has fetchGarminData, isTokenExpired, daysUntilExpiry. MISSING: fetchHrvStatus, fetchBodyBattery, fetchIntensityMinutes |
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 |
Missing Infrastructure Files (CONFIRMED NOT EXIST)
- CREATED in P0.2src/lib/auth-middleware.ts- CREATED in P0.2src/middleware.ts
API Routes (12 total)
| Route | Status | Notes |
|---|---|---|
| GET /api/user | COMPLETE | Returns user profile with withAuth() |
| PATCH /api/user | 501 | Returns Not Implemented |
| 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 | 501 | Returns Not Implemented |
| DELETE /api/garmin/tokens | 501 | Returns Not Implemented |
| GET /api/garmin/status | 501 | Returns Not Implemented |
| GET /api/calendar/[userId]/[token].ics | 501 | Has param extraction, core logic TODO |
| POST /api/calendar/regenerate-token | 501 | Returns Not Implemented |
| POST /api/cron/garmin-sync | 501 | Has CRON_SECRET auth check, core logic TODO |
| POST /api/cron/notifications | 501 | Has CRON_SECRET auth check, core logic TODO |
Pages (7 total, ALL placeholders)
| Page | Status | Notes |
|---|---|---|
Dashboard (/) |
Placeholder | Needs real data integration |
Login (/login) |
Placeholder | Needs PocketBase auth integration |
Settings (/settings) |
Placeholder | Needs form implementation |
Settings/Garmin (/settings/garmin) |
Placeholder | Needs token management UI |
Calendar (/calendar) |
Placeholder | Needs MonthView integration |
History (/history) |
Placeholder | Needs list/pagination implementation |
Plan (/plan) |
Placeholder | 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 only, MISSING: calendar grid |
MonthView |
Partial (~30%) | Has header only, MISSING: calendar grid + DayCell integration |
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 - 4 tests (GET profile, auth, sensitive field exclusion) |
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/lib/nutrition.test.ts |
MISSING |
src/lib/email.test.ts |
MISSING |
src/lib/ics.test.ts |
MISSING |
src/lib/encryption.test.ts |
MISSING |
src/lib/garmin.test.ts |
MISSING |
| E2E tests | NONE |
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
- ICS Feed: Generates 90 days of phase events for calendar subscription
P0: Critical Blockers
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
Minimum viable product - app can be used for daily decisions.
P1.1: PATCH /api/user Implementation
- Allow profile updates (cycleLength, notificationTime, timezone)
- Files:
src/app/api/user/route.ts- Implement PATCH handler with validation
- Tests:
src/app/api/user/route.test.ts- Test field validation, persistence
- 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 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
- Functional login form with PocketBase auth
- Files:
src/app/login/page.tsx- Form with email/password, error handling, redirect
- Tests:
- E2E test: valid login redirects to dashboard, invalid shows error
- Why: Users need to authenticate to use the app
- Depends On: P0.1
P1.7: Dashboard Page Implementation
- Wire up dashboard with real data from /api/today
- Files:
src/app/page.tsx- Fetch data, render DecisionCard, DataPanel, NutritionPanel, OverrideToggles
- Tests:
- E2E test: dashboard loads data, override toggles work
- Why: This is the main user interface
- Depends On: P0.4, P1.3, P1.4, P1.5
- Note: Components (DecisionCard, DataPanel, NutritionPanel, OverrideToggles) are already COMPLETE
P2: Important Features
Full feature set for production use.
P2.1: Garmin Data Fetching Functions
- Add specific fetchers for HRV, Body Battery, Intensity Minutes
- Files:
src/lib/garmin.ts- AddfetchHrvStatus(),fetchBodyBattery(),fetchIntensityMinutes()
- Tests:
src/lib/garmin.test.ts- Test API calls, response parsing, error handling
- Why: Real biometric data is required for accurate decisions
- Note: Currently only has generic fetchGarminData, isTokenExpired, daysUntilExpiry
P2.2: POST/DELETE /api/garmin/tokens Implementation
- Store encrypted Garmin OAuth tokens
- Files:
src/app/api/garmin/tokens/route.ts- Implement with encryption.ts
- Tests:
src/app/api/garmin/tokens/route.test.ts- Test encryption, validation, storage
- Why: Users need to connect their Garmin accounts
- Depends On: P0.1, P0.2
P2.3: GET /api/garmin/status Implementation
- Return Garmin connection status and days until expiry
- Files:
src/app/api/garmin/status/route.ts- Implement status check
- Tests:
src/app/api/garmin/status/route.test.ts- Test connected/disconnected states, expiry calc
- Why: Users need visibility into their Garmin connection
- Depends On: P0.1, P0.2, P2.1
P2.4: POST /api/cron/garmin-sync Implementation
- Daily sync of all Garmin data for all users
- Files:
src/app/api/cron/garmin-sync/route.ts- Iterate users, fetch data, store DailyLog
- Tests:
src/app/api/cron/garmin-sync/route.test.ts- Test auth, user iteration, data persistence
- Why: Automated data sync is required for morning notifications
- Depends On: P2.1, P2.2
- Note: Route exists with CRON_SECRET auth check, needs core logic
P2.5: POST /api/cron/notifications Implementation
- Send daily email notifications at user's preferred time
- Files:
src/app/api/cron/notifications/route.ts- Find users by hour, compute decision, send email
- Tests:
src/app/api/cron/notifications/route.test.ts- Test timezone handling, duplicate prevention
- Why: Email notifications are a key feature per spec
- Depends On: P2.4
- Note: Route exists with CRON_SECRET auth check, needs core logic
P2.6: GET /api/calendar/[userId]/[token].ics Implementation
- Return ICS feed for calendar subscription
- Files:
src/app/api/calendar/[userId]/[token].ics/route.ts- Validate token, generate ICS
- Tests:
- Integration test: valid token returns ICS, invalid returns 401
- Why: Calendar integration for external apps
- Note: Route has param extraction, needs ICS generation (90 days of events per spec)
P2.7: POST /api/calendar/regenerate-token Implementation
- Generate new calendar token
- Files:
src/app/api/calendar/regenerate-token/route.ts- Create random token, update user
- Tests:
src/app/api/calendar/regenerate-token/route.test.ts- Test token uniqueness, old URL invalidation
- Why: Security feature for calendar URLs
- Depends On: P0.1, P0.2
P2.8: GET /api/history Implementation
- Return paginated historical daily logs
- Files:
src/app/api/history/route.ts- Query DailyLog with pagination
- Tests:
src/app/api/history/route.test.ts- Test pagination, date filtering
- Why: Users want to see their training history
- Depends On: P0.1, P0.2
P2.9: Settings Page Implementation
- User profile management UI
- Files:
src/app/settings/page.tsx- Form for cycleLength, notificationTime, timezone
- Tests:
- E2E test: settings update and persist
- Why: Users need to configure their preferences
- Depends On: P0.4, P1.1
P2.10: Settings/Garmin Page Implementation
- Garmin connection management UI
- Files:
src/app/settings/garmin/page.tsx- Token input form, connection status, disconnect button
- Tests:
- E2E test: connect flow, disconnect flow
- Why: Users need to manage their Garmin connection
- Depends On: P0.4, P2.2, P2.3
P2.11: Calendar Page Implementation
- In-app calendar with phase visualization
- Files:
src/app/calendar/page.tsx- Month view with navigationsrc/components/calendar/month-view.tsx- Complete calendar grid using DayCell
- Tests:
- E2E test: navigation works, phases displayed correctly
- Why: Planning ahead is a key user need
- Depends On: P2.6
- Note: DayCell is COMPLETE, MonthView needs grid implementation (~70% remaining)
P2.12: History Page Implementation
- View past training decisions and data
- Files:
src/app/history/page.tsx- List view of DailyLogs with pagination
- Tests:
- E2E test: history loads, pagination works
- Why: Users want to review their training history
- Depends On: P2.8
P2.13: Plan Page Implementation
- Phase-specific training plan view
- Files:
src/app/plan/page.tsx- Current phase details, upcoming phases, limits
- Tests:
- E2E test: correct phase info displayed
- Why: Users want detailed training guidance
- Depends On: P0.4, P1.3
P2.14: Mini Calendar Component
- Dashboard overview calendar
- Files:
src/components/dashboard/mini-calendar.tsx- Complete calendar grid with phase colors
- Tests:
- Component test: renders current month, highlights today
- Why: Quick visual reference on dashboard
- Note: Component exists with header only, needs calendar grid (~70% remaining)
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
- Unit tests for nutrition guidance
- Files:
src/lib/nutrition.test.ts- Seed cycling, carb ranges, keto guidance by day
- Why: Nutrition advice must be accurate
P3.3: Email Tests
- Unit tests for email composition
- Files:
src/lib/email.test.ts- Email content, subject lines
- Why: Email formatting must be correct
P3.4: ICS Tests
- Unit tests for calendar generation
- Files:
src/lib/ics.test.ts- ICS format validation, 90-day event generation
- Why: Calendar integration must work with external apps
P3.5: Encryption Tests
- Unit tests for encrypt/decrypt round-trip
- Files:
src/lib/encryption.test.ts- Round-trip, error handling
- Why: Token security is critical
P3.6: Garmin Tests
- Unit tests for Garmin API interactions
- Files:
src/lib/garmin.test.ts- API calls, error handling, token expiry
- Why: External API integration must be robust
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
- Email warnings at 14 and 7 days before Garmin token expiry
- Files:
src/lib/email.ts- AddsendTokenExpirationWarning()src/app/api/cron/garmin-sync/route.ts- Check expiry, trigger warnings
- Tests:
- Test warning triggers at exactly 14 days and 7 days
- Why: Users need time to refresh tokens (per spec requirement)
P3.10: E2E Test Suite
- 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
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.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
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 |
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)
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
API Routes
- GET /api/user - Returns authenticated user profile, 4 tests (P0.4)
- 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)
Discovered Issues
Bugs and inconsistencies found during implementation
- CREATED in P0.2src/lib/auth-middleware.tsdoes not exist- CREATED in P0.2src/middleware.tsdoes not existgarmin.tsis only ~30% 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
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
- 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 must be sent at exactly 14 days and 7 days before expiry