feat(tracks): add Tracks library hub page (closes #299) (#300)

New /tracks route with three sections:

- Hero "Track of the moment" — random pick with play / enqueue / reroll
- Random Pick rail — 18 song cards, rerollable; hero song deduped
- Browse all tracks — virtualized list (@tanstack/react-virtual),
  paginated 50 at a time

Browse uses Navidrome's native /api/song?_sort=title&_order=ASC for
proper A-Z order (no Subsonic equivalent), with automatic fallback
to search3 on non-Navidrome servers. Search input drives search3
with 300ms debounce. Bearer token cached module-level, re-auth on 401.

Play button on rows + cards calls a new enqueueAndPlay() helper that
appends to the existing queue (skip if duplicate) and jumps to the
song — different from playSongNow which replaces the queue. Enqueue
button stays opaque (no hover-only).

i18n keys for sidebar.tracks + tracks.* namespace in all 8 locales.
New AudioLines sidebar icon. Sidebar entry inserted between
"All Albums" and "Build a Mix".

Performance: cover thumbnails dropped (uniform layout instead),
RAF-throttled scroll prefetch, hover transforms removed from cards
(WebKitGTK compositing-friendly).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Frank Stellmacher
2026-04-25 14:23:15 +02:00
committed by GitHub
parent 93b724fc65
commit e3aabd98b7
21 changed files with 1512 additions and 0 deletions
+15
View File
@@ -21,6 +21,7 @@ export const nlTranslation = {
cancelDownload: 'Download annuleren',
offlineLibrary: 'Offline bibliotheek',
genres: 'Genres',
tracks: 'Nummers',
playlists: 'Playlists',
mostPlayed: 'Meest gespeeld',
radio: 'Internetradio',
@@ -376,6 +377,20 @@ export const nlTranslation = {
offlineQueuing: '{{count}} album(s) in wachtrij voor offline…',
offlineFailed: 'Toevoegen van {{name}} offline mislukt',
},
tracks: {
title: 'Nummers',
subtitle: 'Bladeren. Zoeken. Ontdekken.',
heroEyebrow: 'Nummer van het moment',
heroReroll: 'Een ander kiezen',
playSong: 'Afspelen',
enqueueSong: 'Aan wachtrij toevoegen',
railRandom: 'Willekeurige selectie',
browseTitle: 'Alle nummers doorbladeren',
browseUnsupported: 'Deze server toont niet de hele bibliotheek in één keer. Gebruik de zoekbalk hierboven om specifieke nummers te vinden.',
searchPlaceholder: 'Zoek op titel, artiest of album…',
count_one: '{{count}} nummer',
count_other: '{{count}} nummers',
},
artists: {
title: 'Artiesten',
search: 'Zoeken…',