From d732efc4c50c1352a43edd8142f3ed27afb3a07b Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 16 Aug 2026 11:12:10 +0200 Subject: [PATCH] fix(portals): wait for the catalog before stating Discover availability Triggering initializeContent() for the discover route was only half the fix: TMDB usually answers before a cold catalog finishes importing, and the content gate renders the route while that runs. The page dropped its spinner as soon as the TMDB request settled, so cards computed against an empty catalog claimed that titles the user owns are missing and their clicks opened a search instead of the detail page. Availability now waits for the catalog too. Readiness is keyed on what is in flight rather than on isContentInitialized, mirroring the recently-added route, so a failed import settles the page instead of spinning forever. Co-Authored-By: Claude Fable 5 --- .../xtream-discover-route.component.ts | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/libs/portal/xtream/feature/src/lib/discover/xtream-discover-route.component.ts b/libs/portal/xtream/feature/src/lib/discover/xtream-discover-route.component.ts index a6b8afc2b..6b6f26c5d 100644 --- a/libs/portal/xtream/feature/src/lib/discover/xtream-discover-route.component.ts +++ b/libs/portal/xtream/feature/src/lib/discover/xtream-discover-route.component.ts @@ -79,7 +79,26 @@ export class XtreamDiscoverRouteComponent { private readonly facetKey = computed(() => discoverFacetKey(this.facets())); private readonly results = signal([]); - readonly isLoading = signal(true); + private readonly isLoadingResults = signal(true); + + /** + * TMDB usually answers before a cold catalog finishes importing, and + * the content gate renders this route while that runs. Publishing + * results against an empty catalog would state that titles the user + * owns are missing, so availability waits for the catalog too. + */ + readonly isLoading = computed( + () => this.isLoadingResults() || !this.isCatalogReady() + ); + + // Keyed on what is in flight rather than on isContentInitialized, so + // a failed import settles the page instead of spinning forever + private readonly isCatalogReady = computed( + () => + !this.xtreamStore.isLoadingContent() && + !this.xtreamStore.isLoadingCategories() && + !this.xtreamStore.isImporting() + ); readonly showScopeToggle = this.titleMatch.isAvailable; readonly scope = signal('portal'); @@ -242,11 +261,11 @@ export class XtreamDiscoverRouteComponent { // with the SAME key, so recency — not the key — decides who may // commit: otherwise the older one's failure blanks valid results const token = this.discoverRequest.start(); - this.isLoading.set(true); + this.isLoadingResults.set(true); this.globalMatches.set(null); if (!hasDiscoverFacet(facets)) { this.results.set([]); - this.isLoading.set(false); + this.isLoadingResults.set(false); return; } const titles = await this.tmdbEnrichment.discoverTitles(facets.type, { @@ -258,7 +277,7 @@ export class XtreamDiscoverRouteComponent { return; } this.results.set(titles ?? []); - this.isLoading.set(false); + this.isLoadingResults.set(false); if (this.scope() === 'global') { void this.loadGlobalMatches(); }