Skip to content

BETA → main [ON HOLD — do not merge]#41

Draft
TheAngryRaven wants to merge 159 commits into
mainfrom
BETA
Draft

BETA → main [ON HOLD — do not merge]#41
TheAngryRaven wants to merge 159 commits into
mainfrom
BETA

Conversation

@TheAngryRaven
Copy link
Copy Markdown
Owner

@TheAngryRaven TheAngryRaven commented May 24, 2026

🚧 ON HOLD — do not merge

Tracking PR for everything staged on BETA ahead of main. Kept as a draft;
coaching is still under active development and a few verification passes remain
(see Still to do). This description is the running ledger of what's landed.


What's in BETA

🧩 Plugin framework (new) — #40, #42, #47, #49#52

  • Panels: plugins contribute self-contained UI panels to a named slot; the
    host renders them in titled cards, each isolated by an error boundary + Suspense
    (so panels can be React.lazy). Slots are self-gating.
  • Chromeless panels (Chromeless plugin panels + coach dashboard (v0.2.0) #49Refresh coach plugin to v0.2.3 #52): a panel can render full-bleed (no card chrome)
    for panels that own their layout (e.g. the coach dashboard).
  • Inline mounts + per-plugin storage + external npm plugins.

🏎️ AI Coach — #47, coach @perchwerks/eye-in-the-sky v0.2.4 (#55)

  • Dedicated Coach tab; full-bleed analysis dashboard (deterministic Stage-1
    debrief, uPlot charts, corner/sector breakdowns, Leaflet race-line map),
    all lazy. Ships from the public npm registry.

🔁 Canonical telemetry channels — #48

  • Channels normalized to a canonical identity at import time → consistent field
    defaults + saved graph/overlay selections across every logger format. G-force
    distinct per source. Existing files migrate transparently.

📈 Position-based lap delta / pace — #43, #44

  • Position-based pace by default (arc-length resampled reference); legacy distance
    method still selectable. Upgrades pace everywhere.

📸 Lap snapshots — #73

Frozen "course fastest lap" captures — an immutable single-lap baseline for
cross-session comparison (and future AI coaching). Local-first, cloud-synced.

  • Identity = (course + engine): exactly one snapshot per pair, keyed by a
    deterministic id. A faster lap upserts in place, so the count never inflates.
    Engine is the layman's "primary key"; the chassis travels inside the frozen setup.
  • What's frozen (never changes once saved): the lap's GPS samples ± a 5 s
    buffer
    on each side, the course geometry, lap time, source file/lap, and a copy
    of the vehicle + setup.
  • Capture: assigning an engine + setup to a log prompts to save/update the
    course fastest lap when its best lap beats (or has no) stored snapshot; a manual
    Save as snapshot also lives in the lap-list Snapshots picker (header, so
    it serves simple + pro mode).
  • Loaded as a comparison overlay only — rides the external-reference slot, so
    it's excluded from playback and the video player and never an appended lap.
    The overlay label shows the engine (2-stroke vs 4-stroke reads clearly).
  • Cloud: a dedicated lap_snapshots table with a per-tier COUNT quota
    (not byte document storage — snapshots are chunky and AI-valuable). Always pushes
    on save; a local delete never propagates to the cloud (cloud copy removed only
    explicitly from Profile → Lap snapshots, like the log menu), with tombstones so
    reconcile won't resurrect a surviving local copy. Local storage stays unlimited.

☁️ Cloud Sync, accounts, storage & profiles — #42, #45, #46, #53, #54, #56, #57, #58, #59, #60

  • First-party Cloud Sync plugin (manual push/pull of files + garage data).
  • Per-file selection for log sync (opt-in) + a "Cloud files" pull list.
  • Public accounts (gated by VITE_ENABLE_CLOUD, default off): email/Google,
    forgot/reset password.
  • Document storage + auto-sync (Document storage + auto-sync, propagation deletes, quotas + Profile tab #53): garage data is a free "documents"
    storage type (5 MB); logs are a separate 20 MB type. Propagation deletes
    for vehicles/setups (loud warning). Limits enforced server-side. Far-right
    Profile tab with usage meters. Anon local garage migrates on first sign-in.
  • User display names (Add unique, editable user display names #54): unique, editable, auto-generated (SpeedyRac3r-546)
    when blank; Profile-tab editor with "that name's taken" handling.
  • Cloud log management (Cloud log deletion (Profile tab) #56): the Profile tab lists your cloud log files (date +
    size) and deletes them — cloud copy only (other devices keep their download),
    with an opt-in toggle to also delete the local copy on this device.
  • Offline-aware, conflict-safe sync (Offline-aware, conflict-safe document sync (timestamp merge + pending set) #57): garage records carry an updatedAt;
    sync is pending-wins + last-write-wins, so a newer change is never stomped
    and offline edits take priority on reconnect. Profile tab flags offline state
    • the pending-change count.
  • Tracks & courses sync (Sync user tracks & courses (documents storage type) #58): your user tracks/courses now sync like setups
    (auto-sync, delete propagation, the same timestamp merge), under the documents
    type. They live in localStorage, so the engine grew a store-accessor seam
    (IndexedDB for most stores, localStorage for tracks). Built-in public tracks
    never sync.
  • Log-delete propagation + orphan-safety (Propagate log deletes to the cloud + prevent orphaned blobs #59): deleting a synced log locally
    offers an opt-in "also delete the cloud copy" switch (off by default — the cloud
    copy is a backup; offline → queued, propagates on reconnect), via a generic
    FileDeleteConfirm host mount. Uploads no longer orphan a blob if the quota
    trigger rejects the index write (rollback), and pre-existing orphans are reclaimed
    when the Cloud logs panel opens.
  • Over-limit partial push (Partial document push when over the cloud quota #60, stacks on Propagate log deletes to the cloud + prevent orphaned blobs #59): when local garage data exceeds
    the documents limit, sync no longer fails wholesale — it pushes the batch, and on
    a quota rejection falls back to per-record upserts so everything that fits still
    syncs, reporting how many items were skipped (manual-push toast + background
    reconcile notice).

💳 Paid subscription tiers — Stripe — #62 (backend), #66 (UI), #68 (premium), #71 (monthly/annual · grace · full integration)

Stripe-backed paid tiers that scale the cloud-sync logs quota on top of the free
type limits above. Each paid tier bills monthly or annual.

tier price logs quota docs quota lap snapshots
free $0 20 MB 5 MB 5
plus $1/mo · annual 500 MB 5 MB 10
premium $3/mo · annual 1 GB 5 MB 20
pro (AI, coming soon) $10/mo · annual 1 GB + AI credits 5 MB 50

(prices provisional; numbers match the in-app PricingCards, which now read live prices from Stripe. The lap-snapshot count column is the per-tier limit added in #73.)

🔐 Privacy, GDPR & data retention — #70, #72

  • Self-service data tools (Profile → Data & privacy, cloud builds): Download my data
    (one ZIP of all server-side + browser-local data; export-account-data fn) and Delete my
    account
    (email one-time-code confirmed, scheduled 7 days out and cancellable;
    request-account-deletion + cron process-account-deletions).
  • Data-retention TTL: a daily job nulls submitter IPs on messages/submissions after 90
    days, deletes them after 1 year (submissions only once reviewed), and clears expired IP bans
    • stale rate-limit rows.
  • 16+ age confirmation at sign-up (email + Google); banned-IP expiry in the admin
    panel (selectable TTL, default 90 days).
  • Customizable engine types (Customizable engine-type combobox for vehicles #72): the vehicle Engine field is a searchable, per-user
    combobox; the list syncs with the rest of the garage.

🛠️ Build / env / PWA infra

  • VITE_ENABLE_CLOUD gates all cloud auth + sync; HTT_ env mirror prefix;
    preview SW cache eviction.

New env vars

Var Default Purpose
VITE_ENABLE_CLOUD false Public accounts + Cloud Sync + auth routes
HTT_* mirrors HTT_SUPABASE_URL/PUBLISHABLE_KEY/PROJECT_ID, HTT_ENABLE_CLOUD, HTT_ENABLE_ADMIN
DOVE_PLUGIN_PACKAGES @perchwerks/eye-in-the-sky Override external plugin package list
STRIPE_SECRET_KEY Edge-function secret — Stripe API key for prices/checkout/webhook/portal (#62, #71)
STRIPE_WEBHOOK_SECRET Edge-function secret — Stripe webhook signing secret (#62)
DELETION_CRON_SECRET Edge-function secret — guards the process-account-deletions cron worker (#70)

Prices are resolved by lookup_key (#71), so there are no Stripe Price ids in env or DB.
The log-trimming (#71) and data-retention/deletion (#70) jobs run on pg_cron — enable the
extension (Dashboard → Database → Extensions) or run them from an external scheduler.
Lap snapshots (#73) add no new env vars — the count limit is data on subscription_tiers.

Backend / migrations

Quality

  • CHANGELOG [Unreleased] current. Four CI gates green on contributing PRs
    (lint / typecheck / 737 tests / build — Lap snapshots: frozen "course fastest lap" per engine #73 adds 10 snapshot tests). Bundle
    budget respected (uPlot, Leaflet, and the sync engine all stay off the initial path).

Still to do (why this is on hold)


📌 Roadmap notes (owner)

This repo (host) / cloud & accounts

Coaching plugin (other repo)

Open questions — monetization / infra

https://claude.ai/code/session_012D8zxba3CCmUdqgT16zZav

claude and others added 3 commits May 24, 2026 03:33
First concrete extension point for the plugin system: plugins contribute
self-contained React panels to a named slot, and the host mounts them.
Built so future personal/third-party plugins plug in the same way.

- panels.ts: PluginPanel/PluginPanelProps contract, PANELS_POINT, PanelSlot,
  getPanelsForSlot. PluginPanelProps is a curated, read-only session snapshot
  so plugins never depend on the host's internal session context.
- PluginPanelHost: mounts every panel for a slot in a titled card, each behind
  a per-panel error boundary so a buggy plugin can't crash the tab.
- LabsTab renders the "labs" slot; Index shows the Labs tab automatically when
  a plugin contributes a labs panel, even with the experimental setting off.

https://claude.ai/code/session_01QF56Xjp5ZMgXrqfTWD14Le
…plugins-vIcuh

Add plugin UI panel framework, surfaced in the Labs tab
First in-repo plugin built on the panel framework: a Labs panel that signs the
user in and does manual, directional push/pull of local IndexedDB data to
Supabase. Structured stores sync as jsonb documents in a new sync_records table;
raw session blobs round-trip through a private per-user Storage bucket. Both are
RLS-scoped to the owner. Sync is additive (no deletion propagation yet) and
online-only — the core app stays fully offline without it.

- supabase migration: sync_records table + user-files bucket, owner-scoped RLS
- syncStores.ts holds the pure store/key config (unit-tested); syncEngine.ts
  does the IDB <-> cloud I/O; cloudClient.ts isolates the typed-client escape
  hatch until Supabase types regenerate
- PluginPanelHost now wraps panels in Suspense so panel components can be lazy;
  CloudSyncPanel is lazy-loaded to keep it off the initial bundle
- sign-in only for now (Google to be added via Lovable); auth UI is a thin stub

https://claude.ai/code/session_01QF56Xjp5ZMgXrqfTWD14Le
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 24, 2026

Coverage Summary

Lines: 35.5% (2397/6751) · Statements: 34.54% · Functions: 28.88% · Branches: 36.05%

Per-file coverage
File Lines Functions Branches
src/components/video-overlays/dataSourceResolver.ts 84.12% 70% 79.41%
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 0% 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 0% 0% 0%
src/hooks/useDocumentHead.ts 0% 0% 0%
src/hooks/useEngineManager.ts 0% 0% 0%
src/hooks/useFileManager.ts 0% 0% 0%
src/hooks/useKartManager.ts 100% 100% 100%
src/hooks/useLapManagement.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/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/integrations/lovable/index.ts 0% 0% 0%
src/lib/aimParser.ts 87.96% 100% 69.13%
src/lib/alfanoParser.ts 80.46% 100% 57.48%
src/lib/billing.ts 94.73% 100% 96.29%
src/lib/billingClient.ts 0% 0% 0%
src/lib/ble/test/mockBle.ts 100% 100% 50%
src/lib/ble/battery.ts 93.33% 100% 87.5%
src/lib/ble/connection.ts 0% 0% 0%
src/lib/ble/fileTransfer.ts 90.69% 95% 72.91%
src/lib/ble/format.ts 50% 100% 72.22%
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/channels.ts 100% 100% 84.61%
src/lib/chartColors.ts 100% 100% 100%
src/lib/chartUtils.ts 0% 0% 0%
src/lib/courseDetection.ts 99.01% 100% 84.14%
src/lib/datalogParser.ts 18.51% 50% 17.39%
src/lib/db/index.ts 0% 0% 0%
src/lib/db/supabaseAdapter.ts 0% 0% 0%
src/lib/db/types.ts 100% 100% 100%
src/lib/dbUtils.ts 3.12% 0% 0%
src/lib/deviceSettingsSchema.ts 93.33% 100% 96.42%
src/lib/deviceTrackSync.ts 100% 100% 90%
src/lib/doveParser.ts 89.6% 72.72% 73.63%
src/lib/dovexParser.ts 76.56% 76.92% 47.27%
src/lib/emailValidation.ts 100% 100% 100%
src/lib/engineStorage.ts 0% 0% 100%
src/lib/engineUtils.ts 100% 100% 91.66%
src/lib/fieldResolver.ts 100% 100% 83.33%
src/lib/fileStorage.ts 0% 0% 0%
src/lib/garageEvents.ts 100% 100% 100%
src/lib/gforceCalculation.ts 100% 100% 100%
src/lib/graphPrefsStorage.ts 0% 0% 0%
src/lib/kartStorage.ts 0% 0% 0%
src/lib/lapCalculation.ts 96.12% 100% 90.32%
src/lib/lapDelta.ts 98.96% 100% 82.35%
src/lib/lapSnapshot.ts 100% 100% 87.5%
src/lib/lapSnapshotStorage.ts 0% 0% 0%
src/lib/motecParser.ts 4.29% 3.44% 0.69%
src/lib/nmeaParser.ts 85.62% 92.85% 71.22%
src/lib/noteStorage.ts 0% 0% 100%
src/lib/overlayCanvasRenderer.ts 0% 0% 0%
src/lib/parserUtils.ts 100% 100% 98.52%
src/lib/pendingCheckout.ts 58.82% 25% 100%
src/lib/referenceUtils.ts 100% 100% 89.28%
src/lib/setupStorage.ts 0% 0% 0%
src/lib/speedBounds.ts 94.11% 66.66% 89.18%
src/lib/speedEvents.ts 86.56% 100% 76%
src/lib/templateStorage.ts 0% 0% 0%
src/lib/trackStorage.ts 3.33% 0% 0%
src/lib/trackUtils.ts 100% 100% 100%
src/lib/ubxParser.ts 5% 0% 0%
src/lib/utils.ts 100% 100% 100%
src/lib/vboParser.ts 83.2% 100% 60.97%
src/lib/vehicleStorage.ts 0% 0% 0%
src/lib/videoExport.ts 0% 0% 0%
src/lib/videoFileStorage.ts 0% 0% 0%
src/lib/videoStorage.ts 0% 0% 0%
src/lib/weatherService.ts 0% 0% 0%
src/plugins/cloud-sync/accountDeletion.ts 0% 0% 0%
src/plugins/cloud-sync/accountExport.ts 0% 0% 0%
src/plugins/cloud-sync/autoSync.ts 0% 0% 0%
src/plugins/cloud-sync/cloudClient.ts 0% 0% 0%
src/plugins/cloud-sync/CloudFilesSection.tsx 0% 0% 0%
src/plugins/cloud-sync/CloudLogsPanel.tsx 0% 0% 0%
src/plugins/cloud-sync/CloudSyncPanel.tsx 0% 0% 0%
src/plugins/cloud-sync/DataPrivacyPanel.tsx 0% 0% 0%
src/plugins/cloud-sync/DownloadAllCloudLogs.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 68.42% 38.46% 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/merge.ts 90.9% 66.66% 100%
src/plugins/cloud-sync/pendingSync.ts 0% 0% 0%
src/plugins/cloud-sync/profile.ts 0% 0% 0%
src/plugins/cloud-sync/snapshotSync.ts 0% 0% 0%
src/plugins/cloud-sync/snapshotTombstones.ts 0% 0% 0%
src/plugins/cloud-sync/StoragePanel.tsx 0% 0% 0%
src/plugins/cloud-sync/storageTypes.ts 100% 100% 100%
src/plugins/cloud-sync/storeAccessors.ts 0% 0% 0%
src/plugins/cloud-sync/syncEngine.ts 0% 0% 0%
src/plugins/cloud-sync/syncStores.ts 100% 100% 100%
src/plugins/index.ts 0% 0% 0%
src/plugins/mounts.ts 100% 100% 100%
src/plugins/panels.ts 100% 100% 100%
src/plugins/PluginMount.tsx 0% 0% 0%
src/plugins/PluginPanelHost.tsx 0% 0% 0%
src/plugins/registry.ts 100% 100% 100%
src/plugins/storage.ts 32.25% 8.33% 33.33%
src/plugins/types.ts 100% 100% 100%
src/types/racing.ts 100% 100% 100%

TheAngryRaven and others added 26 commits May 24, 2026 00:10
…plugins-vIcuh

Add Cloud Sync first-party plugin (Supabase file + garage sync)
Phase 1 of the pacing rework: a standalone, unit-tested lib module porting the
DovesLapTimer issue #29 design to the web tool. Not yet wired into the app —
this lands the core math so it can be reviewed in isolation.

- resampleByDistance(): canonical arc-length grid (one point per N meters),
  independent of GPS rate and lap duration — fixes the legacy distance method's
  cumulative-noise drift and gives uniform spatial resolution for the coach.
- computePositionDelta(): projects each native current fix onto the nearest
  reference segment (interpolating the closest point so the gap doesn't snap),
  with a monotonic windowed search to defeat hairpins/self-crossings, an EMA
  (issue #29 convention) + optional zero-lag forward-backward smoother, and a
  sanity guard. Exposes matchIndex/matchFrac as a cross-lap alignment map.
- 10 tests: grid uniformity, GPS-rate independence, zero gap vs self, growing
  gap for slower laps, segment interpolation, sanity guard, smoothing.

https://claude.ai/code/session_01QF56Xjp5ZMgXrqfTWD14Le
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
X-Lovable-Edit-ID: edt-b7983c69-b33b-4805-b644-fb0536a2cc26
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
…elta

Add plugin UI panel framework, surfaced in the Labs tab
Edited UI in Lovable

Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
X-Lovable-Edit-ID: edt-be5ebb3b-4f23-42f8-b8a7-b92bd8ccacd4
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Routes the app's pace computation through the new position delta behind a
user setting. paceData keeps its exact shape, so every consumer (charts,
race-line, overlays, video export, headline gap) upgrades transparently.

- useReferenceLap now calls computeLapPace() for both the reference and
  best-lap-fallback paths, selecting position (default, zero-lag) vs the legacy
  distance method.
- New settings: deltaMethod ('position' | 'distance', default position) and
  deltaSampleMeters (default 2), with a Settings -> Lap Delta toggle.
- computeLapPace selector added to lapDelta.ts with 2 tests (distance delegates
  to the legacy path; position resamples + projects).

https://claude.ai/code/session_01QF56Xjp5ZMgXrqfTWD14Le
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
TheAngryRaven and others added 30 commits May 26, 2026 22:45
…ures-SfQKQ

GDPR: self-service data export, account deletion & IP retention
…tion-setup-SXPrV

# Conflicts:
#	README.md
#	src/pages/Register.tsx
#	supabase/config.toml
…etup-SXPrV

Complete Stripe integration: monthly/annual, signup plan select, grace + log trimming
Capture a single lap (GPS samples ± a 5s buffer, course geometry, engine,
and a copy of the vehicle/setup) as an immutable baseline for cross-session
comparison and future AI coaching.

- One snapshot per (course, engine): assigning an engine+setup prompts to
  save/update the course fastest lap when it's faster; a manual save lives in
  the lap-list Snapshots picker. A faster lap replaces it in place.
- Loaded as a comparison overlay through the external-reference slot, so it
  never auto-plays and is excluded from playback and the video player.
- Local-first and unlimited on-device; cloud-synced via a dedicated
  lap_snapshots table with per-tier COUNT limits (free 5 / plus 10 /
  premium 20 / pro 50), not byte document storage. Always pushes on save;
  a local delete never propagates to the cloud (cloud copy removed only from
  Profile -> Lap snapshots, like the log menu), with tombstones to prevent
  reconcile resurrection.

https://claude.ai/code/session_01L9h3QDcyTEXmVe6tWMio6T
…-VBWDI

Lap snapshots: frozen "course fastest lap" per engine
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
X-Lovable-Edit-ID: edt-8e5142e8-7839-483a-af55-0de6d5be440c
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Selecting a snapshot already routed it into the reference-overlay slot, but it
wasn't discoverable as "the reference" and had no entry point on the lap page.

- The Snapshots menu now reads "Load as reference lap" and selecting a snapshot
  sets it as the reference comparison.
- Add a "Load snapshot as reference" button next to the external-reference
  loader (Choose Log) on the lap times page, opening the same per-course
  snapshot picker in load-only mode. Threaded through SessionContext +
  ExternalRefBar's new trailing slot.

https://claude.ai/code/session_01L9h3QDcyTEXmVe6tWMio6T
…-VBWDI

Load lap snapshots as the reference lap
The 20260526+ migration batch (subscription_tiers, user_subscriptions,
lap_snapshots, account_deletions + RPCs) was applied to Postgres but
PostgREST kept serving a stale schema cache. Since the REST API backs both
the client and the edge functions' service-role queries, those objects were
invisible over REST — breaking checkout (non-2xx), lap-snapshot sync, and the
snapshot usage meter, while Stripe price loading (no PostgREST) still worked.

Add a migration that issues `notify pgrst, 'reload schema'` so the cache
reloads on deploy (and last in sequence on a fresh DB). Also harden two
failure modes the incident exposed: document and snapshot auto-sync now
reconcile independently, and the Profile snapshot panel no longer blanks when
its best-effort usage meter can't load.

https://claude.ai/code/session_017wyJik7iHRfxTKeuKFc4Xs
Plugin panels (the AI coach) only saw data/laps/selectedLapNumber/course/useKph,
so they had no awareness of lap snapshots even when one was loaded as the
reference. Add `activeSnapshot` to PluginPanelProps: the loaded reference
snapshot as a curated PluginSnapshot with clean-lap samples (capture buffer
trimmed) plus the frozen engine/course/vehicle/setup. Computed once in Index
from the active snapshot id and threaded through SessionContext to the Coach,
Labs, and Profile panel hosts.

https://claude.ai/code/session_01L9h3QDcyTEXmVe6tWMio6T
The auto-upload of local snapshots is wired through autoSync's reconcile on
app boot / sign-in, but a reconcile that fails (e.g. the schema-cache outage)
is swallowed and never retried in the same session, leaving local snapshots
stuck off the cloud. Have the Lap snapshots panel re-run reconcileSnapshots
when it opens and refresh live on snapshot-store changes, so viewing the panel
uploads any local-only snapshots without an app reload.

https://claude.ai/code/session_017wyJik7iHRfxTKeuKFc4Xs
…g-fixes

Fix stale PostgREST schema cache breaking subscriptions + snapshot sync
The active snapshot already carries its frozen setup (the baseline), but the
setup the driver is currently running wasn't being passed to plugins at all.
Add `sessionSetup: VehicleSetup | null` to PluginPanelProps, resolved once in
Index from the session's assigned setup id, threaded through SessionContext to
the Coach / Labs / Profile hosts. A coach panel can now compare the current
setup against the snapshot's frozen baseline.

https://claude.ai/code/session_01L9h3QDcyTEXmVe6tWMio6T
…-VBWDI

Expose active reference snapshot + session setup to plugin panels
pushSnapshot upserts ON CONFLICT (user_id, course_key, engine_key), but the
inline `unique (...)` in the CREATE TABLE was silently skipped when the table
pre-existed the snapshots migration, so the upsert errored with "there is no
unique or exclusion constraint matching the ON CONFLICT specification". Add
the constraint as an idempotent unique index so existing deployments
self-repair on apply, and reload the PostgREST cache so it picks it up.

https://claude.ai/code/session_017wyJik7iHRfxTKeuKFc4Xs
…-index

Add missing lap_snapshots (user, course, engine) unique index
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
X-Lovable-Edit-ID: edt-e538d4fb-fe01-48f0-8b89-49b2dedd48b8
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
Same root pattern as the missing unique constraint: when the lap_snapshots
table pre-existed the snapshots migration, CREATE TABLE IF NOT EXISTS skipped
the whole declaration, so `id uuid primary key default gen_random_uuid()` and
`updated_at timestamptz not null default now()` came in without their defaults.
With the unique index in place the upsert now reaches the INSERT and fails
with "null value in column id violates not-null constraint" because
pushSnapshot doesn't (and shouldn't) send an id.

Idempotent ALTER COLUMN ... SET DEFAULT for both, plus a PostgREST reload.

https://claude.ai/code/session_017wyJik7iHRfxTKeuKFc4Xs
Updates the optional AI coach plugin from 0.2.5 to 0.3.0 in
package.json/package-lock.json and notes the bump under CHANGELOG
[Unreleased] → Changed.
…-defaults

Re-set lap_snapshots column defaults (id, updated_at)
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
X-Lovable-Edit-ID: edt-5c74e9c1-1d5f-4af3-97dc-ed3bea2aa006
Co-authored-by: TheAngryRaven <2923950+TheAngryRaven@users.noreply.github.com>
The Profile-tab Storage section showed bytes for documents + logs but had no
sign of the snapshot count quota (free 5 / plus 10 / premium 20 / pro 50), so
users couldn't see how close they were to the limit before a save was rejected.

- Add a count-based meter for "Lap snapshots" beneath the byte meters, fetched
  from the snapshot_usage RPC (best-effort, doesn't hide the byte meters if it
  fails).
- Add inline "Local storage is always free." subtext next to the Storage label
  so the byte+count limits read as cloud-only.

https://claude.ai/code/session_01L9h3QDcyTEXmVe6tWMio6T
…-VBWDI

Add snapshot quota meter + "local is free" hint to the Storage section
The snapshot quota meter (PR #80) was gated on fetchSnapshotUsage(), which
silently returns null in production — likely a stale PostgREST schema cache.
Both meters that depended on it (the Storage panel's count bar and the Lap
snapshots panel's "X of Y synced" header) were rendering nothing or the
fallback "Synced snapshots" instead.

- Drop the RPC dependency in both panels.
- Use listCloudSnapshots(user.id).length for the count (the same call those
  panels were already making to render the list).
- Use the user's tier's snapshot_count from useSubscription's tier catalogue
  for the limit.

The snapshot_usage RPC stays in cloudClient for now in case other callers
appear; the panels just don't need it.

https://claude.ai/code/session_01L9h3QDcyTEXmVe6tWMio6T
…-VBWDI

Fix snapshot quota meter: derive count + limit client-side
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