feat(tracks): Highly Rated rail + per-card star display (#443)

* feat(tracks): Highly Rated rail + per-card star display

Adds a new SongRail above the Random Pick on the Tracks page that
surfaces the user's highly-rated tracks (sorted by rating DESC).
Auto-hides on non-Navidrome servers and when the library has no
rated tracks yet. Reuses the existing SongRail layout, with the
standard reroll button forcing a cache bypass.

Per-card stars: any SongCard whose `userRating > 0` now shows a
small five-star row (filled to the rating value) below the artist
line — visible everywhere SongCard is used, not only in the new
rail. Read-only display; rating is still done via the row's
context menu or the Now Playing star widget.

Cache layer in `ndListSongs`: opt-in `cacheMs` parameter (skipped
by VirtualSongList; used only by the Highly Rated rail with a 60 s
TTL). Cleared on `setRating` mutation so a freshly-rated track
shows up on the next page revisit, and on server switch alongside
the existing token cache. The reroll button explicitly invalidates
before refetching, so a manual refresh always hits the network.

* docs(changelog): add #443 Tracks Highly Rated rail entry

* chore(credits): add #443 to Psychotoxical contributions
This commit is contained in:
Frank Stellmacher
2026-05-03 18:54:07 +02:00
committed by GitHub
parent 98ff73d17a
commit 1799e90e04
15 changed files with 125 additions and 5 deletions
+1
View File
@@ -388,6 +388,7 @@ export const deTranslation = {
playSong: 'Abspielen',
enqueueSong: 'Zur Warteschlange',
railRandom: 'Zufallsauswahl',
railHighlyRated: 'Hoch bewertet',
browseTitle: 'Alle Titel durchstöbern',
browseUnsupported: 'Dieser Server listet nicht die ganze Bibliothek auf einmal. Nutze die Suche oben, um bestimmte Titel zu finden.',
searchPlaceholder: 'Titel, Künstler oder Album suchen…',
+1
View File
@@ -390,6 +390,7 @@ export const enTranslation = {
playSong: 'Play',
enqueueSong: 'Add to queue',
railRandom: 'Random Pick',
railHighlyRated: 'Highly Rated',
browseTitle: 'Browse all tracks',
browseUnsupported: "This server doesn't list the whole library at once. Use the search above to find specific tracks.",
searchPlaceholder: 'Find a track by title, artist or album…',
+1
View File
@@ -390,6 +390,7 @@ export const esTranslation = {
playSong: 'Reproducir',
enqueueSong: 'Añadir a la cola',
railRandom: 'Selección aleatoria',
railHighlyRated: 'Mejor valoradas',
browseTitle: 'Explorar todas las canciones',
browseUnsupported: 'Este servidor no lista toda la biblioteca de una vez. Usa la búsqueda de arriba para encontrar canciones concretas.',
searchPlaceholder: 'Busca una canción por título, artista o álbum…',
+1
View File
@@ -388,6 +388,7 @@ export const frTranslation = {
playSong: 'Lire',
enqueueSong: 'Ajouter à la file',
railRandom: 'Sélection aléatoire',
railHighlyRated: 'Mieux notés',
browseTitle: 'Parcourir tous les titres',
browseUnsupported: 'Ce serveur ne liste pas toute la bibliothèque d\'un coup. Utilisez la recherche ci-dessus pour trouver des titres précis.',
searchPlaceholder: 'Chercher un titre par titre, artiste ou album…',
+1
View File
@@ -388,6 +388,7 @@ export const nbTranslation = {
playSong: 'Spill av',
enqueueSong: 'Legg til i kø',
railRandom: 'Tilfeldig valg',
railHighlyRated: 'Høyt vurdert',
browseTitle: 'Bla gjennom alle spor',
browseUnsupported: 'Denne tjeneren lister ikke hele biblioteket på én gang. Bruk søket ovenfor for å finne bestemte spor.',
searchPlaceholder: 'Finn et spor etter tittel, artist eller album…',
+1
View File
@@ -387,6 +387,7 @@ export const nlTranslation = {
playSong: 'Afspelen',
enqueueSong: 'Aan wachtrij toevoegen',
railRandom: 'Willekeurige selectie',
railHighlyRated: 'Hoog beoordeeld',
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…',
+1
View File
@@ -412,6 +412,7 @@ export const ruTranslation = {
playSong: 'Воспроизвести',
enqueueSong: 'В очередь',
railRandom: 'Случайная подборка',
railHighlyRated: 'Высоко оценённые',
browseTitle: 'Просмотреть все треки',
browseUnsupported: 'Этот сервер не возвращает всю библиотеку сразу. Воспользуйтесь поиском выше, чтобы найти конкретные треки.',
searchPlaceholder: 'Найти трек по названию, исполнителю или альбому…',
+1
View File
@@ -386,6 +386,7 @@ export const zhTranslation = {
playSong: '播放',
enqueueSong: '加入队列',
railRandom: '随机精选',
railHighlyRated: '高分推荐',
browseTitle: '浏览所有曲目',
browseUnsupported: '此服务器不支持一次性列出整个音乐库。使用上方的搜索来查找特定曲目。',
searchPlaceholder: '按标题、艺人或专辑搜索…',