Architecture
A modern hybrid architecture that retains Google Sheets as the primary user interface whilst Supabase (PostgreSQL) serves as the source of truth.
Overview
┌─────────────────────────────────────────────────────────────────────────────┐│ USER INTERFACE │├─────────────────┬─────────────────┬─────────────────┬───────────────────────┤│ Google Sheets │ iPad Studio │ Companion │ vMix/ATEM ││ (sharing, │ (PWA for │ (TC control) │ (video production) ││ mobile, iPad) │ floor manager)│ │ │└────────┬────────┴────────┬────────┴────────┬────────┴───────────┬───────────┘ │ │ │ │ │ onEdit trigger │ Supabase │ HTTP API │ │ │ Realtime │ │ ▼ ▼ ▼ ▼┌─────────────────────────────────────────────────────────────────────────────┐│ CLOUDFLARE WORKER ││ - Validation and transformation ││ - Conflict handling (optimistic locking) ││ - Rate limiting ││ - Webhook dispatch │└────────────────────────────────┬────────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────────────────┐│ SUPABASE ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ posts │ │ people │ │ programs │ │ audit_log │ ││ │ (ACID, RLS) │ │ │ │ │ │ (history) │ ││ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ ││ ││ - PostgreSQL with full ACID guarantees ││ - Row Level Security (RLS) ││ - Automatic audit via triggers ││ - Realtime subscriptions (WebSocket) ││ - Optimistic locking (version column) │└─────────────────────────────────────────────────────────────────────────────┘Components
1. Google Sheets (UI + Sharing)
- Role: Primary user interface, team sharing, mobile access
- Advantages: Familiar interface, works on all devices, zero cost
- Sync:
onEdittrigger → Cloudflare Worker → Supabase
2. Supabase (Source of Truth)
- Role: Data storage with ACID guarantees, history, realtime
- Schema:
supabase/schema.sql - Features:
versioncolumn for optimistic lockingaudit_logtable populated automatically via triggersposts_activeview excludes soft-deleted postsprogram_statsview for rapid statistics- Realtime enabled for live updates
3. Cloudflare Worker (Sync Layer)
- Role: Intermediary for all synchronisation
- Functions:
- Validation of incoming data
- Conflict detection and resolution
- Rate limiting
- Transformation between Sheets ↔ Supabase formats
- Deployment:
worker/directory
4. iPad Studio PWA
- Role: Optimised view for floor manager
- Features:
- Touch-optimised interface
- Realtime via Supabase subscriptions
- Offline-capable via service worker
- Recording timer
- Deployment:
studio-app/→ Vercel/Netlify
Data Flow
Sheets → Supabase
1. User modifies cell in Sheets2. onEdit trigger fires3. Sync.gs sends to Cloudflare Worker4. Worker validates, checks version5. If conflict: returns 409, displays dialogue6. If OK: saves to Supabase7. Supabase trigger logs to audit_logSupabase → Sheets
1. External client (Studio, Companion) modifies data2. Worker validates and saves to Supabase3. Worker calls Sheets webhook4. Sync.gs receives, updates cellConflict Handling
1. Client A reads post (version=5)2. Client B reads same post (version=5)3. Client B saves change → version=64. Client A attempts save → 409 Conflict5. Client A receives options: - Keep my version (force push) - Use server version - Merge (latest per field)Setup Guide
Step 1: Supabase
# 1. Create project at supabase.com
# 2. Run schema# Navigate to SQL Editor, paste supabase/schema.sql
# 3. Note credentials# - Project URL: https://xxx.supabase.co# - anon key: eyJ...# - service_role key: eyJ... (for Worker)Step 2: Cloudflare Worker
cd worker
# Install dependenciesnpm install
# Configure secretswrangler secret put SUPABASE_URLwrangler secret put SUPABASE_SERVICE_KEYwrangler secret put SHEETS_WEBHOOK_SECRET
# Deploynpm run deploy
# Note URL: https://gudstjanst-sync.xxx.workers.devStep 3: Google Sheets
1. Open Apps Script (Extensions > Apps Script)
2. Add Sync.gs
3. Configure Script Properties: - SYNC_WORKER_URL = <Worker URL> - SYNC_WEBHOOK_SECRET = <same as in Worker> - SYNC_ENABLED = true
4. Run syncProgramToSupabase(1) for initial syncStep 4: iPad Studio PWA
cd studio-app
# Install dependenciesnpm install
# Create .env from .env.examplecp .env.example .env# Fill in VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY
# Run locallynpm run dev
# Build for productionnpm run build
# Deploy to VercelvercelCosts
| Service | Cost | Included |
|---|---|---|
| Supabase Free | £0/month | 500 MB database, 2 GB bandwidth, unlimited API |
| Cloudflare Workers Free | £0/month | 100k requests/day |
| Vercel Free | £0/month | 100 GB bandwidth, unlimited deploys |
| Google Sheets | £0/month | Included in Workspace |
Total: £0/month
Migration Plan
Week 1: Database
- Create Supabase project
- Run schema.sql
- Verify all tables created correctly
Week 2: Worker
- Deploy Cloudflare Worker
- Configure secrets
- Test /health endpoint
Week 3: Sheets Integration
- Add Sync.gs
- Configure Script Properties
- Run initial sync for all programmes
Week 4: iPad View
- Deploy Studio PWA to Vercel
- Test on iPad
- Optimise for production environment
Week 5: Production
- Full production test
- Train users
- Go live
Future Improvements
- Offline-first in Sheets: Service worker for Sheets-like offline functionality
- Push notifications: Notify on TC_IN/TC_OUT via PWA
- Companion plugin: Native integration instead of HTTP
- vMix Data Source: Direct SQL connection to Supabase
- Analytics dashboard: Historical statistics in Supabase Studio
Files in This Repository
the-public-service/├── supabase/│ └── schema.sql # PostgreSQL schema with triggers├── worker/│ ├── src/index.ts # Cloudflare Worker│ ├── package.json│ └── wrangler.toml├── studio-app/│ ├── src/│ │ ├── App.tsx # Main component│ │ ├── main.tsx│ │ └── styles.css│ ├── package.json│ ├── vite.config.ts│ └── .env.example├── Sync.gs # Apps Script sync module├── Config.gs├── Database.gs├── Triggers.gs├── UI.gs├── Views.gs└── ARCHITECTURE-2026.md # This file