Architecture

How the app is wired together — the stack, the file layout, and how the frontend talks to the backend.

Tech stack

LayerChoiceWhy
FrameworkTanStack Start v1 + React 19File-based routing with SSR-capable server functions on a Cloudflare Worker runtime.
BundlerVite 7 + @cloudflare/vite-pluginFast dev, edge-deployable production build.
StylingTailwind CSS v4 (native @import in src/styles.css)Design tokens defined as oklch CSS variables. Dark-first 'Liquid Glass' theme.
UI primitivesshadcn/ui (Radix-based) + lucide-reactAccessible building blocks; only what we use is in src/components/ui.
Data fetching@tanstack/react-queryCached queries, mutations, and invalidation.
BackendLovable Cloud (Supabase)Postgres, RLS, Auth, Realtime — accessed via @supabase/supabase-js.
Formsreact-hook-form + zod via @hookform/resolversUsed in onboarding, signup, login.
ScrapingFirecrawl (firecrawl-py / HTTP API)Pulls AMC showtimes for Metreon 16 and Kabuki 8.

Folder structure

src/
├── routes/                    # File-based routing (TanStack)
│   ├── __root.tsx             # HTML shell, providers, global meta
│   ├── index.tsx              # Landing → redirects into app
│   ├── login.tsx              # Email/password + Google sign in
│   ├── signup.tsx
│   ├── invite.tsx             # Invite-code gate
│   ├── onboarding.tsx         # First-run profile + theater prefs
│   ├── now-playing.tsx        # Voting + featured pick
│   ├── upcoming.tsx           # Future showtimes by movie
│   ├── movie.$id.tsx          # Movie detail + AMC RSVP
│   ├── profile.tsx            # Member card, stats, watch log
│   ├── docs.tsx               # Docs layout (this section)
│   ├── docs.*.tsx             # Docs pages
│   └── api/public/hooks/
│       └── sync-now-playing.ts  # AMC scrape endpoint (server route)
├── components/                # Reusable UI
│   ├── ui/                    # shadcn primitives
│   ├── AppShell.tsx           # Auth gating + tab bar wrapper
│   ├── TopBar.tsx, TabBar.tsx
│   ├── MovieCard.tsx, ShowtimeCard.tsx, AmcShowtimes.tsx
│   ├── VoteButton.tsx, Avatar.tsx
│   └── DocsPage.tsx
├── lib/
│   ├── queries.ts             # All React Query hooks + types
│   ├── me.ts                  # Current user resolver
│   ├── invite-gate.ts         # Invite-code validation
│   ├── theaters.ts            # Theater catalog
│   ├── format.ts, utils.ts, error-capture.ts, error-page.ts
├── integrations/supabase/     # AUTO-GENERATED — never edit
│   ├── client.ts
│   └── types.ts
├── styles.css                 # Tailwind v4 + design tokens
└── router.tsx                 # Router config

supabase/
├── config.toml                # Project-level config
└── migrations/                # SQL migrations

Frontend ↔ Backend

The app is a single-page client that talks directly to Supabase via the auto-generated @/integrations/supabase/client. There is no custom API layer — RLS policies are the security boundary.

  • Reads: React Query hooks in src/lib/queries.ts wrap supabase.from(...).select(...).
  • Writes: Mutations call supabase.from(...).upsert/insert/delete and invalidate the matching query keys.
  • Realtime: useClubRealtime() subscribes to all 6 public tables and invalidates queries on any change.
  • Auth: supabase.auth handles email/password + Google OAuth. Session is read by useMeId().
  • Server route: /api/public/hooks/sync-now-playing runs in the Worker runtime, scrapes AMC via Firecrawl, and upserts movies + showtimes.

Database schema

TablePurposeKey columns
club_membersProfile per signed-in userid, user_id (auth.uid), name, avatar_url, preferred_theaters[], invited, onboarded
moviesCurrently or recently playing filmsid, title, poster_url, genre, runtime_min, slug, currently_playing, added_at
amc_showtimesScraped AMC screeningsid, movie_id, theater, starts_at, format, ticket_url
showtimesMember-proposed (legacy / manual) plansid, movie_id, theater, starts_at, proposed_by
attendeesRSVPs to a showtimeshowtime_id, member_id, seat
votesHeart vote per member per moviemember_id, movie_id
watch_logsLogged watchesid, member_id, movie_id, rating, notes, watched_at, co_attendees[]

All tables have RLS enabled. movies, amc_showtimes, and showtimes are readable by any authenticated user. Member-owned rows (votes, attendees, watch logs) require auth.uid() = user_id on writes.