Commit Graph
34 Commits
Author SHA1 Message Date
HoneyandClaude Opus 4.8 26880016e9 feat(scrobble): web statistics view (Phase 3 UI)
Opens from Settings ("Meine Statistiken") as a self-contained modal overlay
(no router changes). Fetches GET /scrobble/stats and renders: totals tiles
(plays + listening time), a per-bucket play series, top artists/tracks/albums
with mini bars, and a 24h listening clock. Period tabs (all / this year /
last 30 days) switch from/to + bucket. Vanilla inline SVG-free bars, no chart
lib. Reuses .modal-overlay.open + design tokens; sends local tz offset for
day bucketing. Graceful empty/error/unconfigured states. SW cache -> ns-v65.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-07 17:17:14 +02:00
HoneyandClaude Opus 4.8 71190488ca feat(scrobble): own timestamped play-log service (stats/Wrapped foundation)
Phase 1+2 of a self-hosted, multi-user listening archive — the basis for
day/month/year stats and a yearly "Wrapped". Runs IN ADDITION to the existing
Navidrome/Last.fm scrobble; never replaces it.

Service (isolated, own container):
- scrobble/scrobble-server.js — node:sqlite play-log (own volume), routes
  POST /scrobble/plays (idempotent by uuid), GET /scrobble/history (archive),
  DELETE /scrobble/plays?user= (test cleanup), GET /scrobble/health.
  Multi-user auth via Navidrome ping.view (cached) -> user not spoofable.
- docker-compose: novasonic-scrobble service (node:22, port 3710) + volume;
  backend bumped node:20->22 (EOL); SCROBBLE_URL plumbing (entrypoint, .env.example).

Capture (both platforms, offline-safe queue, idempotent batch flush):
- Fires at the existing scrobbleMode counted-play moment — consistent with
  Last.fm, respects the user's setting (no separate 50%/4min rule).
- Web: src/player/scrobbleLog.js (localStorage queue) + subsonicAuthParams()
  in api.js + hooks in player.js + flush init in main.js; S.cfg.scrobbleUrl.
- Android: data/PlayLogClient.kt (filesDir queue, reused Ktor client) +
  repo.logPlay/flushPlayLog + PlayerController hook + NovaSonicApp flush +
  scrobbleUrl in PrefsStore/SettingsViewModel/SettingsScreen (with test button).

Plan + design decisions documented in docs/scrobble-plan.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 22:54:25 +02:00
HoneyandClaude Opus 4.8 17bb527e1d fix: download notification opened the wrong song (same-name album)
The finished-download notification resolved the album by name via search3, then fell back to
a same-named album by a DIFFERENT artist (or the first result) when the exact artist match
wasn't found — e.g. right after download, before Navidrome rescans. Many artists share an album
title ("I LIKE IT!"), so the notification navigated to a completely different song.

- Web (notify.js) + Android (AppNavigation): require the artist to match when known (fuzzy for
  multi-artist / album-artist drift); drop the name-only and first-result fallbacks. If nothing
  matches, show "wird evtl. gerade gescannt" instead of opening a wrong album.

The download itself was correct all along (verified: it grabbed UPSAHL's official "I LIKE IT!"
visualizer) — only the notification's navigation was wrong. SW ns-v63.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-01 02:30:53 +02:00
HoneyandClaude Opus 4.8 1e206e8df3 fix(web): 'Unknown Artist' download from artist page + rename itunes*→deezer* identifiers
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>
2026-08-01 00:31:09 +02:00
HoneyandClaude Opus 4.8 96861bea06 docs(web): unify the artist / album-artist separator hints in the editor
Both fields already accept ; and , (the backend normalizer splits on •·;,). The hints now
read uniformly — "mehrere mit ; oder , trennen" — and explain the actual difference: artist
is split into individual artists, album-artist stays one combined credit. SW ns-v61.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-01 00:10:04 +02:00
HoneyandClaude Opus 4.8 e4f030c98d fix: artist page resolved "Flay!" to the far more popular "K.FLAY"
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>
2026-07-31 23:53:42 +02:00
HoneyandClaude Opus 4.8 b3cf01dd3b fix: duplicate rows in artist "Top Songs" when a track exists twice in the library
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>
2026-07-31 17:48:02 +02:00
HoneyandClaude Opus 4.8 fbee695aad fix: album shown 2-3x in "In Library" on the artist page
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>
2026-07-31 17:41:33 +02:00
HoneyandClaude Opus 4.8 cc4d3b72d2 chore: bump SW to ns-v57 after reverting the artist-image fallback
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-31 17:21:31 +02:00
Honey e059c358b3 Revert "feat: Deezer artist photo fallback for local artists with no Navidrome image"
This reverts commit 569af968c5.
2026-07-31 17:20:45 +02:00
HoneyandClaude Opus 4.8 569af968c5 feat: Deezer artist photo fallback for local artists with no Navidrome image
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>
2026-07-31 17:07:53 +02:00
HoneyandClaude Opus 4.8 640e475c51 fix: owned albums wrongly shown as "Unvollständig 1/0" on artist page
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>
2026-07-31 17:05:14 +02:00
HoneyandClaude Opus 4.8 5528947a93 feat: fans on local Navidrome artists (android) + harmonize web fan layout
- 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>
2026-07-31 16:31:55 +02:00
HoneyandClaude Opus 4.8 c92ac3d719 fix(web): cleaner German fan-count format (559.542 instead of "559,5 Tsd.")
fmtCount now shows the full grouped number below a million (toLocaleString de-DE) and only
compacts millions ("1,2 Mio."). The old "Tsd." form read awkwardly on the artist hero. SW ns-v53.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-31 16:16:03 +02:00
HoneyandClaude Opus 4.8 065d2f7319 feat: disambiguate same-name artists by Deezer fan count + show fans
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>
2026-07-31 16:11:45 +02:00
HoneyandClaude Opus 4.8 1e69831dd8 feat: move the whole metadata/search stack from iTunes to Deezer (1.3)
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>
2026-07-31 12:48:33 +02:00
HoneyandClaude Opus 4.8 a65613b5a1 fix(web): Deezer artist images + rename the search label
- 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>
2026-07-30 22:01:59 +02:00
HoneyandClaude Opus 4.8 68a9cdd7d1 feat(web): switch the Apple/Erkunden search to Deezer
Apple's iTunes /search returns 403 from server IPs, killing the Erkunden search. Deezer
is free, needs no key, and is actually better: it returns explicit versions, artist
images, track durations and 30s previews.

- Backend: /api/deezer/* proxy (CORS-safe); album download accepts a `tracks` array in
  the request body (Deezer albums send their tracks directly, pre-mapped).
- api.js: itunesSearch / itunesLookupAlbum / itunesLookupArtistSongs|Albums now hit
  Deezer and map results to the existing ItunesItem shape, so the UI + download flow are
  unchanged. Function names kept.
- downloads.js: album downloads fetch the Deezer album's tracks and send them in the body.

iTunes /lookup (by ID) and the charts RSS proxy stay. Known follow-ups: artist-type
download still calls iTunes /search server-side; the metadata-editor iTunes autofill;
and the Android client — all still on iTunes for now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 20:54:10 +02:00
HoneyandClaude Opus 4.8 ccf704754a fix(web): proxy Apple charts (CORS) + clear message when Apple search is blocked
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>
2026-07-30 20:32:08 +02:00
HoneyandClaude Opus 4.8 9c8b4775ea fix: use the US iTunes store everywhere for consistent artist naming
Search (web api.js) was JP-first, the download lookups were JP-first, and the Android
client was already US — so the same artist resolved inconsistently: sometimes kanji,
sometimes katakana, sometimes romaji, depending on which path and which store matched.

Switch everything to the US store:
- api.js itunesSearch → country=us (one fetch, matches the Android client)
- metadata.js lookupItunesTrackMeta → US only (drops the JP fetch: consistent names +
  halves the download's iTunes calls, which also eases the rate limit)
- searchItunesAlbumTracks → country=us
- remove the JP-katakana guards in the album/artist download paths (redundant now, and
  they wrongly romanized genuine Japanese artists like 羽岡佳 → "Kei Haneoka")

Titles still come from the source, so a Japanese title the user picked stays Japanese;
only iTunes-derived fields (album_artist, genre, year, artwork) follow the US store.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 10:37:07 +02:00
HoneyandClaude Opus 4.8 d98978a6c7 feat(web): refresh the current view after deleting a song or playlist
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>
2026-07-26 23:27:29 +02:00
HoneyandClaude Opus 4.8 f097dddc64 feat(web): Enter in the importer field starts the import
Pressing Enter in the importer link input now triggers resolveImporterLink()
(same as the Importieren button). Bump SW cache to ns-v45.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 23:11:44 +02:00
HoneyandClaude Opus 4.8 1e242796fe feat(web+android): search play queues only the tapped song (radio fills the rest)
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>
2026-07-25 22:00:58 +02:00
HoneyandClaude Opus 4.8 179e0eb902 fix(web): center NowPlaying star vertically with the whole track-info block
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>
2026-07-25 01:42:09 +02:00
HoneyandClaude Opus 4.8 ea98d81146 chore(web): bump service-worker cache to ns-v42
Forces installed PWAs to pull the new build (metadata "&" split + forward
navigation fixes) instead of serving stale cached assets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 22:54:36 +02:00
HoneyandClaude Opus 4.8 ffa4fb35ea chore(pwa): desktop install polish for manifest
Add PNG icons (192/512) for a crisp taskbar/Start-menu icon, drop the
portrait orientation lock so the window resizes freely on desktop, and add
a stable app id. Installs as a standalone desktop window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 13:59:58 +02:00
HoneyandClaude Sonnet 4.6 8e81404ab6 fix(ui): new equalizer logo, favicon link, fix favicon path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 19:37:15 +02:00
HoneyandClaude Sonnet 4.6 d4ebcf0447 fix(ui): fix shuffle badge shift, regenerate PNG icons, fix favicon path
- 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>
2026-06-21 22:24:28 +02:00
HoneyandClaude Sonnet 4.6 3d6b74b873 feat(ui): new equalizer logo, favicon link, fix star vertical alignment
- 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>
2026-06-21 21:38:40 +02:00
HoneyandClaude Sonnet 4.6 76ecfe2268 feat: wire NSBridge fully + HD artwork + disable watchdog in wrapper mode
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>
2026-05-09 10:33:43 +02:00
HoneyandClaude Sonnet 4.6 d355129134 fix: lock screen audio + notification — audioSession once, deferred metadata, SW artwork cache
- Set navigator.audioSession.type='playback' only once (guarded flag) — repeated
  reassignment was resetting the OS audio session and triggering Doze network cuts
- Defer updateMediaSession() until after play() resolves, not before audio.src swap —
  new MediaMetadata during HAVE_NOTHING state caused Samsung to close/reopen notification
- Fix silent bug: updatePlayerUI(s, true) now actually skips MediaSession (added param)
- 10s safety guards now attempt play() instead of just resetting _trackChanging flag
- SW v40: cache-first for getCoverArt with token-stripped keys — artwork served from
  cache even when Doze blocks network, survives session restarts with new auth tokens
- Pre-warm next track artwork 2s after song starts (earlier than 30s-remaining trigger)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 18:37:23 +02:00
HoneyandClaude Sonnet 4.6 89e169b70a fix: prevent notification close on skip and background audio stall
- playCurrent: set _trackChanging flag and update media session metadata
  before changing src, so the pause event fired by src-swap does not
  set playbackState='paused' and dismiss the Samsung cover screen notification
- Remove stalled->load() handler that silently paused audio with no recovery
- Error handler now retries regardless of paused state (load() causes paused)
- Add .catch() to audio.play() in playCurrent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 20:23:51 +02:00
HoneyandClaude Sonnet 4.6 51babd9e41 fix: bump SW cache to v39, split multi-artists by ' / ', name-search fallback for artist nav
- 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>
2026-04-28 00:13:13 +02:00
HoneyandClaude Sonnet 4.6 9aded56713 Migrate to Vite + ES module architecture
Split monolithic app.js (~1850 lines) into maintainable ES modules:
- src/state.js  — shared mutable state (S object)
- src/utils.js  — md5, esc, formatDur, toast
- src/api.js    — Subsonic API, Navidrome REST, iTunes API
- src/player.js — playback, transport, queue, media session, preview
- src/views.js  — navigation, page loaders, card makers, search
- src/ui.js     — all modals, overlays, sidebar, settings
- src/pwa.js    — service worker registration + update banner
- src/main.js   — app init, connect, window globals

Updated Dockerfile to multi-stage (Node build → nginx serve).
Static assets (sw.js, manifest.json, icon.svg) moved to public/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:01:34 +02:00