Skip to content

Repository files navigation

Matchboxd

Matchboxd icon

Find tonight's movie without the 45-minute debate.

Movies you both want to watch, with ratings and where to stream.

Live app · Repository

Matchboxd is a lightweight MVP that compares two public Letterboxd watchlists and builds one shortlist you can actually use. Enter two usernames, get the overlap first, add a few near-miss picks from each person, and open JustWatch searches in the detected or selected country.

The current product is intentionally narrow. Letterboxd is the first acquisition wedge and taste-data source, not necessarily the long-term center of the product. The larger strategic question is whether Matchboxd should grow into a broader social entertainment, taste-matching, group decision, or watch-planning product before any large feature investment.

What The App Does

  • Reads two public Letterboxd watchlists
  • Finds the shared overlap between them
  • Adds a balanced set of near-miss picks from each person's list
  • Progressively enriches each film with poster, rating, rating count, runtime, genres, and directors
  • Lets users filter by mood, source, runtime, and rating
  • Builds JustWatch search links using detected country with a manual override

Current Product Flow

The live app intentionally keeps the product small:

landing -> pair-loading -> pair-results

There are no accounts, no database-backed history, and no saved matches beyond the small session state stored in local storage for the current browser.

Strategic Status

  • Current product: public Letterboxd watchlist comparison for two people.
  • Current constraints: solo developer, low operating budget, and no planned large build without validation or outside funding.
  • Current dependency risk: public Letterboxd HTML scraping is useful for the MVP, but brittle as a commercial foundation.
  • Current validation priority: understand whether this is a standalone product, a social entertainment wedge, an indie utility, or a VC-scale consumer/social opportunity.
  • Strategy handoff package: see docs/strategy-research-package.md for the browse-enabled LLM research prompt and context.
  • Product orchestration playbook: see docs/product-orchestration/README.md for the advisory evidence loop, event contract, agent prompts, weekly memo template, and Sheets-ready evidence hub.

How Matching Works

  1. Matchboxd fetches both users' public watchlists through a proxy path because browsers cannot read Letterboxd HTML directly.
  2. It finds the overlap between the two lists.
  3. It adds near-miss candidates from each person's non-overlapping titles, alternating between users so one side does not dominate the list.
  4. It renders stubs quickly, then progressively enriches them with detail-page data.
  5. The results page lets the user filter, sort, and switch JustWatch country before opening streaming searches.

By default, the pairing logic caps enrichment to:

  • up to 60 overlapping titles
  • up to 40 near-miss titles

Stack

  • React 18
  • TypeScript
  • Vite
  • Tailwind CSS
  • Framer Motion
  • Vitest
  • Cloudflare Workers
  • GitHub Pages

Project Layout

src/
  app/
    SessionContext.tsx        persisted session + in-memory pairing state
  features/
    onboarding/               landing page and username validation
    pair/                     loading/results flow, filters, and hooks
  components/                 shared layout and UI primitives
  services/
    letterboxd/               proxy client, watchlist scraping, film scraping
    pairWatchlists/           candidate selection, progress, enrichment helpers
    countryDetection.ts       JustWatch region detection and override logic
worker/
  index.js                    Cloudflare Worker proxy for production
  local-proxy.mjs             tiny local proxy for development
.github/workflows/
  ci.yml                      typecheck, test, build
  deploy.yml                  GitHub Pages deploy
docs/
  strategy-research-package.md  product strategy research handoff prompt
  product-orchestration/         advisory product-intelligence loop and evidence hub

Brand Mark Note

  • Canonical accent pink is #ec4899.
  • #f9a8d4 is reserved for overlap glow and supporting highlight, not for one participant.
  • The two participant circles are intentionally equal in hue and opacity.
  • The overlap should remain the brightest point in the mark.

Local Development

Prerequisites

  • Node.js 20+
  • npm

Run The App

npm install
cp .env.example .env
npm run dev

The app will be available at the local Vite URL printed in the terminal.

Recommended: run the local proxy too

Letterboxd scraping is more reliable in local development when the proxy is running:

node worker/local-proxy.mjs

With the default VITE_LETTERBOXD_PROXY_MODE=auto, the app will prefer the local proxy on http://localhost:8787 during development when it is available.

Environment Variables

Important: Matchboxd is a client-side app. Any VITE_* variable ends up in the shipped JavaScript bundle and should be treated as public.

Variable Purpose Default
VITE_LETTERBOXD_PROXY_MODE auto uses the local proxy in dev and the hosted worker/default chain elsewhere; custom prepends your own proxy base URL auto
VITE_LETTERBOXD_PROXY_BASE_URL Base URL for a custom proxy or Worker that accepts ?url= and returns raw HTML (empty)
VITE_ENABLE_PUBLIC_PROXY_FALLBACKS Allows public proxy fallbacks after controlled proxies fail false
VITE_APP_BASE_PATH Base path for the deployed SPA /
VITE_POSTHOG_API_KEY Optional public PostHog project API key; leave empty to disable product analytics (empty)
VITE_POSTHOG_HOST PostHog capture host https://us.i.posthog.com
VITE_APP_VERSION Optional release label attached to analytics payloads (empty)

Deprecated aliases are still supported for one migration pass:

  • VITE_RSS_ADAPTER
  • VITE_RSS_BASE_URL

Available Scripts

Command What it does
npm run dev Starts the Vite development server
npm run build Type-checks and builds the production bundle
npm run preview Serves the production build locally
npm run lint Runs TypeScript type-checking with no emit
npm run perf:selection Regenerates the deterministic before/after selection benchmark report
npm test Runs the full Vitest suite once
npm run test:watch Runs Vitest in watch mode

Testing And CI

Run the main checks locally with:

npm run lint
npm test
npm run build

The repository also includes:

  • integration coverage for the app flow
  • service tests for filtering, pair selection, pairing, country detection, and proxy behavior
  • worker tests for origin allowlists, target allowlists, health checks, and successful proxy responses

GitHub Actions runs these checks on pull_request and on pushes to main via .github/workflows/ci.yml.

Analytics And Validation Inputs

Matchboxd includes lightweight first-party product event instrumentation through a small PostHog-compatible wrapper. It is disabled by default and sends nothing unless VITE_POSTHOG_API_KEY is configured.

Available signals:

  • Cloudflare zone analytics after DNS proxying: traffic, cache, threats, bot traffic, country, and device signals
  • Cloudflare Worker analytics: proxy request volume, latency, and error rates
  • PostHog product events once VITE_POSTHOG_API_KEY is configured
  • GitHub repository insights: stars, clones, visitors, and referrers
  • GitHub Pages and Actions history: deploy/build health, but not product usage

Currently implemented product events:

  • landing_view
  • pair_start
  • pair_success
  • pair_failure
  • results_filter_changed
  • justwatch_click
  • restart_clicked
  • performance_metric

Future events to add only when the corresponding UI exists:

  • share_clicked if sharing is added
  • feedback_submitted
  • save_pair_clicked
  • add_friend_clicked

Do not claim activation, retention, conversion, or willingness-to-pay data until it comes from analytics exports, interviews, or explicit validation tests.

The current product-orchestration setup is intentionally manual before platform automation:

  • docs/product-orchestration/matchboxd-evidence-hub.xlsx is a Google Sheets-ready evidence hub template.
  • docs/product-orchestration/event-contract.md defines the product event contract to implement before relying on behavior analytics.
  • docs/product-orchestration/agent-playbook.md and weekly-memo-template.md define the weekly advisory agent loop.
  • docs/product-orchestration/implementation-workflow.md extends the loop with Implementation Planner, Developer, Tester, and Verifier / Release Gate roles.
  • n8n, Make, GitHub Actions, or Apps Script should be added only after the manual weekly loop proves useful and repetitive.

Deployment

Frontend

The frontend deploys to GitHub Pages through .github/workflows/deploy.yml.

That workflow:

  1. installs dependencies
  2. builds the app
  3. copies dist/index.html to dist/404.html for SPA fallback
  4. publishes the build through GitHub Pages

Proxy Worker

For the preferred production proxy path, deploy the Cloudflare Worker in worker/:

cd worker
npx wrangler deploy

The worker:

  • only allows approved origins
  • only proxies https://letterboxd.com/... targets
  • returns X-Client-Country so the frontend can derive the default JustWatch region

After deploying your own worker, point the app at it with:

VITE_LETTERBOXD_PROXY_MODE=custom
VITE_LETTERBOXD_PROXY_BASE_URL=https://<your-worker>.workers.dev/

Operational Notes

  • Matchboxd scrapes public Letterboxd HTML pages. It does not use an official Letterboxd API.
  • Public profiles and public watchlists are required.
  • JustWatch links are search URLs, not guaranteed deep links to an exact title page.
  • The controlled worker path is the preferred production setup.
  • Public proxy fallbacks are intentionally opt-in.
  • Recent repo-side security work added /.well-known/security.txt, GitHub security policy files, and refreshed Open Graph/Twitter share preview assets. Some security and bot settings still depend on Cloudflare/GitHub dashboard configuration rather than app code.

Common Failure Modes

  • Couldn't find @user on Letterboxd Usually means the username is misspelled or does not exist.
  • @user's watchlist is set to private The profile may exist, but the watchlist is not publicly readable.
  • Couldn't read one or both watchlists A proxy may be blocked or rate-limited. Retry after a short wait or switch to a controlled proxy.

License

MIT

About

Find tonights movie without the 45-minute debate. Built from your public Letterboxd taste.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages