Commit Graph
383 Commits
Author SHA1 Message Date
HoneyandClaude Sonnet 4.6 9d8a5a22bb fix: button focus ring, playlist reorder mode, edit modal mobile, backdrop-filter
- 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>
2026-05-18 22:40:11 +02:00
HoneyandClaude Sonnet 4.6 68bec67880 fix: sync queue insert/append to native Android player
Both 'Als Nächstes einfügen' and 'Am Ende der Queue' updated S.queue in JS
but never called NSBridge, so the native ExoPlayer ignored the change and
kept playing the original next item.

- Export nativeInsertAt(song, position) from player.js
- Call it in showCtxMenu with the exact splice index for insert-next
  and -1 (append) for add-to-end

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:27:25 +02:00
HoneyandClaude Sonnet 4.6 e893b7cc12 fix: radio mode appends songs without restarting; favorites/song row mobile fixes
- 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>
2026-05-18 22:25:26 +02:00
HoneyandClaude Sonnet 4.6 1b7c05f66d feat: notification 'Öffnen' routes to album for track/album downloads
- Single track and album downloads now navigate to the album page
- Artist downloads continue to navigate to the artist page
- Button label changes to 'Zum Album →' or 'Zum Künstler →' accordingly
- album name stored in job metadata so it survives until poll completes
- Search uses exact album+artist match, falls back to album-name-only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:16:15 +02:00
HoneyandClaude Sonnet 4.6 ba382aa6d9 fix: cover art caching, skip desync, notification tap opens now playing
- api.js: cache coverUrl() per id+size so WebView HTTP cache can work;
  previously each call generated a new random salt/token → unique URL →
  no cache hits → images re-fetched on every render

- player.js: replace _nsBridge('next')/'prev' with explicit seekToIndex
  in nextSong/prevSong so native always jumps to the exact same index JS
  computed; 'next'/'prev' relied on native's internal pointer being in sync
  which caused 'UI shows new song but audio plays old one' when they drifted

- MusicService: set setSessionActivity PendingIntent on MediaSession so
  tapping the playback notification opens the app

- MainActivity: handle open_now_playing extra in onCreate (cold start)
  and onNewIntent (app already running) to show the Now Playing screen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:12:29 +02:00
HoneyandClaude Sonnet 4.6 66099c12f0 fix: add kuromoji to backend container npm install
The backend uses NODE_PATH=/deps/node_modules with its own npm install
step — not the root package.json. Added kuromoji there and reverted
the incorrect root package.json change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:48:13 +02:00
HoneyandClaude Sonnet 4.6 5bcb8b09a8 fix: romaji index routes moved after app init + kuromoji in root package.json
- app.get/delete for /api/romaji-index were placed before const app = express()
  causing ReferenceError on startup — moved into the ROUTES section
- kuromoji moved from backend/package.json to root package.json so Docker
  picks it up during the existing npm install step
- Fixed dicPath resolution to use path.dirname(require.resolve(...))

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:26:46 +02:00
HoneyandClaude Sonnet 4.6 aa239f8e6a feat: romaji index for Japanese kanji song search
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>
2026-05-17 21:15:07 +02:00
HoneyandClaude Sonnet 4.6 369a5e82b3 fix: remove duplicate search bar from PC sidebar
The magnifier icon in the top-right already opens search.
Having a second search box in the sidebar was redundant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:08:37 +02:00
HoneyandClaude Sonnet 4.6 bd8919fb39 fix: prevent accidental seek on tap in mobile progress bar
Touch scrubbing now requires ≥8px horizontal drag before committing.
A plain tap on the progress bar is ignored — no unintentional position jump.
Also removed redundant onclick="seekTo()" since _initBarScrub already
handles mouse seeking via mousedown/mousemove/mouseup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:05:55 +02:00
HoneyandClaude Sonnet 4.6 a47b2be177 feat: romaji search for Japanese songs via wanakana
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>
2026-05-17 21:03:42 +02:00
HoneyandClaude Sonnet 4.6 27eb209945 feat: three-dot context menu on song rows + search shows songs as rows
- 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>
2026-05-17 20:58:59 +02:00
HoneyandClaude Sonnet 4.6 7bec106350 fix: fetch YTM URLs once and reuse across cascade steps
Each tryYtmSearch() call made a fresh HTTP request, so YouTube could
return different result orderings between steps. The correct video
(right title, wrong channel name) appeared in step 1 but was gone by
step 3 — a worse video with matching keywords slipped through instead.

Now fetch 10 YTM URLs once upfront and filter the same list with
progressively relaxed rules. Also saves 2-4 extra YTM requests per
download.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:39:07 +02:00
HoneyandClaude Sonnet 4.6 c037ee623d fix: preload hi-res NP cover on track change so overlay opens instantly
_crossfadeNpBg only preloaded the 1024px cover when the NP overlay was
already open. Opening it fresh showed the old cover for ~0.5s while the
new image downloaded. Preloading in updatePlayerUI caches it immediately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:49:57 +02:00
HoneyandClaude Sonnet 4.6 30ef5300cf fix: sync native track index before opening Now Playing overlay
On Android, onTrackChanged fires asynchronously so opening the overlay
quickly after a track change could show the previous song. Query
NSBridge.getState() synchronously at open time to get the real index.
Same flash can also occur on web — openNowPlaying now always renders
from current S.qIdx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:49:05 +02:00
HoneyandClaude Sonnet 4.6 6d0d1b28dc fix: widen sleep picker to 150px so custom input gets proper space
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:37:20 +02:00
HoneyandClaude Sonnet 4.6 6fcf3179be fix: 2-line layout for sleep timer custom row (label above, input+OK below)
Picker is only ~90px wide; single-line label+input+OK was too cramped.
Now stacks vertically so input gets full row width minus the OK button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:30:45 +02:00
HoneyandClaude Sonnet 4.6 ee6df1e1fe fix: prevent music canplay callback from interrupting ABS playback start
When absPlayItem() paused S.audio, the stale canplay listener from the
last playCurrent() call still had a matching _playGen value and would
call S.audio.play() — triggering absInterrupt() which immediately paused
the ABS audio. Fix: export cancelMusicAutoplay() that bumps _playGen
before pausing, invalidating any pending canplay/guard callbacks.

Also shrink OK button and widen time input in sleep timer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:21:29 +02:00
HoneyandClaude Sonnet 4.6 f5343efd37 fix: add OK button back to audiobook sleep timer custom input
Mobile needs the button since there's no keyboard Enter key in all cases.
Button is compact and styled to match the accent color.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:13:31 +02:00
HoneyandClaude Sonnet 4.6 cf88d87791 fix: simplify audiobook sleep timer custom input; update README
- Remove OK button and expand-on-click from sleep timer custom row
- Always show label + plain number input (no spinners, Enter to confirm)
- Update README with all current features (Spotify importer, ABS, lyrics, filters, etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:04:12 +02:00
Honey 757c7045ba fix: normalize title comparison in iTunes lookup to handle Spotify dash-variants vs iTunes paren-variants 2026-05-14 18:53:06 +02:00
Honey 55370ad760 feat: show iTunes enrichment status indicator in Spotify importer 2026-05-14 18:01:34 +02:00
Honey 11dec04fea fix: improve Spotify importer metadata — copy iTunes durationMs, skip redundant backend lookup when frontend already enriched track 2026-05-14 17:58:36 +02:00
Honey e78df7bdb9 feat: add search filter bar to playlist detail and favorites views 2026-05-13 13:58:08 +02:00
Honey d5470a6440 fix: add Alben to mobile bottom nav; match sidebar order to mobile (Playlists before Favoriten) 2026-05-13 13:01:56 +02:00
Honey 47f1605965 feat: add sort filter chips to albums and favorites views 2026-05-13 12:53:34 +02:00
Honey 751cc05b89 fix: reset progress bar to 0:00 immediately on track change on web too 2026-05-13 12:46:58 +02:00
Honey 10e3e0eb1d fix: reset progress bar to 0:00 immediately on track skip/play in bridge mode 2026-05-13 12:45:55 +02:00
Honey a1a4416bb6 fix: restart skip grace period on isPlaying:true to suppress multi-fire buffering during seek 2026-05-13 12:44:27 +02:00
HoneyandClaude Sonnet 4.6 d87abd2e9e fix: suppress transient pause during seek in bridge mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:46:12 +02:00
HoneyandClaude Sonnet 4.6 74a452c037 fix: use playlist/album artworkUrl as fallback when track has no cover
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:41:01 +02:00
HoneyandClaude Sonnet 4.6 ee05998840 fix: suppress transient isPlaying:false during native skip transitions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:42:07 +02:00
HoneyandClaude Sonnet 4.6 1e61d35018 fix: use isPlaying() in syncNowPlaying to handle bridge/native mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 02:00:26 +02:00
HoneyandClaude Sonnet 4.6 0c5fe53f13 fix: inject iTunes previewUrl into Spotify importer track items
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:56:14 +02:00
HoneyandClaude Sonnet 4.6 c8f82ceba5 fix: scrape Spotify OG tags as fallback for single track resolution
song_info returns GenericError for unauthenticated track lookups.
Fall back to scraping og:title/og:description/og:image from the public
Spotify track page — description format is "Song · Artist · Album · Year".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:48:37 +02:00
HoneyandClaude Sonnet 4.6 4f52e7b883 fix: album_info yields pages (flatten), track items use flat format; track GenericError handling
- Album: flatten generator pages with list comprehension, parse flat track.{name,artists,...} directly
- Track: try both URI and URL, report GenericError clearly instead of crashing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:44:56 +02:00
HoneyandClaude Sonnet 4.6 b74d0da294 fix: song_info needs URI not URL; album_info returns generator of track items
- Track: pass spotify:track:ID URI instead of full https:// URL
- Album: Public().album_info() yields track items (generator); metadata from PublicAlbum

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:40:31 +02:00
HoneyandClaude Sonnet 4.6 a401c7a607 fix: use Public().song_info() for tracks, Public().album_info() for albums
Both are the correct unauthenticated API methods on the Public class.
Song class requires a logged-in session and has no public equivalent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:36:33 +02:00
HoneyandClaude Sonnet 4.6 76785a4eaa debug: wrap Song init in try-except, log Public methods before any Song attempts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:21:19 +02:00
HoneyandClaude Sonnet 4.6 5d0c5ec6eb fix: album track wrapping + try multiple Song() call patterns for tracks
- Album: wrap item.get('track') as {itemV2: ...} so _parse_track navigates trackUnion correctly
- Track: try get_song_info(url) then get_song_info(); log Song/Public methods on failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:15:43 +02:00
HoneyandClaude Sonnet 4.6 bbe4412f42 chore: remove unused showSetupError from ui.js (duplicate of _showSetupError in main.js)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:07:49 +02:00
HoneyandClaude Sonnet 4.6 67185b85e9 fix: track needs Public() client; fix album partner API structure parsing
- Song() requires a client instance as first arg; pass Public() for unauthenticated access
- Album response is data.albumUnion.{name,artists,coverArt,tracks.items} not flat keys
- Add per-field debug logging for track and album responses

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:01:35 +02:00
HoneyandClaude Sonnet 4.6 6bedd3ed28 fix: proxy iTunes API through backend to resolve CORS errors
Frontend was calling itunes.apple.com directly; browsers block it (no CORS headers).
Added /api/itunes/:endpoint proxy on the backend and updated all itunesSearch/Lookup
calls in api.js to route through it when backendUrl is configured.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:23:40 +02:00
HoneyandClaude Sonnet 4.6 7b5928f809 fix: parse spotapi playlist response structure (data.playlistV2.content.items)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:17:03 +02:00
HoneyandClaude Sonnet 4.6 575ca385fd debug: log spotapi playlist page structure to diagnose 0-track response
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:13:17 +02:00
HoneyandClaude Sonnet 4.6 ddc6cb8cb2 fix: detect spotapi exports at runtime, lazy-import per type
PublicSong doesn't exist in the installed spotapi version. Now discovers
available class names at startup and picks the right one dynamically.
Missing track/album class no longer breaks playlist resolution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:11:06 +02:00
HoneyandClaude Sonnet 4.6 4ec441af57 fix: switch to spotapi (api-partner.spotify.com) to avoid rate limits
api.spotify.com/v1/ aggressively rate-limits server IPs regardless of
TLS fingerprint or token. SpotipyFree was already hitting the correct
endpoint (api-partner.spotify.com) but crashed on KeyError: 'ownerV2'
due to its own outdated field parsing — not a network block.

spotapi uses the same internal GraphQL API (Spotify's web player API)
which is on a separate rate-limit bucket from the public REST endpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:02:28 +02:00
HoneyandClaude Sonnet 4.6 d4c41a9f92 fix: enforce Spotify rate limit cooldown server- and client-side
Server tracks _spRateLimitUntil and rejects retries instantly without
hitting Spotify, returning the remaining wait time. Retrying before the
window expired was resetting Spotify's timer (19s → 7s → 48s).

Frontend now shows a live countdown with a note not to retry early.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 20:22:10 +02:00
HoneyandClaude Sonnet 4.6 5265aa1f33 fix: replace SpotipyFree with curl_cffi, remove dead Node.js TOTP code
SpotipyFree crashes with KeyError: 'ownerV2' — Spotify changed their
internal API response format and the library is out of date.

curl_cffi presents a real Chrome TLS fingerprint so open.spotify.com/api/
server-time responds (plain Node.js TLS is filtered by Spotify's WAF).
Node.js TOTP fallback always failed on this for the same reason — removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 20:17:15 +02:00
HoneyandClaude Sonnet 4.6 750089fc8e revert: remove proxy support, restore SpotipyFree-only resolver
IP is not actually blocked (api.spotify.com returns 401, not 403/timeout).
Previous rate limits were temporary. Reverting to the clean state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 20:04:10 +02:00