Skip to content

feat(catalog): turn /shows and /movies into carousel landings with list pages - #45

Merged
kris1027 merged 27 commits into
mainfrom
feat/home-carousels
Oct 12, 2026
Merged

kris1027 merged 27 commits into
mainfrom
feat/home-carousels

Conversation

@kris1027

Copy link
Copy Markdown
Owner

What

/shows and /movies are no longer one paged grid of 20 popular titles. Each one is now five rows that scroll sideways (carousels) of TMDB lists. Each row has a See all link to its own paged list page, for example /shows/top-rated or /movies/now-playing. Nothing is stored and the schema does not change.

Why

This implements spec 0021, Home carousels (scope feature 24). Visitors can now see several kinds of lists at once (trending, popular, airing or in cinemas, top rated) instead of paging through one list. Every card still shows and changes the signed in user's tracking state, the same way the grid does. Old ?page=N bookmarks keep working.

Changes

TMDB reads

  • New lib/tmdb/lists.ts with fetchTvList and fetchMovieList. They cover trending (week), airing today, on the air, now playing, upcoming and top rated. Only the dated lists send a place: timezone=Europe/Warsaw for TV and region=PL for movies. Results that TMDB flags adult are dropped, because these endpoints take no include_adult (AC-8).
  • getTvList and getMovieList in reads.ts are use cache wrappers. They use cacheLife("hours") on success and the existing failure profile on failure. Popular still uses the existing discover reads, so it shares cache entries with the sitemap.

List registry and dispatcher

  • New lib/catalog/lists.ts: a pure registry of the ten lists (slug, row title, h1, note, page noun, paths, isCatalogListSlug). It has no server-only, so the proxy and the sitemap can import it.
  • New lib/catalog-list-read.ts: readCatalogList(list, page). This is the one place a slug turns into a TMDB call. A row and page 1 of its list page make the same cached call, so opening See all costs no extra request (AC-3).

UI

  • CarouselRow (server) renders a labelled <section> with a header (h2, See all, arrows). Its cards stream in their own Suspense boundary, with 12 card skeletons as the fallback. If one row fails or comes back empty, only that row shows a compact error or empty state (AC-11, AC-12).
  • CarouselScroller (client) is a focusable scroll region with scroll snap. From md up it adds back and forward buttons that scroll 90% of the visible width. It respects reduced motion, and each button is disabled at its end of the row.
  • CatalogListPage is the shared paged list page: back link, h1, source note, grid and pagination, plus the "no such page", error and empty panels. The show and movie card renderers were pulled out into list-cards.tsx, and rows and list pages both use them.
  • PosterCard gets an optional sizes prop, so carousel cards (140px, 180px from md) request the right image width.
  • /showcase shows the carousel row and its loading, error and empty states.

Routing, SEO

  • There are ten small static routes under app/shows/{slug} and app/movies/{slug}, each with generateMetadata built from the registry.
  • The proxy lets a known list slug through before the numeric id check. Any other non numeric segment still gets a real 404, for example /shows/upcoming or /shows/Popular (AC-13).
  • next.config.ts adds permanent (308) redirects: /shows?page=N goes to /shows/popular?page=N, and the same for movies (AC-14).
  • The sitemap lists the ten bare list paths after the landings (AC-16). The landing metadata gets new descriptions.

How to test / verify

  • You may want to run pnpm typecheck, pnpm lint and pnpm test. The new and rewritten suites are lib/tmdb/lists.test.ts, lib/catalog/lists.test.ts, lib/catalog-list-read.test.ts, app/catalog-lists.test.ts, app/movies/request-scope.test.ts, the components/catalog/* tests, proxy.test.ts, next.config.test.ts and app/sitemap.test.ts.
  • You may want to run TMDB_READ_ACCESS_TOKEN= pnpm build. It should pass, because rows read TMDB only at request time (AC-7).
  • Open /shows and /movies at 1280px. You should see five rows each, in spec order. Press the arrows (back starts disabled), then try 375px: there should be no arrows and no sideways page scroll.
  • Open a few See all pages. Then try ?page=abc and ?page=600; both should show "That page doesn't exist".
  • curl -sI "localhost:3000/shows?page=3" should answer 308 with location: /shows/popular?page=3. curl -s -o /dev/null -w "%{http_code}" localhost:3000/shows/upcoming should answer 404.
  • Signed in with pnpm dev:docker as user-a@example.test, track a show from a row. Its badge should match its library page.
  • The full checklist is in verify.md.

Risk & rollout

Low risk, no migrations, no flags. These are public catalog pages only, with no new user data reads. The TMDB list reads take no cookie, header or search param, and the request scope test covers the landings and list pages (AC-17). The ?page redirect is a 308 on purpose, so browsers cache it for good; /shows?page=N has no future meaning. Rolling back means reverting the PR, but browsers that already cached the 308 keep sending ?page bookmarks to /…/popular, which still works.

Notes for reviewers

  • Two value sourcing steps in verify.md are still unticked: the Warsaw midnight check for Airing today, and the signed out bookmark returnPath round trip. Every other step is ticked.
  • No /check review has run for this branch yet. There is no review in docs/reviews/.
  • The fixed PL region and Europe/Warsaw timezone are a deliberate choice. Following each visitor's region is recorded as deferred in the scope.
  • The branch also includes a scope housekeeping commit. It marks feature 23 (progress based library pages) as done and ticks its amendment verify step.

🤖 Generated with Claude Code

kris1027 and others added 19 commits October 12, 2026 16:49
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>
…y states

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 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
@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 5:31pm UTC

kris1027 and others added 2 commits October 12, 2026 18:33
An encoded slug such as /shows/top%2Drated passed the proxy but reached
[id], a soft 404. Also renames lists to listMediaType.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
listPath and listPageHref take the CatalogList, so a slug can no longer
travel apart from its media type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kris1027 and others added 6 commits October 12, 2026 18:33
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All ten lists held the same text, so the field carried nothing.

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kris1027
kris1027 merged commit b12d301 into main Oct 12, 2026
2 checks passed
@kris1027
kris1027 deleted the feat/home-carousels branch October 12, 2026 16:37

This branch was successfully deployed

1 active deployment
Preview — cb4cd4e0 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

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