MOVIENIGHT

A new way to watch together.

The one stop shop for everything you need for a successful Movie Night. Personal AI recommendations, wishlists, badges, and the pick engine. The most fun way to decide what to watch together.

SvelteKit 2TypeScriptPostgreSQLDrizzle ORMWebSocketsTailwindMotionDocker

Director’s Note

Once Upon a Friday Night

You know the scene. Snacks are out, the couch is claimed, the TV’s on — and somehow everyone in the room has actually agreed to watch a movie together. Small miracle. Then you open your streaming app of choice and get hit with an endless scroll of weirdly specific, occasionally unhinged categories. The one movie someone actually wants is on a service nobody’s logged into. The compromise pick costs four bucks to rent. The safe pick got watched two weeks ago. And every suggestion thrown out is one wrong word away from quietly turning the night into a referendum on everyone’s taste.

“The fact that some choice is good doesn’t necessarily mean that more choice is better.”

— Barry Schwartz, The Paradox of Choice

Enter Movie Night. What started as a personal passion project to solve the "what should we watch problem" that has evolved into an all-encompassing movie hub. Details, trailers, actor pages, watchlist, fun customizable badges, reviews, AI recommendations, and every other thing you could hope for in a movie app.

But the real heart of it is the pick engine. Everyone in the room nominates, everyone marks ready, and the result lands at the same instant over a single live connection. Endless ways to decide — a random draw, a bracket battle, a ranked vote, or a two-player veto duel — so the room can match the mode to the night. The whole idea is to take the part of the evening that used to suck and start arguments and turn it into the most fun minute on the couch. Press play. Eat the snacks before they get cold.

Goals

What problem does this solve?

01

End the "what should we watch" stall. Everyone nominates, the room decides, and nobody has to argue.

02

Pull every movie from a single source of truth. Where to watch, how to watch, without ever leaving the site.

03

Make the picking fun, realtime updates and cursor ghosts make deciding a game, not a chore.

04

Personalized AI recommendation that actually matters. Real sentiment analysis. Vector storage and tokenization.

Live rooms

Anatomy of a pick event

One WebSocket per event. Every member sees the same state at the same instant.

  1. 01

    Create the event

    The host picks an engine, sets the nominations-per-user cap, and names the room. The event row goes into Postgres and a join link gets shared.

  2. 02

    Members join

    Visiting the link auto-joins the user (idempotent). Each member gets a color and shows up in the lobby live.

  3. 03

    Nominate

    Members search and nominate movies. The client sends only an identifier — the server re-resolves every display field so the room cannot be poisoned.

  4. 04

    Mark ready

    Everyone flips their ready state. The room watches every state change tick in over a single WebSocket per event.

  5. 05

    Run the engine

    Once the room is ready the chosen engine takes over: a random draw, a bracket battle, a ranked vote, or a rolling-random two-player run.

  6. 06

    Reveal

    The result lands as a broadcast. Everyone sees the winning movie at the same instant, with poster, runtime, and a "play locally" link when the file is available.

Pick your poison

Challengers

Each engine lives behind a shared interface so the only limitation is our own creativity.

🎲

Random Pick

One draw, no drama.

Everyone nominates a set number of movies, the engine picks one at random. Fast, fair, no negotiation.

⚔️

Battle

Single-elimination bracket.

Movies get paired off and the room votes round by round. Random seeding keeps the bracket interesting. Winner is the last one standing.

📊

Ranked

Weighted positional vote.

Everyone orders the whole pool from most to least wanted. Top picks count more, and the highest weighted total wins.

🪙

Rolling Random

Two-player veto duel.

A random pick is drawn each round and removed from the pool. The picker loses a slot, the other gains one. The event stays open until someone locks in a final winner.

Casting agent

(AI) Companion

A ReAct agent that chains catalog, local library, and viewing-history tool calls to pick one movie with a reason.

Example prompt

“A visually stunning movie I probably haven’t seen, around two hours, that I can watch with my partner.”

01

Type how you feel

"Something like Arrival but more playful." "A ninety minute comedy for a tired Tuesday." The prompt is freeform.

02

ReAct agent kicks off

The recommender service spins up an agent that can call catalog search, library lookups, and viewing-history lookups as tools.

03

Chain of tool calls

The agent reasons, calls a tool, reads the result, reasons again. A run can legitimately take several minutes.

04

One movie, one reason

The agent returns a single pick with a written reason. The web app resolves it through the catalog and renders the full card.

Catalog

One source of truth

Every poster, runtime, and credit comes from the same place. The client never sends descriptive fields — it sends an id.

01

Single id in

Everything is keyed on one canonical identifier. Lists, picks, library cross-references, deep dives — all of it.

02

Resolve to canonical

A single resolver builds the canonical shape: title, year, poster, overview, genres, cast, crew, runtime, audience rating.

03

Stale-while-revalidate cache

Cached for a day with a seven-day stale window. The cache hands back the last-known-good response immediately and refreshes in the background.

04

Local media overlay

If the movie is in the local library, the resolver tags the record with a local id so the post-decision UI can offer a play link.

Long form

Deep dives

The recommender can also write long-form essays on a single film: history, themes, key people, sources. Each essay has a research hint (low / medium / high) that controls how much background the agent gathers.

  • Sourced quotes and citations attached to each section
  • Key people surface director / writer / cinematographer cards inline
  • Refresh priority lets the agent re-research when stale

Personal taste

Diary sync

Each user can import their viewing history from a diary export or sync against a public username. The diary feeds the recommender so suggestions account for what you’ve already watched and how you rated it.

  • Zip import or background sync per user
  • Diary entries are matched against the catalog on ingest
  • Rewatches, ratings, and tags all flow into the recommender

Production stills

Now Showing

Scroll to part the curtains.

  • Landing — Neon marquee, starfield, admit-one ticket

    Reel · Landing

    Neon marquee, starfield, admit-one ticket

  • Browse — Catalog grid — posters, watch status, filters

    Reel · Browse

    Catalog grid — posters, watch status, filters

  • Movie details — Cast, crew, runtime, audience rating

    Reel · Movie details

    Cast, crew, runtime, audience rating

  • More details — Reviews, reactions, related films

    Reel · More details

    Reviews, reactions, related films

  • Pick event — Nominations streaming in, ready flags flipping

    Reel · Pick event

    Nominations streaming in, ready flags flipping

  • AI shortlist — Multiple AI picks at a glance

    Reel · AI shortlist

    Multiple AI picks at a glance

  • AI companion — Freeform prompt — one movie, one reason

    Reel · AI companion

    Freeform prompt — one movie, one reason

  • Deep dives — The long-form essay library

    Reel · Deep dives

    The long-form essay library

  • Deep dive — Themes, history, key people

    Reel · Deep dive

    Themes, history, key people

  • Deep dive — Sources, citations, refresh priority

    Reel · Deep dive

    Sources, citations, refresh priority

Reel 01 / 00

Everything Everywhere All at Once

Real-time pick events that everyone sees at the same instant

🎲

A handful of decider engines for different kinds of nights

🎬

Full movie catalog: posters, cast, crew, collections, runtime

AI recommender that takes a freeform prompt and picks one movie

📖

Personal viewing-history import to keep recommendations on taste

📚

Long-form "deep dive" essays generated per movie

🎟️

Movie lists, watchlists, and a "wanted" request queue

Per-user stats, badges, and a personal diary

🗂️

Cross-references your local media library with the catalog

🐳

Docker — deploy anywhere from a Raspberry Pi to a cloud VM

♻️

Smart caching keeps the catalog fast and current

🔒

Admin-gated room management and metadata editing

Challenges & solutions

Challenge 01

Real-time rooms over a single WebSocket

Multiple users in a pick event need to see each other join, watch nominations land, see ready states flip, and watch a winning movie reveal at the same instant. Polling would be wasteful and would still feel laggy. Long-lived connections needed careful lifecycle handling for joins, leaves, reconnects, and the moment a result is decided.

Solution

One WebSocket connection per pick event. Server validates every C2S message, applies the state change against Postgres, and broadcasts the updated snapshot to everyone in the room. Engine-specific messages (battle rounds, ranked submissions, rolling random finalize) live alongside core room messages without leaking through the type system.

Challenge 02

Endless decider engines, one room

The room had to support more than one way to decide. A single random draw is fast but flat. A bracket battle is more fun but takes longer. Ranked voting reads everyone's preference order. Two-player rolling random changes the pool every round. Each mode has its own rules, its own messages, and its own UI state.

Solution

Each engine is its own module behind a shared interface. Engine-specific WebSocket messages live in separate type files so the core room never has to know what "rolling_random:finalize" means. Engine config is stored as opaque JSON on the event row and only read by the engine that owns it.

Challenge 03

Long-running AI agent over HTTP

The recommender service runs a ReAct agent that chains many tool calls — catalog searches, local library lookups, viewing-history scans. Single runs can legitimately take several minutes. Node's default fetch caps both headers and body at five minutes, which surfaces as 502s or 504s right when the agent is about to land an answer.

Solution

A dedicated undici Agent with a ten-minute headers and body timeout, mirrored on the Node HTTP server. The Recommender call is wrapped in a helper that surfaces useful cause information (ECONNREFUSED, DNS, AggregateError walking) instead of the generic "fetch failed" error you get from undici by default.

Challenge 04

Catalog hydration cost vs. freshness

Hitting the upstream catalog on every page render would burn quota fast and add latency for the user. But cached entries go stale: posters get updated, ratings drift, a movie gets a new tagline. The catalog needed to feel canonical without paying the upstream cost on every request.

Solution

Stale-while-revalidate caching keyed on a canonical id, with a one-day TTL and a seven-day stale window. The cache serves the last-known-good response immediately and revalidates in the background. Hot endpoints get wrapped in a cache helper; long-tail endpoints fall back to direct fetches with a shorter TTL.

Roll credits

End credits

The cast and crew that made Movie Night possible. Yes, even the divs.

A MOVIE NIGHT

PRODUCTION

Directed by

SvelteKit

Produced by

Postgres · Drizzle ORM

Original concept by

Friday, 8:45pm, nobody can pick a movie

Crew

Live broadcastSocket.IO
Director of photographyTailwind CSS
Visual effectsMotion (formerly Framer)
Score composed byOpenAI · a ReAct loop with feelings
Catalog researchTMDB
Local archivePlex API
Edited inNeovim, three monitors, one candle
Color gradingoklch() and a strong opinion
Stunt coordinatortry / catch / finally
ContinuityA single canonical movie id
CastingMath.random() and a seeded shuffle
Sound design~~crickets in the group chat~~

Cast · in order of appearance

The Diceas Random Pick
The Bracketas Battle
The Calculatoras Ranked
The Duelistsas Rolling Random

Special thanks

  • The 17 hours spent centering a single <div>
  • The 43 "just one more commit" commits
  • Stack Overflow, circa 2014
  • That one Postgres index that fixed everything
  • Caffeine. And more caffeine.
  • The friend who said "what if it was a bracket"
  • pnpm, for being on time
  • localhost:3000, our second home

In loving memory of

  • The Redux store we replaced
  • Server-side rendering, briefly
  • Six different attempts at a recommender prompt
  • Every npm package that got deprecated mid-build
  • Prop drilling (1995 – 2024)

No pixels were harmed in the making of this application. All flexbox stunts were performed by trained professionals on a closed track. Any resemblance to working WebSocket code is purely intentional.

FIN

© 2026 josh doeswork pictures

Box Office

Request a Screener

Reserve a seat for a private screening of Movie Night. Fill out the request below and the projectionist will be in touch.

Screening request

Movie Night

Across the Project-Verse