# 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 `private` flag (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 render` runs `merge_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:** 1. Recommend OsmAnd / Organic Maps for GPS recording — also built a `/record/` page in the app for in-app GPS recording 2. Capacitor wraps the existing Astro/Svelte site as a native iOS/Android app 3. `/convert/` page — Pyodide runs the full extract pipeline in-browser (GPX/FIT/TCX → BAS JSON) 4. 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-bas` edit 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 | ~~Shorten "BincioActivity" logo to "BA" on narrow screens~~ | ✅ 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 | ~~Power curve Y axis should start at 0 W; X axis should extend to at least 2 hours~~ | ✅ 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 | ~~Syncing from Strava does not trigger a site rebuild~~ | ✅ 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 |