Files
bincio-activity/todo.md
T
Davide Scaini 5ad3aee8f6 rename privacy "private" → "unlisted"; enable GPS for unlisted
- "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"
2026-04-13 18:49:20 +02:00

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 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."

"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