search: the results page, the typeahead, and a session that survives a reload

The page is the signed-off prototype built out: floating rounded hero, inline
top-tracks panel, filter chips, two-column Songs|Albums beside Artists, and all
seven states — results, searching, no results, one-kind-only, library-empty,
provider-unreachable, first visit. MusicBrainz records are merged in silently
behind the Deezer rows.

The top result is ranked by POPULARITY, not by kind: each kind puts up its most
popular exact match and the winner is whichever holds the largest share of its
own kind's leader. A raw fans-vs-rank comparison was tried first and handed
"panda bear" to an Owl City song.

Two provenance traps are paid for here. Deezer's /search/album returns no
release_date at all, so sorting newest-first ordered by PROVENANCE rather than
by date; dates are backfilled from the artist overview, resolved PER ALBUM
ARTIST because Deezer's artist search answers nothing for "weezer blue album".
And the typeahead holds its keyboard selection as the row's KEY rather than its
index, because the MusicBrainz supplement lands ~600ms later and re-orders the
album group under the cursor.

The hero's middle third is hidden on the width of the PANE, not the window. The
sidebar and the now-playing rail take ~490px between them, so a 1504px window
can leave the hero 1012px — a window-width media query cannot see that, and the
"latest release" card ended up sheared down to 26px of itself. The budget is
written out at the @container rule.

Session restore rounds it off: reload or restart returns to the same place with
the same track, everything but the transport coming from localStorage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
KeelrLeekandClaude Opus 5 committed 2026-08-03 07:03:45 -05:00
1 parent 7547fed56d
commit b173f60085
6 files changed
+3315 -493

No files matched your search

+2 -32
View File
@@ -11,9 +11,7 @@
refreshRecents,
toggleSidebar,
goToView,
openCatalogAlbum,
openAlbumByName,
openLibraryAlbum,
openRecentAlbum,
getArt,
} from "../lib/state.svelte.js";
import * as api from "../lib/api.js";
@@ -139,34 +137,6 @@
return !!a.art_path && a.art_path === playingArt;
}
async function openRecent(a) {
if (a.kind === "catalog" && a.catalog_album_id) {
// The catalog page, not the old library album view — streamed
// records live in the catalog. The seed paints title/artist/cover
// immediately instead of flashing a skeleton.
openCatalogAlbum(a.catalog_album_id, {
seed: {
id: a.catalog_album_id,
title: a.title,
artist: a.artist,
cover: a.art_url ?? null,
},
});
return;
}
// A library row has no catalog id to open with — local albums carry no
// catalog identity at all (`albums.mbid` is null and never surfaced) —
// so the record is resolved by artist + title, where BOTH must match
// normalized or nothing opens. That namesake guard lives in
// openAlbumByName and is the reason this can't just take a first hit.
//
// Either way it is the SAME album page: a record no catalog has (Panda
// Bear's "A ? of WHEN", verified live) opens from the library's own
// data instead. There is no second album view to fall back to.
if (await openAlbumByName(a.artist, a.title)) return;
void openLibraryAlbum(a.album_id);
}
$effect(() => {
if (!recentsStore.loaded) refreshRecents();
});
@@ -261,7 +231,7 @@
class="rec"
class:on={isPlaying(row.album)}
title="{row.album.title} — {row.album.artist}"
onclick={() => openRecent(row.album)}
onclick={() => openRecentAlbum(row.album)}
>
{#if coverOf(row.album)}
<img src={coverOf(row.album)} alt="" draggable="false" />