mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
fc34a0ec59
* 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.
392 lines
30 KiB
TypeScript
392 lines
30 KiB
TypeScript
import { isNewer } from '../utils/componentHelpers/appUpdaterHelpers';
|
||
|
||
// Credits list rendered on the Settings → System tab. Update via PR when adding a contributor.
|
||
const CONTRIBUTOR_ENTRIES = [
|
||
{
|
||
github: 'jiezhuo',
|
||
since: '1.21',
|
||
contributions: [
|
||
'Chinese (Simplified) translation',
|
||
],
|
||
},
|
||
{
|
||
github: 'nullobject',
|
||
since: '1.22.0',
|
||
contributions: [
|
||
'Seek debounce & race condition fix (PR #7)',
|
||
'Waveform seekbar stability on position update (PR #8)',
|
||
],
|
||
},
|
||
{
|
||
github: 'trbn1',
|
||
since: '1.22.0',
|
||
contributions: [
|
||
'Replay Gain metadata propagation (PR #9)',
|
||
'songToTrack() — unified track construction across all sources',
|
||
],
|
||
},
|
||
{
|
||
github: 'nisarg-78',
|
||
since: '1.29.0',
|
||
contributions: [
|
||
'Click-to-seek in synced lyrics (PR #38)',
|
||
'Volume scroll wheel on volume slider (PR #38)',
|
||
'Lyrics line visual states: active / completed / upcoming (PR #38)',
|
||
'Queue auto-scroll to keep upcoming tracks in view; fixed re-renders from currentTime in QueuePanel (PR #115)',
|
||
],
|
||
},
|
||
{
|
||
github: 'JulianNymark',
|
||
since: '1.29.0',
|
||
contributions: [
|
||
'OGG/Vorbis container support via symphonia-format-ogg (PR #42)',
|
||
'Themed toast notifications for audio playback errors (PR #43)',
|
||
'Human-readable audio error messages (PR #44)',
|
||
],
|
||
},
|
||
{
|
||
github: 'zz5zz',
|
||
since: '1.32.0',
|
||
contributions: [
|
||
'Norwegian (Bokmål) translation (PR #101)',
|
||
'Ongoing bug & quirk reports via Discord (PRs #747, #750 et al.)',
|
||
],
|
||
},
|
||
{
|
||
github: 'cucadmuh',
|
||
since: '1.33.0',
|
||
contributions: [
|
||
'Russian translation & i18n locale split (PR #106)',
|
||
'Russian locale refinements using phrasing from ru2 (PR #113)',
|
||
'Gapless manual skip: honor user-initiated play over pre-chained track (PR #119)',
|
||
'Hot playback cache — queue prefetch (PR #123)',
|
||
'Per-server music folder filter and sidebar library picker (PR #124, PR #125)',
|
||
'Richer star ratings, skip threshold, and library filtering (PR #130)',
|
||
'Statistics: scope album and song totals to selected music library (PR #138)',
|
||
'AudioMuse-AI discovery integration for Navidrome (PR #147)',
|
||
'Hot playback cache — eviction budgeting, grace period, and live Audio settings readout (PR #153)',
|
||
'Folder Browser: keyboard navigation, context menus, now-playing path emphasis, and adaptive column layout (PR #158)',
|
||
'Infinite queue: artist-driven candidates via Top Songs + Similar Songs with random fallback (PR #163)',
|
||
'Folder Browser: per-column filter with keyboard flow and Shift+Enter queue append (PR #165)',
|
||
'Keybindings: modifier + key chords for in-app shortcuts; fixed seek ±10s units (PR #167)',
|
||
'Statistics: genre insights scoped to music library, cached Subsonic fetches, localized duration formatting (PR #144)',
|
||
'Audio output device picker: clearer ALSA labels, duplicate disambiguation, system-default mark, live refresh (PR #173)',
|
||
'Folder Browser: arrow navigation blocked when modifier keys are held (PR #174)',
|
||
'Linux audio output device picker: stable watcher (disable false enumeration-miss resets), canonicalize ALSA name drift, ghost entry for unlisted device (PR #176)',
|
||
'Opus audio playback via symphonia-adapter-libopus with bundled libopus (PR #183)',
|
||
'CLI player controls with D-Bus forwarding, shell completions for bash/zsh, library/audio-device/instant-mix CLI commands, active server switcher in header (PR #187)',
|
||
'Streaming playback stability: stream-first start, seek recovery, crossfade/gapless backup preload, hot-cache promotion (PR #200)',
|
||
'ReplayGain values in Queue tech strip (PR #196)',
|
||
'Playback source badge (offline / cache / stream) in Queue tech strip (PR #201)',
|
||
'WebKitGTK wheel scroll mode: smooth (kinetic) default with optional linear toggle (PR #207)',
|
||
'ArtistCardLocal i18n: use plural-aware artists.albumCount key instead of hardcoded German',
|
||
'NixOS / flake install guide with Cachix setup (PR #209)',
|
||
'Subsonic: align Rust HTTP UA with main WebView UA (PR #235)',
|
||
'UI: overlay scrollbars, resizer hit-test, and Linux mini-wheel (PR #255)',
|
||
'Server invites: magic-string paste, Navidrome admin share, add-user validation (PR #258)',
|
||
'Library deep links (psysonic2 scheme) — paste track/album/artist/queue (PR #261)',
|
||
'Linux: stop Wayland GTK drag proxy and PsyDnD ghost (PR #268)',
|
||
'Sidebar: long-press drag to reorder and hide nav items (PR #269)',
|
||
'Lucky Mix — instant queue from listening history, ratings, and AudioMuse similar tracks (PR #278)',
|
||
'UI perf: fix spike when medulla-perch lines up with hair-fan gestures (PR #283)',
|
||
'Navidrome smart playlists workflow in the Playlists page (PR #289)',
|
||
'Loudness normalization — LUFS / EBU R128 analysis cache + persistent waveform store (PR #315)',
|
||
'Queue resize handle: no longer blocked by main scroll hit-test (PR #324)',
|
||
'LUFS loop stabilization, serialized CPU seed work, mixed mean/peak waveform bins (PR #326)',
|
||
'Queue undo/redo — hotkeys, playback-aware restore, scroll restoration (PR #331)',
|
||
'Lucky Mix + mix rating filter (Navidrome / OpenSubsonic) (PR #332)',
|
||
'Loudness: target sync, effective pre-analysis trim, queue/settings copy (PR #333)',
|
||
'CLI logs mode with tail/follow and shell completions (PR #337)',
|
||
'Player: reserve preview row space in delay modal (PR #344)',
|
||
'Infinite queue: respect rating filter in top-up (PR #357)',
|
||
'NixOS install guide refresh for channels and CI (PR #380)',
|
||
'UI refinements — sidebar discovery indicators, adaptive header, interaction polish (PR #397)',
|
||
'Queue: drag rows outside to remove with trash ghost (PR #420)',
|
||
'Tauri: modularize audio and lib command layers (PR #422)',
|
||
'Shortcuts: action registry, dynamic CLI help, 9 new input targets + F1 help binding (PR #435)',
|
||
'Environment upgrade & hot-cache playback — replay via RAM/disk on same-track and queue-end resume, playback-source icon stays correct after resume/undo/gapless, sidebar new-releases 500-id cap merge, Windows tray double-click fix, lazy-loaded routes, rodio 0.22 migration (PR #463)',
|
||
'Audio: post-sleep stream recovery (Windows + Linux) with poll-gap-armed stall watchdog; preview seekbar freeze + anti-jump on preview end; remove card hover lift and per-card GPU compositing hints (PR #476)',
|
||
'Analysis queue control: prune stale http-backfill / cpu-seed jobs when tracks leave the playback queue, cap loudness backfill warmup to current + next 5 tracks, plus debug counters for diagnostics (PR #480)',
|
||
'CachedImage / useCachedUrl: blob URL only for matching cacheKey, layout load reset on key change; fixes broken player cover flash on track switch (#606) (PR #695)',
|
||
'OpenSubsonic albumArtists in album header; track artists in player bar, mobile, mini + songToTrack (#552) (PR #696)',
|
||
'Search: filter search3 artist rows with zero albums (report: zunoz on Psysonic Discord) (PR #697)',
|
||
'Internet Radio: portal add/edit station modal to document.body — fixes clipped modal on empty library (report: voidboywannabe on Psysonic Discord) (PR #699)',
|
||
'Now Playing: composite list keys on similar artists, album-card tracklist, and top songs — avoids duplicate React keys when Subsonic repeats ids (PR #703)',
|
||
'Search: share links in live/mobile search + queue preview modal (PR #716)',
|
||
'Multi-server: pin queue streams, cover art, links, context menu, and Now Playing to queue server (PR #717)',
|
||
'M4A/MP4 streaming: moov-at-end tail prefetch and Symphonia isomp4 probe fix (PR #737)',
|
||
'HTTP stream buffering — seekbar/timer at zero and cover overlay until playback arms (PR #737)',
|
||
'M4A playback: fix AtomIterator overread in patched isomp4 demuxer — probe gate for ranged tail prefetch, zero-hole fallback detection (PR #757)',
|
||
'Multi-server: Lucky Mix and Now Playing keep browsed server; queue metadata via apiForServer (PR #768)',
|
||
'Linux: session GDK/WebKit mitigations, in-page library scroll, Wayland text presets (PR #731)',
|
||
'In-page browse: virtual list scrollMargin + CachedImage load priority; Artists infinite-scroll batching (PR #783)',
|
||
'Lucky Mix: hand off queue to browsed server after multi-server switch (PR #785)',
|
||
'Home album rails: stable play/enqueue hover on WebKitGTK/Wayland (PR #787)',
|
||
'Local library index: multi-server settings UI, serial sync queue, music-library-scoped local search, parallel initial ingest, i18n across 9 locales (PR #846)',
|
||
'Library browse: local-vs-network text search race, All Albums/Artists catalog from index, DevTools browse-race logging (PR #847)',
|
||
'Player stats: local listening history tab with heatmap, year summary, recent days, and day drill-down (PR #849)',
|
||
'Playback speed: global 0.5–2.0× with Speed / Varispeed / Pitch shift strategies, player bar popover, Orbit passthrough (PR #852)',
|
||
'Local library index: full resync orphan sweep (IS-7) — remove server-deleted tracks after successful re-sync (PR #861)',
|
||
'Track enrichment: oximedia BPM/mood analysis, mood-group Advanced Search, queue display, unified playback analysis dispatch (PR #863)',
|
||
'Server index-key rebuild follow-up: startup-safe migration orchestration, per-server analysis strategy controls, playback/cache scope hardening, and backup/restore for library databases with blocking progress UX (PR #864)',
|
||
'Live Search: server-scoped local FTS, multi-server hit fix, and local vs search3 race merge (PR #868)',
|
||
'Cover art pipeline: tier ladder, WebP disk cache, dense-grid prefetch, Settings cover cache budget (PR #869)',
|
||
'Lossless: local index browse, Advanced Search and All Albums filters, artist/album drill-down mode, conserved sidebar page (PR #871)',
|
||
'Albums: combined browse filters (genre/year/favorites/lossless/compilations), session restore from album detail, favorites reconcile via local index (PR #876)',
|
||
'Artist detail: sort albums by year (newest/oldest) in the Albums section (PR #877)',
|
||
'Servers: optional second address per profile (LAN + public), LAN-first connect with sticky fallback, same-server verify on save, share-link picker, URL-change library remigration, magic invite v2 (PR #880)',
|
||
'Cover art: Windows thumbnails, tier fallback, PNG decode, Subsonic coverArt id resolution (PR #878)',
|
||
'Analytics: native advanced library backfill coordinator — UI stays responsive on large libraries (PR #881)',
|
||
'Analytics: library backfill scan phase/cursor persistence so advanced indexing can finish large libraries (PR #882)',
|
||
'Analytics: Opus waveform/LUFS/enrichment decode via symphonia-adapter-libopus in the analysis pipeline (PR #883)',
|
||
'Artist page: top-track thumbnails use the same album cover path and warm batch as the albums grid (PR #886)',
|
||
'Library browse navigation: session restore on back for Artists, Search/Tracks, New Releases, Random Albums; unified SearchBrowsePage (PR #936)',
|
||
'Genres: local index genre browse with Subsonic fallback, aligned counts cache, scroll restore, hold-to-shuffle play (PR #937)',
|
||
'Live Search: scoped browse on Artists, Albums, New Releases, Tracks, and Composers — header badge, ghost restore, album title FTS, session stash (PR #938)',
|
||
'Performance: idle Rust CPU — backfill coordinator park, probe overlay stability, throttled idle polls, lazy cover prefetch restore (PR #939)',
|
||
'Cover backfill: disk-free idle gate, snapshot-diff worklist, live-tunable parallelism, transient-error retries, and memoized offline & cache stats to stop idle CPU spin (PR #943)',
|
||
'Cover art: fix per-song cover over-fetch on Navidrome — only genuine per-disc artwork expands, collapsing ~520k per-track fetches to one cover per album (PR #944)',
|
||
'Performance Probe: cover pipeline covers-per-minute (cpm) throughput, the cover analogue of analysis tpm (PR #945)',
|
||
'Performance Probe: live runtime logs tab with depth switch, line cap, and ordered include/exclude word filter (PR #946)',
|
||
'Performance Probe: on-demand (ui) cover throughput alongside backfill (lib) cpm (PR #947)',
|
||
'Performance Probe: throughput (analysis tpm, cover cpm) measured over a trailing 5s window so the rate reacts promptly instead of coasting on minute-long inertia (PR #948)',
|
||
'Cover backfill: follow the smart local/public endpoint switch so off-LAN clients stop fetching covers from the unreachable local address (PR #952)',
|
||
'Audio: Symphonia 0.6 migration with libopus adapter 0.3; ranged-stream start latency fix (probe seek-gate) and a probe timeout so a stalled stream no longer hangs playback start (PR #999)',
|
||
'Offline experience — unified media layout (cache/library/favorites), localPlaybackStore, library-index Offline Library, favorites auto-sync, cached album/playlist/artist pin reconcile, mixed-server offline queue, and single mediaDir setting (PR #1008)',
|
||
'Offline browse — local-bytes catalog when server is down, integration contract (context/policy/resolvers), disconnect nav fork, Home stale-cache feed, read-only context menus, PlayerBar rating/favorite guard (PR #1017)',
|
||
],
|
||
},
|
||
{
|
||
github: 'kilyabin',
|
||
since: '1.34.0',
|
||
contributions: [
|
||
'Russian locale improvements (PR #107, PR #120)',
|
||
'Auto-install script for Debian / RHEL (PR #121)',
|
||
'Album cover art in Discord Rich Presence via iTunes API (PR #111)',
|
||
'Tiling WM detection: hide custom TitleBar on Hyprland/Sway/i3/etc. (PR #134)',
|
||
'Russian translation: lyricsServerFirst settings strings (PR #140)',
|
||
'Russian translation refinements (PR #148)',
|
||
'Merge Random Mix & Albums into a single Build a Mix hub (PR #155)',
|
||
'Fullscreen player: software-rendering performance fixes + portrait toggle & dimming setting (PR #156)',
|
||
'Fullscreen player: stop mesh blob and portrait animations in no-compositing mode; remove seekbar box-shadow repaint (PR #175)',
|
||
'Apple Music-style scrolling lyrics with spring-physics scroll for fullscreen player and sidebar; per-style controls (PR #205)',
|
||
'Golos Text and Unbounded fonts with Cyrillic support (PR #206)',
|
||
'Fullscreen & sidebar lyrics: duration-based ease-out scroll animator replacing spring physics; bottom fade for plain lyrics (PR #214)',
|
||
'Sidebar lyrics: YouLy+ source strings render in a single line (PR #215)',
|
||
],
|
||
},
|
||
{
|
||
github: 'nisrael',
|
||
since: '1.34.0',
|
||
contributions: [
|
||
'Nightfox.nvim theme group in Open Source Classics (PR #114)',
|
||
'Switch reqwest to rustls-tls for cross-platform TLS (PR #112)',
|
||
'ICY stream metadata & AzuraCast Now Playing support (PR #146)',
|
||
],
|
||
},
|
||
{
|
||
github: 'kveld9',
|
||
since: '1.34.4',
|
||
contributions: [
|
||
'Spanish (es) translation — 964 strings (PR #159)',
|
||
'Column-header sorting for albums & playlists (PR #160)',
|
||
'Multi-select for albums, artists & playlists with bulk "Add to Playlist"; collapsible sidebar playlist section; infinite scroll on Artists page; "Remove from Playlist" in context menu (PR #168)',
|
||
'3 visual toggles: cover art background, playlist cover photo, show bitrate badge (PR #181)',
|
||
'8 community themes (AMOLED Black, Monochrome Dark, Amber Night, Phosphor Green, Midnight Blue, Rose Dark, Sepia Dark, Ice Blue) + waveform live theme update (PR #182)',
|
||
'Favorites redesign: sortable columns, genre filter, age range filter, new columns (PR #184)',
|
||
'Albums and playlist headers redesign with improved layout and theme integration (PR #186)',
|
||
'Tracklist column picker overflow fix in AlbumTrackList (PR #188)',
|
||
'Spotify CSV playlist import (PR #190)',
|
||
'Context menu for songs in SearchBrowsePage (PR #191)',
|
||
'Tracklist column picker alignment and toggle fix across Favorites and PlaylistDetail (PR #192)',
|
||
'CSV import: dynamic match threshold, cleaned title search, score display in report (PR #199)',
|
||
'Discord Rich Presence: configurable text templates for details, state and album tooltip (PR #198)',
|
||
'Click-to-toggle duration / remaining time in player bar with persisted preference (PR #212)',
|
||
'Opt-in floating player bar with themed background, accent-colored border, rounded album art, and centered volume section (PR #216)',
|
||
'Linux GPU-vendor auto-detection to configure the WebKitGTK DMA-BUF renderer (disabled on NVIDIA proprietary) (PR #217)',
|
||
'Artist page: continue playback when starting top songs (PR #220)',
|
||
'Floating player bar: scroll-padding fix (PR #221)',
|
||
'Queue Panel — position counter, tri-state duration toggle (total/remaining/ETA), persistent Now Playing collapse, animated EQ indicator (PR #419)',
|
||
'Community themes — redesign pass: removed 5 overlapping / eye-straining palettes, added 8 new dark themes (Obsidian Black, Carbon Grey, Volcanic Dark, Forest Green, Violet Haze, Copper Oxide, Sakura Night, Obsidian Gold) (PR #490)',
|
||
'Customizable queue toolbar — drag-and-drop button reordering, per-button visibility toggles, optional separator, persisted across restarts (PR #534)',
|
||
'Playlist page layout — per-element visibility toggles (Add Songs, Import CSV, Download ZIP, Cache Offline, Suggestions) under Settings → Personalisation (PR #556)',
|
||
'Player bar layout — per-control visibility toggles (Star rating, Favorite, Last.fm love, Equalizer, Mini player) under Settings → Personalisation (PR #627)',
|
||
'Queue panel — persist header duration mode (total / remaining / ETA) across app restarts (PR #625)',
|
||
'Settings: dim disabled toggle rows; Queue toolbar "Clear" → "Clear queue" rename across 9 locales (adopted from PR #558, rewritten in PR #778)',
|
||
],
|
||
},
|
||
{
|
||
github: 'peri4ko',
|
||
since: '1.43.0',
|
||
contributions: [
|
||
'WebView2 idle hooks when Tauri windows are hidden — Windows GPU and compositor mitigation (PR #273)',
|
||
],
|
||
},
|
||
{
|
||
github: 'Sayykii',
|
||
since: '1.46.0',
|
||
contributions: [
|
||
'Discord Rich Presence: cover art from your own server (Subsonic getAlbumInfo2) with three-way picker — none / server / Apple Music (PR #462)',
|
||
'Artist page: group albums by OpenSubsonic releaseTypes (Album / EP / Single / Compilation / Live / Soundtrack / Remix) with deterministic order and i18n section headers in all 9 locales (PR #471)',
|
||
],
|
||
},
|
||
{
|
||
github: 'MihaiCatalin120',
|
||
since: '1.46.0',
|
||
contributions: [
|
||
'Romanian (ro) full UI translation (PR #663)',
|
||
],
|
||
},
|
||
{
|
||
github: 'DanielWTE',
|
||
since: '1.46.0',
|
||
contributions: [
|
||
'Search: queue pasted share links from live search (PR #551)',
|
||
],
|
||
},
|
||
{
|
||
github: 'artplan1',
|
||
since: '1.46.0',
|
||
contributions: [
|
||
'Player: cap persisted queue to ±250-track window — fixes QuotaExceededError on large playlists (PR #756)',
|
||
'Playlists: virtualized tracklist for large playlists — no UI freeze on 10k+ tracks (PR #755)',
|
||
'Favorites: virtualized songs tracklist for large starred collections (PR #805)',
|
||
],
|
||
},
|
||
{
|
||
github: 'Psychotoxical',
|
||
since: '1.0.0',
|
||
contributions: [
|
||
'Initial app scaffold — Tauri v2 + React + Zustand + Subsonic protocol, multi-server auth (v1.0)',
|
||
'Rust/rodio audio engine replacing Howler.js (v1.2)',
|
||
'Waveform seekbar, MilkDrop visualizer, EQ bars (v1.3)',
|
||
'10-band parametric equalizer with per-theme UI (v1.5)',
|
||
'Replay Gain, Crossfade, Download Folder Modal (v1.6)',
|
||
'Last.fm scrobbling, Similar Artists, Statistics page (v1.7)',
|
||
'TooltipPortal + CustomSelect portal-based UI primitives (v1.7)',
|
||
'Keybindings system + font picker (v1.9)',
|
||
'Lyrics system with LRCLIB integration (v1.12)',
|
||
'Queue management overhaul + DnD (v1.22)',
|
||
'Advanced Search + Genre Mix overhaul (v1.23)',
|
||
'Playlist Management — create/edit/cover upload, drag reorder (v1.24)',
|
||
'Functional tray icon, Minimize to Tray, Sidebar customization (v1.25)',
|
||
'Bulk Select, Song Info modal, Recently Played (v1.26)',
|
||
'In-App Auto-Update + Configurable Home (v1.27)',
|
||
'Infinite Queue + Start Radio + single-click play (v1.28)',
|
||
'Internet Radio with fast-start, ICY metadata support (v1.29)',
|
||
'Discord Rich Presence, offline bulk download, artist images, lazy loading (v1.30)',
|
||
'AutoEQ integration, resizable tracklist columns (v1.31)',
|
||
'Genre browser with filter + FLAC seek fix (v1.20)',
|
||
'Fullscreen Player with dynamic accent color + mesh blobs (v1.34)',
|
||
'Bit-perfect hi-res playback + underrun hardening (v1.34)',
|
||
'Fullscreen lyrics overlay with FsArt crossfade (v1.34.1)',
|
||
'Offline Mode (beta) + MPRIS seek (v1.18)',
|
||
'NSIS Windows installer (v1.19)',
|
||
'WCAG contrast audits across 60+ themes (v1.17-v1.34)',
|
||
'Custom Linux title bar with now-playing display (v1.34)',
|
||
'Device Sync — fixed cross-OS naming scheme + playlist folders (v1.40)',
|
||
'macOS signing + notarization + Tauri auto-updater (v1.40)',
|
||
'Mini player — floating window, custom titlebar, queue DnD, persistent geometry, keyboard shortcut, WebView2 lifecycle fix (PR #162, v1.42.x)',
|
||
'67 themes across 8 groups (Mediaplayer, OS, Games, Movies, Series, Social Media, OSS Classics, Psysonic)',
|
||
'Admin-gated User Management tab with per-user library assignment (PR #222)',
|
||
'Comprehensive mobile UI overhaul (PR #238)',
|
||
'Runtime log levels and debug log export (PR #241)',
|
||
'ReplayGain Auto mode — picks track vs album gain from queue context (PR #242)',
|
||
'Now-Playing Info tab with artist bio, song credits, Bandsintown tour dates (PR #244)',
|
||
'Performance suite — search cover cache, DeviceSync N+1, Albums prefetch, Lyrics IDB, bundle splitting, Artists memo, Genres pagination (PRs #245-#251)',
|
||
'Artist page — user-configurable section visibility and order (PR #254)',
|
||
'Album enqueue via cover hover, context menu, multi-select toolbar (PR #256)',
|
||
'Settings refactor — thematic tab regroup, accordion sub-sections, in-page search (PR #259)',
|
||
'Navidrome admin API hardening (PR #260)',
|
||
'Now-Playing redesign as info dashboard + draggable widget cards (PRs #266, #267)',
|
||
'Sleep timer — circular ring + in-button countdown (PR #272)',
|
||
'Streaming seek UI freeze + snapback fix (PR #236)',
|
||
'Windows: tighten WebView2 idle hooks (follow-up to #273) (PR #276)',
|
||
'Audio: defer chained-track volume to gapless transition (PR #277)',
|
||
'Mini-player: portal volume popover so it cannot get clipped (PR #279)',
|
||
'Mini-player: drop saved position when its monitor is gone (PR #280)',
|
||
'Toolbar: swap Gapless / Infinite Queue icons (closes #274) (PR #284)',
|
||
'Linux audio: prefer PipeWire / Pulse aliases over raw ALSA default (PR #288)',
|
||
'Playlists: bulk-delete button in selection-mode header (PR #290)',
|
||
'Home: refresh Mainstage when active server changes (PR #291)',
|
||
'Search: enqueue on live-search click + reposition context menu on right-click (PR #298)',
|
||
'Tracks library hub page (closes #299) (PR #300)',
|
||
'Home: Discover Songs rail in Mainstage (PR #301)',
|
||
'Search: right-click context menu on artist and album rows (PR #302)',
|
||
'Unified SongRow + paginated song results in search pages (PR #303)',
|
||
'Orbit — Multi-User Listen-Together merged to main (PR #304)',
|
||
'Genres: tag-cloud refactor — log-scaled colour-tinted pills replace icon cards (PR #311)',
|
||
'imageCache: per-component object URLs to fix the blob: load flood (PR #313)',
|
||
'Queue: preserve scroll context on manual click (PR #314)',
|
||
'Seekbar: split waveform style into truewave (analysed) + pseudowave (deterministic) (PR #316)',
|
||
'Settings: restructure Normalization section for clarity (PR #317)',
|
||
'Cross-device resume: flush play-queue position on pause + all exit paths (PR #318)',
|
||
'Login: language picker on the login page (PR #328)',
|
||
'Playlists: confirm before re-adding songs already in the playlist (PR #329)',
|
||
'Discord: debug-build logging for Rich Presence IPC path (PR #330)',
|
||
'Player: persist queue panel visibility + drop dead loudness binding (PR #336)',
|
||
'Playlists: suggestion-row preview UX (PR #365)',
|
||
'macOS: Equalizer canvas blanking fix when laid-out dimensions are missing (PR #384)',
|
||
'Themes: Kanagawa, Atom One and 1984 palettes; OSS Classics regrouped by family (PR #390)',
|
||
'Rust track preview engine + player-bar preview indicator with smart stop semantics (PRs #392, #394)',
|
||
'Tray: now-playing track tooltip + i18n menu labels (PR #395)',
|
||
'Preview: audio start sync, ring animation, download timeout (PR #423)',
|
||
'Statistics: shareable Top-Albums card export (PR #425)',
|
||
'Windows: playback stutter under GPU load — MMCSS Pro Audio promotion + animation pause + reduce-animations toggle (PR #426)',
|
||
'Audio: frame-align gapless-off track-separation silence (fixes mono-channel playback after natural track end) (PR #439)',
|
||
'Settings: 3-state animation mode (Full / Reduced / Static) — replaces boolean reduce-animations toggle (PR #441)',
|
||
'Tracks: Highly Rated rail and per-card star display, with cache layer for ndListSongs (PR #443)',
|
||
'Random Mix: playlist-size picker (50/75/100/125/150) and filter-panel layout cleanup (PR #445)',
|
||
'Queue: optional "Preserve Play Next order" toggle — multiple Play Next inserts queue up behind each other instead of latest-on-top (PR #464)',
|
||
'Library: "favorites only" filter on Albums, Artists and Advanced Search — toolbar toggle reading star overrides live (PR #466)',
|
||
'Settings: keep current active server when adding a new one — no more auto-switch interrupting playback or library context (PR #475)',
|
||
'Help page: full rewrite with 45 focused entries across 10 themed sections (Getting Started / Playback & Queue / Audio Tools / Library & Discovery / Lyrics / Sharing & Social / Personalization / Power User / Offline & Sync / Integrations & Troubleshooting), in-page live search with case-insensitive substring matching and auto-expand on hits, translated to all 9 locales (PR #485)',
|
||
'Library: Browse by Composer — native-API role listing for classical libraries, library-scoped queries, composer as a first-class share entity (PR #487)',
|
||
'Home: "Because you listened" recommendation rail — Last.fm-anchored similar-artist surfacing with round-robin anchor rotation per server (PR #489)',
|
||
'Song Info: absolute file path on Navidrome via native /api/song/{id} — Subsonic only ever returned a relative path (or none on Navidrome), the native endpoint surfaces the full server-side location (PR #504)',
|
||
'Home: Lossless Albums rail + dedicated /lossless-albums page with infinite scroll and header parity (selection mode, enqueue, offline, download ZIPs), streaming load via per-fetch onProgress, sidebar entry default visible, detection via Navidrome native bit_depth-sorted song cursor with always-lossless suffix allowlist (PR #506)',
|
||
'Accessibility: OpenDyslexic font option in the Settings picker — bundled locally via @fontsource/opendyslexic, asymmetric glyph shapes for easier b/d, p/q tracking, Latin-only with translated subtitle in all 9 locales calling out the dyslexia-friendly intent and the Cyrillic/CJK fallback (PR #507)',
|
||
'Settings: tri-state Clock Format (Auto / 24h / 12h) overriding the locale default for the queue ETA and the sleep-timer preview (PR #742)',
|
||
'Servers: inline edit for existing profiles (PR #780)',
|
||
'Interface Scale: scales the entire window — sidebar, queue, player bar, modals and the fullscreen player follow the main content (PR #781)',
|
||
'Local library index (preview): SQLite per-server track store, background initial and delta sync, live and Advanced Search against the local index, integrity verify and auto-reconcile on count drop (PR #846)',
|
||
'Server index-key rebuild: safe dual-DB migration flow, per-server analysis strategy controls, and playback/index scope hardening (PR #864)',
|
||
'Settings: opt-in Linux input-focus repaint — workaround for the WebKitGTK 2.50.x text-field freeze (PR #884)',
|
||
'Discord Rich Presence: configurable activity-name template — member list shows the playing track instead of "Psysonic" (PR #885)',
|
||
'Library browse: lazy 200-row local catalog chunks, unified in-page scroll, cover priority during scroll (PR #890)',
|
||
'Performance Probe: Monitor/Toggles redesign, live CPU/RSS/thread metrics, overlay pins and sparklines, macOS snapshots (PR #890)',
|
||
'Performance Probe: opt-in thread-group CPU poll toggle and includeThreadGroups IPC fix (PR #891)',
|
||
'Queue: switchable display mode — Queue (upcoming only) vs Playlist (full list), with header toggle and settings entry (PR #922)',
|
||
'Community Theme Store: semantic-token refactor, dedicated Themes tab with day/night scheduler, install/update/uninstall, local .zip import, and free-form community themes (safety floor + state-reactive styling); 80+ palettes moved to an on-demand CDN repo (PR #1009, #1011, #1012, #1013, #1014, #1015, #1016)',
|
||
],
|
||
},
|
||
{
|
||
github: 'ImAsra',
|
||
since: '1.47.0',
|
||
contributions: [
|
||
'Long-press album Play to shuffle with hold progress animation (PR #888)',
|
||
],
|
||
},
|
||
] as const;
|
||
|
||
// PR number of a contributor's first listed contribution, used as the
|
||
// tie-breaker when several contributors share the same `since` version.
|
||
function firstContributionPr(entry: { contributions: readonly string[] }): number {
|
||
const match = entry.contributions[0]?.match(/PR #(\d+)/);
|
||
return match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
|
||
}
|
||
|
||
// Rendered chronologically: ascending by the app version a contributor first
|
||
// appeared in (`since`), then by their first-contribution PR number. Sorting
|
||
// here keeps the order correct no matter where new entries land in the array.
|
||
export const CONTRIBUTORS = [...CONTRIBUTOR_ENTRIES].sort((a, b) => {
|
||
if (isNewer(a.since, b.since)) return 1;
|
||
if (isNewer(b.since, a.since)) return -1;
|
||
return firstContributionPr(a) - firstContributionPr(b);
|
||
});
|
||
|
||
export const MAINTAINERS = [
|
||
{ github: 'Psychotoxical' },
|
||
{ github: 'cucadmuh' },
|
||
] as const;
|