The SEC-06 replacement swallowed the trailing comma into the comment:
allow_credentials=False # comment, ← comma in comment
Fixed to:
allow_credentials=False, # comment ← comma after value
Abstract base class for device sync services (device_sync_base.py):
- Standard OAuth token management with automatic refresh
- Credential decryption from integration_configs table
- Activity import pipeline with deduplication by external_id
- Webhook validation and handling interface
- get_valid_token(), get_credentials(), import_activity() helpers
Garmin, WHOOP, and Oura sync service implementations follow when
API credentials are obtained (Scot action items):
- Apply for Garmin Developer Program (DiligenceWorks Pte. Ltd.)
- Register WHOOP developer app at developer.whoop.com
- Register Oura app at cloud.ouraring.com
AI coaching service (backend/app/services/ai_provider.py):
- Two code paths: OpenAI-compatible (6 providers) + Anthropic (Claude)
- Streaming SSE responses via httpx async
- System prompt built from AGENT_GUIDE + live user context
- Graceful error handling for all provider failures
- Gemini adapter for Google's generateContent API
Chat endpoint (backend/app/routers/ai_chat.py):
- POST /api/ai/chat — SSE streaming response
- GET /api/ai/status — check configured provider
- History capped at 20 messages, content at 4K chars
Provider registry expanded to 20 providers:
- 9 device integrations (strava, polar, garmin, whoop, oura, coros, fitbit, withings, suunto)
- 8 AI providers (openai, openrouter, huggingface, groq, ollama, claude, gemini, custom_ai)
- 3 other (usda, nutritionix, telegram)
- Categorized: device, ai_provider, nutrition, notifications
- COROS: MCP bridge approach (no API integration needed)
- Strava: AI/ML warning per their ToS
Frontend ChatCoach.jsx to follow in next commit.
SEC-16: Add frontend ports mapping (80:80) to docker-compose.yml
UI-01: Add routes for Welcome, SettingsIntegrations, MealPlan in App.jsx
SEC-01: Remove traceback leak from auth error responses
SEC-02: Fix require_admin to use is_admin column (was undefined ADMIN_USERNAME)
SEC-03: Add API_TOKEN auth for MCP connector -> backend communication
SEC-04: OAuth state now uses signed JWT tokens (was raw user UUID)
OS-01: First registered user gets admin immediately during registration
10 files changed, 97 insertions(+), 25 deletions(-)
Two bugs fixed:
1. Backend: list_programs endpoint did not include catalog_id in its
response, so the frontend filter (p.catalog_id) always matched zero
programs. The previous LogActivity card never rendered because
loadActiveProgram() found no active program with a catalog link.
Now also exposes current_week.
2. UX restructure: instead of showing the program workout card at the
top of /log unconditionally, the card now appears AFTER the user
selects the 'Workout' category — matching the natural flow:
Log -> Workout -> see today's program workout option.
The Workout category now reveals:
- Active program banner (clickable, shows program name + week,
navigates to ProgramDetail on tap)
- Today's workout card with exercise preview and one-tap 75 pts
- Other uncompleted workouts in the current week (up to 3)
- 'Or log freeform' divider before the existing form
When all workouts in the current week are done, a green completion
banner appears instead.
Refactored the workout card into a ProgramWorkoutCard component since
it's now used for both today's featured workout and weekly upcoming.
Dashboard:
- The program bar at the top of the home page is now clickable and
navigates to the program detail page. Backend get_today_status now
returns program_id alongside name/day/total_days.
LogActivity:
- On mount, fetches the user's active catalog program and its today's
workout. If there's an uncompleted non-rest workout for today, a
featured 'Today's Program Workout' card appears at the top with an
exercise preview and a one-tap '✓ Complete — 75 pts' button.
- Tapping Complete fires the same /workout/{id}/complete endpoint as
ProgramDetail, surfaces weekly/completion bonuses in the success
message, and returns to the Dashboard.
- The standard category grid below is now relabeled 'Or log something
else' when a program workout is available.
Catalog stores only the template (typically week 1) but programs span
many weeks. The schedule endpoint was returning only the literal stored
rows, so after day 3 of StrongLifts a user would see no upcoming
workout despite being mid-program.
Changes:
- Add week_number column to workout_logs (migration in main.py) so the
same template workout can be completed once per real week
- Synthetic workout IDs: '{template_uuid}::{real_week}' — frontend
treats opaquely, backend parses on every endpoint
- get_program_schedule rotates the template across catalog.duration_weeks
real weeks and returns synthetic IDs
- today_workout: first uncompleted non-rest entry in current real week
(more flexible than literal day-of-week match)
- complete_workout / get_workout_detail parse synthetic IDs and track
completion per (template_id, real_week) pair
- check_weekly_bonus filters by workout_logs.week_number for the real week
- check_completion_bonus and progress endpoint compute total as
template_per_week * total_weeks, not just template count
ROOT CAUSE FOUND: 'db' resolved to multiple IPs on the Docker network.
Coolify's own PostgreSQL (or another stack's) also responds to 'db'
via the predefined network. Our backend was connecting to the WRONG
postgres — one that requires password auth.
Fix: rename service from 'db' to 'fitness-db' so the hostname is unique.
The actual bug: config.py default database_url included ':fitness' password.
When Coolify doesn't pass DATABASE_URL env var to the container,
pydantic_settings falls back to this default, which PostgreSQL rejects
because the DB initialized with trust authentication (no password).
Fix: removed password from default database_url in config.py.
Also removed unnecessary custom db/Dockerfile.
Coolify does not pass environment variables to non-build services (image-only).
PostgreSQL initializes with trust authentication by default in this case.
Solution: connect without password, explicitly set POSTGRES_HOST_AUTH_METHOD=trust.
Also removed debug endpoint.