After MB search returns text-relevance-scored results, fetch bulk
popularity data from ListenBrainz (POST /1/popularity/{artist,
recording,release-group}) for all result MBIDs. Blend scores:
final = 0.6 * mb_relevance + 0.4 * log10_popularity
Log-scale normalization ensures massive artists don't drown out
everything, but popular results rise above obscure exact matches.
Release groups (no MB score) sort by raw popularity.
Three LB POST calls run concurrently — each hits a different
endpoint. All are rate-limited and cached (24h TTL).
Example: searching 'tatsuro' now ranks Tatsuro Yamashita (2.5M LB
listens, score 97) above 'tatsuro' vocaloid producer (4 listens,
score 64) despite the latter being an exact name match on MB.
The ListenBrainz popularity API returns snake_case JSON fields
(recording_name, artist_name, total_listen_count, recording_mbid)
but LBTopRecording used camelCase JSON tags for Wails serialization.
All fields silently deserialized as zero values — empty strings and
zero counts — producing ~8000 blank rows in the top tracks section.
Fix: add lbTopRecordingWire with snake_case tags for API unmarshal,
convert to LBTopRecording (camelCase) for Wails. Cap results at 10
to avoid rendering thousands of rows for prolific artists.