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>
This commit is contained in:
4grayandClaude Fable 5.1 committed 2026-10-04 09:34:33 +02:00
1 parent b87c88833b
commit 94668fb058
4 files changed
+51 -38

No files matched your search

+23 -17
View File
@@ -472,17 +472,19 @@ interface is unavailable; exact external timestamps remain best-effort.
The detail "…" menu's "Open in external player" sends the title to MPV/VLC
through `PortalPlayer.openExternalPlayback(playback, player)` whatever the
configured player is. The launch IPC cannot be cancelled, and Electron
publishes the session only once it resolves, so every detail host keeps these
rules:
configured player is. The launch IPC cannot be cancelled, and until it
resolves the session is at most `launching`, with no exact closer. Every
detail host therefore keeps these rules:
- **One external player per title.** Before launching, the host closes the
external session it owns for that title. The `owns` predicate names only the
page's own content; any other session is left alone. With instance reuse
off, a second detached player would otherwise start beside the first.
Stalker hosts use `replaceOwnedExternalSession` from
`@iptvnator/portal/shared/util`; the Xtream pages use the equivalent
`closeRunningExternalSession`.
- **One external player per owner.** Before launching, the host closes the
external session the page owns: the session of the same title on the
Stalker pages and the Xtream series page; the session it launched, else the
one matching its movie, on the Xtream movie page. Sessions the page does not
own are left alone. With instance reuse off, a second detached player would
otherwise start beside the first. Stalker hosts use
`replaceOwnedExternalSession` from
`@iptvnator/portal/shared/util`; the Xtream pages use
`closeRunningExternalSession` with the same outcome rules.
- **Unconfirmed teardown cancels the launch.** A live session without a
closer, or a close that rejects, leaves the running player in place and
nothing new launches.
@@ -495,15 +497,19 @@ rules:
other start of that title until the launch settles. Series pages hold the
latest episode choice and, once the launch settled, replace the player it
opened, only while that series is still on screen.
- **Pending state is owner-scoped.** A start still resolving, or a progress
reset still writing, holds the actions of its own title only: another title
shown by the reused page is not blocked by it. Movie hosts track starts with
- **Pending starts are owner-scoped.** A start still resolving holds the
actions of its own title only: another title shown by the reused page is
not blocked by it. Movie hosts track starts with
`createPendingPlaybackStart` (`@iptvnator/portal/shared/util`): only the
latest start may clear the flag, and `isPendingFor(owner)` answers for one
owner. Stalker hosts, whose starts wait on a portal round trip, also
`retire(owner)` when the selection leaves it, so a request that never
settles cannot hold a return to the same title. The one page-wide gate is
the Xtream movie page's external launch that has not settled yet.
owner. The Stalker movie hosts, whose starts wait on a portal round trip,
also `retire(owner)` when the selection leaves it, so a start that never
settles does not keep the flag set on a return to the same title. A movie's
"Reset progress" is scoped the same way.
- **Two gates are page-wide.** The Xtream movie page refuses every start
while an external launch it made has not settled. A series page runs one
watched or reset batch at a time, whichever series is shown; the Stalker
page also holds episode starts until that batch settles.
Owner keys and queueing are provider contracts:
+16 -12
View File
@@ -1567,10 +1567,10 @@ the Stalker keys and queues are:
Series (`StalkerSeriesViewComponent`, `stalker-series-launch-queue.ts`):
- Pending starts and held choices are keyed by `playlist:series`
(`currentSeriesKey`). The view is reused across series and provider ids
collide across playlists, so one series settling never drops what another
holds.
- Pending starts and the launch queue's held choices are keyed by
`playlist:series` (`currentSeriesKey`). The view is reused across series
and provider ids collide across playlists, so one series settling never
drops what another holds.
- A start is pending for its series from the click until it settles. A forced
launch stays pending through the close of the previous player, the launch
and the release of a held choice. The pending flag disables the hero button
@@ -1584,28 +1584,32 @@ Series (`StalkerSeriesViewComponent`, `stalker-series-launch-queue.ts`):
release it is dropped when the series is no longer shown. Otherwise
`replacePlayer` closes what the launch opened before the choice starts, and
an unconfirmed close drops the choice.
- An episode chosen while a watched or reset batch runs is held until the
batch settles, then goes through the usual gates only on the series it was
made for: episode identities overlap across series.
- An episode chosen while a watched or reset batch runs is held in one slot
tagged with its series; the last choice wins. When the batch settles it
goes through the usual gates only if that series is still shown: episode
identities overlap across series.
Movies (`createStalkerVodDetailActions`, used by the catalog detail, the
collection detail and search):
- A repeat for the same `playlist:movie` while its launch is in flight is
ignored. Launches of other movies are not held back.
ignored, also after leaving the movie and returning to it. Launches of
other movies are not held back.
- The launch joins the host's starts (`beginPendingStart`): it supersedes an
earlier start, is dropped once a later one begins, and keeps Play, Start
over, the watched toggle and the menu rows disabled until it settles.
- The resolved stream is discarded when the movie is no longer selected or a
newer start took over. The selection check includes the content type,
because movie and series ids collide. Otherwise the movie's own external
session is replaced, the inline player closes, and the launch is sent. A
launch that resolves after either condition changed closes the session it
opened; one that fails by then is not reported.
session is replaced, the host's `beforeExternalLaunch` hook runs (the
catalog and collection details close their inline player there), and the
launch is sent. A launch that resolves after either condition changed
closes the session it opened; one that fails by then is not reported.
- "Reset progress" counts as a pending start of the movie until the write
lands, so a start made meanwhile cannot resume from the row being cleared.
- The pending start is owner-scoped (`createPendingPlaybackStart`). Each host
retires it when the selection leaves the owner.
retires it when the selection leaves the owner; that clears the pending
flag, not the repeat guard of a launch still in flight.
Regression coverage: `stalker-series-launch-queue.spec.ts`,
`stalker-series-view.component.spec.ts`,
+6 -4
View File
@@ -776,10 +776,12 @@ rules are in
(`VodDetailsPlaybackService.pendingResets`, `vod-details-reset-target.ts`),
not one flag: the reused page can show another movie and come back, and
resets of one copy can overlap, so each reset removes only its own entry.
- A start is refused (`startResolvedPlayback`), and Play, Start over and the
menu launch are disabled (`startBlocked`), while an external launch has not
settled or the list holds the copy the page currently acts on
(`resetTarget`). A reset still writing for another copy does not block it.
- A start is refused (`startResolvedPlayback`) while a launch this page made
has not settled or the list holds the copy the page currently acts on
(`resetTarget`). `startBlocked` disables Play, Start over and the menu
launch on those conditions and while a matched session is still
`launching`; the menu rows are also held while a start is pending. A reset
still writing for another copy does not block it.
## Provider codec metadata
@@ -424,8 +424,8 @@ The series page keeps its launch state at module level in
page left the owner closes the session it opened.
- The duplicate guard is keyed by page token plus episode. The token
(`pageToken()`) is owner, page instance and visit, so a launch left behind
by an earlier visit of the same series does not swallow the reopened page's
click; that click queues on the owner's chain.
by an earlier visit of the same series does not swallow a launch from the
reopened page; that launch queues on the owner's chain.
- While a forced launch of the owner is pending (`forcedLaunchPending`), a
start that does not force a player is queued instead of started. One choice
is kept per owner, the latest wins, and it carries the host and `start` of
@@ -438,6 +438,7 @@ The series page keeps its launch state at module level in
- The launch-position marker and a launch-failure message apply only while
the page token is unchanged.
Regression coverage: `serial-details-external-launch.spec.ts`,
`serial-details-playback.service.spec.ts` and
`serial-details-menu.service.spec.ts`.
Regression coverage: `serial-details-external-launch.spec.ts` (chain,
duplicate guard, queued choice), `serial-details-playback.service.spec.ts`
(page token) and, for the menu rows,
`libs/ui/components/src/lib/detail-ui/series-hero.state.spec.ts`.