OpenTrailPaper
Open-source bike computer firmware for the LilyGO T5S3 4.7" E-Paper PRO, with offline maps, GPX routes, FIT recording and Bluetooth sensors.
Personal marathon training planner
Translation pending
Sports & fitnessTypeScript · PLpgSQLGitHub @maximevalentin12-creatorOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Personal running training planner: generates weekly sessions from Garmin data, scores completed runs, tracks shoes and builds race-specific plans. Next.js + Supabase.
Taken from the repository README
Loading the full README.
Read the full README ↗A personal running journal with a rule-based training planner. It pulls your activities from Garmin Connect, builds a plan backward from your goal race (or a continuous week-by-week plan when there is none), then checks each completed run against what was planned and tells you why it did or didn't match. No machine learning, no LLM — fixed, readable rules.
It was built for one person (me) who runs 3–5 times a week and cross-trains alongside. It is a hobby project, not a product — see Known limitations.
The UI copy is in French.
lib/planner/config.ts.garminconnect, run by GitHub Actionsgoogleapis (Calendar), pdfkit (reports)app/ Next.js pages and API routes (app/api/*)
components/ Screen pieces (race sheet, week plan, tab bar…)
lib/planner/ The planners (pure TypeScript, no I/O): racePlan.ts, rules.ts, config.ts, paces.ts…
lib/auth/ Supabase session clients (server + browser)
lib/garmin/ CSV parsing and activity <-> planned-session matching
lib/google/ Calendar OAuth client and sync
lib/athlete/ Profile calibration (HR max, resting HR)
lib/reports/ Monthly summary and PDF generation
middleware.ts Auth gate for every page and API route
scripts/ sync_garmin.py (Garmin -> Supabase) and its tests
supabase/schema.sql Database schema; supabase/migrations/ holds one-off migrations
.github/workflows/ The scheduled Garmin sync
Authentication and data access. Every page and API route requires a signed-in user, except /login and the Google Calendar webhook (which authenticates Google's call with a per-channel secret). The server reads the user id from the verified session — never from the URL or a request body — and queries Supabase with the anon key plus the user's session, so the row-level-security policies (auth.uid() = user_id) are what protect the data. The service-role key is used in only two places: the Google webhook, and the Garmin sync job.
Garmin sync. scripts/sync_garmin.py logs in with your Garmin credentials once, on your machine (--login), and stores the session tokens in the garmin_tokens table. The scheduled GitHub Actions job (.github/workflows/sync-garmin.yml, 07:00 and 20:00 Paris time) only resumes that stored session: it never sends a password, and on a 429 or Cloudflare challenge it does not retry — it records a cooldown and refuses to contact Garmin until it passes. Because the repository is public, so are the job logs; the script logs only counts, dates and error class names (scripts/test_sync_garmin.py guards that). In production (Vercel) the "Actualiser mes séances" button does not run Python; it matches the freshly imported activities to your planned sessions. Locally the button still runs the script.
Planner rules. buildRacePlan() (lib/planner/racePlan.ts) turns a goal race, its tune-ups and your recent history into one entry per week; buildWeekPlan() (rules.ts) turns a week's entry, your weekly setup and your recent feelings into dated sessions. Without a goal race the older continuous mode (progression blocks, stored long run, volume brake) runs unchanged.
Requirements: Node.js 18+, Python 3.10+, a Supabase project, a Garmin Connect account. A Google Cloud OAuth client is only needed for Calendar sync.
npm install
python -m pip install -r scripts/requirements.txt
supabase/schema.sql in the Supabase SQL editor.supabase/migrations/2026-10-auth-cutover.sql (edit the user id and password at the top first). It creates the login with the id your data already uses and sets one row-level-security policy per table. For a brand-new database, insert your row in public.users first.cp .env.local.example .env.local
Fill it in — the file explains every variable. .env.local is gitignored; never commit it.npm run dev
Open http://localhost:3000 and sign in.python scripts/sync_garmin.py --login --user-id <your-user-uuid>
Reads SUPABASE_URL and SUPABASE_SERVICE_KEY from the environment or a .env file (the app passes them itself when it runs the script). Handles an MFA prompt.npm test # planner rules (node:test via tsx)
python -m unittest scripts/test_sync_garmin.py # Garmin script: log hygiene, no credential login in scheduled runs
npx tsc --noEmit # types
npm run build # production build
Vercel (the app)
vercel.json).NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY.SUPABASE_SERVICE_ROLE_KEY, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI, APP_BASE_URL.NEXT_PUBLIC_VAPID_PUBLIC_KEY (public), VAPID_PRIVATE_KEY (server-only), VAPID_SUBJECT (optional mailto:, defaults to the signed-in email). Generate with npx web-push generate-vapid-keys.GARMIN_USERNAME, GARMIN_PASSWORD (local use only).GitHub (the scheduled Garmin sync) — Settings → Secrets and variables → Actions → New repository secret:
| Secret | Value |
|---|---|
SUPABASE_URL | https://<project>.supabase.co |
SUPABASE_SERVICE_ROLE_KEY | the project's service_role key |
GARMIN_SYNC_USER_ID | your user uuid |
No Garmin password is stored on GitHub. Run the workflow once by hand (Actions → Sync Garmin → Run workflow) and check it is green. GitHub pauses scheduled workflows after 60 days without repository activity.
garminconnect relies on Garmin's private endpoints, which can break or be rate-limited at any time — and Garmin may throttle GitHub's shared runner addresses. The cooldown makes a block harmless but a blocked sync would need to run from your own machine instead.lib/reports/claudeSuggestions.ts).