E5 S4: Plex Discover rail-surface (default landing)

A dedicated Plex "Discover" landing — a stack of horizontal Carousel rails
served from ONE consolidated read (serve-from-DB, no live Plex/TMDB call):

- GET /api/plex/discover → continue_watching (in-progress movies + shows, most
  recently progressed) · recently_added · genres (top-N tiles) ·
  because_you_watched (recs of the most-recent watched, TMDB-enriched movie,
  resolved to library rows by guid via serve_block's resolver).
- GET /api/plex/surprise → a fresh random, scope-bounded sample for the ↻ rail.

Frontend: PlexDiscover.tsx (rails reuse the S1 Carousel + S3 PlexPosterCard;
colored genre tiles → tap to filter the grid; ↻ reshuffles the surprise rail
alone). Discover is now the root sub-view; the flat grid becomes the bookmarkable
"Browse all" secondary view (?v=browse) reached by a header search/filter (a
browse-intent flip) or the new Discover⟷Browse toggle.

resolve_recs is promoted from _resolve_recs (now cross-module). Tests:
test_plex_discover.py (rail shape/order, continue-watching, because-you-watched
+ null, surprise bounds, scope) and PlexDiscover.dom.test.tsx; plexSubUrl
round-trip updated for discover-root/grid-browse. Backend lane 393, vitest 332.
This commit is contained in:
2026-08-20 22:15:54 +02:00
parent 5b5e76b73d
commit f9b63745f9
13 changed files with 1022 additions and 9 deletions
+2 -2
View File
@@ -334,7 +334,7 @@ def run_enrich(db: Session) -> dict:
return {"enriched": n}
def _resolve_recs(db: Session, recs: list[dict] | None) -> list[dict]:
def resolve_recs(db: Session, recs: list[dict] | None) -> list[dict]:
"""Tag each recommendation with whether it's in OUR library (matched by guid `tmdb:<id>`) and, if so,
its rating_key — so the client can deep-link the ones we have and merely display the rest."""
if not recs:
@@ -374,6 +374,6 @@ def serve_block(db: Session, row) -> dict | None:
"crew": row.crew or [],
"studios": row.studios or [],
"production_countries": row.production_countries or [],
"recommendations": _resolve_recs(db, row.recommendations),
"recommendations": resolve_recs(db, row.recommendations),
"backdrops": row.backdrops or [],
}