test-web / test (push) Successful in 33s
Client half of #367. Two new Library tabs, each an index plus a drill-down. Genres are ordered by track count rather than alphabetically. Raw ID3 carries a long tail of one-off tags, so alphabetical would bury the handful of genres you actually have a library's worth of. Years are grouped into decades — a flat list of every year in a decades-deep library is a wall of numbers, and the decade is how people actually think about it. ## Selection travels in the query string, not the path `?g=Rock%2FPop`, not `/library/genres/Rock%2FPop`. A slash-bearing genre cannot survive a path segment — the server sees two segments, and a hard reload wouldn't reconstruct it through the SPA fallback either. There's a test pinning the encoded href and another pinning that the DECODED value reaches the API. ## Why these two pages don't use svelte-query for their lists The indexes do — fetched once per mount, so static options suffice and the cache survives bouncing in and out of a drill-down. The drill-down lists deliberately don't. Their selection comes from the URL and changes WITHOUT remounting the page, and this codebase has no reactive-query-options pattern anywhere; inventing one here would be a larger change than the feature justifies, and one I can't exercise locally. So they use $effect keyed on the derived selection with an explicit Load more. The stale-response guard is a plain `let`, not $state, and that's load-bearing: as reactive state, reading the token inside the fetch path would make the effect depend on its own writes. Its job is to discard a late response for a previously selected genre instead of painting it over the current one. ## Also Added the year filter to /library/albums' contract but NOT to that page's UI — its infinite scroll is a svelte-query infinite query, and making it react to a filter is the same reactive-options problem. The dedicated pages cover the capability, which is the shape the task offered as its alternative. Library tab bar's comment claims it mirrors Android's LibraryScreen. These two tabs have no Android equivalent, so I noted that inline rather than leaving the claim quietly false. Parity remains an open call. Not yet done from #367's bullet list: genre/year quick-jump links on album and artist detail. Year is free (AlbumRef already carries it) but genre is exposed nowhere client-side — AlbumDetail is AlbumRef + tracks, and neither carries genre — so it needs a small API addition. Following as its own commit.
53 lines
2.0 KiB
Svelte
53 lines
2.0 KiB
Svelte
<script lang="ts">
|
|
import { page } from '$app/state';
|
|
|
|
let { children } = $props<{ children: import('svelte').Snippet }>();
|
|
|
|
// Tab bar for the Library section. Mirrors Android's LibraryScreen
|
|
// (artists / albums / liked / history / playlists) — Playlists lives
|
|
// here so the operator can find their personal collection in one
|
|
// place rather than tracking a separate top-level route.
|
|
// Genres and Years sit next to Albums — all three are ways of walking the
|
|
// same collection — rather than at the end beside the personal tabs (Liked,
|
|
// History, Playlists). NOTE: these two are web-only for now; Android's
|
|
// LibraryScreen has no equivalent, so the "mirrors Android" claim above is
|
|
// currently aspirational for this pair. Parity is an open call (#367).
|
|
const tabs = [
|
|
{ href: '/library/artists', label: 'Artists' },
|
|
{ href: '/library/albums', label: 'Albums' },
|
|
{ href: '/library/genres', label: 'Genres' },
|
|
{ href: '/library/years', label: 'Years' },
|
|
{ href: '/library/liked', label: 'Liked' },
|
|
{ href: '/library/history', label: 'History' },
|
|
{ href: '/library/playlists', label: 'Playlists' }
|
|
];
|
|
|
|
function isActiveTab(href: string): boolean {
|
|
return page.url.pathname === href || page.url.pathname.startsWith(href + '/');
|
|
}
|
|
</script>
|
|
|
|
<div class="space-y-4">
|
|
<nav aria-label="Library sections" class="border-b border-border">
|
|
<ul class="flex gap-1 overflow-x-auto">
|
|
{#each tabs as tab}
|
|
{@const active = isActiveTab(tab.href)}
|
|
<li>
|
|
<a
|
|
href={tab.href}
|
|
class="block px-3 md:px-4 py-3 text-sm whitespace-nowrap border-b-2
|
|
{active
|
|
? 'border-accent text-text-primary'
|
|
: 'border-transparent text-text-secondary hover:text-text-primary'}"
|
|
aria-current={active ? 'page' : undefined}
|
|
>
|
|
{tab.label}
|
|
</a>
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
</nav>
|
|
|
|
{@render children?.()}
|
|
</div>
|