docs/PLAN.md's Phase 0 section and its "Done when"/Verification entries still described the original Postgres+RLS, docker-compose, auto-redeploying design — none of which is what actually got built and deployed. Marks it done, states the two deliberate deviations plainly (SQLite not Postgres+RLS, manual redeploy not automatic), and separates what's actually verified (login persists a session, checked by scripts/smoke-test.sh after a real bug) from what nobody has tried yet (PWA home-screen install). docs/DECISIONS.md D19 records the auto-update investigation: the real fix for Unraid's own "not available" update-check badge (a third, independent place the D17 self-signed cert needed trusting — Unraid's PHP update checker doesn't share Docker's own certs.d), the structural reason "up to date" can't be fully trusted on this host even after that fix (CI builds on the same dockerd the app runs on, so the local :latest tag is always fresh regardless of whether the container was recreated from it), the failed first Watchtower attempt (stale image, wrong Docker API version) and why CI-triggers-a-redeploy was rejected again rather than reconsidered. "Deliberately deferred" gets three new entries: finishing Watchtower, migrating Gitea/CI to a dedicated VM (raised as the real fix for the root cause D19 kept running into), and persisting the accumulated host-local trust files across a reboot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R2ZKeWkZV7ehf7fivrAkkG
bike-app
A self-hosted cycling app: syncs rides from a Bryton Rider 650, tracks mileage like Strava, and adds a spare-parts inventory and a maintenance record with mileage-milestone reminders.
Status: planning complete, no code written yet.
Why
Today the Rider 650 syncs over Bluetooth to the Bryton Active phone app, which forwards to Strava — but Active has no background sync, so you have to remember to open the app. Research found a better path that removes the phone entirely:
ride ends -> Rider 650 joins home Wi-Fi (Main Menu -> Data Sync)
-> uploads to Bryton cloud
-> this app's poller fetches the ORIGINAL FIT file
-> rides, wear tracking, and push reminders
That's also higher fidelity than the current route — Strava's API can only ever return smoothed streams, never the original file.
Docs
| File | What's in it |
|---|---|
docs/PLAN.md |
The full implementation plan: stack, schema, ingestion pipeline, auth, notifications, phased roadmap, CI/CD, risks, verification |
docs/RESEARCH.md |
Raw findings: the Bryton cloud protocol (endpoints, headers, auth), FIT library comparisons, maintenance interval tables, self-hostable geo services, Gitea Actions gotchas |
docs/DECISIONS.md |
Every decision taken, what was rejected, and why |
Planned stack
Python 3.12 / FastAPI / SQLAlchemy async / PostgreSQL 16 + PostGIS, procrastinate for jobs,
SvelteKit static SPA as an installable PWA, MapLibre GL, all behind Caddy in Docker Compose.
Source control and CI in self-hosted Gitea with an act_runner on the same box.
Four containers in v1, under 2GB RAM.
Next steps
- Verify on the Rider 650: does
Main Menu -> Data Syncupload automatically on joining Wi-Fi, or only on manual trigger? This determines how completely the phone leaves the loop. - Plug the 650 in over USB and
ls -Rthe mounted volume to confirm the real.fitpath (documented asBryton/Activities/, but worth confirming). - Grab a real
.fitfile from it and run it throughfitdecode— Bryton's encoder is not Garmin's, and the schema should be checked against reality before it's written. - Then Phase 0: scaffolding and CI (see the roadmap in
docs/PLAN.md).