# 012 — What we ask the network for, and what we already had > **Completed.** Findings 1, 2 and 4 shipped. Finding 3 — the bound-but-uncalled methods — is now **#86**. **Status:** all four findings fixed. Lint (3 configs), Go tests (3 configs), `tsc` and 752 Vitest tests pass; **not driven against the real app**, so the numbers below are read off the code, not measured. One claim in the audit was wrong and is corrected in finding 3: `CheckLibraryMBIDs` is *not* dead — `downloadcatalog.go:152` calls it. It has no *frontend* caller, which is what was checked and not what was written. **Branch:** none yet **Created:** 2026-08-13 **Related:** 010 (owned albums offline), 011 (owned artists' discography) --- ## Scope Every frontend call site that can reach the network, and the backend method behind it. The question asked of each: *is there a local answer first, and if we do go out, do we go out once for many things or many times for one?* ## What is already right, and is the standard the rest is measured against - **Every catalog read is index-first.** `LookupArtist`, `LookupReleaseGroup`, `BrowseReleaseGroups`, `TopRecordingsForArtist`, `TopReleaseGroupsForArtist`, `SimilarArtists` and `ResolveReleaseGroupMBIDs` all answer from `explore_index` / `similar_artist_map` and only fall through on a miss — several kick a background fetch and return empty rather than blocking, with a `*Ready` event to re-read. - **Album art has the right shape:** seed from the library, one `GetThumbnails` batch that is *cached-only by contract*, then per-item `GetThumbnail` calls that stream in (`explore-view.ts:1445`). Nothing waits on a batch of network fetches. - **Artist art has the right shape in exactly one place:** `seedSimilarArtistImagesFromLibrary` (`explore-artist-details.ts:1627`) — library store, then disk-only `GetArtistImageCachedPath`, fired in parallel, zero network calls. It is the model for finding 1. ## Finding 1 — Explore's artist images: no disk check, and serial `explore-view.ts:1526-1546`. `loadArtistImages` seeds from `libraryStore.cachedArtists` — i.e. **owned artists only**, which on a catalog search is a small minority of results — and then, for every remaining artist: ```ts const url = await GetArtistImageURL(a.mbid); // in a for loop ``` Two faults, both fixed by patterns already in the codebase: - **No cached-path pass.** `GetArtistImageCachedPath` and `GetArtistImageCached` are disk-only and free, and neither is used here. An artist whose portrait is already on disk from a previous search still takes the resolution path. - **`await` in a loop.** `GetArtistImageURL` is the *resolving* entry point: on a miss it does MB artist-rels (on the 1/s artist-image limiter) → Wikidata → Wikipedia → a Wikimedia image download. Serial awaits mean 8 unresolved artists are 8 of those end to end, each blocking the next, while the equivalent album-art path fires all of them at once. The same "resolver used where a cache check belongs" appears at `top-results-row.ts:218` and `artist-details.ts:207` (both fire in parallel, so only the first fault applies, and both are small-N). **Fix:** disk-cached pass first, then network in parallel. A `GetArtistImagesCached(mbids []string) map[string]string` mirroring `GetThumbnails` would make it one IPC call instead of N — see finding 4 for why that is not `GetArtistImages`. ## Finding 2 — The artist page prefetches tracklists twice, or four times `prefetchReleases` (`explore-artist-details.ts:1531`) is called from **both** `fetchTopReleaseGroups` (:1467) and `fetchReleaseGroups` (:1506), and `PrefetchReleases` fires up to **8** `BrowseReleases` per call — the most expensive request the app makes (every version of a release group, with `recordings` and `media`). The top release groups are a subset of the discography, so the two calls are asking about overlapping sets; the backend's `BrowseReleasesCached` guard stops a *literal* repeat, which means the second call spends its 8 slots on the next 8 uncached albums rather than doing nothing. One page view is therefore up to 16 browses — and on a cold artist, `ArtistDiscographyReady` re-runs both fetchers (:945, :948), taking it to 32. Worse, some of that is now provably wasted: since tag-derived completeness landed (`dcc40b1`), **a complete, MBID-matched album opens with no catalog call at all**, so warming its tracklist buys nothing. **Fix, in order of value:** 1. Prefetch once, from the union of both lists, after both resolve. 2. Skip release groups that are owned and complete — `GetAlbumCompleteness` already answers this locally. 3. Revisit the cap of 8 with the other two in place. Plan 010 flags the same number from the other direction. ## Finding 3 — Batch helpers with no caller (one of which was live) `CheckLibraryMBIDs`, `GetPopularityBatch` and `GetArtistImages` are bound to the frontend and have **no call site in `frontend/src`**. They are the batch shapes a future N+1 would want, and their existence is presumably why the N+1s above were not noticed. **`CheckLibraryMBIDs` is not dead** — `downloadcatalog.go:152` calls it from Go, one MBID at a time. Deleting it broke the build, which is how that was found; it is kept, with a comment saying who its consumer is. Read "no frontend caller" as exactly that, and grep both languages before removing a bound method. Note `GetArtistImages` is not the helper finding 1 needs: it resolves names through `libMBID.AllArtistMBIDs()`, so it only answers for artists **in the library** — the exact set Explore's search results are not. Either give it an MBID-keyed sibling or replace it. Also bound with no caller, and worth a separate decision about whether the feature is live at all: `GetTrackLyrics`, `GenerateMix`, `GetArtistPlayCount`, `GetLibrarySimilarArtists`, `GetCandidateThumbnail`. ## Finding 4 — One more background pass with no job and no priority `BackfillLibraryLyrics` (`lyrics.go:129`) is a bare `go` call: bounded by passes and per-track (LRCLIB has no batch endpoint, so per-track is correct), but with no `jobs` registration and no `WithBackgroundPriority` marking. It runs on its own limiter, so it starves nothing today — but it is invisible and uncancellable, which is the gap 011 just closed for the other two backfills. ## Not a finding, recorded so it is not re-audited - `GetThumbnails` returning only cached entries is deliberate and documented; the per-item follow-up is the streaming half, not an N+1. - `explore-artist-details` calling both `TopReleaseGroupsForArtist` (50) and `BrowseReleaseGroups` (200) reads overlapping rows from the index twice, but both are local queries feeding two different sections. Not worth merging. - The newest components (`home-view`, `catalog-scope-notice`, `page-header`, the notification stack, `shortcuts-overlay`) make no network calls at all. `home-view` is `GetShelves` + `GetAlbumTracks`, both local. ## Done when - An Explore search with no owned artists in it makes zero artist-image network calls for portraits already on disk, and resolves the rest concurrently. - Opening an artist page issues one prefetch pass, over albums that are not already fully owned. - The bound-but-uncalled batch helpers are either wired or removed.