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:
@@ -5,6 +5,7 @@ import { parsePositiveIntId } from "../lib/parseId";
|
||||
// encode/decode round-trip (and its guards) can be unit-tested without pulling the whole component
|
||||
// (and its lazy hls.js player) into the test graph.
|
||||
export type Sub =
|
||||
| { kind: "discover" }
|
||||
| { kind: "grid" }
|
||||
| { kind: "show"; id: string }
|
||||
| { kind: "season"; showId: string; seasonId: string }
|
||||
@@ -14,12 +15,16 @@ export type Sub =
|
||||
| { kind: "playlist"; id: number };
|
||||
|
||||
// Mirror the Plex sub-view to ?v=<kind>:<args> so a show / season / movie-info / person / playlist page
|
||||
// is bookmarkable. Grid (root) and the ephemeral player are not mirrored (encode → null). The person
|
||||
// name is the whole tail after the first ":", so a name containing ":" round-trips.
|
||||
// — and the flat browse grid — are bookmarkable. Discover is the ROOT landing (E5 S4), so it (and the
|
||||
// ephemeral player) is not mirrored (encode → null); the grid gets `?v=browse` so "Browse all" is a
|
||||
// distinct, shareable URL. The person name is the whole tail after the first ":", so a name containing
|
||||
// ":" round-trips.
|
||||
export const PLEX_SUB_URL: SubviewUrl<Sub> = {
|
||||
key: "v",
|
||||
encode: (v) => {
|
||||
switch (v.kind) {
|
||||
case "grid":
|
||||
return "browse";
|
||||
case "show":
|
||||
return `show:${v.id}`;
|
||||
case "season":
|
||||
@@ -31,7 +36,7 @@ export const PLEX_SUB_URL: SubviewUrl<Sub> = {
|
||||
case "playlist":
|
||||
return `playlist:${v.id}`;
|
||||
default:
|
||||
return null; // grid + the ephemeral player are not URL-mirrored
|
||||
return null; // discover (root) + the ephemeral player are not URL-mirrored
|
||||
}
|
||||
},
|
||||
decode: (raw) => {
|
||||
@@ -39,6 +44,8 @@ export const PLEX_SUB_URL: SubviewUrl<Sub> = {
|
||||
const kind = i === -1 ? raw : raw.slice(0, i);
|
||||
const arg = i === -1 ? "" : raw.slice(i + 1);
|
||||
switch (kind) {
|
||||
case "browse":
|
||||
return { kind: "grid" };
|
||||
case "show":
|
||||
return arg ? { kind: "show", id: arg } : null;
|
||||
case "info":
|
||||
|
||||
Reference in New Issue
Block a user