diff --git a/.codex/skills/iptvnator-theme-style/SKILL.md b/.codex/skills/iptvnator-theme-style/SKILL.md index 0763e7c8e..55cd394e5 100644 --- a/.codex/skills/iptvnator-theme-style/SKILL.md +++ b/.codex/skills/iptvnator-theme-style/SKILL.md @@ -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. diff --git a/.codex/skills/iptvnator-ui-design/SKILL.md b/.codex/skills/iptvnator-ui-design/SKILL.md index 6c49bb223..b1be20fbe 100644 --- a/.codex/skills/iptvnator-ui-design/SKILL.md +++ b/.codex/skills/iptvnator-ui-design/SKILL.md @@ -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. diff --git a/.codex/skills/stalker-portal/SKILL.md b/.codex/skills/stalker-portal/SKILL.md index d59fa7a20..7b97a1bd0 100644 --- a/.codex/skills/stalker-portal/SKILL.md +++ b/.codex/skills/stalker-portal/SKILL.md @@ -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. diff --git a/.codex/skills/xtream-electron/SKILL.md b/.codex/skills/xtream-electron/SKILL.md index 305afcd27..a89cb0540 100644 --- a/.codex/skills/xtream-electron/SKILL.md +++ b/.codex/skills/xtream-electron/SKILL.md @@ -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` diff --git a/CLAUDE.md b/CLAUDE.md index 4678a75ae..534b5bac8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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**: diff --git a/docs/architecture/iptvnator-ui-guidelines.md b/docs/architecture/iptvnator-ui-guidelines.md index 519f2f60a..bf08dce48 100644 --- a/docs/architecture/iptvnator-ui-guidelines.md +++ b/docs/architecture/iptvnator-ui-guidelines.md @@ -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 diff --git a/docs/architecture/stalker-epg.md b/docs/architecture/stalker-epg.md index 437f251be..95833c2e5 100644 --- a/docs/architecture/stalker-epg.md +++ b/docs/architecture/stalker-epg.md @@ -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 diff --git a/docs/architecture/stalker-portal.md b/docs/architecture/stalker-portal.md index f04d954f7..e238a09ce 100644 --- a/docs/architecture/stalker-portal.md +++ b/docs/architecture/stalker-portal.md @@ -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). diff --git a/docs/architecture/xtream-portal-compatibility.md b/docs/architecture/xtream-portal-compatibility.md index 74f57c1e4..d0eb29d88 100644 --- a/docs/architecture/xtream-portal-compatibility.md +++ b/docs/architecture/xtream-portal-compatibility.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 diff --git a/libs/ui/styles/_index.scss b/libs/ui/styles/_index.scss index 06421af87..c038c32ef 100644 --- a/libs/ui/styles/_index.scss +++ b/libs/ui/styles/_index.scss @@ -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';