- 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>
Added _reorderCtx.active flag — plReorderEnter bails early if already
active, preventing duplicate handles and event listeners from stacking.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- button:focus{outline:none} — removes the blue tap-highlight box in WebView
- Playlist reorder: replaced always-on drag handles with explicit 'Neu anordnen'
mode. Handles only appear after tapping the button; a sticky Speichern/Abbrechen
bar appears above the list. Touch drag is now fully supported. Context-menu
(long-press) is blocked while reorder mode is active to prevent conflicts.
- styles.css: .pl-drag-handle always visible when pl-draggable (no hover needed)
- Edit modal footer: wrap buttons on small screens so Save doesn't overflow
- Edit modal overlay: remove backdrop-filter:blur — causes WebView compositor
glitch where 3/4 of the modal disappears after returning from background
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fillRadioQueue: use addToQueue instead of setQueue so toggling radio mode
no longer resets position to 0 on the current song
- loadStarred: pass noArtistLink/noAlbumLink to prevent accidental navigation
taps on mobile (matches playlist behavior)
- styles.css: add pointer:coarse alongside hover:none so song-actions are
visible on Android WebView which may report hover:hover
- components.js: remove inline download button from song row; keep only star
and 3-dots (download stays accessible via context menu)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- Add /api/romaji-index endpoint using kuromoji morphological analyzer
- Fetches all Navidrome songs, converts kanji → hiragana readings → romaji
- Only indexes songs with Japanese characters; caches for 20 min
- /api/romaji-index DELETE clears cache (force rebuild)
Frontend:
- fetchRomajiIndex() in api.js: lazy-fetches and caches the index
- doLocalSearch: when query is romaji (ASCII), queries romaji index in
parallel with Navidrome search3 and merges+deduplicates results
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When search input is pure ASCII (romaji), converts it to hiragana in
parallel using wanakana and merges both result sets. Deduplicates by ID
so no double entries. Helps find artists/songs tagged with hiragana in
sort fields (e.g. 'inabakumori' → 'いなばくもり').
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ⋮ button to every song-item triggering the context menu (same as right-click)
- Show song-actions always visible on touch devices via @media (hover:none)
- Search results: songs now render as makeSongItem rows instead of cards
(artists/albums remain as horizontal card rows)
- Fix Android app opening system settings on every launch after notification
permission denial — now only redirects to settings once using notif_settings_shown flag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
player.js
- Replace `_native()` function with `const HAS_BRIDGE` (evaluated once at
module load so bridge detection is rock-solid)
- Add `buildTrackList()` — maps S.queue to NSBridge Track array with 1024px
artworkUrl so lock-screen cover is crisp on high-DPR displays
- Add `_pushQueueIfBridge()` — called by `fillRadioQueue()` so radio top-ups
are pushed to ExoPlayer in real time (fixes autoplay gap after radio refill)
- `playQueue`: route through `NSBridge.setQueue` when bridge present; old
`S.audio` path retained for browser/PWA fallback
- `playCurrent`: `NSBridge.seekToIndex + play` in bridge mode
- `nextSong / prevSong / togglePlay / onEnded`: delegate to bridge, skip local
S.qIdx manipulation (native sends onTrackChanged callback instead)
- `seekTo / _initBarScrub`: use `_lastDurationMs` for ms-accurate seeks
- `onTrackChanged`: guard with `p.index !== S.qIdx` to avoid redundant redraws
- `updateMediaSession`: artwork sizes 96/300/512 → 256/512/1024
- Player-bar cover: 100 → 256px; NP cover/bg: 400 → 1024px;
queue items: 60 → 128px; preload hint sizes: 96/300/512 → 256/512/1024
main.js
- Wrap watchdog setInterval in `typeof window.NSBridge === 'undefined'` guard;
the foreground service keeps ExoPlayer alive — JS watchdog not needed there
views.js
- Album detail / playlist detail covers: 300 → 512px
- Playlist grid cards: 200 → 512px
- Search result song cards: 200 → 256px
- Artist top-songs / all-songs thumbnails: 60 → 128px
- Partial album art in _renderArtistAlbums: 300 → 512px
components.js
- makeAlbumCard: 200 → 512px
- makeSongItem: 60 → 128px
nowplaying.js
- NP queue items: 60 → 128px
- NP action sheet thumb: 80 → 256px
sw.js
- Bump CACHE to ns-v41 so stale small-cover entries are evicted on next load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
e.target in dragstart is always the draggable item element, never the
handle child — so closest('.pl-drag-handle') always returned null and
every drag was cancelled. Now mousedown on handle sets fromHandle=true
and item.draggable=true; dragstart checks the bool instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
draggable=true must be set before any mouse event (not on mousedown).
Firefox requires setData() for drag to work. Removed duplicate dragend
listener that was resetting draggable=false and conflicting with the first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drag handle appears on hover, moves items in DOM, syncs order to
songs array, and saves to Navidrome via createPlaylist 800ms after
last drop (debounced to avoid rapid API calls).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- renderQueue: in-place active-class update when queue unchanged — no DOM rebuild, no image reload
- updatePlayerUI: update img.src directly instead of replacing innerHTML
- _syncNpDirect: same img.src update for now-playing cover
- navigate('home'): always reloads home data so new albums appear without restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CSS: target #player-artist-name span:hover and #np-artist-name span:hover so only the hovered artist name highlights, not all at once
- change artist separator from / to • in song items, player bar, now playing overlay, album header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- components.js: expand artist split regex to handle both ' / ' and '•' separators
- views.js: enable artist+album links in playlist song items (were noArtistLink:true)
- views.js: enable artist links in album detail song items
- views.js: album header artist is now split and renders as clickable links
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sw.js: ns-v38 → ns-v39 forces PWA cache clear so previous fixes take effect
- components: split s.artist by ' / ' in makeSongItem — each part becomes its
own clickable link; noArtistLink turns all parts into plain text
- views: s_goArtist searches Navidrome by name when no artistId is available
(needed for secondary artists that only have the primary artistId in the song)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- nowplaying: add cancellation token to _loadNpLyrics to prevent stale async
calls overwriting newer results (synced→plain race condition)
- nowplaying: replace scrollIntoView with explicit container.scrollTo to prevent
page-level scroll on desktop (topbar shifting / gap at edge)
- backend: remove results[0] fallback in lookupItunesTrackMeta — only use
exact title+artist match to avoid embedding wrong artwork
- components/views: makeSongItem accepts noArtistLink/noAlbumLink opts;
album-detail disables both, playlist-detail disables artist link
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds POST /api/spotify/track-artwork endpoint that takes an array of
{idx, trackName, artistName} and returns iTunes artwork URLs in batches
of 8 concurrent requests.
Frontend calls this after rendering the track list, then patches each
.gtrack-art element with the correct album cover as results arrive.
Also updates _spotifyResolved.tracks so downloads use the right artwork.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _spTrackItem now unwraps GraphQL-style {data:{__typename,...}} wrappers
- _spAllArtists joins all featured artists (e.g. "Kløn, Renzyx")
- _spArtwork adds albumOfTrack path used by newer Spotify embed
- fetchSpotifyEmbed: added entity.content.items / entity.data fallback paths
- Track download now uses Spotify metadata directly (album, art, year,
trackNum) when provided — avoids iTunes returning wrong album for
multi-artist tracks
- Playlist trackList also tries entity.content.items and entity.tracks.items
- Frontend: redesigned preview card with green top accent, bigger art,
playlist checkbox styled as pill with Spotify color on check, album
button shows actual album name
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace Client Credentials flow with Spotify's public web-player token endpoint
(open.spotify.com/get_access_token) which works without any account or Premium.
Remove Spotify credential fields from UI entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New 'Spotify Importer' page under Entdecken (sidebar + mobile nav)
- Backend: POST /api/spotify/resolve — resolves track/album/playlist URLs via Spotify API
- Backend: new 'playlist' download type accepting pre-resolved track list (no iTunes re-fetch)
- Frontend: paste Spotify link → preview with cover, type badge, track list
- Track: single download or full album via iTunes fallback
- Album: download all tracks with Spotify metadata
- Playlist: download all tracks + optional 'Als Navidrome-Playlist speichern' checkbox
- Navidrome playlist creation: searches library by title/artist after download, creates playlist
- Spotify Client ID + Secret stored in app settings (setup screen + settings modal)
- iTunes metadata tags kept for all downloaded files (existing pipeline unchanged)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Hero height 220px mobile → 320px desktop (min-width: 769px)
- Larger artist name (46px) and more padding on desktop
- Overlay gradient now also darkens the right edge instead of fading to transparent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 'Alle X Songs' button now navigates to a dedicated page instead of expanding inline
- Partial/not-downloaded albums now open an iTunes album detail page with track list,
preview buttons and download option
- Added page-artist-all-songs and page-itunes-album-detail to index.html
- Replaced _expandArtistAllSongs with loadArtistAllSongs + loadItunesAlbumDetail
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- loadArtistDetail now works for both Navidrome artists and iTunes-only
artists (id=null + itunesArtistId). loadItunesArtistResults from global
search now navigates to the same artist-detail page.
- Album sections: "In Library" (fully downloaded), "Unvollständig" (partial
— e.g. 2/12 tracks), "Noch nicht heruntergeladen" (iTunes-only). Partial
albums show X/Y badge and a direct download button on hover.
- iTunes albums now fetched via lookup endpoint (up to 200 albums) instead
of search (max 20), fixing the missing-albums problem.
- "Alle X Songs in Library" expand button fetches all songs across albums
and renders them grouped by album with play/context-menu support.
- Back button uses _navGoBack() for context-aware navigation.
- CSS: .section-count, .album-partial-badge, .card-dl-btn (hover reveal)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- fetchLyrics() queries LRCLIB for plain + synced lyrics
- Synced lyrics saved as .lrc file (Navidrome reads these)
- Plain lyrics embedded via ffmpeg -metadata lyrics=
- ITUNESADVISORY=1 written for explicit tracks
- explicit flag passed through album/track download chain
- fetchItunesTracks + searchItunesAlbumTracks use country=us&explicit=Yes
Frontend:
- explicit:true sent in download payload from iTunes track data
- "E" badge shown on explicit tracks in global search track list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
iTunes search endpoint always returns cleaned versions.
Added itunesLookupArtistSongs() using /lookup?explicit=Yes which returns
the actual explicit tracks. Used in artist profile (global search) and
artist detail page (library). Album track lookup also gets explicit=Yes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
country=de silently filtered explicit content from iTunes results.
Switched to country=us&explicit=Yes so all songs appear regardless of explicit flag.
Removed wrong explicit-filtering code from views.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Global search now shows only iTunes results (no Navidrome library section)
- All iTunes results (tracks, albums, artist pages, album detail, recommendations)
filtered to exclude explicit content (trackExplicitness/collectionExplicitness === 'explicit')
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Player bar: drag handle pill + live lift/peek animation when swiping up to NP
- Global search: now includes Navidrome library results alongside iTunes; fix songs not showing (filter wrapperType=track)
- iTunes artist page: hero banner with album art, top songs section
- Artist detail: iTunes global top tracks + extra albums not on server
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>