Product documentation

How FrameSouth picks tonight's film, and what it will not pretend to know.

VersionBeta, rev. 03
Last updated2 Oct 2026
StatusActive
Films3,964

00 / OverviewWhat FrameSouth is

A discovery app for South Indian cinema that answers one question: what should we watch tonight? It holds 3,964 Telugu, Tamil, Malayalam and Kannada releases from 2022 to 2026, with TMDB's posters, stills and cast for the 2,218 it has matched, and where 1,398 of them stream in India.

You give it a mood; it gives you three films, Tonight's 3, each with a match score, the reason and the services it is on. Around that sit quicker ways in (Swipe, This or That, Surprise me) and slower ones (Discover with its filters, the South Atlas, eleven collections, search), plus a library and a Cinema DNA built from what you do.

Three rules govern it. Facts from TMDB, judgement from FrameSouth: images, credits and availability are TMDB and JustWatch facts, shown through one image component that lint and the build check enforce; moods, matches and reasons are FrameSouth's own. No accounts: your history lives against one anonymous cookie. No guessing: a film that cannot be scored is never recommended, and a service is shown only where a named source lists it.

01 / Getting inUsing FrameSouth

Getting in

On this lane FrameSouth is simply open. There is no code to ask for and nothing to sign in to: framesouthapp.manp.space holds public film metadata from openly-licensed sources, and the anonymous activity of its visitors (what was saved, swiped and picked) tied to a random cookie, with no name, email or account. The on-premises deployment is the one that genuinely closes the door, because there the application listens on loopback and is reached only through the gateway.

Tonight: the home page

  • Pick a mood from the orbit: Comfort, Funny, Romantic, Intense, Mind-bending or Dark. The room takes the mood's colour.
  • Tonight's 3 changes on the same page: a best match, a great match and a wildcard, a different kind of film from further down the ranking. The chosen frame fills the screen with its still, match score, reason and the services it streams on. See the film opens it, Watchlist saves it, and Not feeling it keeps it out of later answers.
  • Below that: South Pulse (how the catalogue divides across the four cinemas and how much of each streams in India), the South Atlas, a film strip of new releases, the collections, Play, and your Cinema DNA.
  • A projector, the one 3D object, loads only when its section comes near; without WebGL or with reduced motion a still stands in, and nothing depends on it.

Swipe

One film at a time. Interested, Not for me, Seen it or Save, by button or key (→ interested, ← not for me, ↑ seen it, S save). Filter by language. The deck never shows a film you have already answered.

This or That and Audience Battles

Two films; pick one by clicking or pressing 1 or 2 or an arrow key. The film you pick stays and a new challenger steps in, for seven rounds, then a winner; after each pick a line says what it taught FrameSouth. Every pick is an anonymous vote, and Audience Battles tallies them by pairing once a pairing has four votes.

My films, Profile and Cinema DNA

My Films has Watchlist, Watched, Liked, Not for me, Recently viewed and Recommendation history, each filterable by language. Cinema DNA turns your history into an organic shape, language shares, top genres and a one-line description of the watcher you are. With too little history it says so and draws no shape.

Discover, collections, search and Surprise me

  • Discover opens on a feeling (Feel Good, Intense, Romantic, Mind-bending, Dark, Emotional, Funny) and narrows by language, genre, runtime, year, streaming service, rating and a director or actor, with the count of matching films.
  • The South Atlas, on the home page, relights the room in a cinema's colour, sets its script large and shows three of its films; each cinema leads to its own page.
  • Collections: eleven shelves, such as Great Movies Under 2 Hours, Malayalam Thrillers and Slow Burns, each opening on a featured film.
  • Search opens as a palette from the top bar and reads a sentence: "malayalam thriller on netflix rated 7+" becomes a language, a genre, a service and a rating, and the results say how the query was read. Names stay free text.
  • Surprise me, from the top bar on any page, rolls one film within the constraints you tick.

A film page

The TMDB still and poster where the film is matched, or FrameSouth's generated artwork where it is not; the title, with the original-script title beneath it; year, language, runtime and the TMDB rating when it has at least 20 votes; the actions (Watchlist, Watched, Like, Pass); the overview; Where to watch in India, grouped into stream, rent and buy with the source and the date it was checked, or a line saying nothing is listed; then why you may like it, the vibe meters, cast with their characters, similar films and the collections it is in. TMDB and its data sources are credited at the foot of the page.

Keyboard and assistive technology

A skip link is the first stop on every page. Every control has a visible focus ring, and mood controls report their pressed state. Reduced motion is meant to stop every animation; on 2 October the home, Swipe and Discover pages were found still animating, which is not yet fixed. On a phone, the bottom navigation carries Tonight, Discover, Play, Saved and Search.

02 / A day in the lifeOne household, one evening

A couple in Bengaluru, one Kannada-speaking and one Malayalam-speaking, with an hour and three-quarters before bed. An illustration of the flow, not a real user.

  1. Over lunch, one of them swipes for five minutes: eight not-for-mes, three interesteds. That is enough history for the matches to start leaning their way.
  2. At nine, they pick Mind-bending on the home page, and Tonight's 3 changes in place.
  3. The best match is a film one of them has seen. Not feeling it removes it from later answers; they look at the wildcard instead.
  4. They disagree, so they open This or That and play seven rounds. The winner goes to the film page.
  5. Where to watch lists it under Stream on ZEE5, from JustWatch, checked on a stated date. If it listed nothing, they would check the apps themselves, because FrameSouth will not guess.

03 / ProvenanceWhere the data comes from

Sources

SourceUsed forTerms
Wikipedia year lists and articlesThe original catalogue: title, release date, director, cast, and for 118 listings the service a film premiered onCC BY-SA 4.0
WikidataIdentifiers and cross-checksCC0
TMDBFacts, posters, stills, cast and crew, ratings, and empty genres, runtimes and synopses filledTMDB API terms; not endorsed or certified by TMDB
JustWatch, through TMDBWhere a film streams, rents or sells in IndiaCredited on every film page; JustWatch does not endorse FrameSouth

All of them are read offline, by scripts, and never in a visitor's request path. The TMDB key is read only on the server. The one thing a visitor's browser fetches from outside is film images, straight from TMDB's image CDN at the size each place needs.

Matching a film to TMDB

Many South Indian films share a title across languages and years, so a match is scored before it is trusted. An exact title, language and year is high confidence and attaches; a film in another language never attaches, even with the same title; a near title or a release a year away needs the director to agree. Medium matches wait for a person at /admin/tmdb, and a manual decision sticks. Today 2,218 films are matched, 18 wait for review, and 1,728 have no match and keep their original record.

Where to Watch, specifically

Availability comes from JustWatch through TMDB's providers endpoint, for India only: 3,223 listings, each with the date it was checked. A service's ad tier and its channels fold into the one service, so each is named once. For films TMDB lists nothing for, 118 listings are kept from the films' own Wikipedia release sections, where an article says with a citation which service a film premiered on. A refresh command re-reads providers; it is run by hand, not on a schedule.

Every derivation, stated

Each film's vibe (darkness, comedy, emotion, romance, tension, energy, pacing and family suitability) and a confidence score are derived from its genres, story and runtime when it is imported, and recomputed whenever TMDB fills a gap. Recommendations use a film only when that confidence clears a fixed floor, which 2,581 films do today. The film page labels the vibe meters as FrameSouth's reading and shows the confidence.

What is deliberately empty

A synopsis, runtime or genre that neither source has is absent, never invented. 2,701 films have a synopsis, 2,581 genres and 2,339 a runtime. A rating is shown only with 20 or more TMDB votes, which 176 films have. Trailers and certification are not shown at all.

04 / ArchitectureHow it is put together

Stack

LayerChoice
ApplicationNext.js 16 (app router), React 19, server components by default
StylingTailwind CSS 4 plus a hand-written token system (design.md); Bodoni Moda and Geist
3DThree.js, for one lazy-loaded projector
DataPostgreSQL through Prisma 7; inputs validated with Zod 4
CloudVercel, Mumbai region, with Neon PostgreSQL 18
On premisessystemd on loopback, PostgreSQL 16, behind this gateway

Data model

  • movies: one row per film, with its record, its derived vibe scores and confidence, and its TMDB fields (id, original title, overview, poster and still paths, rating, credits, and the match state).
  • streaming_providers and movie_ott: 39 services and tiers as TMDB names them, and each film's availability with its country, type (stream, ads, free, rent, buy), source and the date it was checked.
  • viewers: a random id per browser, created on the first action, and nothing else.
  • interactions: flags, swipes, battle votes (with the opponent) and "try another", each against a viewer and a film.

Matching

The repository keeps a compact in-memory index of the catalogue and ranks it against the request: mood weights, the time budget, the company, the language and the viewer's taste once there are three or more interactions. Films the viewer has watched, disliked or passed are excluded. The top twelve come back with a one-sentence explanation each, and Tonight's 3 takes the first two and a wildcard: the one further down with the most hidden-gem signal whose lead genre differs from both.

The mechanical guarantees

A build-time check fails on a film image rendered anywhere but the one image component, or built anywhere but the one image module; on a hard-coded year; or on a colour value outside the one token file. Lint enforces the same image door. A contrast check asserts 17 colour pairs against WCAG AA. Six browser tests still assert the retired no-imagery rule and fail until they are replaced.

05 / APIReference

JSON over HTTP. Every parameter is validated before it reaches the database; a bad request returns a 4xx with a body of exactly {"error": string}, and no error body carries a stack trace, a table name or a path. Personal endpoints are never cached.

GET /api/match

The ranking behind Tonight's 3. Parameters: moods (comma-separated, up to four), time (short, standard, long, any), company (alone, couple, friends, family), language, exclude (slugs).

{ "results": [ { "movie": { "slug": "ananthan-kaadu-2026-malayalam", … },
                 "score": 97, "explanation": "…", "reasons": ["It's intense"] }, … ],
  "ott": { "ananthan-kaadu-2026-malayalam": ["Prime Video", "Lionsgate Play", "ManoramaMAX"] },
  "personalised": false }

GET /api/swipe/deck

Twelve cards. language optional; exclude optional. Films the viewer has already swiped are left out.

GET /api/battle/pair · GET /api/battle/board

A pair of two different films, optionally by language and genre, excluding exclude. The board returns the audience tallies for a named board.

GET /api/search?q=

Free text up to 120 characters, with offset for paging. The response includes how the query was read.

GET /api/surprise

One film, optionally by language and with constraints, with a one-line explanation.

GET /api/also-liked?slug=

Films that viewers who liked this one also liked.

GET /api/me · POST /api/me

The viewer's library, history counts and Cinema DNA (null until there is enough evidence). POST records one action:

{ "type": "flag",   "slug": "…", "flag": "watchlist", "value": true }
{ "type": "swipe",  "slug": "…", "dir": "save" }        // pass · save · interested
{ "type": "battle", "winner": "…", "loser": "…" }
{ "type": "nope",   "slug": "…" }                       // "not feeling it"

An unknown film returns 404; a battle between a film and itself returns 400.

POST /api/admin/import

Behind HTTP Basic auth. Receives batches of rows from the import page; every row is validated again here, whatever the browser already checked. The TMDB review page at /admin/tmdb sits behind the same check.

06 / Loading filmsData work, not engineering work

Films arrive as a CSV upload at /admin/data; their TMDB facts arrive through scripts. Adding a year, fixing a synopsis or refreshing streaming is data work, not a release.

The template has one row per film and these columns:

title,year,language,release_date,runtime_minutes,genres,directors,cast,
synopsis,ott,ott_verified,ott_source
  • Lists (genres, directors, cast, ott) are separated by semicolons.
  • ott names services from the known nine (aliases such as "Hotstar" resolve); an unknown name rejects the row rather than being dropped. ott_verified may not be in the future.
  • A film is identified by title, year and language. Re-uploading it updates it; an identical row is skipped. A blank ott leaves existing availability alone.

The page previews the file first (new, updates, rejected, and how many carry where-to-watch), names the field that failed on every rejected row, and writes only when you confirm. A full re-upload of the 3,964-film catalogue was rehearsed at about three seconds when nothing but availability changed.

Facts, images and providers come from TMDB through server-side commands:

npm run tmdb:match               # score candidates; high attaches, medium waits
npm run tmdb:sync                # facts, images, credits; recompute FrameSouth's scores
npm run tmdb:enrich              # fill only empty genres, runtimes and synopses
npm run tmdb:providers:refresh   # re-read where each film streams in India
npm run tmdb:discover            # list TMDB films the catalogue lacks (dry)

Schema changes ship as reviewed SQL in db/sql/. Catalogue changes reach the cloud database by staging the changed rows and applying them in one transaction with a count check, matched by slug; films are never truncated, because real audience activity hangs off them.

07 / OperationsTwo lanes, one build

On premisesCloud
Wheresystemd, on a loopback portVercel, Mumbai
DatabasePostgreSQL 16, its own database; tests use a separate oneNeon PostgreSQL 18
AccessMailed code, through the gatewayOpen
Where to WatchThe same in both: 3,341 listings for 1,458 films
Checked byThe browser suite: 38 of 54 pass on the redesignNot re-run for this release

Secrets (database URLs, the admin password, the TMDB key, the mail key, the cookie secret) live in gitignored environment files on premises and in encrypted project variables in the cloud. Browser tests against production deliberately skip the two that would record interactions, so test runs never land in real audience data.

08 / Known limitsStated plainly

These are the same rows marked partial and roadmap on the build ledger. You will find them anyway.

  • A third of the catalogue has no TMDB match. 1,728 films keep their original record and generated artwork; 18 more wait for review.
  • Recommendations read two-thirds of the catalogue. 2,581 of 3,964 films have enough record to score. The rest are findable but never recommended.
  • Availability is as of its date. Each listing shows when it was checked; nothing refreshes it on a schedule yet. 2,506 films list no service at all.
  • Two layout defects on phones. Search results overflow by 37 pixels at 375 wide, and an industry page also scrolls sideways.
  • Reduced motion is incomplete. The home, Swipe and Discover pages still animate with it switched on.
  • The browser suite lags the redesign. 38 of 54 pass; eleven failures are stale tests, five are the defects above.
  • A profile lives in one browser. There are no accounts; clearing cookies starts a new viewer.
  • Audience boards fill slowly. A pairing needs four votes before it is ranked, and the beta has very little real traffic.
  • Five years. 2022 to 2026, and TMDB knows 1,289 films from those years that are not imported.