5ad3aee8f6
- "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"
214 lines
11 KiB
Markdown
214 lines
11 KiB
Markdown
# 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 |
|