fix(portals): probe like playback, and stop the pin answering for a remake

Five findings from the latest review pass.

Writing a pin to every alias — last round's fix for stale aliases — was
wrong in the other direction: `title:{base}:` is shared by every remake, so
a known-year decision stored there answers for a different film. Pin Dune
(2021), open Dune (1984) before its year arrives, and it would start the
2021 source. A write now clears every alias and stores only the canonical
key, which retires the stale ones without making any of them ambiguous.

The probe checked a bare URL while playback sends the playlist's User-Agent,
Referer and Origin. A panel that requires them answers 401/403, so a stream
that plays perfectly was reported dead and penalised in failover ranking.

The switch toast interpolated the raw playlist name. Users routinely name a
playlist after the URL they pasted, so that line could put credentials over
the video; the notice now carries the same safe label the rows use.

External players have no timeupdate, so their polled position IS the live
one. Feeding it through the seed — which stops at the first value — froze
the resume point where playback started, and a switch an hour in rewound to
the beginning.

And auto-failover concluded "nowhere to go" when a stream failed before
discovery answered, stranding the user on the error screen.

Moves `switchTo` into the session module, which is where the rest of the
switch mechanics already live, and splits the route spec along the same
rendering/behaviour seam the other suites use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5 committed 2026-07-28 01:16:56 +02:00
1 parent e3465d3a45
commit b1ad9657c3
20 files changed
+718 -188

No files matched your search

@@ -33,6 +33,7 @@ import {
import { PortalDebugEvent } from './portal-debug.interface';
import { CatalogTitleMatch } from './catalog-title-match.interface';
import {
StreamProbeHeaders,
VodSourceCandidateRow,
VodSourcePin,
} from './vod-source.interface';
@@ -688,7 +689,9 @@ export interface ElectronBridgeApi {
/** Generic stream reachability probe (VOD multi-source availability) */
probeStreamUrl: (
url: string,
method?: 'GET' | 'HEAD'
method?: 'GET' | 'HEAD',
/** Playback headers the owning playlist requires, when it has any. */
headers?: StreamProbeHeaders
) => Promise<ElectronBridgeXtreamProbeResult>;
refreshPlaylist: (
payload: PlaylistRefreshPayload
@@ -51,6 +51,19 @@ export type VodSourceProbeStatus =
*/
| 'unknown';
/**
* Playback headers a playlist requires, carried into the probe.
*
* A panel configured to demand its own User-Agent, Referer or Origin answers
* 401/403 without them, and calling a stream that plays fine "unavailable" is
* exactly the confident lie this feature refuses to tell.
*/
export interface StreamProbeHeaders {
userAgent?: string | null;
referer?: string | null;
origin?: string | null;
}
export interface VodSourceProbeResult {
status: VodSourceProbeStatus;
/** HTTP status when one was actually received; 0 when none was. */