# Supabase (optional round sync)

Settle the Card can talk to your Supabase project for **shared round state** (Fairway Sync). This is optional: if URL or anon key is missing, sync helpers stay idle.

## Keys (important)

- **Publishable / anon key** -- safe to use in the browser. Put it in `index.html` or set `window.STC_SUPABASE_ANON_KEY` (same for URL: `window.STC_SUPABASE_URL`).
- **Secret / service_role key** -- **never** embed in the app, GitHub Pages, or this repo. Use only in trusted server code or Supabase SQL Editor. If a secret key was ever pasted into chat or committed, **rotate it** in the dashboard (Settings > API > regenerate).

## Configure the static app

1. In [Supabase](https://supabase.com) > your project > **Settings > API**, copy:
   - **Project URL** (e.g. `https://abcdefgh.supabase.co`)
   - **Publishable** (or legacy **anon public**) key
2. In `index.html`, set the meta tags:

```html
<meta name="stc-supabase-url" content="https://YOUR_REF.supabase.co">
<meta name="stc-supabase-anon-key" content="YOUR_PUBLISHABLE_OR_ANON_KEY">
```

Alternatively, before `app.js` loads:

```html
<script>
  window.STC_SUPABASE_URL = "https://YOUR_REF.supabase.co";
  window.STC_SUPABASE_ANON_KEY = "YOUR_PUBLISHABLE_OR_ANON_KEY";
</script>
```

## Disable the cloud play module (local-only build)

To ship a **local-only** build without changing `app.js`: load **`stc-cloud-play.js`** (already included before `app.js` in `index.html`) and set either:

```html
<meta name="stc-cloud-play-enabled" content="false">
```

or, before `stc-cloud-play.js`:

```html
<script>window.STC_CLOUD_PLAY_ENABLED = false;</script>
```

When disabled:

- The **Fairway Sync** card on Setup is hidden.
- `stcIsSupabaseConfigured()` is false, so no host/follow/play-along network calls run.
- Any saved **host / follow / play-along** sync identity in `localStorage` is cleared on the next page load (mode returns to **Local only**).

The Supabase UMD script may still load from the CDN; remove that block too if you want a smaller offline-first page.

---

## Database -- Fairway Sync v2 (required)

1. Open **`docs/stc-game-slots.sql`** in this repo -- copy the whole file into **Supabase > SQL Editor > Run**.
2. If `ALTER PUBLICATION supabase_realtime ADD TABLE` errors with *"already member"*, the table is already in Realtime (safe to ignore).
3. If `CREATE EXTENSION pg_cron` fails, enable pg_cron under **Database > Extensions** first, then re-run just section 5.

> The older `docs/supabase-rounds.sql` (`rounds` + `round_device_states` tables) can remain; they are no longer used by Fairway Sync v2 but the app will not error if they exist.

---

## Database -- Pro / Free entitlement (optional, for paid tier)

1. **Authentication (Pro upgrade uses Google):** in Supabase → **Authentication** → **Providers** → **Google**:
   - Turn **Google enabled** on.
   - In [Google Cloud Console](https://console.cloud.google.com/) → **APIs & Services** → **Credentials** → **Create credentials** → **OAuth client ID** (application type **Web application**). Add **Authorized JavaScript origins** for your app (e.g. `https://settlethecard.golf`). Under **Authorized redirect URIs**, add the exact URL Supabase shows on the Google provider page — it looks like `https://<your-project-ref>.supabase.co/auth/v1/callback`.
   - Copy **Client ID** and **Client Secret** into the Supabase Google provider fields and **Save**.
2. **Site URL / Redirect URLs:** under **Authentication** → **URL Configuration**, set **Site URL** to your **production** app (e.g. `https://settlethecard.golf`), not `http://localhost:3000`. If **Site URL** stays on localhost, users who start Google sign-in from production can be sent back to localhost. Add **Redirect URL** entries for every origin: production (apex + `www` if you use it), **Vercel preview** hosts if you test there, and `http://localhost:…` for local dev. Optional: in `index.html`, **`meta stc-oauth-return-base`** forces the OAuth `redirectTo` origin (must still be allowlisted).
3. Users must have an `auth.users` row for the app to read **`stc_user_entitlements`**.
4. Open **`docs/stc-entitlements.sql`**, copy into **SQL Editor** → **Run**.
5. If `EXECUTE FUNCTION` on the trigger fails on an older Postgres build, try `EXECUTE PROCEDURE public.stc_handle_new_user_entitlements();` instead.

The app reads **`public.stc_user_entitlements`** with a **second** Supabase client that **persists auth** (`storageKey: stc-supabase-auth`). Fairway Sync keeps the original client session-less (`persistSession: false`).

- **Browser:** after sign-in, `stcGetEntitlementTier()` prefers the server row when `window.__stcEntitlementServerState` is resolved (see `docs/STC-TIER.md`). Call `await stcRefreshEntitlementFromSupabase()` from devtools after you change a row in SQL.
- **Writes:** do **not** grant `INSERT`/`UPDATE` on this table to `anon` or `authenticated`. Update rows from the **SQL Editor** (Dashboard), a **service_role** Edge Function, or your **Stripe webhook** backend.

Example (SQL Editor, after you know the user’s UUID from **Authentication → Users**):

```sql
UPDATE public.stc_user_entitlements
SET tier = 'plus', plus_expires_at = NULL, updated_at = now()
WHERE user_id = '00000000-0000-0000-0000-000000000000';
```

Use `plus_expires_at` for time-limited Pro (subscription end). `NULL` means no expiry.

For **Stripe** checkout and webhooks that update this table, see **[STRIPE-SUPABASE-ENTITLEMENTS.md](./STRIPE-SUPABASE-ENTITLEMENTS.md)**.

---

### What `stc_game_slots` looks like

| Column       | Type        | Description |
|---|---|---|
| `game_code`  | text        | Human-readable room code (e.g. `ABCD12`) |
| `team_slot`  | integer 1-4 | 1 = host, 2-4 = buddies in join order |
| `device_id`  | text        | Unique per browser (stored in `localStorage`) |
| `write_token`| text        | Per-device random token; UPDATE rejected if it doesn't match |
| `player_data`| jsonb       | Array of `{ id, name, courseHandicap, grossScores, dots }` -- THIS device's players only |
| `game_config`| jsonb       | Full game setup (course, rules, wagers) -- **slot 1 only** writes this |
| `revision`   | integer     | Increments on every successful save |
| `updated_at` | timestamptz | Used by the 12-hour cleanup cron |

### How Fairway Sync v2 works

**Strict data ownership** — each phone is its **own** game for roster + setup: **names, tees, and handicaps on this device are owned only here.** Another phone never overwrites your player rows.

**Aggregation only** — the scorecard pills, segment ticker, and Settle tab **combine** scores from every slot so the group sees one live round. That merge uses **separate** player ids on the wire (`slot1_p1`, `slot2_p1`, …) so “David on the host” and “Larry on the buddy” never share one `state.players` row.

```
Host (slot 1)                     Buddy (slot 2, 3, 4)
────────────────────────────────  ──────────────────────────────────────
Writes stc_game_slots slot 1      Writes stc_game_slots slot N
  - game_config (course, rules)     - player_data (this phone’s players + scores)
  - player_data (host squad)
                                  Reads slot 1 → applies game_config only
Reads slot 2, 3, 4 →               (course, wagers — not your roster)
  merges remote player_data as     Reads slot 1 player_data →
  extra rows / prefixed ids         merges host scores for ticker / settlement
  for ticker / settlement           (never overwrites your Setup roster rows)
```

**Poll cadence:** all devices fetch all slots for their `game_code` every **20 seconds**. The Realtime publication on `stc_game_slots` also triggers an immediate update when any slot changes (faster-than-20s path).

**Isolation guarantee (`stcApplyRemoteSlotRows`):** Incoming `player_data` ids in `state.sync.playAlongLocalPlayerIds` are **never** written (your scores and rows stay yours). Rows from other slots must use **wire ids prefixed with `slot{N}_`** (e.g. `slot1_p1`); unprefixed legacy payloads are **not** merged into an existing roster row with the same id (different people on different phones can both use `p1`…`p4`).

### Data retention (12 hours idle)

Section 5 of `stc-game-slots.sql` schedules pg_cron to run every 15 minutes and DELETE any `stc_game_slots` row whose `updated_at` is older than 12 hours.

If your plan does not include pg_cron, use an external scheduler (e.g. GitHub Action) calling a service_role Supabase client with the same DELETE predicate.

---

## In the app (Setup > Fairway Sync)

1. **Local** -- default; scores stay on this device only.
2. **Host** -- tap **Start Game**. The app clears stale sync slot data (prior `roundId`, buddy snapshots, merged `slotN_*` wire players), creates a `stc_game_slots` slot-1 row, and shows a **room code**. Share the code (or the auto-generated co-scorer link) with buddy phones.
3. **Play along** -- enter the host's room code (4+ chars) and tap **Join**. The app:
   - Reads all existing slots to find the next free slot (2, 3, or 4).
   - Applies the host's `game_config` from slot 1 (course, rules, wagers — **not** your player names).
   - Keeps your local Setup roster as **your** players (those IDs are recorded in `playAlongLocalPlayerIds`).
   - Writes your slot row with your player scores (wire ids prefixed per slot so merges never clobber your rows).
   - Polls every 20 s; host scores appear in ticker / Settle via **aggregated** rows — your Setup and scores stay on this phone.
4. **Follow** -- view-only; polls updates, no writes.
5. **Disconnect** -- returns to local-only (your last scores stay in the browser).

**Co-scorer link format (v2):** `https://yourdomain/app#stc-join=GAMECODE`  
Opening this on another phone automatically calls `stcSlotBuddyJoinGame` with the code from the URL hash.

---

## Files

| File | Purpose |
|---|---|
| `index.html` | Loads `@supabase/supabase-js` UMD from jsDelivr |
| `docs/stc-game-slots.sql` | **Required** -- DDL for `stc_game_slots` (Fairway Sync v2) |
| `docs/stc-entitlements.sql` | **Optional** -- DDL for `stc_user_entitlements` (server-side Pro vs free) |
| `api/stripe/README.md` | Stripe **webhook** + **create-checkout-session** on Vercel |
| `docs/supabase-rounds.sql` | Legacy v1 DDL -- leave in place, no longer active |
| `app.js` | `stcSlotPushMyData`, `stcSlotPollRemote`, `stcApplyRemoteSlotRows`, `stcSlotHostStartGame`, `stcSlotBuddyJoinGame`, `stcSlotDisconnect` |

## GitHub Pages / service worker

Bump the cache name in `sw.js` when you change `app.js` / `index.html` / `style.css` behavior. The Supabase library is loaded from the CDN at runtime (not precached by the service worker).
