From fa0898847123769d1ef46f599aef0592dbcd5c4e Mon Sep 17 00:00:00 2001 From: Davide Scaini Date: Mon, 30 Mar 2026 11:43:13 +0200 Subject: [PATCH] updated changelog --- CHANGELOG.md | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bab8ae7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,114 @@ +# Changelog + +## [Unreleased] — 2026-03-30 + +### Athlete page + +- **`/athlete` page** — three-tab layout: Power Curve · Records · Profile +- **Mean Maximal Power (MMP) curve** — computed at extract time for each activity with power data + - Sliding-window O(n) algorithm over 1 Hz power timeseries; 15 standard durations (1 s → 1 h) + - Multi-curve overlay with range selector: All time / Last 365 d / Last 90 d / user-defined seasons + - Log-scale x-axis via Observable Plot; FTP reference line; per-point tooltips + - Seasons configurable in `extract_config.yaml` under `athlete.seasons` +- **Personal records (Records tab)** — sport-specific best efforts computed via sliding window + - Running: 400 m, 1 km, 1 mile, 5 km, 10 km, half marathon, marathon + - Cycling: 5 km, 10 km, 20 km, 50 km, 100 km + - Swimming: 100 m, 200 m, 500 m, 1 km, 2 km + - Table shows time, pace (running) or speed (cycling/swimming), date, activity link + - Hiking / Walking: longest distance and most elevation gain + - **Best climbs** — top 10 biggest single climbs (Kadane's algorithm on 1 Hz elevation deltas); ranked table with elevation, date, activity link +- **Profile tab** — max HR, FTP, HR zones, power zones +- **`bincio edit` athlete API** (`GET /api/athlete`, `POST /api/athlete`) — reads/writes `edits/athlete.yaml` +- **`AthleteDrawer.svelte`** — slide-in profile editor (gated behind `PUBLIC_EDIT_URL`) + - Max HR and FTP number inputs + - HR and power zone tables: changing a zone's upper bound auto-cascades to the next zone's lower bound + - Season list: name + date range, add/remove rows +- **`athlete.json`** — written at extract time; contains pre-aggregated MMP curves and records; symlinked into `_merged/` by `merge_all()` + +### Extraction pipeline + +- **MMP computation** — `compute_mmp()` added to `metrics.py`; stored in both detail JSON and index summary (enables client-side season filtering without extra fetches) +- **Best-effort computation** — `compute_best_efforts()` two-pointer sliding window on 1 Hz speed; `_best_climb()` Kadane's on elevation deltas +- **`write_athlete_json()`** — aggregates MMP and records from all summaries into `athlete.json` + +### Scripts + +- **`scripts/backfill.py`** — backfills `mmp`, `best_efforts`, and `best_climb_m` into existing activity JSONs from already-extracted 1 Hz timeseries; no FIT re-parsing needed (~20 s for 2500 activities) + +--- + +## [0.1.0] — 2026-03-29 + +### Extraction pipeline + +- **Parallel extraction** — activities now processed with `ProcessPoolExecutor`; large shared state (Strava lookup, known hashes) sent once per worker via `initializer=` rather than once per task +- **TCX parser fixes** — handles both `http://` and `https://` Garmin namespace URIs +- **Sport classification overhaul** + - FIT parser now reads sport from the `session` frame as fallback when no separate `sport` frame is present (fixes Karoo and Strava-generated FIT files) + - Strava CSV `Activity Type` used as authoritative override when present + - Expanded sport mapping: e-bike variants (`ebikeride`, `e_bike_ride`), `ride`, `run`, date-prefix stripping, and more + - Skiing added as first-class sport: `cycling` | `running` | `hiking` | `walking` | `swimming` | `skiing` | `other` + - Nordic sub-sport: FIT sub_sport values `cross_country_skiing`, `nordic_skiing`, `skate_skiing`, `backcountry_skiing` → `"nordic"` +- **Distance calculation fix** — when a FIT device records `distance = 0.0` (not `null`), the extractor now falls back to haversine-computed GPS distance instead of using the zero value directly; fixes skiing activities that had valid tracks and speeds but showed 0 km +- **`metadata_csv` is fully optional** — omitting it from config works cleanly; only needed for Strava bulk exports + +### Site — maps & charts + +- **MapLibre GL map** fully working on the activity detail page + - Static import + `optimizeDeps.include` (not `exclude`) fixes silent tile worker failure + - `build.target: 'es2022'` required for MapLibre's ES2022 class field syntax + - MapLibre v5 requires explicit `center`/`zoom` in Map constructor and `setLngLat()` before `addTo()` +- **Observable Plot charts** (elevation, speed, HR, cadence) working + - Switched from dynamic `await import()` to static import — fixes unreliable Svelte reactivity + - Curve name is `"monotone-x"` not `"monotoneX"` +- **Power chart** added as fifth tab alongside elevation/speed/HR/cadence +- **HR and power zone histograms** — configurable zone boundaries via `athlete.hr_zones` / `athlete.power_zones` in `extract_config.yaml`; histogram x-axis capped at actual data max so sentinel values (`999`, `9999`) don't stretch the axis +- **Adjustable trim range** on histograms + +### Site — activity feed + +- **SVG track thumbnails** on feed cards — drawn from `preview_coords` (no extra fetch) +- **Sport filter bar** — pill buttons for All / Cycling / Running / Hiking / Walking / Swimming / Skiing / Other + +### Site — stats page + +- **Sport filter bar** — same pill UI as the feed; all stats and heatmap reflect the selected sport +- **Heatmap colour improvements** + - Blended colours in "All" mode: each cell's RGB is a weighted average of sport colours by distance + - Percentile-based intensity scaling (active): each day ranked against all active days, spreading colour evenly regardless of km outliers; configurable back to linear/max-relative (documented in CLAUDE.md) + - `applyIntensity()` lerps from zinc-800 background to full sport colour — dim cells fade into the background rather than going black + - `$: cellColors` precomputed as a reactive `Map` — fixes Svelte not re-rendering cells when filter changes +- **Month label fix** — labels embedded in the week-column flex grid (no more absolute-positioning bugs); `getWeeks()` uses local date formatting (`localISO()`) instead of `toISOString()` to avoid UTC/local mismatch that produced a spurious "Dec" label at column 0 +- **Cell tooltips** — hovering a cell shows a floating card with date, and for each activity: name, sport, distance, duration; each activity is a clickable link to its detail page; 120 ms grace period when moving from cell to tooltip + +### Site — activity editing (`bincio edit`) + +- **`bincio edit` write API** — FastAPI server (`--data-dir`, default port 4041) + - `GET /api/activity/{id}` — current values with sidecar overrides applied + - `POST /api/activity/{id}` — writes sidecar `.md`, triggers `merge_all()` + - `POST /api/activity/{id}/images` — multipart image upload + - `DELETE /api/activity/{id}/images/{filename}` +- **Activity sidecar system** (`bincio/render/merge.py`) + - Sidecars live in `edits/` alongside extracted data (never co-mingled with immutable BAS JSON) + - Fields: `title`, `sport`, `description`, `hide_stats`, `highlight`, `private`, `gear` + - `merge_all()` produces `_merged/` output; `public/data` → `_merged/` at runtime +- **`EditDrawer.svelte`** — slide-in drawer in the Astro site (no separate HTML from the server) + - Opens in-page via Edit button; only rendered when `PUBLIC_EDIT_URL` env var is set + - Title, sport dropdown, gear, markdown description textarea + - Image drag-and-drop with chip list + delete + - Hide-stats toggle buttons (elevation, speed, heart_rate, cadence, power) + - Highlight and private flags + - Optimistic local update on save — title and description update immediately without reload +- **Photo gallery + lightbox** on activity detail page — keyboard navigation (←/→/Esc), filename + counter overlay +- **Markdown descriptions** rendered with `marked`; local relative images suppressed from inline rendering (shown in gallery instead) + +### Documentation + +- **README** rewritten — philosophy statement front and centre, clear two-stage architecture diagram, quick start +- **CHEATSHEET.md** added — daily workflow, all CLI commands, config reference, privacy table, patching snippets, diagnostic scripts, key files table +- **CLAUDE.md** updated — MapLibre GL v5 gotchas, Observable Plot curve names, heatmap colour scaling approaches (linear vs percentile), sidecar/edit architecture decisions +- **`extract_config.example.yaml`** cleaned up — personal paths removed, `metadata_csv` commented out with explanation + +### Infrastructure + +- `publish.sh` — builds and pushes static site to GitHub Pages via orphan branch