docs(stalker): codify is_series cross-surface contract

This commit is contained in:
4gray committed 2026-07-20 21:49:36 +02:00
1 parent de445a013f
commit 1c3cc6c9fa
5 files changed
+126 -24

No files matched your search

+64
View File
@@ -0,0 +1,64 @@
---
name: stalker-portal
description: Repository guidance for Stalker/Ministra portal catalogs, VOD/series shapes, playback metadata, collections, EPG, and remote control.
---
# Stalker Portal
Use this skill when changing Stalker/Ministra routes, stores, catalog/detail
views, playback, favorites/recent activity, EPG, or remote control.
## Read First
- `docs/architecture/stalker-portal.md`
- `docs/architecture/stalker-epg.md` for ITV EPG work
- `docs/architecture/remote-control.md` for live remote-control work
## Ownership
- Feature UI: `libs/portal/stalker/feature/src/lib/`
- Store/API data access: `libs/portal/stalker/data-access/src/lib/`
- Electron requests: `apps/electron-backend/src/app/events/stalker.events.ts`
- Shared Stalker item normalization:
`libs/shared/interfaces/src/lib/stalker-item.normalizer.ts`
- Dashboard aggregation: `libs/workspace/dashboard/data-access/src/lib/`
Keep provider-specific API and normalization behavior in Stalker data access.
Keep shared portal layouts/utilities provider-neutral. Preserve full-portal
session auth and simple IPC request paths.
## `is_series` Cross-Surface Checklist
Treat VOD items with `is_series` as series across every downstream surface.
Do not stop after making the detail view render.
1. Accept portal flags `true`, `1`, and `'1'` through the existing normalizers.
Preserve all three modes: regular `/series`, embedded VOD `series[]`, and
lazy Ministra VOD `is_series`.
2. Build quick-start state through the shared series utility. Preserve
`labelKey`, `labelParams`, and `episodeLabel` when adapting it for Stalker;
translation parameters must reach the template.
3. Preserve `is_series` and the VOD origin in favorites/recent activity.
`extractStalkerItemType()` must normalize that activity to dashboard type
`series`.
4. Before either inline or external episode playback, persist the parent
`seriesXtreamId` plus resolved `seasonNumber` and `episodeNumber`. Keep
generated episode tracking IDs stable for lazy `is_series` episodes.
5. The dashboard reads saved playback positions; it must not infer episode
numbers from provider payloads. Legacy rows without season/episode metadata
remain badge-less until that episode is played again.
## Regression Coverage
- Series view/UI and playback handoff:
`pnpm nx test portal-stalker-feature`
- Stalker shape/store behavior:
`pnpm nx test portal-stalker-data-access`
- Dashboard classification and position lookup:
`pnpm nx test workspace-dashboard-data-access`
- Dashboard badge rendering when changed:
`pnpm nx test workspace-dashboard-feature`
For user-visible workflow changes, run the closest available E2E target. If no
fixture covers the affected portal shape, record that gap and perform the
strongest targeted unit/build validation available.
+5
View File
@@ -383,6 +383,11 @@ Key files:
Use when moving heavy database work off the main thread, adding worker-backed SQLite operations, or wiring loading/progress UI for Xtream and playlist DB flows.
File: `.codex/skills/iptvnator-sqlite-db-worker/SKILL.md`
- `stalker-portal`
Repository-specific guidance for Stalker/Ministra catalogs, all three VOD/series modes, cross-surface `is_series` behavior, playback metadata, collections, EPG, and remote control.
Use when changing Stalker routes, stores, detail views, playback, favorites/recent activity, EPG, or remote control.
File: `.codex/skills/stalker-portal/SKILL.md`
- `xtream-electron`
Repository-specific guidance for IPTVnator's Electron-first Xtream implementation, including feature/data-access boundaries, worker-backed DB flows, and Xtream loading/progress UX expectations.
Use when working on Xtream routes, store/data-source logic, or Electron-backed Xtream import/search/delete behavior.
+1
View File
@@ -780,6 +780,7 @@ engine` (restart required) or
- 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)
- Watch state derives from `inlinePlayback() !== null` only; external MPV/VLC playback keeps the browse layout. Esc and "Close player" exit to browse without navigation; the now-playing back arrow is route-level back (straight to the list via the host's `goBack()`)
- A successful external MPV/VLC episode launch immediately persists the selected episode as the latest playback-position entry and retargets the series CTA to `Play episode N`; real player telemetry overwrites that marker when available, so episode identity is reliable while exact external timestamps remain best-effort.
- Stalker preserves this contract for regular `/series`, embedded VOD `series[]`, and lazy Ministra VOD `is_series` items: quick-start translation parameters must reach the CTA, and inline/external episode handoffs must include the parent series id plus resolved season and episode numbers. This metadata lets the dashboard render the tracked S/E badge for VOD-backed series. Existing playback rows without it remain badge-less until the episode is played again.
- Hosts pass hero chips/meta/actions as `*appDetailTags`/`*appDetailMeta`/`*appDetailActions` templates; the shell stamps them into both the hero and the About block
- Seasons are tabs (`SeasonTabsComponent`, dropdown beyond 6 seasons) with auto-selection (playing episode's season → resume season → first) that fires the same `seasonSelected` lazy-load/enrichment hooks as manual clicks; grid/list episode view toggle persists to localStorage; season descriptions come from `get_series_info` (Xtream) or TMDB (Stalker)
- Dashboard hero/Continue Watching clicks for an Xtream series carry a one-shot resume target through the global-recent inline-detail handoff; after series metadata and playback positions load, the exact saved episode starts at its stored position. A failed positions load leaves the target unconsumed and the handoff detail-only, so a transient storage error never starts the episode from the beginning. Ordinary global-recent grid clicks remain detail-only.
+49 -21
View File
@@ -29,7 +29,8 @@ Stalker support covers:
## Routing Structure
Primary route tree lives in `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-feature.routes.ts`.
Primary route tree lives in
`libs/portal/stalker/feature/src/lib/stalker-feature.routes.ts`.
- `/stalker/:id/vod`
- `/stalker/:id/series`
@@ -45,30 +46,31 @@ Primary route tree lives in `/Users/4gray/Code/iptvnator/libs/portal/stalker/fea
1. Angular Stalker screens call methods/resources in `StalkerStore`.
2. `StalkerStore` builds request params based on selected content type and current view state.
3. Requests go through `DataService.sendIpcEvent(STALKER_REQUEST, ...)` or `StalkerSessionService` (full portal auth).
4. Electron main process handles `STALKER_REQUEST` in `/Users/4gray/Code/iptvnator/apps/electron-backend/src/app/events/stalker.events.ts`.
4. Electron main process handles `STALKER_REQUEST` in
`apps/electron-backend/src/app/events/stalker.events.ts`.
5. Axios calls Stalker `load.php` API with required headers/cookies and returns normalized payloads to renderer.
## Main UI Components
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-main-container.component.ts`
- `libs/portal/stalker/feature/src/lib/stalker-main-container.component.ts`
- Category + content layout for `vod` and `series`
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts`
- `libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts`
- ITV live playback, radio playback, channel/station navigation, EPG panel integration
- `/Users/4gray/Code/iptvnator/libs/ui/playback/src/lib/audio-player/audio-player.component.ts`
- `libs/ui/playback/src/lib/audio-player/audio-player.component.ts`
- Shared inline audio player used by M3U radio channels and Stalker radio stations
- `/Users/4gray/Code/iptvnator/libs/ui/components/src/lib/stalker-series-view/stalker-series-view.component.ts`
- `libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.ts`
- Season/episode UI for all Stalker series modes
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-favorites/stalker-favorites.component.ts`
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/recently-viewed/recently-viewed.component.ts`
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.ts`
- `libs/portal/stalker/feature/src/lib/stalker-favorites/stalker-favorites.component.ts`
- `libs/portal/stalker/feature/src/lib/recently-viewed/recently-viewed.component.ts`
- `libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.ts`
## Store and Data Flow
Stalker store is now feature-composed:
- Facade: `/Users/4gray/Code/iptvnator/libs/portal/stalker/data-access/src/lib/stalker.store.ts`
- Feature slices: `/Users/4gray/Code/iptvnator/libs/portal/stalker/data-access/src/lib/stores/features/*`
- Shared helpers: `/Users/4gray/Code/iptvnator/libs/portal/stalker/data-access/src/lib/*`
- Facade: `libs/portal/stalker/data-access/src/lib/stalker.store.ts`
- Feature slices: `libs/portal/stalker/data-access/src/lib/stores/features/*`
- Shared helpers: `libs/portal/stalker/data-access/src/lib/*`
Important store responsibilities:
@@ -185,6 +187,9 @@ Stalker has multiple real-world data shapes. The current implementation supports
- For unloaded VOD-series seasons, the CTA target label is derived from season
metadata and rendered as `SxxE01` until episode details are loaded.
- Uses unique generated tracking IDs for episode playback position compatibility.
- Quick-start actions preserve both their translation key and interpolation
parameters when adapted for the Stalker CTA. Dropping `labelParams` exposes
the raw `{{episode}}` placeholder.
Series inline playback behavior is shared across all three modes:
@@ -194,11 +199,27 @@ Series inline playback behavior is shared across all three modes:
- Inline series autoplay is enabled by default. On player EOF (`ended`), Stalker starts the next episode only when it already exists in the current season's mapped episode list.
- Autoplay and Next stop at the last episode of the current season. They do not jump to the next season and do not lazy-load an unloaded `is_series=1` season. Quick start remains the only flow that may load another VOD-series season before playback.
- Previous is disabled on the first episode of the current season and otherwise switches directly to the previous episode.
- Before either inline or external playback starts, the resolved content info
includes the parent `seriesXtreamId` and the mapped `seasonNumber` /
`episodeNumber`. Future playback-position rows therefore carry enough
metadata for workspace surfaces to render an episode badge. Existing rows
without those fields are intentionally not migrated and remain badge-less
until the episode is played again.
The VOD-series contract is cross-surface:
- Favorites and recently viewed records preserve the raw `is_series` flag and
VOD origin so reopening still uses the lazy Ministra resources.
- `extractStalkerItemType()` normalizes those activity records to dashboard
type `series`.
- 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.
Core decision logic and normalization are centralized in:
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/data-access/src/lib/stalker-vod.utils.ts`
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/data-access/src/lib/models/*.ts`
- `libs/portal/stalker/data-access/src/lib/stalker-vod.utils.ts`
- `libs/portal/stalker/data-access/src/lib/models/*.ts`
## Favorites and Recently Viewed
@@ -213,10 +234,10 @@ Current implementation is shared via Stalker-specific helpers:
Where this is used:
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-favorites/stalker-favorites.component.ts`
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/recently-viewed/recently-viewed.component.ts`
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.ts`
- `/Users/4gray/Code/iptvnator/libs/ui/components/src/lib/stalker-favorites-button/stalker-favorites-button.component.ts`
- `libs/portal/stalker/feature/src/lib/stalker-favorites/stalker-favorites.component.ts`
- `libs/portal/stalker/feature/src/lib/recently-viewed/recently-viewed.component.ts`
- `libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.ts`
- `libs/portal/stalker/feature/src/lib/stalker-favorites-button/stalker-favorites-button.component.ts`
Navigation rule to preserve:
@@ -264,7 +285,7 @@ Import rule:
Stalker live remote control is implemented in:
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts`
- `libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts`
Supported today:
@@ -299,9 +320,12 @@ This reduces duplicate UI logic across portal types and keeps compatibility beha
## Regression Coverage
Focused regression tests for Stalker VOD mode branching live in:
Focused regression tests for Stalker VOD mode branching and the cross-surface
series contract live in:
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/data-access/src/lib/stalker-vod.utils.spec.ts`
- `libs/portal/stalker/data-access/src/lib/stalker-vod.utils.spec.ts`
- `libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.spec.ts`
- `libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.spec.ts`
Covered scenarios include:
@@ -310,3 +334,7 @@ Covered scenarios include:
- VOD-backed series favorites keep VOD-series loading semantics when opened from
favorites/global favorites
- Favorite toggle helper path invokes the expected add/remove flow
- Quick-start episode labels interpolate their episode number
- Inline and external episode handoffs carry resolved season/episode metadata
- Dashboard activity classifies `is_series` VOD as series and resolves its
saved episode position
+7 -3
View File
@@ -93,10 +93,14 @@ Render rules:
2. It derives the dashboard surface via `computed()`:
1. `hero` — first item of `globalRecentItems()`.
2. `continueWatchingCards` — maps `globalRecentVodItems()` to movie/series
cover cards. Xtream playback positions are bulk-loaded per playlist so
cover cards. Portal playback positions are bulk-loaded per playlist so
hero and cards can show progress, remaining time, and series season/
episode badges. Series lookup uses keyed maps for both direct episode ids
and series ids; card renders must not scan the full playback-position map.
episode badges. This includes Stalker VOD activity normalized to series
through `is_series`. Series lookup uses keyed maps for both direct
episode ids and parent series ids; card renders must not scan the full
playback-position map. The badge uses saved `seasonNumber` /
`episodeNumber` metadata and does not infer it from provider payloads;
legacy rows without that metadata remain badge-less until replay.
Dashboard-originated Xtream series clicks also carry that exact episode
target through the global-recent inline-detail handoff. Once the series
metadata and playback positions load, the detail player consumes the