Skip to content
Available for work
Adam
←  All projects

Case study · 2026

Tonight

Six taps to something you'll actually watch, on the services you already have.

Visit site (opens in a new tab)
Screenshot of the Tonight interface
Status
Live
Year
2026
Stack
Next.js · TypeScript · Python · FastAPI · SQLite · NumPy · SciPy · Tailwind CSS · GitHub Actions · Vercel
Tests
139 tests (38 unit · 68 backend · 33 e2e)
Data
TMDB with JustWatch availability, synced daily into a read-only SQLite snapshot

Overview

Tonight answers "what should we watch?" without the endless scroll. It only suggests films and series on the US streaming services you already pay for. You set a mood and how much time you have, then choose between four posters for five rounds. Every tap teaches it your taste, the candidate pool visibly narrows, and you finish on a shortlist where each pick says why it is there.

The idea it is built on: people are bad at describing their taste and very good at choosing between things.

The hard part

Learning a taste from a handful of taps, fast, with no account and no server round trip. An offline Python pipeline turns every title into weighted terms (keywords, genres, director or creator), runs TF-IDF and a sparse truncated SVD down to 48 dimensions, then quantises the vectors to int8 so the whole pool ships to the browser once.

From there the engine runs entirely client side. Each tap reads as "this one over those three" and moves the taste vector toward the pick, the pool keeps its best 38% each round, and the final shortlist uses maximal marginal relevance so you never get five versions of the same film.

Highlights

  • Fair rounds. The four posters in each round are chosen by farthest-point sampling from titles of similar popularity, so recognition does not decide the tap.
  • No upstream calls on a page view. A daily GitHub Action syncs TMDB and availability data into SQLite, compacts it, and redeploys from a rolling branch, keeping a 50 MB database out of git history.
  • Read-only SQLite on serverless. A write-ahead-log database cannot open read-only, so the pipeline converts the snapshot before shipping and the service skips WAL when it opens read-only.
  • A sync built to be re-run. Request budgets, a lock with a timeout, resumable runs, and year-sharded queries that stay inside the API's page limits.
  • 139 tests, including 33 end to end across the full pick flow.