Back to portfolio
Consumer AI · PRJ-001

Movie Wheel

A gamified decision engine that ends streaming paralysis — and learns your taste while it does it.

Visit live site
60+
API route handlers
38
SQL migrations
3-in-1
Web, iOS & Android — one codebase
The Problem

Movie Wheel attacks “streaming paralysis”: the modern bind of paying for five or more streaming subscriptions yet spending 15+ minutes scrolling across apps without ever committing to anything. The decision cost of choosing has quietly grown larger than the cost of the content itself.

The target user is the “post-dinner decider” — someone who sits down with a partner, friends, or alone, has plenty of options across Netflix, Max, Disney+, and the rest, and simply freezes. The problem is not a lack of content; it is the absence of a fast, confident way to land on one title that fits the moment — the occasion, energy, length, and platforms on hand — and the viewer’s actual taste.

So the North Star is deliberately not time-in-app. It is “decisions completed”: a user lands on a title and acts on it. Every system in the product is built to drive a confident decision quickly, then quietly learn taste so the next one is even faster.

The Architecture

Movie Wheel is a single Next.js 16 application — SSR pages, ~60 API route handlers, and the client UI in one codebase, wrapped by Capacitor to ship natively to iOS and Android. On that foundation sit a gamified spin engine, a dual recommendation system, an agentic assistant, and a Supabase/Postgres data layer engineered for evolving AI data and strict per-user isolation.

01

The Spin Wheel engine

The core mechanic is a four-reel slot machine. Users can hold or lock individual reels across spins (for example, “always Netflix” + “always recent”) and switch between a Filters mode (Platform, Format, Genre, Era) and a Vibe mode (occasion, length, energy). A tap — or a physical phone shake via a native CoreMotion listener — triggers a spin. The engine pulls TMDB candidates, ranks them by a composite quality score, and picks from the top so results feel relevant but a little serendipitous, with content “pools” (Hot Now, New, Popular, Niche, Cult Classic) and acclaim-based “style chips.”

02

Taste-capture surfaces

Signal is gathered everywhere, not just from spins. A Tinder-style Swipe Discovery deck collects rapid like / pass / super-like signals; a Games Hub of eight short games (Star Grid, Hot or Not, Actor Battle, This or That, and more) feeds the taste profile; and a seven-level sentiment system per title (goated → favorite → loved → liked → okay → disliked → blocked) drives ranking, with “blocked” feeding a “Poison Well” of titles to never show again.

03

Dual recommendation system

Two models run together. A lightweight three-axis “Triple-Helix” DNA profile handles fast, cheap, cold-start-friendly matching; a more advanced ~26-dimension “Genome” engine scores titles with an LLM and clusters each user’s taste with k-means centroids. Both are backed by pgvector similarity search, so recommendations are grounded in real, vectorized title data.

04

Felix — the agentic assistant

Felix is a Pro-tier conversational agent, not a scripted chatbot. It streams responses over SSE and is required to call a tool before every reply, so the app always renders clickable, real results instead of hallucinated titles. Its tools search and discover content, surface details, read the user’s taste profile, save sentiments and watchlist items, block content, follow actors, and remember durable facts across conversations. A companion “Sommelier / Vibe card” generates short, personalized explanations of why a title matches.

05

Data, identity & state

Supabase (PostgreSQL) with row-level security isolates user-owned data while content tables stay public-read. JSONB columns with GIN indexes store flexible, evolving AI data (fingerprints, genomes, game state, chat tool data), and pgvector columns power DNA / genome similarity — across ~38 timestamped migrations. Clerk handles auth, decoupled from the database, with a one-time routine that migrated legacy accounts to Clerk IDs. TanStack Query manages server state; localStorage and Dexie / IndexedDB handle lightweight and offline client state.

06

AI cost engineering & platform

A single provider-agnostic LLM layer targets OpenAI, Anthropic Claude, or Google Gemini by environment variable, with per-job tuning — low temperature for deterministic enrichment, high for varied prose. Expensive work is moved off the request path: enrichment and fingerprinting run as batched Vercel Cron jobs on a cheap model, taste cards cache ~7 days, Sommelier notes 24h–30d, and Felix streams for perceived speed. A four-tier paywall (anonymous / free / trial / pro) is enforced server- and client-side, billed via Stripe on web and RevenueCat on iOS, with Upstash Redis rate limiting on sensitive endpoints.

Key Decisions & Tradeoffs

The reasoning behind the build — and what each choice cost.

One Next.js codebase wrapped by Capacitor, instead of separate native apps

Why

SSR pages, ~60 API routes, and the entire client UI live in a single codebase, then ship to web, iOS, and Android through Capacitor — no parallel mobile codebase to maintain.

Tradeoff

Native-only capabilities have to be bridged deliberately (CoreMotion shake-to-spin, haptics, push), and the app runs in a web view rather than a fully native UI.

Two recommendation models — Triple-Helix and a 26-dimension Genome — running together

Why

The lightweight three-axis DNA profile is cheap and cold-start-friendly; the LLM-scored, k-means-clustered Genome engine adds depth once a user has signal. Together they serve both the brand-new user and the power user.

Tradeoff

Maintaining and reconciling two scoring systems is more complex than one, and the Genome path carries LLM scoring cost that has to be controlled.

Felix must call a tool before every reply

Why

Forcing a tool call guarantees every answer is grounded in real, clickable catalog results and can take real actions — save, block, follow — eliminating hallucinated titles, the cardinal sin of an LLM recommender.

Tradeoff

Every conversational turn pays a tool round-trip in latency and orchestration; mitigated by SSE streaming for perceived speed.

Provider-agnostic LLM layer with cost moved off the request path

Why

Abstracting OpenAI, Claude, and Gemini behind one interface avoids vendor lock-in and lets each job pick the right model and temperature; batching enrichment into cron jobs and caching generated prose makes per-interaction cost predictable.

Tradeoff

The abstraction adds engineering overhead and trends toward the lowest common denominator of provider features, and aggressive caching means some generated content can briefly be stale.

Supabase Postgres with RLS, JSONB + GIN, and pgvector — one database for everything

Why

Relational integrity, flexible JSONB for fast-evolving AI structures, and vector similarity search all live in one managed Postgres, with row-level security enforcing per-user isolation by default.

Tradeoff

RLS policies and JSONB schemas demand discipline as the model evolves across ~38 migrations, and mixing relational, document, and vector access patterns in one store requires careful indexing.

Clerk for identity, migrated off Supabase Auth

Why

Decoupling identity and billing from the application database made auth and subscription gating cleaner and portable across web and native.

Tradeoff

User IDs became TEXT (Clerk IDs) rather than UUIDs throughout the schema, and a one-time migration routine was needed to remap existing accounts on first login.

Outcome & Performance

Movie Wheel is pre-launch — the Capacitor shell is being prepared for App Store submission — so there is no live user count to cite. What exists is a fully built, instrumented product engineered to prove its own thesis the moment it goes live.

Success is defined as “decisions completed,” not engagement. The supporting metrics are already wired in: activation (a first decision in session one), time-to-decision against the ~15-minute “scroll and give up” baseline, spins per session, and D1 / D7 / D30 retention — captured through a user-activity log, persisted game sessions, the subscription lifecycle, and the durable user memory Felix builds.

The concrete outcomes today are engineering ones. AI cost is bounded by design — batched cron enrichment on a cheap model, multi-day caching of generated prose, and streamed responses convert per-request LLM spend into mostly one-time-per-title generation. Reliability comes from Upstash rate limiting and short-timeout fallbacks on third-party calls. And the sheer scope shipped solo — ~60 API routes, ~38 migrations, a four-reel ranking engine, a dual recommendation system, eight games, an agentic assistant with persistent memory, multi-provider LLM support, a four-tier paywall, and a native iOS / Android wrapper — is itself the proof of capability.

Capabilities
Next.js 16 · React 19TypeScriptSupabase · Postgres · pgvectorClerk authCapacitor (iOS / Android)TanStack QueryOpenAI · Claude · GeminiTMDB · WatchmodeStripe · RevenueCatUpstash RedisVercel CronFramer Motion