🎉 MVP: heatmap viewer with Rust tile proxy and Svelte frontend #1

Merged
mat merged 14 commits from mvp into main 2026-09-30 07:44:33 +00:00
Owner

Summary

browser ── GET / ───────────► single Rust binary (axum)
browser ── GET /api/config ─► { authenticated, maxZoom, sports, colors }
browser ── GET /api/tiles/{sport}/{color}/{z}/{x}/{y}.png
             │
             ▼
        memory LRU ── disk cache (14-day TTL, atomic writes, PNG-validated)
             │ miss
             ▼
        Strava upstream (8-permit semaphore, per-tile single-flight)
        ├─ anonymous: heatmap-external-{a,b,c}.strava.com/tiles/…   (zoom ≤ 11)
        └─ STRAVA_SESSION_COOKIE set:
           CloudFront cookie refresh (single-flight, 30 s failure cooldown)
           content-a.strava.com/identified/globalheat/…              (zoom ≤ 15)

frontend/                  # Svelte 5 + MapLibre GL (Vite, TypeScript)
proxy/                     # crate canicule (axum, cache, cookies, spa)
Makefile                   # dev / build / test
Dockerfile, docker-compose.yml

First usable version of canicule: browse Strava's Global Heatmap with no Strava account (zoom 11), or with a server-side session cookie for high resolution (zoom 15). Map UI parity with the official viewer: 5 sports, 6 color palettes, opacity slider, 3 Carto basemaps, geolocation, shareable #z/lat/lng/sport/color URLs, responsive layout, resolution badge + About modal.

Evidence

Before: main only contained README + gitignore.
After (fresh full run on this branch):

  • make test → 40 Rust tests + 17 Vitest + 3 Playwright browser tests, all passing; svelte-check 0 errors
  • cargo fmt --check and cargo clippy -D warnings clean
  • make build → single release binary embedding the frontend; serves the Vite bundle and a real anonymous Strava tile (200 image/png)
  • docker build + container smoke: curl /api/config → {"authenticated":false,"maxZoom":11,…}, real tile 200 PNG
  • Cache behavior proven by tests: miss→hit (X-Cache), ocean 404 negative cache, 10 concurrent identical requests → 1 upstream fetch, corrupt/truncated PNG never served, disk TTL not extended on promotion, failed cookie refreshes share one attempt

Independent whole-branch review completed; its three Important findings (truncated-PNG acceptance, TTL extension on disk promotion, refresh stampede on failure) are fixed RED→GREEN in 344039f.

Merge Danger

Door: two-way — no data migrations, no external state; revert the merge and redeploy the previous image.

Blast Radius: new service only. Caveats for operators:

  • The authenticated zoom-15 path is tested against a mock Strava (wiremock); it has not been verified against live Strava (no session cookie was available). Verify before public demo.
  • Strava can change tile auth at any time; upstream URL templates are constants in proxy/src/app.rs.
  • Fair use: cache-first design minimizes outbound requests; not affiliated with Strava.
## Summary ```text browser ── GET / ───────────► single Rust binary (axum) browser ── GET /api/config ─► { authenticated, maxZoom, sports, colors } browser ── GET /api/tiles/{sport}/{color}/{z}/{x}/{y}.png │ ▼ memory LRU ── disk cache (14-day TTL, atomic writes, PNG-validated) │ miss ▼ Strava upstream (8-permit semaphore, per-tile single-flight) ├─ anonymous: heatmap-external-{a,b,c}.strava.com/tiles/… (zoom ≤ 11) └─ STRAVA_SESSION_COOKIE set: CloudFront cookie refresh (single-flight, 30 s failure cooldown) content-a.strava.com/identified/globalheat/… (zoom ≤ 15) frontend/ # Svelte 5 + MapLibre GL (Vite, TypeScript) proxy/ # crate canicule (axum, cache, cookies, spa) Makefile # dev / build / test Dockerfile, docker-compose.yml ``` First usable version of canicule: browse Strava's Global Heatmap with no Strava account (zoom 11), or with a server-side session cookie for high resolution (zoom 15). Map UI parity with the official viewer: 5 sports, 6 color palettes, opacity slider, 3 Carto basemaps, geolocation, shareable `#z/lat/lng/sport/color` URLs, responsive layout, resolution badge + About modal. ## Evidence **Before:** `main` only contained README + gitignore. **After (fresh full run on this branch):** - `make test` → 40 Rust tests + 17 Vitest + 3 Playwright browser tests, all passing; svelte-check 0 errors - `cargo fmt --check` and `cargo clippy -D warnings` clean - `make build` → single release binary embedding the frontend; serves the Vite bundle and a **real anonymous Strava tile** (200 `image/png`) - `docker build` + container smoke: `curl /api/config` → `{"authenticated":false,"maxZoom":11,…}`, real tile 200 PNG - Cache behavior proven by tests: miss→hit (`X-Cache`), ocean 404 negative cache, 10 concurrent identical requests → 1 upstream fetch, corrupt/truncated PNG never served, disk TTL not extended on promotion, failed cookie refreshes share one attempt Independent whole-branch review completed; its three Important findings (truncated-PNG acceptance, TTL extension on disk promotion, refresh stampede on failure) are fixed RED→GREEN in 344039f. ## Merge Danger **Door:** two-way — no data migrations, no external state; revert the merge and redeploy the previous image. **Blast Radius:** new service only. Caveats for operators: - The authenticated zoom-15 path is tested against a mock Strava (wiremock); it has **not** been verified against live Strava (no session cookie was available). Verify before public demo. - Strava can change tile auth at any time; upstream URL templates are constants in `proxy/src/app.rs`. - Fair use: cache-first design minimizes outbound requests; not affiliated with Strava.
mat added 14 commits 2026-09-30 07:41:18 +00:00
mat changed title from MVP: heatmap viewer with Rust tile proxy and Svelte frontend to 🎉 MVP: heatmap viewer with Rust tile proxy and Svelte frontend 2026-09-30 07:43:56 +00:00
mat merged commit d535582ad9 into main 2026-09-30 07:44:33 +00:00
mat deleted branch mvp 2026-09-30 07:44:33 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
mat/canicule!1
No description provided.