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
@@ -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
@@ -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
@@ -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);