Skip to content

feat(catalog): match card sizes and add richer title pages (spec 0022) - #46

Merged
kris1027 merged 26 commits into
mainfrom
feat/card-size-title-pages
Oct 12, 2026
Merged

kris1027 merged 26 commits into
mainfrom
feat/card-size-title-pages

Conversation

@kris1027

Copy link
Copy Markdown
Owner

What

Poster cards on every grid page now come out at about the home row card's width (about 180px), because the grid steps from 2 up to 6 columns. Movie and show pages gain a Trailer, Where to watch, More like this, key crew, an age rating chip and a Facts panel. Nothing is stored and the schema does not change.

Why

This implements spec 0022, Consistent card size and richer title pages (scope feature 25). A poster used to be one size in a home row and a bigger size on its See all page. Title pages also left out data TMDB already offers, so you had to leave BeStats to find a trailer or a place to stream. The new title data comes from one extra cached TMDB read per title. getTvShow stays as it is, so the library pages, which read up to 500 shows, cost the same as before.

Changes

Card size

  • PosterGrid takes a columns preset. catalog is the default: 2 / 3 (sm) / 4 (md) / 5 (lg) / 6 (xl) columns. wide is the old 2 / 4 layout, and only the season grid uses it.
  • The exported POSTER_GRID_SIZES and POSTER_GRID_EAGER maps own each preset's image sizes and eager count, so the four local EAGER_POSTERS copies are gone. poster-grid.test.tsx works out the expected sizes from the column rule, so a column change that skips its sizes fails the suite (AC-4, AC-5).
  • LibrarySkeleton shows exactly one row at the current column count, so a one row library no longer shifts when it loads (AC-6). /showcase shows 6 grid cards.

TMDB reads

  • New lib/tmdb/extras.ts with fetchMovieExtras and fetchTvExtras. Each is one /movie/{id} or /tv/{id} call with append_to_response for videos, recommendations, watch providers and release dates or content ratings. They are validated by a loose schema in schemas.ts and normalized in normalize-extras.ts. getMovieExtras and getTvExtras in reads.ts are use cache with cacheLife("hours") (AC-8).
  • The normalizer picks the trailer (YouTube Trailer before Teaser, official first, then the newest), groups providers into Stream, Free, and Rent or buy, picks an age rating for every country, and drops adult recommendations.
  • Movie adds status, budget, revenue, production companies, directors and writers, all from the existing credits response. TvShow adds type, created by, networks and production companies. Neither makes a new request.

Region

  • lib/catalog/region.ts has the pure parseRegion. lib/region.ts has getVisitorRegion(), wrapped in React cache(), which reads Vercel's x-vercel-ip-country header and falls back to US (it also rejects XX, T1 and anything Intl.DisplayNames can't name). It runs only inside streamed Suspense boundaries. The cached extras hold every country, and the page picks the visitor's country after the read returns, so the region is never cached, stored, logged or sent to TMDB (AC-9, AC-10).

Title pages

  • TrailerButton (client) opens a base-ui dialog that loads a youtube-nocookie.com embed only while the dialog is open. It goes in the new actions slot on DetailHero (AC-11).
  • WhereToWatch, the age rating chip and More like this (a carousel row of home row cards with your status) are each async slots in their own Suspense boundary, with skeletons while loading and a RetryLink error line when the read fails (AC-12 to AC-14, AC-18).
  • title-details.tsx adds the crew lines (Directed by, Written by, Created by) and the Facts panel (AC-15, AC-16). Both pages follow the spec's section order (AC-17). SECTION_HEADING_CLASS gives every section heading one shared style.
  • The Cast section is now a carousel row of cards at row card width, using the same scroller as More like this.

Legal

  • YouTube joins PROCESSORS. /privacy gains the country sentence and the trailer sentence. JUSTWATCH_ATTRIBUTION lives in lib/legal/operator.ts, and the boundary test guards it. LEGAL_LAST_UPDATED is now 2026-10-12 (AC-19).

How to test / verify

  • You may want to run pnpm typecheck, pnpm lint and pnpm test. The new suites include poster-grid.test.tsx, lib/tmdb/extras.test.ts, lib/catalog/region.test.ts, lib/region.test.ts, components/catalog/title-extras.test.tsx, title-details.test.tsx, trailer-button.test.tsx, where-to-watch.test.tsx and app/title-pages.test.ts.
  • You may want to run pnpm build. /movies/[id] and /shows/[id] should still be listed as Partial Prerender (AC-10).
  • Open /movies/popular at 1280px. You should see 6 columns with cards about 187px wide. Then try 375, 640, 768 and 1024px; you should see 2, 3, 4 and 5 columns. On /shows/1396 the seasons should stay at 2 and 4 columns.
  • Open /movies/550. You should see an R chip, a Trailer button, the crew lines, Where to watch in United States, Cast, More like this and Facts (Budget $63M, Revenue $100.9M). Until you press Trailer, the network panel should show no YouTube request.
  • curl -s -H "x-vercel-ip-country: PL" localhost:3000/movies/550 should show "Where to watch in Poland" and an 18 chip. The values XX, T1, pl and no header should all fall back to United States.
  • /movies/1000010 should show no Trailer, no chip, and "Not streaming in United States right now."
  • The full checklist is in verify.md. Every step is ticked except one known issue, described below.

Risk & rollout

Low risk, no migrations, no flags. The pages are public catalog pages, and nothing reads new user data. The cost is one extra TMDB request per title per hour, shared by every visitor. YouTube is a new processor, so the privacy policy changes in this same PR, as spec 0017 asks. The trailer player loads only after a click. If you roll back by reverting, LEGAL_LAST_UPDATED and the processor list go back with it.

Notes for reviewers

  • The Cast carousel commit (34434a4) is not one of spec 0022's acceptance criteria. It makes Cast match the More like this row next to it, and it keeps spec 0006's 12 member limit.
  • You can't force the extras failure state live, because the extras read shares its URL with the detail read. It was checked with a temporary thrown TmdbError, which has since been reverted (see verify.md).
  • Known and not caused by this branch: signed in at 768px and 1024px, the navbar is wider than the page. That still needs confirming on main and its own issue.
  • No /check review has run on this branch yet, so there is nothing for it in docs/reviews/.
  • The 1024px to 1279px band deliberately gives bigger cards (about 186px to 228px), as AC-2 says.

🤖 Generated with Claude Code

kris1027 and others added 17 commits October 12, 2026 19:01
…0022)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e this and facts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r dialog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kris1027 kris1027 added accessibility Keyboard access, screen reader support, contrast, and accessibility docs Documentation additions and updates feature New user-facing functionality test Automated tests and test infrastructure ui Layouts, styling, and shared interface components labels Oct 12, 2026
@kris1027 kris1027 self-assigned this Oct 12, 2026
@kris1027 kris1027 moved this from Backlog to In review in BeStats Oct 12, 2026
kris1027 and others added 5 commits October 12, 2026 21:13
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…true cause

A 404 or a bad response from the extras read said TMDB didn't respond.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kris1027 and others added 4 commits October 12, 2026 21:16
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r copy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kris1027
kris1027 merged commit 9c562a8 into main Oct 12, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in BeStats Oct 12, 2026
@kris1027
kris1027 deleted the feat/card-size-title-pages branch October 12, 2026 19:24
@vercel

vercel Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
bestats Ready Ready Preview Oct 12, 2026 7:42pm UTC

This branch was successfully deployed

1 active deployment
Preview — c5e61d2c Deployed Oct 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accessibility Keyboard access, screen reader support, contrast, and accessibility docs Documentation additions and updates feature New user-facing functionality test Automated tests and test infrastructure ui Layouts, styling, and shared interface components

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant