Skip to content

Commit 331d9a0

Browse files
Merge pull request #307 from TheAngryRaven/BETA
Release v3.0.0
2 parents d0dd71e + c0306ad commit 331d9a0

148 files changed

Lines changed: 7249 additions & 344 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
> from git history and grouped by theme rather than exhaustive per-commit
1212
> detail.
1313
14+
## [3.0.0] - 2026-06-30
15+
16+
### Added
17+
- **Public-vehicle badge.** Vehicles marked *Show on profile* now carry a small
18+
**Public** badge in the garage's Vehicles tab, so you can see at a glance which are
19+
shared on your driver profile.
20+
- **Request a datalogger.** The contact form has a new **New datalogger connection**
21+
category, and the "Download from logger" picker has a **Request additional
22+
dataloggers** button that opens the contact form with that category preselected.
23+
- **Custom tracks ride along with leaderboard snapshots.** When you submit a snapshot
24+
for a track that isn't in the built-in list, its layout + sectors are included with
25+
the snapshot (so others can view the lap) and the track is **automatically submitted
26+
to the community track database** for review — same flow as the manual submitter, no
27+
CAPTCHA for signed-in users, deduped so it's only sent once.
28+
- **Alfano logger groundwork.** Picking the Alfano tile in the logger picker now
29+
explains that Alfano transfers over Bluetooth serial (Classic Bluetooth SPP),
30+
which browsers can't access in-browser, so it will require the native app. The
31+
native-side download flow is scaffolded (`src/lib/loggers/alfano/`,
32+
`AlfanoDownload`) against the shared native IPC client; the Rust backend that
33+
drives it is still to come.
34+
- **Public driver profiles (plan 0006).** A new public `/driver/{name}` page (shareable,
35+
viewable signed-out) shows a driver's **profile picture**, display name, their **opt-in
36+
vehicles** (name/type/engine only — never weights or setups), and their uploaded
37+
**leaderboard snapshots** as cards. URLs are case-insensitive. **Click a driver's
38+
avatar on the Leaderboards** to open their profile, and **click any snapshot card** to
39+
load that lap in the read-only viewer.
40+
- **Profile pictures.** Tap your profile picture on the Profile tab to upload one; it's
41+
cropped to a centred square and downscaled to ≤256px on-device, then stored in the
42+
cloud. Avatar thumbnails now appear next to names on the Leaderboards.
43+
- **Copy profile link.** A button under Sign out on the Profile tab copies your public
44+
driver-profile link.
45+
- **Show a vehicle on your profile.** Each vehicle in the garage now has a *Show on
46+
profile* toggle that publishes a public-safe projection (no weight, no setup).
47+
- A **← Back to home** button on the Leaderboards and driver-profile pages.
48+
- **Leaderboards (plan 0005).** A new public **Leaderboards** page (linked from the
49+
landing page) where anyone — signed in or not — can browse fastest community laps
50+
by **track → course → engine class**, with an optional **Group by weight** toggle
51+
and a **Show top** selector (3/10/25/50/100/All). Picking a group launches the
52+
normal telemetry viewer in a new **read-only mode** (alert-coloured header,
53+
Coach/Tools/Setups + video/weather/snapshots hidden) where every submitted lap is
54+
a row, fastest first, labelled by the submitter's name.
55+
- **Submit snapshots to the leaderboards.** From the Profile tab, signed-in users
56+
with lap snapshots get a **Submit to leaderboards** dialog. GPS, engine name and a
57+
**listed weight** are public; **engine-telemetry channels** (RPM, temps) stay
58+
private unless you opt to share them. Setup data is never uploaded. The listed
59+
weight defaults to the vehicle's weight and can be overridden (e.g. show a class
60+
weight). Identical snapshots can't be resubmitted.
61+
- **Engine classification + moderation (admin).** A new admin **Leaderboards** tab
62+
lists every submission with approve/deny (allow-by-default), a per-record engine
63+
**class override**, and admin notes. Engine **classes** are keyword groups that
64+
collapse free-text engine names ("Tillotson 225" / "225RS" / "Tilly") into one
65+
class automatically, with a **Reclassify** action — without ever mutating the
66+
user's raw engine string.
67+
68+
### Changed
69+
- **An engine is now required on every vehicle.** The vehicle form won't save without
70+
one (it drives leaderboard grouping and snapshot matching), and any existing
71+
engine-less vehicle is flagged in the garage so you can fill it in.
72+
- **Display names are now unique case-insensitively** so a name can't be impersonated by
73+
changing case (existing case-duplicates are auto-suffixed by the migration).
74+
- **Leaderboard names update live.** A submitter's name on the leaderboards now comes from
75+
their current profile (linked by account) instead of a copy frozen at submit time, so
76+
renaming your display name updates all your existing entries.
77+
1478
## [2.9.2] - 2026-06-25
1579

1680
### Added

CLAUDE.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,15 @@ telemetry viewer.
8080
```
8181
src/
8282
├── pages/
83-
│ ├── Index.tsx # ★ Main SPA — file import, tab views, all state orchestration
83+
│ ├── Index.tsx # ★ Main SPA — file import, tab views, all state orchestration. Also hosts the read-only Leaderboards viewer (plan 0005): consumes a leaderboardHandoff bundle on mount, injects its prebuilt laps/selection, flips a `readOnly` flag that alert-colours the header + hides Coach/Tools/Setups + video/weather/snapshots and labels laps by submitter.
84+
│ ├── Leaderboards.tsx # ★ Public /leaderboards page (cloud-gated): Track→Course→engine/weight accordion (Group-by-weight + Show-top), opens a group into Index's read-only viewer via leaderboardHandoff; shows uploader avatar thumbnails
85+
│ ├── DriverProfile.tsx # ★ Public /driver/:username page (plan 0006, anon, case-insensitive via .ilike): avatar + name + opt-in vehicles (no weights/setups) + the driver's approved leaderboard snapshots grouped by course/weight
8486
│ ├── Admin.tsx # Admin panel (behind VITE_ENABLE_ADMIN)
8587
│ └── … # Login / Register / Privacy / Terms / NotFound
8688
├── components/
8789
│ ├── ui/ # shadcn/ui primitives
88-
│ ├── admin/ # Admin tabs (Tracks, Courses, Submissions, Users, BannedIps, Tools, Messages)
90+
│ ├── SiteHeader.tsx # ★ Shared sticky top banner (sponsor + settings + profile); LandingPage shows all, Leaderboards hides supported-files/about
91+
│ ├── admin/ # Admin tabs (Tracks, Courses, Submissions, Users, BannedIps, Tools, Messages, Leaderboards)
8992
│ ├── tabs/ # View tabs (GraphView, RaceLine, LapTimes, Coach, Tools; SetupsNotesPanel = Setups+Notes 50/50 split on md+, separate tabs on phones — bodies live in drawer/)
9093
│ ├── graphview/ # Pro mode: GraphPanel, GraphViewPanel, MiniMap, SingleSeriesChart, GGDiagram, InfoBox, PanelCard (resizable card chrome for relocated Video/Mini-Map panels). The left column collapses via a divider flag tab (any screen size), and Video/Mini-Map can be relocated into the resizable graph stack from the top of the "Add Graph" picker (GraphPanel reports which are active so the host drops its duplicate VideoPlayer — single shared video ref). Split graphs (tablet+): SecondaryGraphStack mirrors the main panel's graph set for a chosen overlay lap in a draggable two-up view, overriding PlaybackContext for its subtree (nested PlaybackProvider) so one cursor lands on the same track position in both laps (distance-mapped via lib/referenceUtils mapIndexByDistance); SecondaryVideo is a literal second, lap-synced <video> for in-session overlay laps.
9194
│ ├── drawer/ # File-manager drawer tabs (Files, Vehicles/Karts, Device*); SetupsTab + NotesTab also here but mounted as main-view tabs
@@ -115,6 +118,9 @@ src/
115118
│ ├── sampleData.ts # ★ Bundled sample log seeded as an ordinary file (→ docs/subsystems.md)
116119
│ ├── lapOverlays.ts / lapAlignment.ts # ★ Multi-lap overlay logic + Kabsch drift-align (→ docs/subsystems.md)
117120
│ ├── lapSnapshot*.ts # ★ Snapshot types/buffer + IndexedDB CRUD (→ docs/subsystems.md)
121+
│ ├── leaderboard*.ts # ★ Leaderboards (plan 0005): leaderboardTypes (shared), leaderboardBrowse (Track→Course→engine/weight tree), leaderboardSession (transpose entries → one read-only synthetic session, fastest=lap 1), leaderboardHandoff (one-shot page→Index handoff). Submission + Supabase access live in plugins/cloud-sync (leaderboardSubmission/leaderboardClient). → docs/backend.md
122+
│ ├── imageCrop.ts # ★ Pure on-device avatar crop (1:1 centre + downscale ≤256, webp/jpeg) — no Supabase (plan 0006)
123+
│ ├── driverProfileGroups.ts # ★ Pure: one driver's leaderboard entries → Course→weight buckets (plan 0006, DriverProfile)
118124
│ ├── setupRevision*.ts # ★ Content-addressed setup history + IndexedDB CRUD (→ docs/subsystems.md)
119125
│ ├── setupHistory.ts # ★ Pure setup-history view-model (diff + fastest-lap aggregation) → drawer/SetupHistoryPanel (→ docs/subsystems.md)
120126
│ ├── vehicleHistory.ts # ★ Pure vehicle-history view-model (per-vehicle setup revisions, fastest-lap first, course filter) → drawer/VehicleHistoryPanel; reuses setupHistory primitives; shared card chrome in drawer/HistoryCard.tsx
@@ -124,7 +130,7 @@ src/
124130
│ ├── fileLoadingState.ts # ★ Host pub/sub for the global file-load overlay
125131
│ ├── *Storage.ts # IDB/localStorage store modules (file, vehicle, engine, template, note, setup, …)
126132
│ ├── gps/ # ★ Phone-as-datalogger layer: gpsFix, customGps, sessionGate, realtimeTimer, dovepWriter
127-
│ ├── loggers/ # ★ Generic LoggerConnection (listLogs/downloadLog/disconnect) + per-logger adapters — Fledgling=web BLE, mychron/=MyChron over native (Tauri) Wi-Fi IPC, doveslogger/=same Fledgling hardware over native (Tauri) BLE IPC (scan→connect→list→download); native/ipc.ts = shared kind-agnostic native IPC (both lazy; @tauri-apps/api dynamic-imported, native-only); Alfano later. progress.ts = transport-neutral formatters + computeProgress (→ docs/ble.md)
133+
│ ├── loggers/ # ★ Generic LoggerConnection (listLogs/downloadLog/disconnect) + per-logger adapters — Fledgling=web BLE, mychron/=MyChron over native (Tauri) Wi-Fi IPC, doveslogger/=same Fledgling hardware over native (Tauri) BLE IPC (scan→connect→list→download), alfano/=Alfano over native (Tauri) Bluetooth-serial IPC (SKELETON: web-side seam only, Rust backend TBD — Bluetooth serial can't be reached in-browser so there's no web path); native/ipc.ts = shared kind-agnostic native IPC (all lazy; @tauri-apps/api dynamic-imported, native-only). progress.ts = transport-neutral formatters + computeProgress (→ docs/ble.md)
128134
│ ├── speedHeatmap.ts / mapMarker.ts / brakingZones / gforceCalculation / … # racing math
129135
│ ├── chartUtils / canvas2d / chartAxis / chartColors / videoExport / overlayCanvasRenderer # charts/video
130136
│ ├── videoPlaylist.ts # ★ Pure GoPro chunked-video model: parse/order GH/GX/GP/GOPR chunk names, build a virtual timeline (cumulative offsets) + virtual↔local time mapping + planAudioSegments (export audio stitch). useVideoSync swaps the <video> src per chunk; a single file is a 1-chunk playlist
@@ -384,6 +390,7 @@ and the seeder: **→ `docs/i18n.md`**.
384390
| `VITE_TURNSTILE_SITE_KEY` | Client | Cloudflare Turnstile site key (optional CAPTCHA) |
385391
| `TURNSTILE_SECRET_KEY` | Server (edge fn) | Turnstile secret — `???` |
386392
| `VITE_FIRMWARE_MANIFEST_URL` | Client | Override the logger firmware OTA manifest URL. Unset: `main` → production manifest, non-`main`/preview → beta channel (same `isPreviewBuild()` switch). |
393+
| `SUPABASE_ACCESS_TOKEN` | Build (secret) | Supabase PAT. On a **feature-branch** build (not `main`, not `BETA`), `vite.config.ts` resolves that branch's own Supabase **preview-branch DB** via the Management API (`scripts/resolveSupabaseBranch.ts`) and bakes its creds in, else falls back to the static `*_PREVIEW`/beta creds. Never on `main`/`BETA`/dev/runtime. → plan 0007. |
387394
| `DOVE_PLUGIN_PACKAGES` | Build | Comma-separated external plugin npm packages. Overrides the default when set. |
388395
| `ANTHROPIC_API_KEY` / `I18N_SEED_MODEL` | Maintainer tool | Used **only** by `bun run i18n:seed` (`ANTHROPIC_API_KEY` = `???`). Never in the app or CI build. |
389396
| `VITE_APP_VERSION` / `VITE_GIT_HASH` / `VITE_BUILD_DATE` / `VITE_GIT_BRANCH` / `VITE_GIT_COMMIT_DATE` | Build (auto) | Footer version stamp — **not hand-set**; baked from `package.json` + git in `vite.config.ts`. |
@@ -399,10 +406,14 @@ the dashboard). The beta domain `beta.lapwingdata.com` can't bind to a Branch
399406
Preview URL, so a separate thin reverse-proxy Worker in `beta-proxy/` owns it and
400407
forwards to `beta-lapwing.perchwerks.workers.dev` (auto-deployed by the
401408
`deploy-beta-proxy.yml` workflow on `BETA` pushes that touch `beta-proxy/**`;
402-
see `beta-proxy/README.md`). Per-branch preview backend: `vite.config.ts` `pick()`
403-
prefers `*_PREVIEW` Supabase creds on any non-`main` branch
404-
(`WORKERS_CI_BRANCH`/`CF_PAGES_BRANCH`), so beta deployments bake in a preview DB.
405-
`main` and local dev never read `_PREVIEW`. See README "Deployment".
409+
see `beta-proxy/README.md`). Backend by branch (`vite.config.ts` `pick()`, keyed on
410+
`WORKERS_CI_BRANCH`/`CF_PAGES_BRANCH`): **`main`** → base/production vars;
411+
**`BETA`** → static `*_PREVIEW` creds (the shared beta DB — never the resolver);
412+
**any other branch** → with a `SUPABASE_ACCESS_TOKEN` secret, that branch's own
413+
Supabase preview-branch DB via the Management API (`scripts/resolveSupabaseBranch.ts`,
414+
plan 0007), else the `*_PREVIEW`/beta fallback. Each build logs `[backend] Supabase
415+
URL baked: … — <tier>`. `main` and local dev never read `_PREVIEW`/the token. See
416+
README "Deployment".
406417

407418
---
408419

README.md

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
- Pro graph view with multi-series telemetry charts
3535
- Reference lap overlay & pace delta comparison
3636
- Lap snapshots — save a "course fastest lap" per engine, frozen for cross-session comparison (local-first, optionally cloud-synced)
37+
- Public leaderboards — submit your snapshots and browse fastest community laps by track, course and engine class (with optional weight grouping); opens any group in a read-only viewer (cloud-enabled builds)
3738
- Video sync with telemetry playback (incl. GoPro chunked recordings — select all chapter files and they play as one continuous video)
3839
- 9 overlay gauge types (digital, analog, graph, bar, bubble, map, pace, sector, lap time)
3940
- MP4 video export with overlays & audio (H.264 + AAC)
@@ -166,6 +167,7 @@ view. Older JSON with only `sector_2_*`/`sector_3_*` is read as the two majors.
166167
| `STRIPE_SECRET_KEY` | No (required for paid tiers) | Stripe secret key used by the `create-checkout-session`, `stripe-webhook`, and `create-portal-session` edge functions (edge function secret — `???`) |
167168
| `STRIPE_WEBHOOK_SECRET` | No (required for paid tiers) | Signing secret for the `stripe-webhook` endpoint, from the Stripe dashboard webhook config (edge function secret — `???`) |
168169
| `DELETION_CRON_SECRET` | No (required for scheduled account deletion) | Shared secret the `process-account-deletions` edge function requires in the `x-cron-secret` header. Must match the Vault secret `deletion_cron_secret` that the daily pg_cron job sends (edge function secret — `???`) |
170+
| `SUPABASE_ACCESS_TOKEN` | No (preview-branch DBs) | Build-time secret: a Supabase **personal access token**. When set, a **feature-branch** build (not `main`, not `BETA`) resolves its own per-branch Supabase preview database via the Management API and bakes those creds in, falling back to the static `*_PREVIEW`/beta creds when the branch has no preview DB. See *Preview-branch backend* below. Never read by `main`/`BETA` builds, local dev, or the runtime app. |
169171
| `DOVE_PLUGIN_PACKAGES` | No | Build-time: comma-separated external plugin npm packages to load. Overrides the default (`@perchwerks/eye-in-the-sky`, the public AI coach) when set |
170172
| `ANTHROPIC_API_KEY` | No (translation tooling) | Required by `bun run i18n:seed` to machine-translate locale files (`scripts/seed-translations.mjs`). Maintainer tool only — never read by the app or the standard CI build (`???`). |
171173
| `I18N_SEED_MODEL` | No | Optional model override for `bun run i18n:seed` (default `claude-sonnet-4-6`). |
@@ -433,12 +435,22 @@ static frontend.
433435

434436
#### Preview-branch backend (Supabase Branching → preview deployments)
435437

436-
Pushes to a **non-production branch** produce a preview version/URL of the same
437-
Worker. To point those preview builds at a Supabase **preview-branch database**
438-
(so beta work doesn't touch production data), set parallel `*_PREVIEW` build
439-
variables. Workers Builds exposes `WORKERS_CI_BRANCH` (Pages: `CF_PAGES_BRANCH`)
440-
on every build; `vite.config.ts` prefers the `_PREVIEW` value of each key
441-
whenever that branch isn't `main`, and ignores them on `main` and in local dev.
438+
The build picks its Supabase backend by **git branch**, in three tiers:
439+
440+
| Branch | Database | How it's chosen |
441+
|--------|----------|-----------------|
442+
| `main` | production | base build vars (`VITE_*` / `HTT_*`) |
443+
| `BETA` | the shared beta DB | the `*_PREVIEW` build vars |
444+
| any other branch | that branch's **own** Supabase preview-branch DB | resolved via the Management API, falling back to `*_PREVIEW`/beta when none exists |
445+
446+
`BETA` is the shared integration database and is **never** routed onto a per-branch
447+
DB — only feature branches consult the resolver. Each build logs the result
448+
(`[backend] Supabase URL baked: … — <tier>`) so a deploy always states which DB it
449+
baked and why. Set the `*_PREVIEW` vars so non-`main` builds don't touch production:
450+
451+
Workers Builds exposes `WORKERS_CI_BRANCH` (Pages: `CF_PAGES_BRANCH`) on every
452+
build; `vite.config.ts` prefers the `_PREVIEW` value of each key whenever the
453+
branch isn't `main`, and ignores them on `main` and in local dev.
442454

443455
1. Enable **Branching** in Supabase, then copy the preview branch's URL, anon
444456
key, and project ref from the **Branches** panel.
@@ -455,6 +467,31 @@ whenever that branch isn't `main`, and ignores them on `main` and in local dev.
455467
is also accepted. Add the Cloudflare preview URL to the preview branch's
456468
**Auth → Redirect URLs** so cloud sign-in works there.
457469

470+
##### Dynamic per-branch databases (feature branches)
471+
472+
Without a token, every non-`main` preview (including feature branches) uses the
473+
static `*_PREVIEW`/beta DB. To instead give each **feature** branch its **own**
474+
Supabase preview-branch database — so its preview deployment exercises that
475+
branch's migrations without merging into beta first — add a `SUPABASE_ACCESS_TOKEN`
476+
build secret (`BETA` always stays on `*_PREVIEW` regardless):
477+
478+
1. Create a Supabase **personal access token** (Account → Access Tokens).
479+
2. Worker → **Settings → Build → Variables and Secrets** → add it as a secret:
480+
`SUPABASE_ACCESS_TOKEN` (keep the static `*_PREVIEW` values as the fallback).
481+
482+
On a **feature-branch** build (not `main`, not `BETA`), `vite.config.ts` asks the
483+
Supabase **Management API** whether a preview branch exists for the build's git
484+
branch (`scripts/resolveSupabaseBranch.ts`). If one does and it's healthy, that
485+
branch's URL + anon key + ref are baked in; otherwise it falls back to the static
486+
`*_PREVIEW`/beta creds. The lookup never throws and times out fast, so it can't
487+
break a deploy. Full design: [`docs/plans/0007-dynamic-supabase-branch-db.md`](docs/plans/0007-dynamic-supabase-branch-db.md).
488+
489+
> Supabase only generates a preview branch when the git branch carries **migration
490+
> changes**. Branches without DB changes simply fall back to beta. To force a DB
491+
> for a branch anyway, push a no-op migration or create the branch by hand in the
492+
> Supabase dashboard. Remember to add each preview URL to the branch's
493+
> **Auth → Redirect URLs** for cloud sign-in.
494+
458495
#### Custom domains (production + beta)
459496

460497
- **Production — `lapwingdata.com`:** `wrangler.jsonc` declares a

0 commit comments

Comments
 (0)