mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 04:26:16 -08:00
Merge remote-tracking branch 'origin/master' into claude/reverent-thompson-163619
# Conflicts: # CLAUDE.md # package.json
This commit is contained in:
commit
0285cf6c40
127 files changed
+3651
-4869
No files matched your search
@@ -28,10 +28,10 @@ Use it when changing existing views or introducing new list-based UI in the work
|
||||
`libs/ui/components/src/lib/channel-list-container/channel-list-item/channel-list-item.component.html`
|
||||
- Channel row styles:
|
||||
`libs/ui/components/src/lib/channel-list-container/channel-list-item/channel-list-item.component.scss`
|
||||
- Shared EPG pane:
|
||||
`libs/ui/shared-portals/src/lib/epg-view/epg-view.component.html`
|
||||
- Shared EPG pane styles:
|
||||
`libs/ui/shared-portals/src/lib/epg-view/epg-view.component.scss`
|
||||
- Shared EPG timeline:
|
||||
`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 list selection style:
|
||||
`apps/web/src/nav-list.scss`
|
||||
- Theme tokens:
|
||||
@@ -141,12 +141,11 @@ The shared row should be reused instead of rebuilding channel markup per view.
|
||||
|
||||
### Collapsible Live EPG
|
||||
|
||||
- Live TV layouts with an internal player use `app-live-epg-panel` around the
|
||||
- Live TV layouts with an internal player render `app-epg-timeline` as the
|
||||
EPG content, including playlist-specific live pages and the global
|
||||
favorites/recent live tabs.
|
||||
- The live panel toolbar owns the current-program summary and live date
|
||||
navigation together, so `app-epg-list` hides its internal date navigator when
|
||||
projected inside that panel.
|
||||
- The timeline's own panel bar owns the current-program summary and live date
|
||||
navigation together; there is no separate wrapper component around it.
|
||||
- Collapsed state is shared across M3U, Xtream, and Stalker with
|
||||
`live-epg-panel-state`; missing or invalid values restore to expanded.
|
||||
- The collapsed panel is a slim current-program strip with a trailing progress
|
||||
@@ -289,7 +288,7 @@ Settings use the same system but are flatter than content-heavy views.
|
||||
Before creating new markup or CSS:
|
||||
|
||||
1. Check whether `app-channel-list-item` can be reused.
|
||||
2. Check whether `app-epg-view` already provides the correct structure.
|
||||
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.
|
||||
|
||||
|
||||
@@ -266,16 +266,33 @@ per-tab EPG logic:
|
||||
favorites). Callers read their `progressTick()` signal first so the computed
|
||||
re-runs on the ~30s tick.
|
||||
|
||||
### EPG Timeline Panel
|
||||
### EPG Panel (Timeline & List views)
|
||||
|
||||
The programme guide under the player is a horizontal **timeline ribbon**
|
||||
(`app-epg-timeline`, `libs/ui/epg/src/lib/epg-timeline/`) shared by all four live
|
||||
surfaces: the M3U video player, the unified live tab, and the Xtream and Stalker
|
||||
live-stream layouts. It replaces the former vertical `app-epg-list` /
|
||||
`app-epg-view`.
|
||||
The programme guide under the player renders in one of **two interchangeable
|
||||
views**, chosen by the **`epgViewMode`** setting (`'timeline'` default, or
|
||||
`'list'`; Settings → EPG → *Guide view*):
|
||||
|
||||
The component stays presentation-focused; the reusable, view-agnostic pieces
|
||||
(so a future list view can share them) are split out and re-exported from
|
||||
- **Timeline** — a horizontal **ribbon** (`app-epg-timeline`,
|
||||
`libs/ui/epg/src/lib/epg-timeline/`).
|
||||
- **List** — a vertical, single-day **programme list** (`app-epg-list-view`,
|
||||
`libs/ui/epg/src/lib/epg-list-view/`) with a prev/today/next stepper.
|
||||
|
||||
Both are shared by all four live surfaces: the M3U video player, the unified live
|
||||
tab, and the Xtream and Stalker live-stream layouts (replacing the former
|
||||
vertical `app-epg-list` / `app-epg-view`). `EpgListViewComponent` mirrors
|
||||
`EpgTimelineComponent`'s input/output contract **1:1**, so each host swaps them
|
||||
with a plain `@if (epgViewMode() === 'list') { <app-epg-list-view … /> } @else {
|
||||
<app-epg-timeline … /> }` — identical bindings in both branches. Hosts read
|
||||
`epgViewMode` from `SettingsStore` (a signal), so flipping the setting swaps the
|
||||
panel live. The setting flows end-to-end (`Settings.epgViewMode` →
|
||||
`DEFAULT_SETTINGS` → `SettingsStore`/`StorageMap` → the segmented control in
|
||||
`settings-epg-section`) and needs no backend change. The control is
|
||||
**Electron-only in practice** — the EPG settings section (and the form control)
|
||||
is gated behind `supportsEpg`, which is false in PWA; there the stored value
|
||||
simply stays at the `'timeline'` default.
|
||||
|
||||
Both components stay presentation-focused; the reusable, view-agnostic pieces
|
||||
(shared by the timeline and the list) are split out and re-exported from
|
||||
`@iptvnator/ui/epg`:
|
||||
|
||||
- `epg-timeline.utils.ts` (axis/blocks/date helpers) + `epg-timeline-render.util.ts`
|
||||
@@ -290,6 +307,19 @@ The component stays presentation-focused; the reusable, view-agnostic pieces
|
||||
scrolling + channel-select auto-focus); timeline-specific, kept out of the
|
||||
component so it stays under the line ceiling.
|
||||
|
||||
The **list view** (`epg-list-view/`) composes those same shared modules — it does
|
||||
**not** duplicate classification or gating logic. It reuses `classifyTimelineWhen`
|
||||
/ `hasProgramsForDateKey` / `nearestDateKeyWithPrograms`, `epg-archive.util`,
|
||||
`epg-summary.util`, the `epg-date` helpers, `EpgProgrammeDialogService`, and the
|
||||
shared `app-epg-timeline-empty-state` — and drops all ribbon geometry, zoom, and
|
||||
horizontal scroll. It filters the loaded window to the selected day (overlap-based,
|
||||
matching `hasProgramsForDateKey`), sorts, and deduplicates via a pure
|
||||
`buildEpgListRows` (`epg-list-view.utils.ts`); renders each row through the dumb
|
||||
`app-epg-list-view-row`; and delegates its own vertical auto-focus + sticky
|
||||
"now" strip to `EpgListScrollController` (`epg-list-scroll.controller.ts`). Render
|
||||
states, the collapsed inline summary, the date stepper, catch-up/timeshift
|
||||
activation, and the details dialog behave identically to the timeline.
|
||||
|
||||
- **One channel, preloaded window.** The panel always shows a single channel.
|
||||
Each provider returns a multi-day window in roughly one call (M3U
|
||||
`GET_CHANNEL_PROGRAMS`; Stalker `get_epg_info`; Xtream `get_simple_data_table`),
|
||||
@@ -370,7 +400,11 @@ The component stays presentation-focused; the reusable, view-agnostic pieces
|
||||
tag / "Watch") stays pinned at the bottom. With an inline player the guide is
|
||||
a compact panel (`.epg.epg--inline` → `flex: 0 0 clamp(180px, 36vh, 264px)`
|
||||
in `_portal-layout.scss`) so the player stays dominant; with an external
|
||||
player the guide keeps `flex: 1` and fills the whole content area.
|
||||
player the guide keeps `flex: 1` and fills the whole content area. In **list
|
||||
mode** the hosts also set `.epg--list`, which raises only the inline clamp
|
||||
(`--epg-inline-height: clamp(280px, 46vh, 430px)`) — vertical rows need more
|
||||
height than the ribbon; the timeline height is unchanged, and the collapsed
|
||||
56px clamp still wins because the modifier sets just the CSS variable.
|
||||
- **Wide-tier description preview.** When a block is the `wide` tier (rendered
|
||||
width ≥ `132px`, i.e. long programmes and/or zoomed in) **and** the programme
|
||||
has a `desc`, a dimmed (`--text-secondary`) preview of the description renders
|
||||
@@ -564,8 +598,8 @@ class EpgService {
|
||||
|
||||
| Component | Purpose |
|
||||
| ----------------------------- | ------------------------------------ |
|
||||
| `EpgListComponent` | Timeline view for single channel |
|
||||
| `EpgListItemComponent` | Individual program in timeline |
|
||||
| `EpgTimelineComponent` | Horizontal timeline for one channel |
|
||||
| `EpgListViewComponent` | Vertical single-day list alternative |
|
||||
| `EpgItemDescriptionComponent` | Program details dialog |
|
||||
| `MultiEpgContainerComponent` | Grid view of all channels' schedules |
|
||||
|
||||
|
||||
@@ -82,3 +82,16 @@ playback, or EPG data belongs in `libs/portal/shared/data-access`, not
|
||||
`libs/portal/shared/util`. That keeps pure collection helpers importable by
|
||||
Xtream/Stalker data-access libraries while allowing shared UI to use
|
||||
provider-specific collection services without creating cycles.
|
||||
|
||||
Note: `workspace-shell-util` (`libs/workspace/shell/util`) is tagged
|
||||
`type:data-access` despite its path. It exports injectable services such as
|
||||
`WorkspaceStartupPreferencesService` that depend on `@iptvnator/services`, and
|
||||
it must stay eagerly importable from `apps/web/src/app/app.routes.ts` without
|
||||
pulling the lazy-loaded workspace shell feature bundle into the initial chunk.
|
||||
|
||||
## CI Enforcement
|
||||
|
||||
The `Lint` job in `.github/workflows/ci.yml` runs
|
||||
`pnpm nx run-many --target=lint --all` on every PR, so
|
||||
`@nx/enforce-module-boundaries` violations, legacy bare-alias imports, and
|
||||
`max-lines` violations fail CI. Run `pnpm run lint` locally before pushing.
|
||||
@@ -14,7 +14,7 @@ Stalker now uses two EPG paths with different purposes:
|
||||
|
||||
- The active channel EPG panel uses `get_epg_info` as a bulk endpoint, fetches a
|
||||
7-day window once per playlist session, caches programs by channel id, and
|
||||
renders the selected channel through the shared `app-epg-list` component.
|
||||
renders the selected channel through the shared `app-epg-timeline` component.
|
||||
- Channel rows no longer send preview EPG requests during initial category load.
|
||||
They stay empty until bulk EPG has been fetched once, then derive their
|
||||
current program and progress bar from the cached bulk map.
|
||||
@@ -39,7 +39,7 @@ date-navigator UI used in the M3U/Xtream flows.
|
||||
│ │ ensureBulkItvEpg(168) │
|
||||
│ │ selectedItvEpgPrograms() │
|
||||
│ ▼ │ │
|
||||
│ current program preview ├── bulk hit → app-epg-list │
|
||||
│ current program preview ├── bulk hit → app-epg-timeline │
|
||||
│ after first bulk load └── empty/unsupported → short fallback │
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
│ │
|
||||
@@ -166,7 +166,7 @@ Key mapped fields:
|
||||
|
||||
### Active panel data (`get_epg_info` / fallback) → `EpgProgram`
|
||||
|
||||
The active panel uses controlled `EpgProgram[]` because `app-epg-list` filters
|
||||
The active panel uses controlled `EpgProgram[]` because `app-epg-timeline` filters
|
||||
and groups by day.
|
||||
|
||||
Normalization rules:
|
||||
@@ -183,10 +183,9 @@ Normalization rules:
|
||||
| File | Purpose |
|
||||
| -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| `libs/portal/stalker/data-access/src/lib/stores/features/with-stalker-epg.feature.ts` | bulk cache and fallback handling |
|
||||
| `libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts` | active-channel EPG loading and controlled `app-epg-list` wiring |
|
||||
| `libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts` | active-channel EPG loading and controlled `app-epg-timeline` wiring |
|
||||
| `libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.html` | active panel template |
|
||||
| `libs/portal/stalker/feature/src/lib/stalker-collection-channels-list/stalker-collection-channels-list.component.ts` | row preview loading |
|
||||
| `libs/ui/shared-portals/src/lib/epg-list/epg-list.component.ts` | shared controlled EPG list with date navigator |
|
||||
| `libs/ui/epg/src/lib/epg-timeline/epg-timeline.component.ts` | shared controlled EPG timeline with date navigator |
|
||||
|
||||
### Store API
|
||||
|
||||
@@ -210,7 +209,7 @@ 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
|
||||
4. `selectedItvEpgPrograms()` feeds `app-epg-list`
|
||||
4. `selectedItvEpgPrograms()` feeds `app-epg-timeline`
|
||||
5. If the selected channel has no bulk programs, the component falls back to
|
||||
`get_short_epg`
|
||||
|
||||
@@ -267,5 +266,5 @@ is available.
|
||||
## Future Enhancements
|
||||
|
||||
- add cache refresh / invalidation for long-running live sessions
|
||||
- add Stalker catch-up support to `app-epg-list` once the playback flow exists
|
||||
- add Stalker catch-up support to `app-epg-timeline` once the playback flow exists
|
||||
- optionally add category-level prefetch timing metrics for bulk EPG
|
||||
@@ -17,16 +17,38 @@ pnpm nx show projects --withTarget e2e
|
||||
| ---------------------------------- | ----------------------------------- |
|
||||
| Angular renderer entry points | `pnpm run typecheck:web` |
|
||||
| Electron main process entry points | `pnpm run typecheck:backend` |
|
||||
| Curated CI unit suite | `pnpm run test:unit:ci` |
|
||||
| Full unit suite (all projects) | `pnpm run test:unit:ci` |
|
||||
| EPG data access | `pnpm nx test epg-data-access` |
|
||||
| Workspace shell utilities | `pnpm nx test workspace-shell-util` |
|
||||
| Shared SQLite schema/connection | `pnpm nx test database` |
|
||||
| Packaging metadata | `pnpm nx test packaging` |
|
||||
|
||||
## Lint
|
||||
|
||||
```bash
|
||||
pnpm run lint # nx run-many --target=lint --all
|
||||
pnpm nx lint <project> # single project
|
||||
```
|
||||
|
||||
The CI workflow (`.github/workflows/ci.yml`) runs lint for every project on
|
||||
each PR. This enforces `@nx/enforce-module-boundaries` (scope/domain/type tag
|
||||
constraints), the legacy bare-alias ban, and the `max-lines` file-size rule
|
||||
(hard maximum 400 lines per TypeScript file). Files that predate the
|
||||
`max-lines` rule are baselined in `tools/eslint/max-lines-baseline.mjs`; after
|
||||
splitting a baselined file below the limit, regenerate the list with
|
||||
`node tools/eslint/generate-max-lines-baseline.mjs`. Never add new files to
|
||||
the baseline.
|
||||
|
||||
## Coverage Tiers
|
||||
|
||||
Use `tools/coverage/coverage-policy.json` as the source of truth for coverage
|
||||
ownership.
|
||||
ownership. Every project with a `test` target must be classified in a tier;
|
||||
`pnpm run coverage:policy:check` (part of `coverage:ci`) fails CI when a new
|
||||
project is missing from the policy, a listed project no longer exists, or a
|
||||
Tier A entry has no test target. CI runs Tier A with coverage (uploaded to
|
||||
Codecov) and each Tier B/C project's `validationCommand` (falling back to
|
||||
`nx test`) without coverage; projects with an `e2e` target are skipped there
|
||||
because the E2E workflow already runs them on every PR.
|
||||
|
||||
| Tier | Rule | Validation |
|
||||
| --- | --- | --- |
|
||||
|
||||
Reference in new issue
Block a user