Commit Graph
796 Commits
Author SHA1 Message Date
HoneyandClaude Opus 4.8 a57e18198f docs(changelog): correct metadata-editor autofill bug description
Artists are stored ;-separated (feat./ft. normalized away at download), and
both lookups already use the first artist. The real gap is the strict match
failing for multi-artist tracks (no fallback) plus iTunes autofill not filling
the artist/album-artist fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 13:00:46 +02:00
HoneyandClaude Opus 4.8 6e7a18302a docs(changelog): note duplicate check is case-sensitive (filename FS match)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 12:58:19 +02:00
HoneyandClaude Opus 4.8 8b6b4b0839 docs(changelog): track playlist re-download (dupe drift) + no-duration wrong-cover bugs
Duplicate check matches exact computed path only, so iTunes/romaji/trackNum/
album drift between runs re-downloads existing tracks. The no-duration cascade
branch lacks Mittelweg protection and grabs wrong covers (Yorushika 車窓 →
Piano Echoes Shasou).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 12:52:58 +02:00
HoneyandClaude Opus 4.8 f3fa68928c docs(changelog): track metadata-editor autofill bugs (album artist + multi-artist split)
iTunes/MusicBrainz autofill don't fill the Album-Artist field; multi-artist
lookup splitting is inconsistent across the two endpoints (neither handles
/, feat., ft.).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 12:49:59 +02:00
HoneyandClaude Opus 4.8 feaa8ee61f docs(changelog): track known backend download/metadata bugs
Duplicate detection (filename-only), missing download size cap (52MB wrong
video), and uncaptured iTunes album artist (collectionArtistName) affecting
Navidrome grouping/sorting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 12:45:36 +02:00
HoneyandClaude Opus 4.8 4c85fec4ca docs: add CLAUDE.md with project conventions and download-pipeline knowledge
Captures the rules and hard-won details from recent work: no mocked frontend
data, two-part versioning, yt-dlp-only download cascade + Mittelweg rule, file
naming, the YTM-search limitation for obscure tracks, and edit/delete file
resolution. Loaded automatically by Claude Code at session start.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:48:29 +02:00
HoneyandClaude Opus 4.8 0fe2de5084 docs(changelog): release 0.8 + switch to two-part MAJOR.MINOR versioning
- Promote 0.7.1 → 0.8 with this session's work (Mittelweg cascade, web_music
  client, clean filename, duration-filter + metadata-editor fixes, squid removal)
- Drop the patch component from all historical versions (0.7.0 → 0.7, etc.)
- Add Metadata editor and Song delete rows to the parity matrix (Web only)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 18:35:21 +02:00
HoneyandClaude Opus 4.8 a870f50c87 feat(backend): Mittelweg cascade — never download wrong song on no duration match
When a track's duration is known but nothing matches the duration window, the
cascade no longer falls back to blind title-only/no-filter grabs (which pulled
same-titled videos by other uploaders, e.g. a "recognize" fan clip). Instead it
relaxes only the duration constraint while still REQUIRING the artist channel +
title. If nothing matches, the download produces no file and the job now fails
cleanly with "Nicht eindeutig gefunden — bitte Direktlink im Importer nutzen"
(previously it falsely reported success with no file).

Single, album and playlist paths all surface the not-found state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 18:13:59 +02:00
HoneyandClaude Opus 4.8 cf6e75b312 fix(backend): restore YTM Songs filter param for cleaner song search
Re-add the &params= Songs-shelf filter (the documented way to song-search
YouTube Music) so results exclude videos/playlists/clips. Verified neutral
for the ambiguous-artist case (yt-dlp's search can't surface that track
regardless) but cleaner for normal tracks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:51:43 +02:00
HoneyandClaude Opus 4.8 1e8e5e8874 fix(backend): strip release suffix from filename too (01 - Artist - Title)
The filename used the raw title, so "recognize - Single" leaked into the
filename ("01 - MINA - recognize - Single.mp3"). Now the cleaned title drives
both the filename and the embedded title tag, giving "01 - MINA - recognize.mp3"
(Track - Artist - Title). The album folder keeps the full album name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:29:14 +02:00
HoneyandClaude Opus 4.8 902243586b fix(backend): match files when Navidrome stem desyncs by release suffix
After the embedded-title change (recognize vs "recognize - Single"),
Navidrome's title-derived stem can drop the "- Single"/"- EP" suffix the
on-disk filename still carries, so _findFileByStem matched neither exact nor
suffix and every fallback failed -> "File not found on disk" on edit/delete.
Now also match on the structured prefix in both directions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:39:35 +02:00
HoneyandClaude Opus 4.8 77bbb18c8d chore(backend): include resolved path in delete-song "file not found" error
The delete-song endpoint returned a bare "File not found on disk" with no
context, making path-resolution mismatches impossible to diagnose. Now
reports the Navidrome path, music root, and searched filename — matching the
detail the edit-metadata endpoint already provides.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:32:46 +02:00
HoneyandClaude Opus 4.8 e93c0c7ab1 fix(backend): drop YTM Songs filter param — it dropped the exact track
The &params= Songs-tab filter re-ranked YTM results toward a same-named
artist's discography and excluded the official track (qNffDItRUeo) entirely,
even logged-out. The default blended search (what the browser shows) ranks
the exact track first. Removing the param so yt-dlp gets the same result set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:15:12 +02:00
HoneyandClaude Opus 4.8 c4e4c8c090 fix(backend): use space (not dash) between artist and title in search query
"MINA - recognize" skewed YTM ranking toward a same-named artist's full
discography, so the actual track (qNffDItRUeo) never appeared in results.
"MINA recognize" — matching how users type in the YTM search box — ranks
the exact official track first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:06:00 +02:00
HoneyandClaude Opus 4.8 c34123af38 fix(backend): apply web_music client to YTM search extraction too
The YTM search (getYtmVideoUrls) ran without the web_music client, so yt-dlp
parsed the search page as a generic YouTube search and returned unrelated
videos instead of the native Songs shelf — the actual artist track (e.g.
qNffDItRUeo for MINA - recognize) never appeared in results. Apply the same
player_client=web_music,default arg to the search step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:48:26 +02:00
HoneyandClaude Opus 4.8 9014659112 fix(backend): use web_music player client for YTM track access
yt-dlp's android_vr player client returns "This video is not available" for
many YouTube Music Art Track / topic uploads (e.g. MINA - recognize) that
play fine in the browser. Switch to player_client=web_music with default
fallback so these tracks become downloadable, fixing the cascade falling
through to wrong YouTube videos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:46:53 +02:00
HoneyandClaude Sonnet 4.6 ce4577e8f4 fix(backend): strip release suffix from title metadata tag
searchTitle (e.g. "recognize") is now passed to embedMetadata instead of
the raw title ("recognize - Single"), so the ID3 title tag matches the
actual song name. Album tag stays unchanged ("recognize - Single").

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:44:13 +02:00
HoneyandClaude Sonnet 4.6 bb0be52f46 fix(backend): fix getYtmVideoUrls always returning empty + remove unsupported ytmsearch5
[youtube:tab] in stderr fires for all YTM search page processing, not just
artist channel expansion, causing channelExpanded=true for every search and
returning [] — bypassing all YTM cascade steps entirely. Fix: only check
stdout for /browse/UC (actual channel URL in results). Remove ytmsearch5:
scheme which is not supported by this yt-dlp version. Revert pool to 10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:32:22 +02:00
HoneyandClaude Sonnet 4.6 3c54850cca fix(backend): add ytmsearch5 step + expand YTM pool to 20 for better coverage
ytmsearch5 (yt-dlp native YTM search) added as step 2 — returns different
result ordering than the manual YTM URL, recovering songs that the URL
approach ranks too low. YTM pool expanded from 10 → 20 results to reduce
fallthrough to YouTube.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:25:37 +02:00
HoneyandClaude Sonnet 4.6 4804023a98 fix(backend): don't abort cascade on yt-dlp non-zero exit (e.g. video not available)
spawnYtdlp now always resolves so the hasAudio() cascade continues when a
single YTM result is geo-blocked or unavailable. Error is still logged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:23:59 +02:00
HoneyandClaude Sonnet 4.6 88d3e336c8 fix(backend): use searchTitle in YTM URL (was using raw title with release suffix)
YTM search used `title` directly, so "recognize - Single" was searched on
YouTube Music instead of "recognize" — causing no YTM results and falling
through to the less reliable YouTube search cascade.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:19:43 +02:00
HoneyandClaude Sonnet 4.6 d5bc887f31 fix(backend): tighten duration filter margin from ±15% to ±10% (min 10s)
Matches spotdl behaviour; still covers YouTube intros/outros while
reducing false positives from similarly-lengthed wrong songs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:07:19 +02:00
HoneyandClaude Sonnet 4.6 4cbfa35655 fix(backend): normalize iTunes lookup across all download paths
- Track path: always fetch iTunes when durationMs/trackNum missing (not just genre)
- Both paths: strip release suffixes from title before iTunes lookup
- Log "Album:" instead of "Album aus Spotify:" (also fires for iTunes global search)
- Log duration when fetched from iTunes so it's visible in job log

Ensures all importers have consistent yt-dlp search filters (duration filter
now always runs when iTunes finds the track).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:00:32 +02:00
HoneyandClaude Sonnet 4.6 86d3999628 fix(download): pass Spotify durationMs through to yt-dlp duration filter
- downloads.js: use durationMs (Spotify) before trackTimeMillis (iTunes)
- server: read durationMs from top-level body, not only from tracks[0]
- server: also grab durationMs + trackNum from iTunes in the Spotify path

Without durationMs the duration filter never ran, allowing a 58s fan video
to pass all text filters and be downloaded instead of the actual song.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:55:17 +02:00
HoneyandClaude Sonnet 4.6 5f257bf451 fix(backend): generalize release-suffix stripping from search title
Handles all Spotify trailing suffixes: Single, EP, Remaster, Deluxe Edition,
Bonus Tracks, B-Sides, bare years (2024), Official Music Single, etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:44:06 +02:00
HoneyandClaude Sonnet 4.6 bff652a415 fix(backend): strip release-type suffixes from YTM search title + extend stopwords
- "recognize - Single" title → search uses "recognize" (strips "- Single/EP/Album/…")
- Adds Single/EP/Remaster/Deluxe/… to titleKws STOPWORDS so they never become
  mandatory title-match filters (no YouTube video title contains "Single")

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:42:07 +02:00
HoneyandClaude Sonnet 4.6 d1b107ae71 docs: add CHANGELOG v0.7.1 (squid removal + ytdlp search fix)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:36:42 +02:00
HoneyandClaude Sonnet 4.6 ecb7721e3b fix(backend): only append album to YTM query for Japanese titles
Western tracks now use artist + title only (matches spotdl behaviour).
Album suffix was causing noise for short English titles like singles
where album name = title + "Single".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:35:32 +02:00
HoneyandClaude Sonnet 4.6 319a3804f3 fix(backend): strip title-duplicate and generic words from album search suffix
Album "recognize - Single" produced query "MINA - recognize recognize Single"
because "recognize" appeared in both title and album name. Now filters album
words already present in the title, plus generic words (Single, EP, Remaster…).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:29:39 +02:00
HoneyandClaude Sonnet 4.6 f1bdd811ca chore: remove squid.wtf (Qobuz) backend — yt-dlp only
Deletes backend/lib/squid.js; strips all references from downloader.js,
config.js, novasonic-server.js, docker-compose.yml, .env.example, README,
and CHANGELOG. Default DOWNLOAD_BACKEND is now ytdlp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:59:30 +02:00
HoneyandClaude Sonnet 4.6 925c173c15 chore: stage previously uncommitted screen changes (ArtistDetail, ItunesArtist, NowPlaying)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:52:42 +02:00
Honey 85252e24df fix: unresolved reference 'albums' after rename to albumsByNewest/albumsByName 2026-06-18 20:21:36 +02:00
HoneyandClaude Sonnet 4.6 7030f029b7 docs: finalize CHANGELOG v0.7.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:15:11 +02:00
HoneyandClaude Sonnet 4.6 93c5fb481d feat(android): add description field to playlist edit dialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:13:19 +02:00
HoneyandClaude Sonnet 4.6 43cf11465e fix: playlist edit — cover upload in pencil dialog, remove cover-remove button
Android: camera hero button removed; cover upload moved into edit dialog.
Web: trash icon removed from cover actions in edit modal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:12:27 +02:00
HoneyandClaude Sonnet 4.6 6a1bf33f31 fix(android): album Neueste now uses Navidrome type=newest (add-date), not year sort
Loads albumsByNewest (type=newest) and albumsByName (alphabetical) in parallel.
Neueste → Navidrome add-date order; A-Z → alphabetical; Älteste → client-side by year asc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:09:32 +02:00
HoneyandClaude Sonnet 4.6 9ffd071c58 feat(android): artist fast-scroll index bar with letter bubble
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:06:44 +02:00
HoneyandClaude Sonnet 4.6 dd484df636 fix(android): album sort — scroll to top on change, remove Künstler, default Neueste
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:05:00 +02:00
HoneyandClaude Sonnet 4.6 6be7981b8a feat(android): playlist mgmt, album sort, home hero parity, notification fix
- Playlist: create (FAB + dialog), rename, delete, remove songs, cover upload/remove
- Library Albums tab: sort chips (Neueste/Älteste/A-Z/Künstler) matching StarredScreen
- ABS notification: shows current chapter title; updates live without re-buffering
- Home hero: pool now matches web (frequent(5) ∪ recentlyPlayed(5)); label MEISTGESPIELT / ZULETZT GEHÖRT
- Home: added Zuletzt gehört section (type=recent) matching web layout
- Notifications: fixed delivery bug — polling moved to NotificationStore app-lifetime scope so notifications arrive even after user navigates away from ImporterScreen
- NotificationStore.startPoll() extended with navType/navAlbum/navArtist/navId params
- Removed defunct AbsPlayerService.kt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 19:04:36 +02:00
HoneyandClaude Sonnet 4.6 eacce487df fix: lyrics scroll — center active line only, remove drift phase
The 'drift to next line' animation moved the text while the user was
still reading/singing the current line, making it feel out of sync.
Now matches the web behavior: smooth-scroll to center the active line
when it changes, nothing more.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 01:39:51 +02:00
HoneyandClaude Sonnet 4.6 ec9c0add9e feat: notification nav + ABS search + HTML desc fix + brighter NowPlaying bg
- 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>
2026-06-17 01:38:58 +02:00
HoneyandClaude Sonnet 4.6 72feac41bb fix: ABS skip library list + nav label overflow + NowPlaying top bar + dominant color bg
- ABS: new AbsBooksScreen loads books directly on tab open; library pills shown only when >1 library — no more two-step library→books navigation
- Nav: shorten "Hörbücher"→"Bücher", "Einstellungen"→"Einst.", reduce label font to 9sp with maxLines=1 to prevent label wrapping
- NowPlaying: increase top padding so the tab bar sits lower on screen (statusBarTop + 16dp, min 52dp)
- NowPlaying: replace blurred-cover background with dominant-color gradient extracted via Palette from cover art; animates on track change; falls back to dark color

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 00:23:43 +02:00
HoneyandClaude Sonnet 4.6 820a75f40b feat: ABS full playback on Android — chapters, speed, sleep timer, progress sync
- AbsClient: add session API (startSession, closeSession, getProgress, saveProgress, getItemChapters) and data classes (AbsChapter, AbsAudioTrack, AbsPlaySession, AbsProgress)
- AbsPlayerController (new): standalone ExoPlayer controller for ABS audio; tracks global time across multi-track audiobooks, syncs progress to ABS every 30 s, chapter navigation, speed control, sleep timer
- AbsScreen: book detail screen with cover/meta/progress/chapter list/play button; AbsNowPlayingScreen overlay (progress bar, prev/+30s/play/-30s/next controls, speed picker, sleep timer, chapter list with auto-scroll)
- AppNavigation: create AbsPlayerController, wire up book detail and player overlay, hardware back button handling for all ABS sub-screens
- PrefsStore + SettingsViewModel + SettingsScreen: absChapterTrackMode toggle — progress bar shows current chapter length instead of full book
- README: update ABS section to cover both web and Android features

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 00:15:30 +02:00
HoneyandClaude Sonnet 4.6 3280ffee37 docs: trim README to core features only; fix: remove duplicate save button in Android settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:58:15 +02:00
HoneyandClaude Sonnet 4.6 ba16f88355 docs: update README — accurate feature list + Android native app description
Removed:
- "WebView wrapper" — Android is native Jetpack Compose, not a WebView
- "Artwork pre-warming / pre-buffer on cold start" — not implemented this way
- ABS chapters/speed/sleep/progress on Android — web only for now

Added:
- Lyrics section: lrclib.net, fallback chain, in-memory cache, synced scroll
- Android: last played song in MiniPlayer across restarts
- Android: ABS library browser tab (hidden until configured)
- Android: no login flash on startup
- Settings: connection status dots, save button top+bottom, matched web order
- Multi-artist support in NowPlaying
- Artist cards with images in search
- Genre browsing
- ABS web vs Android split into separate entries
- Accurate Android project structure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:56:02 +02:00
HoneyandClaude Sonnet 4.6 613d5156fa fix: settings order now matches web — Theme → Bibliothek → ABS → Discover APIs → Navidrome
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:54:11 +02:00
HoneyandClaude Sonnet 4.6 a0d3c7a592 feat: ABS integration, last played song, settings reorder + conn dots
Settings:
- Reordered to match web: Navidrome → Bibliothek → Theme → Audiobookshelf → Discover APIs
- Save button now appears at top AND bottom for easy access
- Audiobookshelf section: Server URL + API Key (masked) + green/red status dot with Test button
- NovaSonic Backend section: same green/red conn dot + Test button
- Connection status uses ConnStatus sealed class (Idle/Checking/Ok/Err)

Audiobookshelf:
- New AbsClient.kt: Ktor HTTP client, proxies through NovaSonic backend or direct with Bearer token
- New AbsScreen.kt: library list → book list with covers, author, duration
- Hörbücher tab appears in bottom nav only when ABS URL + API key are both set
- absUrl + absApiKey persisted in DataStore

Last played song:
- PlayerController saves song to DataStore on every media item transition
- MiniPlayer shows last song (dimmed, single play button) when no active queue
- Tapping play fetches the song from Navidrome and resumes
- LastPlayedSong stored in DataStore (id, title, artist, coverArt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:49:36 +02:00
HoneyandClaude Sonnet 4.6 f7012dab49 fix: login flash, album name casing, lrclib duration mismatch
- Android: show nothing while DataStore loads credentials (null initial
  value) to prevent login screen flash on startup when already logged in
- Web: dispAlbumName now only uses sortName as romaji for Japanese titles;
  English albums with lowercase ALBUMSORT tags now display correct casing
- lrclib: add no-duration fallback exact match to find songs where local
  file duration differs from lrclib entry (e.g. Sanctuary Opening 5:58 vs 4:24)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 22:50:11 +02:00
HoneyandClaude Sonnet 4.6 0272b9e99f chore: remove NetEase lyrics fallback from Android and web
NetEase returned wrong/unsynced results. lrclib with exact + fuzzy
search is sufficient; no third-party Chinese platform needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 21:48:46 +02:00
HoneyandClaude Sonnet 4.6 eb5b0ea360 fix: lrclib rate limit — exact first, then 2 parallel searches; check HTTP 200
3 simultaneous requests were hitting lrclib rate limiter.
Now: exact match alone → if no content, 2 parallel searches.
Also check response.status === 200 before parsing to avoid treating
404/429 error bodies as valid empty lyrics responses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 21:46:09 +02:00