docs(skills): align provider and UI guidance

This commit is contained in:
4gray committed 2026-07-30 12:36:44 +02:00
1 parent fa6f988dd3
commit 543d3fd8c7
10 files changed
+298 -150

No files matched your search

+37 -18
View File
@@ -1,31 +1,50 @@
---
name: iptvnator-theme-style
description: Theme architecture, design tokens, shared SCSS library, portal header/sidebar patterns, Electron drag regions, and cross-portal style consistency.
description: Use when changing IPTVnator SCSS tokens, shared layout mixins, portal headers, sidebars, detail views, Electron drag regions, or cross-portal visual consistency.
---
# IPTVnator Theme Style
Use this skill when changing SCSS tokens, shared layout mixins, portal headers, sidebars, detail views, or Electron draggable regions.
## Canonical Sources
## Shared Style Sources
- Theme contexts and app tokens: `apps/web/src/m3-theme.scss`
- Shared forwarding inventory: `libs/ui/styles/_index.scss`
- Shared partials: `libs/ui/styles/_portal-layout.scss`,
`libs/ui/styles/_content-grid.scss`, `libs/ui/styles/_portal-sidebar.scss`,
`libs/ui/styles/_panel-header.scss`, `libs/ui/styles/_detail-view.scss`, and
`libs/ui/styles/_detail-view-actions.scss`
- UI policy and migration debt: `docs/architecture/iptvnator-ui-guidelines.md`
- `libs/ui/styles/_index.scss`
- `libs/ui/styles/_portal-layout.scss`
- `libs/ui/styles/_content-grid.scss`
- `libs/ui/styles/_portal-sidebar.scss`
- `libs/ui/styles/_panel-header.scss`
- `libs/ui/styles/_detail-view-actions.scss`
- `docs/architecture/iptvnator-ui-guidelines.md`
The index is a barrel, not a configured Sass include path. Production
consumers currently use relative `@use` paths to the needed partial.
## Rules
## Token Boundary
- Prefer shared SCSS mixins and Material system tokens over local hard-coded colors.
- Keep Electron drag behavior explicit: interactive controls need `app-region: no-drag`.
- Do not duplicate large SCSS files across feature libraries; extract a shared partial or mixin.
- Use relative SCSS imports only when no stable shared entrypoint exists.
- Check Xtream, Stalker, M3U, and workspace views for cross-portal style drift when editing shared patterns.
- App-owned surfaces, text, separators, hover states, selections, and provider
accents use `--app-*` tokens from `m3-theme.scss`.
- Use `--app-selection-on-color` for foregrounds placed on the selection
accent; do not assume white has enough contrast in both themes.
- Angular Material mixins and Material-component overrides may use Material
tokens. Outside Material-owned components, use a `--mat-sys-*` token only
after proving it is emitted in both light and dark contexts and supplying a
real app-token or literal fallback.
- Local semantic status colors are acceptable. Existing hard-coded layout,
selection, and EPG surface colors are migration debt, not precedent.
## Shared Layout Rules
- Extend the matching partial instead of copying a large provider stylesheet.
Provider-neutral services and UI use the existing shared data-access/UI
libraries; repeated visual structure belongs in a shared Sass partial.
Portal-shared consumers use their existing forwarding style modules instead
of copied SCSS.
- In an Electron drag region, every interactive descendant—buttons, links,
inputs, overlays, and resize handles—must explicitly use
`app-region: no-drag`.
- A shared change must be checked across M3U, Xtream, Stalker, workspace,
portal catalog/shared UI, and unified collections where relevant.
## Validation
- Run lint/tests for the affected UI project.
- Manually inspect the changed screen in light and dark themes for layout, selection, and drag-region regressions.
Run the affected consumer's Nx lint/test/build target. Inspect light and dark
themes, selected/hover states, and Electron title-bar drag/no-drag behavior.
+31 -16
View File
@@ -1,29 +1,44 @@
---
name: iptvnator-ui-design
description: Repository-specific UI design guidance for IPTVnator channel rows, EPG views, settings surfaces, shared selection styles, and light/dark theme consistency.
description: Use when changing user-visible Angular UI in IPTVnator, especially channel rows or lists, EPG views, settings and playlist surfaces, selection states, shared portal components, or light/dark styling.
---
# IPTVnator UI Design
Use this skill when changing user-visible Angular UI in the IPTVnator app, especially channel lists, EPG panels, settings, playlist surfaces, and portal shared components.
## Inspect First
## Principles
- Policy: `docs/architecture/iptvnator-ui-guidelines.md`
- Theme and navigation: `apps/web/src/m3-theme.scss`,
`apps/web/src/nav-list.scss`
- Channel row: `libs/ui/components/src/lib/channel-list-container/channel-list-item/`
- Shared EPG timeline/list: `libs/ui/epg/src/lib/epg-timeline/`,
`libs/ui/epg/src/lib/epg-list-view/`
- Prefer dense, scannable application UI over marketing-style layouts.
- Preserve existing Material 3 token usage and light/dark theme behavior.
- Keep repeated selection states aligned with existing `--app-selection-*` tokens.
- Use existing shared UI components before adding local one-off markup.
- Avoid large visual rewrites in behavior-focused changes.
Before adding local markup, inspect `@iptvnator/ui/components`,
`@iptvnator/ui/epg`, `@iptvnator/ui/playback`,
`@iptvnator/ui/shared-portals`, `@iptvnator/portal/shared/ui`, and
`@iptvnator/playlist/shared/ui`. Stateful collection loading, persistence, and
cross-provider orchestration belong in `@iptvnator/portal/shared/data-access`,
not UI or util.
## Checklist
## Design Contract
1. Inspect the nearest existing component and shared UI library before editing.
2. Check both light and dark theme styles when touching colors, borders, hover states, or selection states.
3. Keep text and controls within fixed-width rows from resizing the layout.
4. Prefer existing components from `@iptvnator/ui/components`, `@iptvnator/portal/shared/ui`, and `@iptvnator/ui/epg`.
5. Add or update focused component tests for changed interaction states.
- Prefer dense, scannable application UI. Behavior-only work must not include
an opportunistic visual rewrite.
- App chrome uses app-owned selection, surface, and text tokens in both themes.
Do not extend the known hard-coded EPG/surface styling debt.
- Dense rows use minimum dimensions rather than a fixed width: the text column
needs `min-width: 0` plus ellipsis, while logos and trailing actions use
`flex-shrink: 0`.
- Assign one scroll owner per pane. Preserve sticky controls, keyboard/focus
feedback, and loading, empty, error, disabled, hover, and selected states.
- Providers supply controlled data to shared EPG timeline/list/panel components
instead of rebuilding those views.
- Shared changes require checking every affected M3U, Xtream, Stalker,
workspace, and collection consumer in light and dark themes.
## Validation
- Run the affected Nx project test target.
- For visible workflow changes, run the closest Playwright E2E target or manually verify through Electron CDP as documented in `AGENTS.md`.
Run the focused component/unit target and the closest Playwright workflow for a
visible change. Electron CDP is a fallback for Electron-only gaps, not a
replacement for an available E2E flow. Record any uncovered visual state.
+52 -47
View File
@@ -1,66 +1,71 @@
---
name: stalker-portal
description: Repository guidance for Stalker/Ministra portal catalogs, VOD/series shapes, playback metadata, collections, EPG, and remote control.
description: Use when changing Stalker or Ministra routes, stores, catalog or series shapes, playback progress, favorites and recent items, EPG, or 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
- `docs/architecture/stalker-epg.md` for ITV EPG
- `docs/architecture/remote-control.md` for live remote control
## 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/`
- Routed UI: `libs/portal/stalker/feature/src/lib/`
- API, session, store, and normalization:
`libs/portal/stalker/data-access/src/lib/`
- Electron transport: `apps/electron-backend/src/app/events/stalker.events.ts`
- Provider-neutral collections: `libs/portal/shared/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.
Keep Stalker request and shape rules in Stalker data access. Shared portal UI
must remain provider-neutral.
## `is_series` Cross-Surface Checklist
## Series Contract
Treat VOD items with `is_series` as series across every downstream surface.
Do not stop after making the detail view render.
Inside Stalker portal code, `isStalkerSeriesFlag()` is the sole flag
interpretation and accepts exactly `true`, `1`, and `'1'`. The activity
normalizer in shared interfaces remains dependency-neutral and preserves the
same closed set for favorites/recent and dashboard classification. Preserve
all three modes: regular `/series`, VOD with embedded `series[]`, and lazy
Ministra VOD `is_series`.
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. When
`season_number` is absent, derive the coordinate from the same naturally
ordered season list used by quick start; do not default every season to 1.
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.
Favorites/recent preserve the raw flag and VOD origin so reopening still uses
the correct lazy or embedded mode. Keep quick-start translation parameters and
the naturally ordered season fallback when `season_number` is absent.
## Regression Coverage
Lazy episodes use a deterministic tracking ID scoped by parent series,
provider episode, season key, and episode number. `legacyTrackingId` is only a
guarded compatibility alias. Reconciliation is limited to the current parent
series and optional matching season/episode metadata; an exact scoped row
always wins the resolved display position, while a compatible legacy row may
remain tracked only for cleanup. The scoped ID is the in-memory key. At the
strict migration boundary, save the scoped row before clearing a confirmed
legacy row, and keep legacy progress when the save fails.
- 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`
Before inline or external handoff, attach parent `seriesXtreamId` and resolved
season/episode numbers. Keep them on subsequent position writes.
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.
## Live Contract
- Start bulk ITV EPG eagerly once channel rows exist. Rows read the bulk cache;
only the active channel may fall back to `get_short_epg`.
- Radio skips EPG and external players, preserves live collection identity
with `radio: 'true'`, and uses the shared inline audio player.
## Validation
Run:
- `pnpm nx test shared-interfaces`
- `pnpm nx test portal-stalker-data-access`
- `pnpm nx test portal-stalker-feature`
- the affected `portal-shared-data-access` / `portal-shared-ui` target for
collection or radio behavior
- the affected `workspace-dashboard-data-access` and
`workspace-dashboard-feature` test targets
For the user workflow, run
`pnpm nx run web-e2e:e2e-ci--src/stalker.e2e.ts` or document the missing
fixture and strongest focused coverage.
+51 -17
View File
@@ -1,30 +1,64 @@
---
name: xtream-electron
description: IPTVnator's Electron-first Xtream implementation, including feature/data-access boundaries, worker-backed DB flows, and Xtream loading/progress UX.
description: Use when changing Xtream routes, Signal Store or data sources, content identity, SQLite-backed import, search or delete, sparse VOD playback, or Electron and PWA behavior.
---
# Xtream Electron
Use this skill when working on Xtream routes, stores, data sources, import/search/delete behavior, or Electron-backed Xtream playback and persistence.
## Read First
## Key Areas
- `docs/architecture/xtream-portal-compatibility.md`
- `docs/architecture/portal-detail-navigation.md`
- `docs/architecture/sqlite-db-worker.md`
- `docs/architecture/vod-multi-source.md`
- Feature UI: `libs/portal/xtream/feature/src/lib/`
- Data access: `libs/portal/xtream/data-access/src/lib/`
- Shared portal utilities: `libs/portal/shared/util/src/lib/`
- Shared portal UI: `libs/portal/shared/ui/src/lib/`
- Electron DB events: `apps/electron-backend/src/app/events/database/`
- DB worker operations: `apps/electron-backend/src/app/database/operations/`
## Ownership And Runtime
## Rules
Routed screens live in `libs/portal/xtream/feature`; API, cache, Signal Store,
and data sources in `libs/portal/xtream/data-access`; collection services and
reusable multi-source discovery/resolution in
`libs/portal/shared/data-access`; reusable views in
`libs/portal/shared/ui`; pure contracts/helpers only in
`libs/portal/shared/util`. Screen-session multi-source orchestration stays in
the Xtream feature.
- Keep provider-specific API/cache behavior in `portal/xtream/data-access`.
- Keep reusable layout and collection UI in `portal/shared/ui` or `portal/shared/util`.
- Prefer worker-backed DB operations for large imports, global search, delete, and restore.
- Preserve request cancellation and progress reporting for long imports.
- Validate both PWA and Electron data-source paths when changing Xtream APIs.
Select `ElectronXtreamDataSource` only through
`RuntimeCapabilitiesService.supportsXtreamSqliteDataSource`; otherwise use the
PWA data source. A generic `window.electron` check is not the capability
contract; existing favorites/recent branches that still use one are migration
debt, not precedent.
## Data And Navigation Contracts
- Xtream identity is playlist + normalized content type + provider
`xtream_id`; collection keys include type and ID. Never resolve colliding
live/movie/series IDs by number alone. Distinguish SQLite row IDs from
provider `xtream_id` / `stream_id` / `series_id`.
- Browse and search use canonical item routes. Favorites/recent use
collection-owned inline detail. Preserve enough provider/route identity to
recover hidden categories without sending a local database ID to the API.
- Sparse VOD publishes the fallback selection first and recovers its provider
category in the background. A playable source is one complete positive
stream-ID + extension pair; never combine fields from incomplete candidates.
- VOD multi-source is capability-gated, owner-scoped, Electron-only
Xtream-to-Xtream movie behavior. Its reusable core belongs in portal shared
data access; the screen session/host belongs in the Xtream feature.
`XtreamStore` composes portal, content, selection, search, EPG, player,
favorites, recent items, and playback positions.
On the SQLite path, large import/search/delete/restore work remains
worker-backed. Renderer-visible DB progress and cancellation use
`operationId`. Xtream API requests use `requestId`, import sessions use
`sessionId`, and the DB worker also has an internal transport request ID; never
conflate any of them.
## Validation
- Run `pnpm nx test portal-xtream-data-access` and `pnpm nx test portal-xtream-feature` for store/UI changes.
- Run `pnpm nx run web-e2e:e2e-ci--src/xtream.e2e.ts` or the closest Electron E2E target for user-visible Xtream workflow changes.
Run both data-source specs plus affected Xtream store/feature tests. Use the
closest atomized flow:
- `web-e2e:e2e-ci--src/xtream.e2e.ts`
- `electron-backend-e2e:e2e-ci--src/xtream-responsiveness.e2e.ts`
- `electron-backend-e2e:e2e-ci--src/xtream-vod-details.e2e.ts`
- `electron-backend-e2e:e2e-ci--src/vod-multi-source.e2e.ts`
+20 -16
View File
@@ -289,7 +289,7 @@ This is an Nx monorepo with the following structure:
- **portal/stalker/{data-access,feature}** - StalkerStore and routed Stalker components
- **portal/catalog/feature** - Portal catalog UI
- **portal/downloads/feature** - Download manager UI
- **portal/shared/{data-access,ui,util}** - Cross-portal shared code (incl. the VOD multi-source discovery/resolve/ranking layer in `data-access/src/lib/multi-source/`)
- **portal/shared/{data-access,ui,util}** - Cross-portal shared code: stateful collection services and VOD multi-source discovery/resolve/ranking live in `data-access`; reusable views live in `ui`; `util` is for pure contracts/helpers
- **services** - Abstract DataService contract and shared app services (incl. the TMDB metadata enrichment module in `lib/tmdb/`)
- **shared/interfaces** - TypeScript interfaces and types (incl. `ElectronBridgeApi`)
- **shared/logging** - Dependency-free structured redaction for diagnostic logs
@@ -332,14 +332,14 @@ The Xtream Codes module uses NgRx Signal Store with a layered architecture:
│ (Composes feature stores, unified API) │
└─────────────────────────────────────────────────────────────────┘
│
┌────────────┬────────────┼────────────┬────────────┐
▼ ▼ ▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
│ withPortal│ │withContent │ │withSelection│ │ withSearch │ │ withPlayer │
└────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘
│ │ │
└───────────────────────────┼──────────────┘
▼
▼
┌─────────────────────────────────────────────────────────────────┐
│ withPortal · withContent · withSelection · withSearch · withEpg │
│ withPlayer · withFavorites · withRecentItems │
│ withPlaybackPositions │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ DATA SOURCE LAYER │
│ IXtreamDataSource │
@@ -391,15 +391,19 @@ Key patterns:
- **Feature stores**: Each `with*.feature.ts` uses `signalStoreFeature()` for focused functionality
- **Facade pattern**: `XtreamStore` composes all features, maintaining backward compatibility
- **Data source abstraction**: `IXtreamDataSource` interface with environment-specific implementations
- **Factory injection**: `provideXtreamDataSource()` selects Electron or PWA implementation at runtime
- **Data source abstraction**: `IXtreamDataSource` has SQLite-backed and
API/in-memory implementations
- **Factory injection**: `provideXtreamDataSource()` selects
`ElectronXtreamDataSource` only when
`RuntimeCapabilitiesService.supportsXtreamSqliteDataSource`; otherwise it
selects `PwaXtreamDataSource`
Data strategies by environment:
Xtream data strategies by runtime capability:
| Environment | Strategy |
| ------------ | ------------------------------------------------------- |
| **Electron** | DB-first: Check DB → fetch API if missing → cache to DB |
| **PWA** | API-only: Always fetch from API, store in memory |
| Capability | Strategy |
| ---------- | -------- |
| **Complete Xtream SQLite bridge** | DB-first: check DB → fetch API if missing → cache to DB |
| **Bridge unavailable** | API-only: fetch from API and keep session data in memory |
**M3U Playlist Module Architecture**:
+47 -16
View File
@@ -32,6 +32,10 @@ Use it when changing existing views or introducing new list-based UI in the work
`libs/ui/epg/src/lib/epg-timeline/epg-timeline.component.html`
- Shared EPG timeline styles:
`libs/ui/epg/src/lib/epg-timeline/epg-timeline.component.scss`
- Shared EPG list:
`libs/ui/epg/src/lib/epg-list-view/epg-list-view.component.ts`
- Shared EPG list styles:
`libs/ui/epg/src/lib/epg-list-view/epg-list-view.component.scss`
- Shared list selection style:
`apps/web/src/nav-list.scss`
- Theme tokens:
@@ -46,6 +50,7 @@ Use it when changing existing views or introducing new list-based UI in the work
These tokens are the base for interactive emphasis:
- `--app-selection-color`
- `--app-selection-on-color`
- `--app-selection-surface`
- `--app-selection-surface-strong`
- `--app-selection-border`
@@ -61,16 +66,16 @@ in `apps/web/src/m3-theme.scss`):
- `--app-on-surface` — primary text
- `--app-eyebrow-color` — secondary/muted text
**Do not reach for `--mat-sys-*` surface tokens.** Angular Material's system
tokens are *not* emitted globally in this app — only `--mat-sys-on-surface`
exists. `var(--mat-sys-surface-container-high)` and friends resolve to nothing,
which usually goes unnoticed because the surrounding Material component
supplies its own default and masks it. Outside a Material component — a CDK
overlay, a projected panel — the same reference silently produces a fully
transparent, unreadable surface.
Angular Material mixins and Material-component overrides may use the tokens
owned by that component. Outside a Material-owned component, prefer the
app-owned tokens above. A `--mat-sys-*` reference is acceptable there only
after the built light and dark theme contexts both prove that it is emitted,
and it must still have a real app-token or literal fallback, for example:
`var(--mat-sys-surface-container, var(--app-widget-bg))`.
If a `--mat-sys-*` token is genuinely needed, always give it a real fallback:
`var(--mat-sys-on-primary, #fff)`.
Several existing app surfaces still reference Material system tokens without
that proof or use hard-coded layout/selection colors. Treat those references
as migration debt, not patterns to copy.
Do not hardcode unrelated accent colors for selected state when these tokens already exist.
@@ -89,6 +94,9 @@ Apply the same visual recipe to selected list items, active channels, and curren
- Text:
selected text should inherit `var(--app-selection-color)`
Use `var(--app-selection-on-color)` when text or an icon sits directly on a
solid `var(--app-selection-color)` fill.
Use this pattern for:
- `.nav-item.selected` / `.nav-item.active`
@@ -112,9 +120,9 @@ the wrapper file that includes it.
The shared row should be reused instead of rebuilding channel markup per view.
### Structure
### Current Reference Values
- Min height:
- Current minimum height:
`68px`
- Horizontal gap:
`12px`
@@ -122,11 +130,16 @@ The shared row should be reused instead of rebuilding channel markup per view.
`8px 10px 8px 12px`
- Radius:
`12px`
- Logo shell:
- Current logo shell:
`44x44`, rounded, subtle inset treatment
- Compact variant:
`52px` min height with slightly tighter padding
These values describe the current shared row, not a fixed-width contract. Keep
the row responsive: the text column uses `min-width: 0` and ellipsis, while
logos, drag affordances, and trailing actions use `flex-shrink: 0`. Prefer
minimum dimensions and flexible columns over fixed row widths.
### Content Layout
- Title is one line, medium-bold, slightly condensed
@@ -143,6 +156,12 @@ The shared row should be reused instead of rebuilding channel markup per view.
## EPG Views
The shared timeline and list still contain local dark surfaces, blue selection
accents, and white foregrounds. These non-semantic hard-coded colors are
migration debt. New work should use app surface/selection/text tokens and must
not spread those local fallbacks. Semantic live, error, and status colors may
remain local when the meaning is explicit.
### Shared EPG Pane
- Header title stays sticky
@@ -271,7 +290,9 @@ Settings use the same system but are flatter than content-heavy views.
### Light Theme
- Prefer white or near-white cards
- Use neutral borders from `--mat-sys-outline-variant`
- Use app-owned neutral borders, or a proven Material token with a real
fallback such as
`var(--mat-sys-outline-variant, var(--app-widget-border))`
- Keep active sections mostly defined by outline and subtle tint
- Avoid dark translucent backgrounds
@@ -286,7 +307,7 @@ Settings use the same system but are flatter than content-heavy views.
### Light Theme
- Flat beats glossy
- White and surface-container layers should separate content
- White and app-owned widget/content surface layers should separate content
- Selection should read as a blue outline plus soft tint, not a solid slab
### Dark Theme
@@ -302,7 +323,14 @@ Before creating new markup or CSS:
1. Check whether `app-channel-list-item` can be reused.
2. Check whether `app-epg-timeline` already provides the correct structure.
3. Check whether `nav-list.scss` already solves the list-selection problem.
4. Extend tokens first, duplicate styles last.
4. Inspect the public APIs of `@iptvnator/ui/components`,
`@iptvnator/ui/epg`, `@iptvnator/ui/playback`,
`@iptvnator/ui/shared-portals`, `@iptvnator/portal/shared/ui`, and
`@iptvnator/playlist/shared/ui`.
5. Put provider-neutral collection loading, persistence, and cross-provider
orchestration in `@iptvnator/portal/shared/data-access`, not a UI library or
the shared util library.
6. Extend tokens first, duplicate styles last.
## Implementation Workflow
@@ -312,13 +340,16 @@ When updating IPTVnator UI:
2. Reuse the shared structure where possible.
3. Keep selection, progress, and spacing in sync across Xtream, Stalker, and shared portal views.
4. Verify in both light and dark themes.
5. Verify in the running Electron app when the change is visual or layout-sensitive.
5. Run the focused component/unit target and the closest Playwright workflow.
6. Use the running Electron app through CDP only for Electron-only gaps or
additional layout inspection; it does not replace available E2E coverage.
## Anti-Patterns
Avoid these:
- introducing a new selected-state color unrelated to the theme tokens
- copying the shared EPG's hard-coded dark/blue fallbacks into new surfaces
- duplicating channel row markup in portal-specific views
- showing placeholder logos behind real logos
- making entire panes scroll when only the list should scroll
+10 -8
View File
@@ -109,10 +109,10 @@ GET load.php?type=itv&action=get_short_epg&ch_id={channel_id}&size={n}&JsHttpReq
**Notes**
- The response is normalized into shared `EpgItem[]`
- The list-preview path uses this directly
- The active-panel fallback maps the result into controlled `EpgProgram[]`
- Only the active-panel fallback uses this path and maps the result into
controlled `EpgProgram[]`
### `get_epg_info` (bulk active-panel source)
### `get_epg_info` (bulk row-preview and active-panel source)
**Request**
@@ -218,7 +218,8 @@ playlists.
1. User activates a live channel
2. The component ensures playback link resolution as before
3. The component calls `ensureBulkItvEpg(168)` on first use for the playlist
3. The component ensures `ensureBulkItvEpg(168)` has run; the eager row effect
normally started the same de-duplicated request before playback
4. `selectedItvEpgPrograms()` feeds `app-epg-timeline`
5. If the selected channel has no bulk programs, the component falls back to
`get_short_epg`
@@ -231,10 +232,11 @@ stream URL has been resolved; external playback keeps the full EPG-only panel.
### Channel row preview flow
Before the first live-channel playback, channel rows do not fetch EPG at all.
After bulk EPG has been loaded once for the playlist, visible row previews are
derived locally from `bulkItvEpgByChannel`:
Once non-radio ITV channels render, the post-reset component effect calls
`ensureBulkItvEpg(168)`. It starts eagerly before playback and is de-duplicated
against the active-channel path. Individual rows never issue per-row requests.
As soon as the bulk request completes, visible row previews derive locally
from `bulkItvEpgByChannel`:
- pick the current program for the channel, if one exists
- compute progress from the cached program timestamps
+11 -6
View File
@@ -267,9 +267,12 @@ list:
Stalker has multiple real-world data shapes. The current implementation supports all three:
Across catalog, detail, and activity normalization, `is_series` is accepted
only as boolean `true`, numeric `1`, or string `'1'`. Unsupported
`is_series` values do not by themselves classify a VOD item as a series.
Within Stalker portal data access and feature code,
`isStalkerSeriesFlag()` is the sole interpretation of `is_series`. The
dependency-neutral activity normalizer in `libs/shared/interfaces` preserves
the same closed set for dashboard records: boolean `true`, numeric `1`, or
string `'1'`. Unsupported values do not by themselves classify a VOD item as a
series.
1. Regular Series (`/series`):
@@ -308,7 +311,8 @@ only as boolean `true`, numeric `1`, or string `'1'`. Unsupported
series ID, provider episode ID, season key, and episode number. The season
key follows the mapping fallback (`season_number`, then name, then ID).
- The previous season/episode hash remains available only as a compatibility
alias. New playback positions always use the scoped tracking ID.
alias in `legacyTrackingId`. The scoped ID is the in-memory episode key, and
new playback positions always use it.
- Quick-start actions preserve both their translation key and interpolation
parameters when adapted for the Stalker CTA. Dropping `labelParams` exposes
the raw `{{episode}}` placeholder.
@@ -472,8 +476,9 @@ Stalker ITV now splits EPG usage:
- active channel panel: bulk `get_epg_info` cached once per playlist and rendered
through the shared EPG panel (`app-epg-timeline`, or `app-epg-list-view` in list mode)
- channel row preview: no pre-playback network requests; previews are derived
from cached bulk EPG only after the first active-channel fetch succeeds
- channel row preview: once ITV channels render, a post-reset effect eagerly
starts the de-duplicated bulk `get_epg_info` load; rows issue no per-row
request and derive previews from that cache
- active panel fallback: `get_short_epg` when bulk EPG is missing or unsupported
Full details are documented in [Stalker Portal EPG Architecture](./stalker-epg.md).
@@ -3,6 +3,40 @@
This document captures the Xtream Codes compatibility rules shared by the
Electron and PWA paths.
## Runtime Selection And Ownership
`provideXtreamDataSource()` selects `ElectronXtreamDataSource` only when
`RuntimeCapabilitiesService.supportsXtreamSqliteDataSource` proves that the
complete SQLite-backed Xtream bridge is available. Otherwise it selects
`PwaXtreamDataSource`. A generic Electron or `window.electron` check is not the
data-source capability contract. Older favorites/recent branches that still
probe `window.electron` directly are migration debt, not an alternate runtime
selection rule; changes in those paths should follow the selected data source
and explicit capabilities.
Ownership follows the workspace boundaries:
- routed screens and screen-session orchestration:
`libs/portal/xtream/feature`
- Xtream API, cache, Signal Store, and data sources:
`libs/portal/xtream/data-access`
- provider-neutral collection services and reusable multi-source
discovery/resolution: `libs/portal/shared/data-access`
- reusable presentation: `libs/portal/shared/ui`
- pure provider-neutral contracts/helpers: `libs/portal/shared/util`
Persisted Xtream identity is playlist-scoped and content-type-aware:
`playlist_id + content.type + xtream_id`. Mixed collection keys likewise
include type plus provider ID because live, movie, and series IDs can collide.
Do not confuse the normalized SQLite row ID with provider `xtream_id`,
`stream_id`, or `series_id`, especially when recovering a hidden provider
category for detail playback.
See [Nx Workspace Boundaries](./nx-workspace-boundaries.md),
[SQLite DB Worker](./sqlite-db-worker.md),
[Portal Detail Navigation](./portal-detail-navigation.md), and
[VOD Multi-Source](./vod-multi-source.md).
## Connection Input
Xtream server URLs are normalized through
+5 -6
View File
@@ -1,13 +1,12 @@
// ─── IPTVnator UI styles library ─────────────────────────────────────────────
// Add libs/ui/styles to stylePreprocessorOptions.includePaths in project.json
// to import these without relative paths:
// Canonical forwarding inventory for shared IPTVnator UI styles.
// The workspace has no global Sass include path for this directory; production
// consumers use a relative path to the partial they need, for example:
//
// @use 'portal-layout' as portal;
// @use 'content-grid' as grid;
// @use 'portal-sidebar';
// @use '../../../../../../ui/styles/portal-layout' as portal;
@forward 'portal-layout';
@forward 'content-grid';
@forward 'portal-sidebar';
@forward 'panel-header';
@forward 'detail-view';
@forward 'detail-view-actions';