feat: wire detail pages to explore cache for instant hydration

Artist detail page:
- Checks exploreCache for pre-loaded artist image (from search)
- Checks libraryStore for albums by this artist (by name match)
  and shows them as discography instantly before API calls
- Skips fetchArtistImage if cache already provided one

Album detail page:
- Checks exploreCache for cached album metadata (title, artist,
  year) from search results and pre-populates the header
- API calls still run to get full data (releases, tracks)
This commit is contained in:
2026-03-29 19:03:30 -04:00
parent 8096b28d17
commit 92cd6f268c
2 changed files with 66 additions and 2 deletions
@@ -10,6 +10,7 @@ import type {
MBRelease,
MBTrack,
} from '@go/explore/Service';
import { exploreCache } from '../../store/explore-cache';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
/* ── Constants ── */
@@ -398,6 +399,21 @@ export class ExploreAlbumDetails extends LitElement {
`[explore-album] loading: "${this.albumName}" (${mbid})`,
);
// Phase 0: hydrate from explore cache (instant).
const cached = exploreCache.getAlbum(mbid);
if (cached) {
this.releaseGroup = {
mbid: cached.mbid,
title: cached.title,
artistCredit: cached.artistName,
firstReleaseDate: cached.year || '',
primaryType: 'Album',
} as MBReleaseGroup;
this.loadingInfo = false;
console.log(`[explore-album] hydrated from cache: "${cached.title}"`);
}
// Phase 1: API calls for full data.
const [infoResult, releasesResult] = await Promise.allSettled([
this.fetchReleaseGroup(mbid),
this.fetchReleases(mbid),
@@ -17,6 +17,8 @@ import type {
LBTopReleaseGroup,
LBSimilarArtist,
} from '@go/explore/Service';
import { exploreCache } from '../../store/explore-cache';
import { libraryStore } from '../../store/library-store';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
/* ── Constants ── */
@@ -646,7 +648,10 @@ export class ExploreArtistDetails extends LitElement {
`[explore-artist] loading: "${this.artistName}" (${mbid})`,
);
// Fire all requests in parallel — each section is independent.
// Phase 0: hydrate from caches (instant, no Go calls).
this.hydrateFromCache(mbid);
// Phase 1: fire all API requests in parallel.
const [artistResult, tracksResult, topReleasesResult, releasesResult, similarResult] =
await Promise.allSettled([
this.fetchArtist(mbid),
@@ -657,7 +662,9 @@ export class ExploreArtistDetails extends LitElement {
]);
// Artist image is fire-and-forget — doesn't block the page.
this.fetchArtistImage(mbid);
if (!this.artistImageURL) {
this.fetchArtistImage(mbid);
}
// Check which release groups are in the local library.
this.checkLibrary();
@@ -674,6 +681,47 @@ export class ExploreArtistDetails extends LitElement {
);
}
/**
* Hydrate state from the explore cache and library store.
* Shows cached data instantly before API calls complete.
*/
private hydrateFromCache(mbid: string) {
// Artist image from explore cache (populated by search results).
const cachedArtist = exploreCache.getArtist(mbid);
if (cachedArtist) {
if (cachedArtist.imageURL) {
this.artistImageURL = cachedArtist.imageURL;
} else if (cachedArtist.imageMedium) {
this.artistImageURL = cachedArtist.imageMedium;
} else if (cachedArtist.imageSmall) {
this.artistImageURL = cachedArtist.imageSmall;
}
}
// Library albums by this artist — show as discography instantly.
const cachedAlbums = libraryStore.cachedAlbums;
if (cachedAlbums) {
const artistName = this.artistName.toLowerCase();
const libraryAlbums: MBReleaseGroup[] = [];
for (const a of cachedAlbums) {
if (a.ArtistName.toLowerCase() === artistName) {
libraryAlbums.push({
mbid: a.MBID || '',
title: a.Name,
primaryType: 'Album',
artistCredit: a.ArtistName,
firstReleaseDate: a.Year ? String(a.Year) : '',
} as MBReleaseGroup);
}
}
if (libraryAlbums.length > 0) {
this.releaseGroups = libraryAlbums;
this.loadingReleases = false;
}
}
}
private async fetchArtist(mbid: string) {
try {
this.artist = await LookupArtist(mbid);