- "unlisted" = not shown in the public feed, but GPS track, timeseries and detail JSON are all accessible by direct URL (security by obscurity) - "private" accepted as legacy alias everywhere (backward compat with existing data on disk) - New writes from Strava sync / ZIP upload / sidecar use "unlisted" - Only "no_gps" now suppresses the GPS track - isUnlisted() helper in format.ts used by all Svelte/Astro components - SCHEMA.md and CLAUDE.md document the privacy model and the distinction between "unlisted" and "no_gps"
11 KiB
BincioActivity — Ideas & Roadmap
1. Strava import from UI ✅
Goal: Let users trigger a Strava sync directly from the web UI instead of running CLI commands.
Current state: bincio extract CLI handles Strava OAuth + sync. The edit server (FastAPI) runs locally.
Ideas:
- Repurpose the upload button popup to offer two paths: "Upload file" (existing) or "Connect Strava"
- OAuth flow: open a popup window → Strava auth → redirect back → edit server exchanges code for token → triggers extract
- Edit server would need new endpoints:
GET /strava/auth-url,GET /strava/callback,POST /strava/sync - Progress feedback: SSE (Server-Sent Events) or polling endpoint to stream sync progress back to the UI
Open questions:
- Where to store the Strava token? Currently it's a CLI argument / env var. For server mode, probably a local file in the data dir.
- Should sync be incremental (only new activities) or full re-extract?
- How does this interact with multi-user mode (see #2)?
Effort: Medium. OAuth popup + SSE progress are the tricky parts.
2. Multi-user VPS deployment with invite system
Goal: Deploy bincio on a VPS so multiple users can have accounts. Invite-only registration.
Current state: Entirely single-user, local files, no auth layer at all.
Clarified model
- A bincio instance is a deployment (personal or group)
- Personal instance: 1 user, fully self-contained — current state
- Group instance: N users, invite-only. All users see each other's public activities by default.
The
privateflag (already in the schema) means "only me". - Federation is between instances via BAS data URLs — no accounts needed to follow someone
- Data isolation = run your own instance. No per-user isolation within an instance.
Data layout (multi-user)
/data/
{handle}/ ← one dir per user (extract output)
activities/
index.json
athlete.json
edits/
_merged/
instance.json ← user registry + invite codes
The instance's combined feed merges all users' _merged/index.json files at render time.
Auth layer
- Session-based auth added to the edit server (FastAPI handles login/logout)
- Unauthenticated reads stay fully public (static files, same as today)
- Write endpoints check: logged in + owns the activity
- Astro site detects logged-in user to show/hide the Edit button per activity
Invite system
- Invites: short random codes (8 chars), stored in
instance.json - Invite record:
{code, created_by, used_by, created_at, used_at} - On registration: validate code → create user dir → mark invite used
- Limits: admin (unlimited), regular users (3 invites, configurable)
- UI: "Your invites" page — generate codes, see who used them
Render pipeline (multi-user)
bincio renderrunsmerge_all()for each user, then builds one Astro site- Combined
index.json= all users' public summaries merged, sorted by date - Per-user profile pages at
/{handle}/showing only their activities - Activity detail pages stay at
/activity/{id}/(IDs are globally unique already) - Activity cards in combined feed show "by {handle}" label
Decided:
- SQLite for user/invite storage (safe under concurrent writes, still no external DB dependency)
- Mastodon-style follow UX: paste a BAS URL into a "Follow" input → instance fetches and caches that feed → activities appear in combined feed with "from {url}" attribution
- Feed refresh: pull-on-demand with cache TTL (no background job — fetch on first stale pageload). [open: exact TTL, stale-while-revalidate vs blocking]
Effort: Large. Auth + multi-user render pipeline are the main work. Needs a design pass before coding.
3. Federation explainer for non-technical users
Goal: Write a clear, friendly explanation of how bincio federation works for people who don't know what JSON or APIs are.
Three ways to share your data:
a) Upload raw files
"Export your GPX/FIT files from Garmin, Wahoo, or wherever, and upload them directly. Bincio converts them automatically."
b) Upload BAS files
"Already running bincio locally? Export your BAS data and host it anywhere — GitHub Pages, Netlify, a USB stick. Anyone with the link can include your activities in their feed."
c) Link to another bincio instance
"Self-hosting bincio? Just share your URL. Friends can follow you by adding your address to their feeds — no accounts needed, no central server."
Promo angle ideas:
- "Your data, your rules. No algorithms, no engagement metrics."
- "Like RSS for your training log."
- "Works offline. Export once, keep forever."
- "Follow friends without either of you needing an account on the same platform."
Format: Could be a FEDERATION.md, a landing page section, or a one-pager PDF.
Effort: Small (writing). Medium if we want a designed landing page.
4. Mobile data capture & processing
Goal: Record activities on a phone, convert to BAS, possibly run the full bincio stack on-device.
Four sub-questions:
4a. Record on phone
- Standard GPS recording → GPX export is already solved by apps like OsmAnd, Organic Maps, Strava, Komoot
- Could build a minimal PWA (Progressive Web App) that records GPS and exports GPX
- Web Geolocation API + background service worker has limitations (iOS kills background JS)
- React Native / Capacitor would give better background GPS access
- Simplest path: just recommend OsmAnd/Organic Maps, skip building a recorder
4b. Convert GPX/FIT → BAS on phone
- The extract pipeline is pure Python with no native deps (haversine math, standard library)
- Could run via Pyodide (Python in WebAssembly) in a browser/PWA
- Or package as a mobile app with BeeWare/Kivy (Python → iOS/Android)
- Performance: A typical 1-hour FIT file is ~1MB, should parse in <1s even in Pyodide
- Interesting path: PWA with Pyodide — no install needed, works in Safari/Chrome
4c. Serve the full bincio app on a phone
- The site is a static Astro build — it's already a collection of HTML/JS/CSS files
- A phone could serve these files locally via a small HTTP server
- iOS: no background servers, but a Capacitor wrapper could embed a local server
- Android: more permissive, could run a Python/Node micro-server as a service
- Alternative: just open the pre-built site via file:// protocol (may work for static assets)
4d. Fully offline mobile app
- Capacitor + the existing Svelte components + Pyodide for extract = plausible stack
- Would need to bundle MapLibre tile data locally (big) or use an offline tile server
- This is a significant project but technically feasible with existing code
Decided path:
- Recommend OsmAnd / Organic Maps for GPS recording — also built a
/record/page in the app for in-app GPS recording - Capacitor wraps the existing Astro/Svelte site as a native iOS/Android app
/convert/page — Pyodide runs the full extract pipeline in-browser (GPX/FIT/TCX → BAS JSON)- Fully offline on-device: needs more work (see below)
Done:
- Capacitor setup in
site/(capacitor.config.ts, scripts, packages) /convert/page with Pyodide (loads bincio wheel, converts files, download or save to cloud)/record/page with live GPS recording, exports GPX → hands off to/convert/POST /api/import-basedit server endpoint (accepts pre-converted BAS JSON)site/public/bincio.whl— bincio Python wheel for Pyodide
Fully offline — ordered plan:
| # | Step | Effort | Status |
|---|---|---|---|
| 1 | SW + IndexedDB write (sw.js, localstore.ts, "Save to device") |
Medium | ✅ Done |
| 2 | dataloader.ts — runtime-merge server + IDB; update all Svelte components |
Medium | 🔄 In progress |
| 3 | Test convert→save→feed loop in browser (npm run dev) |
Low | Not started |
| 4 | Sidecar merge in JS (port merge_all, no Pyodide) |
Medium | Not started |
| 5 | Pyodide SW cache (cache CDN assets on first visit) | Medium | Not started |
| 6 | npx cap add android + test in Capacitor WebView |
Low | Not started |
| 7 | Test on iOS device/simulator | Low | Not started |
| 8 | Native micro-server (only if step 7 reveals SW blocked) | Hard | Contingency |
capacitor-nodejs is not on the roadmap. If iOS blocks service workers, fallback is a native micro-server or eliminating all remaining fetch('/data/*') callsites via step 2.
See ARCHITECTURE.md — "Fully offline — missing pieces" for design details and open questions (storage limits, uninstall, sync conflicts).
5. Gear from Strava export
Import bikes.csv and shoes.csv from the Strava ZIP export to pre-populate a gear selector
in the edit drawer. The CSV has: Bike Name, Bike Brand, Bike Model, Bike Default Sport Types.
Could store as a gear.json in the user's data dir and surface it in the EditDrawer sport/gear fields.
6. User feedback backlog (April 2026)
Items reported via the in-app feedback form.
| # | Who | Area | Description | Notes |
|---|---|---|---|---|
| F1 | brut | Mobile / Nav | ✅ Done | |
| F2 | brut | Mobile / Stats | Day-click tooltip in StatsView falls off-screen on mobile |
Needs viewport-clamping on the tooltip position |
| F3 | brut | Data / Display | Cadence unit "rpm" is wrong for walking and hiking — should be "spm" or hidden | ActivityCharts.svelte — sport-aware label or hide tab |
| F4 | brut | Feature | No way to mark an activity as "virtual"; virtual activities should be excluded from records | virtual: true sidecar flag + EditDrawer checkbox; filter in RecordsView and write_athlete_json |
| F5 | brut | Charts / MMP | ✅ Done | |
| F6 | brut | Feature | Import gear (bikes, shoes) from Strava export; edit gear; maintenance log with cumulative km | See item 5 above for gear import. Maintenance log is new: date + km + notes per bike |
| F7 | brut | About page | Make user handles in the community tree clickable; show online user count | Handles link to /u/{handle}/; online count needs server-side tracking (non-trivial) |
| F8 | brut | Strava sync | ✅ Done — trigger was firing after yield "done", client had already closed the SSE connection |
|
| F9 | diego_p | Data | Check elevation gain on activity 2026-04-11T051441Z |
Likely a GPS artefact or smoothing issue; needs manual inspection |
Prioritization (rough)
| # | Feature | Impact | Effort | Priority |
|---|---|---|---|---|
| 1 | Strava sync from UI | High | Medium | Soon |
| 3 | Federation explainer | Medium | Small | Soon |
| 4a/4b | Mobile record + convert | Medium | Medium | Later |
| 2 | Multi-user + invites | High | Large | Later (needs design) |
| 4c/4d | Full mobile app | Low | Large | Future |