Release v3.0.0#307
Conversation
Pulling tags from main
Build a public leaderboards feature on top of lap snapshots, mostly inside the cloud-sync plugin + admin panel + a new /leaderboards route. - Data model: leaderboard_entries + engine_classes tables with public-read RLS, allow-by-default moderation, per-user content-hash anti-resubmit, automatic keyword classification (admin-overridable) + reclassify RPC. - Submission: Profile-tab panel + dialog; GPS/engine/listed-weight public, setup and engine-telemetry channels private by default. Vehicle weight is now frozen into snapshots as the default listed weight. - Browse: /leaderboards page (shared SiteHeader) with a Track -> Course -> engine/weight accordion, Group-by-weight + Show-top controls. - Read-only viewer: a group transposes into one synthetic session (fastest = lap 1) handed off into Index.tsx, which flips a read-only mode (alert header, no Coach/Tools/Setups/video/weather/snapshots, laps labelled by submitter). - Admin: Leaderboards tab to approve/deny, override engine class, edit notes, and manage engine classes. Pure logic (session transpose, browse tree, submission privacy/hashing, handoff, snapshot weight) is unit-tested. Docs + CHANGELOG (2.10.0) updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXC54QHvgfZce87ohuMHcz
Replace the English placeholder values added with the leaderboards feature with real translations for the plugins, landing, leaderboard and admin namespaces. Placeholders + key parity preserved (i18n parity test green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXC54QHvgfZce87ohuMHcz
…e-yf36wl Add community leaderboards (plan 0005)
|
Updates to Preview Branch (BETA) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
lapwing | c0306ad | Commit Preview URL Branch Preview URL |
Jun 30 2026, 04:24 AM |
Coverage SummaryLines: 58.69% (6356/10829) · Statements: 57.64% · Functions: 56.01% · Branches: 53.58% Per-file coverage
|
The submit/refresh catches swallowed the underlying Supabase error, so a failed submit only showed a generic toast with nothing in the debug console. Log the error and include its message in the toast so the actual RLS/schema/constraint cause is visible for diagnosis. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXC54QHvgfZce87ohuMHcz
lap_time_ms is an integer column but snap.lapTimeMs is fractional (derived from float sample timestamps), so Postgres rejected the insert with "invalid input syntax for type integer". Round it on the way in; add a regression test. The submit toast is restored to the friendly message (the raw error stays in console.error for diagnosis). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXC54QHvgfZce87ohuMHcz
The engine powers leaderboard grouping and snapshot matching, so a vehicle must always carry one. Make it a required field in the vehicle form (guard + disabled submit + inline hint), and flag any pre-existing vehicle that has no engine with a warning in the list so members who set one up earlier are prompted to fix it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXC54QHvgfZce87ohuMHcz
The read-only leaderboard viewer showed "–" for S1/S2/S3 because the transposed laps were built without sector data. Add computeLapSectors() — a single-lap sector calculator that anchors the lap at the sample-slice ends (no start/finish re-detection, since an entry's clean lap has no lead-in) and locates the intermediate sector-line crossings, mirroring calculateLaps' per-lap logic. buildLeaderboardSession now fills sectors/sectorTimes/sectorBoundaries per entry (boundaries offset into the concatenated array). Tests added for both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXC54QHvgfZce87ohuMHcz
…005)
Clicking an engine/weight class now expands a fourth accordion level: a ranked
"{rank} {name} {time}" list instead of immediately loading a session. Clicking a
single entry opens the read-only viewer for that one lap; a "Load top N laps as a
single session" button at the top of the list loads the multi-lap session
(honouring the Show-top selector). GroupNode now carries a fastest-first entries
list (id/name/time); page wires group-level expansion + single/top loaders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RXC54QHvgfZce87ohuMHcz
…e-yf36wl Leaderboards: surface real submit error for diagnosis (plan 0005)
Sharing chassis setup on a public leaderboard made people uneasy, so the submission flow no longer collects, sends, or stores it. Drop the Share-setup toggle + setupPublic option across the panel, submission lib, client row, and shared types; the data payload never carries setup. New migration drops the setup_public column and strips any lingering `setup` from existing entry payloads. Engine-telemetry sharing is unchanged. Docs + changelog updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXC54QHvgfZce87ohuMHcz
Branch-specific front-end previews now resolve their own Supabase preview-branch DB at build time instead of all sharing one static beta DB. On a preview build with a SUPABASE_ACCESS_TOKEN secret, vite.config asks the Supabase Management API whether a preview branch exists for the build's git branch (scripts/resolveSupabaseBranch.ts). If one does and it's healthy, its URL + anon key + ref are baked in; otherwise it falls back to the existing static *_PREVIEW/beta creds — the common "no DB changes, so Supabase made no branch" case. The resolver never throws and times out fast, so a flaky control plane can't break a deploy. With no token set, behaviour is byte-identical to before. - scripts/resolveSupabaseBranch.ts (+ tests): pure branch-matching/key-picking logic plus a fail-safe Management API resolver - vite.config.ts: async factory; pick() gains a top-precedence override for the three Supabase keys - vitest.config.ts: include scripts/**/*.test.ts (coverage stays src-only) - README/CLAUDE.md: SUPABASE_ACCESS_TOKEN env + operator setup - docs/plans/0006: design record Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DcAij6Tqtar9yWGeGwKR7Y
…etup Leaderboards: remove setup sharing entirely (plan 0005)
Beta → Main Release Review — 2026-06-27🚦 Verdict: GO-WITH-FIXESNo Critical or release-blocking High findings, and the coach plugin is correctly on the production npm package. The only true gate is unfinished release-prep (version is incoherent and the CHANGELOG is undated/incomplete); a Medium privacy gap in leaderboard submission is worth fixing before public exposure. Blockers: 0 Critical, 0 release-blocking High. PR: #307 (
Release contract checklist
The three failing gates are exactly what the Summary
This release is structurally sound and safe to ship once release-prep is completed. The Leaderboards feature (plan 0005) is well-tested, the main→BETA merge was a clean no-op, setup-sharing was removed cleanly with no dead code/columns, and no IndexedDB migration was owed. The correctness sweep came back empty. The main residual risk is a privacy gap (SEC-1): the engine-telemetry "keep private" guarantee only scrubs 6 canonical channel ids, so telemetry recorded under non-canonical / Findings (sorted: severity → dimension)[Medium] REL-02 — Version incoherence across package.json, CHANGELOG, and PR title
[Medium] REL-01 — CHANGELOG omits the "engine now required on every vehicle" change
[Medium] SEC-1 — Engine-telemetry privacy stripping uses a hardcoded canonical-id allowlist; custom-slug telemetry leaks even with sharing off
[Low] SEC-2 — Submitter
|
Add user profiles to round out the web app: - Avatars: tap the profile picture to upload; on-device 1:1 centre-crop + downscale to <=256px (lib/imageCrop.ts, pure+tested) stored in a new public user-avatars bucket with a ?v= cache-buster. - Case-insensitive unique display names (lower(display_name) unique index + CI name-resolution functions); /driver/:username resolves via .ilike. - Public /driver/:username page (lazy, anon): avatar + name + opt-in vehicles (name/type/engine only) + approved leaderboard snapshots grouped by course/weight (lib/driverProfileGroups.ts, pure+tested). - Opt-in vehicle publishing: Vehicle.publicProfile flag + "Show on profile" toggle drive a public-safe public_vehicles projection synced off the garage event path (publicVehicleSync), never exposing weight/setup. - Copy-profile-link button under Sign out; avatar thumbnails on Leaderboards rows; shared Back-to-home button on both off-session pages. Backend: two migrations (CI names + avatar cols + anon public_profiles view; public_vehicles table + public user-avatars bucket); deletion worker now also empties the avatar folder. Supabase stays off the eager graph (DriverProfile + publicProfile are their own lazy chunks). i18n: new driver namespace + keys; non-en stop-gap seeded (run bun run i18n:seed to translate). Docs: plan 0006, CLAUDE.md, backend.md, CHANGELOG. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
…-control-oqhj4j plan 0006: dynamic per-branch Supabase preview database
…s-fheq7s plan 0006: public driver profiles with avatars
Alfano transfers over Bluetooth serial (Classic Bluetooth SPP), which Web Bluetooth (BLE GATT only) can't reach, so it has no in-browser path and will require the native app. - Web: the Alfano tile stays "coming soon" and, on click, explains that Alfano uses Bluetooth serial — not compatible in-browser, native app required. - Native: the tile mounts a lazy `AlfanoDownload` flow (scan → connect → list → download) built on the shared native IPC client. This is a skeleton seam — the Rust backend that drives it is still TBD. - New `src/lib/loggers/alfano/` adapter (`createAlfanoConnection` + Alfano-tagged `logger_scan`/`logger_connect`), mirroring the DovesLogger native-BLE path, with unit tests. - Roadmap: strike through "Downloads from 3rd-party loggers" and update the Alfano sub-item across all locales; add a `done` flag the list renders as line-through. - Docs + CHANGELOG + all 7 logger/landing locales updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ETkUErddusp2JVL4K73S5X
…-placeholders-uqc1vz Wire up Alfano connectivity placeholders + native skeleton
The original two plan-0006 migrations didn't fully take on the beta preview DB — the user-avatars bucket row existed but its storage.objects RLS policies were missing, so avatar uploads failed with an RLS violation. Add one fully idempotent migration that re-asserts the entire plan-0006 backend (avatar columns, CI-unique display name + index + functions, public_profiles view, public_vehicles table + policies, and the user-avatars bucket forced public + its three owner-folder storage policies). Safe on any DB: no-ops where the originals applied, repairs where they were skipped. Fixes the upload without hand-running SQL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
…heq7s plan 0006: self-healing repair migration for user-profiles backend
…load 403) Avatar uploads use upsert:true, so storage-api does an existence check (SELECT on storage.objects) before writing. The user-avatars bucket had INSERT/UPDATE/ DELETE policies but no SELECT, so that check was denied and the upload 403'd even though the INSERT policy was correct (request folder == auth.uid(), valid authenticated JWT). user-files has always had this read policy; mirror it. Idempotent, new version — self-heals the beta DB and is a clean add on main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
…y-fheq7s plan 0006: add missing SELECT policy on user-avatars (fixes avatar upload 403)
…others=resolver)
Make the build pick its Supabase database by git branch, deterministically:
- main → base/production build vars
- BETA → static *_PREVIEW vars (the shared beta DB) — never the resolver
- any other → that branch's own Supabase preview-branch DB (Management API),
falling back to *_PREVIEW/beta when none exists
Previously the resolver ran for EVERY non-main preview build, so it could route
BETA onto a per-branch DB. Gate it to feature branches only (isFeatureBranch =
non-main AND non-BETA). Also log the chosen backend at build time
(`[backend] Supabase URL baked: … — <tier>`) so every deploy states which DB it
baked and why. Docs (README/CLAUDE.md/plan 0006) updated to the three-tier rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
…rs-fheq7s Branch-tiered Supabase backend (main=base, BETA=_PREVIEW, feature=resolver) + fixes
…r warning) Supabase flags public_profiles as a SECURITY DEFINER view (bypasses base-table RLS). Recreate it with security_invoker = true and grant public (anon) read on profiles so anonymous /driver + leaderboard reads still resolve. Profile columns (display name, avatar path/timestamp) are public by design; writes stay owner-only, so the public can read but not insert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
Tasks 1 & 2 of the database tweaks (task 3 = public_profiles security_invoker is the prior commit on this branch): - Link leaderboard display name to the user instead of hardcoding it. Add an FK leaderboard_entries.user_id → profiles(user_id) and embed profiles(display_name) in the read queries so the name resolves LIVE — a rename now propagates to all existing entries. Stop writing the denormalized column (kept nullable for the deploy window; droppable later). mapEntryRow reads the embed. - Custom-track snapshots: the snapshot already bundles the full course geometry (sectors + layout) in `data`, so on submit we now show a notice that the track rides along, plus a one-tap "Add track to database" that reuses the existing community submit-track flow (SubmitTrackDialog). Docs (CHANGELOG, backend.md) + i18n keys updated. Green: lint, typecheck, 2082 tests, build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
…ting FK migration - Migration self-corrects: backfill any missing profiles (same logic as the original profiles backfill) BEFORE adding the leaderboard→profiles FK, so the migration can't fail on prod's existing accounts — a missing profile is created, not rejected. - Auto-submit the snapshot's custom track to the community DB on submit (replaces the manual button): new pure trackSubmission.buildCourseSubmission (single course vs defaults → typed submission, deduped, derives a short name) + cloud-sync trackAutoSubmit.autoSubmitSnapshotTrack (best-effort, never blocks the snapshot). - Edge fn submit-track skips the Turnstile CAPTCHA for signed-in submitters (they're accountable via JWT; IP ban + rate limit still apply) so the auto-submit needs no interactive CAPTCHA. Docs (CHANGELOG, backend.md) + i18n updated; tests for buildCourseSubmission. Green: lint, typecheck, 2086 tests, build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
DB tweaks: live leaderboard names, custom-track submit, public_profiles security_invoker
…napshot cards
- Leaderboards: a driver's avatar thumbnail is now a link to their /driver/{name}
profile (the row's name/time still opens the lap).
- DriverProfile: restructure the layout (avatar + name on the left, public vehicles
to the right) and render uploaded snapshots as cards below both. Clicking a card
loads that single snapshot in the read-only viewer via the SAME hand-off the
Leaderboards page uses (fetchGroupEntries → buildLeaderboardSession →
setPendingLeaderboardSession → navigate).
i18n + CHANGELOG updated. Green: lint, typecheck, 2086 tests, build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
…kable-fheq7s Clickable leaderboard avatars → driver profile; clickable snapshot cards
…one roadmap item - VehiclesTab: vehicles marked "Show on profile" now show a small Public badge (Globe icon) so public vehicles are obvious at a glance. - Logger picker: the Fledgling tile reads "DIY logger · Bluetooth LE" (clarifies it's BLE vs Alfano's Bluetooth-serial that needs the native app). - Landing roadmap: mark "Track leaderboards & public user profiles" done (struck through) now that it has shipped. i18n updated across all locales; green: lint, typecheck, 2086 tests, build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
Minor UX: public-vehicle badge, "Bluetooth LE" logger label, strike done roadmap item
Coach plugin is already on the production npm package (@perchwerks/eye-in-the-sky) on BETA, so no coach flip was needed. Bump package.json to 3.0.0, rename the topmost unreleased CHANGELOG heading [2.10.0] → [3.0.0] dated today, and move the Leaderboards/Submit/Engine-classification feature entries from Changed to Added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
- New contact-form category "New Datalogger Connection" (ContactDialog + submit-message ALLOWED_CATEGORIES + admin MessagesTab map/colour + i18n). - ContactDialog gains optional `trigger` and `defaultCategory` props so it can be opened from elsewhere with a category preselected. - LoggerPicker: a "Request additional dataloggers" button under the logger tiles opens the contact form pre-set to the new category — so users confused about why a native app is needed (e.g. Alfano) have an obvious way to ask for more loggers. i18n across all locales + CHANGELOG. Green: lint, typecheck, 2086 tests, build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
…ntact Add "request a datalogger" contact category + logger-picker button
Release prep v3.0.0
…BR/ja)
Replace the English stop-gap values left in the non-English locale files for
the user-profiles (plan 0006) and request-datalogger surfaces with real
translations across all six languages: driver-profile page, avatar/copy-link
toasts, public-vehicle toggle + hints, back-to-home, leaderboard view-profile,
custom-track submission notice, and the "New datalogger connection" contact
category. Preserves {{name}} interpolation and locale quote glyphs.
Also drop a now-unused eslint-disable directive in ContactDialog.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
i18n: translate profiles & datalogger stop-gap strings
Beta → Main Release Review — 2026-06-30🚦 Verdict: GOSafe to ship. All seven dimensions ran; every confirmed finding is Low severity and none block the release. Blockers: 0 Critical, 0 release-blocking High. PR: #307 ( Release contract checklist
Summary
A large but well-structured release (leaderboards plan 0005 + user profiles plan 0006 + branch-tiered Supabase backend + Alfano scaffolding + request-datalogger + full i18n). Security, performance/bundle-budget, and merge/migration integrity all came back clean — the bundle-splitting rule holds ( Findings (Low — sorted by dimension)REL-01 — CHANGELOG omits the "engine now required on every vehicle" change
COR-01 — Leaderboards top-session loading key can cross-disable buttons across courses
COR-02 —
|
Address all confirmed (non-blocking) findings from the BETA→main release-gate review on PR #307: - REL-01: changelog — note that an engine is now required on every vehicle. - COR-01: Leaderboards — namespace the top-session loading key by course (top:${courseKey}|${groupKey}) so same-engine groups under different courses no longer cross-disable their load buttons. - COR-02: ProfileAvatar — fall back to the UserIcon placeholder on image load error instead of the browser's broken-image glyph (resets on url change). - TST-01: export + test escapeLike (ilike wildcard escaping for /driver lookup). - TST-02: test autoSubmitSnapshotTrack skip/dedupe/invoke branches. - DOC-01: rename the branch-db plan 0006 -> 0007 (it collided with the user-profiles plan) and update its references in CLAUDE.md + README. Also adds the release-review report under docs/reviews/ for the record. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUypCCbFtY85CaHxj9VWE6
Fix the six Low findings from the v3.0.0 release review
v3.0.0 — 2026-06-30
Added
Public badge in the garage's Vehicles tab, so you can see at a glance which are
shared on your driver profile.
category, and the "Download from logger" picker has a Request additional
dataloggers button that opens the contact form with that category preselected.
for a track that isn't in the built-in list, its layout + sectors are included with
the snapshot (so others can view the lap) and the track is automatically submitted
to the community track database for review — same flow as the manual submitter, no
CAPTCHA for signed-in users, deduped so it's only sent once.
explains that Alfano transfers over Bluetooth serial (Classic Bluetooth SPP),
which browsers can't access in-browser, so it will require the native app. The
native-side download flow is scaffolded (
src/lib/loggers/alfano/,AlfanoDownload) against the shared native IPC client; the Rust backend thatdrives it is still to come.
/driver/{name}page (shareable,viewable signed-out) shows a driver's profile picture, display name, their opt-in
vehicles (name/type/engine only — never weights or setups), and their uploaded
leaderboard snapshots as cards. URLs are case-insensitive. Click a driver's
avatar on the Leaderboards to open their profile, and click any snapshot card to
load that lap in the read-only viewer.
cropped to a centred square and downscaled to ≤256px on-device, then stored in the
cloud. Avatar thumbnails now appear next to names on the Leaderboards.
driver-profile link.
profile toggle that publishes a public-safe projection (no weight, no setup).
landing page) where anyone — signed in or not — can browse fastest community laps
by track → course → engine class, with an optional Group by weight toggle
and a Show top selector (3/10/25/50/100/All). Picking a group launches the
normal telemetry viewer in a new read-only mode (alert-coloured header,
Coach/Tools/Setups + video/weather/snapshots hidden) where every submitted lap is
a row, fastest first, labelled by the submitter's name.
with lap snapshots get a Submit to leaderboards dialog. GPS, engine name and a
listed weight are public; engine-telemetry channels (RPM, temps) stay
private unless you opt to share them. Setup data is never uploaded. The listed
weight defaults to the vehicle's weight and can be overridden (e.g. show a class
weight). Identical snapshots can't be resubmitted.
lists every submission with approve/deny (allow-by-default), a per-record engine
class override, and admin notes. Engine classes are keyword groups that
collapse free-text engine names ("Tillotson 225" / "225RS" / "Tilly") into one
class automatically, with a Reclassify action — without ever mutating the
user's raw engine string.
Changed
changing case (existing case-duplicates are auto-suffixed by the migration).
their current profile (linked by account) instead of a copy frozen at submit time, so
renaming your display name updates all your existing entries.
Full changelog: v2.9.2...main