# 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.

### 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/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).
