Back to Document Hub Architecture Map

Summit Editing

A credit-based video-editing SaaS for real-estate media companies. Clients buy credits, submit raw footage with an edit spec, and editors deliver finished property videos through a tracked, audited order lifecycle.

TanStack Start · Vite 7 · React 19 Deploy: Cloudflare Workers DB: Supabase Postgres + RLS Pkg mgr: Bun Payments: Stripe Launch target: Jul 6, 2026
1 Overview

Summit Editing is a server-rendered React app on TanStack Start + Vite 7, deployed to Cloudflare Workers. The backend is Supabase Postgres, with all authorization enforced in the database via Row-Level Security (RLS) and SECURITY DEFINER functions keyed off the Supabase-issued JWT (auth.uid()). Payments run through Stripe (embedded checkout, hand-rolled webhook verification), and transactional email is a Supabase-native pgmq queue + pg_cron drainer pipeline rendered with React Email. A thin Lovable Cloud layer supplies OAuth, email transport, webhook verification, and the Vite plugin chain — only ~4 application files import @lovable.dev/*.

Request lifecycle

                       ┌─────────────────────────────────────────────────────────┐
 Browser               │  Cloudflare Worker  (entry: src/server.ts)              │
 ───────               │  ─────────────────────────────────────────             │
 React SPA / SSR  ────▶ │  server.ts ── catches catastrophic SSR errors,         │
 fetch + serverFn      │     │         renders branded 500 page                  │
  (Bearer JWT)         │     ▼                                                    │
                       │  @tanstack/react-start/server-entry                     │
                       │     ├─▶ File routes (src/routes/**)                      │
                       │     │     • React pages (SSR + hydrate)                  │
                       │     │     • Server routes (.ts API handlers)             │
                       │     └─▶ Server functions (createServerFn)                │
                       │           start.ts middleware:                           │
                       │             • errorMiddleware (request)                  │
                       │             • attachSupabaseAuth (function, client side) │
                       └─────────────────────┬────────────────────────────────────┘
                                             │  Authorization: Bearer <supabase JWT>
                                             ▼
                                 ┌───────────────────────────┐
                                 │  Supabase Postgres        │
                                 │  • RLS policies (auth.uid)│
                                 │  • SECURITY DEFINER RPCs  │
                                 │  • has_role() gate        │
                                 │  • pgmq + pg_cron (email) │
                                 └───────────────────────────┘
  • src/server.ts — thin wrapper around @tanstack/react-start/server-entry; normalizes h3's swallowed {"unhandled":true,"message":"HTTPError"} 500s into a branded error page (normalizeCatastrophicSsrResponse).
  • src/start.ts — registers a request-level error boundary and the function-level attachSupabaseAuth middleware that attaches the user's Supabase access token as a Bearer header on every server-function RPC.
  • The browser holds the Supabase session; every gated read/write carries that JWT into Postgres, where RLS and has_role() decide what's permitted.
2 Directory map
Summit Edits Pro/
├── src/
│   ├── server.ts                  Cloudflare Worker entry — SSR error normalizer + branded 500
│   ├── start.ts                   createStart(): request error mw + attachSupabaseAuth fn mw
│   ├── router.tsx                 TanStack Router instance (QueryClient wiring)
│   ├── routeTree.gen.ts           GENERATED route tree — never hand-edit
│   ├── styles.css                 Tailwind v4 entry, OKLCH theme tokens, --neon brand color
│   │
│   ├── routes/                    File-based routes (pages + server route handlers)
│   │   ├── __root.tsx             Root layout / document shell
│   │   ├── _authenticated.tsx     Auth-gated layout: redirects to /login when no user
│   │   ├── _authenticated/        Role-split gated pages (dashboard.* / editor.* / admin.*)
│   │   ├── api/public/*           Server routes: contact, payments/webhook, hooks/editor-due-soon
│   │   ├── lovable/email/*        Lovable email infra routes (auth webhook, queue drain, suppression)
│   │   ├── email/unsubscribe.ts   One-click unsubscribe handler
│   │   ├── sitemap[.]xml.ts       Sitemap
│   │   └── (flat public pages)    index, pricing, faq, how-it-works, submit, buy-credits,
│   │                              checkout.return, login, signup, forgot/reset-password, contact
│   │
│   ├── integrations/
│   │   ├── lovable/index.ts       [GENERATED] Lovable OAuth → sets Supabase session
│   │   └── supabase/
│   │       ├── client.ts          [GENERATED] Lazy Proxy browser client (anon key)
│   │       ├── client.server.ts   Server-side admin (service-role) client
│   │       ├── auth-middleware.ts  [GENERATED] requireSupabaseAuth (server fn guard, getClaims)
│   │       ├── auth-attacher.ts    [GENERATED] attachSupabaseAuth (client fn mw, Bearer)
│   │       └── types.ts           [GENERATED] Full Database type from schema
│   │
│   ├── lib/
│   │   ├── orders.functions.ts    Order lifecycle server fns (assign, status, revision, cancel…)
│   │   ├── inbox.functions.ts     Admin contact-inbox server fns
│   │   ├── chat.functions.ts      In-app chat/notes server fns
│   │   ├── due-soon-emails.functions.ts  Admin due-soon email tooling
│   │   ├── stripe.ts / stripe.server.ts  Stripe client + Workers-safe webhook verify
│   │   ├── gmail.server.ts        Inbound contact email integration
│   │   ├── error-capture.ts / error-page.ts  SSR error capture + branded HTML
│   │   ├── email/                 enqueue.server.ts (pgmq enqueue), send.ts
│   │   ├── email-templates/       React Email templates + registry.ts
│   │   └── utils.ts               cn() helper
│   │
│   ├── utils/payments.functions.ts  Stripe checkout-session server fns
│   ├── hooks/                       use-auth (client user), use-mobile, use-video-observer
│   ├── components/                  App components + components/ui (shadcn New York)
│   └── assets/                      Static imports
│
├── supabase/
│   ├── config.toml
│   └── migrations/                ~39 SQL migrations (schema, RLS, RPCs, email infra,
│                                  package_prices, editor-availability RPC, invite gate)
│
├── public/                        Marketing video/asset payload (banner, ai-samples, samples)
├── .lovable/                      project.json, plan.md (editor-workflow scope decisions)
├── vite.config.ts                 Wraps @lovable.dev/vite-tanstack-config (do NOT add plugins)
├── wrangler.jsonc                 Cloudflare Workers config (main: src/server.ts, nodejs_compat)
├── bun.lock / bunfig.toml         Bun is the package manager of record
└── package-lock.json              Leftover Lovable artifact — ignore

Generated files (routeTree.gen.ts, the integrations/** Lovable/Supabase files, types.ts) carry a "do not modify" header. Regenerate rather than hand-edit.

3 Routing model

File-based routing in src/routes/ via @tanstack/router-plugin. Three shapes of route.

Public marketing / auth pages flat in src/routes

index, pricing, faq, how-it-works, submit, buy-credits, checkout.return, login, signup, forgot-password, reset-password, contact, ai-examples, unsubscribe. No auth required.

_authenticated.tsx — the auth gate

A layout route that reads useAuth(); if there is no user after loading, it navigate({ to: "/login", search: { redirect } }). Everything under src/routes/_authenticated/ sits behind it. This is a client-side UX guard — real authorization is enforced server-side by RLS and the requireSupabaseAuth server-fn middleware.

Gated pages are role-prefixed (roles live in the user_roles table, not on the route):

PrefixRolePages
dashboard.*customerindex (order list), orders.$id (detail), orders.$id.delivery, profile
editor.*editorindex (queue), orders.$id (workspace), availability (active toggle + max concurrent)
admin.*adminindex, editors (workload + assign UI), inbox (contact), orders.$id (detail), due-soon-emails

Server routes .ts handlers, not React pages

PathPurpose
api/public/contact.tsPublic contact-form submission
api/public/payments/webhook.tsStripe webhook (signature-verified, ?env=sandbox|live)
api/public/hooks/editor-due-soon.tspg_cron-driven editor "due soon" alerts (Bearer CRON_HOOK_SECRET)
lovable/email/auth/webhook.tsSupabase Auth email webhook → enqueue auth emails
lovable/email/auth/preview.tsAuth template preview (dev)
lovable/email/queue/process.tsQueue drainer — pgmq read → send → log/DLQ (Bearer service-role)
lovable/email/suppression.tsBounce/complaint webhook → suppression list
lovable/email/transactional/send.ts · preview.tsTransactional send + preview
email/unsubscribe.tsOne-click unsubscribe (token)
sitemap[.]xml.tsSitemap

Auth middleware (two halves)

  • Client sideattachSupabaseAuth (auth-attacher.ts), a global functionMiddleware in start.ts: reads the browser session and attaches Authorization: Bearer <access_token> to every server-function call.
  • Server siderequireSupabaseAuth (auth-middleware.ts): validates the Bearer token via supabase.auth.getClaims(token), derives userId = claims.sub, and yields a request-scoped Supabase client (bound to the user's JWT) into the server-fn context — so RLS applies to everything that client does.
4 Data model

Postgres via Supabase. ~39 migrations in supabase/migrations/. Authoritative generated schema: src/integrations/supabase/types.ts.

Enums

  • app_role: admin | customer | editor  — the DB value is customer even though UX copy says "client"; editor was added after the initial migration.
  • payment_status: unpaid | paid | refunded.
  • order_status — the lifecycle:
awaiting_footage accepted uploading_footage in_progress ready_for_delivery delivered· cancelled

Core tables

TableHolds
profilesUser profile + credit_balance, editor capacity (is_editor_active, max_concurrent_orders default 5, editor_specialty, editor_bio)
user_rolesRoles as rows (not a profile column); checked via has_role(uid, role)
ordersThe job. user_id, package_name/package_slug, amount_cents/currency (default CAD), footage_link, special_instructions, status, payment_status, stripe_session_id/stripe_payment_intent, assigned_to, due_at, delivered_at, credits_spent, clip_count/overage_credits, revision_count, due_soon_alerted_at, admin_notes
order_deliveries, order_delivery_downloadsDelivered files + download tracking
order_revision_attachmentsFiles attached to revision requests
order_status_historyAudit trail of every status change
order_internal_notesStaff-only internal notes
notificationsIn-app notifications
credit_transactionsLedger of credit deltas (purchases, edit spends, refunds)
contact_submissionsPublic contact form
package_prices, addon_pricesServer-side authoritative price tables (slug → credits)
invite_codesInvite-gate codes (no client read access; validated server-side)
email_send_log, email_send_state, email_unsubscribe_tokens, suppressed_emailsEmail infra: idempotency, rate-limit cooldown, unsubscribe, suppression

Where authorization lives

  • RLS everywhere. Customers select only their own orders and may update them only while awaiting_footage; a BEFORE UPDATE trigger (orders_restrict_customer_updates) hard-limits non-admins to changing only footage_link / special_instructions. Admins have full access; editors are scoped to assigned_to = auth.uid().
  • has_role(uid, role)SECURITY DEFINER function over user_roles; the single source of truth for role checks across policies and RPCs.
  • prevent_privileged_profile_updates trigger blocks editors/customers from changing credit_balance, is_editor_active, max_concurrent_orders directly; the editor-availability RPC bypasses it via a transaction-scoped GUC signal.

Key SECURITY DEFINER RPCs

RPCWhat it does
submit_edit_order(...)Customer creates an order. Costs it from the server-side package_prices/addon_prices tables (validates slug, raises on unknown), applies per-clip overage, debits credit_balance under FOR UPDATE, sets due_at (24h rush / 48h standard), inserts the order + a credit_transactions row.
assign_order(_order_id, _editor_id)Admin-only. Validates target is an active editor, soft-warns on over-capacity (still assigns), updates orders.assigned_to, inserts in-app notification for the new editor (and removal notice for the previous one). Returns JSON incl. over_capacity; the editor-assignment email is enqueued from the server fn. No auto-assignment.
set_editor_availability(_is_active, _max_concurrent)Editor-only. Updates own availability fields. Sets a transaction-scoped GUC (app.bypass_profile_guard='editor_availability') so the profile-guard trigger permits exactly these two fields, then resets it.
request_revision(_order_id, _note, _attachments)Customer requests a revision on a delivered order; records note + attachments and moves the order back into the revision flow.
set_revision_stage(_order_id, _stage)Moves an order's status backward/forward through revision sub-stages.
cancel_order(...)Cancels an order (status → cancelled) with refund/credit handling.
editor_workload_summary()Per-editor active/overdue/awaiting/in-progress/delivered-30d counts + capacity (staff dashboard).
validate_invite_code(_code)Called inside handle_new_user; locks + decrements an invite_codes row, raising if missing/invalid/expired/exhausted. Globally disable via app.invite_gate='off'.
handle_new_user()AFTER INSERT trigger on auth.users: enforces the invite gate, creates the profiles row, grants default customer role. Fires for all signup paths.
enqueue_email, read_email_batch, move_to_dlq, delete_emailpgmq wrappers for the email pipeline.
5 Data-flow diagrams

(a) Auth / session flow

Email / passwordnative SupabasesignInWithPassword()
OAuthLovable Cloudlovable.auth.signInWithOAuth → setSession()
Sessionbrowser holds Supabase JWTuse-auth.tsx → { user, loading }
Server fn callattachSupabaseAuthAuthorization: Bearer <jwt>
VerifyrequireSupabaseAuthgetClaims → userId = sub → RLS
Signup: signup.tsx → supabase.auth.signUp({ ..., data: { invite_code } })
        → handle_new_user() trigger: validate_invite_code() (gate)
        → create profiles row + grant 'customer' role

Gate (client): _authenticated.tsx → no user? → navigate /login

(b) Order lifecycle

awaiting_footage accepted uploading_footage in_progress ready_for_delivery delivered ⟲ revision cancelled
Customer (submit.tsx) ── submit_edit_order(slug, addons, footage…)
   → costs from price tables, debits credits, sets due_at
      │
      ▼  awaiting_footage   (customer may still edit footage_link / instructions)
      │  admin: assign_order(order, editor) → notification + editor-assignment email
      ▼  accepted → uploading_footage → in_progress   (editor works the order)
      ▼  ready_for_delivery
      │  editor uploads files → notifyDeliveryUploaded (delivery-file-added email)
      ▼  delivered
      │  customer: request_revision(note, attachments)
      ▼  set_revision_stage moves status back → in_progress  (revision loop)

Any state ──▶ cancelled   (cancel_order; refund/credit handling)
Every status change → order_status_history (audit) + order-status-update email

(c) Email queue pipeline Supabase-native

Producers
─────────
• Server fns (order events) ── enqueueTemplateEmail() ─┐  lib/email/enqueue.server.ts
• editor-due-soon hook ───────────────────────────────┤   - idempotency: skip if message_id
• Supabase Auth webhook (auth emails) ────────────────┘     already in email_send_log
• suppression check: skip + log if recipient suppressed
      │  rpc enqueue_email(queue, payload{ html, subject, ... })
      ▼
┌──────────────────────────────────────────────┐
│  pgmq queues                                 │
│   • auth_emails           (TTL ~15 min)      │
│   • transactional_emails  (TTL ~60 min)      │
└───────────────────┬──────────────────────────┘
                    │  pg_cron periodically POSTs (Bearer = service-role key)
                    ▼
      /lovable/email/queue/process  (queue drainer)
        1. check email_send_state.retry_after_until (rate-limit cooldown)
        2. read_email_batch (auth first, then transactional; vt=30)
        3. per message:
             • drop if past TTL → move_to_dlq
             • skip if already 'sent' (VT-race guard) → delete_email
             • sendLovableEmail()  (@lovable.dev/email-js)
                 ├─ success → log 'sent' + delete_email
                 ├─ 429     → log 'failed', set retry_after_until, STOP
                 ├─ 403     → move_to_dlq, STOP
                 └─ other   → log 'failed' (retries until MAX_RETRIES → DLQ)
                    ▼
              Provider sends → bounce/complaint webhook
                    ▼
      /lovable/email/suppression → suppressed_emails (future sends skipped)

React Email templates live in src/lib/email-templates/*.tsx, registered in registry.ts (TEMPLATES[name] = { component, subject }) and rendered to static HTML at enqueue time.

(d) Stripe credit-purchase flow

buy-credits.tsx
   │  payments.functions.ts → create Stripe Checkout Session
   ▼     (metadata.userId, price lookup_key e.g. credits_500_usd)
StripeEmbeddedCheckout.tsx  (embedded UI, gateway via connector-gateway.lovable.dev)
   │  customer pays
   ▼
Stripe ── checkout.session.completed ──▶ /api/public/payments/webhook?env=…
                                            │
                                            ▼
                     verifyWebhook()  (lib/stripe.server.ts)
                     • parse stripe-signature (t=, v1=)
                     • reject if timestamp age > 300s
                     • HMAC-SHA256 over `${t}.${body}` via WebCrypto
                       (Workers-safe; no Stripe SDK constructEvent)
                                            │
                                            ▼
                     handleCheckoutCompleted (service-role client)
                     • idempotency: skip if session already in credit_transactions
                     • re-fetch session, expand line_items.price
                     • map lookup_key → credits (CREDITS_BY_PRICE)
                     • INSERT credit_transactions(delta = credits)
                     • UPDATE profiles.credit_balance += credits
                     ✗ does NOT create an orders row (credit buys ≠ jobs)
                                            │
                                            ▼
checkout.return.tsx  shows confirmation; credits now spendable via submit_edit_order
6 Integrations
IntegrationRoleKey files
SupabasePostgres DB, Auth (JWT), RLS, RPCs, pgmq/pg_cron email enginesrc/integrations/supabase/*; supabase/migrations/**
Lovable Cloudcloud-auth-jsOAuth only (email/password is native Supabase)src/integrations/lovable/index.ts
Lovable Cloudemail-jsTransactional send + auth-payload parsesrc/routes/lovable/email/queue/process.ts, src/routes/lovable/email/auth/webhook.ts
Lovable Cloudwebhooks-jsWebhook signature verificationsrc/routes/lovable/email/suppression.ts, src/routes/lovable/email/auth/webhook.ts
Lovable Cloudvite-tanstack-configVite plugin chainvite.config.ts
StripeCredit payments, embedded checkout, Workers-safe webhook verifysrc/utils/payments.functions.ts, src/lib/stripe.ts, src/lib/stripe.server.ts, src/components/StripeEmbeddedCheckout.tsx, src/routes/api/public/payments/webhook.ts
EmailTransactional + auth email via pgmq/pg_cron + React Emailsrc/lib/email/enqueue.server.ts, send.ts, src/lib/email-templates/*, src/routes/lovable/email/**, src/routes/email/unsubscribe.ts
Gmail (inbound)Contact-email integrationsrc/lib/gmail.server.ts

Email/password auth is native Supabase (signInWithPassword/signUp). Only OAuth goes through Lovable. Email transport is Supabase-native (pgmq); Lovable only supplies the final sendLovableEmail transport call. Exactly 4 packages couple the app to Lovable, across the files above.

7 Deployment & environment

Platform

  • Cloudflare Workers via Wranglerwrangler deploy.
  • wrangler.jsonc: main: src/server.ts, compatibility_flags: ["nodejs_compat"], compatibility_date: 2025-09-24, app name tanstack-start-app.
  • Deploys also flow through Lovable Cloud, which provisions Supabase/auth/email env.

Build / tooling

  • Bun is the package manager of record (bun.lock + bunfig.toml). package-lock.json is a leftover Lovable artifact. The bunfig supply-chain guard skips packages published < 24h ago.
  • vite.config.ts wraps @lovable.dev/vite-tanstack-config, which already injects tanstackStart, viteReact, tailwindcss, tsConfigPaths, the Cloudflare plugin, VITE_* env, the @ alias, dedupe, and error-logger plugins. Do not add those plugins manually. The entities alias workaround is load-bearing for react-markdown.
  • No standalone typecheck script; tsc runs with noEmit.

Commands

bun install        # respects 24h supply-chain guard
bun run dev        # Vite dev server (TanStack Start)
bun run build      # production build (build:dev for development mode)
bun run preview    # preview a production build
bun run lint       # eslint .
bun run format     # prettier --write .
wrangler deploy    # deploy to Cloudflare Workers

Environment variables names only — never values

VarUsed by
SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, SUPABASE_PROJECT_IDServer Supabase clients / auth middleware
VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY, VITE_SUPABASE_PROJECT_IDBrowser Supabase client
SUPABASE_SERVICE_ROLE_KEYServer-side admin client (webhooks, queue drainer, cron hook)
VITE_PAYMENTS_CLIENT_TOKENStripe embedded checkout (client)
STRIPE_SANDBOX_API_KEY, STRIPE_LIVE_API_KEYStripe server client (per env)
PAYMENTS_SANDBOX_WEBHOOK_SECRET, PAYMENTS_LIVE_WEBHOOK_SECRETStripe webhook signature verification
LOVABLE_API_KEY, LOVABLE_SEND_URLLovable gateway (Stripe proxy) + email send
CRON_HOOK_SECRETBearer auth for the editor-due-soon pg_cron hook (also in Supabase Vault)

The Supabase client throws "Connect Supabase in Lovable Cloud" if VITE_SUPABASE_* are missing — env is Lovable-provisioned.

Appendix · Gotchas
  • Lovable coupling runs deep but narrow — only 4 app files import @lovable.dev/*, yet auth, email transport, env injection, and the Vite plugin all flow through them. The authorization model (has_role(), ~36 RLS migrations, pgmq/pg_cron) is welded to a Supabase-issued JWT driving auth.uid() — shedding Lovable packages is feasible; leaving Supabase is a security-model rewrite.
  • Dual lockfiles — use Bun, ignore package-lock.json.
  • DB role naming — product says "client," the enum says customer. Roles are rows in user_roles, never a profile column; always check via has_role().
  • Assignment is admin-only — no auto-assignment exists. Dropping the admin role without first adding auto-assign/self-claim leaves orders unassigned with no due date.
  • SSR error handling is customsrc/server.ts exists to catch h3's swallowed {"unhandled":true} 500s; preserve normalizeCatastrophicSsrResponse.
  • Generated files (routeTree.gen.ts, Lovable/Supabase integration files, types.ts) won't survive regeneration — don't hand-edit.