mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
master
483
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6c8f5028c7 |
perf(epg): render only the timeline programmes near the visible range (J3) (#1817)
* perf(epg): render only the timeline programmes near the visible range (J3) Selecting a live channel rendered every programme block of its schedule (about 240 for the Xtream mock) while the stream was starting: about 6,000 of J3's 6,199 renderer.domMutationsToPlaying. The ribbon now renders the blocks, ticks and day dividers within half a viewport of the visible range; the track keeps the full schedule's width, so positions, the scrollbar and scroll-to-now are unchanged. A resize reported before the scroll-to-now must not re-centre the window (it once jumped to the schedule's start and back); resizes only widen it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): sweep the EPG ribbon to see every programme Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(epg): estimate the ribbon window once per channel or mount The live estimate followed the centred day and the 30 s now tick while the ribbon was not yet scrolled: a small scroll across midnight re-centred the window on the next day's noon and left the visible range empty, and the host width was re-read (a forced layout) on every tick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): keyboard Tab reaches programmes beyond the rendered EPG range Greptile flagged that windowing the ribbon could strand keyboard users at the last rendered block. Focusing a block scrolls it into view, which re-windows before the next key press; the new test walks ten unrendered programmes past the range with Tab and with Shift+Tab, and fails if focus ever leaves the ribbon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(epg): re-centre the ribbon window in the same pass as a zoom Review follow-up (Codex): a zoom (button, Ctrl/⌘ wheel, coalesced wheel burst) or a group expansion changed the scale before the anchored scrollLeft landed on a later frame, so the window was the previous centre at the new scale until the next scroll event re-measured it. The ribbon could flash empty or show the wrong section. The zoom controller now hands the window the minute its anchored scroll will centre, and a group expansion the group's centre, together with the new scale (TimelineWindowController.centreOnMinute). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(epg): window the scroll position a zoom can actually reach Review follow-up (Greptile, Codex): a zoom-out anchored right of centre at the ribbon's start computed a negative scroll position. The window centred on it, the browser kept scrollLeft at 0, and with the position unchanged no scroll event re-windowed, so the visible right-hand part stayed empty. The centre now uses the position clamped at 0, and once the frame applies scrollLeft the window re-centres on what the browser kept, which also covers the clamp at the end of the track. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(epg): close the programme popover when the window drops its block Review follow-up (Codex): the ribbon window can remove a focused narrow block on scroll, and a removed node fires no focusout, so the fixed tooltip kept showing a programme no longer on screen. The popover is now a linkedSignal over the rendered items that keeps its state only while its block (by key) is still rendered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(epg): re-measure the ribbon window when the axis origin moves Review follow-up (Codex): a time offset that carries the first programme across midnight moves the axis origin and every track position while scrollLeft and the ribbon stay put, so no scroll event fires and the window kept its epoch-time centre at a different pixel position. The window identity now includes the axis start; when only that changes, the viewport is measured from the ribbon instead of re-estimated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8581bddcaf |
perf(web): keep the NgRx store devtools out of production bundles (#1810)
* perf(web): keep the NgRx store devtools out of production bundles app.config.ts imported @ngrx/store-devtools statically and gated it on AppConfig.production at runtime, so the optimizer kept the module in main.js for the production, PWA and performance builds. The providers now come from environments/store-devtools.providers.ts, an empty list in every build; only the development and electron-e2e configurations swap in the devtools through fileReplacements. A build-config test keeps it that way. renderer.initialBytes: 1,608,610 -> 1,596,045 bytes (-12,565) on a local production build; the baseline is lowered to the measured value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(web): cite #1810 as the initial-bytes baseline evidence Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
acb8cb0318 |
fix(workspace): lead header Back to a parent route when the page opened the session (#1830)
* fix(workspace): lead header Back to a parent route when the page opened the session Settings, Discover, actor and in-portal search registered a header Back that only ran Location.back(). As the first entry of the session (deep link, reload, restored view) that did nothing in Electron and left the app in a browser. WorkspaceBackNavigationService.back(resolveParent) keeps Location.back() while the previous entry is an in-app one, and while that is unknown because the Navigation API is missing. Otherwise it opens the page's parent with replaceUrl, so history Back cannot return to the page: - Settings: the first workspace view (resolveDashboardPath()). - Discover: the catalog section it lists (vod for movies, series for TV). - Actor and search: the portal root, which redirects to its default section within the same navigation. The web E2E opens these pages in a fresh tab: a page.goto in the same tab leaves the previous document behind, often at the parent's URL, so history Back passed without the fix. Electron covers settings after a window reload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(workspace): lead first-entry Back to the parent without the Navigation API Review follow-ups (Greptile): - Without the Navigation API (older Safari and Firefox) back() always called Location.back(), so a page that opened the session still left the app. The service now tracks the router's in-app history depth there (trackRouterHistoryDepth): first navigation 0, push +1, replacement keeps it, a traversal restores the depth recorded for its entry. Depth 0 opens the parent; an unknown depth (an entry from before a reload) keeps Location.back(). - Stalker's Discover (movie/tv section), actor and search pages now have tests that they hand the service the parent under the portal :id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(workspace): adopt the router navigation the Back depth tracker missed Review follow-up (Codex, Greptile): the lazy workspace shell creates the Back service after the first NavigationStart, so the tracker saw only its NavigationEnd, left the depth unknown and counted the next push as the first entry. It now adopts the router's current or last successful navigation when it starts: a first navigation is depth 0, a later one leaves the depth unknown (browser history Back), and a late start of the adopted navigation is not counted again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
93c5f1a051 |
fix(portals): preserve playlist ownership during detail handoffs (#1825)
* fix(portals): preserve playlist ownership during detail handoffs * fix(portals): reload Stalker categories only for a held destination Review follow-ups (Greptile, Codex): resetCategories() reloaded the category resource, and the route session calls it on a portal switch before the destination is resolved and on teardown, so it asked the portal being left, and a failed destination lookup could let that answer repopulate the sidebar. resetCategories() now only clears; the session calls the new reloadCategories() after installing the destination, and only when a handoff had already put that playlist in the store (the owner, and so the resource params, did not change). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0a6f54ca15 |
perf(playlist): make the playlist import and shared UI components OnPush (#1820)
* perf(playlist): make the playlist import and shared UI components OnPush Plan item C6 step 3 for libs/playlist (import/feature and shared/ui): the twelve Eager components switch to OnPush. Two of them rendered plain fields written after an await, outside any template event, which only an Eager check on the next zone tick picked up: - playlist-item's portal status dot (PWA, after the async portal check) now reads a signal; - playlist-info's playlist is backed by a signal behind its existing getter/setter name, so the EPG source list follows removals and file picks that land after awaited cleanup and dialogs. A regression test for each fails on OnPush with the plain field and passes with the signal. The Stalker import's post-await patchValue needs no change (see the zoneless checklist). The m3u feature-player components stay Eager for the playback PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(playlist): check the OnPush dialogs without forcing a render Review follow-ups (Greptile, Codex): - The portal-status and EPG-row tests forced detectChanges() after their await, so they passed with plain fields. They now let the fixture render on its own; with portalStatus back on a plain field the status test fails. - New: the playlist info dialog enables Save and shows the path after a native EPG file pick, without a forced render. pristine and valid read the form's state signals, so the OnPush dialog follows on its own. - New render spec for the add-playlist dialog with the real URL form: Add enables after typing and after a patch from outside the child (as an auto-detect prefill does). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
335392afa9 |
perf(portal): make the portal Eager components OnPush (#1821)
* perf(portal): make the portal Eager components OnPush Plan item C6 step 3 for libs/portal: the nine Eager components in portal/shared/ui, portal/stalker/feature and portal/xtream/feature switch to OnPush. Their templates read signals, signal inputs, async pipes and template-event state; the plain fields they write outside events (playback request ids, save throttles) are not rendered. The already-OnPush live channel lists filled their favorites Maps in a subscription and the Xtream list dropped programme previews after the EPG mapping dialog, all without marking the view. They now call markForCheck like the neighbouring handlers do, so a late favorites answer shows its hearts without waiting for an unrelated check. A regression test for the Xtream list fails without the call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(portal): let the favorites handler's markForCheck render the heart Review follow-up (Greptile): the test forced detectChanges() after the favorites arrived, so it passed without the handler's markForCheck(). It now lets the fixture render on its own; removing the call fails it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5e5b483dca |
fix(workspace): keep the macOS header clear of the lights when zoomed out (#1815)
* fix(workspace): keep the macOS header clear of the lights when zoomed out App zoom scales CSS pixels but not the native traffic lights. At zoom -3/-4 the header column starts near 29 window pixels, so Back and the playlist switcher slid under the lights, and the 27px header band let the lights overlap the context panel below. A shell-level TrafficLightsClearanceDirective now publishes the lights' clearance in CSS pixels (84 x 48 window pixels, from the page zoom factor) on macOS. The header band grows to the vertical clearance (the rail starts its first link at the same band, replacing the rail's own zoom listener), and the header's leading padding grows to the horizontal clearance less the rail column. Both equal the default layout at 100 %; Windows/Linux and the phone layout are unchanged. The native position is shared with the main process as MACOS_TRAFFIC_LIGHTS_POSITION. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(workspace): pass the header clearance poll labels as options Equivalent to the string form, which Playwright 1.62 also accepts, but explicit in every version (Greptile review). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(workspace): check the header switcher before history exists Since the header's history fallback, a list reached by navigation leads with Back. The macOS check now takes the switcher on the first page, which has nothing to go back to, and Back on a detail page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5f21e240e9 |
test(performance): add J4 search journey (#1816)
* test(performance): add J4 search journey Measures typing a six-character query into the header search box on /workspace/search until the global search results settle, on a profile with the M3U fixture and the mock's existing 12,000-item `large` Xtream catalog. Counters: bridge calls and SQL statements per search (with a per-keystroke breakdown), serial IPC depth, DOM mutations, change-detection ticks, layout shift and long tasks; wall-clock last keystroke to settled and first keystroke to first result. Runs in the existing journeys target and the warn-only CI job, whose summary now prints the per-keystroke table. Moves J3's picsum artwork blocker into a shared helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(performance): J4 settles only on the final term's query The probe could settle on cards of an earlier term that stay visible while the final term debounces. The journey now stamps every dbGlobalSearch trace event in the main process, and the record requires the last query between the sentinels to be for the final term and to have completed before the end sentinel. Iterations with a keydown gap over 250 ms (below the 350 ms debounce) are rejected; gaps and the final query are kept as evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(performance): anchor J4's SQL count to the journey sentinels renderer.sqlStatementsPerSearch was the difference of test-side samples taken before the first key and after the end sentinel had been read, so database work in either gap could be counted. The main process now reads main.sqlStatements when the start and end sentinels arrive, and the counter is their difference; sqlStatementsAfterSettled starts at the end sentinel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
22a9c1f1e5 |
perf(web): add an opt-in zoneless change-detection build flag (#1824)
* perf(web): add an opt-in zoneless change-detection build flag
Plan item C6 step 4. app.config.ts takes its change-detection providers
from environments/change-detection.providers.ts, which keeps
provideZoneChangeDetection({ eventCoalescing: true }) for every existing
build. The new electron-performance-zoneless and electron-e2e-zoneless
web configurations are their base configuration plus one fileReplacements
swap to provideZonelessChangeDetection(), so the journeys and the Electron
E2E suite can run zoneless while nothing ships it. zone.js stays in the
polyfills until the flip.
A build-config test pins each *-zoneless configuration to its base plus
the swap and refuses the swap anywhere else.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(epg): schedule the guide's post-render scroll without zone.js
The programme guide jumps to now once the virtual list first renders rows,
and focuses cells after keyboard scrolls, from afterNextRender hooks
registered in CDK and RxJS callbacks. zone.js followed those callbacks
with a tick; under zoneless change detection a render hook schedules no
render, so the guide opened at midnight (epg-guide.e2e.ts on the zoneless
build). The guide now marks itself when it registers one, which is
harmless with zone.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(performance): record the zoneless flag measurements and E2E run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(performance): say the zoneless flag ran with the three implemented journeys
Review follow-up (Greptile): J4 search is still planned, so the flag was
validated with J1-J3 and the Electron E2E suite, not all four journeys.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: 4gray <fourgray@proton.me>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
e17ee53a22 |
perf(workspace): make the workspace shell Eager components OnPush (#1819)
Plan item C6 step 3 for libs/workspace: the seven Eager components in workspace/shell/feature, including the workspace shell root the idle audit found re-rendering on every idle tick. Their templates read signals, signal inputs, computed values and template-event state only; the one plain field written outside the template (categoryLockTarget) is not rendered. They switch to OnPush without other changes. Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
77458b3931 |
perf(web): make the app root and settings components OnPush (#1823)
* perf(web): make the app root and settings components OnPush Plan item C6 step 3 for apps/web: the fifteen Eager components switch to OnPush, among them the app root and the update notification panel that the idle audit found re-rendering on every idle tick. Their template state is signals from the settings facades, signal inputs and the shared reactive settings form. The checklist flagged the backup import, which patches the form from a detached file input with no template event. A new spec patches only a value, which changes no form status, and confirms the OnPush general section still shows the new theme; it guards that path for the zoneless flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(settings): re-render OnPush sections when the form changes outside them Review follow-ups (Greptile, Codex): - The settings sections read form values in their templates (selected theme and cover size, epgField.value, form().value.player), and the parent changes the form outside their events: Discard and backup import patch it, the store hydrates it, the EPG file picker sets a control after an await. Under OnPush the section kept the old selection or EPG status. Each section now marks itself on its form's events (markSectionForCheckOnFormEvents). - The value-only patch test no longer forces detectChanges(); with the fixture rendering on its own it fails without the marking, and so does a new test for a control set outside the EPG section. - The zoneless guard counts only changeDetection metadata outside comments, so a comment naming the strategy is not an Eager component. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(settings): guard the unsaved-changes bar after a save off the sections Review follow-up (Codex): Save marks the form pristine after an async store write, also on Backup, Reset or search, where no form section is rendered. The OnPush page re-renders anyway because pristine and valid read the form's state signals; the new test checks that on the Backup page without forcing a render (it waits for the scheduled one). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2e321cb1bd |
chore(deps): bump mpegts.js from 1.8.1 to 1.8.2 (#1835)
* chore(deps): bump mpegts.js from 1.8.1 to 1.8.2 Bumps [mpegts.js](https://github.com/xqq/mpegts.js) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/xqq/mpegts.js/releases) - [Commits](https://github.com/xqq/mpegts.js/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: mpegts.js dependency-version: 1.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * test(playback): accept the mpegts.js 1.8.2 error contract 1.8.2 keeps the public ErrorTypes and ErrorDetails exports unchanged, so the version lock moves to 1.8.2 with the same accepted contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3cc5af1492 |
perf(playback): make the web players and M3U player OnPush (#1822)
* perf(playback): make the web players and M3U player OnPush Plan item C6 step 3 for playback: the eight Eager components in libs/ui/playback (video.js, ArtPlayer, HTML5/hls/mpegts, audio player, web player view, VOD details, sidebar, external-player dialog) and the M3U video player and VOD detail switch to OnPush. The player libraries' events already reach the UI through the signal-backed controls adapter or outputs, and the players' DOM belongs to the libraries. The M3U video player rendered three plain fields written outside template events: the channel-number overlay, cleared by a 2 s debounce timer, and the player choice, written from an IndexedDB read and a settings effect. They are signals now, and a test checks that the overlay leaves the DOM when the timer fires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(playback): let the overlay's signal write schedule its own render Review follow-up (Greptile): the test forced a render with fixture.detectChanges() after the debounce timer, so it would pass even if the signal write stopped scheduling an OnPush render. It now runs the fixture with autoDetectChanges and only advances the fake timers; with plain fields under OnPush the overlay never renders and the test fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e315467c2d |
test(perf): record which nodes move in a journey's layout shift (#1845)
* test(perf): record which nodes move in a journey's layout shift J2's renderer.layoutShiftScore went from 0.222 to 0.233 with #1814, and its evidence only held the recent-input / without-recent-input split, so the moved element could not be named from a summary. The probe now keeps the first 20 counted shifts (value, recent input, time since the journey start and the moved nodes, as J1's late shifts do), and J2 writes them to evidence.layoutShift.shifts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(perf): record the horizontal move of layout-shift sources J2's 0.233 shift on the runner moves main.workspace-content, the header search field and the header actions with deltaY and deltaHeight 0, so the move is horizontal and the probe could not show it. Sources now also carry deltaX and deltaWidth. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(perf): count every layout shift next to the capped list Review follow-up (Greptile): the score counts every shift but the evidence lists only the first 20, so a reader could not tell that later shifts were omitted. The probe now keeps shiftCount, and J2 writes it beside evidence.layoutShift.shifts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d73bb7ca16 |
chore(deps): bump shaka-player from 5.2.4 to 5.2.12 (#1836)
* chore(deps): bump shaka-player from 5.2.4 to 5.2.12 Bumps [shaka-player](https://github.com/shaka-project/shaka-player) from 5.2.4 to 5.2.12. - [Release notes](https://github.com/shaka-project/shaka-player/releases) - [Changelog](https://github.com/shaka-project/shaka-player/blob/v5.2.12/CHANGELOG.md) - [Commits](https://github.com/shaka-project/shaka-player/compare/v5.2.4...v5.2.12) --- updated-dependencies: - dependency-name: shaka-player dependency-version: 5.2.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * test(playback): accept the Shaka 5.2.12 error contract 5.2.12 keeps the public error severities, categories, codes and request types of 5.2.4, so the version lock moves to v5.2.12 with the same accepted contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0a006cb027 |
ci(perf): enforce the journey counters stable on master (#1829)
* ci(perf): enforce the journey counters stable on master Promote the J1, J2 and J3 counters that were identical in all 55 measured iterations of the 11 master runs from 2026-10-03 to 2026-10-04 to journey-baselines.json, and check them in the Performance journeys job (still warn-only), in one step together with #1828's two validated J1 entries. None of the new ones has Principle 3 evidence, so each carries a "guard only, not validated" note that the checker prints with a failure. A performance-tools test keeps the job's --only list equal to the journey entries. Number formatting uses three decimals, the precision of the layout-shift scores. J2 renderer.layoutShiftScore is 0.233, not the window's 0.222: every master run from #1814 (page Back buttons in the header) on reads 0.233. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci(perf): check the journey counters whenever a summary was written Review follow-up (Greptile): the check ran only after the composite action succeeded, so a failed job-summary report after a written summary.json skipped every baseline. It now runs unless the job was cancelled, as long as the action produced a summary path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b315f8564d |
fix(electron): show the main window once its document has loaded; enforce J1 IPC and mutation counters (#1828)
* fix(electron): show the main window once its document has loaded; enforce J1 IPC and mutation counters Re-lands #1788, which merged into #1782's branch after #1782 had already reached master, so none of it is on master. The hidden main window was shown on ready-to-show only. On Linux under X11, when the startup scripts run before the window's first frame, the next frame comes about a second later: nothing is on screen and the splash's requestAnimationFrame waits, so J1's first card came ~940 ms after load instead of ~480 ms in most runner launches (18 bridge calls / 1,031-1,033 DOM mutations instead of 15 / 558). The window is now shown at ready-to-show or the main frame's did-finish-load, whichever comes first, with the splash colour as its background so showing before the first paint does not flash. The journey gate keeps the app's did-finish-load listeners away from its about:blank detour, as it already does for ready-to-show. Three dispatched runs on this branch (37192092882, 37192097790, 37192103151) read 15 calls and 558 mutations in all 18 iterations, stable: true. Both become baselines (slack 0), and the Performance journeys job checks them with check-journey-ratchet.mjs --only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(perf): record the evidence PR of the J1 runtime baselines Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: 4gray <fourgray@proton.me> |
||
|
|
b782243760 |
test(performance): skip every test-only file suffix in the zoneless guard (#1831)
* test(performance): skip every test-only file suffix in the zoneless guard #1813 added serial-details.test-stubs.ts, whose stub components set ChangeDetectionStrategy.Eager. The zoneless checklist guard listed only some test-only suffixes, counted the stub file as production code and has failed the performance-harness job on master since. It now skips every `.spec` / `.test` file with or without a suffix, test-setup.ts and test-stubs/ directories. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(performance): accept multi-segment test-only suffixes in the zoneless guard Review follow-up (Greptile): `(-\w+)?` allowed one suffix segment, so a file such as `rail.test-data-stubs.ts` would be scanned as production. The suffix now repeats, and a classifier test pins which names are skipped and which ship. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2738bc28a1 |
docs(portals): document forced MPV/VLC launch and pending-start contracts (#1809)
* docs(portals): document forced MPV/VLC launch and pending-start contracts The rules #1792 settled for forced external launches and playback-start bookkeeping lived only in code comments and specs. Record them as contracts in the owning documents: - embedded-inline-playback.md: the shared detail-host rules (one external player per title, unconfirmed teardown cancels, ownership rechecked after every await, owner-scoped pending state). - xtream-portal-compatibility.md: the series launch chain, page-token duplicate guard and queued choice. - vod-multi-source.md: the movie menu launch and reset follow the copy the primary button acts on; pending resets are a list of targets. - stalker-portal.md: the per-series launch queue, the batch-held choice and the movie launch/reset pending start. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(portals): correct launch-contract claims against the code - A launching session is published before the launch IPC resolves; what it lacks until then is an exact closer. - The series watched/reset batch and the Xtream movie launch gate are page-wide, not owner-scoped. - Only the Stalker movie hosts retire a pending start, and that does not clear the repeat guard of a launch still in flight. - Name only the specs that exercise the Xtream series rules. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(portals): tighten closer timing and page-wide gate wording A launching session may already have its closer before the launch IPC resolves, the Xtream movie launch gate does not cover the inline player's diagnostic fallback, and the hero-state spec covers only the external-player and reset rows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(portals): fix stale session-timing comment and search guard wording - serial-details-external-launch.ts: Electron publishes a `launching` session as soon as the launch IPC arrives, not only after the launch settles. Comment only; no behaviour change. - stalker-portal.md: the search host's selection check does not include the content type; a switch to a series supersedes the launch through the playback owner key instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
2dfdd65f53 |
refactor(details): give the detail-page files real max-lines headroom (#1813)
* refactor(xtream): split the series details page into focused services serial-details.component.ts sat at the 400-line max-lines limit and its playback service and spec were close behind. Move cohesive concerns out without changing behavior: - route params, the provider-only flag and the (re)load of the addressed series go to SerialDetailsRouteService; the component still registers the effect, so effect order is unchanged - season descriptions, posters and the TMDB season enrichment go to SerialDetailsSeasonsService - actor, Similar and Discover navigation go to injectXtreamDetailNavigation, shared with the movie page - the watched toggles and the episode playback payload leave SerialDetailsPlaybackService for SerialDetailsWatchToggles and buildSerialEpisodePlayback - the spec's TestBed moves to a harness and the watched-toggle cases to serial-details.season-watch.spec.ts Counted lines: component 400 -> 318, playback service 388 -> 342, component spec 1196 -> 674. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(xtream): move VOD route selection and position state out of the page vod-details-route.component.ts grew from 618 to 741 counted lines and vod-details-playback.service.ts reached 395. Extract without changing behavior: - the route-derived read model (selected movie, category, catalog item, fallback view, multi-source identity, session and content keys) goes to VodDetailsSelectionService; the component keeps the same member names - trailer state and the Similar-rail click move into the hero presenter, the cancel-download prompt and the progress-ring geometry into the downloads service, navigation into injectXtreamDetailNavigation - stored positions (last seen, route row, guarded load) become VodDetailsPositionState and the external-launch bookkeeping becomes VodExternalLaunchClaim - drop the unused MatTooltip import (NG8113) and eight unused imports Counted lines: route component 741 -> 492, playback service 395 -> 344. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(stalker): extract series-view positions and watched toggles stalker-series-view.component.ts grew from 1601 to 1813 counted lines and its spec reached 1198 of 1200. Move code out verbatim: - saved positions, their reconcile and the persist/clear writes go to the component-provided StalkerSeriesPositionsService; the ordering of reads and writes goes to StalkerSeriesPositionQueue - episode, season and series watched toggles go to StalkerSeriesWatchToggleService, the batch core to runStalkerWatchToggleBatch - the lazy VOD season loads go to StalkerVodSeasonEpisodeLoader Every effect stays registered in the component constructor in its original order; template bindings and public member names are unchanged. The spec setup moves to a shared harness and the spillover-prefetch and TMDB season cases to their own specs; the 184 cases of the directory are unchanged. Counted lines: component 1813 -> 1136, component spec 1198 -> 670. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(stalker): extract catalog-detail position and search paging - stalker-catalog-detail.component.ts (397): the stored VOD position and its runtime updates become StalkerCatalogVodPosition, the Play/Resume start becomes startStalkerCatalogVodPlayback - stalker-search.component.ts (776, baselined): the paging resource, the accumulated results and the parental-lock bookkeeping become StalkerSearchPagingController, with pure helpers in stalker-search-results.util.ts. The spec reaches the moved members through component.paging; its cases and assertions are unchanged. Counted lines: catalog detail 397 -> 317, search 776 -> 482. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(ui): give season-container and vod-details real line headroom - season-container.component.ts (396): season auto-selection and the seasonSelected emission move to createSeasonAutoSelectState, called at the same place in the constructor so effect order is unchanged; the episode subline becomes buildEpisodeSubline - vod-details.component.ts (393): the cross-portal Similar loader, the provider-only download state and the actor/Similar route helpers move to sibling modules Inputs, outputs, selectors and public members are unchanged. Counted lines: season container 396 -> 341, vod details 393 -> 337. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(web-e2e): move Stalker portal helpers into fixtures stalker.e2e.ts was at 1196 of 1200 counted lines. Move the mock endpoints, scenario MACs and page helpers to stalker-portal.fixture.ts and the embedded-series steps three tests repeated to stalker-embedded-series.fixture.ts. Every test stays in stalker.e2e.ts in the same order, with the same serial mode and OWNED_MACS reset. Counted lines: 1196 -> 981. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: follow the detail-page extractions Name the Stalker watched-toggle and position services that now own the batch and reconcile code, point AUTH_REJECTED_MAC and the scenario MACs at stalker-portal.fixture.ts, and drop two stale statements about components sitting at the max-lines cap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(xtream): note why the seasons service is injected last Its TMDB enrichment effect keeps the position it had as the first effect of the component constructor only while it is created after the other services' effects. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
7a629f5fe5 |
fix(dashboard): hero legibility in the light theme and stable page heading (#1811)
UI-24 from the UI consistency audit.
- No-artwork slides paint their gradient in CSS from the slide hue: a light
tint in the light theme, unchanged near-black in the dark one. The dark
gradient under the light page-coloured scrim read as a grey slab.
- The side scrim holds 88% of the page colour up to the slide's right edge
(inset + min(560px, 55%)), so the end of a full slide no longer sits on
about 45%.
- Narrow layout (container <= 720px): a full-bleed 90% scrim behind the text
block, a scrim-coloured text shadow, and an entrance without a fade so
that scrim never flashes the art on a rotation.
- --hero-body is 85% of the heading colour (was 72%).
- Light --app-rating-color #a16207 -> #7a4a00: measured 3.36:1 on the chip
over artwork, now 5.10:1. The details pages share the chip and token.
- Buttons cap at the slide width and end long labels in an ellipsis.
- The page gets one visually hidden h1 ("Dashboard"); slide titles are h2.
- One live region outside the re-created slide announces slide changes;
progress bars are named and VOD ones read "N% watched"; dots are 24px.
dashboard-hero-legibility.e2e.ts replaces every image with a checkerboard
and measures each piece of slide text from the screen in both themes, wide
and narrow, for backdrop, poster, no-artwork and live slides. On master the
worst cases were 2.35:1 (body text) and 2.65:1 (pills).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
26ca8e2cb0 |
perf(ui): make the libs/ui Eager components OnPush (#1818)
* docs(performance): inventory the zoneless change-detection migration Plan item C6 step 2. docs/architecture/zoneless-migration.md lists the 66 production files (67 components) that still set ChangeDetectionStrategy.Eager, the ten places where a template-read plain field is written outside an Angular event, the NgZone and ChangeDetectorRef calls to remove at the flip, and the IPC, player, observer, timer and dialog paths checked as signal-safe. On Angular 22 an unset changeDetection already means OnPush, so only the explicit Eager components re-render on every tick. zoneless-migration.spec.ts in the performance harness compares the checklist with the code: a new Eager component, or a converted one left unticked, fails it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(ui): make the libs/ui Eager components OnPush Plan item C6 step 3 for libs/ui/components and libs/ui/epg: eleven components (twelve with the EPG trust dialog) set ChangeDetectionStrategy.Eager and were checked on every tick, among them the always-mounted EPG progress panel the idle audit found re-rendering on every idle tick. Their template state is already signals, signal inputs, immutable dialog data or fields written from template events, so they switch to OnPush without other changes. The epg-item-description spec mutated dialog data after creation and marked only the fixture's host view; it now marks the component's own view, which OnPush requires. The libs/ui playback and remote-control components stay Eager for their own PRs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
84aef83a6c | feat(workspace): move page Back buttons into the header and add a history fallback (#1814) | ||
|
|
6f247fb538 | fix(workspace): start the macOS rail below the traffic lights (#1806) | ||
|
|
7fd3d1dab0 | fix(tools): capture the Xtream guide shot against the current connection test (#1807) | ||
|
|
bc5a7fcbf9 |
fix(playback): keep the saved Embedded MPV player when the mpv check is inconclusive (#1803)
* fix(playback): keep the saved Embedded MPV player when the mpv check is inconclusive On Linux native-view the support check runs `mpv --version` by bare name and waits for the login shell PATH first. Since #1784 that lookup is asynchronous with a 10 s budget; when it ran out, the check ran on the inherited PATH and answered a plain `supported: false`. The settings store took that as a verdict and persisted the default player over a saved Embedded MPV selection. The main process probed again once the shell answered, but nothing restored the setting. `EmbeddedMpvSupport` now carries `inconclusive`. The native service sets it on a missing mpv while its probe has only seen the inherited PATH; the IPC handler declares that state before probing and registers the re-probe before the check, so a throwing check cannot leave it stuck. Every other answer stays final. Consumers no longer settle on an inconclusive answer: the settings store keeps the saved player, and the command palette and the settings search probe again on their next use instead of caching it for the session. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(playback): keep asking for Embedded MPV support while the answer is inconclusive Keeping the saved player on an inconclusive answer left a mounted player stuck on it: the session controller asked for support once, in its constructor, and the session effect never starts while unsupported, so the player did not recover after the login shell answered. The settings page held its one answer the same way. `watchEmbeddedMpvSupport()` asks again every 3 s until the answer is final or the surface is destroyed. The player controller and the settings page facade load support through it, so playback starts by itself and the Embedded MPV option appears without reopening the page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(settings): follow an inconclusive Embedded MPV answer to a final decision The settings store checked a saved Embedded MPV selection once. After an inconclusive answer it kept the selection and never looked again, so when mpv turned out to be really missing the player stayed on Embedded MPV instead of falling back to the default one. The store now follows the answer with `watchEmbeddedMpvSupport()` until it is final and only then decides. It acts on an answer only while Embedded MPV is still the saved player, so a player picked meanwhile, also while the first answer was pending, is never overwritten. The watch backs off from 3 s to 30 s between rechecks, so a login shell that never answers does not keep the app polling at the first rate, and it no longer schedules a recheck after its answer handler stopped it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(settings): keep the settings search following an inconclusive Embedded MPV answer The settings page asks the search service for Embedded MPV support once, when its search facade is created. After an inconclusive answer the service only probed again on its next call, so with the page left open the Embedded MPV rows stayed unsearchable after the login shell answered, while the player option on the same page already updated. The service now follows the answer with `watchEmbeddedMpvSupport()` until it is final, which updates the open page and the command palette alike. A call made while the answer is still inconclusive restarts the watch, so it asks at once as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(settings): follow Embedded MPV support for search only while the settings page is open The settings search service is provided in the root injector, so the watch it started on its first use had no owner: with a login shell that never answers it kept asking every 30 s until the app quit, long after the settings page or the command palette that needed the answer was closed. A failed recheck also ended the watch as if it were a final answer, hiding the Embedded MPV rows for the rest of the session. The service now separates the two uses. `ensureEmbeddedMpvSupportLoaded()` is a single request again, for the command palette. The settings page calls `followEmbeddedMpvSupport()` and ends it when the page is destroyed. Only a final answer is kept: after an inconclusive one or a failed request the next use asks again, for the palette's player commands too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
040acbd976 |
feat(tools): land mixins from another Sass module where they are included (#1795)
A mixin included from another Sass module (`@use 'x'; @include x.m`) now lands where it is included, as a same-file mixin already did: its top-level weights meet the including rule's family, and its family becomes that rule's, in the order Sass writes them out. Include sites resolve through the existing `@use`/`@forward` scope resolution, the definition Sass resolves is the one that runs, and an include inside a mixin body resolves where that mixin runs. The header's "Not traced" list keeps what stays out (positional arguments, content blocks placed by another module's mixin, a name two `@import`ed files define). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e8b181fcea |
fix(ui): Cyrillic/Greek weights, html lang, weight normalisation (#1780)
Load Roboto 600/700 and DM Sans 700 so Cyrillic and Greek headings render real semibold and bold faces instead of a synthetic bold, keep <html lang> in step with the UI language, and move every font weight onto the 400/500/600/700 scale (JetBrains Mono at 500 or lighter; the dashboard LIVE badge now uses the interface font at 700). Add the `styles:font-weights:validate` ratchet guard and its CI step. It reads stylesheets much as Sass and the browser do (cascade, layers, mixins, content blocks, `@extend`, `@at-root`, `:is()`/`:where()`, keyframes) and lists what it deliberately does not trace in its header. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ab8460338a | feat(ui): cinematic movie and series details pages and dashboard hero (#1792) | ||
|
|
0768ae5ea2 |
ci(i18n): fail on new English-identical translations (#1793)
* ci(i18n): fail on new English-identical translations The drift check only warned about locale values identical to English, so untranslated strings kept landing. It now fails on any such value that tools/i18n/identical-en-baseline.json does not record for that locale and key. The baseline captures today's 2,015 entries: legitimately identical values (brand and technical names, language autonyms, PIN) and the existing debt. An entry only covers the English text it recorded, so copying reworded English into a locale fails too. Baseline entries that are no longer identical are reported, not fatal. `pnpm run i18n:baseline:update` rewrites the baseline deliberately; CI runs `pnpm run i18n:validate` (node tests, then the check) and never rewrites it. `--fail-on-identical` remains as a strict audit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(i18n): keep the baseline intact on failed updates and strict audits `--update-baseline` now writes nothing while any locale is unreadable or has missing or extra keys, so an incomplete translation cannot reshape the baseline. `--fail-on-identical` no longer reads the baseline it ignores, so a damaged file cannot block a strict audit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4adc3ba20f | fix(ui): one watch-progress colour in app chrome and in the player (#1798) | ||
|
|
a8dd1eaa97 |
fix(import): consistent add-source forms with masked passwords and URL errors (#1796)
* fix(import): consistent add-source forms with masked passwords and URL errors - Mask the Xtream password in add and edit (and the Stalker one in edit) behind a shared PasswordVisibilityToggleDirective: one translated "Show password" label, state in aria-pressed, type="button". - Give the Xtream server URL its own mat-error and a neutral hint instead of the EPG file error; give the M3U URL a mat-error. - Use "Playlist title" in every add form, "MAC address" casing, a single ellipsis in "Validating portal…" and one "Add playlist" submit label; translate the method radiogroup's aria-label. - Show Stalker refusals inline under the portal URL (role="status", like the Xtream connection test), translated in the template and cleared by edits; translate the snackbars for outcomes that close the dialog. - Translate new strings into all locales; reuse the identical Stalker URL error translations; fix MAC casing and ellipses; drop unused keys. - Unit specs per form, edit-dialog spec, new add-source-forms web E2E; update E2E locators; UI guidelines Forms section; Stalker contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(import): mask the password again when an add form is cleared Clear erased the password but left the visibility toggle on, so the next password typed in the Xtream or Stalker form showed in plain text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d677fbaf8c |
perf(electron): look up the login shell PATH without blocking the main thread (#1784)
* perf(electron): look up the login shell PATH without blocking the main thread fix-path ran $SHELL -ilc env synchronously right after the first load. With a typical zsh profile that held the main thread for 1-2 s, while the database worker's ready message and the renderer's first IPC calls waited, so the launch journey's first card came that much later. Use shell-path's async shellPath() with fix-path's fallback, so the resulting PATH is the same and the main thread stays free. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(perf): name the PR that made the login shell PATH lookup async Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): let bare-name player spawns wait for the login shell PATH With the lookup now asynchronous, an external player launched (or the Linux embedded MPV support check, which runs and caches a bare `mpv --version`) within the first seconds could see the inherited PATH. The OPEN_MPV_PLAYER / OPEN_VLC_PLAYER handlers and every embedded MPV handler now await waitForLoginShellPath() (settled lookup, at most 10 s, immediate on Windows), restoring the guarantee the blocking lookup gave. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): wait for the login shell PATH only for bare-name spawns External players wait only when they resolve to a bare name (no configured path, no well-known install found); a path to an executable starts at once. Embedded MPV waits only on Linux and only for support and prepare, which run the cached bare-name `mpv --version` check; sessions and controls never wait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): wait for the login shell PATH only before the mpv probe Embedded MPV support and prepare waited on every Linux call, although getSupport() returns before the bare-name `mpv --version` probe for the frame-copy engine, native Wayland, a disabled feature or a cached result. willProbeLinuxMpvExecutable() now gates the wait on the probe actually running. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): stop waiting for a login shell that already timed out once After the first wait for a hung `$SHELL -ilc env` runs out, later bare-name player launches and Linux mpv probes proceed at once instead of each waiting the full limit again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): bound login shell PATH waits by the lookup's own budget Replace the latch on the first expired wait with a deadline set when the lookup starts (10 s). Every wait ends when the lookup settles or the deadline passes: a launch retried while the shell is still within its budget waits for the PATH again, and once the budget is spent no launch waits, so a hung shell still delays at most the first seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): re-probe a missing mpv once a late login shell answers When the PATH lookup runs out of budget, the Linux embedded MPV support check probes `mpv --version` with the inherited PATH and caches a missing result for the rest of the session. waitForLoginShellPath() now reports whether the lookup settled; after a timed-out wait the handler forgets a cached "missing" once the lookup finishes, so the next support check probes again with the login shell PATH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): share one deadline among waits before the PATH lookup starts A bare-name launch that waited before the lookup was scheduled started its own 10 s limit, so while startup was stuck every retry paid the full delay again. The first early wait now sets the shared deadline; the lookup still replaces it with its own budget when it starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): re-probe mpv after a late login shell PATH either way A Linux mpv probe that ran on the inherited PATH can be wrong in both directions: the login shell PATH may add mpv or drop the directory the inherited one found it in. forgetLinuxMpvExecutableProbe() now clears a found result as well as a missing one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): skip the login shell PATH wait for Flatpak host launches In Flatpak, players start through `flatpak-spawn --host`, which resolves the name with the host's PATH; the sandbox's login shell lookup cannot change it. The launch handlers now decide from the same launch context the player uses: no wait in flatpak-host mode, otherwise only for a bare player name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cb252940b2 | fix(workspace): move detail Back into the header and drop the rail brand (#1789) | ||
|
|
c9d169e3dc |
fix(dashboard): scroll a focused rail card fully into view (#1785)
* fix(dashboard): scroll a focused rail card fully into view Chromium skips its focus scroll when 32px or more of the element already shows, so Tab onto the last source card of a rail that overflows by less than a card left it half-hidden under the edge fade. The rail track now handles focusin and scrolls to the first card-start snap position that reveals the whole card; a plain "nearest" scroll is not enough because mandatory snapping can round it back (seen on the live channel rail). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dashboard): keep mouse clicks on partly hidden rail cards A mouse press focuses the card link on mousedown. Revealing the card at that moment could slide it from under the pointer when the target snap position overshoots (the live channel rail moves 316px for a 306px card), so the click landed elsewhere. The rail now reveals a card only for keyboard and programmatic focus, using the CDK FocusMonitor origin. Adds an Electron E2E that checks the final layout after snapping: Tab and focus() leave the last source card fully visible, and a mouse press keeps the rail still and still opens the source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dashboard): tell pointer focus apart without touching the DOM FocusMonitor toggles cdk-*-focused classes on the monitored track, so a mouse press on a source card mutated the DOM before the click. The J2 "open a source" performance journey rejects iterations with DOM activity between its settle snapshot and the click. Read the input modality from the CDK InputModalityDetector in a focusin handler instead: it only listens, so the rail stays untouched until the click. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dashboard): reveal script-focused rail cards after a mouse click The input modality stays "mouse" after any click, so a later focus() on a partly hidden card left it clipped. The rail now skips the reveal only for focus caused by a press inside the track: the focus has to arrive within 100ms of that pointerdown (650ms for touch, whose focus comes with the tap's compatibility mouse events, as in the CDK FocusMonitor). Only event timestamps are compared, so the DOM still stays untouched before the click. The E2E now clicks elsewhere before the script focus, and unit tests cover a tap and focus() after an earlier mouse press. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dashboard): keep an over-wide focused rail card in view In a window narrower than a card (or under zoom), a focused card could never fit, so its own snap offset fell short of the needed scroll and the rail jumped to the next card's snap point, moving the focused card offscreen. Such a card is now aligned at its own start instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(dashboard): select rail internals through stable test ids The dashboard contract makes data-test-id hooks the supported Electron E2E selector surface. The rail now exposes -viewport, -track and -card-link hooks next to its existing ones, and the focus E2E selects those (and the rail heading by role) instead of internal class names. The dashboard doc lists the new hooks and records the focus-reveal contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
23a1860119 |
fix(ui): destructive confirmations, verb labels and provider icons (#1783)
* fix(ui): destructive confirmations, verb labels and provider icons
Confirmations: ConfirmDialogData.confirmLabel is required, so no dialog can
fall back to "Yes"/"No"; the dismiss defaults to "Cancel" and
`tone: 'destructive'` styles the confirm with .app-destructive-button. Every
caller names its action ("Remove playlist", "Clear", "Refresh playlist",
"Cancel download" with a "Close" dismiss). The confirm button has the
confirm-dialog-confirm test id and drops its no-op color="primary".
The no-op `warn` color input becomes .app-destructive-button on the EPG
mapping, playlist item, error view, EPG/reset settings, delete-all and source
cleanup buttons, and on the unsaved-changes dialog's Discard.
Provider icons come from SOURCE_TYPE_ICONS in shared/interfaces (Xtream
cloud, Stalker cast, M3U playlist_play / link / description / subject) in the
add dialog, auto-import, empty state, playlist switcher, playlist rows,
dashboard source rail, command palette, Sources filters and both reset
summaries. Stalker no longer borrows the Dashboard icon, and Xtream no longer
shares a glyph with M3U URL playlists.
The playlist error view removed a playlist through the stale
PlaylistActions.removePlaylist: it dropped the playlist from state before the
delete ran, swallowed failures, skipped the source activity guard and showed
no toast. It now uses PlaylistDeleteActionService like every other removal,
commits only a completed delete, toasts and goes home. The unused action and
its effect are removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): one provider icon per playlist row, imperative Korean remove label
A restored Stalker or Xtream playlist can also carry a URL, and the row's
independent checks then showed the M3U URL icon next to the provider icon.
The row now switches on resolvePlaylistSourceIconKey(), the precedence every
other surface uses, so each source shows exactly one icon.
HOME.PLAYLISTS.REMOVE now names the confirm button and the row's delete
tooltip; in Korean it read "the playlist has been removed". It now says
"remove playlist", like every other locale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): keep the auto-refresh badge on playlist rows with one provider icon
Showing one provider icon per row moved the auto-refresh badge into the M3U
branches only, so a restored Stalker playlist with a URL and auto-refresh
lost it although the URL is still re-fetched. The row now renders one icon
container: the provider icon from the shared precedence, then the badge for
any row with a URL or a local M3U, exactly the rows that showed it before.
The Xtream portal-status dot, used without source health, keeps that corner.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): let the playlist row's cancel action render in the error color
The row's action buttons set `color: inherit`, and the selected row does so
again with more specific selectors. Both beat Material's token-driven icon
color, so the .app-destructive-button cancel action kept the row color
(selection blue on the active row). Pin the cancel button to
--mat-sys-error in both row states.
The large-deletion Electron E2E now checks the cancel color in both themes;
without this rule it reads rgb(47, 123, 255) instead of the error red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(ui): give the dialog service spec the now-required confirm labels
ConfirmDialogData.confirmLabel became required, and the spec still built
confirmations without one. Jest only transpiles, so the suite stayed green,
but the "Typecheck Jest spec programs" CI step rejected it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
b6357da6af | fix(dashboard): keep the hero skeleton until every hero source has loaded (#1782) | ||
|
|
572034f3be | fix(ui): declare Material system tokens and migrate dead --mdc overrides (#1775) | ||
|
|
e4cf48fdc2 |
test(perf): count change-detection ticks in the electron-performance build (#1776)
* test(perf): count change-detection ticks in the electron-performance build J1 renderer.cdTicksToFirstCard, J2 renderer.cdTicksToFirstPage and the J1 idle baseline renderer.cdTicksIdle30s. Angular's ɵsetProfiler is only reachable through the dev-mode window.ng global, so the electron-performance configuration alone swaps environment.ts for environment.performance.ts, which re-exports the production AppConfig and wraps ApplicationRef._tick. Production and PWA sources and output are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(perf): refuse a J1 idle window that opened late after the settle point Addresses review: the idle window opens in the settle timer's callback while the settle point is that timer's deadline, so a late callback left ticks uncounted between the two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5a4d7a8a11 |
test(perf): measure the serial IPC depth before the J1 first card (#1773)
* test(perf): measure the serial IPC depth before the J1 first card Adds renderer.ipcSerialDepthToFirstCard to the launch journey: the length of the longest chain of bridge calls in which each call started after the previous one completed, among calls that completed before the first card. The main IPC capture now records the ordered start/completion timeline; the depth, its lower bound, the chain and the timeline are per-iteration evidence, and the CI job summary prints the chain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(perf): keep the IPC timeline consistent around the J2 start marker A call that started before the start marker no longer records its completion in the timeline, and completions of a method with calls in flight both inside and outside the timeline are attributed outside and counted, instead of skipping the first marker-method completion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d1e79bdc3e | fix(parental-lock): per-flow PIN dialog labels and visible mismatch error (#1777) | ||
|
|
8b6fcf3560 | fix(e2e): let web-e2e:e2e run outside CI (#1779) | ||
|
|
6e229f85b6 |
test(perf): add the J3 playback journey (#1774)
* test(perf): add the J3 playback journey J3 clicks a live channel of an Xtream portal and ends at the built-in HTML5 player's first `playing` event, with `loadedmetadata` as a secondary phase. It follows J2: every iteration is a fresh J1 launch on a copy of a profile seeded through the app's dialogs, and the click happens after the app has settled in the portal's first live category. The portal is the mock's `live-fallback` account, whose `.ts` live URLs serve the local H.264/AAC MPEG-TS fixture that mpegts.js plays through MSE on every platform. The marketing accounts' local live bytes are zero-filled and never reach `playing`. Seeding selects the HTML5 player and the `ts` stream format; the catalog's picsum.photos logos are cancelled from the test side so no request leaves the machine. Counters: renderer.ipcCallsToPlaying, renderer.httpRequestsToPlaying, renderer.domMutationsToPlaying, renderer.layoutShiftScore and renderer.longTasks; wall-clock click->loadedmetadata and click->playing. renderer.ipcSerialDepthToPlaying is listed as unavailable until the serial-depth helper lands. No baseline yet. The probe gains a media-event terminal; J2's pre-click settle moves to journey-click-settle.ts so both journeys share it unchanged, and the probe spec's jsdom fixtures move to a shared test helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(perf): show J3's HTTP boundary margins and watch 1 s after playing Review follow-up. renderer.httpRequestsToPlaying compares the ledger's arrival stamps with the renderer's click and playing stamps, which come from different processes on the same host clock. Each iteration now records the distance of the nearest request on either side of both boundaries, so a count a clock difference could flip is visible. Requests after playing were a single snapshot taken right after the probe; the test now watches the ledger for a fixed 1 s after playing. A live stream never leaves the mock quiet, so J2's quiet wait does not apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
adb4889b0f |
fix(player): keyboard focus, contrast and ARIA for controls and settings (#1769)
* fix(player): keyboard focus, contrast and ARIA for controls and settings
Dock and settings-panel icon buttons draw a 2px --pc-text ring on
:focus-visible, and Material's theme-coloured focus layer is off, so
keyboard focus shows on video in the light theme too. A focused selected
subtitle swatch now differs from one that is only selected.
Settings headings read --pc-text-secondary on denser glass
(--pc-glass-bg-dense, 0.86): 4.5:1 or more over mid-grey and white
frames. They wrap (overflow-wrap: anywhere, hyphens: auto), so long
German and Russian headings stay inside the sheet's heading column.
The settings panel is now radio groups only (SettingsRadioGroupDirective
over a CDK FocusKeyManager): one Tab stop per group on the checked option,
arrows, Home and End move focus without applying, and Space/Enter checks.
The dialog and its groups are named by real h2/h3/h4 headings, the
load-file action sits outside the subtitle radio group, the subtitle and
speed chips carry their value in their name ("Subtitles: English"), and
tune has aria-haspopup="dialog".
Adds a web E2E for the keyboard path in both themes with an axe check on
the open panel, and de/ru sheet heading wrapping; axe-core is a new dev
dependency for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(player): arrows check settings radios; subtitle chip reads On
Review follow-up:
- Arrow keys, Home and End now check the settings radio they reach, as a
native radio group does (the directive clicks it, so the template's
handler applies the choice); an option the engine already reports as
checked is not applied again.
- With subtitles on but no track marked selected yet (the engine can
report the switch before the track list), the subtitle chip reads and
announces "On" (new SUBTITLES_ON key, 19 locales) instead of "Off".
- The swatch row has 4px padding on every side, so the outer focus ring
is not clipped at the scroll edge of the panel body.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(player): re-apply a settings radio while a switch is pending
The arrow-key check skipped any option the engine still reported as
checked. Arrowing from audio track A to B and back to A before the engine
confirmed B therefore sent no command for A, and playback ended on B with
focus on A. An arrow move always lands on an option other than the last
one applied, so it now applies unconditionally.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5d50995c30 |
fix(e2e): let per-file E2E targets run without mock serve dependencies (#1772)
* fix(e2e): let per-file E2E targets run without mock serve dependencies Since #1710 the Playwright configs start the Stalker and Xtream mocks themselves (`node --import tsx …`), so @nx/playwright can no longer map those webServers to Nx tasks and infers the atomized `e2e-ci--*` targets as non-parallel. The `e2e-ci--src/*.e2e.ts` target default still made them depend on the continuous `stalker-mock-server:serve` and `xtream-mock-server:serve` targets, and Nx refuses to run a non-parallel task with continuous dependencies, so every per-file target failed before running anything. Drop the redundant mock dependencies and keep the Electron build. The mock-launch guard spec now also rejects any nx.json target default that depends on a mock-server task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): scope the mock dependency guard to E2E targets Check only the `e2e*` target defaults in nx.json, so an unrelated default may still depend on a mock, and also check every target in the `apps/*-e2e` project.json files, where a mock `serve` dependency would break the same targets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(e2e): build Electron only before Electron per-file E2E targets The `e2e-ci--src/*.e2e.ts` target default also matched web-e2e, so every browser-only per-file target built electron-backend first. Split it into project-filtered entries: Electron targets keep `build-e2e`, web targets get an empty dependency list (which also keeps the inferred `web:serve` dependency, rejected by Nx on a non-parallel target, out of them). The mock dependency guard now also catches `^serve`-style dependencies, which schedule the mocks through the E2E projects' implicit dependencies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1dba959852 |
fix(portals): treat an undetermined audio language as unknown (#1689)
* fix(portals): treat an undetermined audio language as unknown ICU 78, which Electron 43 and Node 26 ship, canonicalizes the ffprobe marker `und` to the subtag `und` instead of an empty one. The source metadata then recorded it as a stated language, so a switch between a copy tagged `und` and one tagged English raised the "dub may differ" warning. CI's older ICU hid this: the existing spec only fails on the newer runtime. Decline `und` explicitly, and cover `und-US` plus the dub comparison. * test(portals): cover undetermined language across runtimes --------- Co-authored-by: 4gray <serega05@gmail.com> |
||
|
|
444ec06e94 | fix(detail): keep the Back button from covering detail content (#1763) | ||
|
|
525ca7bc44 |
fix(playback): show the Up next card near the real end of an episode (#1768)
* fix(playback): show the Up next card near the real end of an episode The card appeared a fixed 8 minutes before the end: most of a short episode, and far into the story of a long one, with no way to hide it. - Adaptive lead: 4% of the episode, clamped to 40 s … 3 min. - A closing-credits chapter in the last third, when timeline segments carry one, brings the card forward to its start (capped at 5 min). - Close button and Escape dismiss the card for the current next episode. - After 10 s (not while hovered) the card collapses into a one-line pill. - Seconds countdown in the last minute. - New playback setting "Up next card" (default on) turns it off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): address Up next card review feedback - Offer the Up next card setting for Embedded MPV only under the frame-copy engine; native view never mounts the shared controls. - Hovering pauses the collapse delay instead of restarting it. - Document that the card stays visible when the controls auto-hide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): restart the Up next collapse delay for a new episode - A card that stays mounted while its next episode changes gives the new item the full delay instead of the previous item's leftover. - The setting description no longer promises credit-based timing: no current series path supplies chapters yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cb317bad4c |
fix(ui): keep dialog action rows on one line (#1762)
* fix(ui): keep dialog action rows on one line Settings "Unsaved changes" dialog: - Cancel / Discard / Save replace the phrase labels in all 19 locales, and the dismiss now comes first; the shared CANCEL key replaces the unused UNSAVED_DIALOG_STAY. - At the 640px phone breakpoint the actions stack one per row, full width, in DOM order. EPG programme dialog: - mat-dialog-title gives the dialog an accessible name. - The footer Close is the only dismiss; it comes first and the primary action last. - The archive copy/download tools move under their notice, so the footer stays on one row. - Channel rows now open it through EpgProgrammeDialogService, which owns the 540px config and a panel class scoping the surface overrides. Adds a web-e2e layout spec (en, de, ru, fr, hu, ar on one row; de and ru stacked on a phone), extends the Electron EPG spec to all three openers, and documents the dialog contract in the UI guidelines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(epg): stack programme dialog actions on phones Below the 640px phone breakpoint the viewport caps the programme dialog, and a long translated primary label ("Regarder depuis le début") no longer fit beside Close. The footer had no wrap, and the dialog hides overflow, so the label was clipped. - At the phone breakpoint, the archive tools and the footer now stack one full-width button per row, in DOM order. - Buttons grow to fit their label, so a long label wraps inside its button instead of being clipped. - On desktop the footer can wrap again as a last resort. The new Electron test opens a past programme in French at a 360px viewport and measures both rows. It fails against the previous stylesheet (the footer buttons are 44px narrower than the row). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d4118ad442 |
feat(playback): give the fullscreen side panel the settings panel's glass look (#1767)
* feat(playback): give the fullscreen side panel the settings panel's glass look The fullscreen channel/episode panel was a full-height graphite strip while the controls' settings panel is an inset, rounded glass card. The side panel now wears the same surface: 16px inset (8px under 560px), 20px corners, the controls' glass fill, hairline border, blur and shadow, the same open motion and a 32px square close button. The edge hint uses the same glass, and the episode list marks the playing row with the settings panel's selected cyan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): dismiss the side panel from the exposed edge gutter The inset card leaves the hot zone's left strip visible beside it, above the scrim, so a click there re-ran show() instead of dismissing. A completed primary press in the zone now closes an open panel; hovering there still counts as inside, since a hover-opened panel leaves the pointer resting in that strip. The playing episode row also keeps its cyan tint on hover/focus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): decide an edge press from the panel state at pointerdown A press held on the edge past the hover dwell opened the panel before the release, which then read the open state and dismissed it. The press now remembers whether it began on an open panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |