refactor(portals): extract the destructive Xtream refresh into one flow

`PlaylistRefreshActionService.refreshXtream()` and
`RecentPlaylistsComponent.refreshXtreamPlaylist()` were two independent ~60-line
implementations of the same sequence: confirm, reset the connectivity guard,
delete the cached catalog while collecting playback positions and stamping the
update date, park the restore state, dispatch the meta update, navigate to
re-import. That duplication is what let the guard reset land in one of them and
look landed in both, which the previous commit had to fix separately.

`XtreamRefreshFlowService` now owns the sequence once. The two entry points
differ only in how they report progress — the header action drives one global
preparation signal, the sources page drives per-row busy indicators shared with
deletion — so that part is injected as an `XtreamRefreshProgressReporter`. Its
optional `waitForVisibleProgress` hook keeps the action's paint delay without
imposing it on the sources page, which never had one. A reporter cannot reach
the guard reset, so a third entry point gets it for free.

The sources page now awaits `router.navigate` before clearing its busy row,
matching the action service; it previously cleared the row while navigation was
still in flight.

Both guard-reset regression tests still fail on their own when the reset is
removed from the shared flow. The new spec pins the reporter contract:
re-entry after confirmation, busy-before-reset ordering, progress forwarding
under one run, abort vs error, and a reporter without the optional paint hook.
The sources spec's abort test counted a fixed number of microtasks to reach the
delete; the shared flow adds one await to that path, so it now waits on a
deterministic signal instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5 committed 2026-08-13 08:25:21 +02:00
1 parent 0cba49f3e2
commit e3fc3c8802
8 files changed
+593 -257

No files matched your search

+8 -4
View File
@@ -200,11 +200,15 @@ Call sites:
- The destructive Xtream refresh — before anything is deleted. It removes the
cached catalog and then bootstraps a re-import whose status request an open
guard would fast-fail, leaving the user with no catalog at all until the
cooldown expires. There are **two independent implementations** of this flow
and both need the reset: `PlaylistRefreshActionService.refreshXtream()` and
cooldown expires. The reset lives in `XtreamRefreshFlowService.runRefresh()`
(`libs/playlist/shared/ui`), which owns the whole flow for both of its entry
points: `PlaylistRefreshActionService.refreshXtream()` and
`RecentPlaylistsComponent.refreshXtreamPlaylist()` (the Workspace sources
page). They are near-duplicates of each other, which is exactly why the second
one was missed first time round.
page). Those two used to be independent near-duplicates, which is exactly why
the second one was missed first time round; they now differ only in the
`XtreamRefreshProgressReporter` they hand over, and a reporter cannot reach
the reset. Keep it that way — a third entry point should pass a reporter, not
copy the sequence.
- `PortalStatusService.checkPortalStatusDetails` when `skipCache` is set — the
user-initiated "Test Connection".