/ — artists list with URL-driven sort (?sort=alpha|newest), "Load more" pagination via createInfiniteQuery, and the empty-library message when total === 0.
/artists/:id — artist name + album count + responsive AlbumCard grid (2→3→4→5 cols across breakpoints).
/albums/:id — hero with cover, title, linked artist, year, metadata summary; track list below. Back chevron targets the artist page (not Library).
Five reusable components (ArtistRow, AlbumCard, TrackRow, LibrarySkeleton, ApiErrorBanner) keep the pages short and the loading/error UX consistent across all three routes.
useDelayed rune helper suppresses flash-of-skeleton on cache-hit navigation — skeletons only render after the pending state has persisted ~100ms.
404 branch on detail pages (Artist not found, Album not found) renders a non-retryable card; other errors share <ApiErrorBanner> with a retry button.
Cover rendering via <img src="/api/albums/:id/cover" loading="lazy"> with a data-URL SVG fallback for broken covers.
Test harness: src/test-utils/query.ts wraps mock query results in readable(...) to match TanStack Query's Svelte adapter return type.
Test Plan
go test -short -race ./... passes (unchanged; backend untouched)
golangci-lint run ./... clean
cd web && npm run check → 0 errors / 0 warnings
cd web && npm test → 73 tests across 16 files pass
cd web && npm run build succeeds (adapter-static emits web/build/)
docker build . builds; binary is -x in the final image
Manual browser walkthrough: sign in, drill artists → artist → album → back, verify sort dropdown + Load more + 404 paths
Forgejo CI green on this PR
## Summary
- **Three browse routes on top of TanStack Query:**
- `/` — artists list with URL-driven sort (`?sort=alpha|newest`), "Load more" pagination via `createInfiniteQuery`, and the empty-library message when `total === 0`.
- `/artists/:id` — artist name + album count + responsive AlbumCard grid (2→3→4→5 cols across breakpoints).
- `/albums/:id` — hero with cover, title, linked artist, year, metadata summary; track list below. Back chevron targets the artist page (not Library).
- **Five reusable components** (`ArtistRow`, `AlbumCard`, `TrackRow`, `LibrarySkeleton`, `ApiErrorBanner`) keep the pages short and the loading/error UX consistent across all three routes.
- **`useDelayed` rune helper** suppresses flash-of-skeleton on cache-hit navigation — skeletons only render after the pending state has persisted ~100ms.
- **404 branch** on detail pages (`Artist not found`, `Album not found`) renders a non-retryable card; other errors share `<ApiErrorBanner>` with a retry button.
- **Cover rendering** via `<img src="/api/albums/:id/cover" loading="lazy">` with a data-URL SVG fallback for broken covers.
- **Test harness**: `src/test-utils/query.ts` wraps mock query results in `readable(...)` to match TanStack Query's Svelte adapter return type.
## Test Plan
- [x] `go test -short -race ./...` passes (unchanged; backend untouched)
- [x] `golangci-lint run ./...` clean
- [x] `cd web && npm run check` → 0 errors / 0 warnings
- [x] `cd web && npm test` → 73 tests across 16 files pass
- [x] `cd web && npm run build` succeeds (adapter-static emits `web/build/`)
- [x] `docker build .` builds; binary is `-x` in the final image
- [ ] Manual browser walkthrough: sign in, drill artists → artist → album → back, verify sort dropdown + Load more + 404 paths
- [ ] Forgejo CI green on this PR
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
/— artists list with URL-driven sort (?sort=alpha|newest), "Load more" pagination viacreateInfiniteQuery, and the empty-library message whentotal === 0./artists/:id— artist name + album count + responsive AlbumCard grid (2→3→4→5 cols across breakpoints)./albums/:id— hero with cover, title, linked artist, year, metadata summary; track list below. Back chevron targets the artist page (not Library).ArtistRow,AlbumCard,TrackRow,LibrarySkeleton,ApiErrorBanner) keep the pages short and the loading/error UX consistent across all three routes.useDelayedrune helper suppresses flash-of-skeleton on cache-hit navigation — skeletons only render after the pending state has persisted ~100ms.Artist not found,Album not found) renders a non-retryable card; other errors share<ApiErrorBanner>with a retry button.<img src="/api/albums/:id/cover" loading="lazy">with a data-URL SVG fallback for broken covers.src/test-utils/query.tswraps mock query results inreadable(...)to match TanStack Query's Svelte adapter return type.Test Plan
go test -short -race ./...passes (unchanged; backend untouched)golangci-lint run ./...cleancd web && npm run check→ 0 errors / 0 warningscd web && npm test→ 73 tests across 16 files passcd web && npm run buildsucceeds (adapter-static emitsweb/build/)docker build .builds; binary is-xin the final image