← All projects

personal

Movie Explorer

One React codebase, running on the web and as an LG webOS TV app

A movie and TV browser that pulls titles from TMDB, adds IMDb and Rotten Tomatoes scores, shows where each title streams in your country and, on the TV, launches straight into the right streaming app. Same React tree for mouse and remote.

movie-explorer.maintz.devTry it ↗
Live demo, embedded. Opens in full size via the link.

The hard part

The dual target. The same React tree has to work with a mouse and a TV remote, on modern browsers and older webOS Chromium engines, with no URL rewriting on the TV because webOS loads the app from a file path.

Highlights

  • One codebase, two targets: HashRouter and an es2015 build for webOS. On the TV the keys load from a config file, so they can change without a rebuild
  • TV-first UX: spatial D-pad navigation, media keys, scroll-on-focus and safe-area padding
  • Region resolution with a clear fallback chain (saved setting → config → IP lookup → US) driving the streaming lookups
  • webOS deep links: streaming providers mapped to TV app IDs, launched with Luna params
  • Firebase login with wishlist, watched list and settings in Firestore, locked to their owner by rules. OMDb ratings are cached in Firestore for 30 days to stay on the free tier
  • Roulette that picks a random title filtered by genre, year, minimum score and the services you pay for. MovieWheel started as a spin-off of it

Tests and CI

50 Vitest tests on the pure parts (genre mapping, release dates, cast shaping, platform detection, provider deep links), run through my Foundry gate with lint, types, a coverage floor, audit and secret scanning. Coverage is still low on the components, and the floor only goes up. Every push to main deploys the web build to GitHub Pages.

The live web build runs without the OMDb and streaming keys, so those parts need a local setup.

History

My first version of the app was built with MUI and called ReelScout. I rewrote it with Tailwind and the webOS target in a private repo, then moved that back into this one. The old commits are still in the history.

README.md - movie-explorer▼

movie-explorer

A movie and TV browser that runs in the browser and as a sideloaded app on my LG webOS TV, where you drive it with the remote's D-pad. It pulls everything from TMDB, adds IMDb and Rotten Tomatoes scores via OMDb, shows which streaming services carry a title in your country, and on the TV it can launch straight into the right streaming app.

Live (web build): https://movie-explorer.maintz.dev

The live build doesn't have OMDb or streaming-availability keys, so IMDb/RT scores and per-title deep links only show up locally with those keys set.

Home page

Details page Roulette page

What it does

  • Trending, genre pages and detail pages with cast, trailers and similar titles
  • Ratings from TMDB, IMDb and Rotten Tomatoes (OMDb, cached in Firestore for 30 days so the free tier lasts)
  • Streaming availability per country. Region comes from your saved setting, then config, then IP lookup, then US
  • Deep links into streaming services. On webOS they launch the target app with Luna launch params
  • Roulette: can't decide, spin for a random title filtered by genre, year, minimum score and the services you pay for, from all of TMDB or just your wishlist. MovieWheel started as a spin-off of this feature
  • Firebase email/password login with wishlist, watched list and settings in Firestore
  • TV mode: spatial navigation for the remote, media keys, safe-area padding, 1080p layout that the TV upscales

Stack

React 19, TypeScript, Vite, Tailwind, TanStack Query, React Router 7 (HashRouter, because webOS loads the app from a file path), Firebase Auth + Firestore, norigin-spatial-navigation for the D-pad. APIs: TMDB v3/v4, OMDb, Streaming Availability (movieofthenight), ip-api.com.

Running it

Needs Node 22 (pinned in mise.toml).

npm install
cp .env.example .env   # TMDB + Firebase are required, the rest is optional
npm run dev

For the TV, copy public/appconfig.example.js to public/appconfig.js and put the keys there instead. It loads before the bundle, so keys can be changed on the TV without rebuilding. Then, with ares-cli and the TV in developer mode:

npm run build:tv     # build + webOS manifest and icons
npm run install:tv   # package the .ipk and sideload it
npm run launch:tv
npm run inspect:tv   # remote DevTools

firestore.rules keeps each user's wishlist, watched list and settings readable and writable only by that user. Deploy with firebase deploy --only firestore:rules.

Tests and CI

mise run gate   # lint, typecheck, vitest with coverage floor, npm audit
npm test        # just the tests

50 Vitest tests across 5 files, covering the pure bits: genre mapping, release-date handling, cast/crew shaping, platform detection and the provider/deep-link mapping for streaming services. PRs run the Foundry gate (the same mise run gate, plus structural smell checks, gitleaks, semgrep and a guard against loosening lint rules in the same PR as code changes). Pushes to main build the web version and deploy it to Pages.

Limitations

  • Coverage is low, around 4% of lines. The thresholds in vite.config.ts are a floor that only goes up, but the components and hooks are mostly untested
  • The web build gets its keys baked in at build time, so the TMDB/OMDb keys are visible in the bundle. Fine for free-tier keys, not for anything that costs money
  • Deep links are most useful on webOS. In the browser they just open the title on the service's site in a new tab, or TMDB's where-to-watch page if there's no link
  • On the Pages build, appconfig.js doesn't exist, so that request 404s and the app falls back to the baked-in keys. Harmless, just noise in the console

History

This repo used to hold my first version of the app, built with MUI. I rewrote it with Tailwind and the webOS target in a separate private repo and moved that back here. The old commits are still in the history.

License

MIT

command.exeesc

↑↓ select · Tab complete · Enter run · Esc close

doom.exe

WASD move · ←→ turn · Space fire · E use · Shift run · Esc menu · click to capture mouse · licences