Commit graph

671 commits

Author SHA1 Message Date
Broque Thomas
8ba1585646 Paginate wishlist modal to prevent browser crash on large wishlists (5K+)
Initial load fetches 200 tracks instead of all. "Load More" button at
bottom shows (200 of 5,000) and loads next batch on click. Backend now
returns total count alongside limited results for both album and singles
categories. Rendering logic unchanged — just operates on smaller sets.
2026-04-05 13:15:24 -07:00
Broque Thomas
9f7fe27e7c Add Playlist Pipeline automation + wishlist badges + Discogs enrichment modal + hub cleanup
Playlist Pipeline — single automation that runs the full playlist lifecycle:
refresh → discover → sync → download missing. Replaces 4-automation signal
chains. Phase-aware progress display (Phase 1/4, 2/4, etc.), guard function
prevents concurrent runs, fire-and-forget wishlist at end. Re-sync loop
catches newly downloaded tracks on next scheduled run.

- New action type 'playlist_pipeline' with handler, blocks endpoint config,
  builder UI (playlist select, process all, skip wishlist checkboxes),
  help modal, result display map, and Hub template
- Removed 3 redundant Hub templates (Release Radar, Discovery Weekly,
  Playlist Auto-Sync) — all replaced by the pipeline
- Fixed sync completion polling (status is 'finished' not 'complete')
- Fixed refresh handler progress hijack (null out _automation_id)
- Fixed matched_tracks field access from sync_states result

Also in this commit:
- Wishlist badges on enhanced search and global search tracks (amber)
- Discogs added to manual enrichment modal search + artist/album dropdowns
- Profile PIN forgot recovery on profile selection dialog
2026-04-05 11:34:50 -07:00
Broque Thomas
32cc7cbd5e Fix artist info modal failing on watchlist map nodes with NULL IDs
Watchlist map nodes used w.get('key', '') which returns None when the
DB column is NULL (key exists with None value). None serialized to JSON
null, which is falsy in JS, causing 'No source ID' throw for every
artist click.

- Changed to `w.get('key') or ''` for all ID fields (coerces None→'')
- Added discogs_id to watchlist nodes (was missing entirely)
- Removed hard throw when no source ID — falls back to name-based lookup
- Added console.error logging for future diagnosis
2026-04-04 23:59:02 -07:00
Broque Thomas
58ee8d8a8a Fix cover.jpg not using Cover Art Archive during wishlist processing
cover.jpg was always written from Spotify/iTunes URL (640x640) when the
first track in an album reached _download_cover_art before MusicBrainz
lookup completed. Later tracks with MBID skipped because file existed.

Fix: when cover.jpg exists but is small (<200KB) and we now have a CAA
MBID, attempt to upgrade it with the high-res CAA version. If CAA fetch
fails, keep existing cover — no pointless overwrites.

Also adds Forgot PIN recovery to the profile selection PIN dialog,
reusing the same credential verification flow as the launch lock screen.
Backend reset endpoint now accepts profile_id parameter.
2026-04-04 21:40:21 -07:00
Broque Thomas
48a9de8861 Artist Map: image proxy, caching, on-the-fly explorer, genre uncap, dated changelogs
- Image proxy endpoint (/api/image-proxy) for canvas CORS — allowlisted CDNs,
  browser-like UA for Deezer, 24h cache headers. Direct CORS first, proxy fallback.
- Server-side 5-min cache on all artist map endpoints with auto-invalidation
  on watchlist add/remove, scan complete, and new MusicMap discoveries.
- Explorer fetches similar artists from MusicMap on-the-fly when none stored,
  saves to DB for instant future visits. Validates artist names against
  Spotify/iTunes API before loading map — rejects gibberish with 404.
- Genre map per-genre cap removed (was 300 backend, 400 frontend).
- Center node in Explorer uses type 'center' not 'watchlist' — no longer
  misidentified as a watchlist artist.
- Error overlay auto-dismisses after 2.5s and returns to Discover page.
- Helper What's New restructured with dated sections (April 4/3/2/1, March),
  trimmed from ~80 to ~38 entries, date headers styled as purple dividers.
- Version modal updated with Artist Map section and recent fixes.
2026-04-04 21:18:08 -07:00
Broque Thomas
f348b6a7cb Add Artist Map Hub section below hero slider
Full-width section with three mode cards: Watchlist (existing), Genres
(placeholder), and Explorer (placeholder). Dark gradient background
with purple/blue ambient glow, animated dot grid overlay, and polished
card hover effects. Replaces the old small hero button.

Responsive: 3-column desktop, 1-column mobile.
2026-04-04 12:25:19 -07:00
Broque Thomas
36e83e64ee Redesign Artist Map toolbar + shortcuts key + fixes
Toolbar redesigned: three-section layout (back+brand / centered search
with icon / tool buttons). Brand icon in accent purple. Zoom buttons in
compact pill group. Tool labels visible on desktop, icon-only on mobile.

Added keyboard shortcuts modal (keyboard icon button): lists all 10
shortcuts with styled kbd elements.

Fixes:
- Mouse wheel zoom min matched to button zoom min (0.02)
- Right-click no longer triggers left-click info modal (button filter)
- Removed external "Open on Spotify" link from context menu
- Search results dropdown centered under search bar
2026-04-04 11:56:37 -07:00
Broque Thomas
52894d3c65 Artist Map polish: keyboard shortcuts, context menu, filter toggle
- Keyboard: Escape close, +/- zoom, F fit, S search, H toggle similar
- Right-click context menu: Artist Info, View Discography, Watchlist,
  Open on Spotify — glass-style with auto-close
- Filter toggle button + H key: hide/show similar artists for clean
  watchlist-only overview
- Removed node dragging (caused visual desync with offscreen buffer)
- Better loading progress text
- Cleanup: keyboard handler removed on close, context menu hidden
2026-04-04 11:41:23 -07:00
Broque Thomas
d349754a93 Artist Map: cache backfill, constellation effects, related artists, polish
- Metadata cache backfill: batch-lookup all node names across all sources
  to fill missing IDs, images, and genres
- Source-aware navigation: View Discography passes correct source to
  artist page so non-active-source artists load correctly
- Constellation hover effect: 800ms delay, fade in/out animation, dim
  overlay with glowing connection lines to related artists
- Click ripple animation on node selection
- Related artists list in info modal with clickable navigation
- Rich tooltip with artist photo, name, genres on hover
- Removed node dragging (caused visual desync with offscreen buffer)
- Performance: cached constellation lookups, lighter cache query
  (no raw_json), canvas.width for proper DPR overlay coverage
2026-04-04 11:31:25 -07:00
Broque Thomas
cfac226eed Add Artist Map — force-directed constellation of watchlist + similar artists
Visual canvas map on Discover page showing watchlist artists as large
anchor bubbles surrounded by their similar artists, sized by relevance.

Layout: golden angle spiral for watchlist nodes with push-apart guarantee,
spiral packing with spatial grid collision detection for similar artists.
Offscreen buffer rendering for smooth pan/zoom (single drawImage blit).

Features:
- 320px watchlist bubbles, similar sized 25-55% by rank/occurrence
- Search bar with instant filter + animated zoom-to-node
- Tooltip with artist photo, name, genre tags on hover
- Touch support: single finger pan, pinch zoom, tap to click
- Zoom +/- buttons and fit-to-screen with smooth 250ms animation
- Click opens artist info modal (same as Your Artists)
- Loading overlay with image count progress
- Async image loading via createImageBitmap (non-blocking)
2026-04-04 10:22:15 -07:00
Broque Thomas
58d8e830c6 Your Artists on Discover + Deezer OAuth + MB Lookups Manager + Explorer improvements + bug fixes
YOUR ARTISTS (major feature):
- Aggregates liked/followed artists from Spotify, Tidal, Last.fm, Deezer
- Matches to ALL metadata sources (Spotify, iTunes, Deezer, Discogs)
- DB-first matching: library → watchlist → cache → API search (capped)
- Image backfill from Spotify API for artists missing artwork
- Carousel on Discover page with 20 random matched artists
- View All modal with search, source filters, sort, pagination
- Artist info modal: hero image, matched source badges, genres, bio,
  listeners/plays from Last.fm, watchlist toggle, view discography
- Auto-refresh with loading state on first load, polls until ready
- Deduplication by normalized name across all services

DEEZER OAUTH:
- Full OAuth flow: /auth/deezer + /deezer/callback
- Settings UI on Connections tab (App ID, Secret, Redirect URI)
- Token stored encrypted, auto-included in API calls
- get_user_favorite_artists() for liked artists pool

SERVICE CLIENTS:
- Spotify: added user-follow-read scope + get_followed_artists()
- Tidal: get_favorite_artists() with V2/V1 fallback
- Last.fm: get_authenticated_username() + get_user_top_artists()

FAILED MB LOOKUPS MANAGER:
- Manage button on Cache Health modal
- Browse/filter/search all failed MusicBrainz lookups
- Search MusicBrainz directly and manually match entries
- Optimized cache health queries (11 → 4 consolidated)
- Dashboard cache stats now poll every 15s

EXPLORER IMPROVEMENTS:
- Discover button on undiscovered playlist cards
- Status badges: explored/wishlisted/downloaded/ready
- Auto-refresh during discovery via polling
- Redesigned controls: prominent Explore button, icons

BUG FIXES:
- Fix album artist splitting on collab albums (collab mode fed
  album-level artists instead of per-track)
- Fix cover.jpg not moving during library reorganize (post-pass sweep)
- Fix cover.jpg missing when album detection takes fallback path
- Fix wishlist auto-processing toast spam (was firing every 2s)
- Fix media player collapsing on short viewports
- Fix watchlist rate limiting (~90% fewer API calls)
- Configurable spotify.min_api_interval setting
- Better Retry-After header extraction
- Encrypt Last.fm and Discogs credentials at rest
- Add $discnum template variable (unpadded disc number)
2026-04-03 22:39:05 -07:00
Broque Thomas
d61a779b0e Add $discnum to template variable list, validation, and docs
$discnum was functional but missing from the settings hint text,
template validation whitelist, and docs page. Users would see an
"invalid variable" error when trying to use it.

Also fixes wishlist auto-processing toast spam — was firing every 2s
instead of once per auto-processing run.
2026-04-03 15:59:56 -07:00
Broque Thomas
95b1665e19 Redesign Explorer controls — prominent Explore button, icons, polish
Explore button moved to its own row below playlist cards with gradient,
search icon, and "Explore Selected Playlist" label. Impossible to miss.

Mode toggle redesigned as pill segmented control with grid/list icons.
Action bar buttons get inline SVG icons (checkmark, square, heart).
Primary buttons use gradient + glow shadow treatment.

Build hint shows "Select a playlist above, then explore" → updates to
"Ready: [name]" when playlist selected. Discovery poller refreshes
cards every 5s while active. Button re-enables as "Open" after modal
launch so user can reopen closed discovery modal.
2026-04-03 13:11:23 -07:00
Broque Thomas
8b58434c17 Explorer improvements: discover from Explorer, status badges, auto-refresh
1. Discover button on undiscovered playlist cards — triggers discovery
   directly from Explorer instead of redirecting to Sync page. Button
   changes to "Open" to reopen modal after closing.

2. Status badges on playlist cards: checkmark (in library), heart
   (wishlisted), star (fully discovered), percentage (needs discovery).
   Meta line shows "N in library · M wishlisted" counts.

3. Auto-refresh: polls every 5s during active discovery to update cards.
   WebSocket listener for discovery:progress events. Cards refresh when
   discovery completes.

4. Explored tracking: playlists get green checkmark badge after tree is
   built (session-only, resets on reload).

Backend: new get_mirrored_playlist_status_counts with fail-safe design —
core discovery counts use simple reliable queries, library/wishlist
counts are best-effort extras that won't break discovery detection.

Card layout redesigned: badges inline with playlist name, discover
button below meta text, no more absolute positioning overlaps.
2026-04-03 13:00:04 -07:00
Broque Thomas
93fb082172 Add Failed MB Lookups manager + optimize cache performance
New feature: Failed MusicBrainz Lookups management modal accessible
from Cache Health. Browse all failed lookups with type filter tabs,
search bar, pagination. Click any entry to search MusicBrainz and
manually match — saves MBID at 100% confidence. Clear individual
entries or bulk clear all.

Backend: 4 new endpoints — failed-mb-lookups list, mb-entry delete,
musicbrainz/search (artist/release/recording), mb-match save.

Performance: Cache health stats consolidated from 11 queries to 4
using CASE expressions. Added partial index on musicbrainz_cache for
failed lookups. Dashboard cache stats now poll every 15s instead of
single fire-and-forget fetch. Failed MB type counts cached on frontend,
only re-fetched after mutations.

Also includes: library reorganize now moves cover.jpg via post-pass
sidecar sweep, and changelog updates.
2026-04-03 11:38:50 -07:00
Broque Thomas
55f7e174d8 Fix all watchlist Discogs gaps
- Add discogs_artist_id to ALL watchlist WHERE clauses + bind params
- Fix artistPrimaryId to include discogs_artist_id
- Fix View Discography — add Discogs source branch
- Fix openWatchlistArtistDetailView — destructure discogs_artist_id
  from response (was causing ReferenceError)
- Discogs source/provider badges + CSS + image fetch + ID resolution
2026-04-02 21:33:41 -07:00
Broque Thomas
f51a8a9ee9 Add Discogs to watchlist UI — badges, provider section, config endpoint
- Watchlist artist list: discogs_artist_id in API response
- Watchlist source badges: Discogs badge on artist cards
- Watchlist config modal: discogs_artist_id in SQL query, WHERE clause,
  response, and linked provider section with badge
- CSS for watchlist-source-discogs and watchlist-provider-badge.discogs
2026-04-02 21:09:11 -07:00
Broque Thomas
cd0e8cf342 Fix Discogs badge gaps — artist_data dict, enrichment coverage, all views
- Add discogs_id to manually constructed artist_data dict in
  get_library_artists (was in SQL but not in response dict)
- Add Discogs to enrichment coverage circles on artist detail page
- Add Discogs to enhanced artist/album ID badges and match status chips
- All badge locations verified: library cards, artist hero, enhanced view
2026-04-02 20:42:09 -07:00
Broque Thomas
930ffbd085 Add Discogs badge to all artist views — fix missing data in queries
- Add discogs_id to library artists SQL SELECT (was missing)
- Add discogs_id to artist detail discography SQL SELECT and service
  IDs loop — fixes hero badges not showing Discogs
- DISCOGS_LOGO_URL constant, badge in library cards, hero, enhanced view
- Match status chip and manual match support for Discogs
2026-04-02 20:03:01 -07:00
Broque Thomas
240dd87727 Fix Discogs cache — add field extractor, wire worker caching, browser UI
- Add _extract_discogs_fields to metadata cache — handles Discogs field
  names (title vs name, images array, Artist - Title format)
- Worker uses _fetch_and_cache_artist/_fetch_and_cache_album helpers
  that cache raw data while returning it for enrichment
- All search/lookup methods cache results for repeat queries
- Cache browser: Discogs stat pill, source filter, clear button, badge
- Fixes albums showing as 'Unknown' and artists missing images in cache
2026-04-02 19:47:36 -07:00
Broque Thomas
1455112d40 Move reclassified singles to singles grid, fix collectors edition matching
- Cards reclassified from album to single/EP (via lazy track count)
  now physically move from albums-grid to singles-grid
- Singles section auto-shows when cards move into it
- Add collectors edition to album title variation patterns — fixes
  "Damn" not matching "DAMN. COLLECTORS EDITION." in library
- Both base-title-to-edition and edition-to-base variations now include
  collectors edition alongside deluxe/platinum/special
2026-04-02 19:22:23 -07:00
Broque Thomas
7ea5eb2c06 Fix Discogs completion: lazy track counts, edition matching, type reclassify
- Fetch real track count from source during completion check when
  total_tracks is 0 (Discogs masters) — one API call per album, runs
  during existing per-album ownership check phase
- Reclassify album cards to single/EP when track count reveals 1-3/4-6
  tracks — updates type label and data attribute in place
- Add collectors edition to album title variation patterns for matching
  "Damn" against "DAMN. COLLECTORS EDITION." in library
- Hide 0/0 fraction when expected_tracks is 0, show proper count when
  fetched
2026-04-02 19:02:23 -07:00
Broque Thomas
a9eccfe1c5 Fix Discogs source display, album types, and track search
- Fix source name mapping so sidebar/dashboard shows 'Discogs' instead
  of falling through to 'iTunes'
- Fix album type detection: parse format string from artist releases
  endpoint (e.g. "File, FLAC, Single, 320") to correctly identify
  singles, EPs, albums, compilations — was defaulting everything to
  'single' because track count was 0
- Remove fake track search that returned albums as tracks — Discogs
  has no track-level search API, so tracks section is empty (honest)
- Track data available via album tracklists instead
2026-04-02 17:48:57 -07:00
Broque Thomas
cc95cfcdf2 Wire Discogs as fully featured fallback metadata source
- SpotifyClient: add _discogs lazy-load property, route _fallback to
  DiscogsClient when configured (requires token, falls back to iTunes)
- web_server: _get_metadata_fallback_client returns DiscogsClient when
  selected and token present
- Enhanced search: Discogs added as source tab with NDJSON streaming,
  only available when token configured
- Alternate sources list includes Discogs when token is set
- Frontend: source labels, tab styling, fetch list all include Discogs
- Consistent with iTunes/Deezer pattern — same interfaces, same routing
2026-04-02 17:32:28 -07:00
Broque Thomas
e35d84ba96 Change Discogs gauge color from gray to warm bronze for readability 2026-04-02 17:20:26 -07:00
Broque Thomas
72e720dd88 Add Discogs enrichment button to dashboard header
- Circular button with "dc" logo text, matching exact pattern of
  AudioDB/Deezer/Spotify/iTunes/Last.fm/Genius/Tidal/Qobuz buttons
- Spinner animation when active, dimmed when paused, green when complete
- Hover tooltip showing status, current item, and progress stats
- Click to toggle pause/resume with config persistence
- WebSocket status handler updates button state in real-time
2026-04-02 17:06:49 -07:00
Broque Thomas
b68aa09469 Add Discogs enrichment worker with full metadata extraction
- New core/discogs_worker.py — background worker enriching artists and
  albums with Discogs metadata following AudioDBWorker pattern exactly
- Artist enrichment: discogs_id, bio, members, URLs, image backfill,
  genre backfill, summary backfill from bio
- Album enrichment: discogs_id, genres, styles (400+ taxonomy), label,
  catalog number, country, community rating, image backfill
- DB migration: discogs columns on artists (id, match_status, bio,
  members, urls) and albums (id, match_status, genres, styles, label,
  catno, country, rating, rating_count)
- Worker initialization with pause/resume persistence
- Status/pause/resume API endpoints
- Integrated into enrichment status system, rate monitor, auto-pause
  during downloads/scans, WebSocket status emission
2026-04-02 16:57:18 -07:00
Broque Thomas
44a8be4469 Add Discogs to Settings connections and rate monitor
- New Discogs section on Settings → Connections with personal token input
- Discogs added as fallback metadata source option alongside iTunes/Deezer
- Token saved to discogs.token config key
- Discogs added to API rate monitor gauges (60/min with auth)
- Help text links to discogs.com/settings/developers for token generation
2026-04-02 16:25:23 -07:00
Broque Thomas
5d8f3bcaec Store original audio details in track provenance (#245)
- Add bit_depth, sample_rate, bitrate columns to track_downloads table
- Read audio info from file via Mutagen when recording provenance
- Source Info popover shows "Audio: 24-bit · 96.0kHz · 2304kbps"
- These values are captured from the original file before transcoding,
  so users can see the original specs even after Blasphemy Mode converts
  FLAC to lossy format
2026-04-02 15:16:49 -07:00
Broque Thomas
bec81cfd8d Add webhook POST then-action for automation engine
- New 'webhook' then-action: sends HTTP POST with JSON payload to any
  user-configured URL (Gotify, Home Assistant, Slack, n8n, etc.)
- Config: URL, optional custom headers (Key: Value per line with
  variable substitution), optional custom message
- Payload includes all event variables as JSON fields
- 15s timeout, errors on 400+ status codes
- Follows exact same pattern as Discord/Pushbullet/Telegram handlers
- Frontend: config fields, config reader, icon, help docs
- Updated changelogs with webhook, M3U fix, orchestrator hardening
2026-04-02 12:22:07 -07:00
Broque Thomas
7c85c31e8b Skip auto M3U export for album downloads — playlists only (#241)
- autoSavePlaylistM3U() returns early for album downloads (detected by
  playlistId prefix) — albums are already grouped by media servers,
  M3U just creates empty duplicate playlists (Navidrome auto-imports them)
- Fix broken isAlbum detection — data-context was always "playlist",
  now uses reliable playlistId prefix matching
- Update toggle label: "playlists and albums" → "playlists"
- Update hint text to explain albums are skipped and why
- Manual Export M3U button still works for both (explicit user action)
2026-04-02 12:05:53 -07:00
Broque Thomas
d5bb0dfaa1 Combine enrichment pills into unified rate monitor cards, debounce idle
- Merge enrichment worker status into rate monitor WebSocket payload
- Hide old enrichment pills — rate monitor cards now show: service name,
  worker status badge, arc gauge, calls/min, 1h/24h counts, budget bar
- Debounce idle detection with 5s grace period — prevents status
  flickering between Running and Idle on every worker cycle
- Responsive grid layout with richer card design
2026-04-02 11:17:04 -07:00
Broque Thomas
559b89353f Add API Rate Monitor dashboard with real-time speedometer gauges
- New core/api_call_tracker.py — centralized tracker with rolling 60s
  timestamps (speedometer) and 24h minute-bucketed history (charts)
- Instrument all 9 service client rate_limited decorators to record
  actual API calls with per-endpoint tracking for Spotify
- 1-second WebSocket push loop for real-time gauge updates
- Modern radial arc gauges with service brand colors, glowing active
  arc, endpoint dot, 0/max scale labels, smooth CSS transitions
- Click any gauge to open detail modal with 24h call history chart
  (Canvas 2D, HiDPI, gradient fill, grid lines, danger zone band)
- Spotify modal shows per-endpoint history lines with color legend
  and live per-endpoint breakdown bars
- Rate limited state indicator — blinking red badge with countdown
  timer appears on gauge card when Spotify ban is active
- REST endpoint GET /api/rate-monitor/history/<service> for chart data
- Responsive grid layout (5 cols desktop, 3 tablet, 2 phone)
2026-04-02 10:46:43 -07:00
Broque Thomas
9bcff9b43d Add configurable concurrent downloads setting
- New "Concurrent Downloads" dropdown on Settings page (1-10, default 3)
- Saved to download_source.max_concurrent config key
- All 6 batch creation sites use configured value instead of hardcoded 3
- Soulseek-only album downloads still use 1 worker (source reuse per user)
- Hybrid/YouTube/Tidal/Qobuz/Deezer albums use full configured concurrency
2026-04-02 09:26:03 -07:00
Broque Thomas
b8870e7310 Add loading indicators for streaming search and lazy-load artist images
- Per-section loading spinners (artists/albums/tracks) shown until each
  NDJSON chunk arrives, auto-replaced with real content on receipt
- Active tab content auto-re-renders as streaming data arrives for both
  enhanced search and global search
- Global search lazy-loads artist images for iTunes/Deezer via
  /api/artist/{id}/image fallback (album art), matching enhanced search
2026-04-02 09:06:06 -07:00
Broque Thomas
1bb59f3c24 Stream enhanced search source results as NDJSON for progressive rendering
- Source endpoint now streams artists/albums/tracks as separate NDJSON
  lines as each search type completes — iTunes users see artists in ~3s
  instead of waiting 9+ seconds for all 3 rate-limited calls to finish
- Enhanced search _fetchAlternateSource reads stream with ReadableStream
  reader, merges each chunk into source data, re-renders tabs immediately
- Global search uses same streaming pattern via _gsFetchSourceStream
- No data loss: streamed data merges incrementally, primary response
  preserves already-received alternate source data
2026-04-02 08:48:19 -07:00
Broque Thomas
4786dc21ec Route import through automation engine and show per-track progress
- Remove direct request_scan() calls from album and singles import —
  emit batch_complete through automation engine instead, matching the
  same chain as download batches (scan → DB update)
- Show current track name in import queue status display instead of
  just processed/total count
2026-04-02 08:34:01 -07:00
Broque Thomas
68b4aace68 Fix track source-info/redownload 404 and add clear-match (#237, #236)
- Change <int:track_id> to <track_id> on 5 library track endpoints —
  Jellyfin uses GUID strings, int converter rejected them with 404 (#237)
- Add PUT /api/library/clear-match endpoint — sets service ID to NULL
  and match status to not_found, allowing users to undo wrong matches (#236)
- Add "Clear Match" button in the manual match modal for all services
- Add bottom padding to .page to prevent floating buttons (bell, help)
  from overlapping track action buttons at page bottom (#237)
2026-04-02 07:56:42 -07:00
Broque Thomas
b194e1e15b Add discovery artist blacklist — block artists from all discovery playlists
- New discovery_artist_blacklist table with NOCASE name matching
- Filter blacklisted artists from all 6 discovery pool queries, hero
  endpoint, and recent releases via SQL subquery and Python set check
- Name-based filtering means one block covers all sources (Spotify/iTunes/Deezer)
- Hover any discovery track row → ✕ button to quick-block that artist
- 🚫 button on Discover hero opens management modal with search-to-add
  (powered by enhanced search) and list of blocked artists with unblock
- CRUD API: GET/POST/DELETE /api/discover/artist-blacklist
- Updated changelogs
2026-04-01 23:35:56 -07:00
Broque Thomas
7acf7a7d80 Expose MusicBrainz cache in UI — browse, clear, and unified health display
- Add MusicBrainz to Cache Browser: stats pill, source filter, dedicated
  browse endpoint, cards with matched/failed status indicators
- Add Clear MusicBrainz and Clear Failed MB Only to cache clear dropdown
- Move MusicBrainz into Cache Health "By Source" bar chart alongside
  Spotify/iTunes/Deezer instead of isolated metric row
- Rename ambiguous "Failed Lookups" to "Failed MB Lookups" in summary cards
- Add browse-musicbrainz and clear-musicbrainz API endpoints
- Add musicbrainz_total/musicbrainz_failed to cache stats response
- Add Global Search Bar and MusicBrainz cache to changelogs
2026-04-01 23:12:16 -07:00
Broque Thomas
800727299f Fix library playback from search — album art, double-fire, path resolution
- Strip cloned inline onclick on global search play button swap to prevent
  simultaneous stream search + library play
- Include album thumb_url in library-check response and resolve relative
  Plex paths to full URLs with base URL + token
- Pass album art through to playLibraryTrack from both global and enhanced
  search library check handlers
- Add Plex music library locations as candidate dirs in _resolve_library_file_path
- Remove debug console.log from _gsDeactivate
2026-04-01 22:56:06 -07:00
Broque Thomas
13629720e8 Add global search bar with full enhanced search parity
Persistent Spotlight-style search bar at bottom-center, accessible
from any page via click, /, or Ctrl+K. Hidden on Downloads page
where enhanced search already exists.

Features matching enhanced search:
- Clear button when input has text
- Source tabs with live switching
- Source badges, library check, play buttons
- Album click opens download modal directly
- Artist click navigates to detail page
- Tab switching stays open (timestamp guard)
- Mobile responsive
2026-04-01 22:31:12 -07:00
Broque Thomas
0e3a217591 Polish Wing It UX — dropdown on LB cards, position detection, better toasts
1. LB Discover page cards now use the dropdown (Download/Sync) instead
   of the old single button with full choice dialog
2. Dropdown position auto-flips downward when button is near viewport top
3. Dropdown centered on button instead of right-aligned
4. Sync completion toast now includes playlist name and  indicator
5. Download modal already shows  prefix on playlist name as indicator

All changes purely additive — existing flows unaffected.
2026-04-01 20:30:34 -07:00
Broque Thomas
a9dd93b176 Fix Wing It sync live status and button persistence across all modals
Live status: updateYouTubeModalSyncProgress was hardcoded to youtube-*
element IDs but each source uses its own prefix (listenbrainz-*, tidal-*,
deezer-*, etc). Now tries all prefixes to find the correct elements.
Fixes Wing It sync progress AND a pre-existing bug where normal LB/Tidal
sync from the modal wouldn't show live progress.

Wing It button added to sync_complete phase so it persists after sync.
Fixed tracks lookup with state.playlist?.tracks fallback. Increased
button size in modal to match other action buttons.
2026-04-01 20:10:30 -07:00
Broque Thomas
3c19cc085b Fix Wing It downloads not creating download bubble
The discoverMetadata (needed for bubble creation) was only set for
playlist IDs starting with discover_lb_, listenbrainz_, or source
SoulSync. Wing It uses wing_it_ prefix which wasn't matched.
Added wing_it_ to the condition so bubbles appear on dashboard
and sidebar during Wing It downloads.
2026-04-01 19:31:05 -07:00
Broque Thomas
9556fc9b5c Add Wing It mode — download or sync without metadata discovery
Wing It bypasses Spotify/iTunes/Deezer matching and uses raw track
names directly. User chooses Download or Sync from a choice dialog.

Download: opens Download Missing modal with force-download-all
pre-checked. wing_it flag skips wishlist for failed tracks.

Sync: new POST /api/wing-it/sync endpoint runs _run_sync_task with
raw track dicts. Live inline sync status display on the LB card
using the same progress elements as normal sync. Unmatched tracks
skip wishlist via _skip_wishlist flag on sync_service.

Button in three places:
- Next to "Start Discovery" in all discovery modals (fresh phase)
- Next to "Download Missing"/"Sync" after discovery (discovered phase)
- Next to "Download" on ListenBrainz cards (Discover page)

Fixed force-download toggle ID, sync progress field names
(total_tracks/matched_tracks not total/matched). All changes
purely additive — normal flows unaffected.
2026-04-01 19:16:51 -07:00
Broque Thomas
f58be8f05c Fix discovery progress not updating in modal (ListenBrainz, YouTube, etc.)
Discovery status polling was skipped when WebSocket was connected
(8 places), assuming socket events would push updates. But no
WebSocket events exist for discovery progress — the table stayed
on "Pending..." forever. Now always polls the status endpoint.
Pre-existing bug, not caused by recent changes.
2026-04-01 16:45:51 -07:00
Broque Thomas
119840fa5d Redesign toast notification system with bell button and history panel
Complete replacement of the old bottom-center stacking toast system:

Compact Toasts: Single toast at a time, bottom-right above buttons.
Pill shape with type-colored left border stripe, icon, message, and
optional "Learn more" link. Slides in, fades out after 3.5s. Click
to dismiss. New toasts replace the current one smoothly.

Notification Bell: 44px circle button next to the helper (?), with
red badge counter for unread notifications. Click opens panel.

Notification Panel: Glass popover above bell button showing history
of last 50 notifications. Each entry has type icon, message, relative
timestamp, and optional help link. Unread dot indicator. Clear All
button. Marks all as read when panel opens.

Same showToast(message, type, helpSection) signature — all 842
callers unchanged. Deduplication preserved. Updated version modal
and helper What's New.
2026-04-01 16:41:41 -07:00
Broque Thomas
89b5178838 Fix Download Discography on library page
Three issues fixed:
1. Stale discography data from a previous Artists page search was used
   instead of fetching for the current library artist. Now detects
   library page context and forces fresh fetch when names differ.
2. Enhanced view may not be loaded, so metadata IDs (spotify/itunes/
   deezer) are fetched from the enhanced endpoint on demand.
3. Fixed undefined spotifyId reference — now uses properly scoped
   metadataArtistId variable.

Falls back to name-based search when no metadata IDs are available.
Better error message directs users to Artists page as alternative.
2026-04-01 16:26:25 -07:00
Broque Thomas
77e4671236 Fix Download Discography button on library artist page
The button called openDiscographyModal() which expected discography
data in artistsPageState — but the library page never populated it.
Now fetches discography on-demand from /api/artist/<id>/discography
when called from the library page, using the artist's DB ID and name.
2026-04-01 15:57:26 -07:00