feat(epg): add vertical list view for the live EPG panel

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>
This commit is contained in:
4grayandClaude Opus 4.8 committed 2026-07-02 00:29:52 +02:00
1 parent 401012eac4
commit c4890748e0
57 files changed
+2981 -90

No files matched your search

@@ -231,6 +231,38 @@ test.describe('Electron Settings', () => {
}
});
test('@settings @persistence @electron persists the EPG view mode across app restart', async ({
dataDir,
}) => {
const firstLaunch = await launchElectronApp(dataDir);
try {
await openSettings(firstLaunch.mainWindow);
const listToggle = firstLaunch.mainWindow.locator(
'[data-test-id="epg-view-mode-list"]'
);
await expect(listToggle).toBeVisible();
await listToggle.click();
await expect(listToggle).toHaveAttribute('aria-checked', 'true');
await saveSettings(firstLaunch.mainWindow);
} finally {
await closeElectronApp(firstLaunch);
}
const secondLaunch = await launchElectronApp(dataDir);
try {
await openSettings(secondLaunch.mainWindow);
await expect(
secondLaunch.mainWindow.locator(
'[data-test-id="epg-view-mode-list"]'
)
).toHaveAttribute('aria-checked', 'true');
} finally {
await closeElectronApp(secondLaunch);
}
});
test('@settings @electron starts on sources when dashboard is disabled', async ({ dataDir }) => {
const firstLaunch = await launchElectronApp(dataDir);