When multiple dailyLog records exist for the same date (from multiple
syncs), getFirstListItem was returning the oldest one with stale data.
Now sorts by -created to return the most recent record.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Garmin API returns bodyBatteryValuesArray as [timestamp, level]
tuples (2 elements), not [timestamp, status, level, quality] (4 elements).
Was accessing index 2 which doesn't exist, now correctly using index 1.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Need to inspect the raw response structure to debug why current
and yesterdayLow values are missing despite hasCurrentData=true.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Garmin API uses path parameters, not query parameters:
- Correct: /usersummary-service/stats/im/weekly/{start}/{end}
- Wrong: /usersummary-service/stats/im/weekly?start=...&end=...
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Body Battery:
- Change endpoint from /usersummary-service/stats/bodyBattery/dates/
to /wellness-service/wellness/bodyBattery/reports/daily
- Parse new response format: array with bodyBatteryValuesArray time series
- Current value = last entry's level (index 2)
- YesterdayLow = min level from yesterday's data
Intensity Minutes:
- Change endpoint from /fitnessstats-service/activity
to /usersummary-service/stats/im/weekly
- Add date parameter to function signature
- Parse new response format: array with moderateValue/vigorousValue
Endpoints verified against python-garminconnect source code.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The connect.garmin.com/modern/proxy URL returns HTML (website) instead
of JSON API responses. Garth library uses connectapi.garmin.com subdomain
directly, which is the actual API endpoint.
- Change base URL from connect.garmin.com/modern/proxy to connectapi.garmin.com
- Update User-Agent to match garth library: GCM-iOS-5.19.1.2
- Factor out headers into getGarminHeaders() to avoid duplication
- Remove NK header (not needed when using connectapi subdomain)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Garmin now requires a mobile app User-Agent header (GCM-iOS-5.7.2.1)
for API access. Without it, they serve the website HTML instead of
JSON API responses.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Garmin is returning HTML error pages instead of JSON data. This
change reads the response as text first, checks if it starts with
{ or [, and logs the first 1000 chars of the response body if not.
This will help diagnose what page Garmin is returning (login, captcha,
rate limit, etc).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add phaseflow_build_info metric with version and commit labels
- Inject GIT_COMMIT env var at build time via next.config.ts
- Add logging to all Garmin fetch functions (HRV, body battery, intensity)
- Log API response status codes, actual data values, and errors
This enables verifying which build is deployed and diagnosing
silent failures where Garmin API returns errors but sync reports success.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PocketBase coerces null number fields to 0 when reading. When Garmin
API returned no data (null), we stored null, which became 0 on
retrieval. The nullish coalescing (?? 100) in the API route didn't
catch this because 0 is not nullish.
Now store default value 100 when Garmin returns null, matching the
existing pattern used for decision engine calculations.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PocketBase returns date fields as ISO strings, not Date objects.
The sync was failing with "e.getTime is not a function" because
the code expected Date objects.
- Export mapRecordToUser from pocketbase.ts
- Use mapRecordToUser in cron route to properly parse dates
- Add test for handling date fields as ISO strings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The cron job needs to list all users, but the users collection
doesn't have a public listRule (for security). Added admin
authentication so the job can access user records.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The period_logs collection was returning 403 errors because API rules
were only configured in the e2e test harness, not in the production
setup script. This consolidates the setup logic so both prod and test
use the same setupApiRules() function.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix OIDC tests with route interception for auth-methods API
- Add data-testid to DecisionCard for reliable test selection
- Fix /api/today to fetch fresh user data instead of stale cookie data
- Fix period logging test timing with proper API wait patterns
- Fix decision engine test with waitForResponse instead of timeout
- Simplify mobile viewport test locator
All 206 e2e tests now pass with 0 skipped.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Creates test infrastructure to enable previously skipped e2e tests:
- Onboarding user (no period data) for setup flow tests
- Established user (period 14 days ago) for normal usage tests
- Calendar user (with calendarToken) for ICS feed tests
- Garmin user (valid tokens) for connected state tests
- Garmin expired user (expired tokens) for expiry warning tests
Also fixes ICS feed route to strip .ics suffix from Next.js dynamic
route param, adds calendarToken to /api/user response, and sets
viewRule on users collection for unauthenticated ICS access.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add OAuth1 to OAuth2 token exchange using Garmin's exchange endpoint
- Track refresh token expiry (~30 days) instead of access token expiry (~21 hours)
- Auto-refresh access tokens in cron sync before they expire
- Update Python script to output refresh_token_expires_at
- Add garminRefreshTokenExpiresAt field to User type and database schema
- Fix token input UX: show when warning active, not just when disconnected
- Add Cache-Control headers to /api/user and /api/garmin/status to prevent stale data
- Add oauth-1.0a package for OAuth1 signature generation
The system now automatically refreshes OAuth2 tokens using the stored OAuth1 token,
so users only need to re-run the Python auth script every ~30 days (when refresh
token expires) instead of every ~21 hours (when access token expires).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Increase garminOauth1Token and garminOauth2Token max length from
5000 to 20000 characters to accommodate encrypted OAuth tokens.
Add logic to update existing field constraints in addUserFields().
2. Fix flaky pocketbase-harness e2e test by adding retry logic with
exponential backoff to createAdminUser() and createTestUser().
Handles SQLite database lock during PocketBase startup migrations.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix race conditions: Set workers: 1 since all tests share test user state
- Fix stale data: GET /api/user and /api/cycle/current now fetch fresh data
from database instead of returning stale PocketBase auth store cache
- Fix timing: Replace waitForTimeout with retry-based Playwright assertions
- Fix mobile test: Use exact heading match to avoid strict mode violation
- Add test user setup: Include notificationTime and update rule for users
All 1014 unit tests and 190 E2E tests pass.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The suggested notifications.spec.ts E2E tests for notification preferences
are already thoroughly covered by existing settings.spec.ts tests which
validate notification time and timezone input, validation, and persistence.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Verifies that the app correctly applies light/dark mode styling based on
the user's system preference (prefers-color-scheme). Tests cover both
light and dark modes using Playwright's emulateMedia API.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add tests to verify error handling when network requests fail:
- Error toast when token save fails (500 response)
- Error toast when disconnect fails (500 response)
- Error state display when status fetch fails
- Retry succeeds after network failure
These tests improve resilience coverage for the Garmin connection flow.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests mobile responsiveness including:
- Login page renders correctly on mobile viewport
- Dashboard displays correctly on mobile viewport
- Dashboard uses single-column layout on mobile (< 768px)
- Navigation elements are interactive on mobile
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New auth.spec.ts tests:
- OIDC button shows provider name when configured
- OIDC button shows loading state during authentication
- OIDC button is disabled when rate limited
- Session persists after page refresh
- Session persists when navigating between pages
- Logout clears session and redirects to login
E2E test count: 180 → 186 (auth.spec.ts: 14 → 20)
Total tests: 1194 → 1200
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests added:
- notification time changes persist after page reload
- timezone changes persist after page reload
- multiple settings changes persist after page reload
- cycle length persistence verifies exact saved value
- settings form shows correct values after save without reload
Updated IMPLEMENTATION_PLAN.md with accurate E2E test counts (now 180 E2E tests).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New tests cover:
- Period date modal opens from dashboard onboarding banner
- Period date input restricts future dates via max attribute
- Logging period from modal updates dashboard cycle info
- Edit period modal flow changes date successfully
- Delete period confirmation flow removes entry
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New tests:
- Yellow warning banner when token expires in 10 days (warning level)
- Red critical banner when token expires in 5 days (critical level)
- Expired token state shows token input for re-entry
- Connection persists after page reload
- Can reconnect after disconnecting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reduced the implementation plan from 1514 lines to 156 lines (90% reduction)
by consolidating completed items into compact summary tables. The project is
feature complete with 1014 unit tests and 165 E2E tests passing.
Key changes:
- Removed detailed task descriptions for completed P0-P5 items
- Consolidated library, API, page, component, and E2E test summaries
- Preserved critical business rules and architecture notes
- Kept E2E enhancement opportunities for future reference
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Period logging tests (5 new):
- Future date validation
- Cycle length display between periods
- Prediction accuracy display
- Delete period log from history
- Edit period log from history
Calendar tests (8 new):
- Today highlight in calendar view
- Phase colors in calendar days
- Phase legend display
- Today button for quick navigation
- Multi-month navigation with return to today
- Calendar URL generation
- URL format validation
- Copy to clipboard functionality
Total E2E tests: 113 (was 100)
Total unit tests: 1014 (51 test files)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New E2E test files:
- e2e/health.spec.ts: 3 tests for health/observability endpoints
- e2e/history.spec.ts: 7 tests for history page
- e2e/plan.spec.ts: 7 tests for exercise plan page
- e2e/decision-engine.spec.ts: 8 tests for decision display and overrides
- e2e/cycle.spec.ts: 11 tests for cycle tracking, settings, and period logging
Total E2E tests: 100 (up from 64)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Root cause: The setup-db script was missing user field definitions
(garminConnected, tokens, etc.). Production PocketBase had no such
fields, so updates silently failed to persist.
Changes:
- Add user custom fields to setup-db.ts (matches e2e harness)
- Fix status route to use strict boolean check (=== true)
- Add verification in tokens route with helpful error message
- Add ENCRYPTION_KEY to playwright config for e2e tests
- Add comprehensive e2e tests for Garmin connection flow
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, 15 e2e tests were skipped because TEST_USER_EMAIL and
TEST_USER_PASSWORD env vars weren't set. Now the test harness:
- Starts a fresh PocketBase instance in /tmp on port 8091
- Creates admin user, collections, and API rules automatically
- Seeds test user with period data for authenticated tests
- Cleans up temp directory after tests complete
Also fixes:
- Override toggle tests now use checkbox role (not button)
- Adds proper wait for OVERRIDES section before testing toggles
- Suppresses document.cookie lint warning with explanation
Test results: 64 e2e tests pass, 1014 unit tests pass
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- NutritionPanel: Display seed switch alert on day 15 per dashboard spec
- MonthView: Add phase emojis to legend (🩸🌱🌸🌙🌑) per calendar spec
- DayCell: Show period indicator (🩸) for days 1-3 per calendar spec
- Auth middleware: Log client IP from x-forwarded-for/x-real-ip per observability spec
- Updated NutritionGuidance type to include seedSwitchAlert field
- /api/today now returns seedSwitchAlert in nutrition response
Test coverage: 1005 tests (15 new tests added)
- nutrition-panel.test.tsx: +4 tests
- month-view.test.tsx: +1 test
- day-cell.test.tsx: +5 tests
- auth-middleware.test.ts: +3 tests
- today/route.test.ts: +2 tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Email subject now follows spec format: PhaseFlow: [STATUS] - Day [cycleDay] ([phase])
- Daily email includes seed switch alert on day 15 (using getSeedSwitchAlert)
- Data panel HRV status now color-coded: green=Balanced, red=Unbalanced, gray=Unknown
- Data panel shows progress bar for week intensity vs phase limit with color thresholds
Adds 13 new tests (990 total).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The spec (decision-engine.md lines 93-94) clearly states:
- sleep override -> GENTLE
- pms override -> GENTLE
But the implementation was returning REST for all overrides. This fix:
- Updates decision-engine.ts to use OVERRIDE_DECISIONS with correct status/reason/icon per override type
- Updates tests to expect GENTLE for sleep and pms overrides
- Aligns implementation with specification
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update gap analysis note to reflect current test counts (977 unit tests across
50 files + 64 E2E tests) and complete status of all P0-P5 items.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Mark P3.10 E2E Test Suite as COMPLETE (was showing unchecked)
- Update file path from tests/e2e/ to e2e/ (actual location)
- Add reference to P5.4 which has full implementation details
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Creates Gitea Actions workflow that runs on pull requests and pushes
to main. Enforces quality gates (lint, typecheck, unit tests) in CI,
complementing the local Lefthook pre-commit hooks.
Features:
- Node.js 24 with pnpm 10 setup
- pnpm dependency caching for faster runs
- Linting with biome
- TypeScript type checking
- 950 unit tests via vitest
Completes P5.3 from the implementation plan.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add GET /api/period-history route with pagination, cycle length
calculation, and prediction accuracy tracking
- Add PATCH/DELETE /api/period-logs/[id] routes for editing and
deleting period entries with ownership validation
- Add /period-history page with table view, edit/delete modals,
and pagination controls
- Include 61 new tests covering all functionality
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fetch fresh user data from database in status endpoint instead of
relying on auth store cookie, which may be stale after token save.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add playwright-web-flake to flake.nix for NixOS browser support
- Pin @playwright/test@1.56.1 to match nixpkgs version
- Create playwright.config.ts with Chromium-only, auto-start dev server
- Add e2e/smoke.spec.ts with initial smoke tests
- Add .mcp.json for Claude browser control via MCP
- Update .gitignore for playwright artifacts
- Remove E2E test skip from spec.md Known Limitations
- Update specs/testing.md to require three-tier testing approach
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>