Files
psysonic/src/config/settingsCredits.ts
T
Psychotoxical 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%.
2026-06-07 02:47:33 +02:00

391 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.52.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)',
],
},
{
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, and 80+ palettes moved to an on-demand CDN repo (PR #1009)',
],
},
{
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;