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>
is_live meint nur "laeuft gerade als Stream" — eine Konzert-AUFNAHME kam
ungehindert durch und landete als Albumtrack in der Bibliothek ("Dat Adam - Ufo
Video Days 2015 Live" auf dem echten Kuenstlerkanal, Dauer im Fenster). Der
Variantenfilter bekommt daher eine live-Regel. Sie schaltet sich ab, wenn der
gesuchte Titel selbst live enthaelt; die Pruefung nutzt \blive\b statt includes,
sonst wuerde "deliver" die Regel aushebeln. Drei Tests dazu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Folgekorrektur zu 02b5905: "… - 2020 Remaster" steht auf der Veroeffentlichung,
aber nie im YouTube-Titel — als Pflicht-Keyword haette die Zahl den richtigen
Upload abgelehnt. Jahreszahlen neben einem Qualifier (remaster/version/edition/
anniversary/mono/stereo) fallen daher wieder raus; ein Titel, der selbst eine
Jahreszahl ist ("1999", "Berlin 2020"), behaelt sie. Gegen 29 Beispieltitel
gemessen verhalten sich 24 exakt wie vorher.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"700 Main Street" lud "777 Main Street" eines fremden Artists: buildTitleKeywords
behielt nur die zwei laengsten Woerter (Street, Main), die unterscheidende 700
fiel als zu kurz raus — und Schritt 3 der Kaskade prueft den Kuenstler-Channel
nicht mehr. Zahlen werden jetzt immer als \b<n>\b gefordert. Dazu eine kleine
Abkuerzungstabelle (street/st, saint/st, mount/mt, doctor/dr, versus/vs,
number/no), denn der korrekte Art Track heisst "700 Main st" und scheiterte
bisher an jedem Schritt. Tests in backend/test/ytdlp.test.js.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fasst die Aenderungen seit 2.2 als Release zusammen: image-basiertes
docker-compose.yml (Bauen wandert nach .dev.yml), der auf GITEA_URL
umgeschriebene APK-Asset-Download und der Alben-Tab-Fix, dazu das
veroeffentlichte versionCode 3. Am Handy bestaetigt: Update-Dialog,
In-Place-Install und die Scrollpositionen funktionieren.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
APK mit dem Debug-Key vom alten Rechner gebaut (Signatur 1039a00e…, passend zur
installierten App) und als Asset im Release apk-latest ersetzt, Body auf
versionCode=3. Dazu das ausfuehrbare Bit auf gradlew, das im Clone fehlte, und
.idea/ ignoriert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LaunchedEffect(state.albumSort) { scrollToItem(0) } feuerte nicht nur beim
Sortierwechsel, sondern auch beim ersten Compose nach der Rueckkehr aus einem
Album — die im ViewModel gehaltene Position wurde also sofort ueberschrieben.
Das Hochscrollen sitzt jetzt in setAlbumSort() und laeuft nur bei echtem
Wechsel; dazu zwei ungenutzte remember*State-Importe entfernt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The app checks {backend}/api/apk/info on launch and, if a newer versionCode
than BuildConfig.VERSION_CODE is offered, shows an update dialog that
downloads {backend}/api/apk into a single wiped-each-time cache file and
hands it to the system installer (FileProvider + REQUEST_INSTALL_PACKAGES).
No APK hoarding; in-place install preserves data (stable signing key needed).
- backend /api/apk/info now returns versionCode (parsed from the release body).
- publish-apk.mjs auto-increments versionCode, sets versionName, and writes
versionCode/versionName into the Gitea release body.
Co-Authored-By: Claude Opus 4.8 <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>
You can still enjoy an incomplete album: the downloaded-tracks section now has
Abspielen + Zufall buttons (play/shuffle the downloaded songs) and each
downloaded row gets the 3-dot context menu, threaded from both artist screens.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The /api/apk + /api/apk/info routes now fetch a fixed Gitea release
("apk-latest") server-side over the LAN and stream its .apk asset, falling
back to /data/novasonic.apk. Gitea can stay internal-only — the backend holds
the token, the browser only ever talks to the backend. Follows redirects,
constant ~30 MB (overwrite the one release's asset each build).
env: GITEA_URL, GITEA_REPO, GITEA_TOKEN (private repo only), GITEA_APK_TAG.
docker-compose documents them; unset = local-file fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend serves a staged APK so co-users self-serve the latest build:
- GET /api/apk streams APK_PATH (default /data/novasonic.apk) with the
android package content-type + attachment disposition; 404 if absent.
- GET /api/apk/info reports availability, size, mtime, and an optional
version from /data/novasonic.version.txt.
- Settings gets a "🤖 Android App" section that appears only when an APK is
present and shows version/size/date.
Maintainer updates the APK with:
docker cp app-release.apk novasonic-backend:/data/novasonic.apk
Android source is already tracked; only build artifacts stay out of git.
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>
- .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>
- Downloads started from global search / Bibliothek now poll via the
app-lifetime NotificationStore, so they show success/failure like the
importer and artist page did (was a silent toast only).
- Swipe between tabs, synced with the tab bar, matching Erkunden:
* Now Playing: Player <-> Lyrics <-> Queue (cover's skip-track swipe
still wins as a child gesture).
* Bibliothek: Playlists <-> Alben <-> Künstler <-> Offline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Importer: each resolved track gets a play/stop button that plays its
Deezer/Spotify preview via the app-level preview bar (pauses library
playback, highlights the row). Parity with the web importer.
- Artist "Unvollständig" now works: Deezer's /artist/{id}/albums omits
nb_tracks, so owned albums never registered as partial. The artist VM
now resolves the real total for matched albums via a per-album lookup
(like web), so the partial section + downloaded/missing split populate.
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>
- CHANGELOG: remove the accidentally duplicated Changed/Fixed/Investigated
block that sat orphaned before [0.8]; [0.9] now appears once.
- README: artist-pages bullet now mentions the same-name artist pin
(Deezer link/id) and that a pinned artist's similar list comes from Deezer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Downloads were 403ing on two fronts: (1) recent yt-dlp needs a JS runtime to
solve YouTube's signature/nsig challenge (Deno, added in cb7a3d7), and (2) the
current yt-dlp *stable* had a bug that 403'd the android_vr client on every video
(yt-dlp#17456, fixed in nightly). Switch the backend from the stable yt-dlp
binary to nightly via pip (`--pre 'yt-dlp[default]'`). Verified end-to-end incl.
a real frontend download. No cookies / PO-token service / proxy needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
None of it got downloads working on the flagged datacenter IP (web_music media
403, then 429 rate-limiting), so revert the whole lot to keep things clean:
- config.js: remove potProviderUrl + ytdlpProxy
- ytdlp.js: remove potArgs/proxyArgs; player_client back to web_music,default
- novasonic-server.js: remove pot/proxy args from importer resolve
- docker-compose.yml: remove the bgutil-provider service + backend env/plugin/depends_on
- CHANGELOG: drop the [2.2] section
Kept the one genuine fix: a failed direct-link download now reports failure
(moved its note into [2.1]).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the cookies-file stopgap (needs a throwaway account + manual refresh)
with a self-hosted PO token provider — the cookie-less way past YouTube's
datacenter-IP block (bot check + media 403).
- docker-compose: new bgutil-provider service (brainicism/bgutil-ytdlp-pot-provider)
+ install the bgutil-ytdlp-pot-provider plugin in the backend + POT_PROVIDER_URL
env + depends_on.
- ytdlp.js / server.js: when POT_PROVIDER_URL is set, pass
--extractor-args youtubepot-bgutilhttp:base_url=... to the download cascade, the
YTM search probe and the importer resolve. Empty → omitted (unchanged behaviour).
- Removed the cookiesFile config + --cookies wiring so there's no dead code.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
YouTube now returns "Sign in to confirm you're not a bot" for yt-dlp from
datacenter IPs, blocking downloads, search and link-resolve. When a cookies file
is present (YTDLP_COOKIES, default /data/youtube-cookies.txt) every yt-dlp call
passes --cookies so it authenticates. Checked at call time (drop the file in, no
code change); absent → unchanged behaviour. Applied to the download cascade, the
YTM search probe, and the importer resolve.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wrap the Erkunden content in a HorizontalPager so a left/right swipe switches
between the Bibliothek and Global search tabs, two-way synced with the TabRow: a
chip tap animates the pager; a settled swipe updates the selected tab (and stops
any preview, like a tap). Removes the idea from the backlog now that it's built.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After skipping through already-buffered tracks the player stays READY, so
onPlaybackStateChanged never fires and syncState() isn't re-called; ctrl.duration
was TIME_UNSET(→0) at the transition, leaving state.durationMs stuck at 0. The
seek bar then computed fraction*0 = 0 and snapped playback to the start every time
(or to a stale value → "only 10s"). Restarting the app re-read the real duration,
which is why it recovered.
Fix: durationMs now self-heals — the 500ms ticker refreshes it too, and
resolveDurationMs() falls back to the current song's known duration while
ctrl.duration is still unset. Also record the Erkunden swipe-between-tabs idea.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pinned similar artists (Deezer related) were non-clickable on Android because
Deezer artists carry no Navidrome id. On tap, resolve the name against the
library (exact case-insensitive match) and navigate to that Navidrome artist if
present, else a short "not in your library" toast.
Also: extend the CHANGELOG "Artist following" backlog idea with a dedicated
"Following" view (grid of followed artists) + follow/unfollow toggle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the share-links / Wrapped / household-leaderboard / ListenBrainz ideas
into the existing "Planned / Backlog" section at the bottom of the CHANGELOG
(not a new top section), and add a short Musik-Match clause to the README
listening-stats feature.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CHANGELOG is the home for feature ideas going forward. Records the
share-by-link deep-link idea (PWA on PC / Android app on phone via App Links +
assetlinks.json), plus the already-tracked Wrapped, household leaderboard, and
optional ListenBrainz relay.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Android CompareScreen mirroring the web feature: partner picker fed by
GET /scrobble/users, then GET /scrobble/compare?with= → a match-score ring
(Canvas arc), per-user totals, mirrored shared-artist bars, shared tracks,
and each side's unique top artists. Rows navigate to artist/album. Reuses the
one PlayLogClient HttpClient (fetchUsers/fetchCompare). Opened from Settings
"🎧 Musik-Match". Parity matrix updated (Android ✅).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds GET /scrobble/users (distinct users for the partner picker) and
GET /scrobble/compare?with= to the scrobble service. Taste match = cosine
of the two users' artist-play vectors (0..100); also returns shared artists
(mirrored bars), shared tracks, and each side's unique top artists. Reuses
the existing per-user play-log — no new data capture.
Web UI src/ui/compare.js (overlay from Settings "🎧 Musik-Match"): partner
picker fed by /scrobble/users, match-score ring, per-user totals, shared +
unique lists with clickable artist/album rows. Cosine + shared logic verified
against a hand computation on an in-memory DB. Android pending (parity: 🔜).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With NowPlaying (or an ABS player) open, backgrounding and resuming the app made the next back
press pop the NavHost invisibly to Home before dismissing the overlay. Returning from the
background re-registers the NavController's own OnBackPressedCallback on top of the dispatcher,
stealing back-priority from the overlays.
AppNavigation now bumps a resumeTick on Lifecycle.Event.ON_RESUME and keys every overlay
DisposableEffect (NowPlaying, ABS player, ABS item, Erkunden overlay) on resumeTick +
currentBackEntry, so each overlay re-adds its callback above the NavController after a resume or
navigation. Needs on-device verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
Empty artist name (Deezer /artist/albums omits the artist object) + openDlModal defaulting to
type=track for album cards + downloads.js 'Unknown Artist' literal → yt-dlp searches
'Unknown Artist <album>' and fails. Documented as a Known Bug; no code change yet.
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>
- 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>
Preview bar:
- 30s previews used to keep playing with no control once you left the album/artist/Explore
screen they started on. Moved the preview player from ExploreViewModel to an app-level
PreviewController, and added a global PreviewBar (above the mini player, on every screen)
to pause/resume/stop it. Starting real library playback cancels the preview.
Artist fan count (parity with web):
- ItunesItem carries fanCount (Deezer nb_fan); shown on search artist cards and the artist
page ("559.542 Fans" / "1,2 Mio. Fans"). Combined with the existing nb_fan sort this makes
same-name collisions (5x "Joji") resolve to the right artist on Android too.
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>
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>
CHANGELOG: 1.2 entry for the post-1.1 work — web forward/back navigation, album
detail album-artist, metadata autofill "&" normalization, Android Erkunden back
re-assert + directional tab transitions, and the backend mixed-artist / album_artist
download fixes.
CLAUDE.md: album/artist download album_artist comes from iTunes collectionArtistName
(not the request/lead-single artist); client-side History router applies nsIdx on
popstate for both directions and must never pushState in the back path.
README left unchanged — the post-1.1 changes are fixes/polish, not new features.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Polish release covering Android navigation and offline resilience, download
accuracy for stylised artist names, and the lrclib lyrics-throttling fix.
Also updates the parity matrix (single-song offline downloads, player bar on all
screens) and corrects the Windows media-tile known bug — the two-element gapless
handoff was kept for skip speed, not reverted as the entry claimed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow artists and get notified on new releases (iTunes/MusicBrainz/Last.fm)
and nearby concerts (reusing the existing Ticketmaster integration), surfaced
in the in-app notification bell, with a download action from the alert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Themed/random discovery mixes (Rock/Charts/Random) that auto-download via the
yt-dlp backend, auto-delete after a configurable retention time, with an option
to keep tracks in the permanent library. Captures sources, retention, keep flow,
and open questions for later implementation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The two-element gapless handoff was reverted (switching <audio> elements
still ends/starts the OS media session, so Windows rebuilds the tile).
Correct the changelog claim and add a Known Bugs entry; the real fix is MSE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>