Releases: izzoa/plexmix
v0.11.0 - Web UI Redesign & Cancellable Jobs
✨ New Features
Web UI Redesign
- A slim glass icon-rail and a ⌘K command palette — search pages, run actions, fire a quick vibe, and
g+key navigation. - The Generator is reimagined as an "AI-thinking" showpiece: a live particle vector-field, a four-phase progress tracker bound to the real generation pipeline, a streaming terminal log, and a results playlist with removable tracks.
- New Settings → Appearance: theme toggle plus Density (comfortable/compact) and Accent-intensity (subtle/balanced/vivid), persisted across reloads.
Cancellable Long-Running Jobs
- Cancel any running job from the UI — library sync, embedding generation, MusicBrainz enrichment, audio analysis, Doctor fixes, and tagging — each behind a confirmation, with the work actually stopping and a
cancelledoutcome shown. - New global "Cancel running task" command (⌘K) that stops everything in flight.
🔧 Changed
- The web UI now defaults to light mode (previously dark). Your choice is remembered and can be toggled from the rail, the top bar, or Settings → Appearance.
- Refreshed default AI models to a balanced, cost-aware tier: Gemini
gemini-3.5-flash, OpenAIgpt-5.4-mini, Claudeclaude-sonnet-4-6, Coherecommand-a-03-2025. Flagships (gpt-5.5,claude-opus-4-8,gemini-3.1-pro-preview,command-a-plus-05-2026) remain selectable. Embeddings are unchanged — existing FAISS indexes do not need regenerating. - The
temperaturesetting now has no effect for OpenAI GPT-5, Gemini 3, and Claude Opus 4.7+ (those families reject a custom temperature); it still applies to Claude Sonnet/Haiku, Cohere, custom, and local providers.
🐛 Fixed
- OpenAI GPT-5 requests now use
max_completion_tokensand omittemperature— OpenAI playlist generation and the Settings "Test OpenAI" button no longer fail with a 400. - Gemini 3 requests constrain thinking (
thinking_level=low) and use the model's default temperature. - Claude Opus 4.7+/4.8 requests omit sampling parameters (
temperature/top_p/top_k) those models reject.
🗑️ Removed
- End-of-life
gemini-2.0-flash-001(Google shutdown 2026-06-01) and the invalidclaude-opus-4-1-20250414model ID.
📦 Install / Upgrade
pip install --upgrade plexmix
# Docker
docker pull ghcr.io/izzoa/plexmix:0.11.0Breaking Changes: None.
✅ CI verified locally: Black, Ruff, and Mypy clean; 704 passed, 4 skipped (coverage 47.7%).
Full Changelog: v0.10.0...v0.11.0
v0.10.0 - Hardened AI Provider Error Handling
Highlights
A reliability release that fixes a costly failure mode in AI tagging and prevents its most common root cause.
Previously, a single unrecoverable error (e.g. a malformed API key) during AI tag generation repeated across every batch — on a large library that meant thousands of doomed API calls, empty tags written library-wide, and an unreadable wall of raw error output. Now the run stops immediately with a clear, actionable message, and keys are sanitized and validated before use.
Added
- API key sanitization & validation across all sources (system keyring, environment variables, and saved settings): surrounding whitespace and newlines are stripped automatically, and a key containing characters that cannot be sent over HTTP fails fast with a clear "re-enter your key in Settings" message instead of a cryptic transport error mid-run.
Fixed
- Fail-fast on unrecoverable provider errors: AI tag generation now stops immediately on a fatal error (invalid API key, or an HTTP-edge/proxy rejection) instead of retrying every batch and writing empty tags across the entire library. Tags generated before the failure are preserved.
- Human-readable provider errors: opaque failures — including the HTML
Error 400 (Bad Request)page returned by an API gateway or proxy — are translated into a single actionable message pointing at the API key or network path.
Improvements (technical)
- New shared
FatalProviderError+classify_provider_error()helper (src/plexmix/ai/errors.py) with an explicit retryable/fatal mapping:- Retryable: connection/timeout, 408, 429, 5xx
- Fatal: 400, 401, 403, 404, 422 (and any other 4xx)
- Fail-fast handling wired into all five tag-generation entry points: sync pipeline,
plexmix tags generate, and the tagging/doctor UI states.
Testing
- 41 new tests (credential sanitization, provider error classification, sync fail-fast).
- Full suite: 677 passing, coverage 47%.
Breaking Changes: None
v0.9.0 - MusicBrainz Integration & Test Coverage Push
New Features
MusicBrainz Metadata Enrichment
- Community-curated genres: Enrich tracks with MusicBrainz genre tags (shoegaze, dream pop, post-punk, etc.) for more accurate semantic search and playlist generation
- Canonical artist IDs (MBIDs): Resolve artist name variants (e.g., "The Beatles" vs "Beatles") for better playlist deduplication
- Recording type detection: Identify live recordings, remixes, covers, and studio tracks
- SQLite cache: 90-day TTL cache avoids redundant API calls; transient errors are never cached
New CLI Commands
```bash
plexmix musicbrainz enrich [--force] # Enrich tracks with MusicBrainz metadata
plexmix musicbrainz info # Show enrichment stats
plexmix musicbrainz clear-cache # Clear expired cache entries
plexmix sync --musicbrainz # Enrich during sync
```
Web UI Enhancements
- MusicBrainz settings tab: Enable/disable, confidence threshold slider, contact email
- Library page: "Enrich with MusicBrainz" button with progress tracking
- Doctor page: Detects missing MusicBrainz enrichment with one-click fix
- Doctor page: "Regenerate All" tags button with confirmation modal
- Dashboard: MusicBrainz enriched tracks stat tile
- Last sync time: Human-readable 24hr format (e.g., "Mar 14, 2026 16:50")
License
- Changed from MIT to AGPL-3.0-only
Integration Points
MusicBrainz genres and recording types are woven into the existing pipeline:
- Embedding text: MB genres appended for better semantic similarity search
- AI tag prompts: MB genres included for improved tag generation accuracy
- Playlist generation: Artist MBIDs used for diversity deduplication (handles name variants)
Bug Fixes
- Changelog not loading in Docker (`.dockerignore` fix)
- MusicBrainz transient API errors no longer cached as definitive no-match results
- SQLite thread-affinity fixed in MusicBrainz enrichment (DB created inside executor thread)
- MusicBrainz enrichment properly gated on `enabled` setting in sync pipeline and UI
- Free disk space in Docker CI to prevent build failures
Test Coverage
639 tests total (+119 new), 47% coverage
- 32 embedding pipeline tests (provider generation, dimension mismatch, end-to-end search, tagging service integration)
- 29 sync error recovery tests (network timeouts, corrupt metadata, embedding/tag failures, audio analysis integration)
- 29 MusicBrainz tests (client, service, cache, database)
Docker Environment Variables
| Variable | Description |
|---|---|
| `MUSICBRAINZ_ENRICH_ON_SYNC` | Enable MusicBrainz enrichment during sync (`true`/`false`) |
| `MUSICBRAINZ_CONTACT_EMAIL` | Contact email (required by MusicBrainz TOS) |
v0.8.5 - Bug fixes and changelog modal
Fixed
- Tagging Edit button: Fix
KeyError: 'id'when clicking Edit on the tagging page — passes individual field values instead of the whole dict to avoid Reflex event serialization issues - Table row hover text: Fix unreadable text when hovering over song rows in library and tagging pages — adds explicit
color: var(--pm-gray-12)with proper CSS specificity so text adapts to the hover background in both light and dark modes
Added
- Changelog modal: Version badge in the sidebar is now centered and clickable — opens a scrollable dialog showing the full release history rendered from
CHANGELOG.mdviarx.markdown
v0.8.4 - Docker latest tag fix
Fixed
- Docker
:latesttag now correctly points to the slim (default) image instead of the-localimage - Reordered CI workflow jobs so the slim image is pushed last — GHCR shows the most recently pushed manifest as the default package
Details
No application code changes. This is a CI-only fix that ensures docker pull ghcr.io/izzoa/plexmix:latest pulls the slim image (without local ML models) as intended.
Users who want the heavier image with local models should use :latest-local or a versioned -local tag (e.g., :0.8.4-local).
v0.8.3 - Fix Frontend Freezing During Background Tasks
Bug Fixes
- Frontend freezing during heavy tasks — Sync, tag generation, and embedding generation were running blocking API calls and
time.sleep()directly on the asyncio event loop, starving WebSocket heartbeats and causing "Cannot connect to server: timeout" errors - Wrapped all blocking work in
run_in_executor:start_sync— Plex sync engine operationsgenerate_embeddings— Embedding API calls (library page)start_tagging— AI tag generation + DB saves (tagging page)regenerate_missing_tags— AI tag generation (doctor page)_generate_embeddings_for_tracks— Embedding API calls + FAISS index building (doctor page)
- Replaced deprecated
asyncio.get_event_loop()withasyncio.get_running_loop()
Note: Audio analysis and playlist generation already used run_in_executor correctly — this brings the remaining handlers up to the same standard.
Testing
549 tests passing
v0.8.2 - Dashboard Hotfix
Fix
- Dashboard SyntaxError — Fix
on_mountkeyword argument placed before positional child components indashboard.py, which prevented the dashboard page from loading
This is a hotfix for v0.8.1. Users on v0.8.1 (Docker or PyPI) should upgrade.
v0.8.1 - Dashboard Fix & Version Display
Bug Fixes
- Dashboard 0/0/0/0 on fresh load — Added
on_mountfallback so stats and config status load reliably after the index page's client-side redirect - Last Sync showing "Never" — Now uses
sync_historytable instead ofMAX(last_played), which was always NULL for users who sync but don't play tracks through Plex - CI fixes — Resolved mypy errors for optional dependency imports (reflex, torch, transformers, sentence-transformers) and Ruff lint warnings
New Features
- Version number in sidebar — App version (
v0.8.1) now displayed in the lower-left of the desktop sidebar and mobile slide-out menu
Testing
549 tests passing
v0.8.0 - Persistent TaskStore & Service Layer
New Features
Persistent TaskStore Architecture
- Background tasks (sync, tagging, audio analysis, embeddings, doctor fixes) now survive browser disconnects — close your browser, reopen it, and progress resumes seamlessly
- Client-pull polling via hidden DOM buttons replaces server-push progress updates, eliminating all "disconnected client" warnings
TaskEntrydataclass tracks status (running/completed/failed/cancelled), progress percentage, messages, and extensible metadata- Global exclusivity per job type prevents concurrent DB-contending operations (e.g., two syncs can't run simultaneously)
- Session recovery:
poll_task_progress()on page load automatically reconnects to any in-progress tasks
Multi-User Forward Compatibility
- TaskStore keyed by
(user_id, job_type)with"default"for single-user mode - Ready for future multi-user scenarios without architectural changes
Infrastructure Improvements
Shared Service Layer
- New service modules (
sync_service.py,playlist_service.py,tagging_service.py,audio_service.py) eliminate duplicated provider/connection setup across CLI and UI - Centralized constants module (
config/constants.py) for batch sizes, pagination, retry defaults, and diversity constraints
Code Splitting
- Split
SettingsState(1,068 → 638 lines) into core state +_settings_testing.py+_settings_downloads.py - Split
settings.pypage (1,045 → 364 lines) into core page +_settings_sections.py - Extracted reusable form field components (
form_field,year_range_field,help_text)
CLI Refactoring
- CLI commands (sync, create, tags, embeddings, audio) refactored to use shared service layer
Testing
- 29 new tests for TaskStore and JobManager lifecycle, progress, cancel/pause, and exclusivity
- 549 tests total, all passing
Files Changed
- 93 files changed, 12,821 insertions, 5,540 deletions
- 33 new files (service layer, CLI modules, components, tests)
Breaking Changes
None
v0.6.7 - Parallel Audio Analysis
New Features
Parallel Audio Analysis
Audio analysis now processes multiple tracks concurrently using a thread pool with a sliding-window pattern. This provides a significant speedup on multi-core systems.
Configuration:
AUDIO_WORKERS=4(env var) oraudio.workers: 4in config.yaml- Default: 4 workers
- Set to
1to restore sequential behavior
Applied everywhere
Parallel analysis is used in all three flows:
- Library page — "Analyze Audio" button
- Sync-time — automatic analysis when
AUDIO_ANALYZE_ON_SYNC=true - Doctor page — "Analyze" maintenance action
Pause, resume, stop, and ETA all work correctly with concurrent workers.