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>
Statt eines Prozentrings ("wie aehnlich seid ihr") zeigt die Ansicht jetzt, wer
worin gewinnt: fuenf Kategorien mit je einem Sieger und ein Gesamtstand.
Hoerzeit, Vielfalt, Tiefe (Plays je Kuenstler), Nachteule (Anteil vor 5 Uhr) und
Entdeckerdrang (Anteil exklusiver Kuenstler) — bewusst Kennzahlen zur Hoerweise
statt zur Menge, sonst gewinnt immer, wer laenger dabei ist.
Am laufenden Dienst mit zwei gegensaetzlichen Nutzern geprueft: 60 Plays a 300 s
ergeben 5,0 h und Tiefe 12,0, jeder dritte Play nachts ergibt 33 %, 60 von 62
exklusiven Kuenstlern ergeben 97 % — Endstand 3:2. Das JSON-Feldschema wurde
gegen das Kotlin-Modell abgeglichen.
Dazu drei Fixes im selben Bereich:
- Die Web-Ansicht lebte nur von Inline-Stilen mit festen Flex-Verhaeltnissen und
zerfiel am Desktop. Jetzt ein Raster mit eigenen Klassen, eine Spalte am Handy,
zwei ab 780 px; das Fenster musste dafuer von 720 auf 940 px, sonst haette die
zweite Spalte nie ausgeloest.
- Gemeinsame Songs wurden ueber song_id abgeglichen und fielen auseinander,
sobald eine Datei umbenannt wurde. Jetzt normalisierter Titel+Kuenstler.
- Media-Session: die Metadaten wurden feldweise mutiert (gegen Kachel-Flackern
unter Windows), ueber MPRIS kam davon nichts an — Plasma-Widgets zeigten
dauerhaft den zuerst gespielten Titel. Bei echtem Titelwechsel jetzt ein neues
MediaMetadata, sonst weiter in-place.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The header search overlay was anchored to the search bar's width, leaving a
dead strip on the right where scrolling hit the background page. Anchor it to
the sticky #mobile-header instead (which already sits right of the sidebar), so
it spans the full main-content width — sidebar stays visible, no dead strip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- .artist-hero-name: add padding-right so the heavy/negative-letter-spacing
final glyph isn't cut by overflow:hidden.
- CHANGELOG: merge the 2.3 batch into a single 2.2 (2026-08-22) section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Anchor #header-search-overlay to the search bar (position:absolute inside a
relative .header-search-wrap) instead of position:fixed full-viewport width,
so the results drop down at the bar's width and the left sidebar stays visible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dragging the mouse near a field edge (Global Suche, the importer, etc.) started
a text selection of the surrounding page titles/labels instead of feeling like an
app. Disable user-select on the #app shell; re-enable it for real text fields
(input/textarea/contenteditable) and copyable content (lyrics). Setup screen is
outside #app and unaffected. SW cache -> ns-v66.
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>
- Android local artist page (ArtistDetailScreen/ArtistViewModel) now resolves the Deezer
artist (fan-sorted, so same-name collisions pick the popular one) and shows "<n> Fans"
under the name — parity with the web local artist page.
- Also migrated ArtistViewModel's preview to the app-level PreviewController: the local
artist page had its OWN uncontrollable MediaPlayer preview, which had the same
"keeps playing after navigating away" bug. Now every preview uses the global bar.
- Web: the artist-hero fan line was laid out in a row next to the name (flex without
column direction) and used an odd tinted color — now stacked cleanly under the name in
a muted white. SW ns-v54.
- CHANGELOG: documented the above + added a Known Bug (Android synced-lyrics auto-scroll
still doesn't follow playback).
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>
The favorite star lived inside .np-title-row, so it sat pinned to the title line
rather than centered against the title + album + artist block. Move it out to be a
flex sibling of .np-text inside .np-info (align-items:center), so it centers on the
full text block. Bump SW cache to ns-v43.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Home rows get hover left/right arrows that only show for the scrollable
direction (enhanceScrollRow + initScrollRows, called from startApp);
visibility recomputed on scroll/resize/content-load.
Fix the OS media tile flicker when skipping on desktop PWA: mutate the
existing MediaMetadata in place instead of assigning a new one each track
(the replacement made Windows tear down and rebuild the tile). Action
handlers are now set once.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- styles.css: remove overflow:hidden from .player-info-wrap — it was
clipping the player-status-row and shifting the shuffle badge
- index.html: use absolute /icon.svg path for favicon; add PNG fallback
for browsers without SVG favicon support
- icon-192.png / icon-512.png: regenerated from new equalizer SVG design
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap player-info + star in .player-info-wrap (flex, align-items:center)
so the star is immediately to the right of the text block and centered
vertically with it, instead of floating to the far end of the 1fr column.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- icon.svg: replaced music-note with 5-bar equalizer waveform (gold
gradient on dark bg) — works well as favicon and app icon at all sizes
- index.html: add <link rel="icon"> so browsers pick up the favicon;
move #btn-star-current out of the title row and into player-track so
player-track's align-items:center centers it with the full info block
- styles.css: add flex:1 to .player-info base rule; remove stale mobile
overrides that assumed the star was inside player-info (position:absolute,
padding-right hack)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single object-position can never work well for portrait photos in a ~6:1 wide
banner — we'd only see ~11% of the image height no matter the value.
Desktop now uses two layers:
1. artist-hero-bg: same image, blurred + darkened, fills full banner width
2. artist-hero-img: sharp portrait anchored to the right at natural proportions
(height: 100%, width: auto — no cropping, full face visible)
Mobile unchanged (cover + center center was already working great).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Only override object-position on desktop (>=769px) to center 30%.
Mobile keeps center center which was already working great.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switching from background-image (inline JS, single position for all sizes)
to an <img> element lets CSS media queries apply different object-position:
- Mobile: center top (portrait images show face in upper portion)
- Desktop: center 30% (wide banner, lower anchor avoids showing only hair)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The pseudo-element added for the flicker fix had position:absolute;inset:0
with no pointer-events:none, so it sat over the entire overlay and consumed
every click — queue items, lyrics lines, buttons. Add pointer-events:none.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NP overlay flicker (stronger fix):
- Remove contain:layout style paint (causes layer issues on some Android WebView)
- Add ::before pseudo-element with solid background so no frame can be transparent
- Add -webkit-transform prefixes for both open and closed states
Shuffle fully random:
- playQueue no longer pins startIdx when shuffle is on
- All songs shuffled into a completely random order, S.qIdx=0 always
- toggleShuffle mid-queue still pins current song first (Spotify toggle behavior)
Playlist count wrong in overview:
- Navidrome's getPlaylists returns stale/wrong songCount in its index
- Add data-pl-id to each playlist card in loadPlaylists
- After loadPlaylistDetail gets the accurate entry count, backpatch the
matching overview card's subtitle so the grid shows the real song count
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace will-change:transform with translate3d(0,100%,0) / translate3d(0,0,0).
will-change triggers a layer-creation/destruction cycle on WebView — when the
player bar DOM updates during a skip, the compositor can briefly expose the
layer's clear back buffer, flashing the content below through the overlay.
translate3d pins the element to a stable GPU layer from the start.
Also add contain:layout style paint to isolate the overlay's paint context
from external repaints, and -webkit-backface-visibility for older WebView.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three improvements:
- openNowPlaying(): reads NSBridge.getState() (always-fresh @Volatile snapshot)
via ID search to reconcile S.qIdx before render; fixes NP showing old song
when deferred evaluateJavascript callbacks haven't fired yet after backgrounding
- onPositionUpdate(): periodic check (~every 2s) detects track mismatch via
snapshot ID; catches missed/delayed onTrackChanged when NP is open
- updatePlayerUI(): pre-warms next song's 1024px cover so auto-advance NP
swap is instant from browser cache instead of a 1-second load delay
- styles: add backface-visibility:hidden + isolation:isolate to .np-overlay
to reduce GPU composite flicker on Android WebView on manual skip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace saturated blur filter with actual dominant-color extraction:
fetch cover as blob (avoids canvas CORS taint), draw on 24x24 canvas,
find most vivid pixel, darken to ~28% for a moody solid background.
Blurred bg layers hidden when mode is active. Transitions smoothly
between songs via CSS transition on background.
Renamed setting label to "Einfarbiger Player-Hintergrund".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Now Playing: add "Dynamische Hintergrundfarbe" toggle in Theme settings;
when on, np-bg uses higher saturation/brightness so the cover color really
shows through the blurred background
- Settings modal: extend to bottom nav on mobile (was leaving ~74px dead space
for the player bar which the overlay already covers at z-index 200)
- Android: fix onTrackChanged always staying on the first song — the snapshot
from MediaController can lag behind ExoPlayer on auto-advance, causing the
stale currentIndex to overwrite the correct p.index; now only advance to
a higher live index (for rapid-skip catch-up) and use p.trackId directly
instead of the potentially-stale snapshot id for the queue-mismatch guard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change overlay from position:absolute to position:fixed so it isn't
clipped by main-content's overflow-y:auto
- Set overlay top dynamically from header.offsetHeight so it always
anchors exactly below the sticky header
- Responsive max-height: accounts for player bar + bottom nav on mobile
- Hide logo text on ≤359px screens to keep search pill wide enough
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the hidden toggle-based mobile search bar with a permanently
visible pill-shaped search input in the header row, next to the logo.
- Search is always visible — no button click required
- Typing shows a floating overlay with library results (artists, albums,
songs) without navigating away from the current page
- Clearing the input or navigating to any page closes the overlay silently
- The ✕ clear button appears only while text is present
- navigate() now calls closeHeaderSearch() so results disappear cleanly
when tapping any result or bottom nav tab
- Old toggleMobileSearch / closeMobileSearch kept as no-op stubs for safety
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- views.js: skip ASCII word-match when artist name is non-ASCII so Japanese
artists (e.g. ヨルシカ) resolve correctly to an iTunes artist ID
- index.html + styles.css: collapsible Sort-Tags section in edit modal
- ui.js: pre-fill sort fields from romaji index; include in save body
- novasonic-server.js: accept manual titleSort/artistSort/albumArtistSort/albumSort
overrides; fall back to auto-computed romaji when not provided
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Normalize •, ·, ;, , separators on save (both edit and download paths)
- Fix ndSong scope bug (was inaccessible outside try block in edit-metadata)
- Apply _normalizeArtists to artist/albumArtist before ffmpeg tags and file rename
- Storage format: Artist A;Artist B (no space, Navidrome/MusicBrainz style)
- Display format: Artist A; Artist B (space, via _fmtArtists in frontend)
- Show separator hint below label in metadata editor fields
- Remove hover effect on player bar song title
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Naming convention (download + edit-metadata):
Before: {01 - }Title.ext
After: {01 - }Artist1, Artist2 - Title.ext
- ytdlp.js: builds fileBase with artist prefix; findExistingTrack checks
both new and old format so existing files aren't re-downloaded
- metadata.js: findExistingTrack accepts optional safeArtist param
- novasonic-server.js: file rename on title edit now includes artist
(falls back to Navidrome's stored artist if not provided in body)
Song list subtitle: hide album name when album === title (singles where
the album is named after the track appear to duplicate the title)
Player bar: removed .player-title:hover underline/colour effect
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New src/soundcloud.js: resolves SoundCloud URLs via yt-dlp --dump-single-json,
renders track/playlist preview with per-track download buttons
- Backend POST /api/soundcloud/resolve: extracts metadata (title, artist, genre,
artwork, duration) directly from SoundCloud — no iTunes lookup needed
- ytdlp.js: sourceUrl param — if set, downloads directly from URL instead of
ytsearch cascade (used for SoundCloud tracks)
- Spotify importer page renamed to Song Importer, now contains both
Spotify and SoundCloud sections in one view
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Info modal gets an edit pencil button that switches to edit form
- Editable fields: title, artist, album, genre, year, track#, disc#, total tracks
- Cover art: upload your own image or auto-fetch from Cover Art Archive via MusicBrainz
- MusicBrainz button queries /api/musicbrainz/lookup and autofills all fields
- Save POSTs to /api/edit-metadata which rewrites tags via ffmpeg and triggers Navidrome rescan
- Backend: GET /api/musicbrainz/lookup + POST /api/edit-metadata endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shuffle now shuffles the queue array once (current song first, rest
randomised behind it) instead of picking a random index on every skip.
Toggling shuffle off restores the original queue order. Removed the
'once' repeat state added in the previous commit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scrobble timer: 12s → 15s
- Play count moved after action buttons, shown as "23×" (distinct from duration)
- Song star/menu buttons always visible (removed opacity:0 hover-only rule)
- Artist all-songs page: added star + 3-dots to every row (same as top songs)
- Hero card: picks from full recent+frequent pool (was capped at first 5 = all frequent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scrobbling:
- Replace end-of-track scrobble with 12s timer approach
- Android: schedule on onTrackChanged, cancel on pause
- Web: schedule in playCurrent, cancel in togglePlay pause + onEnded
- Skip/new track resets the timer automatically (new song ID)
Artist top songs:
- Add star button and 3-dots context menu to every top song row
- Topsong grid expanded to 5 columns to fit action buttons
Settings — play count toggle:
- New "Bibliothek" section with toggle switch
- When on: shows play count in album/playlist song rows
- Artist page always shows play count regardless of toggle
- Persisted in ns_cfg localStorage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scrobble to Navidrome on auto-advance (Android) and track end (web)
so type=recent and type=frequent are accurate for the hero card
- Genre section: pill chips → grid cards with colored left bars
- Top 15 genres shown by default (sorted by song count), expand toggle
- Genre card: click → genre detail page with full song list
- Genre card: hover play button → shuffle-play without navigating
- Genre detail page reuses album-detail layout (back btn, stats, song list)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Home view:
- Add hero card (random pick from recent+frequent combined pool)
- Add genre browser chips (sorted by song count, tap = shuffle-play)
Android sync fix:
- Add currentId to getState() snapshot so JS can verify song identity
- onReady: reset stale localStorage queue when native song ID mismatches
- onTrackChanged: reject stale old-queue callbacks by ID verification
- onPlaybackStateChanged safety net: ID-guard prevents wrong-queue index corruption
- connectToService: call updateStateSnapshot() before onReady so getState() is never empty
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>