Commit Graph

382 Commits

Author SHA1 Message Date
Davide Scaini ff8981b3a1 fix power curve y-axis: use zero:true instead of domain:[0,null] 2026-04-11 10:54:13 +02:00
Davide Scaini 087ef1b776 fix power chart ranges 2026-04-11 09:02:58 +02:00
Davide Scaini 8219db7bfa shorten bincioactivity to ba on mobile 2026-04-11 09:01:47 +02:00
Davide Scaini 18551f9f36 document where feedback is saved 2026-04-11 09:01:34 +02:00
Davide Scaini ef5b06c5b3 trigger rebuild after activities upload 2026-04-11 08:47:27 +02:00
Davide Scaini 82830222ba For users uploading:
- POST /api/upload now returns text/event-stream instead of JSON
  - Per-file progress events stream back as each file is processed: ↓ 3/47 (6%) — morning_ride.fit
  - Final done event shows the summary: "12 added, 35 duplicates"
  - The Vite proxy is configured to stream this properly (no buffering)

  For the admin:
  - New GET /api/admin/jobs endpoint (admin-only) returns the list of active upload jobs, each with
  user, started_at, total, done, current (filename being processed)
  - A pulsing amber badge appears in the nav bar for admins when any user has an active upload running
   — it shows e.g. "2 uploads running" with a tooltip listing each user's progress (@alice: 12/50
  files)
  - Polls every 5 seconds, disappears automatically when all jobs finish
2026-04-11 08:33:21 +02:00
Davide Scaini 01db4eb9ae ingest activities.csv 2026-04-11 08:13:27 +02:00
Davide Scaini cbd5a98cd3 - merge.py: keep private activities in _merged/index.json instead of
stripping them; privacy filtering is now done client-side
      - ActivityFeed: detect logged-in user via bincio:me event; show private
        activities only when viewing your own profile; private cards get a lock
        badge
2026-04-10 23:16:38 +02:00
Davide Scaini c99b755382 The culprit is in renderChart(): it calls chart?.remove() which empties the
container div, causing the layout to collapse to zero height for a moment.
  The browser then scrolls to keep the viewport anchored, but since the page
  got shorter it jumps to the top. When the new SVG is appended, the page is
  taller again but the scroll position was already reset.

  Fix: give the chart container a min-height matching the chart height (220px)
  so it never collapses.
2026-04-10 22:58:34 +02:00
Davide Scaini bc30e0a2fc option to keep all activities private from strava zip, fix copy of register link 2026-04-10 22:51:29 +02:00
Davide Scaini da622131fd upload zip archive from strava 2026-04-10 22:26:11 +02:00
Davide Scaini fc6c00c6eb fix: mobile nav scrolls horizontally without spilling to page width
Logo and action buttons are shrink-0 anchors; nav links occupy the
remaining space with overflow-x:auto and a hidden scrollbar so they
scroll independently. body gets overflow-x:hidden to prevent the
whole page from drifting sideways on narrow screens.
2026-04-10 22:05:42 +02:00
Davide Scaini 3b8bc159c5 upload strava zip 2026-04-10 22:01:44 +02:00
Davide Scaini e5eadc69f2 fix: remove double px-4 on user profile page headers 2026-04-10 19:20:00 +02:00
Davide Scaini 9fd088c693 - "Last sync: never": The old blocking sync was killed by nginx at 120s before save_token was reached. The activities made it to disk (ingestion happens per-activity as it goes), but the token's
last_sync_at timestamp was never written. After deploying, do a soft reset — it'll set last_sync_at to your most recent activity's timestamp so the next sync only fetches newer ones.
  - Reset 404: Added POST /api/strava/reset to serve/server.py. The soft reset now looks in _merged/index.json first (multi-user path), falling back to index.json.
2026-04-10 18:34:53 +02:00
Davide Scaini 816f103b4c fix: write empty index.json for new users at registration so shard resolves immediately 2026-04-10 18:20:35 +02:00
Davide Scaini 3e4ff4019b limit number of workers 2026-04-10 18:13:49 +02:00
Davide Scaini cf414a08ad fix strava import? 2026-04-10 18:13:32 +02:00
Davide Scaini e2765ea012 fix for strava auth 2026-04-10 18:01:32 +02:00
Davide Scaini f67e7552fd fix to strava sync 2026-04-10 17:55:24 +02:00
Davide Scaini eeed3fe3b2 Root cause of the 404: _trigger_rebuild was firing bincio render (= full astro build), but:
1. The build took minutes → 404 during that window
  2. Even after the build, the output lands in site/dist/ — nginx serves from /var/www/bincio/ which is only updated by the rsync in the post-receive hook, not by the server process

  Fixes applied:

  1. bincio/render/cli.py: Added --no-build flag — merges sidecars and updates manifests but skips astro build. This is fast (~1 second).
  2. bincio/serve/server.py _trigger_rebuild: Now passes --no-build. After an upload, _merged/ and root index.json are updated immediately, so the feed reflects the new activity. The static Astro pages are
  only rebuilt on git push.
  3. site/src/components/ActivityDetailLoader.svelte (new): Svelte component that reads the activity ID from the URL, calls loadIndex to resolve the shard tree, then renders ActivityDetail dynamically — no
  pre-built page needed.
  4. site/src/pages/activity/index.astro (new): Generic Astro shell page that renders ActivityDetailLoader. Gets compiled to dist/activity/index.html.
  5. docs/deployment/vps.md: Added location /activity/ { try_files $uri $uri/ /activity/index.html; } to the nginx config. When a request arrives for /activity/2026-04-06T153345Z/ and no pre-built file
  exists, nginx serves the shell, which loads the data dynamically from /data/ (which nginx already serves live from disk).
2026-04-10 17:48:23 +02:00
Davide Scaini 61349e6292 fix: resize map before fitBounds and defer with rAF to avoid layout-timing glitch 2026-04-10 17:44:52 +02:00
Davide Scaini a20df6bd57 fix: hide community stats/tree on about page for non-logged-in users 2026-04-10 17:40:16 +02:00
Davide Scaini 96a3deee5d fix: serve data/ from disk via nginx alias; return full athlete data from API 2026-04-10 15:57:23 +02:00
Davide Scaini ae883a7dba fix: rebuild athlete.json on every ingest; remove bincio-extract references from UI 2026-04-10 15:47:50 +02:00
Davide Scaini f8e02f3da2 fix: athlete page falls back to /api/athlete when static file not yet rebuilt 2026-04-10 15:43:01 +02:00
Davide Scaini 6a8ef984cb fix: GET/POST /api/athlete work without a pre-existing athlete.json 2026-04-10 15:37:05 +02:00
Davide Scaini f790deb54f fix: athlete page shows empty state instead of error when athlete.json missing 2026-04-10 15:35:41 +02:00
Davide Scaini 8d8b009a78 fix: use shutil.which to find uv in _trigger_rebuild; never 500 on rebuild failure 2026-04-10 15:33:06 +02:00
Davide Scaini 7088b94a87 fix: bincio init always sets private:true even if index.json already exists 2026-04-10 15:26:11 +02:00
Davide Scaini 27f94cf581 fix: respect instancePrivate when determining single-user mode in Base.astro 2026-04-10 15:19:41 +02:00
Davide Scaini 2481093a0a trigger rebuild 2026-04-10 15:15:54 +02:00
Davide Scaini 5371c77c8f update vps instructions 2026-04-10 15:14:44 +02:00
Davide Scaini e006175285 fix: don't redirect to /u/{handle}/ on private (multi-user) instances 2026-04-10 15:06:45 +02:00
Davide Scaini ceb8e28b74 update deployment instructions 2026-04-10 15:06:31 +02:00
Davide Scaini 4593478863 feedback page 2026-04-10 14:23:31 +02:00
Davide Scaini 6d3673b2f7 1. Image upload size limit — _MAX_IMAGE_BYTES = 10 MB in both serve/server.py and edit/server.py
2. Image MIME type whitelist — _ALLOWED_IMAGE_TYPES blocks SVG XSS in both servers
  3. Filename collision safety — _unique_image_name() helper in both servers
  4. OAuth CSRF — state token generated in edit/server.py auth-url, stored in _oauth_states, validated and discarded in callback; strava_api.auth_url() accepts optional state param
  5. Error message leak — upload processing errors now return generic "Processing failed" instead of exception type/message
  6. Handle injection in subprocess — _trigger_rebuild now asserts handle matches _VALID_HANDLE before passing to subprocess
2026-04-10 13:56:39 +02:00
Davide Scaini 8b7cdd9ed1 explain invitation system 2026-04-10 13:49:15 +02:00
Davide Scaini 053da10ab9 some basic statistics and invite tree, plus watch new data 2026-04-10 13:21:31 +02:00
Davide Scaini 6b2d31a44a test server imports 2026-04-10 13:06:00 +02:00
Davide Scaini f37e898eb5 about page 2026-04-10 13:05:51 +02:00
Davide Scaini 469a5954cc "keep data on the server" opt-in/out 2026-04-10 13:01:21 +02:00
Davide Scaini 5170afa9e8 vps instructions 2026-04-10 12:53:35 +02:00
Davide Scaini 8ceb714765 bulk upload 2026-04-10 12:50:38 +02:00
Davide Scaini ce00a63d43 Merge branch 'vps' into mobile_app 2026-04-10 12:38:40 +02:00
Davide Scaini 683b7d9c1b limit max number of users 2026-04-10 12:38:17 +02:00
Davide Scaini b4acf8c1a6 populate pages when a new user joins 2026-04-10 12:35:34 +02:00
Davide Scaini cbac82a2ba Fix 1 — new user pages 404 (server.py:228):
After registration creates the user's directories, it now calls _write_root_manifest(dd). This rewrites index.json to include the new handle's shard immediately. Since Astro dev re-evaluates getStaticPaths() on every request (reading that file), /u/pres/, /u/pres/stats/, and /u/pres/athlete/ will resolve correctly as soon as the new user navigates there.

  Fix 2 — invites link (athlete/index.astro:33):
  Added an Invites button (top-right, same style as "Edit profile") that starts hidden. When bincio:me fires and me === handle (you're on your own page), the subnav tabs are removed as before AND the invites button is revealed. Other visitors see neither.
2026-04-09 21:44:38 +02:00
Davide Scaini 084c652fdd fixing stuff after splitting jsons 2026-04-09 15:27:00 +02:00
Davide Scaini 8118f6f316 1 — Timeseries split
- writer.py: timeseries is now written to {id}.timeseries.json as a separate file. The detail JSON gets a timeseries_url field instead. finalize_pending and cleanup_pending handle the extra file.
  - merge.py (merge_one): symlinks the .timeseries.json file alongside the detail JSON. merge_all already handles it transparently (the .timeseries.json stem doesn't match any activity
  ID in to_merge, so it falls through to the symlink branch).
  - types.ts: timeseries is now timeseries?: Timeseries | null, and timeseries_url?: string | null added.
  - dataloader.ts: new loadTimeseries(url, detailUrl, base) function that resolves paths correctly in both single- and multi-user modes (uses the fetched detail URL's directory as the base).
  - ActivityDetail.svelte: loads timeseries separately after detail loads; uses detail.timeseries for IDB activities (embedded) or fetches via detail.timeseries_url for server activities. Charts show a pulse placeholder while loading.

 2 — GZip

  - GZipMiddleware (min 1 KB) added to both bincio/serve/server.py and bincio/edit/server.py — all API JSON responses are now gzip-compressed.
  - For static files (the big timeseries JSONs), nginx should be configured with gzip on; gzip_types application/json application/geo+json; — no code change needed on the server side.

  Net effect: opening an activity page now fetches ~1.4 KB (detail) instead of ~586 KB. The timeseries fetches ~60–150 KB gzip-compressed shortly after (it loads concurrently with the map rendering).
2026-04-09 14:01:02 +02:00