Die Liste, wem man folgt, gehört dorthin, wo man Künstler ohnehin sucht —
bisher gab es sie nur als waagerechte Reihe unter den Favoriten.
getArtists liefert kein starred mit, die Markierungen kommen deshalb aus
getStarred2 und werden 30 s gehalten, damit das Umschalten flott bleibt.
Ein Herz-Klick verwirft diesen Zwischenspeicher sofort — sonst zeigte der
Filter nach dem Entfolgen noch eine halbe Minute den alten Stand. Beim
Testen aufgefallen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eigener Punkt in der Seitenleiste: jüngste Veröffentlichungen aller
gefolgten Künstler, nach Monat gruppiert, neueste zuerst, umschaltbar auf
30/90/365 Tage. Was schon in der Bibliothek liegt, ist markiert (Vergleich
über Künstler + Albumname, akzent- und zeichenunabhängig); alles andere
bekommt einen Laden-Knopf zum vertrauten Download-Dialog.
Neuer Endpunkt POST /api/artist-releases (backend/lib/releases.js): die
Deezer-Zuordnung passiert serverseitig, weil die Künstler-Pins dort
ohnehin liegen. Gepinnte nutzen die feste ID, ungepinnte werden über den
Namen gesucht und nach Fan-Zahl sortiert; ohne Treffer kommt der Name als
ohneZuordnung zurück und die Oberfläche rät zum Pinnen.
Cache im Backend statt im Client — bei 30 gefolgten Künstlern wären das
sonst 30 Deezer-Aufrufe pro Gerät und App-Start. 6 h für Alben, 24 h für
die Namenszuordnung, max. 4 Anfragen parallel, bei Deezer-Fehlern wird
der abgelaufene Stand ausgeliefert statt einer leeren Liste.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Herz sitzt jetzt im Banner neben dem Pin-Zahnrad statt in der
Aktionsleiste: die Leiste ist "was ich jetzt tue" (abspielen, mischen,
Konzerte), Folgen ist ein Zustand und gehört an den Künstler. Auf beiden
Plattformen gleich. Beim Album bleibt das Herz in der Aktionsleiste, dort
gibt es kein Banner.
Dazu die Übersicht, wem man folgt: die Favoritenseite bekommt Abschnitte
Künstler / Alben / Songs. Ohne zusätzlichen Aufruf — getStarred2 liefert
Künstler und Alben im selben Paket wie die Songs, wir haben sie bisher
verworfen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Folgen ist kein eigener Speicher, sondern Navidromes Stern auf dem
Künstler (star&id=<ndArtistId>) — Navidrome wirft id, albumId und
artistId beim Markieren in eine Liste und behandelt sie gleich, der
vorhandene Aufruf trug also ohne Änderung. Zustand aus `starred` in
getArtist/getAlbum, beide Plattformen schalten optimistisch um.
Android nutzt den vorhandenen StarState, der ohnehin über IDs arbeitet.
Dazu: getTopSongs bekommt seit Navidrome 0.64 zusätzlich `id`. Der
Namensweg sucht mit LIKE und Max:1, bei gleichnamigen Künstlern gewann
der erste Treffer. Jetzt id + artist zusammen; in Erkunden bleibt es beim
Namen, weil die artistId dort Deezers ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
navigate() ließ den Stack-Eintrag weg, wenn sich die Ansicht nicht
geändert hatte, rief history.pushState aber trotzdem. Zweimal auf
denselben Tab geklickt = zwei Browser-Einträge auf dieselbe Ansicht,
jeder davon ein Zurück-Druck ohne sichtbare Wirkung. Im Test: 3x "Alben"
-> 3 Browser-Einträge bei 1 Stack-Eintrag, danach bewegten 4 Zurück-
Drücke die Ansicht einmal. Jetzt pusht nur eine wirklich neue Ansicht,
sonst replaceState. Vorwärts mitgetestet, unverändert.
Playlists: Navidrome liefert owner mit, das Web nutzte es nie. Übersicht
und Detail zeigen "· von <Name>", nur bei fremden Listen. Android zeigte
ihn bisher bei jeder Liste und unbeschriftet; beide Plattformen jetzt
gleich, Bibliotheks-Übersicht ebenfalls.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Name stand in --text2 (#8888aa) und 14px unter einem 28px starken
Albumtitel — er ging darin unter, und dass er anklickbar ist, war nicht
erkennbar. Jetzt --text, 15px, halbfett; wo ein Ziel dahintersteht,
zusätzlich dezent unterstrichen (Hover in Akzentfarbe).
Gilt für lokale Albumseite, Deezer-Albumseite und die Inline-Ansicht in
der globalen Suche. Die Teil-Album-Seite bleibt ohne Link, dorthin kommt
man nur von der Künstlerseite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Künstlername unter einem Deezer-Album war reiner Text — von dort zum
Künstler ging nur über Zurück und neu suchen. Er ist jetzt anklickbar und
öffnet dieselbe Deezer-Künstlerseite wie eine Künstler-Kachel.
Kostet keinen zusätzlichen API-Aufruf: Deezer legt /search/album und
/album/{id} das artist-Objekt samt id bei (120/120 Treffer über zwölf
Suchen geprüft), beide Mappings übernahmen die id längst.
/artist/{id}/albums lässt das artist-Objekt weg — Alben von dort bleiben
bewusst reiner Text, statt ins Leere zu zeigen.
Android zeigte in der Album-Detailansicht bisher gar keinen Künstler und
lädt Bild + Fanzahl nach, wenn der Künstler aus einem Album stammt.
.album-artist-name trug cursor:pointer auch ohne Ziel — jetzt nur noch,
wo wirklich etwas dahintersteht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Android holt zwei Sachen nach, die es im Web laengst gibt:
- Song von der Festplatte loeschen, im Kontextmenue unten abgesetzt und rot, mit
derselben Sicherheitsabfrage wie im Web. Laeuft ueber /api/delete-song und
erscheint nur mit konfiguriertem Backend — Navidrome allein loescht nichts.
- Playlist direkt aus dem Import anlegen: Haekchen plus Namensfeld ab zwei
Titeln, danach Scan anstossen, Titel suchen, Playlist aus den gefundenen.
Nicht gefundene werden gezaehlt statt die Erstellung scheitern zu lassen.
Dazu der Web-Fix zur Album-Position: die gespeicherte Scrollposition wurde in
genau einem requestAnimationFrame gesetzt. Solange Cover und Eintraege noch
nachluden, war scrollHeight kleiner als das Ziel und der Browser kappte den Wert
stillschweigend — man landete wieder oben. Jetzt wird er ueber bis zu ~0,5 s
erneut gesetzt, bis er sitzt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
album-back-btn war die einzige Detailseite, die per navigate() VORWAERTS auf die
Albenliste sprang statt einen Schritt im Verlauf zurueck. Dadurch landete man
nicht dort, wo man das Album angeklickt hatte (z. B. Home), und es kam ein
zusaetzlicher History-Eintrag auf den Stapel, durch den sich der Browser-Zurueck-
Knopf erst arbeiten musste. Jetzt _navGoBack() wie bei Artist, Genre, Deezer- und
Partial-Album; die Albenliste bleibt nur das Ziel, wenn es keinen Verlauf gibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Going into an album/artist and back now restores the scroll position and
keeps the typed query, instead of resetting to the top.
- Web: navigation.js snapshots each history entry's #main-content scroll and
any open local (header) search, restoring them on Back/Forward; a genuine
new navigation still starts at the top.
- Android: the Alben/Künstler lists (LibraryViewModel) and the global/local
search-results lists (ExploreViewModel/SearchViewModel) hoist their
LazyListState/LazyGridState, so scroll survives a drill-in/back for the
tab's lifetime.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The album-name normaliser stripped everything but a-z0-9, so any JP/CJK
title collapsed to "". All of an artist's Japanese singles then normalised
identically, de-duplicated into one entry and "matched" each other — e.g.
inabakumori (稲葉曇) showed only 2 of 9 releases. Normalisers now keep
Unicode letters/numbers (\p{L}\p{N}) in:
- web _normAlbum (detail.js)
- android normAlbum (categorizeAlbums), normTrack (partial-album split),
the enrichPartialCounts helpers, and the Deezer artist-name match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Backend now caches Ticketmaster events per artist and prefers the larger
recent result, so a flaky partial/empty response can't shrink a good list;
every client (web/Android) stays consistent (1h TTL self-heals).
- Web SoundCloud importer preview: add the missing .gtrack-num grid cell so
the 4-column row no longer squeezes the cover/title (title was wrapping one
char per line).
- CHANGELOG [2.3] opened for this batch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Pin ("Falscher Künstler?") is now a small icon-only ⚙ chip on both web
and Android — accent-blue when pinned, subtle white otherwise; no more
text banner over the hero.
- Android artist discography no longer filters to record_type=="album";
it now shows albums, EPs AND singles (ordered albums→EPs→singles),
matching Deezer's own artist page. Web already showed all.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The importer no longer has its own sidebar/bottom-nav entry. The Global
Suche field now handles both jobs: plain text runs the Deezer search;
a pasted Spotify/SoundCloud/YouTube link switches into importer mode
(badge + "Importieren" button + preview), matching the Android app and
tidying the left nav.
- New onGlobalInput/tryImporterResolve; importer DOM moved into
page-discover; old page-spotify-importer removed; routing cleaned up.
- Existing resolvers unchanged (still read the mirrored importer-link-input).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two gaps the pin left, on both platforms:
1. Search still showed the wrong same-name artist's photo. The library search
now prefers the pin's Deezer picture for a pinned artist (web: search.js
artist cards; android: LibrarySearchContent via SearchViewModel overrides).
2. Similar Artists were "weird" — they come from Last.fm keyed to the (merged/
wrong) Navidrome artist. When pinned, use Deezer "related" of the pinned
artist instead (new deezerRelatedArtists / ItunesClient.lookupRelatedArtists).
Also: web Similar Artists now render with profile pictures (avatar chips), like
the Android app, instead of plain text chips.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- esc() now coerces to string first (String(s ?? '')), so passing a number (e.g. the
Deezer artist id in the pin picker) no longer throws 'replace is not a function'.
- The '⚙ Falscher Künstler?' button moved from under the fan count to the top-right
of the artist banner (absolute, on the hero). SW cache -> ns-v75.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes same-name collisions where NovaSonic guessed the wrong Deezer artist (e.g.
'Nana' → a random DJ): fans, top songs, missing albums and the hero image were all
off. A '⚙ Falscher Künstler?' button on the artist page opens a picker (same-name
Deezer candidates with image + fan count); the chosen mapping is stored SERVER-SIDE
(shared across users, like the romaji overrides) in /data/deezer-artist-overrides.json.
Once pinned, the artist page short-circuits the name search to the pinned Deezer id
(drives albums/top-songs/fans) and overrides the hero image with the pinned artist's
Deezer picture (NovaSonic-side, so it works even for tag-merged same-name artists —
Navidrome can't disambiguate them without an MBID). New endpoints
GET/POST/DELETE /api/artist-overrides; api.js load/get/set/remove; loaded at startup.
SW cache -> ns-v74. Needs on-device runtime test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix (the reported bug):
- Deezer's /artist/{id}/albums omits the artist object, so discography album cards had an
empty artist name → the download modal fell back to the literal "Unknown Artist", which
poisoned the yt-dlp search ("Unknown Artist <album>") and its artist/channel filter, failing
with "Nicht eindeutig gefunden".
- deezerLookupArtistAlbums now backfills the known artist name (we're on that artist's page).
- openDlModal now defaults album cards to an ALBUM download (was always Track), so a one-click
download no longer grabs an album as a single track named after the album.
Rename (requested cleanup, dependency-checked so nothing breaks):
- All frontend itunes* identifiers → deezer* (functions, the ItunesItem-shape field keys
itunesData/itunesArtistId/itunesFanCount/itunesId, the itunes-album-detail route + DOM ids,
the badge-itunes CSS class). The backend reads none of these fields, so the boundary is safe.
- Verified: build passes; route/DOM-id/CSS producer↔consumer all consistent; only the factual
"Apple's iTunes /search 403" comments keep the historical name. SW ns-v62.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Deezer artist match required only that every query word appear in the candidate name, so
"Flay!" (query word: flay) matched "K.FLAY" (words: k, flay). Sorted by fan count, K.FLAY
(54,822 fans) buried the real "Flay!" (39 fans) — the page showed K.FLAY's fans + top tracks.
Now prefer an EXACT normalized-name match ("flay" == "flay", not "kflay"), falling back to the
loose match only when there's no exact one. Web (detail.js) + Android (ArtistViewModel).
Verified against Deezer: exact match selects the real Flay! (id 12242284, whose top tracks
are actually Flay!). SW ns-v60.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Navidrome can hold the same track twice (downloaded as a single AND as an album track), so
getTopSongs returned two identical rows ("PIXELATED KISSES" x2). Collapse the top-songs list
by title+album for display (keeps the highest play count) on web + Android. SW ns-v59.
Note: this is a display fix — the underlying duplicate files remain a library-cleanup matter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deezer returns the same album as several variants (single/EP/album/regional/explicit), and
each variant matched the same Navidrome album, so one owned album rendered 2-3 times
("PIXELATED KISSES" x3).
- Web (detail.js): collapse matched entries to one per owned Navidrome album and one per
not-owned title before classifying (also avoids duplicate track-count fetches).
- Android (categorizeAlbums): distinctBy(collectionId) missed variants with different ids;
now also skip an owned album already classified.
SW ns-v58.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Niche local artists (e.g. N!GHT) whose Navidrome metadata agents have no image showed a
blank hero. Now, when Navidrome has no artist image, the hero falls back to the matched
Deezer artist's photo (Navidrome image → Deezer photo → album cover), on web + Android.
- Web (detail.js): the Deezer-photo fallback previously ran only for non-local artists;
now it runs whenever the Navidrome image is absent, local or not.
- Android: ArtistViewModel captures the matched Deezer artist's photo (deezerImage);
ArtistDetailScreen uses it as the hero fallback.
SW ns-v56.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deezer's /artist/{id}/albums list omits nb_tracks, so the album total read as 0 and every
owned album (e.g. N!GHT's "Moonlight"/"Yume") was classified as incomplete "1 / 0 Tracks".
- Web (detail.js): resolve the real Deezer track count via the album endpoint for MATCHED
(owned) albums only — a couple of calls, not the whole discography — and treat an unknown
total as complete. Genuine partials still work when the true count is known.
- Android (categorizeAlbums): mark partial only when a known total exceeds what you own;
unknown total counts as complete (never a false "1 / 0").
- CHANGELOG: documented the fix; corrected the Android lyrics known bug (auto-scroll follows
playback but isn't smooth — it jumps rather than glides). SW ns-v55.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deezer's artist search doesn't rank by popularity, so a name collision (5 artists named
"Joji") returned a niche one first (62 fans) instead of the real Joji (559k) — the artist
page then loaded the wrong discography and owned songs didn't show as downloaded.
- api.js: _dzArtist carries fanCount (nb_fan); artist search sorts by fanCount desc.
- search.js: artist cards show the fan count as subtitle; fan count passed to the artist page.
- detail.js: pick the most-popular name match (not the first); for local Navidrome artists
with a real collision (>=2 same-name candidates), tie-break by SONG OVERLAP — prefer the
Deezer candidate whose top tracks actually overlap the library (each artist has different
songs, so this nails the right person). Costs a few calls only on a collision.
- Artist hero shows "<n> Fans" (immediately for global-search artists, after match for local).
- Android: DzArtist.nb_fan + /search/artist sorted by nb_fan so the right artist is first.
- Also: web global-search "unavailable" message now says Deezer, not Apple. SW ns-v52.
Verified live against Deezer: both fan-sort and overlap select the real Joji (id 5078097).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apple's iTunes /search returns 403 from server/datacenter IPs, which had broken global
search, charts, album/artist/single downloads and the metadata-editor autofill in
production. Everything now sources metadata from Deezer (free, no key, more requests,
returns explicit versions + real artist images + durations). Web + Android share one
source, so an artist resolves to one consistent name everywhere. Deezer results are
mapped to the legacy "ItunesItem" shape, so the UI + download flow are unchanged.
Backend:
- New backend/lib/deezer.js: lookupTrackMeta, fetchAlbumTracks, searchAlbumTracks,
fetchArtistAlbums (+ pure pickTrackMatch/mapAlbumTrack for tests).
- metadata.js drops the iTunes lookups; novasonic-server download paths (track, album,
artist, playlist enrich, spotify artwork) + autofill now use Deezer.
- Artist download uses Deezer discography (real albums only), not the iTunes album search.
- Editor autofill endpoint /api/itunes/lookup -> /api/meta/lookup; removed the dead
iTunes /search proxy and the Apple charts RSS proxy.
- backend/test/deezer.test.js: unit tests for numbering, mixed-artist album-artist
stability, explicit flags, the Japanese-title match guard (npm test; RUN_LIVE=1 for live).
Web:
- api.js search/lookups already on Deezer; charts now use Deezer /chart. Artist cards
show the real Deezer photo. Visible "iTunes"/"Apple Music" labels -> "Deezer"
(search badge, charts badge, preview bar, editor button, artist heading). SW ns-v51.
Android:
- New DeezerModels.kt with Dz* models + mappers to ItunesItem/ItunesRssEntry.
- ItunesClient is Deezer-backed (search = tracks+albums+artists in parallel; album/artist
lookups; /chart charts), same method signatures. Visible strings -> Deezer.
Docs: README, CLAUDE.md and CHANGELOG (1.3) updated; Platform Parity Matrix reflects the
shared Deezer source + Android non-local artist page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Search artist cards now use the Deezer artist picture directly (Deezer artists carry
a photo, unlike iTunes entities) instead of a null image + an extra album lookup;
the lookup stays only as a fallback when no picture is present.
- Non-local (Deezer) artist pages: fetch the Deezer artist picture (/artist/{id}) for
the hero photo, which was blank because getArtistImage only runs for Navidrome artists.
- Label "iTunes Künstler" → "Deezer Künstler".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Confirmed via curl from a clean IP: Apple's iTunes /search returns 403 (blocked
server-side, likely datacenter-IP anti-scraping) while /lookup?id= still returns 200.
So the Erkunden Apple search fails through no fault of ours.
- Charts: the "most-played" RSS (rss.applemarketingtools.com) was fetched directly
from the browser and hit CORS; proxy it via /api/itunes-charts/:kind.
- Error visibility: itunesSearch records the last outcome (HTTP status / network);
the global-search view now shows "Apple-Suche gerade nicht verfügbar" with the
reason (403 = Apple blocks the API, not you) instead of a blank "no results".
- iTunes proxy now forwards the upstream status code instead of always 200.
Deezer (free, no key, ~50 req/5s) is the planned search fallback; MusicBrainz is too
rate-limited (1/s) for interactive search.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deletes left the removed item visible because the list loaders are cached (getPlaylists
~3 min): deletePlaylist navigated to a still-cached list, and deleteSong only reloaded
playlists. Add refreshCurrentView() — drops the API cache and re-runs the active nav
entry's loader (playlists, playlist/album/artist detail, starred, home, …). deleteSong
calls it after the delete+rescan delay; deletePlaylist invalidates getPlaylists before
navigating. Bump SW cache to ns-v46.
Also record the post-1.2 work in the 1.2 changelog (importer Enter, search single-song
+ radio, NowPlaying order, official-audio artist-metadata match, JP-katakana album
artist fix, year back-fill, delete refresh).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Playing a song from search results queued the whole result list. Now it plays only
the clicked song; Radio mode, if on, fills the rest — closer to Spotify. Both
platforms already trigger a radio top-up on the first track (web playQueue →
fillRadioQueue; Android onMediaItemTransition → checkRadioRefill), so no extra wiring
is needed. Album/playlist play still queues the full list. Bump SW cache to ns-v44.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
loadAlbumDetail overwrote the header artist with parseArtists(songs[0]) — the first
track's artist. On soundtracks/compilations that is wrong (e.g. "KPop Demon Hunters"
showed "TWICE" from track 1 "TAKEDOWN"). Use r.album (the getAlbum response, which
carries the album's own album-artist tags in .artists / .artist) so it matches the
Android app: "KPop Demon Hunters Cast • HUNTR/X • Saja Boys".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The popstate listener always called _navGoBack(), but both Back and Forward fire
popstate — so Forward was treated as Back and never restored the page you came from.
_navGoBack also pushState()'d on every back, which wiped the browser's forward stack
entirely.
Route all history through the History API: navigate() pushes a state (nsIdx) only on
genuine new navigation; popstate now applies whichever entry the browser moved to via
_navOnPopState(state.nsIdx), covering Back AND Forward with no pushState in the
handler. In-app back buttons / Backspace go through history.back() so there's a single
path and the browser stack stays in sync. NowPlaying overlays still close first on a
back press.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
loadHomeData now takes a force flag; navigating to Home via a click
(not back/forward) invalidates the home rows' API cache so they re-fetch
and the random row re-randomizes — matching the Android re-tap behavior.
CHANGELOG: note the Home-refresh fix under 1.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Notification sheet: add 'Zum Album →' / 'Öffnen →' button; tapping searches
Navidrome and navigates to the matching album/playlist
- AppNotification: add navType/navAlbum/navArtist/navId fields (mirrors web notify.js)
- ImporterViewModel: store resolved album/artist/type in notification after download
- ABS Android: search bar with 400ms debounce, server-side search via /search endpoint
- AbsClient: add searchLibraryItems() for library-specific search
- AbsScreen/abs.js: strip HTML tags from book descriptions (<p> tags no longer visible)
- NowPlayingScreen: vibrant palette swatch + lighter scrim (0.15) + wider gradient
for more visible dominant color background
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same pool of music-themed phrases as Android — picks randomly each load.
With username: "Ready for some beats, Marc" — without: "Pick something great"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Partial album detail: tapping incomplete albums now shows downloaded songs
(normal playback) above and missing iTunes tracks (30s preview + download)
below — on both Android and web PWA
- Android: ItunesArtistScreen now renders full-screen above the tab/search bar
- Android: ArtistDetailScreen (Bibliothek) now loads iTunes data in parallel —
adds Weltweit populär section and In Library/Unvollständig/Nicht heruntergeladen
album sections identical to the Erkunden artist page
- Fix: artist no longer appears in its own Similar Artists row
- Fix: duplicate album entries deduplicated by collectionId in categorizeAlbums
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After saving sort tags via the metadata editor, invalidate _artistSortMap
and the apiCached getArtists entry so the updated Navidrome sortName values
are picked up on the next page visit without a hard refresh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ensureArtistSortMap() fetches getArtists once per session (cached) and
populates _artistSortMap automatically. Called at start of loadAlbumDetail
when showRomaji is on, so artist sort names are always available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Song objects carry sortName for title only, no album/artist sort fields.
Album sort is only available on the album object itself (r.album.sortName).
Updated loadAlbumDetail to use the fresh API response album, not the nav album.
Simplified dispAlbum to not check non-existent song sort fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dispAlbumName now takes the full album object and reads album.sortName
directly — no kuromoji fallback, no backend translation.
dispAlbum (song context) also drops the kuromoji fallback, keeping only
the sortAlbum/albumSort file tag.
Override list → Navidrome sort tag → raw Japanese name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added _artistSortMap populated from getArtists (artist listing) and
getArtist (artist detail) responses. dispArtistById now checks the
Navidrome sort tag first, falls back to kuromoji only when missing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hero now only waits for getArtist (fast local Navidrome) before revealing.
getArtistImage (Last.fm lookup) was the 2-4s bottleneck when uncached —
it now resolves async and patches in the artist photo after the hero is
already visible.
Album card, album detail header, and home hero now display the raw album
name from Navidrome instead of converting to romaji when showRomaji is on.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend: swap /api/events/:artist to use Ticketmaster Discovery API
(startDateTime=now, classificationName=music, size=20, sort by date).
Maps TM response to the same {datetime, venue, offers} shape the modal
expects. Returns [] if TICKETMASTER_API_KEY is not set.
Frontend: remove all debug console.logs from events.js; drop redundant
client-side date filter (TM already filters by startDateTime server-side).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split the allSettled into two phases: await imgP+albumP first (both are
fast — getArtistImage hits S.artistImgCache when navigating from the
listing, getArtist is a local Navidrome call), then reveal the hero.
The slow calls (getArtistInfo2 / Last.fm bio, iTunes search, events)
keep running in parallel and are only awaited when rendering the content
sections below the hero — so the user sees the hero card immediately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>