docs(downloads): document offline details flow

This commit is contained in:
4gray committed 2026-08-01 16:29:51 +02:00
1 parent ce0f563735
commit 1fe2692f77
24 files changed
+333 -35

No files matched your search

+4 -5
View File
@@ -3,8 +3,7 @@ type: feature
area: downloads
---
Downloads now separate active transfers from a cover-size-aware offline
library and group completed episodes by series. Missing files move to Needs
attention with a safe Download again action, while ready cards open provider
details and keep local Play explicit. Card source details now live in the
overflow menu.
Downloads now open movies and series in focused offline details with saved
metadata and optional TMDB enrichment. Series show only episodes available
locally, while View in portal opens the complete online catalog and provider
playback.
+31 -1
View File
@@ -491,7 +491,11 @@ See `docs/architecture/m3u-playlist-module.md` for complete documentation.
- Stalker portal: `/workspace/stalker/:id` (children: `itv`, `vod`, `radio`, `series`, `favorites`, `recent`, `search`, `actor/:personId`, `downloads`) — `libs/portal/stalker/feature/src/lib/stalker-feature.routes.ts`
- Global collections: `/workspace/global-favorites`, `/workspace/global-recent`
- Global search: `/workspace/search` (Electron-only; a guard redirects the PWA to `/workspace/sources`)
- Downloads: `/workspace/downloads`
- Downloads: `/workspace/downloads` with focused
`/workspace/downloads/:downloadId`; source-scoped equivalents are
`/workspace/xtreams/:id/downloads/:downloadId` and
`/workspace/stalker/:id/downloads/:downloadId`. Focused download details hide
the workspace context panel.
- Settings: `/workspace/settings` (`/settings` redirects there)
**Service Architecture** (Factory Pattern):
@@ -897,6 +901,32 @@ engine` (restart required) or
chrome, with browser-dependent subtitles. AirPlay, Cast, Document PiP, a PiP
keyboard shortcut, and Embedded MPV popup/native support are out of scope.
**Download Manager**:
- The desktop-only manager shares one global download store across the global,
Xtream-scoped, and Stalker-scoped routes. Completed movie and grouped-series
cards use the global Small/Medium/Large cover-grid tokens; missing completed
files move to Needs attention instead of remaining in Ready to watch.
- Ready movie and grouped-series cards open a focused local detail. Movies play
the finalized local file; series list only locally available episode rows and
every episode action targets its own downloaded file. Focused routes disable
route search and use `contextPanel: 'none'`.
- Downloads capture a versioned metadata snapshot from the rendered Xtream or
Stalker movie/episode detail at start time, including already-merged TMDB
fields. Legacy, sparse, stale, or wrong-language snapshots are safely
backfilled from row/provider metadata and optional TMDB enrichment when the
focused detail opens.
- `View in portal` resolves an exact source target and opens the provider's
normal detail in one-shot `provider-only` presentation. Provider playback and
the complete catalog remain available, while Offline/local/download actions
are hidden. This applies to Xtream movies/series and regular, embedded
`series[]`, and Ministra `is_series=1` Stalker flows.
- If a finalized file disappears while a focused detail is open, the
authoritative download list refreshes and returns to the manager. A failed
redirect leaves an actionable missing-file state with Back and Retry.
- Canonical contract: `docs/architecture/download-manager.md`; provider handoff:
`docs/architecture/portal-detail-navigation.md`.
**VOD/Series Detail Pages (two-state layout)**:
- Xtream and Stalker detail pages use the shared `PortalDetailShellComponent` (`libs/ui/components/src/lib/portal-detail-shell/`) with two states: **Browse** (hero with poster/metadata/actions, episodes below) and **Watch** (hero collapses with a ~300ms morph, the inline player takes the full content width, metadata moves to an About block below the episodes)
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Сезон {{season}}",
"SEASON_RANGE": "Сезоны {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Скачанные эпизоды",
"OFFLINE_DETAIL": {
"AVAILABLE": "Доступно офлайн",
"PLAY": "Воспроизвести офлайн",
"VIEW_IN_PORTAL": "Открыть в портале",
"VIEW_IN_PORTAL_UNAVAILABLE": "Контент больше недоступен в исходном портале",
"DOWNLOADED_EPISODES": "Скачано эпизодов: {{count}}",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Загрузка не найдена",
"NOT_FOUND_BODY": "Эта загрузка больше недоступна.",
"BACK": "Назад к загрузкам"
},
"ARIA": {
"OPEN": "Открыть {{title}}",
"POSTER_UNAVAILABLE": "Обложка для {{title}} недоступна",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+11
View File
@@ -1128,6 +1128,17 @@
"SEASON": "Season {{season}}",
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
"DOWNLOADED_EPISODES": "Downloaded episodes",
"OFFLINE_DETAIL": {
"AVAILABLE": "Available offline",
"PLAY": "Play offline",
"VIEW_IN_PORTAL": "View in portal",
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
"FILE_SIZE": "{{size}}",
"NOT_FOUND_TITLE": "Download not found",
"NOT_FOUND_BODY": "This download is no longer available.",
"BACK": "Back to Downloads"
},
"ARIA": {
"OPEN": "Open {{title}}",
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
+43 -29
View File
@@ -97,36 +97,45 @@ variants, contextual buttons, and theme-aware styling.
playlist scope when the page is opened under a source route. VOD and episode
detail views continue to render a paused download as an active Resume button
(`DownloadsService.isPaused()` / `resumeDownloadByContent()`). Artwork and
titles on completed movie and grouped-series cards open the provider detail
page; the explicit Play action starts the local file. A legacy standalone
episode without a usable series id stays directly playable because there is
no reliable detail route to open. Missing completed rows show `File missing`
with `Download again`; Play and Show in folder are withheld. A file-action
race that returns `File not found` refreshes the authoritative list before
pending state clears.
titles on completed movie and grouped-series cards open the focused offline
detail for that download; the explicit card Play action still starts the
local file. A legacy standalone episode without a usable series id stays
directly playable because there is no reliable series detail to build.
Missing completed rows show `File missing` under Needs attention with
`Download again`; Play and Show in folder are withheld. A file-action race
that returns `File not found` refreshes the authoritative list and returns
the user from focused details to the manager.
## Offline detail playback
## Focused offline details
- A completed VOD with an available finalized file renders an Offline tag on
both rich and fallback detail shells. Its primary action plays the downloaded
file, while a neutral
“Play from this source” action, when a usable provider source is available,
preserves the existing provider, pinned-source, resume, and restart path.
- A completed row whose file is missing does not advertise Offline or local
playback on details; the provider playback path remains primary.
- Managed MPV/VLC state remains authoritative: Opening disables conflicting
playback actions, and Stop closes the matched external session before any
local or provider choice can run.
- Download rows carry a versioned, display-only metadata snapshot, so the
offline detail resolver can work without the owning Xtream or Stalker
provider. Sparse, stale, or wrong-language snapshots are refreshed from the
typed provider catalog and optional TMDB enrichment. A successful refresh
advances its freshness timestamp; transient TMDB failures may retain improved
provider fields without advancing the timestamp or language, so a later visit
retries. Legitimately sparse successful results are remembered in a bounded,
language-scoped runtime throttle for the same TTL, preventing persistence
reloads from starting an enrichment loop. Concurrent movie and grouped-series
refreshes are request-ordered so only the newest generation may persist.
- Ready movies and grouped series open a local-focused detail view rather than
a provider catalog page. A movie exposes local Play and Show in folder. A
series projects only completed episode rows whose finalized files are still
available, grouped into seasons; every episode Play and Show in folder action
targets that row's local file. The provider's other seasons and episodes are
deliberately absent from this view.
- `View in portal` resolves the exact Xtream or Stalker source item before the
action becomes available. It then leaves the offline view and opens the
provider's normal detail host in explicit `provider-only` presentation: the
complete online catalog and provider playback remain available, while local,
Offline, and download actions are hidden. Regular provider navigation does
not inherit this one-shot presentation state.
- A completed row that is no longer locally available is never rendered as a
ready offline detail. Direct or stale detail URLs return to the manager; if
navigation fails, the detail shell shows the missing-file error with Back and
Retry. Invalid or removed download ids render a focused not-found state.
- Movie and episode downloads capture a versioned, display-only metadata
snapshot at start time from the already-rendered Xtream or Stalker detail,
including any TMDB fields already present. The snapshot keeps provider
identity/category separately from presentation metadata and lets the offline
view render even when the source portal is unavailable.
- Legacy rows and sparse, stale, or wrong-language snapshots are backfilled
when focused details open: row metadata supplies a safe local fallback,
provider data is merged when it can be resolved, and opt-in TMDB enrichment
uses the same app language and merge rules as provider details. Successful
refreshes persist to the representative row; transient failures keep safe
improvements without falsely marking the snapshot fresh, and concurrent
refreshes are request-ordered so only the latest generation may write.
- Snapshot artwork accepts only safe HTTP(S) image URLs. Renderer normalization
and main-process persistence independently reject credential-shaped path and
query keys, including `username`, so provider credentials cannot be cached in
@@ -144,7 +153,12 @@ variants, contextual buttons, and theme-aware styling.
`/workspace/xtreams/:id/downloads` and
`/workspace/stalker/:id/downloads`. All three routes render the same global
store; the `:id` routes derive a view-only playlist scope.
- Each scope exposes an offline detail child at `downloads/:downloadId`.
- Each scope exposes the same focused child route:
`/workspace/downloads/:downloadId`,
`/workspace/xtreams/:id/downloads/:downloadId`, and
`/workspace/stalker/:id/downloads/:downloadId`. The workspace shell treats
all three as focused content: route search is disabled and the context panel
is `none`, so provider categories are not shown beside a local-only item.
- Downloads navigation is data-driven: `libs/portal/shared/util/src/lib/navigation/portal-rail-links.ts` emits a `downloads` section link (`path: [...root, 'downloads']`) for both portals, so they reuse the same download page.
## Queuing, persistence, and UX notes
@@ -29,6 +29,15 @@ Related:
opening the series from the collection grid itself remains detail-only. If the
positions load fails, the target stays unconsumed and the handoff degrades to
detail-only rather than starting the episode at offset zero.
- Ready Download Manager cards open one of the three focused
`downloads/:downloadId` routes. These local details hide the workspace
context panel, play only finalized local files, and show only locally
available episodes for a series.
- `View in portal` is the explicit bridge from a focused offline detail to the
source catalog. It resolves an exact provider target and passes the one-shot
`detailPresentation: 'provider-only'` navigation state. The destination keeps
provider playback and the complete provider episode catalog, but hides
Offline/local/download presentation.
- Do not force both portals into the same browse/detail behavior unless the full
portal detail architecture is being changed.
@@ -95,6 +104,19 @@ Search behavior to preserve:
- Selecting an Xtream item from search should still navigate to the canonical
Xtream content type/category/item route when the item is not a live stream.
Download handoff behavior:
- An Xtream movie handoff requires its exact VOD category and item route; a
series handoff requires its exact series category and item route. The
download snapshot's provider category is preferred, with the typed catalog
used to recover it for legacy rows. The action stays unavailable rather than
opening a bare collection route when the target cannot be resolved.
- Provider-only mode is read by the normal VOD/series detail components. It
preserves provider Play/Resume and every provider episode, while suppressing
the local Offline state and download controls. Reusing the route component
for another item must clear the mode unless that navigation explicitly
carries the marker.
## Stalker
Stalker details are represented by store state and inline detail rendering on the current screen.
@@ -135,6 +157,18 @@ Behavior to preserve:
- Back from the collection-owned detail should restore the previous collection
tab and scope instead of resetting the collection screen to its defaults.
Download handoff behavior:
- `View in portal` preserves Stalker's inline/store-state architecture. The
download target carries `openStalkerItem` into the normal VOD or series
category host instead of inventing a canonical item route.
- The provider-only marker follows the exact selected item through regular
series, embedded VOD `series[]`, and lazy Ministra VOD `is_series=1` flows.
All provider seasons/episodes and provider playback remain available, while
the shared VOD or series UI suppresses local Offline and download controls.
Consuming the marker is identity-scoped so a later ordinary item open returns
to normal provider presentation.
## Decision Rule For Future Changes
When deciding how a favorites/recent/search click should behave:
+12
View File
@@ -41,6 +41,8 @@ Primary route tree lives in
- `/stalker/:id/search`
- `/stalker/:id/actor/:personId`
- `/stalker/:id/downloads` (shared `DownloadsComponent` from `@iptvnator/portal/downloads/feature`)
- `/stalker/:id/downloads/:downloadId` (focused local movie/series detail with
no category context panel)
## Runtime Architecture
@@ -361,6 +363,16 @@ The VOD-series contract is cross-surface:
- The dashboard resolves episode progress by the parent `seriesXtreamId` and
renders the saved season/episode metadata. It does not infer episode numbers
from provider payloads.
- Episode downloads from regular series, embedded VOD `series[]`, and lazy
Ministra VOD `is_series=1` capture the rendered parent/episode metadata and
provider category in a versioned offline snapshot. The focused Download
Manager detail uses only locally available episode rows; it does not reuse
the provider season resource as an offline availability list.
- `View in portal` hands the stored Stalker identity back to the normal
category/inline detail host with identity-scoped provider-only presentation.
All three series modes retain their complete provider seasons, episodes, and
provider playback, while Offline/local/download controls stay hidden for
that handoff. A normal Stalker item open remains unchanged.
Core decision logic and normalization are centralized in: