Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8c0e801
Merge pull request #303 from TheAngryRaven/main
TheAngryRaven Jun 25, 2026
6b5bbda
Add community leaderboards (plan 0005)
claude Jun 26, 2026
70dfab8
Translate leaderboards i18n strings (es/fr/de/it/pt-BR/ja)
claude Jun 26, 2026
372e5d7
Merge pull request #306 from TheAngryRaven/claude/leaderboards-featur…
TheAngryRaven Jun 26, 2026
3841b90
Surface real error when a leaderboard submit fails (plan 0005)
claude Jun 26, 2026
640e80a
Fix leaderboard submit: round lap_time_ms to integer (plan 0005)
claude Jun 26, 2026
49b1566
Require an engine on every vehicle (plan 0005)
claude Jun 26, 2026
9338cdd
Compute sector splits for leaderboard laps (plan 0005)
claude Jun 26, 2026
c9cf7e2
Leaderboards: add ranked entry list with per-lap + top-N load (plan 0…
claude Jun 26, 2026
a485ac3
Merge pull request #308 from TheAngryRaven/claude/leaderboards-featur…
TheAngryRaven Jun 26, 2026
4dcf8df
Leaderboards: remove setup sharing entirely (plan 0005)
claude Jun 26, 2026
4564556
plan 0006: dynamic per-branch Supabase preview database
claude Jun 26, 2026
59156d8
Merge pull request #309 from TheAngryRaven/claude/leaderboards-drop-s…
TheAngryRaven Jun 27, 2026
95bcc79
plan 0006: public driver profiles with avatars
claude Jun 27, 2026
51fafe8
Merge pull request #311 from TheAngryRaven/claude/dynamic-supabase-db…
TheAngryRaven Jun 27, 2026
c5913b0
Merge pull request #310 from TheAngryRaven/claude/user-profiles-image…
TheAngryRaven Jun 27, 2026
d7c6447
Wire up Alfano connectivity placeholders + native skeleton
claude Jun 28, 2026
15cddfe
Merge pull request #312 from TheAngryRaven/claude/alfano-connectivity…
TheAngryRaven Jun 28, 2026
e405e7c
plan 0006: self-healing repair migration for user-profiles backend
claude Jun 28, 2026
b68984b
Merge pull request #313 from TheAngryRaven/claude/avatar-rls-repair-f…
TheAngryRaven Jun 28, 2026
977169b
plan 0006: add missing SELECT policy on user-avatars (fixes avatar up…
claude Jun 29, 2026
87016f6
Merge pull request #315 from TheAngryRaven/claude/avatar-select-polic…
TheAngryRaven Jun 29, 2026
93523d0
plan 0006: branch-tiered Supabase backend (main=base, BETA=_PREVIEW, …
claude Jun 29, 2026
7e2f2f5
Merge pull request #316 from TheAngryRaven/claude/supabase-branch-tie…
TheAngryRaven Jun 29, 2026
cef1bb7
plan 0006: make public_profiles a security_invoker view (clears linte…
claude Jun 29, 2026
fe3d0ab
plan 0006: live leaderboard names + custom-track submit notice
claude Jun 29, 2026
a19e018
plan 0006: auto-submit custom tracks on snapshot submit + self-correc…
claude Jun 29, 2026
1fbd5dd
Merge pull request #317 from TheAngryRaven/claude/db-tweaks-fheq7s
TheAngryRaven Jun 30, 2026
18960d3
plan 0006: clickable leaderboard avatars + clickable driver-profile s…
claude Jun 30, 2026
e32f903
Merge pull request #318 from TheAngryRaven/claude/driver-profile-clic…
TheAngryRaven Jun 30, 2026
e9b0744
Minor UX: public-vehicle badge, "Bluetooth LE" logger label, strike d…
claude Jun 30, 2026
5b4d4bd
Merge pull request #319 from TheAngryRaven/claude/minor-edits-fheq7s
TheAngryRaven Jun 30, 2026
49d2a6f
chore(release): prep v3.0.0 — version bump + dated changelog
claude Jun 30, 2026
c53b7fa
Add "request a datalogger" contact category + logger-picker button
claude Jun 30, 2026
da6d3bc
Merge pull request #321 from TheAngryRaven/feature/request-loggers-co…
TheAngryRaven Jun 30, 2026
1a9e2e3
Merge pull request #320 from TheAngryRaven/release/v3.0.0
TheAngryRaven Jun 30, 2026
6356bf4
i18n: translate profiles/datalogger stop-gap strings (es/fr/de/it/pt-…
claude Jun 30, 2026
ae6ee52
Merge pull request #322 from TheAngryRaven/chore/translate-i18n-fheq7s
TheAngryRaven Jun 30, 2026
c9e915f
fix: resolve the six Low findings from the v3.0.0 release review
claude Jun 30, 2026
c0306ad
Merge pull request #323 from TheAngryRaven/fix/release-review-lows
TheAngryRaven Jun 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
> from git history and grouped by theme rather than exhaustive per-commit
> detail.

## [3.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
- **An engine is now required on every vehicle.** The vehicle form won't save without
one (it drives leaderboard grouping and snapshot matching), and any existing
engine-less vehicle is flagged in the garage so you can fill it in.
- **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.

## [2.9.2] - 2026-06-25

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

---

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

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

Pushes to a **non-production branch** produce a preview version/URL of the same
Worker. To point those preview builds at a Supabase **preview-branch database**
(so beta work doesn't touch production data), set parallel `*_PREVIEW` build
variables. Workers Builds exposes `WORKERS_CI_BRANCH` (Pages: `CF_PAGES_BRANCH`)
on every build; `vite.config.ts` prefers the `_PREVIEW` value of each key
whenever that branch isn't `main`, and ignores them on `main` and in local dev.
The build picks its Supabase backend by **git branch**, in three tiers:

| Branch | Database | How it's chosen |
|--------|----------|-----------------|
| `main` | production | base build vars (`VITE_*` / `HTT_*`) |
| `BETA` | the shared beta DB | the `*_PREVIEW` build vars |
| any other branch | that branch's **own** Supabase preview-branch DB | resolved via the Management API, falling back to `*_PREVIEW`/beta when none exists |

`BETA` is the shared integration database and is **never** routed onto a per-branch
DB — only feature branches consult the resolver. Each build logs the result
(`[backend] Supabase URL baked: … — <tier>`) so a deploy always states which DB it
baked and why. Set the `*_PREVIEW` vars so non-`main` builds don't touch production:

Workers Builds exposes `WORKERS_CI_BRANCH` (Pages: `CF_PAGES_BRANCH`) on every
build; `vite.config.ts` prefers the `_PREVIEW` value of each key whenever the
branch isn't `main`, and ignores them on `main` and in local dev.

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

##### Dynamic per-branch databases (feature branches)

Without a token, every non-`main` preview (including feature branches) uses the
static `*_PREVIEW`/beta DB. To instead give each **feature** branch its **own**
Supabase preview-branch database — so its preview deployment exercises that
branch's migrations without merging into beta first — add a `SUPABASE_ACCESS_TOKEN`
build secret (`BETA` always stays on `*_PREVIEW` regardless):

1. Create a Supabase **personal access token** (Account → Access Tokens).
2. Worker → **Settings → Build → Variables and Secrets** → add it as a secret:
`SUPABASE_ACCESS_TOKEN` (keep the static `*_PREVIEW` values as the fallback).

On a **feature-branch** build (not `main`, not `BETA`), `vite.config.ts` 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, that
branch's URL + anon key + ref are baked in; otherwise it falls back to the static
`*_PREVIEW`/beta creds. The lookup never throws and times out fast, so it can't
break a deploy. Full design: [`docs/plans/0007-dynamic-supabase-branch-db.md`](docs/plans/0007-dynamic-supabase-branch-db.md).

> Supabase only generates a preview branch when the git branch carries **migration
> changes**. Branches without DB changes simply fall back to beta. To force a DB
> for a branch anyway, push a no-op migration or create the branch by hand in the
> Supabase dashboard. Remember to add each preview URL to the branch's
> **Auth → Redirect URLs** for cloud sign-in.

#### Custom domains (production + beta)

- **Production — `lapwingdata.com`:** `wrangler.jsonc` declares a
Expand Down
Loading
Loading