Personal training dashboard with FastAPI backend, Vue 3 frontend, and MCP server for Claude integration.
See docs/README.md for planning and decision documents, including the roadmap.
Spec Kit is configured for this project. See the workflow guide for the skill sequence and artifact locations.
Use TrainLog as a Home Screen web app on the same Wi-Fi as your Mac. See the
iPhone setup guide for just phone, installation, and local-network behavior.
- Backend: FastAPI + SQLite (Python)
- Frontend: Vue 3 + Vite
- MCP Server: Python script that lets Claude push data directly
- Docker Compose: One command to run everything
docker compose up --buildOr use just to start it in the background:
justUse just up when you want Docker Compose to remain in the foreground.
- Frontend: http://localhost:3000
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/docs
Install the MCP dependency:
cd mcp
pip install -r requirements.txtAdd to your Claude Desktop config file:
Mac: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"training-dashboard": {
"command": "python",
"args": ["/ABSOLUTE/PATH/TO/training-dashboard/mcp/server.py"]
}
}
}Replace /ABSOLUTE/PATH/TO/ with the actual path. Then restart Claude Desktop.
The backend now also exposes the same MCP tools over HTTP at:
http://localhost:8000/mcp
Other useful commands:
just up
just restart
just logs
just down
just checkOne command runs everything: frontend behavior tests, the frontend build, the backend suite and the helper-script tests. It stops at the first failure.
just checkIn a fresh checkout, create the environments first (backend virtualenv plus npm ci):
just setup-checksEach part can also run on its own: just test-frontend, just build-frontend, just test-backend, just test-scripts. Tests must not depend on today's date: build dates relative to date.today() so they stay inside the window being checked (for example the current Monday–Sunday week), never fixed calendar days that age out.
The backend now has a lightweight smoke test pass that exercises the modularized app assembly and core routes.
Run it with:
just test-backendNotes:
- The test command uses
backend/.venvwhen it exists (python3 -m venv backend/.venv && backend/.venv/bin/pip install -r backend/requirements.txt), otherwise systempython3with a workspace-local dependency directory at.tmp_test_deps/. - The test app uses
TRAINING_DB_PATHto point SQLite at a temporary test database instead of/data/training.db. - In normal app usage,
TRAINING_DB_PATHis optional and defaults to/data/training.db.
Frontend behavior tests use Node's built-in test runner (frontend/tests/*.test.mjs, no extra dependencies):
cd frontend && npm testQuick verification before connecting ChatGPT:
curl http://localhost:8000/mcpExpected shape:
{
"name": "training-dashboard",
"version": "1.1.0",
"endpoint": "/mcp",
"transport": "jsonrpc-http"
}You can also verify initialize manually:
curl -X POST http://localhost:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'Notes:
- The existing
mcp/server.pyis still the localstdiobridge for Claude Desktop. - ChatGPT will need the remote HTTPS endpoint, not the local
stdioscript. - Read tools and write tools are both exposed; ChatGPT can write into the app through MCP once connected.
- The app itself still runs locally on your machine; only the tunnel URL is public.
- With the current setup there is no auth layer on
/mcp, so use a temporary tunnel only while you need it.
There is no app-level, one-click import from Claude chat history into ChatGPT in this project.
The practical approach is:
- Export or copy the important Claude thread.
- Start a new ChatGPT conversation.
- Paste a project summary or upload the exported text/file as context.
- Ask ChatGPT to continue from that history.
For this dashboard, the better long-term source of truth is the app itself:
- activities
- metrics
- coach notes
- weekly plans
Once ChatGPT is connected to /mcp, it can read those directly instead of relying on old chat history.
You can import activities straight from Strava through the backend, without using MCP tokens.
In the Strava API settings, get:
STRAVA_CLIENT_IDSTRAVA_CLIENT_SECRETSTRAVA_REFRESH_TOKEN
The backend uses the refresh token to obtain access tokens automatically.
Create a root .env file from the example:
cp .env.example .envThen fill in:
STRAVA_CLIENT_ID=your_client_id
STRAVA_CLIENT_SECRET=your_client_secret
STRAVA_REFRESH_TOKEN=your_refresh_tokenDocker Compose reads .env automatically. You can still export them in your shell instead if you prefer:
export STRAVA_CLIENT_ID=your_client_id
export STRAVA_CLIENT_SECRET=your_client_secret
export STRAVA_REFRESH_TOKEN=your_refresh_token
docker compose up --buildDate range is optional.
- If you leave dates empty, the backend syncs from the latest stored activity date through today.
- The start date is inclusive, so if you already have one workout on that day and later add another, the next sync still picks it up.
- For the very first import, set a custom start date if you want a historical backfill.
Use the Activities page import form, or call the backend directly:
curl -X POST http://localhost:8000/integrations/strava/import \
-H "Content-Type: application/json" \
-d '{"start_date":"2026-06-01","end_date":"2026-06-22"}'You can also let the backend choose the range automatically:
curl -X POST http://localhost:8000/integrations/strava/import \
-H "Content-Type: application/json" \
-d '{}'Check integration status:
curl http://localhost:8000/integrations/strava/statusImported activities retain a Strava source reference. Rerunning the same range refreshes the canonical activity, including one first created from HealthFit, instead of duplicating it.
HealthFit can be used as the always-available workout source when Strava API access is unavailable. The backend scans HealthFit's .fit backups from a read-only directory mount.
Set the host directory in .env:
HEALTHFIT_EXPORT_DIR="/Users/your-name/Library/Mobile Documents/iCloud~com~altifondo~HealthFit/Documents"Then rebuild/restart the services and open Sync → HealthFit Directory. Always run the preview before applying an import.
Duplicate protection is deliberately conservative:
- during the first initialization only, files strictly before the latest stored activity date are baselined by filename without opening or importing them;
- cutoff-day files are decoded and linked only when one compatible same-day activity exists;
- only unmatched files newer than the cutoff create activities;
- ambiguous matches are skipped for review;
- every processed file receives a durable HealthFit source reference;
- after initialization, every previously unseen file is parsed regardless of workout date, so late iCloud uploads are not hidden by the cutoff;
- a later Strava import attaches its Strava ID to a uniquely compatible HealthFit activity instead of inserting another activity.
The iCloud directory is mounted read-only at /healthfit. If macOS has evicted a recent FIT file from local storage, download it in Finder before scanning again.
| Page | Description |
|---|---|
| Dashboard | 14-day overview, recent runs/rides, Z2 pace trend, coach notes |
| Plan | Weekly workout plans prepared by Claude, shown day by day |
| Calendar | Weekly calendar view with daily activities, hours, distance, and elevation |
| Activities | Full activity log with filters by type |
| Trends | Overview plus focused Load, Recovery, Daily activity, Weight, and FTP views with personal-baseline charts |
| Coach Notes | All coaching observations categorized by topic |
Once the MCP server is connected, Claude can:
- Log activities — runs, rides, strength sessions from Strava analysis
- Read dashboard activity history — activities, stats, notes, plans, and calendar summaries directly from your app
- Read compact coaching context — one bundled MCP tool for recent load, latest activities, notes, metrics, streak, weekly mix, and active plan
- Add coach notes — observations about HR, pacing, fatigue, heel pain
- Log metrics — Z2 pace benchmarks, FTP, weight, resting HR, streak count, heel pain level
- Update weekly summaries — total km, elevation, sessions per week
- Save weekly plans — structured day-by-day workout weeks directly into the dashboard
- Adjust weekly plans — patch only the remaining days of the current week while preserving past or already completed sessions
"Log today's run to my dashboard and add a note about Zone 2 progress"
"Log my latest FTP test and use it as my cycling threshold"
"Add a coach note about the heel recovery progress"
"Look at my completed workouts so far this week and adjust the rest of my weekly plan"
| Zone | Running HR | Cycling HR |
|---|---|---|
| Z1 recovery | <150 | <140 |
| Z2 aerobic | 150–162 | 140–152 |
| Z3 tempo | 163–172 | 153–162 |
| Z4 threshold | 173–182 | 163–172 |
| Z5 max | 183+ | 173+ |
SQLite database stored in ./data/training.db — persists across container restarts.
The primary Trends experience uses planned sessions fulfilled, adapted, or missed across recent weeks. This respects intentional rest days and is more useful than rewarding consecutive training days.
The backend can read raw JSON files produced by the iOS Health Data Export app from an iCloud Drive directory mounted read-only into the container. Set HEALTH_DATA_EXPORT_DIR in .env; the backend checks for new files on startup and every 15 minutes by default. Data & Sync also provides preview and immediate-import controls. The importer streams large files instead of loading them into memory and safely skips both already-processed files and overlapping samples in later daily exports. Set HEALTH_DATA_AUTO_IMPORT=false to disable the background check or adjust HEALTH_DATA_IMPORT_INTERVAL_SECONDS when needed.
The same folder also accepts Shortcut*.json files written by a free iOS Shortcuts automation, which is more reliable than the export app's scheduled run; see docs/apple-health-shortcut.md.
The selective import covers sleep stages, resting heart rate, HRV, body weight, steps, walking/running distance, and flights climbed. Apple sleep category codes are normalized into core, deep, REM, awake, and unspecified sleep, and overlapping sleep providers are resolved to one nightly source. Raw all-day heart-rate samples and HealthKit workout records remain in the source export; HealthFit and cached run/ride streams stay authoritative for workouts and training-zone distribution.
The backend can still compute the legacy consecutive-day streak for compatibility and compact context:
- Any activity type counts toward the streak.
- If your latest activity was
todayoryesterday, the streak is considered active. - If the latest activity is older than yesterday, the current streak shows
0 days.
Strength exercise guides are available in the exercise picker, workout builder,
live workout runner, and lift details. They use bundled exercise illustrations from
Workout Guide, with optional
frame-by-frame playback and written instructions. Unknown exercise names are left unmatched;
guides never substitute a guessed variation. Assets and source attribution live
in frontend/public/exercise-guides/.
Backend hot-reloads automatically. For frontend changes:
docker compose upChanges to frontend/src/ and frontend/public/ reflect immediately. Recreate
an existing frontend container once to pick up the public-assets mount:
docker compose up -d --force-recreate frontend.
To reset all data:
rm -rf data/
docker compose up