The shared saveSettings helper clicks the settings save control, which is a
native `<button type="submit">` inside `<form (ngSubmit)="onSubmit()">`.
Clicking it makes Chromium register a form-submission navigation that
Angular's ngSubmit handler immediately cancels via preventDefault(), so no
real navigation ever happens. Playwright's default post-click "wait for
signals" barrier still observes that requested-then-cancelled navigation and
waits for it to settle; on slow/loaded CI runners that wait can stall for the
full timeout ("waiting for scheduled navigations to finish").
When the click times out, the test is aborted and its finally block closes
the Electron app; the still-pending click then rejects late, after the test
has finished, so Playwright reports it at the worker level as "1 error was
not a part of any test" and exits non-zero even though the retry passed
(e.g. 78 passed / 1 flaky yet job fails).
Opt out of the barrier with { noWaitAfter: true } and keep asserting the
deterministic post-save state (the button disables once the settings write
resolves and the form is marked pristine), which is a stronger, race-free
confirmation that the save committed. Fixes the flaky radio-playback remote
control test and hardens every saveSettings caller in the suite.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* test(db): cover playback positions, recently viewed, and connection migrations
Add specs for the previously untested persistence paths: playback-position
and recently-viewed operations (upsert/dedup/ordering/scoped deletes),
shared-database path-utils, createTables and the tolerant column/index
migrations incl. Xtream cache deduplication. Exposes createTables through
the existing __databaseConnectionTestHooks object.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(xtream): cover the Electron DB-first data source and favorites guards
Add specs for electron-xtream-data-source (DB-hit vs cold-cache paths,
concurrent request dedup, error propagation, full method delegation) and
extend the favorites feature spec with the invalid-input and
content-not-found guard paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(stalker): cover favorites and recent store features
Add specs for with-stalker-favorites and with-stalker-recent: payload
normalization and id/title fallbacks, series-mode category forcing, meta
sync dispatches, snackbar/callback side effects, and error paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(epg): cover archive/summary utils and the EPG worker service
Add specs for the pure catch-up window and summary-progress helpers shared
by the EPG panels, and for epg-worker.service: in-flight dedup by URL,
double-settle guard, progress-aware timeouts, worker lifecycle and error
broadcasting. Also settle an interrupted fetch in epg.events.spec that
caused "Cannot log after tests are done" in longer runs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(player): cover the VLC session service
Mirror the MPV session spec patterns for VLC: enqueue-command building and
RC response parsing, launch argv construction, instance reuse over the RC
socket, exit-code handling, and the retry-without-RC fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): add downloads page and EPG timeline interaction coverage
Downloads: empty state without sources, and a full lifecycle - authorize a
folder via a stubbed native dialog, download from a local server, verify
the completed item and file on disk, remove it from the UI. Timeline: zoom
changes block widths and the on-air info affordance opens the programme
dialog with the correct title and watch-live action.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: split oversized specs to meet the file-size guideline
Address review feedback: extract shared drizzle mocks into
operations.test-helpers.ts and split the Electron data-source delegation
spec into delegation + user-data files. Pure reorganization - test counts
and assertions unchanged (29/13/10), all files now under 300 lines.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): wait for DB readiness before opening the downloads page
On slow CI runners (macOS) the renderer can query SQLite while the DB
worker is still creating tables, leaving the downloads page on its
skeleton state forever. Poll a playlist read until it succeeds before
navigating on a cold profile.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(build): exclude *.test-helpers.ts from the electron-backend app tsconfig
The new operations.test-helpers.ts uses jest globals and broke the webpack
build and tsc typecheck, which compile every non-spec file in the app.
Exclude the test-helpers pattern alongside the existing spec exclusions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* 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>
* fix(epg): address list-view review findings from Codex and 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>
* fix(epg): drop malformed programmes from the list-view day filter
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 e6fd0d08)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the vertical EPG list with a shared horizontal `app-epg-timeline`
ribbon across all live surfaces (M3U player, unified live tab, Xtream, Stalker):
zoom, day navigation, short-programme grouping, catch-up/timeshift, and
per-state empty views. Backend gains timezone-aware `datetime()` comparisons,
unscoped source fallback, non-ASCII candidate matching, and chunked candidate
queries.
Timeline split into reusable, view-agnostic modules (archive/summary/dialog
service/render util/scroll controller) for the future EPG list view.
Fixes landed during review:
- honor the controlled `selectedDate` input (seed via linkedSignal)
- restore ribbon position across collapse/expand
- keep the ribbon mounted when scrolling across a gap day
- don't trigger block playback on Enter from nested watch/info buttons
- don't reset timeshift playback on the 30s now-tick during EPG gaps
Greptile 5/5 (safe to merge); Codex clean; CI green.
Adds IMDb rating sort and minimum-rating filtering for Xtream VOD/series catalogs, consolidates refinement controls, and guards rating controls away from Live TV.
* feat(ui): add custom title bar window controls for Windows and Linux
Hide the native title bar on win32/linux (titleBarStyle: 'hidden', frame
untouched so native resize borders and snapping keep working) and render
minimize / maximize-restore / close buttons in the renderer, mirroring the
existing macOS traffic-light setup.
- New WINDOW:* IPC contract (minimize, toggle-maximize, close, get-state)
handled in window.events.ts, resolved from the sender WebContents;
close goes through win.close() so window-bounds persistence still runs.
- WINDOW:STATE_CHANGED pushed on maximize/unmaximize/fullscreen so the
maximize/restore glyph stays correct for OS-triggered changes; controls
hide while fullscreen.
- WindowControlsComponent mounts once in app-root as a manual popover so
it stays in the browser top layer above CDK overlays (dialogs,
multi-EPG) - same behavior as macOS traffic lights.
- Theme-aware via CSS vars (--app-on-surface, --app-hover-overlay);
Windows-red close hover. Drag regions get right padding through a
body-level frameless-platform class.
- Gated by RuntimeCapabilitiesService.usesCustomWindowControls; PWA and
macOS never mount the controls.
Includes unit specs for the component and IPC handlers, an Electron E2E
suite (window-controls.e2e.ts), and a window-chrome section in
docs/architecture/workspace-shell.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(linux): upgrade Electron to 41 for frameless window decorations on Wayland
With the native title bar hidden, Linux windows lost the WM-drawn shadow
and rounded corners. Electron draws client-side decorations only on
native Wayland, and frameless-window CSD (GTK drop shadow + extended
resize boundaries) landed in Electron 41 - before that, frameless
windows render as plain rectangles.
- electron ^39.8.5 -> ^41.7.2 (Wayland auto-detected since 38.2; X11
sessions remain undecorated, matching other frameless Electron apps;
Windows keeps its DWM shadow and rounded corners).
- better-sqlite3 pinned to exactly 12.9.0: the last release shipping
prebuilt binaries for both Node 20 (ABI 115, Jest) and Electron 41
(ABI 145, runtime). 12.10.0 dropped the Node 20 prebuilds, forcing a
from-source build that fails without a C++ toolchain.
- pnpm override node-abi 3.85.0 -> 3.92.0 so electron-builder
install-app-deps can map Electron 41 to ABI 145.
Reviewed Electron 40/41 breaking changes: only the renderer clipboard
deprecation, which this app does not use.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(e2e): address review feedback and window-managerless Linux CI
- Skip the three window-manager-dependent E2E assertions (maximize
toggle, main-process state sync, minimize) on Linux CI: GitHub's
ubuntu runners drive Electron under xvfb without a window manager, so
maximize/minimize state never materializes there. Windows CI and
local Linux/macOS runs keep the coverage.
- WINDOW:TOGGLE_MAXIMIZE now returns the requested state instead of
re-reading isMaximized() right after the call, which races on Linux
window managers where maximize()/unmaximize() complete
asynchronously; the WINDOW:STATE_CHANGED push stays authoritative.
- Skip attaching window-state push listeners on macOS, where the
custom controls never mount and the IPC traffic had no subscriber.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): gate custom window controls on the full bridge surface
Include getWindowState and onWindowStateChange in the
usesCustomWindowControls capability check — the controls rely on both
for initial state and for keeping the maximize/restore glyph in sync
with OS-triggered changes, so a partial bridge should not mount them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Two Electron E2E regressions from this branch's redesigns:
1. Hiding the playlist switcher on /settings (a playlist-scoped control)
also hid the global "Add playlist" button, which lives in the same
header-actions block. A user configuring Settings before importing
their first source had no way to add a playlist — and the settings /
playlist-switcher E2E suites, which add a portal right after saving a
setting, timed out waiting for the button. The Add Playlist button is
a global action, so it now stays visible on Settings; only the
per-playlist context shortcut and bulk actions are hidden there.
2. dashboard-activation.e2e.ts asserted on `dashboard-global-favorites-
rail`, the mixed Favorites rail removed earlier in this branch. The
test now targets the v0.22 rails: live favorites resolve through the
favorites-first `dashboard-live-recent-rail`, and the played movie /
series resolve through `dashboard-continue-watching-rail` (renamed
from `dashboard-recently-watched-rail`). The movie-from-favorites
assertion was dropped — that dashboard surface no longer exists; the
favorites collection still has its own page-level coverage.
Verified locally: dashboard-activation, settings, and playlist-switcher
E2E specs all pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The redesign rollup in this branch replaced the Add Playlist dialog's
2-level type × subtype tabs with a single flat 5-card radiogroup, and
replaced the Settings theme list with a compact segmented control whose
options are just "Light"/"Dark"/"System" (no "theme" suffix). E2E tests
that pinned to the old roles/labels failed in CI:
- basic / xtream / stalker / self-hosted: getByRole('tab', ...) on
"Add via file upload" / "Xtream" / "Stalker" — there are no tabs in
the new dialog. Switched to getByRole('radio', { name: /M3U file/i })
and friends, matched against the new radio labels.
- settings: getByRole('radio', { name: 'System theme' }) — the new
picker uses just "System". Scoped to the [data-test-id="select-theme"]
radiogroup so it doesn't collide with the identically-labelled
cover-size options below.
The Electron `clickDialogSegmentedOption` helper grew a radio-role lookup
as its primary path and keeps the old tab/button/legacy-selector
fallbacks so a future redesign won't break every fixture again. The
"M3U" parent category becomes a no-op (the new picker has no standalone
M3U tile — callers always specialise to M3U URL/file/text immediately
after, which is what we want anyway).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Simplified expect statements for channel items and content cards in favorites and recent tests.
- Added new tests for opening Xtream favorite movies and series inline from favorites without switching playlists.
- Enhanced existing tests to ensure consistent behavior when navigating back from detail views.
- Introduced utility function expectInlineCollectionDetail to reduce code duplication in inline detail checks.
- Improved error handling for back button clicks in detail views.
Entire-Checkpoint: c6e522b4276c