Files
lidify/docs/implementation-summaries/spotify-ui-overhaul/MODIFIED_FILES.md
2025-12-25 18:58:06 -06:00

799 lines
46 KiB
Markdown

# Modified Files for Review
> **Last Updated:** December 16, 2025
> **Features:** Spotify Import + UI Overhaul (Activity Panel, Carousels, Notifications, Playlist/Mix/Discover Redesign, Settings Page Redesign)
## Overview
This document tracks all files created or modified as part of:
1. **Spotify Import Feature** - Import Spotify playlists, match tracks, download albums
2. **UI Overhaul** - Activity Panel, horizontal carousels, notification system
---
## Backend - New Files
| File | Purpose |
| --------------------------------------------- | --------------------------------------------------------------- |
| `backend/src/services/notificationService.ts` | Notification CRUD service with convenience methods |
| `backend/src/services/spotifyImport.ts` | Spotify playlist import logic, track matching, album resolution |
| `backend/src/services/spotify.ts` | Spotify API/scraping service (embed data extraction) |
| `backend/src/routes/notifications.ts` | Notification & download history API endpoints |
| `backend/src/routes/spotify.ts` | Spotify import API endpoints |
| `backend/src/utils/playlistLogger.ts` | Debug logger for Spotify import jobs |
## Backend - Modified Files
| File | Changes |
| ----------------------------------------------- | --------------------------------------------------------------------- |
| `backend/prisma/schema.prisma` | Added `Notification` model, `DownloadJob.cleared` field |
| `backend/src/services/simpleDownloadManager.ts` | Added notification integration, failure deduplication |
| `backend/src/services/lidarr.ts` | Smart `anyReleaseOk` fallback, MusicBrainz fallback for artist lookup |
| `backend/src/services/musicbrainz.ts` | Recording filtering, scoring system, title normalization |
| `backend/src/services/spotify.ts` | Embed scraping improvements, debug logging |
| `backend/src/index.ts` | Registered notification routes |
---
## Frontend - New Files
| File | Purpose |
| ----------------------------------------------------- | ----------------------------------------------------- |
| `frontend/components/layout/ActivityPanel.tsx` | Collapsible 3rd column with tabs, PWA install button |
| `frontend/components/activity/NotificationsTab.tsx` | System notifications list |
| `frontend/components/activity/ActiveDownloadsTab.tsx` | Currently downloading items |
| `frontend/components/activity/HistoryTab.tsx` | Completed/failed with retry |
| `frontend/components/ui/HorizontalCarousel.tsx` | Reusable carousel with arrows |
| `frontend/hooks/useActivityPanel.ts` | Panel state management |
| `frontend/app/import/spotify/page.tsx` | Spotify import UI page (preview, selection, progress) |
## Frontend - Modified Files
| File | Changes |
| ------------------------------------------------------------- | ------------------------------------------------------ |
| `frontend/components/layout/AuthenticatedLayout.tsx` | Added 3rd column, event listener for toggle |
| `frontend/components/layout/TopBar.tsx` | Added `ActivityPanelToggle` button |
| `frontend/components/MixCard.tsx` | Reduced padding/sizing (`p-4``p-2.5`) |
| `frontend/features/home/components/ArtistsGrid.tsx` | Uses `HorizontalCarousel` |
| `frontend/features/home/components/MixesGrid.tsx` | Uses `HorizontalCarousel` |
| `frontend/features/home/components/ContinueListening.tsx` | Uses `HorizontalCarousel` |
| `frontend/features/home/components/PodcastsGrid.tsx` | Uses `HorizontalCarousel` |
| `frontend/features/home/components/HomeHero.tsx` | Already optimized (compact greeting) |
| `frontend/lib/api.ts` | Added notification API methods, Spotify import methods |
| `frontend/app/playlists/page.tsx` | Added "Import from Spotify" button/link |
| `frontend/app/playlist/[id]/page.tsx` | Full Spotify-style redesign (see below) |
| `frontend/app/mix/[id]/page.tsx` | Full Spotify-style redesign (matches playlist page) |
| `frontend/app/discover/page.tsx` | Updated to use consistent container widths |
| `frontend/features/discover/components/DiscoverHero.tsx` | Redesigned to match playlist/mix hero style |
| `frontend/features/discover/components/DiscoverActionBar.tsx` | Redesigned with Lidify yellow play button |
| `frontend/features/discover/components/TrackList.tsx` | Redesigned to match playlist/mix track listing |
| `frontend/components/layout/Sidebar.tsx` | Removed unused icon imports |
---
## Database Changes
```prisma
// NEW MODEL
model Notification {
id String @id @default(cuid())
userId String
type String // system, download_complete, playlist_ready, error, import_complete
title String
message String?
metadata Json? // { playlistId, albumId, artistId, etc. }
read Boolean @default(false)
cleared Boolean @default(false)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, cleared])
@@index([userId, read])
@@index([createdAt])
}
// MODIFIED MODEL - DownloadJob
model DownloadJob {
// ... existing fields ...
cleared Boolean @default(false) // NEW: User dismissed from history
}
```
**Migration Applied:** `npx prisma db push`
---
## API Endpoints
### Notifications (`/api/notifications`)
| Method | Endpoint | Description |
| ------ | ------------------------------------ | ---------------------------- |
| GET | `/notifications` | List uncleared notifications |
| GET | `/notifications/unread-count` | Get unread count |
| POST | `/notifications/:id/read` | Mark as read |
| POST | `/notifications/read-all` | Mark all as read |
| POST | `/notifications/:id/clear` | Clear (dismiss) notification |
| POST | `/notifications/clear-all` | Clear all notifications |
| GET | `/notifications/downloads/active` | Active downloads |
| GET | `/notifications/downloads/history` | Completed/failed downloads |
| POST | `/notifications/downloads/:id/clear` | Clear from history |
| POST | `/notifications/downloads/clear-all` | Clear all history |
| POST | `/notifications/downloads/:id/retry` | Retry failed download |
### Spotify Import (`/api/spotify`)
| Method | Endpoint | Description |
| ------ | ---------------------------- | -------------------------------- |
| POST | `/spotify/import/preview` | Generate import preview from URL |
| POST | `/spotify/import/start` | Start import with selections |
| GET | `/spotify/import/:id/status` | Get import job status |
---
## Key Bug Fixes
### 1. Track Matching (Spotify Import)
- **File:** `backend/src/services/spotifyImport.ts`
- **Fix:** Added `stripTrackSuffix()` to remove "- 2011 Remaster" etc. while keeping punctuation
- **Fix:** Added Unicode normalization for artist names (Röyksopp → Royksopp)
- **Fix:** Multiple matching strategies (exact → stripped → fuzzy)
### 2. MusicBrainz Album Resolution
- **File:** `backend/src/services/musicbrainz.ts`
- **Fix:** Score threshold > 50 for studio albums
- **Fix:** Recording filtering (exclude live/demo/acoustic)
- **Fix:** Soundtrack penalty in scoring
### 3. Lidarr Album Addition
- **File:** `backend/src/services/lidarr.ts`
- **Fix:** Smart `anyReleaseOk` fallback (try strict first, then loosen)
- **Fix:** MusicBrainz fallback when Lidarr's metadata server fails
- **Fix:** Immediate error when no releases found
### 4. Multiple Failure Notifications
- **File:** `backend/src/services/simpleDownloadManager.ts`
- **Fix:** 30-second deduplication window for failure events
- **Fix:** Only notify on final exhaustion, not each retry
- **Fix:** Skip notifications for discovery/import batches
---
## Testing Checklist
### Activity Panel
- [ ] Panel opens/closes from TopBar button
- [ ] Panel state persists in localStorage
- [ ] Notifications tab shows system messages
- [ ] Active tab shows downloading items (refreshes every 5s)
- [ ] History tab shows completed/failed
- [ ] Retry button works for failed downloads
- [ ] Clear buttons work
### Home Page Carousels
- [ ] Horizontal scroll works
- [ ] Arrow buttons appear on hover (desktop)
- [ ] Snap behavior works
- [ ] Card sizing is compact
### Spotify Import
- [ ] Preview generation works
- [ ] Album selection works
- [ ] Downloads start correctly
- [ ] Track matching works after downloads
- [ ] Playlist is created with matched tracks
- [ ] Notification appears when complete
### Notifications
- [ ] Download complete creates notification
- [ ] Download failed creates notification (only on exhaustion)
- [ ] Spotify import complete creates notification
- [ ] Unread badge shows count
- [ ] Mark as read works
- [ ] Clear works
### Playlist Page
- [ ] Hero section is compact with bottom-aligned content
- [ ] Shuffle button randomizes and plays tracks
- [ ] Track listing spans full width (no container)
- [ ] Currently playing track is highlighted
- [ ] Track numbers become play icons on hover
- [ ] Album column hidden on mobile
### PWA Install
- [ ] "Install App" button appears in Activity Panel (when installable)
- [ ] Button triggers browser install prompt
- [ ] Button disappears after installation
---
## Rollback Instructions
If issues arise, revert these files:
```bash
# Core files to revert for UI changes
git checkout HEAD~1 -- frontend/components/layout/AuthenticatedLayout.tsx
git checkout HEAD~1 -- frontend/components/layout/TopBar.tsx
git checkout HEAD~1 -- frontend/components/layout/ActivityPanel.tsx
git checkout HEAD~1 -- frontend/components/activity/
# For Spotify import issues
git checkout HEAD~1 -- backend/src/services/spotifyImport.ts
git checkout HEAD~1 -- backend/src/services/musicbrainz.ts
git checkout HEAD~1 -- backend/src/services/lidarr.ts
# Database rollback (if needed)
# Remove Notification model and DownloadJob.cleared from schema
npx prisma db push
```
---
## Notes
- The old `DownloadNotifications.tsx` (floating modal) still exists but is no longer imported in the layout
- All grid components were already converted to carousels prior to this session
- The Spotify import flow uses `lidarrService.addAlbum()` directly instead of `simpleDownloadManager` to avoid same-artist fallback
## Playlist Page Redesign
**File:** `frontend/app/playlist/[id]/page.tsx`
### Changes Made
1. **Fixed React Hooks Error** - Moved `totalDuration` useMemo before early returns
2. **Full-Width Track Listing** - Removed container wrapper, tracks span full panel width like Spotify
3. **Compact Hero Section** - Smaller cover art (140px/192px), bottom-aligned content, reduced title size
4. **Added Shuffle Button** - Shuffles and plays all tracks in random order
5. **Grid-Based Track Layout** - Columns: #, Title/Artist, Album, Duration (responsive)
6. **Track Hover States** - Number becomes play icon on hover, row highlights
### PWA Install in Activity Panel
**File:** `frontend/components/layout/ActivityPanel.tsx`
- Added `beforeinstallprompt` event listener
- "Install App" button appears at bottom of panel when PWA can be installed
- Hides automatically when app is already installed or running in standalone mode
### Sidebar Cleanup
**File:** `frontend/components/layout/Sidebar.tsx`
- Removed unused icon imports (Home, Library, Sparkles, Book, Mic2)
- Navigation items use text-only (no icons) - matching minimalist design
### Playlists Page Redesign
**File:** `frontend/app/playlists/page.tsx`
**Before → After:**
| Element | Before | After |
| ---------------- | --------------------------------- | -------------------------------------- |
| Header title | `text-3xl md:text-4xl font-black` | `text-2xl font-bold` |
| Header padding | `px-6 md:px-8 py-6 md:py-8` | `px-6 pt-6 pb-4` |
| Gradient overlay | Yellow gradient at top | Removed |
| Import button | Green outline with icon | Solid green `bg-[#1DB954]`, no icon |
| Hidden toggle | Icon + text, bordered | Text only, minimal style |
| Card wrapper | `<Card>` component | Simple `<div>` with `hover:bg-white/5` |
| Card padding | `p-4` (via Card) | `p-3` |
| Play button | `w-12 h-12` | `w-10 h-10` |
| Empty state | `<EmptyState>` with icons | Simple centered div |
| Shared badge | Purple badge | Shown in subtitle instead |
| Track count | "tracks" | "songs" (matches Spotify) |
**Design Philosophy:**
- Remove decorative icons where text suffices
- Reduce spacing for tighter, professional feel
- Use native hover states instead of custom components
- Minimal color - let content speak
- Match Spotify's terminology
---
## Spotify-Style Design Patterns
> **Use these patterns consistently across all pages for a cohesive look.**
### 1. Hero Sections (Albums, Playlists, Artists)
```
- Compact height (max ~180px for cover on desktop)
- Content bottom-aligned to the cover art
- Title: text-2xl md:text-3xl font-bold (NOT text-4xl+)
- Subtitle info: text-sm text-gray-400
- Reduced vertical spacing (gap-2 to gap-4 max)
- No decorative gradients overlaying the hero
```
### 2. Track Listings
```
- Full-width, no container card wrapping
- Grid layout: [#] [Title/Artist] [Album] [Duration]
- Album column: hidden on mobile (md:grid-cols-[16px_1fr_1fr_60px])
- Hover: row bg-white/5, number → play icon
- Playing indicator: Lidify yellow (#ecb200) on track number
- Compact row height (~56px)
```
### 3. Page Headers
```
- Title: text-2xl font-bold (not text-3xl+)
- Subtitle: text-sm text-gray-400
- Actions: rounded-full buttons with minimal icons
- No excessive padding (px-6 py-4 is enough)
```
### 4. Cards (Albums, Artists, Playlists)
```
- Compact padding: p-2.5 (not p-4)
- Title: text-sm font-medium truncate
- Subtitle: text-xs text-gray-500
- Play button: bottom-right, shows on hover
```
### 5. Grids → Carousels
```
- Use HorizontalCarousel for content rows
- Single horizontal line, scroll/swipe
- Arrow buttons on hover (desktop)
- Snap behavior for smooth scrolling
```
### 6. General Typography
```
- Section headers: text-lg font-semibold (not text-xl)
- Greeting (home): text-2xl md:text-3xl font-bold tracking-tight
- No ALL CAPS unless absolutely necessary
- Muted subtitles: text-gray-400 or text-gray-500
```
### 7. Buttons & Actions
```
- Primary action: rounded-full, bg-[#ecb200] text-black
- Secondary: bg-white/10 hover:bg-white/20
- Icon-only buttons: rounded-full p-2
- Minimal icon usage - text labels preferred
```
### 8. Spacing Philosophy
```
- Tight but breathable
- Section gaps: gap-6 (not gap-8 or gap-10)
- Card grids: gap-4
- Hero to content: pt-6 (not pt-10)
```
---
## Post-Implementation Fixes
| Date | File | Issue | Fix |
| ---------- | --------------------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------- |
| 2025-12-15 | `backend/src/routes/notifications.ts` | Wrong import path `../db` | Changed to `../utils/db` |
| 2025-12-15 | `frontend/app/playlist/[id]/page.tsx` | React hooks order violation | Moved `useMemo` before early returns |
| 2025-12-15 | `frontend/app/playlist/[id]/page.tsx` | `useAuth` not defined | Removed unused `isAuthenticated` |
| 2025-12-15 | `frontend/components/layout/ActivityPanel.tsx` | Badge not clearing after clear all | Added `notifications-changed` event listener |
| 2025-12-15 | `frontend/components/activity/NotificationsTab.tsx` | Badge not updating | Dispatch `notifications-changed` event on mutations |
| 2025-12-15 | `backend/src/services/spotifyImport.ts` | Track matching failing (apostrophes, artist matching) | Added `normalizeApostrophes()`, changed artist match to use `contains` with first word |
| 2025-12-15 | `frontend/app/playlists/page.tsx` | Page design not matching Spotify style | Full redesign: compact header, cleaner cards, minimal icons, refined typography |
| 2025-12-15 | `frontend/app/import/spotify/page.tsx` | Using Music2 icon instead of Spotify logo | Uses SpotIcon.png, cleaner layout, matches style guide, removed heavy Card components |
| 2025-12-15 | `frontend/app/import/spotify/page.tsx` | Grey/transparent gradient not matching brand | Added yellow-to-purple gradient (same as home page) with quick fade ratio (35vh/25vh) |
| 2025-12-15 | `frontend/app/discover/page.tsx` | Container width inconsistent with hero | Added `max-w-7xl mx-auto` to track listing section |
| 2025-12-15 | `frontend/app/mix/[id]/page.tsx` | Container width inconsistent with hero | Added `max-w-7xl mx-auto` to track listing section |
| 2025-12-15 | `frontend/app/playlist/[id]/page.tsx` | Container width inconsistent with hero | Added `max-w-7xl mx-auto` to track listing section |
| 2025-12-15 | `frontend/features/discover/components/*` | Discover page not matching playlist/mix design | Redesigned DiscoverHero, DiscoverActionBar, TrackList to match Spotify style |
| 2025-12-15 | `frontend/app/library/page.tsx` | Container width not matching other pages | Removed `max-w-7xl mx-auto`, now full-width with `px-4 md:px-8` |
| 2025-12-15 | `frontend/features/library/components/LibraryHeader.tsx` | Container width not matching other pages | Removed `max-w-7xl mx-auto`, now full-width with `px-4 md:px-8` |
| 2025-12-15 | `frontend/app/podcasts/page.tsx` | Container width + card styling not matching | Removed `max-w-7xl mx-auto`, cleaner cards without borders/gradients |
| 2025-12-15 | `frontend/app/audiobooks/page.tsx` | Container width not matching other pages | Removed `max-w-7xl mx-auto`, smaller header text, consistent with Spotify style |
| 2025-12-15 | `frontend/app/artist/[id]/page.tsx` | Container width not matching other pages | Removed `max-w-7xl mx-auto`, now full-width with `px-4 md:px-8` |
| 2025-12-15 | `frontend/app/album/[id]/page.tsx` | Container width not matching other pages | Removed `max-w-7xl mx-auto`, now full-width with `px-4 md:px-8` |
| 2025-12-15 | `frontend/features/artist/components/ArtistHero.tsx` | Hero not matching Spotify style | Compact hero, full-width, bottom-aligned content, kept VibrantJS gradients |
| 2025-12-15 | `frontend/features/artist/components/ArtistActionBar.tsx` | Action bar too heavy | Simplified to play button + shuffle + download, matching playlist style |
| 2025-12-15 | `frontend/features/artist/components/PopularTracks.tsx` | Track list not matching new style | Removed Card wrapper, grid-based layout, cleaner typography |
| 2025-12-15 | `frontend/features/artist/components/Discography.tsx` | Section header too large | Changed header from `text-2xl md:text-3xl` to `text-xl` |
| 2025-12-15 | `frontend/features/artist/components/AvailableAlbums.tsx` | Section headers too large | Changed headers to `text-xl font-bold mb-4`, renamed sections |
| 2025-12-15 | `frontend/features/artist/components/SimilarArtists.tsx` | Cards not matching new style | Cleaner cards with transparent bg, smaller header |
| 2025-12-15 | `frontend/features/artist/components/ArtistBio.tsx` | Using Card component | Replaced Card with simple `bg-white/5` div |
| 2025-12-15 | `frontend/features/album/components/AlbumHero.tsx` | Hero not matching Spotify style | Compact hero, full-width, bottom-aligned content, kept VibrantJS gradients |
| 2025-12-15 | `frontend/features/album/components/AlbumActionBar.tsx` | Action bar too heavy | Simplified to play + shuffle + add to playlist, matching playlist style |
| 2025-12-15 | `frontend/features/album/components/SimilarAlbums.tsx` | Section header too large | Changed header to `text-xl font-bold mb-4` |
| 2025-12-15 | `frontend/app/artist/[id]/page.tsx` | Artist bio/about not showing | Now uses `artist.bio \|\| artist.summary` for library artists with `summary` field |
| 2025-12-15 | `frontend/features/artist/components/ArtistBio.tsx` | Read more link not brand color | Added `[&_a]:text-[#ecb200]` for Lidify yellow links |
| 2025-12-15 | `frontend/app/audiobooks/[id]/page.tsx` | Page design not matching Spotify style | Compact hero, yellow play button, integrated action bar, full-width layout |
| 2025-12-15 | `frontend/features/audiobook/components/AudiobookHero.tsx` | Hero too large and dated | Compact Spotify-style hero with bottom-aligned content, VibrantJS gradients preserved |
| 2025-12-15 | `frontend/features/audiobook/components/AudiobookActionBar.tsx` | Action bar not matching other pages | Yellow play button, inline progress, subtle action icons |
| 2025-12-15 | `frontend/app/podcasts/[id]/page.tsx` | Page design not matching Spotify style | Compact hero, fixed height gradient (25vh), full-width layout |
| 2025-12-15 | `frontend/features/podcast/components/PodcastHero.tsx` | Hero too large and dated | Compact Spotify-style hero with bottom-aligned content, VibrantJS gradients preserved |
| 2025-12-15 | `frontend/features/podcast/components/PodcastActionBar.tsx` | Action bar too heavy | Yellow subscribe button, subtle RSS link, cleaner remove confirmation |
| 2025-12-15 | `frontend/features/podcast/components/ContinueListening.tsx` | Cards not matching new style | Yellow play button, cleaner progress bar, simpler prev/next episodes |
| 2025-12-15 | `frontend/features/podcast/components/EpisodeList.tsx` | Episode list not matching new style | Removed Card wrapper, yellow highlights, cleaner typography |
| 2025-12-15 | `frontend/features/podcast/components/SimilarPodcasts.tsx` | Cards not matching new style | Transparent bg with hover, smaller header, cleaner layout |
| 2025-12-15 | `frontend/features/podcast/components/PreviewEpisodes.tsx` | Cards not matching new style | Removed Card wrappers, yellow subscribe button, cleaner About section |
---
## Settings Page Redesign (December 16, 2025)
### Overview
Complete redesign of the settings page to match Spotify's clean, minimal aesthetic with:
- **Sidebar navigation** - Fixed sidebar with section links, active state tracking via intersection observer
- **Single scrollable page** - All sections on one page instead of tabs
- **Unified Spotify section** - Combined OAuth user connection + Developer API credentials
- **Spotify-style design patterns** - Row-based layouts, clean toggles, minimal borders
### Database Changes
```prisma
model User {
// ... existing fields ...
// NEW: Spotify OAuth connection
spotifyAccessToken String? // Encrypted OAuth access token
spotifyRefreshToken String? // Encrypted OAuth refresh token
spotifyTokenExpiry DateTime? // When access token expires
spotifyUserId String? // Spotify user ID
spotifyDisplayName String? // Display name from Spotify
}
```
### New API Endpoints
| Method | Endpoint | Description |
| ------ | ------------------------------ | ----------------------------------- |
| GET | `/api/spotify/auth/url` | Generate OAuth authorization URL |
| GET | `/api/spotify/auth/callback` | Handle OAuth callback, store tokens |
| POST | `/api/spotify/auth/disconnect` | Remove user's Spotify connection |
| GET | `/api/spotify/auth/status` | Check if user is connected |
### New Frontend Files
| File | Purpose |
| ----------------------------------------------------------------------------- | --------------------------------------- |
| `frontend/features/settings/components/ui/SettingsLayout.tsx` | Sidebar + main content wrapper |
| `frontend/features/settings/components/ui/SettingsSidebar.tsx` | Navigation sidebar with section links |
| `frontend/features/settings/components/ui/SettingsSection.tsx` | Section header with separator |
| `frontend/features/settings/components/ui/SettingsRow.tsx` | Label + description left, control right |
| `frontend/features/settings/components/ui/SettingsToggle.tsx` | Spotify-style toggle switch |
| `frontend/features/settings/components/ui/SettingsSelect.tsx` | Dropdown select |
| `frontend/features/settings/components/ui/SettingsInput.tsx` | Text/password input with show/hide |
| `frontend/features/settings/components/ui/ConnectionCard.tsx` | OAuth connection card (Spotify) |
| `frontend/features/settings/components/ui/index.ts` | Barrel export |
| `frontend/features/settings/components/sections/AccountSection.tsx` | Password change + 2FA |
| `frontend/features/settings/components/sections/PlaybackSection.tsx` | Streaming quality dropdown |
| `frontend/features/settings/components/sections/SpotifyConnectionSection.tsx` | Spotify OAuth connection |
| `frontend/features/settings/components/sections/SpotifyAPISection.tsx` | Developer API credentials |
| `frontend/features/settings/components/sections/CacheSection.tsx` | Cache sizes + automation toggles |
| `frontend/features/settings/hooks/useSpotifyOAuth.ts` | OAuth state management |
### Modified Frontend Files
| File | Changes |
| -------------------------------------------------------------------------- | ------------------------------------- |
| `frontend/app/settings/page.tsx` | Complete redesign with sidebar layout |
| `frontend/features/settings/components/sections/LidarrSection.tsx` | Spotify-style row layout |
| `frontend/features/settings/components/sections/AudiobookshelfSection.tsx` | Spotify-style row layout |
| `frontend/features/settings/components/sections/SoulseekSection.tsx` | Spotify-style row layout |
| `frontend/features/settings/components/sections/AIServicesSection.tsx` | Spotify-style row layout |
| `frontend/features/settings/components/sections/StoragePathsSection.tsx` | Spotify-style row layout |
| `frontend/features/settings/components/sections/UserManagementSection.tsx` | Cleaner design, modal for delete |
### Modified Backend Files
| File | Changes |
| ------------------------------- | ---------------------------------------- |
| `backend/prisma/schema.prisma` | Added Spotify OAuth fields to User model |
| `backend/src/routes/spotify.ts` | Added OAuth routes |
### Deleted Files (Consolidated)
| File | Reason |
| ---------------------------------------------------------------------------- | --------------------------------- |
| `frontend/features/settings/components/UserSettingsTab.tsx` | Replaced by unified settings page |
| `frontend/features/settings/components/AccountTab.tsx` | Replaced by unified settings page |
| `frontend/features/settings/components/SystemSettingsTab.tsx` | Replaced by unified settings page |
| `frontend/features/settings/components/sections/ChangePasswordSection.tsx` | Merged into AccountSection |
| `frontend/features/settings/components/sections/TwoFactorAuthSection.tsx` | Merged into AccountSection |
| `frontend/features/settings/components/sections/PlaybackQualitySection.tsx` | Replaced by PlaybackSection |
| `frontend/features/settings/components/sections/AdvancedSettingsSection.tsx` | Replaced by CacheSection |
| `frontend/features/settings/components/sections/CacheSettingsSection.tsx` | Replaced by CacheSection |
| `frontend/features/settings/components/sections/SpotifySection.tsx` | Split into Connection + API |
### Settings Sections
**All Users:** Account, Playback, Connected Services (Spotify OAuth)
**Admin Only:** Download Services, Media Servers, P2P Networks, AI Services, Spotify API, Storage, Cache & Automation, User Management
---
## Home Page Enhancements (Dec 16, 2025)
### New Features
1. **Radio Stations Section** - Compact horizontal row at the top of the home page showing random Deezer radio stations
2. **Featured Playlists Section** - Grid showing 10 featured Deezer playlists after Popular Artists section
### New Files Created
| File | Purpose |
| ------------------------------------------------------ | ------------------------------------------- |
| `frontend/features/home/components/FeaturedPlaylistsGrid.tsx` | Grid component for featured playlists |
| `frontend/features/home/components/RadioStationsGrid.tsx` | Horizontal scroll component for radio stations |
### Modified Files
| File | Changes |
| ---------------------------------------------------- | ------------------------------------------------ |
| `frontend/app/page.tsx` | Added radio stations and featured playlists sections |
| `frontend/features/home/hooks/useHomeData.ts` | Added browse data fetching for playlists/radios |
| `frontend/hooks/useQueries.ts` | Added browse query keys and hooks |
| `backend/src/routes/browse.ts` | Increased featured playlists limit from 50 to 200 |
---
## Notification & Sync Button Improvements (Dec 16, 2025)
### Changes
1. **Sync Button** - No longer shows toast overlay, turns green with spinning animation while syncing
2. **Optimistic Notification Clearing** - Notifications are cleared from UI immediately before API call completes
3. **Duplicate Key Fix** - Added context parameter to renderCard in browse page to prevent duplicate key errors
### Modified Files
| File | Changes |
| -------------------------------------------------------- | ------------------------------------------------ |
| `frontend/components/layout/Sidebar.tsx` | Removed toast, added green color while syncing |
| `frontend/components/activity/NotificationsTab.tsx` | Implemented optimistic updates for all mutations |
| `frontend/app/browse/playlists/page.tsx` | Fixed duplicate key errors with unique keys |
---
## Essentia Audio Analysis Integration (Dec 16, 2025)
### Overview
Integrated Essentia audio analysis to extract BPM, key, mood, energy, and other audio features from tracks. This enables intelligent mood-based mixes and personalized playlists.
### Database Changes
Added to `Track` model in `backend/prisma/schema.prisma`:
| Field | Type | Description |
| ------------------ | ---------- | ------------------------------------- |
| `bpm` | Float? | Beats per minute |
| `beatsCount` | Int? | Total beats in track |
| `key` | String? | Musical key (C, F#, Bb, etc.) |
| `keyScale` | String? | "major" or "minor" |
| `keyStrength` | Float? | Key detection confidence (0-1) |
| `energy` | Float? | Overall energy (0-1) |
| `loudness` | Float? | Average loudness in dB |
| `dynamicRange` | Float? | Dynamic range in dB |
| `danceability` | Float? | Danceability score (0-1) |
| `valence` | Float? | Happy (1) to sad (0) |
| `arousal` | Float? | Energetic (1) to calm (0) |
| `instrumentalness` | Float? | Vocal presence (0-1, 1=instrumental) |
| `acousticness` | Float? | Acoustic vs electronic (0-1) |
| `speechiness` | Float? | Spoken word content (0-1) |
| `moodTags` | String[] | ML-classified mood tags |
| `essentiaGenres` | String[] | ML-classified genres |
| `lastfmTags` | String[] | User-generated mood tags from Last.fm |
| `analysisStatus` | String | pending/processing/completed/failed |
| `analysisVersion` | String? | Essentia version used |
| `analyzedAt` | DateTime? | When analysis was completed |
| `analysisError` | String? | Error message if failed |
### New Files
| File | Description |
| ------------------------------------------------- | -------------------------------------------------- |
| `services/audio-analyzer/Dockerfile` | Python 3.11 + Essentia container |
| `services/audio-analyzer/analyzer.py` | Main audio analysis service |
| `services/audio-analyzer/requirements.txt` | Python dependencies |
| `backend/src/workers/trackEnrichment.ts` | Last.fm tag enrichment worker |
| `backend/src/routes/analysis.ts` | API routes for analysis status & triggers |
### Modified Files
| File | Changes |
| -------------------------------------------------------------- | ----------------------------------------------- |
| `backend/prisma/schema.prisma` | Added audio analysis fields to Track model |
| `backend/src/workers/index.ts` | Added track enrichment worker startup/shutdown |
| `backend/src/workers/queues.ts` | Added `analysisQueue` for audio analysis jobs |
| `backend/src/index.ts` | Registered `/api/analysis` routes |
| `backend/src/services/programmaticPlaylists.ts` | Added mood-based mix generators |
| `backend/src/routes/library.ts` | Added mood-based radio station filtering |
| `frontend/features/home/components/LibraryRadioStations.tsx` | Added mood-based radio station buttons |
| `docker-compose.yml` | Added `audio-analyzer` service (optional) |
### New Mix Types (Audio Analysis-Based)
| Mix Type | Criteria |
| -------------- | --------------------------------------------- |
| High Energy | energy >= 0.7, BPM >= 120 |
| Late Night | energy <= 0.4, BPM <= 90, low arousal |
| Happy Vibes | valence >= 0.6, energy >= 0.5 |
| Melancholy | valence <= 0.4, minor key preferred |
| Dance Floor | danceability >= 0.7, BPM 110-140 |
| Acoustic | acousticness >= 0.6, energy 0.3-0.6 |
| Instrumental | instrumentalness >= 0.7, energy 0.3-0.6 |
| Road Trip | tags or energy 0.5-0.8, BPM 100-130 |
| Sunday Morning | low energy, high acousticness (day-specific) |
| Monday Motivation | high energy, high valence (day-specific) |
| Friday Night | high danceability, high energy (day-specific) |
### API Endpoints
| Method | Endpoint | Description |
| ------ | ----------------------------- | ---------------------------------------- |
| GET | `/api/analysis/status` | Get analysis progress statistics |
| POST | `/api/analysis/start` | Queue pending tracks for analysis |
| POST | `/api/analysis/retry-failed` | Reset failed tracks to pending |
| POST | `/api/analysis/analyze/:id` | Queue specific track for analysis |
| GET | `/api/analysis/track/:id` | Get analysis data for specific track |
| GET | `/api/analysis/features` | Get aggregated feature statistics |
### Starting the Audio Analyzer
The audio analyzer is disabled by default. To enable it:
```bash
docker-compose --profile audio-analysis up -d
```
Or just run it separately:
```bash
docker-compose up audio-analyzer -d
```
---
## Notification System Fixes (Dec 16, 2025)
### Issues Fixed
1. **Toast overlays for cache clearing and sync** - Removed toast.success overlays for "Caches cleared" and "Library scan started" since these should appear in the activity panel notification bar instead.
2. **Notification badge not clearing immediately** - The `useNotifications` hook wasn't responding to `notifications-changed` events. Fixed by adding an event listener that triggers a refetch.
3. **Settings page glitchy sidebar** - Replaced IntersectionObserver with scroll-based tracking for smoother sidebar highlighting.
### Modified Files
| File | Change |
|------|--------|
| `frontend/hooks/useNotifications.ts` | Added event listener for `notifications-changed` to trigger immediate refetch |
| `frontend/features/settings/components/sections/CacheSection.tsx` | Removed toast.success for cache clearing and sync, added local error state |
| `frontend/components/layout/TopBar.tsx` | Removed toast.success for library scan started |
| `frontend/components/layout/Sidebar.tsx` | Added `notifications-changed` event dispatch after sync |
| `frontend/features/settings/components/ui/SettingsLayout.tsx` | Replaced IntersectionObserver with throttled scroll listener for smoother sidebar tracking |
### Behavior Changes
- **Sync button**: No longer shows toast overlay - progress appears in activity panel
- **Clear caches button**: No longer shows toast overlay - implicit success (button returns to normal state)
- **Notification badge**: Now clears immediately via optimistic updates and event system
- **Settings sidebar**: Smoother scrolling behavior without jumpy highlights
---
## Session 8: Artist Radio Feature
### New Feature: Artist Radio with Hybrid Similarity Matching
| File | Change |
|------|--------|
| `backend/src/routes/library.ts` | Added `artist` case to `/library/radio` endpoint with hybrid matching |
| `backend/src/routes/library.ts` | Added artist name filtering to `/library/genres` endpoint |
| `frontend/features/artist/components/ArtistActionBar.tsx` | Added Radio icon button for library artists |
| `frontend/app/artist/[id]/page.tsx` | Added `handleStartRadio` function and passed to ArtistActionBar |
| `frontend/lib/api.ts` | Added `getRadioTracks()` method |
### Artist Radio Logic
The artist radio uses a **hybrid approach** with vibe boosting:
1. **Last.fm Similar Artists (filtered to library)**: Primary source, gets up to 15 similar artists that exist in user's library
2. **Genre Matching Fallback**: If < 5 similar artists, finds library artists with overlapping genres
3. **Vibe Boost via Audio Analysis**: Scores similar artists' tracks by BPM, energy, valence, and danceability similarity
4. **Track Mix**: ~40% from original artist, ~60% from vibe-matched similar artists
### Genre Filtering Fix
Artist names (like "Jamiroquai") were incorrectly showing as genres. Fixed by:
- Fetching all artist names at query time
- Filtering out any "genre" that matches an artist name (case-insensitive)
### Bug Fix: Artist Radio "Unknown Artist" / No Image
Fixed two issues with artist radio playback:
1. **Frontend**: Removed double-transformation of tracks - backend already returns properly formatted data
2. **Backend**: Fixed `coverArt` to use `track.album.coverUrl` directly instead of conditional `lidarrAlbumId` check
---
## Session 9: Vibe Match Feature
### New Feature: "Vibe Match" Button on Media Player
Allows users to instantly create a queue of tracks that sound like the currently playing track.
| File | Change |
|------|--------|
| `backend/src/routes/library.ts` | Added `vibe` case to `/library/radio` endpoint with audio feature matching |
| `frontend/components/player/MiniPlayer.tsx` | Added Vibe button (AudioWaveform icon) with loading state |
| `frontend/components/player/FullPlayer.tsx` | Added Vibe button (AudioWaveform icon) with loading state |
### How Vibe Match Works
1. **Takes current track's audio features** (BPM, energy, valence, danceability, key, mood tags)
2. **Searches entire library** for tracks with similar audio profiles
3. **Scores matches** using weighted algorithm:
- BPM (25%) - within ±15 BPM is ideal
- Energy (25%)
- Valence/mood (20%)
- Danceability (15%)
- Key compatibility (10%)
- Mood tag overlap (5%)
4. **Falls back gracefully** if not enough audio matches:
- Same artist's other tracks
- Last.fm similar artists' tracks
- Same genre tracks
- Random library tracks
### UI Location
The Vibe button (waveform icon) appears after the Repeat button in both:
- MiniPlayer (sidebar player)
- FullPlayer (bottom bar player)
Clicking it replaces the current queue with vibe-matched tracks and shows a toast notification.
---
## Session 9 (continued): Search Tracks Fix
### Bug Fix: Library Tracks Not Showing in Search
The backend was returning tracks in search results, but the frontend never displayed them.
| File | Change |
|------|--------|
| `frontend/app/search/page.tsx` | Added import for `LibraryTracksList` and section to display library tracks |
| `frontend/features/search/components/LibraryTracksList.tsx` | **New file** - Component to display library tracks in search results |
### Features of LibraryTracksList
- Shows up to 10 tracks matching the search query
- Displays cover art, title, artist, album, and duration
- Click to play (integrates with audio context)
- Currently playing track highlighted in yellow
- Artist and album names link to their respective pages