Skip to content

Release v3.0.0#307

Merged
TheAngryRaven merged 40 commits into
mainfrom
BETA
Jun 30, 2026
Merged

Release v3.0.0#307
TheAngryRaven merged 40 commits into
mainfrom
BETA

Conversation

@TheAngryRaven

@TheAngryRaven TheAngryRaven commented Jun 26, 2026

Copy link
Copy Markdown
Owner

v3.0.0 — 2026-06-30

Added

  • Public-vehicle badge. Vehicles marked Show on profile now carry a small
    Public badge in the garage's Vehicles tab, so you can see at a glance which are
    shared on your driver profile.
  • Request a datalogger. The contact form has a new New datalogger connection
    category, and the "Download from logger" picker has a Request additional
    dataloggers
    button that opens the contact form with that category preselected.
  • Custom tracks ride along with leaderboard snapshots. When you submit a snapshot
    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.
  • Alfano logger groundwork. Picking the Alfano tile in the logger picker now
    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 that
    drives it is still to come.
  • Public driver profiles (plan 0006). A new public /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.
  • Profile pictures. Tap your profile picture on the Profile tab to upload one; it's
    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.
  • Copy profile link. A button under Sign out on the Profile tab copies your public
    driver-profile link.
  • Show a vehicle on your profile. Each vehicle in the garage now has a Show on
    profile
    toggle that publishes a public-safe projection (no weight, no setup).
  • A ← Back to home button on the Leaderboards and driver-profile pages.
  • Leaderboards (plan 0005). A new public Leaderboards page (linked from the
    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.
  • Submit snapshots to the leaderboards. From the Profile tab, signed-in users
    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.
  • Engine classification + moderation (admin). A new admin Leaderboards tab
    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

  • Display names are now unique case-insensitively so a name can't be impersonated by
    changing case (existing case-duplicates are auto-suffixed by the migration).
  • Leaderboard names update live. A submitter's name on the leaderboards now comes from
    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

TheAngryRaven and others added 4 commits June 25, 2026 01:00
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)
@supabase

supabase Bot commented Jun 26, 2026

Copy link
Copy Markdown

Updates to Preview Branch (BETA) ↗︎

Deployments Status Updated
Database Tue, 30 Jun 2026 04:24:27 UTC
Services Tue, 30 Jun 2026 04:24:27 UTC
APIs Tue, 30 Jun 2026 04:24:27 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Tue, 30 Jun 2026 04:24:27 UTC
Migrations Tue, 30 Jun 2026 04:24:28 UTC
Seeding Tue, 30 Jun 2026 04:24:28 UTC
Edge Functions Tue, 30 Jun 2026 04:24:34 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 26, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

Coverage Summary

Lines: 58.69% (6356/10829) · Statements: 57.64% · Functions: 56.01% · Branches: 53.58%

Per-file coverage
File Lines Functions Branches
src/components/map/positionArrowMarker.ts 0% 0% 0%
src/components/video-overlays/dataSourceResolver.ts 85.07% 71.42% 80.82%
src/components/video-overlays/overlayUtils.ts 100% 100% 100%
src/components/video-overlays/registry.ts 100% 100% 100%
src/components/video-overlays/sectorUtils.ts 94.73% 100% 84.37%
src/components/video-overlays/themes.ts 100% 100% 100%
src/components/video-overlays/types.ts 100% 100% 100%
src/hooks/use-mobile.tsx 0% 0% 100%
src/hooks/use-toast.ts 0% 0% 0%
src/hooks/useAuth.ts 100% 100% 100%
src/hooks/useDataLoader.ts 14.11% 20% 17.02%
src/hooks/useDocumentHead.ts 0% 0% 0%
src/hooks/useEngineManager.ts 0% 0% 0%
src/hooks/useFileManager.ts 0% 0% 0%
src/hooks/useFirmwareUpdate.ts 0% 0% 0%
src/hooks/useKartManager.ts 100% 100% 100%
src/hooks/useLapManagement.ts 0% 0% 0%
src/hooks/useLapOverlays.ts 0% 0% 0%
src/hooks/useLapSnapshots.ts 0% 0% 0%
src/hooks/useNoteManager.ts 0% 0% 0%
src/hooks/useOnlineStatus.ts 0% 0% 0%
src/hooks/usePlayback.ts 0% 0% 0%
src/hooks/useReferenceLap.ts 0% 0% 0%
src/hooks/useSessionData.ts 0% 0% 0%
src/hooks/useSessionMetadata.ts 0% 0% 0%
src/hooks/useSettings.ts 0% 0% 0%
src/hooks/useSetupManager.ts 0% 0% 100%
src/hooks/useStripePrices.ts 0% 0% 0%
src/hooks/useSubscription.ts 0% 0% 0%
src/hooks/useTemplateFields.ts 0% 0% 0%
src/hooks/useTemplateManager.ts 0% 0% 0%
src/hooks/useTrackEditorForm.ts 0% 0% 0%
src/hooks/useVehicleManager.ts 0% 0% 100%
src/hooks/useVideoSync.ts 0% 0% 0%
src/hooks/useWakeLock.ts 0% 0% 0%
src/hooks/useWaybackImagery.ts 0% 0% 0%
src/lib/test/idb.ts 100% 100% 100%
src/lib/aimParser.ts 95.85% 100% 81.46%
src/lib/alfanoParser.ts 83.22% 100% 61.9%
src/lib/billing.ts 97.36% 100% 98.21%
src/lib/billingClient.ts 0% 0% 0%
src/lib/ble/test/mockBle.ts 96% 90% 50%
src/lib/ble/battery.ts 93.33% 100% 87.5%
src/lib/ble/connection.ts 0% 0% 0%
src/lib/ble/dfu/dfuPackage.ts 96.66% 100% 71.87%
src/lib/ble/dfu/dfuTypes.ts 100% 100% 100%
src/lib/ble/dfu/firmwareManifest.ts 96.92% 92.85% 93.18%
src/lib/ble/dfu/index.ts 100% 100% 100%
src/lib/ble/dfu/version.ts 96.77% 100% 83.33%
src/lib/ble/fileTransfer.ts 90.69% 95% 72.91%
src/lib/ble/firmwareCrc.ts 100% 100% 100%
src/lib/ble/firmwareUpload.ts 90.4% 87.5% 84.31%
src/lib/ble/format.ts 100% 100% 100%
src/lib/ble/index.ts 100% 100% 100%
src/lib/ble/internal.ts 100% 100% 50%
src/lib/ble/settings.ts 93.6% 100% 85.29%
src/lib/ble/trackSync.ts 89.69% 90.9% 70.96%
src/lib/ble/types.ts 100% 100% 100%
src/lib/bleDatalogger.ts 100% 100% 100%
src/lib/brakingZones.ts 97.14% 100% 86.11%
src/lib/browserCompat.ts 0% 0% 0%
src/lib/buildInfo.ts 100% 100% 100%
src/lib/canvas2d.ts 100% 100% 91.66%
src/lib/channels.ts 100% 100% 84.61%
src/lib/chartAxis.ts 98% 100% 82.92%
src/lib/chartColors.ts 100% 100% 100%
src/lib/chartUtils.ts 100% 100% 97.05%
src/lib/courseDetection.ts 97.16% 100% 80.23%
src/lib/courseSectors.ts 100% 100% 94.87%
src/lib/datalogParser.ts 83.56% 80% 81.81%
src/lib/db/index.ts 0% 0% 0%
src/lib/db/submissionMaterialize.ts 100% 100% 97.5%
src/lib/db/supabaseAdapter.ts 0% 0% 0%
src/lib/db/types.ts 100% 100% 100%
src/lib/dbUtils.ts 76.23% 80% 17.2%
src/lib/debugConsole.ts 57.74% 61.11% 47.72%
src/lib/deviceSettingsSchema.ts 93.33% 100% 96.42%
src/lib/deviceTrackSync.ts 100% 100% 100%
src/lib/doveParser.ts 89.6% 72.72% 77.27%
src/lib/dovexParser.ts 90.62% 100% 80%
src/lib/driverProfileGroups.ts 100% 100% 83.33%
src/lib/emailValidation.ts 100% 100% 100%
src/lib/engineStorage.ts 100% 75% 100%
src/lib/engineUtils.ts 100% 100% 91.66%
src/lib/fieldResolver.ts 100% 100% 83.33%
src/lib/fileBrowserTree.ts 98.87% 97.5% 89.36%
src/lib/fileLoadingState.ts 100% 100% 100%
src/lib/fileStorage.ts 82.79% 78.12% 72.22%
src/lib/fnv1a.ts 100% 100% 100%
src/lib/garageEvents.ts 100% 100% 100%
src/lib/gforceCalculation.ts 100% 100% 97.22%
src/lib/ggDiagram.ts 100% 100% 94.73%
src/lib/gps/customGps.ts 100% 100% 82.05%
src/lib/gps/dovepWriter.ts 100% 100% 92%
src/lib/gps/gpsFix.ts 100% 100% 100%
src/lib/gps/index.ts 100% 100% 100%
src/lib/gps/observationSample.ts 100% 100% 100%
src/lib/gps/realtimeTimer.ts 90.07% 100% 76.04%
src/lib/gps/sessionGate.ts 100% 100% 100%
src/lib/graphPrefsStorage.ts 100% 100% 100%
src/lib/i18n/config.ts 100% 100% 100%
src/lib/i18n/format.ts 100% 100% 85.71%
src/lib/i18n/pluginLocales.ts 0% 0% 100%
src/lib/i18n/seedUtils.ts 100% 100% 95.23%
src/lib/imageCrop.ts 12.19% 10% 0%
src/lib/iracingParser.ts 91.93% 80% 76.47%
src/lib/kartStorage.ts 100% 75% 100%
src/lib/lapAlignment.ts 100% 80% 76.92%
src/lib/lapCalculation.ts 95.85% 100% 89.68%
src/lib/lapDelta.ts 99.2% 100% 85.07%
src/lib/lapOverlays.ts 100% 100% 86%
src/lib/lapSnapshot.ts 100% 100% 88.46%
src/lib/lapSnapshotStorage.ts 100% 83.33% 100%
src/lib/leaderboardBrowse.ts 100% 92.3% 80.76%
src/lib/leaderboardHandoff.ts 100% 100% 100%
src/lib/leaderboardSession.ts 97.67% 83.33% 67.85%
src/lib/leaderboardTypes.ts 100% 100% 100%
src/lib/logFileType.ts 100% 100% 100%
src/lib/loggers/alfano/alfanoConnection.ts 100% 100% 100%
src/lib/loggers/alfano/ipc.ts 100% 100% 100%
src/lib/loggers/doveslogger/dovesloggerConnection.ts 100% 100% 100%
src/lib/loggers/doveslogger/ipc.ts 100% 100% 100%
src/lib/loggers/fledglingConnection.ts 100% 100% 100%
src/lib/loggers/index.ts 100% 100% 100%
src/lib/loggers/mychron/ipc.ts 100% 100% 100%
src/lib/loggers/mychron/mychronConnection.ts 100% 100% 100%
src/lib/loggers/native/ipc.ts 100% 100% 100%
src/lib/loggers/progress.ts 94.73% 100% 96.15%
src/lib/loggers/types.ts 100% 100% 100%
src/lib/mapMarker.ts 100% 100% 100%
src/lib/motecParser.ts 89.61% 81.48% 55.71%
src/lib/navBack.ts 100% 100% 71.42%
src/lib/nmeaParser.ts 85.62% 92.85% 71.22%
src/lib/noteStorage.ts 100% 80% 100%
src/lib/overlayCanvasRenderer.ts 0% 0% 0%
src/lib/parserUtils.ts 100% 100% 98.52%
src/lib/passwordStrength.ts 100% 100% 100%
src/lib/pendingCheckout.ts 58.82% 25% 100%
src/lib/platform.ts 100% 87.5% 91.3%
src/lib/profanity.ts 100% 100% 75%
src/lib/referenceUtils.ts 98.29% 100% 86.66%
src/lib/sampleData.ts 100% 100% 100%
src/lib/satelliteImagery.ts 100% 100% 90%
src/lib/setupHistory.ts 94.17% 100% 72.51%
src/lib/setupRevision.ts 100% 100% 94.11%
src/lib/setupRevisionStorage.ts 84.48% 78.26% 36.84%
src/lib/setupStatus.ts 100% 100% 100%
src/lib/setupStorage.ts 81.35% 58.62% 100%
src/lib/speedBounds.ts 94.28% 66.66% 87.17%
src/lib/speedEvents.ts 86.56% 100% 76%
src/lib/speedHeatmap.ts 100% 100% 100%
src/lib/submittedTracksStorage.ts 0% 0% 0%
src/lib/templateEdit.ts 97.82% 88.88% 86.95%
src/lib/templateStorage.ts 93.54% 69.23% 100%
src/lib/trackStorage.ts 16.4% 28.3% 18.18%
src/lib/trackSubmission.ts 100% 100% 92.64%
src/lib/trackUtils.ts 100% 100% 97.05%
src/lib/ubxParser.ts 99% 100% 89.58%
src/lib/units.ts 100% 100% 100%
src/lib/utils.ts 100% 100% 100%
src/lib/vboParser.ts 90.66% 100% 71.59%
src/lib/vehicleHistory.ts 97.29% 100% 80%
src/lib/vehicleStorage.ts 100% 75% 100%
src/lib/versionCheck.ts 19.44% 12.5% 44.73%
src/lib/videoExport.ts 0% 0% 0%
src/lib/videoExportTarget.ts 100% 100% 84.61%
src/lib/videoFileStorage.ts 100% 76% 61.11%
src/lib/videoPlaylist.ts 95.71% 100% 90%
src/lib/videoStorage.ts 100% 76.92% 77.77%
src/lib/videoTimeline.ts 100% 100% 100%
src/lib/wakeLock.ts 100% 80% 100%
src/lib/weatherCacheStorage.ts 100% 100% 100%
src/lib/weatherService.ts 23.8% 35.29% 25.89%
src/lib/xrk/xrkClient.ts 3.57% 0% 0%
src/lib/xrk/xrkConfig.ts 100% 100% 100%
src/lib/xrk/xrkImporter.ts 70% 75% 80%
src/lib/xrk/xrkMapping.ts 98.7% 100% 82.81%
src/lib/xrk/xrkResample.ts 100% 100% 90.9%
src/lib/xrk/xrkTypes.ts 100% 100% 100%
src/lib/xrk/xrkWorker.ts 0% 0% 0%
src/plugins/cloud-sync/accountDeletion.ts 100% 100% 100%
src/plugins/cloud-sync/accountExport.ts 0% 0% 0%
src/plugins/cloud-sync/accountImport.ts 13.51% 25% 26.08%
src/plugins/cloud-sync/activeUser.ts 100% 100% 100%
src/plugins/cloud-sync/autoSync.ts 0% 0% 0%
src/plugins/cloud-sync/cloudClient.ts 58.82% 30% 87.5%
src/plugins/cloud-sync/CloudLogsPanel.tsx 0% 0% 0%
src/plugins/cloud-sync/DataPrivacyPanel.tsx 0% 0% 0%
src/plugins/cloud-sync/exportManifest.ts 100% 100% 100%
src/plugins/cloud-sync/FileDeleteToggle.tsx 0% 0% 0%
src/plugins/cloud-sync/fileSync.ts 100% 100% 100%
src/plugins/cloud-sync/FileSyncToggle.tsx 0% 0% 0%
src/plugins/cloud-sync/index.ts 0% 0% 0%
src/plugins/cloud-sync/LapSnapshotsPanel.tsx 0% 0% 0%
src/plugins/cloud-sync/leaderboardClient.ts 8.92% 10.52% 1.56%
src/plugins/cloud-sync/leaderboardSubmission.ts 100% 100% 94.11%
src/plugins/cloud-sync/LeaderboardSubmitPanel.tsx 0% 0% 0%
src/plugins/cloud-sync/localUsage.ts 100% 100% 50%
src/plugins/cloud-sync/merge.ts 100% 100% 100%
src/plugins/cloud-sync/pendingSync.ts 100% 100% 100%
src/plugins/cloud-sync/profile.ts 66.66% 60% 67.85%
src/plugins/cloud-sync/publicProfile.ts 6.25% 28.57% 0%
src/plugins/cloud-sync/publicVehicleSync.ts 92.85% 100% 78.57%
src/plugins/cloud-sync/setupRevisionTombstones.ts 100% 100% 83.33%
src/plugins/cloud-sync/snapshotSync.ts 97.22% 87.5% 75%
src/plugins/cloud-sync/snapshotTombstones.ts 100% 100% 100%
src/plugins/cloud-sync/StoragePanel.tsx 0% 0% 0%
src/plugins/cloud-sync/storageTypes.ts 100% 100% 100%
src/plugins/cloud-sync/storeAccessors.ts 100% 100% 90%
src/plugins/cloud-sync/syncEngine.ts 98.9% 95% 80%
src/plugins/cloud-sync/syncStores.ts 100% 100% 100%
src/plugins/cloud-sync/trackAutoSubmit.ts 100% 100% 83.33%
src/plugins/fileSources.ts 0% 0% 100%
src/plugins/index.ts 0% 0% 0%
src/plugins/mounts.ts 71.42% 75% 100%
src/plugins/panels.ts 75% 83.33% 100%
src/plugins/PluginMount.tsx 0% 0% 0%
src/plugins/PluginPanelHost.tsx 0% 0% 0%
src/plugins/registry.ts 77.27% 50% 100%
src/plugins/storage.ts 90.32% 70.83% 66.66%
src/plugins/tools/i18n.ts 0% 0% 100%
src/plugins/tools/index.ts 0% 0% 100%
src/plugins/tools/laptimer/lapTimerSession.ts 89.06% 80% 84.37%
src/plugins/tools/laptimer/LapTimerTool.tsx 0% 0% 0%
src/plugins/tools/laptimer/useLapTimer.ts 0% 0% 0%
src/plugins/tools/seat-position/model.ts 100% 100% 60%
src/plugins/tools/seat-position/SeatDiagram.tsx 0% 0% 0%
src/plugins/tools/seat-position/SeatPositionTool.tsx 0% 0% 0%
src/plugins/tools/toolList.ts 0% 0% 100%
src/plugins/tools/ToolsLandingTile.tsx 0% 0% 0%
src/plugins/tools/ToolsPanel.tsx 0% 0% 0%
src/plugins/types.ts 100% 100% 100%
src/types/racing.ts 0% 0% 0%

claude added 5 commits June 26, 2026 03:59
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
@TheAngryRaven TheAngryRaven changed the title Beta 3.0.0 Beta 3.0.0 - Leaderboards Jun 26, 2026
TheAngryRaven and others added 4 commits June 26, 2026 00:33
…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)

Copy link
Copy Markdown
Owner Author

Beta → Main Release Review — 2026-06-27

🚦 Verdict: GO-WITH-FIXES

No 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 (BETAmain, "Beta 3.0.0 - Leaderboards", draft) · Release: version TBD (see REL-02) · Since: v2.9.2
Run: multi-agent (7 finders, single adversarial verify) · Diff: 80 files, ~3.5k insertions, 8 PRs/commits since tag
Excluded: node_modules, dist, generated supabase client, lockfile line-noise, pre-existing main issues

Note: PR #304 is a Dependabot action bump (cloudflare/wrangler-action 3→4), not the release PR. The actual BETAmain release PR is #307, reviewed here.

Release contract checklist

Gate Status
Coach on production npm (@perchwerks/eye-in-the-sky, both package.json + vite.config.ts)
package.json version == topmost CHANGELOG heading ❌ (2.9.2 vs [2.10.0]; PR title says 3.0.0)
CHANGELOG heading dated (not - unreleased) ❌ (still - unreleased)
CHANGELOG complete vs commits in this release ❌ (missing required-engine change)
No beta/preview-only config leaked into prod path
CI green (lint / typecheck / test / build / coverage) ⏳ unknown (no checks reported on head 59156d8)

The three failing gates are exactly what the beta-release-prep skill exists to fix; this is a draft PR, so prep has not run yet. Run prep (or fix manually) before tagging.

Summary

Severity Count
Critical 0
High 0
Medium 3
Low 5

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 / custom: names can leak to the public, anon-readable table even with sharing off.

Findings (sorted: severity → dimension)

[Medium] REL-02 — Version incoherence across package.json, CHANGELOG, and PR title

  • Dimension: release-config · Blocks release: yes (before tag)
  • Location: package.json ("version": "2.9.2"), CHANGELOG.md:14
  • Evidence: package.json is still 2.9.2 (= last tag), CHANGELOG top heading is ## [2.10.0] - unreleased, PR Release v3.0.0 #307 title says Beta 3.0.0. Three identities for one release. vite.config.ts bakes the in-app version stamp from package.json, so a build shipped now self-reports 2.9.2 while the changelog claims 2.10.0.
  • Impact: In-app version stamp, versionCheck.ts "update available" signal, changelog, and release title disagree.
  • Recommendation: Run beta-release-prep (sets package.json to the topmost CHANGELOG heading and dates it), reconcile to one number, align the PR title.
  • Effort: S · Confidence: High

[Medium] REL-01 — CHANGELOG omits the "engine now required on every vehicle" change

  • Dimension: release-config · Blocks release: no
  • Location: CHANGELOG.md:14-35
  • Evidence: Commit 49b1566 makes engine a required vehicle field (disabled submit + inline hint) and flags pre-existing engine-less vehicles with a warning (src/components/drawer/VehiclesTab.tsx + 8 locales). The [2.10.0] block has only ### Added (Leaderboards); no ### Changed entry for this behavioral change every existing user hits.
  • Impact: Users upgrading face a new blocking validation with no release-note explanation; generated release notes will be incomplete (Golden Rule 4).
  • Recommendation: Add a ### Changed line under [2.10.0], e.g. "Vehicles now require an engine (powers leaderboard grouping / snapshot matching); existing engine-less vehicles are flagged."
  • Effort: S · Confidence: High

[Medium] SEC-1 — Engine-telemetry privacy stripping uses a hardcoded canonical-id allowlist; custom-slug telemetry leaks even with sharing off

  • Dimension: security · Blocks release: no
  • Location: src/lib/leaderboardTypes.ts:8-15, src/plugins/cloud-sync/leaderboardSubmission.ts:48-55
  • Evidence: buildEntryData() strips extraFields only when the key ∈ ENGINE_TELEMETRY_CHANNELS = {rpm, water_temp, oil_temp, egt, temp_1, temp_2}. Per channels.ts, channels normalize to a canonical id or a custom:<slug> key. Telemetry recorded under non-canonical names (EGT/CHT/Lambda/fuel-pressure or proprietary AiM/MoTeC columns) lands as custom: and survives into the uploaded data.samples[].extraFields + fieldMappings. Stripping is client-only; the RLS insert policy checks only auth.uid() = user_id, and approved rows are anon-readable.
  • Impact: The consent UI promises only "GPS, engine name, lap time, weight" are public, but custom-named engine telemetry is published regardless — to an anonymously-readable table. Remedy is withdrawal of already-public data.
  • Recommendation: Strip by channel group membership (include custom: slugs) or send an explicit GPS/speed allowlist only; ideally add a server-side trigger/CHECK rejecting non-whitelisted channel keys when engine_telemetry_public = false.
  • Effort: M · Confidence: Medium

[Low] SEC-2 — Submitter display_name is client-supplied and not validated server-side (spoofing / profanity-filter bypass)

  • Dimension: security · Blocks release: no
  • Location: src/plugins/cloud-sync/LeaderboardSubmitPanel.tsx:99-107, supabase/migrations/20260626000000_leaderboards.sql:122-123
  • Evidence: display_name is read client-side from getMyProfile() and inserted directly; the RLS insert policy enforces only auth.uid() = user_id and never constrains it to profiles.display_name. The profile uniqueness + profanity filter (profile.ts) is bypassed by a direct/tampered insert.
  • Impact: Public, anon-readable rows can carry spoofed or profane submitter labels until an admin denies them (allow-by-default).
  • Recommendation: Denormalize display_name via a BEFORE INSERT trigger that derives it from profiles for auth.uid() instead of trusting the client.
  • Effort: S · Confidence: High

[Low] SEC-3 — display_name falls back to the user's email, written to an anon-readable public table

  • Dimension: security · Blocks release: no
  • Location: src/plugins/cloud-sync/LeaderboardSubmitPanel.tsx:100
  • Evidence: const displayName = profile?.display_name ?? user.email ?? "Anonymous"; — if getMyProfile() returns null (transient read failure), the email is inserted into the anon-readable leaderboard_entries.
  • Impact: Latent PII (email) disclosure to the public leaderboard. Low likelihood (profiles auto-created at sign-up) but a real leak when hit.
  • Recommendation: Drop the email fallback; abort the submit if the profile read genuinely fails. Combine with SEC-2 (source the name server-side).
  • Effort: S · Confidence: High

[Low] TEST-1 — fnv1a.ts shipped without a dedicated unit test

  • Dimension: testing · Blocks release: no
  • Location: src/lib/fnv1a.ts:6-13
  • Evidence: New shared content-hash primitive (used by trackSubmission.ts + leaderboardSubmission.contentHashForSnapshot); no fnv1a.test.ts. Only transitively exercised; no known-vector / zero-padding / unsigned-wrap assertion.
  • Impact: A regression (losing padStart or >>>0) would silently change every content hash and break both dedupe systems. Golden Rule 3.
  • Recommendation: Add a tiny fnv1a.test.ts with a known-vector + 8-char zero-padded-length assertion.
  • Effort: S · Confidence: High

[Low] TEST-2 — buildNewEntryRow reverse-direction branch is untested

  • Dimension: testing · Blocks release: no
  • Location: src/plugins/cloud-sync/leaderboardClient.ts:172
  • Evidence: direction: isReverseCourseKey(snap.courseKey) ? "reverse" : nullleaderboardClient.test.ts only uses a non-reverse courseKey and asserts only lap_time_ms; direction is never asserted for either branch.
  • Impact: The reverse-direction mapping (feeds the read-only viewer's selection.direction) has no regression guard.
  • Recommendation: Add one it() asserting direction for a normal and a "...reverse" courseKey.
  • Effort: S · Confidence: High

[Low] DOC-1 — docs/backend.md Leaderboards section omits the second (drop_setup) migration

  • Dimension: docs · Blocks release: no
  • Location: docs/backend.md (Leaderboards heading)
  • Evidence: Heading/prose cite only ..._leaderboards.sql; the feature ships two migrations (...20260626..._leaderboards.sql + ...20260627..._leaderboards_drop_setup.sql). The plan doc records the drop amendment; backend.md does not.
  • Impact: Cosmetic — the prose already describes the correct final state (no setup column).
  • Recommendation: Add a one-line note that setup sharing was removed in the follow-up migration.
  • Effort: S · Confidence: High

Must-fix before merge

  1. REL-02 — Reconcile the version (package.json ↔ CHANGELOG heading ↔ PR title) and date the heading. This is release-prep; do it before tagging. (Mechanically required, but it's the standard prep step, not a code defect.)

Strongly recommended in the same pass (not hard blockers):
2. REL-01 — Add the missing ### Changed CHANGELOG entry for required-engine.
3. SEC-1 — Close the engine-telemetry privacy gap before this table is publicly populated; leaked data can't be recalled.

Themes & systemic notes

  • Release-prep hasn't run yet. All three failing contract gates (version, dated heading, complete changelog) are the prep skill's job; the coach is already flipped to production npm, so prep may be partially staged.
  • Privacy is enforced client-side only. SEC-1/2/3 share a root cause: the leaderboard trusts the client for what becomes public, anon-readable data, with no server-side compensating control. Worth a server-side guard pass on the submission path.
  • Feature quality is high. Correctness came back clean; the merge is a verified no-op; setup-sharing removal left no dead code/columns; new pure modules are tested (the two test gaps are minor).

What was not covered

  • CI status: no checks were reported on head 59156d8 (status pending, 0 checks). The lint/typecheck/test/build/coverage gate is unverified — confirm green before merge.
  • Dropped finding (1): a performance finding (PERF-1, "read-only handoff runs the full course-detection pipeline before injected laps overwrite it") was refuted during verification — its central mechanism was factually incorrect, so it was excluded.
  • Pre-existing main issues were out of scope by design (release gate only weighs changes this PR introduces).
  • Single adversarial verify per finding (default depth), not the 3-vote panel.

🤖 Multi-agent release-gate review (7 dimensions, adversarial verify). Full report also saved to docs/reviews/beta-release-review-2026-06-27.md.


Generated by Claude Code

claude and others added 12 commits June 27, 2026 05:12
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
claude and others added 8 commits June 29, 2026 04:10
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
@TheAngryRaven TheAngryRaven changed the title Beta 3.0.0 - Leaderboards Release v3.0.0 Jun 30, 2026
claude and others added 5 commits June 30, 2026 03:33
- 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
…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

Copy link
Copy Markdown
Owner Author

Beta → Main Release Review — 2026-06-30

🚦 Verdict: GO

Safe 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 (BETAmain) · Release: v3.0.0 · Since: v2.9.2
Run: multi-agent (7 finders, single adversarial verify per finding — 6/6 confirmed, 0 dropped) · Diff: 145 files, ~7,000 insertions across 17 merged PRs
Excluded: node_modules/, dist/, generated src/integrations/supabase/, bun.lock line-noise

Release contract checklist

Gate Status
Coach on production npm (@perchwerks/eye-in-the-sky@0.5.0, both package.json + vite.config.ts + bun.lock)
package.json version (3.0.0) == topmost CHANGELOG heading ([3.0.0])
CHANGELOG heading dated (2026-06-30, not - unreleased)
CHANGELOG complete vs commits in this release ⚠️ one user-facing change missing (REL-01, Low)
No beta/preview-only config leaked into prod path (main → production creds; resolver gated to feature branches)
CI green (ESLint / tsc -b / Vitest / coverage / build / CodeQL) ✅ 11/11

The coach pin is exact 0.5.0 rather than tilde ~0.5.0. It is the published production package (not the BETA git dep), so the gate passes; the exact pin is intentional-or-harmless, not a blocker.

Summary

Severity Count
Critical 0
High 0
Medium 0
Low 6

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 (DriverProfile/Leaderboards/cloud-sync leaderboard modules are lazy/dynamic only, no vendor-supabase re-merge into the landing payload), RLS on the new public surfaces leaks nothing beyond name/avatar/public-safe vehicle columns, and the SQL migration chain backfills missing profiles before adding the leaderboard FK so it won't fail on prod's ~5 accounts. The six findings are polish: one changelog gap, two cosmetic UI edge cases, two missing-test gaps on small pure helpers, and one plan-numbering collision. None should hold the release.

Findings (Low — sorted by dimension)

REL-01 — CHANGELOG omits the "engine now required on every vehicle" change

  • Location: CHANGELOG.md:14 · Blocks release: no
  • Evidence: Commit 49b1566 makes engine a required field (!form.engine.trim() guard + disabled submit + inline hint) in src/components/drawer/VehiclesTab.tsx, and flags pre-existing engine-less vehicles with a destructive warning. No mention under the dated ## [3.0.0] heading (Golden Rule 4).
  • Recommendation: Add a "Changed" bullet noting an engine is now required (it drives leaderboard grouping / snapshot matching) and pre-existing engine-less vehicles are flagged.

COR-01 — Leaderboards top-session loading key can cross-disable buttons across courses

  • Location: src/pages/Leaderboards.tsx:117 (disable check :292) · Blocks release: no
  • Evidence: openTopSession keys loadingKey as top:${group.key}, but group.key (engineGroupKey(), leaderboardBrowse.ts:62-64) is unique only within a course. The disclosure state namespaces it as ${course.courseKey}|${group.key} (:267); the loading key doesn't. Two simultaneously-expanded same-engine sections under different courses cross-disable their buttons.
  • Impact: Cosmetic — a sibling button briefly appears disabled. No wrong data loaded; openSingle keys by unique entry.id and is unaffected.
  • Recommendation: top:${course.courseKey}|${group.key}, mirroring line 267.

COR-02 — ProfileAvatar has no broken-image fallback

  • Location: src/components/ProfileAvatar.tsx:28 · Blocks release: no
  • Evidence: The '&lt;img src={url}&gt;' has no onError. avatarPublicUrl builds a ?v=-busted public URL; a missing object / transient 404 renders a broken-image glyph instead of the UserIcon placeholder already used for the url === null case.
  • Recommendation: Add an onError that swaps to the UserIcon placeholder.

TST-01 — escapeLike() ships without a test

  • Location: src/plugins/cloud-sync/publicProfile.ts:32 · Blocks release: no
  • Evidence: Non-trivial pure helper escaping ilike metacharacters (\, %, _) before the /driver/:username lookup; no test imports publicProfile.ts (Golden Rule 3). Current impl is correct — this is a regression-coverage gap.
  • Recommendation: Vitest covering _, %, \ escaping + a plain-name passthrough.

TST-02 — autoSubmitSnapshotTrack() skip/dedupe branches untested

  • Location: src/plugins/cloud-sync/trackAutoSubmit.ts:15 · Blocks release: no
  • Evidence: New module, no sibling test. Three branches gate the submit-track edge call: !isUserDefined early return (:16), null-submission skip (:27), content-hash dedupe (:30). The pure core buildCourseSubmission is tested; the orchestration isn't. Best-effort/non-blocking by design.
  • Recommendation: Vitest mocking the dynamic imports + functions.invoke for each branch.

DOC-01 — Plan-numbering collision: two plans both numbered 0006

  • Location: docs/plans/0006-dynamic-supabase-branch-db.md:1 · Blocks release: no
  • Evidence: This PR adds both 0006-user-profiles.md and 0006-dynamic-supabase-branch-db.md; the branch-db plan should have taken 0007 (Golden Rule 8 + docs/plans/README.md:20-22). "plan 0006" now ambiguously references both in CLAUDE.md/CHANGELOG/README.
  • Recommendation: Rename the branch-db plan to 0007-… and update its "plan 0006" references (CLAUDE.md SUPABASE_ACCESS_TOKEN row + backend-resolver paragraph, README Deployment). Keep 0006-user-profiles.md.

Must-fix before merge

None. All findings are Low and non-blocking. Optional quick wins before tagging: REL-01 (one changelog line) and DOC-01 (rename the duplicate plan) are ~1-minute doc edits that tidy the release record.

Themes

  • Clean on the dimensions that actually gate a release — security, bundle-budget, and migration integrity each returned zero verified findings on the highest-risk surfaces (new anon Supabase views/buckets, the eager-graph rule, the prod migration chain).
  • The repeated pattern is "small new helper, no test" (escapeLike, trackAutoSubmit orchestration) — worth a habit of a sibling test on every new lib//plugins/ helper.
  • Two rare, one-line cosmetic UI edge cases, neither user-blocking.

What was not covered

  • Single adversarial verify per finding (default depth), not the 3-vote pass — warranted given all findings are Low and verification was unanimous (6/6 survived).
  • The 6-language locale diffs were treated as data (key/placeholder parity enforced by the green i18n.test.ts), not line-reviewed for translation quality.
  • Migration runtime behavior was reasoned statically against the diff, not executed against live Postgres.

🤖 Generated by the beta-release-review multi-agent gate (13 agents, 7 dimensions + adversarial verify). Full report saved to docs/reviews/beta-release-review-2026-06-30.md.


Generated by Claude Code

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
@TheAngryRaven TheAngryRaven marked this pull request as ready for review June 30, 2026 04:24
@TheAngryRaven TheAngryRaven merged commit 331d9a0 into main Jun 30, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants