feat(epg): add vertical list view for the live EPG panel#1115
Conversation
Add an EPG list view — a vertical, single-day programme list — as an alternative rendering of the live EPG panel, selectable via a new Settings → EPG → "Guide view" toggle (epgViewMode: 'timeline' | 'list', default 'timeline' so existing users see no change). - New EpgListViewComponent (app-epg-list-view) mirrors EpgTimelineComponent's input/output contract 1:1, so all four live hosts (M3U player, unified live tab, Xtream, Stalker) swap the panel with a plain @if and identical bindings. - Reuses the shared view-agnostic EPG modules (classifyTimelineWhen, hasProgramsForDateKey, epg-archive.util catch-up gating, epg-summary.util collapsed-summary maths, epg-date helpers, EpgProgrammeDialogService, app-epg-timeline-empty-state) — no duplicated logic. - Rows show time range, title, optional description, live progress on the on-air row, catch-up "Watch" on past rows when archive playback is available, and a details dialog; keyboard activation guards nested buttons (target === currentTarget). - Auto-focuses the on-air row on channel select, restores it across collapse/expand remounts, and shows a sticky in-flow "On now" strip (never overlaying rows) when the current programme is scrolled away; all scroll maths is rect-based relative to the scroller. - List mode raises only the inline panel height via an epg--list modifier (--epg-inline-height clamp); timeline and collapsed heights are unchanged. - Setting flows end-to-end (Settings interface → DEFAULT_SETTINGS → SettingsStore/StorageMap → segmented control in the EPG section); Electron-only UI, PWA stays on the timeline default. i18n keys added to all 18 locales. - Tests: new component/row/utils/scroll-controller specs, settings persistence spec, swap tests in all four host specs, and Electron E2E for the settings round-trip and the rendered list view. Docs updated (m3u-playlist-module.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c4890748e0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!list || !today) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Reset list view to today on new channels
If a user has navigated the EPG to a non-today date and then selects another channel, the host still passes that old selectedLiveEpgDate; list rows are built only for that off-today date, and this early return prevents the list from ever looking for the new channel's on-air row or emitting a date reset. The timeline controller resets new programme sets back to today when today has data, so list mode can strand users on an empty/old day until they manually press the Now control.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in e6fd0d0: maybeAutoScroll now keys by the full programme-set identity (programsFocusKey) and commits today (when today has data) before focusing, so a channel switch while parked on another day returns the list to today — timeline parity. Covered by the new controller specs (return-to-today, no-takeover when today is empty, day navigation left alone).
Greptile SummaryAdds
Confidence Score: 5/5Safe to merge. The new list view is a purely additive feature behind a settings toggle defaulting to the existing timeline, so all existing users see no change on update. All four live host components are updated consistently, the fallback default is applied at a single point in the store, backward compatibility with stored settings is preserved via optional interface field and triple-fallback chains, and the feature is gated behind the Electron-only supportsEpg condition in the settings form. Test coverage is comprehensive across the utility, scroll controller, row component, and all four host specs. No files require special attention. The new epg-list-view library files are well-structured with thorough unit tests, and the host template changes are mechanical @if/else swaps with identical bindings on both branches. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Settings: EPG Guide View toggle] -->|selectEpgViewMode| B[SettingsStore.updateSettings]
B -->|patchState| C[store.epgViewMode signal]
C --> D[resolvedEpgViewMode computed]
D --> E1[VideoPlayerComponent]
D --> E2[LiveStreamLayoutComponent]
D --> E3[StalkerLiveStreamLayout]
D --> E4[UnifiedLiveTabComponent]
E1 -->|if list| F1[app-epg-list-view]
E1 -->|else| G1[app-epg-timeline]
E2 -->|if list| F2[app-epg-list-view]
E3 -->|if list| F3[app-epg-list-view]
E4 -->|if list| F4[app-epg-list-view]
subgraph EpgListView[EpgListViewComponent]
H[programs input] --> I[buildEpgListRows]
K[nowMs 30s tick] --> I
I --> J[EpgListViewRowComponent rows]
L[EpgListScrollController] -->|auto-focus on-air row| J
L -->|now-strip visibility| M[g-nowstrip button]
end
F1 --> EpgListView
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[Settings: EPG Guide View toggle] -->|selectEpgViewMode| B[SettingsStore.updateSettings]
B -->|patchState| C[store.epgViewMode signal]
C --> D[resolvedEpgViewMode computed]
D --> E1[VideoPlayerComponent]
D --> E2[LiveStreamLayoutComponent]
D --> E3[StalkerLiveStreamLayout]
D --> E4[UnifiedLiveTabComponent]
E1 -->|if list| F1[app-epg-list-view]
E1 -->|else| G1[app-epg-timeline]
E2 -->|if list| F2[app-epg-list-view]
E3 -->|if list| F3[app-epg-list-view]
E4 -->|if list| F4[app-epg-list-view]
subgraph EpgListView[EpgListViewComponent]
H[programs input] --> I[buildEpgListRows]
K[nowMs 30s tick] --> I
I --> J[EpgListViewRowComponent rows]
L[EpgListScrollController] -->|auto-focus on-air row| J
L -->|now-strip visibility| M[g-nowstrip button]
end
F1 --> EpgListView
Reviews (3): Last reviewed commit: "fix(epg): drop malformed programmes from..." | Re-trigger Greptile |
- Reset the list view to today when a new channel's programme set arrives while the user is parked on another day (timeline parity): the scroll controller now keys by the full programme-set identity (programsFocusKey) and commits today before focusing, instead of silently stranding the new channel on the stale day. (Codex P2) - Centralise the 'timeline' fallback as a resolvedEpgViewMode computed on SettingsStore; the four live hosts consume the derived signal instead of duplicating the `?? 'timeline'` expression. (Greptile P2) - Extract the component's reactive plumbing into registerEpgListViewEffects(), bringing the component back under the 300-line guideline (290). (Greptile P2) - Controller spec rewritten around programme-set fixtures with new coverage: return-to-today on channel switch, day navigation left alone, no-takeover when today has no data, empty-set no-op. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Pushed
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6fd0d0825
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!Number.isFinite(startMs) || !Number.isFinite(stopMs)) { | ||
| return false; | ||
| } | ||
| return startMs < dayEndMs && stopMs > dayStartMs; |
There was a problem hiding this comment.
Reject non-positive programme durations
When an EPG provider returns a malformed programme whose stop time is equal to or before its start time, this overlap test still accepts it as long as both timestamps fall within the selected day. The timeline path drops those entries in buildTimelineBlocks, but list mode will render impossible ranges and can even mark them catch-up playable, so list mode can show bogus rows for bad provider data.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in c6dbedd: buildEpgListRows now rejects programmes whose stop is not after their start, matching the timeline's buildTimelineBlocks. Regression test added (zero and negative durations are dropped).
Reject programmes whose stop is not after their start in buildEpgListRows — same as the timeline's buildTimelineBlocks. Bad provider data would otherwise render impossible time ranges and could even be offered as catch-up playable. (Codex P2 on e6fd0d0) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Master added EpgListViewComponent (#1115) written against the old epg-list/ paths this branch deletes, plus the lint/coverage CI hardening (#1117). Resolution: - ui/epg barrel: keep relocated epg-item-description and epg-program-activation-event exports, add the three new epg-list-view exports, drop the deleted epg-list.component export. - epg-list-view imports updated to ../epg-item-description, ../epg-program-activation-event and ../epg-program.utils. - Restore deduplicateProgramsByTimeSlot (+ private helpers) in epg-program.utils.ts — the new list view makes it live again. - _portal-layout.scss / unified-live-tab.component.scss: panel selectors cover app-epg-timeline and app-epg-list-view only. - Drop the deleted epg-list.component.spec.ts entry from the max-lines baseline. - m3u-playlist-module.md: add EpgListViewComponent to the EPG components table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolves conflicts against the dead-component cleanup (#1116), the max-lines lint baseline (#1117) and the vertical EPG list view (#1115): - libs/ui/shared-portals/src/index.ts: keep master's barrel (EpgView and LiveEpgPanel components were deleted, LiveEpgPanelSummary extracted) plus the new actor-view export from this branch - settings-form.utils.ts / settings.component.spec.ts: keep both the new epgViewMode control from master and the tmdb settings group Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
epgViewMode: 'timeline' | 'list'(default'timeline'— existing users see no change after updating; missing key in stored settings falls back to the default on load).EpgListViewComponent(app-epg-list-view) mirrorsEpgTimelineComponent's input/output contract 1:1, so every live host swaps the panel with a plain@ifand identical bindings.Changes
libs/ui/epg/src/lib/epg-list-view/: list component + dumb row component + purebuildEpgListRowsrow-model builder +EpgListScrollController(auto-focus of the on-air row, collapse/expand remount restore, sticky in-flow "On now" strip). Reuses the shared view-agnostic modules (classifyTimelineWhen,hasProgramsForDateKey,epg-archive.util,epg-summary.util,epg-date,EpgProgrammeDialogService,app-epg-timeline-empty-state) — no duplicated logic.epgViewModefromSettingsStore; list mode raises only the inline panel height via anepg--listmodifier (--epg-inline-height), timeline/collapsed heights unchanged.DEFAULT_SETTINGS→SettingsStore/StorageMap → segmented control in the EPG section, persisted immediately). Electron-only UI; PWA stays on the timeline default.SETTINGS.EPG_VIEW_MODE*keys in all 18 locales.docs/architecture/m3u-playlist-module.mdEPG section rewritten for the two view modes.Applicable findings from the #1102 bot reviews were mapped onto the list view and handled (nested-button keyboard guard, scroll-position restore across collapse/expand, gap-day/auto-focus semantics), plus a multi-lens adversarial self-review (viewport-yank on programme rollover, rect-based scroll maths, stale now-strip on non-scrollable lists, 56px collapsed-summary clipping).
Testing
ui-epg122 tests (component render states, activation semantics, row keyboard guard, scroll-controller focus/remount/rollover/strip maths, day filter/dedup utils)epg--listclass)xtream-epg.e2e.ts)nx build web, lint clean across touched projects🤖 Generated with Claude Code