# VOTD — Infrastructure & External Services

Canonical record of all external accounts, domains, services, and configuration decisions. Update this file whenever a new service is registered, a domain is purchased, or a key infrastructure decision is made.

---

## Domains

Both domains purchased on **Porkbun** (March 21, 2026). WHOIS privacy enabled (free on Porkbun, auto-included). Auto-renew is on.

| Domain | Purpose | Registrar |
|--------|---------|-----------|
| `votd.io` | Primary — share links, OG previews, App Store landing, web presence | Porkbun |
| `votd.app` | Secondary — defensive registration, redirect to votd.io | Porkbun |

**Why `votd.io` is primary:** Already baked into the Xcode bundle ID (`io.votd.app`) — the most established, credible TLD for a tech/civic product. `votd.app` was grabbed defensively to prevent squatting and redirect traffic.

**Why Porkbun:** Recommended in the Track B project plan. Cheaper renewals than Hover, avoid GoDaddy.

**Planned use of `votd.io`:**
- `votd.io/v/[id]` — per-vote share link with Open Graph tags for rich iMessage/WhatsApp previews
- OG image endpoint for dynamic per-vote preview cards
- App Store redirect

Do NOT use Supabase project URLs as the share link base — they are internal infrastructure only.

---

## Cloudflare

Added April 7, 2026. Free plan. Manages DNS for `votd.io` and hosts Cloudflare Workers for server-side routing.

| Field | Value |
|-------|-------|
| Account | tomaskeane2o24@gmail.com |
| Plan | Free |
| Zone | votd.io |
| Worker name | `votd-invite` |
| Worker dev URL | `votd-invite.tomaskeane2o24.workers.dev` |
| Nameservers | `deb.ns.cloudflare.com`, `jobs.ns.cloudflare.com` (set in Porkbun) |

**Worker routes on `votd.io`:**

| Route | Purpose |
|-------|---------|
| `/invite/{token}` | Proxies to Supabase `invite` edge function — validates token, tracks click, redirects to TestFlight |
| `/dashboard?secret=votd2026` | Renders invite tracking dashboard (HTML) — fetches data from Supabase REST API |
| `/v/[id]` | (Future) Share link with OG tags for rich previews |

**Why Cloudflare:** Free tier covers 100K requests/day. Workers give full control over HTTP headers and routing. Chosen over Vercel based on simplicity and the fact that all current needs (invite proxy, dashboard, future share links) fit in a single Worker. Also provides free SSL and CDN.

---

## Supabase Projects

Two separate projects — not schemas within the same project. A mistake in prototype can never touch live data.

| Name | Project ID | Region | Status | Role |
|------|-----------|--------|--------|------|
| VOTD Proto | `xtxznjnqikuabjyotjbi` | us-east-1 | Active | Prototype / development |
| VOTD SAC Pilot | `dunktqzfprbjqxbcnooi` | us-west-2 | Inactive | Sacramento pilot (paused) |

**App is connected to:** VOTD Proto (`xtxznjnqikuabjyotjbi`)

The SAC Pilot project is the seed for the regional rollout model — one Supabase project per city/region. It is currently inactive but preserved. When the Sacramento launch begins, it will be activated and wired to its own admin environment.

---

## iOS App

| Field | Value |
|-------|-------|
| Bundle ID | `io.votd.app` |
| Watch bundle ID | `io.votd.app.watchkitapp` |
| Distribution | TestFlight (internal) → App Store |
| Current branch | `main` |
| Build numbering | Sequential, never reuse a number. Skip bad builds. |

---

## App Name & Branding

**VOTD** — stands for "Vote of the Day". Pronounced as letters (V-O-T-D), not as a word. The concept: one civic vote published per day.

Not to be confused with:
- ~~theref~~ — old working name, retired
- ~~The Referendum~~ — original project name from early sessions

---

## Share Link Architecture (planned)

Rich share previews (iMessage, WhatsApp, Slack) require a server-side URL that responds to crawler bots with Open Graph meta tags. This cannot be done natively in the app.

**Plan:**
- Deploy a lightweight endpoint at `votd.io/v/[id]`
- Returns minimal HTML with OG tags: `og:title`, `og:description`, `og:image`
- `og:image` is either a static branded VOTD card (v1) or a dynamically generated per-vote image with question + live tally (v2)
- Body can be a single line: "Download VOTD on the App Store"

**Hosting:** Cloudflare Worker (`votd-invite`). The same Worker already handles `/invite/{token}` and `/dashboard` routes. Add `/v/[id]` route when ready.

---

## Key Infrastructure Decisions

| Decision | Choice | Date | Rationale |
|----------|--------|------|-----------|
| Domains | Porkbun (votd.app + votd.io) | March 21, 2026 | Cheapest renewal cost, free WHOIS privacy |
| Database | Supabase (Postgres) | March 2026 | Auth + DB + Edge Functions in one platform |
| Share link base URL | votd.app (not Supabase) | March 30, 2026 | Supabase URLs are infrastructure, not brand |
| Native package for share | NativeModules.RNShare | March 2026 | react-native-share caused startup crash; bypassed |
| Feedback backend | Supabase `feedback` table | March 2026 | Confirmed working, 2 submissions received from build 80 |
| DNS / CDN | Cloudflare (free plan) for votd.io | April 7, 2026 | Workers for invite proxy + dashboard; future share links |
| Invite tracking | Supabase `invite_links` table + edge functions | April 7, 2026 | One link per tester, click tracking, revoke support |

---

## Invite Link System (TestFlight phase — temporary)

Trackable, per-person invite links for the Founders' Preview TestFlight group. Each tester gets a unique URL; clicks are logged in Supabase.

**How it works:**

1. Supabase `invite_links` table stores token, name, channel, click count, revoke flag
2. Supabase edge function `invite` resolves token → validates → increments click count → 302 redirects to TestFlight public link
3. Cloudflare Worker on `votd.io` proxies `/invite/{token}` to the Supabase edge function

**URLs:**

| What | URL |
|------|-----|
| Create an invite | `https://xtxznjnqikuabjyotjbi.supabase.co/functions/v1/create-invite?name=First+Last&channel=sms&secret=votd2026` |
| Invite link (what you send) | `https://votd.io/invite/{token}` |
| Tracking dashboard | `https://votd-invite.tomaskeane2o24.workers.dev/dashboard?secret=votd2026` |
| Dashboard (once DNS propagates) | `https://votd.io/dashboard?secret=votd2026` |

**Create invite parameters:**

| Param | Required | Example | Notes |
|-------|----------|---------|-------|
| `name` | Yes | `Jane+Doe` | Who the link is for |
| `channel` | No | `sms`, `linkedin`, `email` | Defaults to `direct` |
| `secret` | Yes | `votd2026` | Simple auth gate |

**Monitoring for untracked access:**

The invite system tracks every click through Supabase. Compare the total tracked clicks (visible on the dashboard) against TestFlight install count in App Store Connect. If installs exceed tracked clicks, the raw TestFlight public link may have been shared directly.

A savvy user could theoretically extract the TestFlight URL by inspecting the 302 redirect in browser dev tools. This is acceptable for a founders' preview — the tracking layer is for visibility, not security.

Quick SQL check: `SELECT SUM(click_count) FROM invite_links WHERE revoked = false;`

**If the TestFlight link leaks:**

1. **Disable the public link immediately.** ASC → TestFlight → Founders' Preview → Testers tab → Manage next to Public Link → toggle off. This kills the URL instantly. Existing installs remain functional but no new installs via that URL.
2. **Re-enable to generate a new URL.** Apple issues a fresh public link with a different join code.
3. **Update all invite links in Supabase:**
   ```sql
   UPDATE invite_links SET testflight_url = 'https://testflight.apple.com/join/NEW_CODE' WHERE revoked = false;
   ```
4. **Optionally expire the old build** in ASC (build detail → Expire Build). This prevents the app from launching for anyone on that build — affects all testers, not just the leaker.
5. **Revoke specific invite tokens** if you know which one was shared:
   ```sql
   UPDATE invite_links SET revoked = true WHERE token = 'the_token';
   ```

**What you cannot do:**
- Remotely wipe or kill the app on a specific person's device
- Revoke access for a single tester when using the public link model (only email-invited testers can be individually removed)
- Prevent someone who already installed from using the app until the build expires (90 days) or you manually expire it

**Escalation path if needed:** Disable the public link entirely, switch to email-only invites in ASC, and have the `create-invite` function collect the tester's email address for manual ASC addition. This gives per-person revocation but adds friction to each invite.

**Cleanup:** This system is temporary. Remove Cloudflare Worker routes, archive the edge functions, and drop the `invite_links` table once the app moves to public App Store distribution.

---

*Last updated: April 7, 2026*
