diff --git a/CHANGELOG.md b/CHANGELOG.md index 420fc98..7ad6adc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to Lidify will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.2] - 2025-01-07 + +### Fixed +- Mobile scrolling blocked by pull-to-refresh component +- Pull-to-refresh component temporarily disabled (will be properly fixed in v1.4) + +### Technical Details +- Root cause: CSS flex chain break (`h-full`) and touch event interference +- Implemented early return to bypass problematic wrapper while preserving child rendering +- TODO: Re-enable in v1.4 with proper CSS fix (`flex-1 flex flex-col min-h-0`) + +## [1.3.1] - 2025-01-07 + +### Fixed +- Production database schema mismatch causing SystemSettings endpoints to fail +- Added missing `downloadSource` and `primaryFailureFallback` columns to SystemSettings table + +### Database Migrations +- `20260107000000_add_download_source_columns` - Idempotent migration adds missing columns with defaults + +### Technical Details +- Root cause: Migration gap between squashed init migration and production database setup +- Uses PostgreSQL IF NOT EXISTS pattern for safe deployment across all environments +- Default values: `downloadSource='soulseek'`, `primaryFailureFallback='none'` + ## [1.3.0] - 2026-01-06 ### Added diff --git a/README.md b/README.md index 84d52cb..17af770 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Thanks for your patience while I work through this. - **Stream your library** - FLAC, MP3, AAC, OGG, and other common formats work out of the box - **Automatic cataloging** - Lidify scans your library and enriches it with metadata from MusicBrainz and Last.fm - **Audio transcoding** - Stream at original quality or transcode on-the-fly (320kbps, 192kbps, or 128kbps) +- **Ultra-wide support** - Library grid scales up to 8 columns on large displays

Library View @@ -66,6 +67,8 @@ Thanks for your patience while I work through this. - Dynamic genre and decade stations generated from your library - **Discover Weekly** - Weekly playlists of new music tailored to your listening habits (requires Lidarr) - **Artist recommendations** - Find similar artists based on what you already love +- **Artist name resolution** - Smart alias lookup via Last.fm (e.g., "of mice" → "Of Mice & Men") +- **Discography sorting** - Sort artist albums by year or date added - **Deezer previews** - Preview tracks you don't own before adding them to your library - **Vibe matching** - Find tracks that match your current mood (see [The Vibe System](#the-vibe-system)) @@ -74,6 +77,7 @@ Thanks for your patience while I work through this. - **Subscribe via RSS** - Search iTunes for podcasts and subscribe directly - **Track progress** - Pick up where you left off across devices - **Episode management** - Browse episodes, mark as played, and manage your subscriptions +- **Mobile skip buttons** - Jump ±30 seconds on mobile for easy navigation

Podcasts @@ -84,6 +88,7 @@ Thanks for your patience while I work through this. - **Audiobookshelf integration** - Connect your existing Audiobookshelf instance - **Unified experience** - Browse and listen to audiobooks alongside your music - **Progress sync** - Your listening position syncs with Audiobookshelf +- **Mobile skip buttons** - Jump ±30 seconds on mobile for easy chapter navigation

Audiobooks @@ -172,7 +177,7 @@ Lidify works as a PWA on mobile devices, giving you a native app-like experience - Full streaming functionality - Background audio playback -- Lock screen / notification media controls (via Media Session API) +- Lock screen and notification media controls (iOS Control Center and Android notifications) - Offline caching for faster loads - Installable icon on home screen @@ -310,12 +315,17 @@ docker pull chevron7locked/lidify:nightly The unified Lidify container handles most configuration automatically. Here are the available options: -| Variable | Default | Description | -| --------------------- | ---------------------------------- | --------------------------------------------------------------------------- | -| `SESSION_SECRET` | Auto-generated | Session encryption key (recommended to set for persistence across restarts) | -| `TZ` | `UTC` | Timezone for the container | -| `LIDIFY_CALLBACK_URL` | `http://host.docker.internal:3030` | URL for Lidarr webhook callbacks (see [Lidarr integration](#lidarr)) | -| `NUM_WORKERS` | `2` | Number of parallel workers for audio analysis | +| Variable | Default | Description | +| ----------------------------------- | ---------------------------------- | --------------------------------------------------------------------------- | +| `SESSION_SECRET` | Auto-generated | Session encryption key (recommended to set for persistence across restarts) | +| `SETTINGS_ENCRYPTION_KEY` | Required | Encryption key for stored credentials (generate with `openssl rand -base64 32`) | +| `TZ` | `UTC` | Timezone for the container | +| `PORT` | `3030` | Port to access Lidify | +| `LIDIFY_CALLBACK_URL` | `http://host.docker.internal:3030` | URL for Lidarr webhook callbacks (see [Lidarr integration](#lidarr)) | +| `AUDIO_ANALYSIS_WORKERS` | `2` | Number of parallel workers for audio analysis (1-8) | +| `AUDIO_ANALYSIS_THREADS_PER_WORKER` | `1` | Threads per worker for TensorFlow/FFT operations (1-4) | +| `LOG_LEVEL` | `warn` (prod) / `debug` (dev) | Logging verbosity: debug, info, warn, error, silent | +| `DOCS_PUBLIC` | `false` | Set to `true` to allow public access to API docs in production | The music library path is configured via Docker volume mount (`-v /path/to/music:/music`). @@ -344,7 +354,7 @@ Lidify uses several sensitive environment variables. Never commit your `.env` fi | Variable | Purpose | Required | | ------------------------- | ------------------------------ | ----------------- | | `SESSION_SECRET` | Session encryption (32+ chars) | Yes | -| `SETTINGS_ENCRYPTION_KEY` | Encrypts stored credentials | Recommended | +| `SETTINGS_ENCRYPTION_KEY` | Encrypts stored credentials | Yes | | `SOULSEEK_USERNAME` | Soulseek login | If using Soulseek | | `SOULSEEK_PASSWORD` | Soulseek password | If using Soulseek | | `LIDARR_API_KEY` | Lidarr integration | If using Lidarr | @@ -352,6 +362,24 @@ Lidify uses several sensitive environment variables. Never commit your `.env` fi | `LASTFM_API_KEY` | Artist recommendations | Optional | | `FANART_API_KEY` | Artist images | Optional | +### Authentication & Session Security + +- **JWT tokens** - Access tokens expire after 24 hours; refresh tokens after 30 days +- **Token refresh** - Automatic token refresh via `/api/auth/refresh` endpoint +- **Password changes** - Changing your password invalidates all existing sessions +- **Session cookies** - Secured with `httpOnly`, `sameSite=strict`, and `secure` (in production) +- **Encryption validation** - Encryption key is validated on startup to prevent insecure defaults + +### Webhook Security + +- **Lidarr webhooks** - Support signature verification with configurable secret +- Configure the webhook secret in Settings → Lidarr for additional security + +### Admin Dashboard Security + +- **Bull Board** - Job queue dashboard at `/admin/queues` requires authenticated admin user +- **API Documentation** - Swagger docs at `/api-docs` require authentication in production (unless `DOCS_PUBLIC=true`) + ### VPN Configuration (Optional) If using Mullvad VPN for Soulseek: @@ -367,7 +395,7 @@ If using Mullvad VPN for Soulseek: openssl rand -base64 32 # Generate encryption key -openssl rand -hex 32 +openssl rand -base64 32 ``` ### Network Security @@ -389,7 +417,7 @@ Connect Lidify to your Lidarr instance to request and download new music directl **What you get:** - Browse artists and albums you don't own -- Request downloads with a single click +- Request downloads with a single click - Discover Weekly playlists that automatically download new recommendations - Automatic library sync when Lidarr finishes importing @@ -601,6 +629,22 @@ Administrators have access to additional settings: - **Cache Management** - Clear caches if needed - **Advanced** - Download retry settings, concurrent download limits +### Download Settings + +Configure how Lidify acquires new music in Settings → Downloads: + +- **Primary Source** - Choose between Soulseek or Lidarr as your main download source +- **Fallback Behavior** - Optionally fall back to the other source if the primary fails +- **Stale Job Cleanup** - Clear stuck Discovery batches and downloads that aren't progressing + +### Enrichment Settings + +Control metadata enrichment in Settings → Cache & Automation: + +- **Enrichment Speed** - Adjust concurrency (1-5x) to balance speed vs. system load +- **Failure Notifications** - Get notified when enrichment fails for specific items +- **Retry/Skip Modal** - Choose to retry failed items or skip them to continue processing + ### Activity Panel The Activity Panel provides real-time visibility into downloads and system events: @@ -619,7 +663,16 @@ For programmatic access to Lidify: 2. Generate a new key with a descriptive name 3. Use the key in the `Authorization` header: `Bearer YOUR_API_KEY` -API documentation is available at `/api-docs` when the backend is running. +API documentation is available at `/api-docs` when the backend is running (requires authentication in production). + +### Bull Board Dashboard + +Monitor background job queues at `/admin/queues`: + +- View active, waiting, completed, and failed jobs +- Retry or remove stuck jobs +- Monitor download progress and enrichment tasks +- Requires admin authentication --- diff --git a/frontend/components/ui/PullToRefresh.tsx b/frontend/components/ui/PullToRefresh.tsx index 6f537cd..eb6d883 100644 --- a/frontend/components/ui/PullToRefresh.tsx +++ b/frontend/components/ui/PullToRefresh.tsx @@ -13,6 +13,11 @@ export function PullToRefresh({ children, threshold = 80, }: PullToRefreshProps) { + // HOTFIX v1.3.2: Temporarily disabled - blocking mobile scrolling + // TODO: Fix in v1.4 - Issues: 1) h-full breaks flex layout, 2) touch handlers may interfere + // Proper fix: Change line 90 className to "relative flex-1 flex flex-col min-h-0" + return <>{children}; + const [pullDistance, setPullDistance] = useState(0); const [isRefreshing, setIsRefreshing] = useState(false); const startY = useRef(0);