Commit Graph

83 Commits

Author SHA1 Message Date
6cd0c06396 Fix Garmin intensity minutes and add user-configurable phase goals
All checks were successful
Deploy / deploy (push) Successful in 2m38s
- Apply 2x multiplier for vigorous intensity minutes (matches Garmin)
- Use calendar week (Mon-Sun) instead of trailing 7 days for intensity
- Add HRV yesterday fallback when today's data returns empty
- Add user-configurable phase intensity goals with new defaults:
  - Menstrual: 75, Follicular: 150, Ovulation: 100
  - Early Luteal: 120, Late Luteal: 50
- Update garmin-sync and today routes to use user-specific phase limits

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 20:18:20 +00:00
a1495ff23f Fix garmin-sync to upsert dailyLogs instead of always creating
All checks were successful
Deploy / deploy (push) Successful in 1m39s
The sync was creating a new record every time it ran, causing duplicate
records for the same day. Combined with PocketBase's inability to sort
by the 'created' field, this caused the dashboard to display stale data.

Now checks for an existing record for the user+date before creating,
and updates the existing record if found.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 22:06:03 +00:00
a184909957 Fix PocketBase query error by sorting by date instead of created
All checks were successful
Deploy / deploy (push) Successful in 2m28s
The sort=-created parameter was causing PocketBase to return a 400 error
when querying dailyLogs. This is likely a compatibility issue with how
PocketBase handles the auto-generated 'created' field in certain query
combinations. Changing to sort by -date resolves the issue and makes
more semantic sense for dailyLogs which have one record per day.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 21:55:41 +00:00
3e2d9047fb Fix PocketBase date query - use range operators not contains
All checks were successful
Deploy / deploy (push) Successful in 1m39s
The ~ contains operator doesn't work with PocketBase date fields.
Use >= and < operators with YYYY-MM-DD format instead, matching
the working /api/history pattern.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 15:24:00 +00:00
14bd0407f9 Fix PocketBase date format - use YYYY-MM-DD instead of ISO
Some checks failed
Deploy / deploy (push) Has been cancelled
PocketBase filters don't accept ISO format with T separator (causes 400).
Changed both garmin-sync storage and today route query to use simple
YYYY-MM-DD format, matching the working /api/history pattern.

TDD approach: wrote failing tests first, then implemented the fix.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 15:16:07 +00:00
5f8e913555 Fix dailyLog date query to use proper date comparison
All checks were successful
Deploy / deploy (push) Successful in 1m40s
- Change /api/today query from string contains (~) to date range (>=, <)
- Store dates in full ISO format in garmin-sync for consistent comparison
- PocketBase date fields need proper date operators, not string contains

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 14:37:53 +00:00
f923e1ce48 Add debug logging for dailyLog query
All checks were successful
Deploy / deploy (push) Successful in 2m39s
2026-01-15 14:28:47 +00:00
599a66bbb5 Sort dailyLogs by created DESC to get most recent record
All checks were successful
Deploy / deploy (push) Successful in 1m38s
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>
2026-01-15 14:25:16 +00:00
923b5fdb01 Fix body battery array parsing - use index 1 not 2
All checks were successful
Deploy / deploy (push) Successful in 2m39s
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>
2026-01-15 14:19:27 +00:00
c080e7054d Add debug logging for body battery API response
All checks were successful
Deploy / deploy (push) Successful in 2m28s
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>
2026-01-15 14:14:42 +00:00
83fd29b6c6 Fix intensity minutes URL to use path parameters
All checks were successful
Deploy / deploy (push) Successful in 1m38s
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>
2026-01-15 14:09:46 +00:00
cf89675b92 Fix body battery and intensity minutes Garmin API endpoints
All checks were successful
Deploy / deploy (push) Successful in 2m27s
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>
2026-01-15 13:58:04 +00:00
59d70ee414 Use connectapi.garmin.com directly instead of web proxy
All checks were successful
Deploy / deploy (push) Successful in 1m38s
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>
2026-01-15 13:38:55 +00:00
51f4c8eb80 Add User-Agent header to Garmin API requests
All checks were successful
Deploy / deploy (push) Successful in 2m29s
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>
2026-01-15 13:29:10 +00:00
98293f5ab5 Log raw response when Garmin returns non-JSON
All checks were successful
Deploy / deploy (push) Successful in 1m40s
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>
2026-01-15 13:20:28 +00:00
85b535f04a Add build info metric and diagnostic logging for Garmin sync
All checks were successful
Deploy / deploy (push) Successful in 1m44s
- 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>
2026-01-15 13:09:29 +00:00
7ed827f82c Fix body battery showing zeros on dashboard after Garmin sync
All checks were successful
Deploy / deploy (push) Successful in 2m29s
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>
2026-01-15 12:47:12 +00:00
3a06bff4d4 Fix Garmin sync to handle PocketBase date strings
All checks were successful
Deploy / deploy (push) Successful in 2m38s
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>
2026-01-15 07:38:37 +00:00
4ba9f44cef Add PocketBase admin auth to garmin-sync cron job
All checks were successful
Deploy / deploy (push) Successful in 2m28s
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>
2026-01-15 07:13:18 +00:00
4a874476c3 Enable 5 previously skipped e2e tests
All checks were successful
Deploy / deploy (push) Successful in 1m37s
- 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>
2026-01-15 06:30:51 +00:00
ff3d8fad2c Add Playwright fixtures with 5 test user types for e2e tests
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>
2026-01-15 05:54:49 +00:00
b221acee40 Implement automatic Garmin token refresh and fix expiry tracking
- 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>
2026-01-14 20:33:10 +00:00
00b84d0b22 Fix E2E test reliability issues and stale data bugs
- 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>
2026-01-13 20:23:32 +00:00
27f084f950 Fix Garmin token connection not persisting after save
All checks were successful
Deploy / deploy (push) Successful in 1m38s
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>
2026-01-13 13:20:50 +00:00
8c59b3bd67 Add self-contained e2e test harness with ephemeral PocketBase
Some checks failed
CI / quality (push) Failing after 29s
Deploy / deploy (push) Successful in 2m37s
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>
2026-01-13 09:38:24 +00:00
eeeece17bf Add spec compliance improvements: seed switch alert, calendar emojis, period indicator, IP logging
Some checks failed
CI / quality (push) Failing after 28s
Deploy / deploy (push) Successful in 2m38s
- 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>
2026-01-12 23:33:14 +00:00
d613417e47 Fix spec compliance gaps in email and dashboard
Some checks failed
CI / quality (push) Failing after 28s
Deploy / deploy (push) Successful in 2m39s
- 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>
2026-01-12 23:20:18 +00:00
0ea8e2f2b5 Fix decision-engine override behavior: sleep/pms return GENTLE per spec
Some checks failed
CI / quality (push) Failing after 28s
Deploy / deploy (push) Successful in 1m40s
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>
2026-01-12 23:11:47 +00:00
e971fe683f Add toast notification system with sonner library
- Create Toaster component wrapping sonner at bottom-right position
- Add showToast utility with success/error/info methods
- Error toasts persist until dismissed, others auto-dismiss after 5s
- Migrate error handling to toasts across all pages:
  - Dashboard (override toggle errors)
  - Settings (save/load success/error)
  - Garmin settings (connection success/error)
  - Calendar (load errors)
  - Period History (load/delete errors)
- Add dark mode support for toast styling
- Add Toaster provider to root layout
- 27 new tests (23 toaster component + 4 integration)
- Total: 977 unit tests passing

P5.2 COMPLETE - All P0-P5 items now complete.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:04:27 +00:00
07577dbdbb Add period history UI with CRUD operations
All checks were successful
Deploy / deploy (push) Successful in 2m27s
- 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>
2026-01-12 22:33:36 +00:00
dbf0c32588 Fix garmin status showing stale connection state
All checks were successful
Deploy / deploy (push) Successful in 1m37s
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>
2026-01-12 21:45:35 +00:00
ce80fb1ede Add database setup script and fix dark mode visibility
- Add scripts/setup-db.ts to programmatically create missing PocketBase
  collections (period_logs, dailyLogs) with proper relation fields
- Fix dark mode visibility across settings, login, calendar, and dashboard
  components by using semantic CSS tokens and dark: variants
- Add db:setup npm script and document usage in AGENTS.md
- Update vitest config to include scripts directory tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 21:23:20 +00:00
2408839b8b Fix 404 error when saving user preferences
Routes using withAuth were creating new unauthenticated PocketBase
clients, causing 404 errors when trying to update records. Modified
withAuth to pass the authenticated pb client to handlers so they can
use it for database operations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 16:45:55 +00:00
0e585e6bb4 Add period date setup modal for new users
All checks were successful
Deploy / deploy (push) Successful in 2m27s
Users without a lastPeriodDate can now set it via a modal opened from
the onboarding banner. The dashboard now fetches user data independently
so the banner shows even when /api/today fails due to missing period date.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 14:28:49 +00:00
72706bb91b Fix Invalid Date error in auth middleware
All checks were successful
Deploy / deploy (push) Successful in 2m28s
Add parseDate helper that safely returns null for empty/invalid date
strings from PocketBase. This prevents RangeError when pino logger
tries to serialize Invalid Date objects via toISOString().

- Make garminTokenExpiresAt and lastPeriodDate nullable in User type
- Filter garmin-sync cron to skip users without required dates
- Add test assertions for null date handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 14:13:02 +00:00
2ae6804cc4 Fix OAuth login by syncing auth state to cookie
All checks were successful
Deploy / deploy (push) Successful in 1m37s
Root cause: PocketBase SDK stores auth in localStorage, but Next.js
middleware checks for pb_auth cookie. The cookie was never being set
after successful OAuth login.

Fix: Add pb.authStore.onChange() listener that syncs auth state to
cookie on any change (login, logout, token refresh). This is the
idiomatic PocketBase pattern for Next.js SSR apps.

Also updates authentication spec to reflect that the cookie is
non-HttpOnly by design (client SDK needs read/write access).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 13:59:53 +00:00
e2afee2045 Add OAuth debugging console.log statements
All checks were successful
Deploy / deploy (push) Successful in 1m38s
Temporary debugging to diagnose why authWithOAuth2 promise
doesn't resolve after successful code exchange.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 13:49:02 +00:00
5cac8f3267 Add color-coded backgrounds to DecisionCard
All checks were successful
Deploy / deploy (push) Successful in 2m26s
Per dashboard.md spec requirements:
- RED background and text for REST decisions
- YELLOW background and text for GENTLE/LIGHT/REDUCED decisions
- GREEN background and text for TRAIN decisions

Added 8 new tests for color-coded backgrounds (19 total).
Updated IMPLEMENTATION_PLAN.md to mark spec gap as complete.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 23:11:10 +00:00
31932a88bf Add email sent/failed structured logging
All checks were successful
Deploy / deploy (push) Successful in 1m38s
Implement email logging per observability spec:
- Add structured logging for email sent (info level) and failed (error level)
- Include userId, type, and recipient fields in log events
- Add userId parameter to email functions (sendDailyEmail, sendPeriodConfirmationEmail, sendTokenExpirationWarning)
- Update cron routes (notifications, garmin-sync) to pass userId

6 new tests added to email.test.ts (now 30 tests total)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 23:06:19 +00:00
13b58c3c32 Add logout functionality and Garmin sync structured logging
- 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>
2026-01-11 23:00:54 +00:00
a977934c23 Fix critical bug: cycle phase boundaries now scale with cycle length
CRITICAL BUG FIX:
- Phase boundaries were hardcoded for 31-day cycle, breaking correct
  phase calculations for users with different cycle lengths (28, 35, etc.)
- Added getPhaseBoundaries(cycleLength) function in cycle.ts
- Updated getPhase() to accept cycleLength parameter (default 31)
- Updated all callers (API routes, components) to pass cycleLength
- Added 13 new tests for phase boundaries with 28, 31, and 35-day cycles

ICS IMPROVEMENTS:
- Fixed emojis to match calendar.md spec: 🩸🌱🌸🌙🌑
- Added CATEGORIES field for calendar app colors per spec:
  MENSTRUAL=Red, FOLLICULAR=Green, OVULATION=Pink,
  EARLY_LUTEAL=Yellow, LATE_LUTEAL=Orange
- Added 5 new tests for CATEGORIES

Updated IMPLEMENTATION_PLAN.md with discovered issues and test counts.

825 tests passing (up from 807)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:39:09 +00:00
58f6c5605a Add period prediction accuracy feedback (P4.5 complete)
All checks were successful
Deploy / deploy (push) Successful in 1m36s
Implements visual feedback for cycle prediction accuracy in ICS calendar feeds:

- Add predictedDate field to PeriodLog type for tracking predicted vs actual dates
- POST /api/cycle/period now calculates and stores predictedDate based on
  previous lastPeriodDate + cycleLength, returns daysEarly/daysLate in response
- ICS feed generates "(Predicted)" events when actual period start differs
  from predicted, with descriptions like "period arrived 2 days early"
- Calendar route fetches period logs and passes them to ICS generator

This creates an accuracy feedback loop helping users understand their cycle
variability over time per calendar.md spec.

807 tests passing across 43 test files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:21:52 +00:00
c708c2ed8b Add login rate limiting (P4.6 complete)
All checks were successful
Deploy / deploy (push) Successful in 1m38s
Implement client-side rate limiting for login page with 5 attempts
per minute, matching the spec requirement in authentication.md.

Features:
- Track login attempts with timestamps in component state
- Block login when 5+ attempts made within 60 seconds
- Show "Too many login attempts" error when rate limited
- Show remaining attempts warning after 3 failures
- Disable form/button when rate limited
- Auto-clear after 1 minute cooldown
- Works for both email/password and OIDC authentication

Tests:
- 6 new tests covering rate limiting scenarios (32 total)
- 796 tests passing across 43 test files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:09:34 +00:00
2e7d8dc4ca Add automatic dark mode via prefers-color-scheme (P4.3 complete)
All checks were successful
Deploy / deploy (push) Successful in 1m36s
Switch from class-based dark mode to automatic system preference
detection using CSS prefers-color-scheme media query. The app now
respects the user's OS-level dark mode setting without requiring
a manual toggle, as specified in the dashboard requirements.

Changes:
- Update Tailwind custom variant to use @media (prefers-color-scheme: dark)
- Change .dark selector to media query wrapping :root variables
- No component changes needed - existing CSS variable system handles theming

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:00:50 +00:00
4015f1ba3a Add calendar keyboard navigation (P4.2 complete)
All checks were successful
Deploy / deploy (push) Successful in 2m26s
Implement keyboard navigation for MonthView calendar:
- ArrowLeft/Right: navigate to previous/next day
- ArrowUp/Down: navigate to previous/next week (7 days)
- Home/End: navigate to first/last day of month
- Boundary navigation triggers month change

Features:
- Added role="grid" for proper ARIA semantics
- Added data-day attribute to DayCell for focus management
- Wrapped navigation handlers in useCallback for stability

Tests: 9 new tests for keyboard navigation (790 total)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:55:18 +00:00
649fa29df2 Add accessibility improvements (P4.2 partial)
All checks were successful
Deploy / deploy (push) Successful in 1m36s
- Add skip navigation link to root layout
- Add semantic HTML landmarks (main element) to login and settings pages
- Add aria-labels to calendar day buttons with date, cycle day, and phase info
- Add id="main-content" to dashboard main element for skip link target
- Fix pre-existing type error in auth-middleware.test.ts

Tests: 781 passing (11 new accessibility tests)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:49:26 +00:00
2bfd93589b Add dashboard onboarding banners (P4.1)
All checks were successful
Deploy / deploy (push) Successful in 2m29s
Implement OnboardingBanner component that prompts new users to complete
setup with contextual banners for:
- Garmin connection (links to /settings/garmin)
- Period date (button with callback for date picker)
- Notification time (links to /settings)

Banners display at the top of the dashboard when setup is incomplete,
with icons and styled action buttons. Each banner uses role="alert"
for accessibility.

- Add OnboardingBanner component (16 tests)
- Integrate into dashboard page (5 new tests, 28 total)
- Update UserData interface to include garminConnected, notificationTime
- Test count: 770 tests across 43 files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:38:16 +00:00
f69e1fd614 Separate server-side and client-side PocketBase URLs
All checks were successful
Deploy / deploy (push) Successful in 2m29s
Server-side code (health checks, API routes) needs to use internal
sidecar URL (POCKETBASE_URL at runtime), while client-side needs
public URL (NEXT_PUBLIC_POCKETBASE_URL baked at build time).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:17:58 +00:00
3567fbafd7 Expose PocketBase URL to client-side for OIDC auth
Some checks failed
Deploy / deploy (push) Failing after 6m37s
POCKETBASE_URL was only available server-side, causing the login page
to fall back to localhost:8090 in the browser. Renamed to
NEXT_PUBLIC_POCKETBASE_URL so Next.js bundles it into client code.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 17:33:52 +00:00
8f643c299d Add Nomad deployment configuration and CI/CD pipeline
Some checks failed
Deploy / deploy (push) Failing after 1m43s
- Add docker.nix for Nix-based Docker image builds
- Update flake.nix with dockerImage package output
- Add output: standalone to next.config.ts for production builds
- Add /metrics endpoint for Prometheus scraping
- Add Gitea Actions workflow calling shared deploy-nomad.yaml

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