From 72e9c2d47db2837e4fe3767bb14b06eecdd2181a Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 14 Jul 2026 20:42:33 +0200 Subject: [PATCH] feat(discover): Artists|Albums tabs on /discover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggested artists and Suggested albums were stacked sections; make them tabs like /lastfm (.tabs bar + count badge). Discover-now/freshness and Find-similar stay above the tabs. The Albums tab filters to full-length albums (isFullAlbum) — a client-side stopgap that hides any pre-D1 singles/EP suggestions still in the DB (new sweeps already filter server-side via discover.albumsOnly). Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/app/discover/discover-client.tsx | 37 +++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/web/src/app/discover/discover-client.tsx b/web/src/app/discover/discover-client.tsx index cc7886b..cbc2dcc 100644 --- a/web/src/app/discover/discover-client.tsx +++ b/web/src/app/discover/discover-client.tsx @@ -15,11 +15,22 @@ type Suggestion = { artistName: string; rgMbid: string | null; album: string | null; + primaryType: string | null; + secondaryTypes: string[]; score: number; seedCount: number; sources: string[]; }; +type Tab = "Artists" | "Albums"; + +/** Only full-length albums belong in Suggested albums — hide singles/EPs and + * secondary-type releases (live/comp). New sweeps already filter server-side + * (discover.albumsOnly); this also sweeps any pre-filter rows still in the DB. */ +function isFullAlbum(s: Suggestion): boolean { + return (s.primaryType ?? "Album") === "Album" && (s.secondaryTypes?.length ?? 0) === 0; +} + /** "just now" / "3h ago" / "2d ago" — timeAgo has no suffix, so add one (except "just now"). */ function agoLabel(value: string): string { const a = timeAgo(value, Date.now()); @@ -43,6 +54,9 @@ export function DiscoverClient() { const [followedSimilar, setFollowedSimilar] = useState>(new Set()); const [artistModal, setArtistModal] = useState<{ mbid: string; name: string } | null>(null); const [albumModal, setAlbumModal] = useState(null); + const [tab, setTab] = useState("Artists"); + + const albums = feed.albums.filter(isFullAlbum); const loadFeed = useCallback(async () => { setFeed(await (await fetch("/api/discover")).json()); @@ -172,8 +186,19 @@ export function DiscoverClient() { ) : null} - - {feed.artists.length === 0 ? ( +
+ + +
+ + {tab === "Artists" ? ( + feed.artists.length === 0 ? (

No suggestions yet — run Discover, or follow a few artists to seed it.

) : (
    @@ -204,14 +229,12 @@ export function DiscoverClient() { ))}
- )} - - - {feed.albums.length === 0 ? ( + ) + ) : albums.length === 0 ? (

No album suggestions yet.

) : (
    - {feed.albums.map((s) => ( + {albums.map((s) => (