mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
604cdd54d6a55ebad3fa4f113355855ad9eb7be7
447 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fc34a0ec59 |
feat(offline): local-bytes browse when server is unreachable (#1017)
* feat(offline): local-bytes browse for artists and albums Make Artists, All Albums, and artist/album detail pages work offline from the library index limited to on-disk library and favorite-auto tracks. Add a DEV header toggle to simulate offline browse for testing. * feat(offline): reactive DEV offline toggle with full disconnect simulation Subscribe nav and browse/detail hooks to useOfflineBrowseActive so UI refreshes on toggle. DEV force-offline now blocks server probes, reports disconnected status, and gates Subsonic like real offline for player parity. * feat(offline): bytes-first favorites when offline browse is active Load Favorites from local playback bytes, filter starred tracks client-side, and restrict album-level star queries to local album ids. Drop interim perf attempts (lean SQL, progressive load, connection singleton, prefetch UX). * feat(offline): tracks, help, player stats; suspend library picker offline - Offline browse for Tracks hub from local bytes; sidebar nav for tracks/help/statistics - Statistics redirects to player-stats offline; server/Last.fm tabs skip network fetches - Hide music-library picker offline; save filter and restore on reconnect (all libraries while disconnected) - Unified isOfflineSidebarNavAllowed for library + system entries * feat(offline): fork disconnect navigation by offline browse capability When the server drops: stay on the page if nothing is browsable offline; reload in place on offline-capable routes; otherwise redirect to All Albums instead of the old /offline or /favorites bounce. * feat(offline): browse cached playlists when the server is down List and open manually pinned regular playlists from local library-tier bytes offline, with sidebar/nav routing and read-only playlist UI. * feat(offline): read-only artist detail and local play-all paths Hide favorites and discography offline actions when browse is offline; load Play All, Shuffle, and top-track continuation from local album bytes. * feat(offline): read-only album detail and enqueue from local bytes Hide favorites, download, and cache-offline actions on album pages when offline browse is active. Favorites album cards enqueue via the same resolveAlbumForServer path as play, including local playback bytes. * chore: remove unused import in AlbumCard after enqueue refactor * feat(offline): unify browse integration contract across the app Add useOfflineBrowseContext, offlineMediaResolve, and offlineActionPolicy; wire shell nav to a single capability source; migrate play/enqueue and context-menu paths off raw getAlbum; replace readOnly with action policy on detail surfaces. Tests updated for the media-resolve facade. * feat(offline): close browse contract gaps and fix offline Home feed Split offline browse modules, align favorites capability across servers, wire action policy on context menus, migrate hooks to useOfflineBrowseContext, and preserve stale Home feed cache when offline so the UI does not empty. * fix(offline): block playbar stars, close audit gaps, trim dead exports Hide star rating and favorite in PlayerBar when offline browse is active via offlineActionPolicy playerBar surface. Wire stay-reload token into browse hooks, migrate hooks to context.active, guard rating prefetch network calls, and route playlist load through resolvePlaylist. * docs: add CHANGELOG and credits for offline browse PR #1017 * fix(offline): stop DEV connection probe regression in tests React to devForceOffline transitions only in useConnectionStatus so mount does not double-fire check() or ignore disableBackgroundPolling. Add pingWithCredentials to PlayerBar test mock and DEV-toggle unit tests. |
||
|
|
f4e1086131 |
feat(themes): fresher store refresh + external-services notice (#1016)
* feat(themes): fresher store refresh + external-services notice Manual refresh now fetches the registry from GitHub raw first (~5 min cache) and falls back to the jsDelivr CDN, so a freshly merged theme shows up without waiting on — or purging — the shared CDN @main edge (up to 12 h). Normal loads still use the CDN. Adds a notice on the Theme Store that the catalogue and previews load from external services (jsDelivr / GitHub), in line with the app's network transparency. i18n ×9. * docs(themes): note the store-refresh PR in the Theme Store entry Now that #1015 is merged, fold PR #1016 into the still-unreleased Theme Store changelog and credits entry. |
||
|
|
23f032b274 |
feat(themes): free-form community themes with a security floor (#1015)
* feat(themes): free-form community themes with a security floor Community themes are no longer token-only. The in-app guard (validateThemeCss) now enforces only a security floor — no network (@import / non-data url()), no scripts (<style>/<script>/expression()/ javascript:/-moz-binding), no @property, @keyframes namespaced as <id>-, and a 256 KB cap — and otherwise allows any selectors, structure and animations. validateThemePackage checks the manifest plus that floor. Themes can react to app state via same-element attributes set on the theme root: data-playing, data-fullscreen, data-sidebar-collapsed, data-lyrics-open. The local-import confirm dialog now notes that imported themes aren't reviewed and are installed at the user's own risk. Removes the now-unused bundled token whitelist. * docs(themes): note free-form themes in the Theme Store entry Add PR #1015 and a free-form bullet to the still-unreleased Theme Store changelog and credits entry. |
||
|
|
aad1a6c3f0 |
fix(themes): route remaining UI colours through theme tokens (#1014)
* fix(themes): route remaining UI colours through theme tokens An audit found several surfaces still wired to the fixed Catppuccin palette or hardcoded hex, so community themes could not recolour them: the 5-star rating, the global search field, the gradient-text flourish, badges and category-avatar text, and the What's New page + sidebar banner. Rewire them to existing contract tokens — no new tokens, and the built-in themes look identical (the values match). Community themes now control these areas. Also fixes device-sync rows referencing an undefined --bg-secondary (they rendered with no background); they now use --bg-card. * docs(themes): note the theme-coverage PR in the Theme Store entry Add PR #1014 to the still-unreleased Theme Store changelog and credits entry, alongside the other follow-ups. |
||
|
|
10fc489ce9 |
fix(themes): offline banner for the Theme Store (#1013)
* fix(themes): show an offline banner when the Theme Store is unavailable When the registry can't be fetched and no catalogue is cached, the store now shows a clear offline banner (icon, message, retry) in place of the list, and hides the search/filter toolbar — there is nothing to browse. The cached-catalogue fallback with its offline indicator is unchanged, so the store still works offline when a catalogue was previously cached. * docs(themes): note the offline-banner PR in the Theme Store entry Add PR #1013 to the still-unreleased Theme Store changelog and credits entry, alongside the other follow-ups. |
||
|
|
b3573e7107 |
feat(themes): import a theme from a local .zip (#1012)
* feat(themes): validate and extract locally imported theme packages Add the backend + validation half of local theme import: users will be able to load a theme packaged as a .zip (manifest.json + theme.css). - `import_theme_zip` Tauri command unpacks only manifest.json + theme.css from the archive, outside the webview, with an archive-size cap, per-entry uncompressed caps, and path-traversal rejection. - `validateThemePackage` runs the full theme-store contract in-app: the manifest schema (field patterns copied from the repo schema), the CSS token whitelist with all core tokens required, color-scheme matching the declared mode, data-URI restricted to the arrow token, and a guard against ids that collide with built-in themes. The existing `validateThemeCss` containment guard is reused and runs again at injection time. The contract token list is a byte-identical copy of the themes repo's allowed-tokens.json. Covered by validateThemePackage tests for every rejection class. * feat(themes): add the Import a theme section to the Themes tab A dedicated section between the theme scheduler and the Theme Store lets users import a theme from a local .zip. After the package validates, a confirmation dialog names the theme and its author before it is installed. A rejected import shows a plain-language explanation aimed at end users; the raw contract diagnostics (token names, missing fields) are tucked into a collapsible "Technical details" block for theme authors. Fully localised across all nine languages. * docs(themes): changelog + credits for local theme import Fold the local .zip import (and the store pagination / refresh-scroll polish) into the still-unreleased Theme Store entry rather than a new section, and extend the credits line. PRs #1011 and #1012. |
||
|
|
f9df918c72 |
feat(themes): community Theme Store + semantic-token refactor (#1009)
* feat(themes): add semantic tokens for the theme-store contract (B0 P1) Additive: define --highlight, --accent-2, --bg-deep, --bg-elevated and --text-on-accent on the :root base as --ctp-* mappings. They resolve per-theme automatically and nothing consumes them yet (zero behaviour change) — groundwork for replacing direct --ctp-* use in components. * refactor(themes): components consume semantic tokens, not --ctp-* (B0 P2) Replace every direct --ctp-* reference in component/layout/track CSS and TSX inline styles with the readable semantic token (--bg-app, --accent, --highlight, --text-on-accent, …). --ctp-* now survives only as the Catppuccin palette layer the base maps from, and as the deliberate categorical rainbow in Composers/Genres/artistsHelpers (left untouched). This is the readable contract surface for the community theme store. Divergences (theme set a semantic var != its --ctp- source) are corrections — the element now uses the theme's real semantic colour. * feat(themes): add player-bar title/artist color tokens New optional --player-title / --player-artist, defaulting to --text-primary / --text-secondary so nothing changes unless a theme overrides them. Lets a theme give the now-playing readout its own colour as a plain token. * refactor(themes): token-only theme library (flatten, whitelist, one file per theme) Turn every built-in theme into a single self-contained [data-theme] var block of semantic whitelist tokens (plus the internal --ctp-* palette layer): - Flatten all themes: drop structural override rules, @keyframes, and global-token overrides (radius / shadow-elevation / transition / spacing / font / focus-ring). Signature player-bar readout colours are preserved via the new --player-title / --player-artist tokens. - Normalize the var blocks to the semantic whitelist: drop the alternate token vocabulary (nav-active / scrollbar / bg-input / success / border-default / ...); rename --success -> --positive and --border-default -> --border where no whitelist equivalent was set. Migrate the few components that read those tokens to the whitelist equivalents. - Split multi-theme files so each theme ships as its own file, making the built-in set 1:1 with the per-theme store packaging. Kept as-is (built-in, not flattened): the two colour-blind-safe accessibility themes, plus the two curated core skins. * chore(themes): remove seven themes retired after the token refactor These themes leaned on heavy structural overrides and were dropped rather than flattened. Full removal each: the CSS file(s), the index.css import, the Theme type union, and the ThemePicker entry. * feat(themes): granular tokens — track lists Wire track rows to per-region tokens: row hover (--row-hover), the now-playing row + indicator (--row-playing-bg / --row-playing-text), track title/artist/ number/duration text, column-header text, row dividers, and the resize-handle active colour. Covers the desktop tracklist, the shared song-row (Tracks hub / search), and the mobile tracklist. Drop a baked border fallback. Visual no-op. * feat(themes): granular tokens — cards Wire album and artist cards to per-region tokens (--card-hover-border, --card-title, --card-subtitle, --card-placeholder-bg). Visual no-op. * fix(themes): drop undefined/baked colour aliases Replace the undefined --bg-surface (resolved to nothing — broken placeholder backgrounds and filter input) with --card-placeholder-bg / --input-bg, and the baked-hex aliases --color-error / --color-warning with --danger / --warning so themes can actually recolour them. * feat(themes): Spectrum demo theme + trim unused cascade tokens Add a loud built-in demo theme that gives each region its own hue (sidebar green, player pink, lists cyan, cards gold, menus red, controls blue) so the per-region granularity is obvious when you switch to it. Drop two unused cascade tokens (--sidebar-text-active, --row-active-bg) and the unused on-media block (those media surfaces stay static by design). * style(themes): make Spectrum demo brutally loud Full-saturation neon per region (toxic green sidebar, magenta player, cyan lists, acid-yellow cards, blood-red menus, electric-blue controls, purple scrollbar) so the per-region separation is unmistakable. The earlier soft tints were too subtle to read. * feat(themes): name the granular demo theme Braindead * feat(themes): granular per-region tokens — cascade layer + sidebar Add an optional per-region token layer (semantic-cascade.css) so a theme can recolour individual regions — sidebar hover, player controls, list rows, menus, inputs, on-media surfaces — independently of the global tokens. Every token defaults to its base token (or a media-safe literal), so this is a visual no-op until a theme overrides one; it only adds control points. Wire the sidebar region as the first consumer and drop the baked grey fallbacks (--bg-tertiary, etc.) that no theme could reach. * feat(themes): granular tokens — controls, menus, scrollbar Wire inputs, buttons, sliders, the custom-select, context menus, submenus and modals to per-region tokens, and tokenise the scrollbar. Complete B0 by dropping the last direct --ctp-* references in the input/button/progress/ scrollbar utility CSS. Fix three undefined-token bugs that fell back to nothing (so no theme could reach them): --surface-2 (context-menu hover had no highlight), --bg-surface (submenu create-input had no background), and the baked grey fallbacks in the custom-select. Every other new token defaults to today's value — a visual no-op that only adds override points. * feat(themes): granular tokens — player bar Wire the desktop player bar's transport controls, time toggle, and overflow menu to per-region tokens (--player-control, --player-time-toggle-*, etc.). Fix the undefined --surface-hover/--surface-active grey fallbacks on the time toggle. Visual no-op; defaults match today's values. * chore(themes): remove empty theme stub files Six theme CSS files were reduced to comment-only stubs by the flatten sweep but their files and @import lines remained. Five are empty structural companions of now-flattened themes (morpheus, p-dvd, aero-glass, luna-teal) and two are orphans of cut themes (order-of-the-phoenix, pandora). Removed the files and their imports. * feat(themes): runtime injection foundation for the theme store Plumbing for installed community themes ahead of the in-app store UI, nothing user-visible yet: - installedThemesStore: persisted (localStorage) record of installed community themes incl. their CSS text, so an active community theme is available synchronously at startup (no flash, fully offline). - themeInjection: reconcile <head> <style data-installed-theme> elements with the store; lightweight defense-in-depth sanitize on top of CI. - themeRegistry: jsDelivr registry client with a 12h localStorage cache and stale-on-error fallback. - App: inject installed themes before applying data-theme, in both webviews. - themeStore: widen the Theme type to accept dynamic installed ids. * feat(themes): dedicated Themes settings tab Move theme selection and the day/night scheduler out of Appearance into a new dedicated Themes tab — the future home of the community Theme Store. Appearance keeps grid columns, visual options, UI scale, font and seekbar. - ThemesTab: theme picker + scheduler (relocated verbatim). - AppearanceTab: drop the two relocated sections + now-unused imports. - Register the tab in settingsTabs (Tab union, resolveTab, search index) and Settings (tab bar, render, label map). - i18n: settings.tabThemes in all 9 locales. * feat(themes): community Theme Store browse + install Add the Theme Store section to the Themes tab: - Fetch the jsDelivr registry (12h cache, stale-on-error fallback). - Search by name/author/description + filter by light/dark + refresh. - Per-row CDN thumbnail, name, author, description and actions: Install / Apply / Update / Uninstall. Installing fetches the CSS, persists it (localStorage) and the runtime injection applies it; uninstalling the active theme falls back to the matching core. - Rating slot left reserved (deferred). - i18n: themeStore* keys in all 9 locales. * feat(themes): slim bundle to fixed cores + flat Themes tab Remove the 86 store palettes (incl. braindead) from the app bundle — the CSS files, their index.css imports, the Theme union and the picker data — leaving only the six fixed cores (Catppuccin Mocha/Latte, Kanagawa Wave, Stark HUD, Vision Dark/Navy). Everything else installs from the store. Themes tab is rebuilt flat (no collapsible accordions): - "Your Themes": one card grid of the fixed cores + installed community themes; click to apply, uninstall on community ones (active theme falls back to the matching core). Catppuccin prefix on Mocha/Latte; a CVD-safe pill on the colour-blind-safe Vision themes. - Scheduler day/night options include installed themes. - Theme Store: alphabetical order, thumbnail lightbox, and a submit hint above the search linking to the themes repository. - Nav order: Servers, Library, Audio, Themes, Appearance, Lyrics, … - ThemePicker accordion removed; fixed-theme data moved to fixedThemes.ts. - i18n for all new strings across 9 locales. * feat(themes): reset removed-from-bundle themes to a bundled fallback After slimming the bundle to the six fixed core themes, a profile upgraded from an older build may have an active or scheduler theme that is now store-only and not installed — it has no [data-theme] block and would render as unstyled :root. Reset any theme/themeDay/themeNight that is neither bundled nor installed to a bundled fallback: Mocha for the main + night slots, Latte for the day slot. Runs synchronously in runPreReactBootstrap, rewriting the persisted selection in localStorage before React mounts (no flash; Zustand rehydrates after first paint). No auto-install and no network — the fallback is always a bundled theme, so it works offline. * feat(themes): floating back-to-top button on the Themes tab The Themes tab can get long (theme grid + scheduler + full store list), so add a floating back-to-top affordance that appears once the page is scrolled and smooth-scrolls to the top. It is portalled into the route host and positioned absolute against it — the main scroll viewport sets contain: paint, which would otherwise make position: fixed resolve against the scrolling box and drift with the content. Reusable component (scroll viewport id + threshold props); i18n common.backToTop added in all nine locales. * feat(themes): accessibility + state polish for the Theme Store - Reuse the shared CoverLightbox for the thumbnail preview instead of a second inline dialog — gains a visible close button and a focus-managed, portalled dialog, and drops duplicated markup. - Theme cards expose aria-pressed so assistive tech announces the selected theme, not just the visual check. - Transient store messages get live-region roles (loading/empty/install failure = status, fetch error = alert). - Thumbnails degrade gracefully when offline/missing (hide the broken-image glyph; the thumbnail button no longer stretches with the row, so its background can't show as letterbox bars). * feat(themes): larger store-row thumbnails (120x75 -> 200x125) The list previews were too small to make a theme out; bump the display size (same 1.6 aspect). Thumbnails are now served at 720x450, so the larger display stays crisp. * fix(themes): bust thumbnail cache on registry change jsDelivr serves theme thumbnails with a 7-day max-age, so when a thumbnail is updated the webview keeps showing its cached old image (the path is unchanged). Append the registry's generatedAt as a cache-busting query to the thumbnail URLs (list + lightbox); it changes on every themes push, so a registry refresh makes the webview re-fetch and reflect the current CDN image instead of a stale one. * docs(themes): changelog + credits for the Theme Store Add the 1.48.0 "Themes — community Theme Store" changelog entry (PR #1009) and the matching line in the Psychotoxical credits. * fix(themes): address PR review (uninstall hygiene, validation, polish) Uninstall/scheduler & validation: - uninstallTheme() repairs every selection slot (active + day + night), not just the manual one, and is shared by both uninstall buttons (dedup). - Validate theme CSS at install time and skip persisting CSS that won't inject (no more "installed/active but renders nothing" with no feedback). - Harden the runtime validator: exactly one rule, scoped exactly to the theme's [data-theme='<id>'] selector (no unscoped/foreign selectors), no at-rules, url() only data:, no expression()/javascript:, size-capped. Tokens & polish: - Fix three dangling undefined tokens (--surface-2 x2, --bg-surface). - Finish the warning/success token sweep (--warning / --positive, themeable). - Apply the active theme synchronously before React mounts (no first-frame flash) and inject installed themes up front. - One-time, dismissible notice when the slim-bundle migration reset a theme. - Update badge uses semver, not string inequality. - Offline/stale indicator in the store; cross-window theme sync; drop the now dead REMOVED_THEME_REMAP and Card.mode field. Tests: themeInjection (validator + sync), themeRegistry (cache/force/stale/ malformed), uninstallTheme (slot repair), migration notice. i18n in all nine locales. Full suite green (1755 tests). * test(bootstrap): cover startup theme apply + cross-window sync The review fixes added applyThemeAtStartup / installCrossWindowThemeSync to bootstrap.ts (a hot-path file) without tests, dropping its coverage to 68.3% and failing the frontend hot-path coverage gate (>=70%). Add unit tests for both (and the no-op / malformed-storage paths); bootstrap.ts is back to ~98%. |
||
|
|
2d3c723a6e |
feat(offline): unify local playback, offline library, and favorites sync (#1008)
* feat(local-playback): LP-1 media layout and download_track_local
Add library-index-backed path builder in psysonic-core and a unified
Tauri download command that writes under media/{cache|library}/ with
layout fingerprints; legacy hot/offline commands unchanged for now.
* feat(local-playback): LP-2 localPlaybackStore and media tier Rust helpers
Add unified Zustand index with legacy offline/hot-cache import, media_layout
TS mirror, and Rust commands for tier size/purge/delete/promote.
* feat(local-playback): LP-3 wire prefetch and playback to unified index
Route downloads through download_track_local, delegate hot/offline shims
to localPlaybackStore, and update resolve/promote/prefetch plus key rewrite.
* feat(local-playback): LP-4–LP-6 offline UI, invalidation, and mediaDir
Offline Library loads pinned groups via library index; sync-idle invalidates
stale paths; Settings uses a single mediaDir with cache/library tier sizes.
* feat(local-playback): migrate legacy offline files to media/library layout
Move flat psysonic-offline downloads into nested media/library paths using
library index metadata, with retry on sync-idle when tracks are not yet indexed.
* feat(local-playback): simplify offline disk migration and restore Offline Library UI
Scan psysonic-offline on disk and relocate by library track id; restore pinSource
and cover art for migrated pins; add find_live_by_id for segment/key resolution.
* feat(local-playback): disk-first offline reconcile and fast library tier discovery
Reconcile library-tier index against on-disk files using candidate track IDs
instead of scanning the full catalog. Refresh Offline Library from disk on
open and focus so deleted folders drop out of the UI. Add Rust discover/prune
helpers and wire album/server reconcile through the unified path.
* fix(offline): resolve local playback URLs across server index-key variants
Offline Library play failed when library-tier files were indexed under a
host key while playback looked up only the active profile UUID. Use
findLocalPlaybackEntry for URL resolution, pin queueServerId to the card
server, and build play queues from tracks that still have on-disk bytes.
* fix(offline): playlist cards, playback from Offline Library, and local URL routing
Show playlists with name and quad/custom cover instead of the first track's
album artist. Build play queues with library-batch fallback and offline-only
server switch. Prefer library-tier URLs in playTrack; add playback-unavailable
toast and missing trackToSong import.
* fix(cache): ephemeral disk reconcile, empty-dir prune, and Storage UI
Sweep media/cache after eviction (orphan files, stale index, empty folders).
Settings: split media folder from cover cache; in-browser image cache lives
under Cover art cache with aligned columns; clear only IndexedDB images.
* feat(offline): show library disk usage in Offline Library header
Query media/library tier size on reconcile and display it in a right-aligned
stat block beside the page title and album count.
* fix(cache): defer unindexed hot-cache eviction; drop legacy offline size cap
Reconcile ephemeral cache without deleting files from other app instances;
evict unindexed hot-cache files oldest-first only when over hotCacheMaxMb.
Remove the hidden maxCacheMb gate and offline-full banner on album pages.
* feat(offline): play-all cache card and stable Offline Library grid rows
Add a shuffle-and-play card for all on-disk library pins plus hot-cache
tracks when buffering is enabled. Fix virtual row height for offline cards
and reserve the year line so grid rows no longer overlap.
* feat(offline): queue-cache grid card limited to media/cache
Replace the full-width play-all banner with a playlist-style grid tile.
Shuffle/enqueue only ephemeral hot-cache tracks when buffering is enabled,
not offline library pins.
* chore(licenses): regenerate bundled OSS list for 1.48.0-dev
Set GPL-3.0-or-later on workspace crates and extend cargo-about accepted
licenses so generate-licenses.mjs runs; refresh src/data/licenses.json.
* feat(favorites): auto-sync starred tracks into separate media/favorites tier
Keep manual Offline Library in media/library/ and favorites offline in
favorite-auto/index + media/favorites/ so toggling sync cannot purge
user-pinned bytes; playback resolves library before favorites.
* feat(favorites): compact offline toggle with disk icon and sync semaphore
Move control to the page header (disk + switch, tooltips); show red/yellow/green
LED when enabled instead of the full-width save-offline card.
* fix(favorites): trigger offline sync on star/unstar from anywhere
Hook star/unstar API so favorites offline reconcile runs globally (songs,
albums, artists); optimistic unstar removes local bytes; drop Favorites-page-only sync.
* fix(cache): skip hot-cache prefetch when favorites or library bytes exist
Treat favorite-auto tier like offline library for prefetch, stream promote,
and same-track replay so synced favorites are not duplicated in media/cache.
* fix(favorites): reconcile offline files on merged track union only
Dedupe artist/album/song stars into one target set per track id; drop eager
unstar deletes so overlapping favorites do not remove bytes still needed.
* feat(offline): add Favorites card to Offline Library
Mirror queue-cache card for favorite-auto tier with play, enqueue, and
navigation to Favorites on card click.
* feat(offline): show library+favorites disk total with icon breakdown
Sum media/library and media/favorites in the On disk widget and open an
icon popover on hover with per-tier sizes for screen readers and sighted users.
* feat(favorites): enable offline Favorites tab when auto-save is on
Keep Favorites in the sidebar when disconnected, land on /favorites without
manual pins, and load starred rows from the local library index.
* feat(favorites): cross-server offline browse with per-server covers
When auto-save is on, Favorites merges starred items from every indexed
server and syncs each server independently. Detail links carry ?server=
for offline album/artist pages; cover art resolves disk cache by entity
serverId instead of the active server only.
* feat(playback): mixed-server queue scope and cross-server favorites sync
Per-ref server identity for playback (URL index key in queue refs, profile
UUID for API): trackServerScope, playbackServer helpers, gapless/scrobble/covers
by playing ref. Remove cross-server enqueue block; remap queueItems on URL
remigration.
Favorites: star/unstar and favorite-auto sync target the owning serverId
(not only active); queueSongStar passes server through pending sync.
* fix(offline): suppress Subsonic calls during favorites and local playback
Add reachability guards so offline favorites browse, album detail, queue
sync, scrobble, and Now Playing metadata skip network when the server is
down or the track plays from psysonic-local. Load starred albums/tracks
from the library index only (not the full artist table), refresh favorites
from index first, and pass server scope in favorites navigation.
* fix(queue): export share and playlist save for active server only
Mixed-server queues now filter queue refs by the browsed server profile
before copying a share link or saving/updating a playlist from the toolbar.
* fix(offline): complete album pins and resume interrupted downloads
Prefer full getAlbum track lists when online so partial library index
does not truncate offline pins; refresh songs before pin from album
detail. Resume incomplete persisted pins after reconcile and reconnect,
cancel in-flight work on delete, and chunk library batch fetches past
100 refs.
* fix(offline): pin queue, queued UI, and re-pin after remove
Serialize album and playlist offline pins so parallel enqueue no longer
drops in-flight work. Show an explicit queued state on album and playlist
actions with dequeue on repeat click, sidebar tooltips for long labels,
and clear stale cancel flags so Make available offline works after remove.
* fix(offline): remove Offline Library cards without full page reload
Optimistically drop the deleted card from local grid state, show the
loading spinner only on first visit, and ignore stale disk refreshes so
pin updates no longer flash the whole library view.
* fix(offline): artist discography pin state and queue handling
Detect cached/queued/downloading from persisted album pins instead of
ephemeral bulkProgress, skip already offline or in-flight albums when
enqueueing discography, and show the correct hero button after revisit.
* fix(local-playback): address LP-1 review handoff (B1, M1–M7)
Harden media path sanitization and tier containment, align Rust/TS layout
fingerprints, serialize per-track downloads, and fix favorites re-enable,
multi-server debounce, prev-track promote key, now-playing reachability,
and ephemeral prefetch soft-skip for unindexed tracks.
* fix(offline): cancel in-flight favorites downloads on unstar
Abort Rust streams with the real favorites downloadId when sync is
rescheduled or disabled, and drop completed bytes that no longer belong
in the starred set so unstar does not leave orphan files on disk.
* fix(build): resolve TypeScript errors blocking prod nix build
Align mediaLayout with LibraryTrackDto camelCase, extend analysis-sync
reasons, fix OfflineLibrary grid cover typing, and tighten vitest mocks
so `tsc && vite build` passes under the flake beforeBuildCommand.
* fix(rust): satisfy clippy too_many_arguments for CI
Bundle offline-library analysis and local path/migration helpers into
parameter structs so `cargo clippy -D warnings` passes on the branch.
* fix(settings): show correct hot-cache track count in Buffering section
Count ephemeral localPlayback rows instead of prefix-matching index keys,
which always missed host:port server segments and showed zero tracks.
* fix(media-layout): align truncation threshold on code points (M1)
Rust sanitize_and_truncate_segment now uses char count like TS so long
non-ASCII metadata does not diverge layout fingerprints; add Cyrillic
parity tests and clarify ephemeral cold-miss doc on download_track_local.
* fix(test): use numeric cachedAt in hotCacheStore count test
Align test fixture with LocalPlaybackEntry type so tsc passes in CI.
* docs: add CHANGELOG and credits for offline experience PR #1008
* feat(offline): auto-sync manually cached playlists when track list changes
Re-download new tracks and prune removed ones for playlist pins only, triggered
from updatePlaylist, playlist detail load, smart-playlist polling, and reconnect.
* docs: note cached-playlist sync in CHANGELOG and credits for PR #1008
* fix(offline): exclude smart playlists from manual offline cache and sync
Hide cache-offline for psy-smart-* playlists, block download/sync paths, and
document the distinction in CHANGELOG.
* feat(offline): auto-sync cached albums and artist discographies
Generalize pinned playlist reconcile into pinnedOfflineSync so manually
pinned albums and artist discographies re-download added tracks and prune
removed ones on reopen, reconnect, and catalog changes.
* feat(offline): split pinned sync triggers by pin kind
Album and artist pins reconcile after library index sync and reconnect;
regular playlists reconcile hourly and on in-app playlist edits only.
Remove reconcile-on-open for album, artist, and playlist detail views.
* fix(offline): address PR #1008 review (N1, tests, pin queue)
Scope playlist reconcile to the owning server via getPlaylistForServer.
Add artist discography and mixed-server playlist tests; dedupe pending
sync jobs; skip pinTasks overwrite during active downloads.
|
||
|
|
40db6b08d2 |
chore(playback): remove Preload Next Track setting (#1007)
* chore(playback): remove Preload Next Track setting The configurable next-track RAM preload duplicated hot cache and is obsolete now that ranged streaming starts playback sooner. Gapless and crossfade still use the internal audio_preload backup when hot cache is off. * docs: add CHANGELOG entry for Preload Next Track removal (PR #1007) |
||
|
|
a66d932afe |
fix(preview): Symphonia format sniff and ranged stream startup (#1006)
* fix(preview): Symphonia format sniff and cluster member stream URLs Resolve preview container hints from HTTP headers, Subsonic suffix, and magic-byte sniff after Symphonia 0.6. Route preview streams through clusterBrowseServerId like main playback; guard CoverArtImage when the preview cover ref is still loading. * fix(preview): adapt Symphonia sniff branch for main without cluster routing Keep formatSuffix and cover-ref guards from the cluster work; use buildStreamUrl on the active server instead of clusterBrowseServerId. * fix(preview): use ranged HTTP so preview starts without full-file download Open preview via RangedHttpSource when the server supports byte ranges; fall back to buffered download otherwise. Gate in-memory probe with ProbeSeekGate so Symphonia 0.6 does not scan the entire file before audio. * docs: CHANGELOG and credits for preview fix PR #1006 * chore: drop PR #1006 from settings credits (minor fix) * fix(preview): allow clippy too_many_arguments on audio_preview_play formatSuffix pushed the Tauri command to 8 args; matches other IPC commands. |
||
|
|
1c23305887 |
feat(queue): add Timeline display mode (#1004)
* 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) |
||
|
|
dc4eef1a97 |
feat(fullscreen-player): rebuilt static fullscreen player (#1001)
* feat(player): static media-center fullscreen player (v1) New lean fullscreen player: sharp full-bleed background (artist photo, cover fallback), no blur / no continuous animations. Bottom-left big cover bottom- flush with a full-width semi-transparent text bar (title with queue position, artist, year/genre + rating stars, next-up); control row of transport · center time · actions; full-width seekbar; live clock. Only the seekbar, time readout and clock update at runtime, each owning its state (no per-tick re-render). Reuses FsSeekbar/FsPlayBtn, the cover/artist hooks, idle-fade and queue helpers. Wired in AppShell; old player kept for A/B. * feat(fullscreen-player): true waveform seekbar instead of thin bar Replace FsSeekbar with the real WaveformSeek (cucadmuh's idea). The taller canvas grows the bottom cluster upward, shifting the info/control rows up. Height clamped to 32-52px. * feat(fullscreen-player): up-next popover + control-bar styling - Queue button opens a semi-transparent 'Up next' popover anchored bottom right; clicking a row jumps to that queue item. - Larger bottom-right action buttons. - Control row gets its own darker semi-transparent bar (touches the info bar above) for contrast; play button matches the plain transport buttons (no white circle, same size). * feat(fullscreen-player): high-res (2000px) background cover Fetch the full-screen background cover at the 2000px tier via the existing on-demand fullRes path (same getCoverArt fetch, saved as a high-res WebP outside the backfill pipeline) instead of the low-res 500px pipeline tier. usePlaybackCoverArt now forwards a fullRes option to useCoverArt. * feat(fullscreen-player): scrolling lyrics overlay + control-bar polish - Lyrics button next to Queue toggles a centered, dark semi-transparent scrolling-lyrics overlay (reuses FsLyricsApple). - Control bar darkened to match the lyrics overlay (0.88). - Close button sized down to harmonize with the clock. * feat(fullscreen-player): make rating stars clickable The rating stars next to the year were display-only. Wire them to queueSongRating (same path as the player bar / context menu): click to set, click the current value to clear, with a hover preview. Keeps the lucide outline look to match the rest of the control bar. * fix(fullscreen-player): stable, high-res album cover The foreground cover was keyed per track, so Navidrome's per-track `mf-<id>` coverArt re-triggered the distinct-disc heuristic and reloaded the cover on every song change within the same album. Key it on albumId (via useAlbumCoverRef) so it stays put while the album is unchanged. It also used the low-res tier; reuse the fullRes 2000px cover already fetched for the background so the foreground is crisp and both share a single fetch/decode. * refactor(fullscreen-player): remove the old player and its settings The static rebuild is now the only fullscreen player. Delete the old FullscreenPlayer component, its old-only parts (FsArt, FsPortrait, FsSeekbar, FsLyricsRail, FsLyricsMenu, useFsDynamicAccent) and its test. Remove the now-orphaned settings (showFullscreenLyrics, fsLyricsStyle, showFsArtistPortrait, fsPortraitDim) along with the Appearance 'Fullscreen player' section and its search entry. The new player always shows the artist photo with cover fallback and has its own lyrics toggle. Shared building blocks used by the static player (FsLyricsApple, FsPlayBtn, FsClock, FsTimeReadout, FsQueueModal, useFsArtistPortrait) are kept. * i18n(fullscreen-player): translate the new player's strings Wire the static player's previously English-only strings through i18n across all 9 locales: now-playing label, track position, up-next label, queue/lyrics/shuffle controls, and the queue overlay (title, empty, close). Reuses existing queue.title / common.close keys; adds six new keys to the player namespace. * docs(changelog): note fullscreen player rebuild (#1001) |
||
|
|
c674e4515b |
feat(audio): migrate Symphonia 0.5 -> 0.6 (#999)
* feat(audio): migrate Symphonia 0.5 -> 0.6 Port the audio + analysis pipeline to the Symphonia 0.6 API: - bump symphonia to 0.6 and symphonia-adapter-libopus to 0.3; drop rodio's symphonia-all feature to avoid a duplicate symphonia-core 0.5 - remove the local symphonia-format-isomp4 0.5 patch and rely on upstream 0.6 - switch codec registries to register_enabled_codecs / make_audio_decoder with AudioCodecParameters + AudioDecoderOptions - rework decode.rs SizedDecoder and analysis decode loops for the new AudioDecoder trait, GenericAudioBufferRef, Time/Timestamp newtypes, and next_packet() -> Result<Option<Packet>> Streaming regression fixes: - ProbeSeekGate hides seekability during probe for non-MP4 progressive streams so Symphonia 0.6's trailing-metadata scan no longer forces a full download before ranged FLAC/MP3/OGG playback can start - guard the streaming probe() with a 20s timeout on a worker thread so a stalled ranged source (e.g. right after a server switch) can no longer hang playback start until a player restart; add probe start/done diagnostics * docs(changelog): note Symphonia 0.6 migration and streaming fixes (#999) Add CHANGELOG entries (Changed + Fixed) and a CONTRIBUTORS line for the Symphonia 0.6 migration, ranged-stream start-latency fix, and probe timeout. * test(audio): cover streaming probe path and ProbeSeekGate Add unit tests for SizedDecoder::new_streaming (success + garbage) and ProbeSeekGate (seekability toggle, byte_len, read/seek passthrough) to restore decode.rs above the 70% hot-path coverage gate (67.3% -> 79.4%). |
||
|
|
c39465dfad |
feat(sidebar): toggle to pin Now Playing to the top (#1000)
* feat(sidebar): add toggle to pin Now Playing to the top The fixed Now Playing entry can now be pinned to the very top of the sidebar (above the Library label) instead of sitting above the bottom spacer. Adds a persisted `nowPlayingAtTop` setting (default off, so existing layouts are unchanged) and a toggle in the sidebar customizer next to the Mix-navigation split. The entry stays non-hideable and keeps its playing indicator in both positions. * i18n(settings): Now Playing-at-top toggle strings (9 locales) * docs(changelog): note Now Playing top toggle (#1000) |
||
|
|
5f345dc7aa |
fix(settings): restore full border on active server card (#998)
* fix(settings): restore full border on active server card The active server card mixed the `border` shorthand with borderTop/ borderBottom longhands in one inline style object. React clears the unset longhand sides on mount, so the top and bottom borders fell back to the subtle base border while only the left/right accent border showed. Drive the drag drop-target indicator via an inset box-shadow instead, leaving the border shorthand to apply on all four sides. * docs(changelog): note active server card border fix (#998) |
||
|
|
d1320ea2c8 |
chore(deps): refresh npm and Rust dependencies (#997)
* chore(deps): bump npm patch/minor dependencies Refresh frontend lockfile for React, Vite, Vitest, i18next, axios, Tauri plugins, and related type packages within existing semver ranges. * chore(deps): bump Rust patch dependencies Update id3 to 1.17, reqwest to 0.13.4, and align root zbus with psysonic-audio 5.16. * chore(deps): upgrade jsdom to v29 Major test-environment bump; all Vitest suites still pass and npm audit is clean. * chore(deps): upgrade sysinfo 0.39 and zip 8 Align root sysinfo with psysonic-syncfs and migrate backup archives to zip 8. mach2 0.6 deferred — cpal/rodio still require ^0.5. * chore(nix): sync npmDepsHash with package-lock.json * docs(changelog): note dependency refresh (PR #997) * docs(changelog): move deps note to [1.48.0] section --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
8ca16c1971 | Update CHANGELOG.md (#994) | ||
|
|
7a2f937984 |
docs(changelog): thank the Discord community in 1.47.0 notes (#993)
Add a thank-you note at the top of the 1.47.0 release notes for the Discord community's support, quality checks, bug reports and general collaboration, with the Discord invite link. |
||
|
|
2f50a1bade |
fix(tray): stable tray icon id (no KDE duplicate pile-up on toggle) (#991)
* fix(tray): stable tray icon id so KDE toggle stops piling up duplicates TrayIconBuilder::new() assigns a fresh id on every rebuild. On KDE (StatusNotifierItem) each new id registers a new item and the stale ones linger in the hidden-icons list, so toggling the tray icon off/on stacked up duplicate entries. Build with a constant id so every rebuild reuses the same item. * docs: changelog for tray icon duplicate fix (#991) |
||
|
|
ec2bee1400 |
fix(audio): cap pipewire-alsa client latency on Linux (#862) (#990)
* fix(audio): cap pipewire-alsa client latency on Linux (#862) On some Linux setups the pipewire-alsa bridge negotiates a multi-second output buffer, so play/pause/seek/volume only take effect once it drains (~10-20 s). cpal's buffer-size clamp is ignored by those bridges. Set PIPEWIRE_LATENCY=256/48000 before the audio stream opens to cap the client node latency — the reporter confirmed this makes the controls instant. Linux-only, no-op without PipeWire, and a user-set value is left untouched. * docs: changelog for pipewire latency fix (#990) |
||
|
|
ca502ad833 |
fix(player): stable playbar clocks when showing remaining time (#987)
* chore: restore PR order in CHANGELOG [1.47.0] Fixed section Re-sort ### blocks ascending by PR number per release-note placement rules after out-of-order Discord-fix batch inserts. * fix(player): stable playbar clocks when showing remaining time Pad seekbar time strings to a fixed width so ticking remaining time does not resize WaveformSeek via ResizeObserver; tighten clock-to-waveform spacing and keep the toggle icon inline. * docs: CHANGELOG and credits for playbar remaining-time fix (PR #987) * docs: CHANGELOG entry for playbar remaining-time waveform fix (#987) * chore: drop settingsCredits entry for minor playbar fix (#987) * docs: move #987 CHANGELOG entry to end of [1.47.0] Fixed section |
||
|
|
6da98e476b |
fix(ui): New badge flicker on mainstage album rail hover (#986)
* fix(ui): stop New badge flicker on mainstage album rail first hover Dim cover via ::before under badges; keep play overlay transparent and avoid visibility toggles on rail action buttons. * docs: CHANGELOG for mainstage New badge hover fix (PR #986) * fix(ui): keep New badge above rail cover zoom (match grid stacking) Use contain:paint + translateZ layering like New Releases grid so scaled cover art does not paint over the badge during hover animation. * fix(ui): global album cover badge stacking for all rails and grids Move img/badge/overlay z-index into album-card.css; stop album-grid from resetting card transform or duplicating cover rules that hid New badges. * docs: CHANGELOG — badge stacking applies to all album rails * fix(ui): restore rail play buttons above scaled cover layer Raise play overlay and badge z-index on horizontal rails so WebKit does not paint zoomed cover art over hover controls after the New badge stacking fix. |
||
|
|
631330ebfa |
fix(search): load album cover for song results (#984)
* fix(search): load album cover for song results Live Search song rows carried the per-track `mf-…` coverArt id from search3, which the cover pipeline does not resolve, so the thumbnails went blank. Album rows worked because they use the album-scoped `al-…` id. A song's search thumbnail is its album cover anyway — derive it from the albumId so it loads, and show a music glyph when there is no album. * docs: changelog for search song cover fix (#984) |
||
|
|
19fdba006a |
fix(search): local index rejects FTS syntax in live search queries (#983)
* fix(search): reject FTS syntax chars in local index live search queries FTS5 treats `=` and similar characters as query syntax, so tokens like 1=2 produced unrelated prefix hits. Skip FTS for unsafe tokens instead. * docs: CHANGELOG for local index FTS syntax query fix (PR #983) * fix(search): reject syntax junk in server search3 and live search race Share FTS-safe token guard with search3; skip network invoke for ** and similar queries; show empty dropdown without misleading source badge. * fix(search): allow censorship stars in queries, reject wildcard-only tokens Block ** and **** but keep ***Flawless-style title searches working in local FTS and search3. |
||
|
|
d43a8c6691 |
fix(ui): square song-rail nav buttons (#982)
* fix(ui): square song-rail nav buttons to match other rails The song rail's prev/next (and reroll) buttons were circular while every other rail uses the square rounded-rect nav button. Align them. * docs: changelog for square song-rail nav buttons (#982) |
||
|
|
b0f35eabc9 |
fix: usable layout when the window is small (#981)
* fix(layout): collapse browse toolbar to icons on compact width On a narrow window the labelled filter buttons wrapped into several rows and, being a fixed-height sticky header, starved the album grid below them down to a clipped strip. Wrap each toolbar label in a hideable span and drop to icon-only in compact (mobile) mode; icons keep their tooltip and aria-label so the action stays discoverable. * fix(window): raise minimum window size to 520x640 The old 360x480 floor let the window shrink far past the point the layout holds together. Floor it at a laptop-friendly size where the compact layout (icon toolbar + scrollable lists) still works. * fix(player): scale mobile cover to fit short windows The cover width was viewport-derived with no height bound, so on a short window the square grew past its slot and overlapped the title. Bind it to the shrinkable wrap via max-height and keep it square with auto sizing. * docs: changelog for small-window layout fixes (#981) |
||
|
|
f3eb58c707 |
fix(playlist): suggestion rows match the normal track row (#980)
* fix(playlist): suggestion rows match the normal track row The Suggested Songs table left the Favorite and Rating columns empty and rendered only a single artist, so multi-artist tracks lost their split and the blank columns read as a gap between Genre and Duration. - Extract a shared PlaylistArtistCell that splits the OpenSubsonic artists array into individually navigable links; use it for both the playlist rows and the suggestions so a track reads the same before and after it is added. - Show the real favorite heart and star rating in suggestion rows (global song operations), seeded from the song's own starred/userRating, removing the empty-column gap. * docs: changelog for suggestion row parity (#980) |
||
|
|
a7c79ee210 |
fix: show album artist on featured compilation cards (#979)
* fix: show album artist on featured compilation cards The 'Also featured on' cards are synthesised from search3 child songs, which only read the flat `albumArtist` field. Compilation children leave that empty — the album-artist credit lives in OpenSubsonic's structured `albumArtists` / `displayAlbumArtist` — so the card fell back to the '—' placeholder. Carry the structured credit (and the display string) onto the synthesised album so it resolves a name (and stays navigable when the server supplies an id). * docs: changelog for featured-compilation artist credit (#979) |
||
|
|
47e16ebfef |
fix: RC3 queue link underline, genre album artist split, Artists "Other" bucket (#977)
* fix: RC3 queue link underline, genre album artist split, Artists "Other" bucket Three zunoz reports, one change: - Queue now-playing card: the artist and album links now underline on hover (not just recolour), matching clickable names everywhere else. The album link sits on .queue-current-sub itself; artist links are nested .is-link spans — both selectors covered. - Genre album cards split multi-artist credits into individual links like the rest of the app. Root cause was the local-index genre query hardcoding NULL for the album raw_json column, so OpenSubsonic artists[] never reached the card and it fell back to the flat "A • B" single link. Select a.raw_json instead (parity with the All Albums / advanced-search album queries). - Artists page alphabet index: # is now digits-only, and a new "Other" bucket collects accented Latin (Æ/Ø/Å…) and non-Latin scripts (CJK, Cyrillic, …) that previously fell into the # catch-all. Adds artistBucketKey/compareBuckets helpers (unit-tested), an OTHER bucket sorted last, and the artists.other label across 9 locales. * docs(changelog): queue link hover, genre card artist split, Artists Other bucket (#977) |
||
|
|
88df194808 |
fix(tracks): RC3 layout regressions + multi-artist links (#976)
* fix(tracks): RC3 layout regressions + multi-artist links - Restore vertical rhythm on the Tracks hub: the header/hero/rails/browse sections sat two wrapper divs below .tracks-page so its flex gap never reached them and they collapsed together. New .tracks-hub-stack re-applies the gap, fixing the tagline-touching-hero and Random-Pick-overlapping-hero spacing (the rail's nav buttons no longer ride into the box above). - Stop the sticky browse-table header going transparent on hover: its base background became opaque but the :hover rule still forced transparent, so rows bled through. Header now keeps its card background on hover. - Widen the Duration column (56px -> 72px; 56 -> 64 on mobile) so the "DURATION" header no longer clips/overruns. - Split multi-artist tracks into individually clickable artist links in both the "Track of the moment" hero and the browse list rows (OpenSubsonic artists[] with single-artist fallback), matching the album tracklist. * docs(changelog): Tracks RC3 spacing, Duration, header hover, multi-artist (#976) |
||
|
|
908c349cfd |
fix(mainstage): rename Home Page setting, start-page fallback + empty state (#975)
* fix(mainstage): rename Home Page setting, add start-page fallback + empty state - Rename "Home Page" personalisation section to "Mainstage" across 9 locales so the heading matches the sidebar entry; add "mainstage" search keyword. - Index route "/" now redirects to the first visible library item when the Mainstage sidebar entry is hidden, instead of stranding the app on a blank page. New pure resolveStartRoute() mirrors sidebar order + nav-mode gating. - Show a guided empty state on Mainstage when every section is toggled off, with a CTA into Settings -> Personalisation. - Unit tests for resolveStartRoute. * docs(changelog): Mainstage rename, start-page fallback + empty state (#975) |
||
|
|
3be8c367dd |
Fix queue handle cursor and Favorites column sorting (#974)
* fix(ui): pointer cursor on the queue collapse handle The round handle is a click-to-collapse button but showed the col-resize cursor like the seam strip. Use a pointer on the handle; the seam keeps col-resize and a real drag still switches the body cursor to col-resize. * fix(favorites): make Plays, Last Played and BPM columns sortable The header marked these columns sortable (pointer cursor) but handleSortClick gated on a separate, narrower column set, so clicks did nothing. Both the comparator and the header now share one SORTABLE_COLUMNS source, so the affordance and the behaviour can't drift apart again. * docs(changelog): queue handle cursor + favorites column sorting (#974) |
||
|
|
0d479f3bfa |
Fix Random Mix audiobook exclusion: click area and false matches (#973)
* fix(random-mix): limit exclusion toggle click area to checkbox and title The audiobook exclusion was a full-width label wrapping the checkbox, title and description, so clicking empty space or the description text toggled it. Make only the checkbox and its title clickable; the description and surrounding space are no longer hit targets. * fix(random-mix): stop excluding Thriller and Fantasy as audiobook genres These keywords match regular music (e.g. Trance/Metal genre tags, a track titled "Thriller") because the exclusion checks genre, title, album and artist by substring, dropping a few legit songs per mix. Remove both from the audiobook keyword list. * docs(changelog): random mix audiobook exclusion fixes (#973) |
||
|
|
c119a32277 |
Unify button tooltips across the app (#972)
* feat(tooltip): 2s open delay and shared tooltipAttrs helper Add a 2s hover open delay in TooltipPortal (single behaviour source) so tooltips no longer flash on quick pointer passes; hiding stays immediate. Add tooltipAttrs() to pair data-tooltip with a matching aria-label for buttons touched in the unification work. Covered by Vitest. * feat(tooltip): lower open delay to 1s 2s felt too long in testing; 1s gives the same anti-flash behaviour without making intentional hovers wait. * feat(tooltip): action tooltips on the artist overview Add tooltips describing the action to Last.fm, Wikipedia, Play All, Shuffle and Radio. Shuffle/Radio now show a tooltip on desktop too, not just mobile. Strings added to all 9 locales. * feat(tooltip): action tooltips on the album overview Add tooltips describing the action to the desktop Play, Artist Bio and Download (ZIP) buttons, matching the mobile layout. Strings added to all 9 locales. * feat(tooltip): action tooltips on the All Albums toolbar Add tooltips describing the action to the sort, year and genre filter buttons. SortDropdown gains an optional tooltip prop; the year and genre filter components carry their own, so the tooltips also appear on the other browse pages that reuse them. Strings added to all 9 locales. * feat(tooltip): action tooltips on song-list rows Add Play and Add-to-queue tooltips to the per-row icons in SongRow, used by the Tracks browse list, Search and Advanced Search. Also localizes the aria-labels, which were hardcoded English. New common.addToQueue in all 9 locales. * fix(tooltip): uniform tooltip placement on the Artists toolbar The favourite and multi-select buttons forced tooltips below while the view-mode buttons auto-flipped above, so the row looked inconsistent. Pin the view-mode buttons below too, matching the rest of the row and the Albums toolbar. * feat(tooltip): clarify and align the Advanced Search scope row Add a leading "Search in:" label and per-chip tooltips so the All/Artists/Albums/Songs row reads as a scope limiter. Drop the forced below-placement on the small star filter (used only here) so the favourites chip flips with the others instead of sitting alone below. Strings added to all 9 locales. * docs(changelog): tooltip unification (#972) |
||
|
|
82c414d7bc |
fix(playlists): Smart Playlist editor theme, toggles, and exclude-all-genres (#970)
* fix(playlists): Smart Playlist editor theme, toggles, and exclude-all-genres Replace native sort select with CustomSelect, fix mode-button layout shift, color-code included vs excluded genres, collapse exclude-all to untagged rule, and handle empty smart playlists without false "not found". * docs: CHANGELOG and credits for Smart Playlist editor fix (PR #970) * chore(credits): drop minor fix entries from PR #958 onward |
||
|
|
c9b2d140d9 |
fix(ui): shrink-wrap floating player bar instead of full-width strip (#969)
* fix(ui): shrink-wrap floating player bar instead of full-width strip The bar used fixed left and right insets, which stretched its background across the whole main column. Center it with max-content width so only the pill-shaped controls are painted, and soften the drop shadow. * docs: note PR #969 in changelog and settings credits |
||
|
|
e8962c21ab |
fix(settings): improve in-page search matching and coverage (#968)
* fix(settings): improve in-page search matching and coverage Index AudioMuse and individual shortcut rows, tighten fuzzy matching so junk queries return no hits, and scroll to the parent subsection when a shortcut result is selected. * docs: note PR #968 in changelog and settings credits |
||
|
|
cd47a4b0fa |
fix(player): clamp custom delay input and align preview with armed timer (#967)
* fix(player): clamp custom delay input and align preview with armed timer Reject absurd custom minute values that overflow setTimeout, share one delay helper between the modal preview and schedule actions, and refresh countdown ticks when a new deadline is armed. * docs: note PR #967 in changelog and settings credits |
||
|
|
f3a0b3f7af |
fix(artist-detail): Last.fm/Wikipedia/Favorite hover keeps button border (#966)
* fix(artist-detail): keep ext-link border visible on hover Hover used --border-subtle, which on Catppuccin matches --bg-card and visually erased the rim while only the fill changed. Match btn-surface: --ctp-surface1 border, --ctp-overlay0 on hover. * docs(changelog): credit zunoz on Psysonic Discord for PR #966 * fix(playlists): tooltips on Play/Add Songs and song count pluralization Add data-tooltip to Play and Add Songs in playlist hero; switch playlists.songs to count-based _one/_other forms (was {{n}} without i18next plural suffix, breaking spacing and singular). * fix(playlists): render BPM and optional cols in Suggested Songs rows PlaylistSuggestions shared column headers with the main tracklist but its row switch omitted bpm, genre, playCount, and lastPlayed. |
||
|
|
5990d84f5a |
fix(random-mix): keyword blocks and scoped genre list on Build a Mix (#965)
* fix(random-mix): honor keyword blocks and scope genre list to library Keyword filter was gated behind the audiobook exclusion checkbox, so blocked artists still appeared after Remix. Genre Mix now loads genres via fetchGenreCatalog (scoped index / library filter) instead of raw getGenres; show empty state when all tracks are filtered out. * docs(changelog): credit zunoz on Psysonic Discord for PR #965 |
||
|
|
e76dac87ae |
fix(home): scope Because you listened rail to sidebar library (#964)
* fix(home): scope Because you listened rail to sidebar library Similar-artist album picks used getArtist without library filtering; session cache also ignored musicLibraryFilterVersion. Filter picks to the scoped album set and key cache/reserve by filter version. * docs(changelog): credit zunoz on Psysonic Discord for PR #964 * test: satisfy SubsonicAlbum required fields in new unit tests Fix tsc --noEmit CI: songCount and duration are required on SubsonicAlbum. |
||
|
|
be21f7834f |
fix(composers): hide performer-only artists with zero composer credits (#963)
* fix(composers): drop Navidrome role rows with zero composer albums Navidrome can list performer-only artists under role=composer with stats.composer.albumCount 0; filter them out of the Composers catalog so search no longer surfaces ghost entries like Apollo 440. * docs(changelog): credit zunoz on Psysonic Discord for PR #963 |
||
|
|
c683b5e37b |
fix(cards): selection ring clipping on browse grids (WebKitGTK) (#962)
* fix(artists): inset selection ring on grid cards, stop composer hover clip Artist multi-select used a positive outline-offset that clipped in the first grid row and sat outside the card border on hover. Match album cards with an inset ring; drop composer-card hover translateY that sheared the top edge in the in-page scrollport. Reported by zunoz (v1.47.0-rc.3). * fix(cards): selection ring via inset ::after overlay on WebKitGTK grids Replace outline-based multi-select rings on album/artist/playlist cards with the same inset ::after box-shadow pattern used for card focus rings (card.css) — avoids clipping and the 1px gap vs the inner border on overflow:hidden tiles in All Albums and related browse grids. * docs: note browse grid selection ring fix in CHANGELOG (PR #962) * docs(changelog): credit zunoz on Psysonic Discord for PR #962 |
||
|
|
a07e8e9593 |
fix(composers): keep role-split names in page-local search (#961)
* fix(composers): keep role-split names in page-local search Composers browse already loads the Navidrome role-scoped catalog; scoped search now filters that list instead of replacing it with generic artist index/search3 hits that merge split credits into one joined name. Reported by zunoz on the Psysonic Discord (v1.47.0-rc.3). * docs: note Composers scoped-search fix in CHANGELOG (PR #961) |
||
|
|
4c70408bd6 |
fix(now-playing): split artist links and About the Artist tabs (#960)
* fix(now-playing): split artist links and About the Artist tabs OpenSubsonic artists[] now drives per-artist navigation on Now Playing hero and the queue current-track row (matching player bar). About the Artist loads bio for each performer via tabs when a track has multiple artist ids; queue Info uses the primary ref for bio/tour fetch. Reported by zunoz on the Psysonic Discord (v1.47.0-rc.3). * docs: note Now Playing multi-artist fix in CHANGELOG (PR #960) |
||
|
|
a142bb1dab |
fix(albums): scope All Albums genre filter to selected music library (#959)
* fix(albums): scope All Albums genre filter to selected music library When the sidebar narrows to one Subsonic library, the genre popover fell back to server-wide getGenres() instead of the scoped local index catalog. Load genre options from libraryGetGenreAlbumCounts for library-only scope and enable the catalog path whenever the index is on and a library is selected. * docs: credit All Albums genre filter fix (PR #959, report zunoz) * chore: drop settingsCredits entry for small genre-filter fix |
||
|
|
75e2c7f9c6 |
fix(player): persist player prefs outside quota-bound queue blob (#958)
* fix(player): persist volume/repeat in dedicated localStorage key Player prefs were bundled with the full queue blob in psysonic-player; after thin-state #872 a large queue can exceed the quota and safeStorage silently drops every write, so volume and repeat mode stopped surviving restarts. Move them to psysonic_player_prefs, gate main-blob writes until rehydrate, and sync volume to the Rust engine on startup. * fix(player): split queue visibility and Last.fm cache from main persist blob Move isQueueVisible and lastfmLovedCache to dedicated localStorage keys so they keep saving when psysonic-player hits the quota on large queues. Include the new keys in settings backup export. * docs: note player prefs persist fix in CHANGELOG and credits (PR #958) |
||
|
|
40932d28e2 |
UI/CSS fixes: focus rings, search fields, column dropdown, theme accordion (#954)
* fix(focus): keyboard focus ring no longer clipped by overflow or cover The global :focus-visible ring used a positive outline-offset, so it was drawn outside the element and clipped by any ancestor with overflow:hidden or a scroll container (cards, rails, player bar, queue strip). Draw it inset instead via shared --focus-ring-* variables (single source). Cards draw the ring as an overlay above the cover, since a cover's own stacking context (transform/contain for render stability) would otherwise paint over an inset outline. Dracula now only sets --focus-ring-color. * refactor(focus): fold scattered focus-ring overrides onto shared knob Six components declared their own :focus-visible outline (mostly an exact copy of the old global ring). Remove the redundant ones so they inherit the global inset ring; keep the custom-coloured ones (genre pill, playback-delay modal) but source width/offset from --focus-ring-*; move the because-card ring to the central card focus ring (it has a cover and needs the lifted treatment). * fix(settings): round theme accordion inner box to match its section The theme picker's inner accordion had square corners, so the first (open) group header ran flush into the rounded Theme card. Give .theme-accordion a border-radius + overflow:hidden so its top/bottom corners continue the parent card's rounding, matching the other settings sections. * fix(search): unify search fields to one look Live-search, Help and Settings search differed (pill vs rounded-rect, glow vs plain border-change, mismatched backgrounds). Align them on the canonical input look: radius-md, ctp-base background, accent border + soft accent-dim focus glow. Drop the live-search pill radius, and suppress the input's own inset ring so only the outer cluster glow shows (no double ring). * fix(tracklist): column picker menu no longer clipped on short lists The column-visibility dropdown was an absolutely-positioned menu inside the tracklist, so a short list (e.g. a one-song Favorites view) clipped it via the ancestor's overflow box. Render the menu in a portal to <body> with fixed positioning anchored to the trigger (flips above when there's no room below), following on scroll/resize. Outside-click + Escape close now live in the shared TracklistColumnPicker (the menu is portalled out of the wrapper, so the old wrapper-only check would have closed it on every in-menu click). Fixes albums, playlists and favorites in one shared place. Adds a behaviour test. * docs(changelog): UI/CSS fixes pass (#954) |
||
|
|
cc04a0c93d |
Distinct circular song cards with jump-to-album badge (#953)
* feat(tracks): distinct circular song cards with jump-to-album badge Single-track cards looked identical to album tiles, so clicking the body read as album behaviour even though it starts playback. Give them a round vinyl-style cover (square stays album-only) via a shared .cover-circle utility, and add a 'To album' badge under the artist that navigates to the track's album. Card click still plays; the badge is the explicit nav path. * i18n(tracks): add toAlbum label across 9 locales * docs(changelog): track-card redesign + to-album badge (#953) |
||
|
|
47832632fd |
fix(cover): follow connect-URL flips in library cover backfill (#952)
* fix(cover): follow connect-URL flips in library cover backfill The native cover backfill was configured once with a snapshot of the runtime connect URL. When a laptop moved off the LAN, the smart endpoint switch flipped the sticky connect URL to the public address (playback/UI covers rebuild it per request and followed it), but backfill kept fetching from the now-unreachable local address — flooding the log with "error sending request" failures. Make the connect cache observable (notify on effective flips) and have the backfill hook reconfigure when the resolved URL changes, forcing a pass so the .fetch-failed backoff from the stale address is cleared and those covers retry on the reachable endpoint. * docs(changelog): note cover backfill endpoint-switch fix (PR #952) * fix(cover): abort stale backfill pass + rerun on connect-URL flip Frontend reconfigure alone didn't fully cover the boot case: at startup the first backfill pass starts on the primary (LAN) URL before the reachability probe resolves, so when the probe flips to public the forced rerun was dropped by the pass_running guard and the slow LAN pass (every cover timing out) ran to completion with nothing re-running on the reachable address. set_session now bumps a session generation; the running pass checks it on every focus gate and abandons promptly when the URL flips (same server_index_key, new rest_base_url). try_schedule_full_pass records a rerun when a pass is in flight and drains it once the abandoned pass returns, so a fresh forced pass runs on the new address. * fix(cover-backfill): resolve connect URL per fetch instead of baking it into the queue The backfill worklist no longer carries a URL. Each cover fetch reads the current reachable address live from a single worker cell, so a LAN↔public flip is honoured even by the pass already in flight — its remaining covers download against the new endpoint without aborting/rebuilding the worklist. - Drop rest_base_url from CoverBackfillSession; add live base_url cell read in ensure_one. Remove the session-generation abort machinery (no longer needed). - New lightweight library_cover_backfill_set_base_url command pushes the URL on every connect-cache flip; a real change clears the stale .fetch-failed backoff and runs a forced pass so covers that timed out on the old address retry. - Split useLibraryCoverBackfill into a configure effect (server/creds/strategy) and a flip effect that only pushes the URL. - Keep a single rerun_pending flag for the boot case (flip mid-pass), since the finished pass re-arms the idle gate. |