mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
Merge origin/master into perf/layout-shift-audit and move the gate list out of the component
Resolves the import conflict with the rotating dashboard hero (#1725), which moved the hero TMDB service out of the rails component. Addresses the Codex review on #1738: the 92-line rail-gate initializer grew the grandfathered rails component from 703 to 795 lines. The rail order and conditions now live in dashboard-rail-skeletons.ts behind a structural host interface, the component keeps a single field (+2 lines over master), and a spec covers the dashboard-specific rules (live favorites suppressed by the recent-live rail below it, sources gated on playlists, Xtream on having an Xtream playlist, disabled rails ignored). The seeded-profile timeline on the merged branch, with the new hero, is still 0.0004 on each of three reloads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
6300a1f66a
68 files changed
+3692
-1162
No files matched your search
@@ -733,15 +733,15 @@ since shipped.)
|
||||
does not latch and retries instead. Same gating as trending: TMDB
|
||||
opt-in + Electron DB worker, deferred behind the dashboard's own data.
|
||||
- **Hero extras**: `DashboardHeroTmdbService`
|
||||
(`libs/workspace/dashboard/feature`) patches the hero card with a TMDB
|
||||
backdrop (when the activity row has none), a rating badge and up to two
|
||||
genre chips — resolved through the enrichment facade, so items already
|
||||
opened in a detail view come from the SQLite cache without network.
|
||||
Results are memoized per lookup identity for the session. The hero renders
|
||||
immediately from provider data; extras appear when resolved. Series
|
||||
heroes additionally show the tracked "S{n}·E{n}" badge from the playback
|
||||
position (no TMDB involved); the watch-progress bar is limited to
|
||||
movie/series heroes.
|
||||
(`libs/workspace/dashboard/feature`) patches each movie/series hero slide
|
||||
with a TMDB backdrop (when the activity row has none), a rating badge, up
|
||||
to two genre chips, the overview and the release/first-air year — resolved
|
||||
through the enrichment facade, so items already opened in a detail view
|
||||
come from the SQLite cache without network. Results are memoized per
|
||||
lookup identity for the session. Slides render immediately from provider
|
||||
data; extras appear when resolved and disappear when TMDB is turned off.
|
||||
Series slides additionally show the tracked "S{n}·E{n}" badge from the
|
||||
playback position (no TMDB involved). Live slides never query TMDB.
|
||||
|
||||
The query is built to **match what the detail view searched with**, not
|
||||
just what the card displays. A title alone is weaker identity than the
|
||||
|
||||
@@ -37,7 +37,7 @@ Core implementation:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Hero — Continue Watching (most recent item) │
|
||||
│ Hero — rotating cinematic banner (resume · live · discovery) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ Continue Watching · See all → │
|
||||
│ [poster][poster][poster][poster] →→ │
|
||||
@@ -73,12 +73,14 @@ Render rules:
|
||||
slow rail does not hide already available content.
|
||||
2. `hasPlaylists() === false` → render `<app-empty-state [type]="'welcome-dashboard'">`
|
||||
full-bleed. All rails and the hero are skipped.
|
||||
3. `hero()` = `globalRecentItems()[0]`. If present, render the hero panel.
|
||||
3. The hero (`lib-dashboard-hero`) renders when it has at least one slide;
|
||||
see [Cinematic Hero](#cinematic-hero). It shows its own skeleton while
|
||||
the first history load runs.
|
||||
4. Each rail is emitted via `@if (cards.length > 0)`. Empty rails are hidden
|
||||
— there is no "empty widget" placeholder.
|
||||
5. The continue-watching hero prefers a stored Xtream `backdrop_url`; when it
|
||||
is missing the UI falls back to a blurred poster treatment instead of
|
||||
showing a flat panel.
|
||||
5. Hero slides prefer a stored `backdrop_url`, then the TMDB backdrop; when
|
||||
both are missing the poster becomes a blurred wash plus key art on the
|
||||
right instead of a flat panel.
|
||||
6. Live favorites are promoted into their own live rail; movie/series
|
||||
favorites render in a separate `Favorite movies & series` rail
|
||||
(`favoriteMoviesAndSeriesCards`, `data-test-id="dashboard-favorite-vod-rail"`,
|
||||
@@ -89,6 +91,47 @@ Render rules:
|
||||
favorites. This avoids first-paint partial counts such as a single Stalker
|
||||
favorite appearing before M3U favorites finish resolving.
|
||||
|
||||
## Cinematic Hero
|
||||
|
||||
`DashboardHeroComponent` renders a full-bleed banner: it cancels the page's
|
||||
`--dashboard-gutter`/top padding and the centred `--dashboard-max-width`
|
||||
(the page host is the `dashboard` inline-size container), keeps
|
||||
`clamp(320px, 42vh, 520px)` so the first rail starts above the fold, and uses
|
||||
`--app-content-bg` as its scrim so it dissolves into the page in both themes.
|
||||
|
||||
Slides (`pickDashboardHeroSources`, at most four, stable order, each title
|
||||
once):
|
||||
|
||||
1. the newest unfinished movie/series (`isPortalPlaybackWatched` rows skip);
|
||||
2. a live channel with a programme on air — the first of
|
||||
`selectDashboardHeroLiveCandidates` (up to three favourites, then two
|
||||
recently watched channels) whose EPG answer has a title;
|
||||
3. one favourite movie/series and one Xtream recently-added title;
|
||||
4. remaining places round-robin over the next items of those lists;
|
||||
5. only when nothing qualifies, the newest history row of any kind (a
|
||||
detail action: it can be a finished title).
|
||||
|
||||
While live candidates exist but none has answered yet, one place stays
|
||||
reserved for the live slide, so its late arrival never evicts a slide the
|
||||
user may be viewing.
|
||||
|
||||
The live candidates are derived and pinned by `DashboardLiveEpgPresenter`
|
||||
itself (XMLTV lookup and portal queue), independent of the live rails, so the
|
||||
slide works with those rails hidden. Actions: a resume slide keeps the resume
|
||||
handoff and adds a detail-only "Details" when a series episode can resume;
|
||||
discovery slides open the detail page; live slides open the channel. TMDB
|
||||
extras (backdrop, rating, genres, overview, year) come from
|
||||
`DashboardHeroTmdbService` per featured title and vanish when TMDB is off.
|
||||
|
||||
Rotation is the active dot's CSS fill animation (8 s); its `animationend`
|
||||
advances. Hover, focus inside the hero and the pause button pause it; an
|
||||
explicit Play clears the hover/focus pause until they re-arm; under
|
||||
`prefers-reduced-motion` nothing auto-advances. The active slide is tracked
|
||||
by id, so a late live slide never moves the user off the current one. Test
|
||||
hooks: `dashboard-hero`, `dashboard-hero-slide` (`data-hero-kind`),
|
||||
`dashboard-hero-dot`, `dashboard-hero-pause`,
|
||||
`dashboard-hero-primary-action`, `dashboard-hero-secondary-action`.
|
||||
|
||||
## Rail Contract
|
||||
|
||||
`DashboardRailComponent` is purely presentational:
|
||||
@@ -109,7 +152,8 @@ Render rules:
|
||||
|
||||
1. `WorkspaceDashboardRailsComponent` injects `DashboardDataService`.
|
||||
2. It derives the dashboard surface via `computed()`:
|
||||
1. `hero` — first item of `globalRecentItems()`.
|
||||
1. The hero slides — built by `DashboardHeroSlidesPresenter`, see
|
||||
[Cinematic Hero](#cinematic-hero).
|
||||
2. `continueWatchingCards` — maps `globalRecentVodItems()` to movie/series
|
||||
cover cards. Portal playback positions are bulk-loaded per playlist so
|
||||
hero and cards can show progress, remaining time, and series season/
|
||||
@@ -161,12 +205,12 @@ Render rules:
|
||||
card counts as visible. Cards that leave the list are reported gone
|
||||
at once.
|
||||
- `DashboardPortalLiveEpgPresenter` (component-provided) unions the
|
||||
visible keys of both rails with the pinned hero key and calls
|
||||
visible keys of both rails with the pinned hero keys and calls
|
||||
`DashboardPortalLiveEpgService.sync()` with exactly those entries —
|
||||
on every change, on the 30 s tick, and on a display-offset change.
|
||||
It is reached through `DashboardLiveEpgPresenter`, which derives the
|
||||
portal rows itself from the enabled rails and pins the hero, so the
|
||||
page component only forwards what a rail can see. The queue lives in
|
||||
portal rows itself from the enabled rails and pins the hero's live
|
||||
candidates, so the page component only forwards what a rail can see. The queue lives in
|
||||
the root service, so leaving the dashboard hands the wanted set back
|
||||
(`sync([])` on destroy); otherwise the queue would keep asking for
|
||||
cards on a page that is gone.
|
||||
|
||||
Reference in new issue
Block a user