WebKit's WebProcess was crashing (SIGABRT) during EGL/GPU resource
cleanup when the app exited with the miniplayer window still alive.
Now explicitly destroy the miniplayer window before the main process
exits, giving WebKit time to clean up gracefully.
Handle RunEvent::Exit (fires on SIGTERM from tauri dev hot reload)
to stop audio playback and visualizer before process teardown. The
previous CloseRequested handler only fires on user-initiated close,
not on SIGTERM, leaving ALSA/CPAL C threads active during cleanup
which causes "free(): corrupted unsorted chunks" heap corruption.
- Switch from .run(context) to .build(context).run(callback)
- Stop visualizer and both legacy/V2 players on Exit event
- Destroy miniplayer window on Exit event
Replace browser-side pdfjs-dist canvas rendering with server-side
MuPDF rendering for high-quality PDF booklet display. MuPDF renders
pages to PNG at configurable DPI in Rust, producing much sharper
results than the JavaScript canvas approach which had persistent
JPEG 2000 WASM decoder issues.
- Add mupdf crate with sys-lib features (avoid GTK symbol conflicts)
- Create pdf_viewer.rs with v2_booklet_open/render_page/close commands
- Download PDF to temp file, render pages on demand via MuPDF
- Display rendered PNGs as img tags instead of canvas
- DPI scales with zoom and devicePixelRatio for crisp output
- Support rotation via MuPDF transformation matrix
The worker was loaded via Vite's blob: URL transform, which
prevented it from resolving sibling WASM files (openjpeg.wasm,
jbig2.wasm). Now the worker is served from static/pdfjs/ as a
real HTTP resource alongside the WASM files, so JPEG 2000 and
JBIG2 decoders initialize correctly.
Removed disableWorker hack — worker runs properly now.
PDF was rendering tiny because zoom=1 means 1 PDF point = 1 CSS
pixel. Now calculates zoom to fill the container width on load.
Fit-to-width button does the same recalculation.
pdfjs v5 checks GlobalWorkerOptions.workerSrc before reading
document options like disableWorker. Set it to satisfy the
init check even though the worker is disabled per-document.
The web worker runs in a blob: URL context where /pdfjs/ WASM
paths don't resolve, causing JPEG 2000 images to decode at
degraded quality. Disable worker so all decoding runs on the
main thread where static assets are reachable.
Also bumped render scale from 2x to 3x for near-print quality.
First page was black because renderPage() ran while isLoading was
still true, so the canvas wasn't in the DOM yet. Fixed by setting
isLoading=false and awaiting tick() before rendering.
Increased render quality from 1.5x to 2x DPR multiplier for
sharper booklet pages.
PDF booklets use JPEG 2000 images which require OpenJPEG WASM.
Copy pdfjs-dist WASM files to static/pdfjs/ so they are served
at a known URL, and set wasmUrl parameter on getDocument().
PDF loading was failing because pdfjs-dist cannot fetch from
static.qobuz.com due to CORS restrictions in the WebView.
Added v2_fetch_url_bytes command that fetches URL bytes through
Rust's reqwest (no CORS), then passes the binary data to pdfjs.
Also added rotation support (R key or toolbar button) for
vertical booklets.
Modal PDF viewer for album booklets with page navigation,
zoom controls, and keyboard shortcuts. Uses pdfjs-dist to
render PDF pages from URL in canvas.
All animations default to 60fps. Each panel reads its own setting
from userStorage via shared fpsConfig utility. Settings UI shows
a collapsed section with per-panel dropdowns:
- Background Blur, Spectrum, X/Y (Lissajous), Oscilloscope,
Energy Bands, Transient Pulse, Album Reactive, Spectral Ribbon
Options: Disabled, 15, 30, 60, 120 FPS
i18n keys added to all 4 locales (en, es, de, fr)
WebKit's WebProcess was crashing (SIGABRT) during EGL/GPU resource
cleanup when the app exited with the miniplayer window still alive.
Now explicitly destroy the miniplayer window before the main process
exits, giving WebKit time to clean up gracefully.
Remove unnecessary get() wrapper from svelte/store when calling the
i18n translate function. Direct $t() works correctly in regular
functions (only $derived is problematic per ADR-001). Removes unused
get import from GenreFilterButton, FavoritesView, PlaylistDetailView,
PurchaseAlbumDetailView, PurchasesView, and SettingsView.
Add dev-ghost-doctor.sh (t-shadow check + runtime clean + restart) and
dev-ultra-restore.sh (deep clean with --full/--no-start/--check flags).
Improve check-no-t-shadow.sh patterns for broader t-variable detection.
Clean-dev-runtime now runs guardrail check automatically. Remove --force
from vite dev. Add npm script shortcuts for all new tools.
- Allow dead_code on TokenValidationResponse.code (deserialized but not read)
- Remove unused MusicProvider import in commands_v2.rs
- Restore #[allow(non_snake_case)] on v2_plex_cache_get_tracks
- Replace drop(tokens) with let _ = tokens (drop on reference is no-op)
Mirror the album cover context menu options: right-clicking the artist
photo now shows Open in Browser and Save As below the existing
custom image options, separated by a divider.
Detect maximized state via Tauri window API. When the window is not
maximized (floating), apply 8px border-radius and a soft box-shadow.
Body/html backgrounds set to transparent so corners show through
the Tauri transparent window.
Remove isFavoriteOverride={true} that forced all hearts filled,
preventing the toggle from having any visible effect. Subscribe to
favoritesStore so unfavorited tracks animate out (slide+fade) and
are removed from the list after 300ms without requiring a refresh.