* feat(queue): add Timeline display mode
A third queue display mode alongside Queue and Playlist. Timeline shows
the full queue anchored on the current track — played history above
(dimmed), upcoming below — with 'History' and 'Up next' dividers and the
current row auto-centered. It already follows shuffle order since the
queue is stored in play order. The header mode button now cycles
queue → timeline → playlist; Settings gains a third option.
* i18n(queue): Timeline mode strings (9 locales)
* docs(changelog): note Timeline queue mode (#1004)
* feat(enrichment): oximedia BPM/mood facts, mood search, and queue UI
Run client-side oximedia analysis after CPU seed and persist BPM, mood JSON,
and searchable mood_tag facts. Add product mood groups (joy/sadness/dance/work/
romance) with Advanced Search filter on the local index, queue BPM/mood display,
migration 008 mood_tag index, and refreshed licenses for oximedia crates.
* fix(enrichment): keep mood_groups module comment in English
* feat(search): virtual mood groups, anger filter, and Advanced Search UX
Expand mood search via overlapping virtual groups (tag expansion only),
add anger/Злость group, skip album/artist shortcuts for track-only filters,
and simplify mood search UI (songs-only, hide type tabs). Fix CustomSelect
spurious scrollbar on short option lists.
* feat(analysis): unified track analysis plan and enqueue path
Add TrackAnalysisPlan (waveform, LUFS, enrichment) with a single
enqueue_track_analysis entry for all byte-backed triggers. Run enrichment
when cache is full but library facts are missing; route playback, cache,
and backfill through the planner. Fix browseTextSearch LocalSearchOpts tsc
gap and remove obsolete read_seed_bytes_if_needed helper.
* fix(analysis): wire playback dispatch, preload enrichment, and UI refresh
Route stream, gapless, preload, and local-file playback through analysis_dispatch
so BPM/mood enrichment runs when waveform/LUFS are already cached. Fix audio_preload
cache-hit and hot-cache paths, emit preload-cancelled for retry, and add
analysis:enrichment-updated plus content_cache_coverage key resolution.
* fix(audio): preload local files from disk and stop analysis retry loop
Seed hot/offline next tracks via LocalFilePlayback (512 MiB) instead of copying
into the RAM preload slot. Keep bytePreloadingId set after preload-ready so
progress ticks do not re-invoke audio_preload every second.
* fix(enrichment): clippy, album bpm filter routing, and queue mood display
Clippy-clean analysis_dispatch and engine imports; restrict track-derived album
routing to mood_group/mood_tag only so bpm is skipped on album queries. Log
enrichment plan errors with retry-all plan; filter queue mood labels to oximedia ids.
* fix(enrichment): simplify mood_tag backfill branch in plan_track_enrichment
Remove empty if-block; keep same behaviour when backfill fails and moods row exists.
* docs: CHANGELOG and credits for track enrichment PR #863
* docs(credits): track enrichment PR #863 contributor line
* chore(enrichment): clippy-clean plan branch and trim dead exports
Collapse mood_tag backfill if for clippy; remove unused moodGroupById and
OXIMEDIA_MOOD_LABELS re-exports; stop poll when server BPM is already known.
* fix(enrichment): close R2/S1–S3 limits and Song Info BPM fallback
Return TrackEnrichmentOutcome::Failed on oximedia errors so retries are not
masked as complete; extract mood Advanced Search SQL, unify top-3 mood tag
selection in mood_groups with TS invariant tests, and show measured BPM in
Song Info when tag BPM is missing or zero.
* fix(enrichment): restore offline coverage and show mood in Song Info
Add unit tests for offline download cancel/clear registry after the analysis
seed refactor dropped read_seed_bytes coverage; show localized mood labels in
Song Info when library enrichment facts exist.
* fix(enrichment): soft mood scoring and unblock offline cancel tests
Replace oximedia quadrant happy/excited mapping with valence/arousal
soft scores across all mood tags for display, storage, and backfill;
fix offline cancel unit tests that deadlocked by calling clear while
holding the global offline_cancel_flags mutex.
* fix(enrichment): dedupe joy cluster and cap mood display at two labels
Never show happy and excited together; pick one tag per V/A cluster,
tighten oximedia recalibration, and limit queue/Song Info to two moods
that pass a relative score floor.
* fix(enrichment): disable oximedia mood labels in UI and search tags
Oximedia 0.1.7 mood is a spectral energy heuristic, not independent mood
weights; valence correlates with loud/bright audio and false-labels metal
and lyrical tracks as happy. Hide queue/Song Info mood and stop writing
mood_tag facts until a reliable detector lands; keep V/A/moods JSON stored.
* fix(enrichment): disable oximedia mood analysis and add BPM advanced search
Stop planning, running, and storing oximedia mood facts; purge accumulated
mood rows via migration 009. Hide mood filters in Advanced Search, expose
BPM range filter with dual-storage resolution, and show a BPM column in song
results when that filter is active.
* feat(search): analysis BPM priority, source tooltip, and filter UX
Prefer analysis track_fact over file tags for BPM resolution; show source
in list tooltips. Validate BPM range on blur, add clear button, fix double tooltip.
* fix(enrichment): prefer analysis BPM in Song Info and queue tech row
Show measured track_fact BPM before file tags until analysis completes;
pick the highest-confidence analysis fact when several exist.
* feat(settings): dim disabled toggle rows + rename Clear → Clear queue
* Settings toggle rows (`.settings-toggle-row`, `.sidebar-customizer-row`) dim
their non-toggle content to 0.6 opacity when the switch is off. Driven by a
single `:has(.toggle-switch input:not(:checked):not(:disabled))` rule so it
applies across every Settings tab without per-row markup. Mutex-disabled
toggles (Crossfade/Gapless) are excluded to avoid stacking with the existing
inline 0.45 dim on their row.
* Queue toolbar "Clear" button now reads "Clear queue" in all 9 locales for
parity with "Shuffle queue" and to distinguish from playlist clear/delete.
Adopted from @kveld9's PR #558 — cherry-picked the spirit, rewrote the dim as
a single global selector instead of three inline-styled customizer rows, and
extended the rename to the Romanian locale that landed after #558 was opened.
Co-Authored-By: kveld9 <179108235+kveld9@users.noreply.github.com>
* docs(changelog,credits): credit @kveld9 for #778
Co-Authored-By: kveld9 <179108235+kveld9@users.noreply.github.com>
---------
Co-authored-by: kveld9 <179108235+kveld9@users.noreply.github.com>
* fix(playback): pin queue streams, cover art, and library links to queue server
When the active server changes while a queue from another server is playing,
keep streams and UI on queueServerId; switch back for artist/album links and
queue or player-bar context menus.
* fix(playback): switch to queue server when opening Now Playing
Ensure active server matches queueServerId before Subsonic fetches on the
Now Playing page, mobile player route, and queue info panel; scope caches
by server id.
* docs(credits): mention Now Playing in PR #717 contribution line
* fix(playback): route scrobble and queue sync to queue server
Address PR review: apiForServer for scrobble/now-playing/savePlayQueue,
clear queueServerId on server removal, mini-player queueServerId sync,
block cross-server enqueue with toast, and regression tests.
Each src/locales/<lang>.ts (~1800 LOC) becomes a folder src/locales/<lang>/
with one module per i18n namespace (44 each) plus an index.ts barrel that
reassembles <lang>Translation in the original key order.
Mechanical, script-driven split with a JSON round-trip check: every
locale object is byte-identical to its pre-split form. i18n.ts is
unchanged — './locales/<lang>' now resolves to the folder index.
The per-namespace settings.ts files land ~440-460 LOC; a single i18n
namespace is the natural, non-arbitrary split unit for a flat string
table, so they are intentionally left whole.