* feat(themes): add Kanagawa, Atom One, 1984 palettes; group OSS Classics by family
Adds three upstream-faithful theme families to Open Source Classics and
restructures the picker so the section is no longer alphabetically wild.
New themes (9 total):
- Kanagawa (rebelot/kanagawa.nvim): Wave, Dragon, Lotus
- Atom One (Th3Whit3Wolf/one-nvim): Dark, Light
- 1984 (juanmnl/vs-1984): Default, Cyberpunk, Light, Orwell
(Fancy + Unbolded skipped — identical palette to Default, style-only)
Each theme defines the full token set (--bg-*, --accent, --text-*, all
--ctp-*, --waveform-*, --positive/warning/danger, --select-arrow), so
login screen, queue sidebar tabs, and all subpages inherit the palette
without component-level overrides.
Picker restructure:
- ThemeDef gains optional `family?: string`
- Open Source Classics regrouped: 1984, Atom One, Catppuccin, Dracula,
Gruvbox, Kanagawa, Nightfox, Nord
- Family headings rendered inline (grid-column: 1 / -1) when family
changes; new .theme-family-header style in components.css
- Theme scheduler dropdown labels prefixed with family for context
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(release): sync Cargo.lock to 1.45.0-dev
Whats New and the post-update modal required an exact ## [version] header,
so prerelease package versions (rc, dev) often had no matching section.
Match on major.minor.patch first; if several sections share the core,
prefer a plain X.Y.Z heading when present.
* feat(playlists): suggestion-row preview UX (30s preview, double-click play-next, scroll keep)
Reworks the interaction on the suggestion rows below a playlist so users
can audition songs before deciding what to do with them.
- New "Preview" pill button left of each track title plays a 30-second
mid-song sample via a parallel HTML5 audio element. The main player
pauses on the first preview and auto-resumes when the preview ends.
Switching previews chains without re-resuming. External main-player
playback (spacebar, mediakey) cancels the preview without resuming.
Animated underline shows the 30 s progress.
- Double-click the row to insert the song at queueIndex + 1 and skip to
it ("Play next"). Single-click is intentionally inert so a stray click
next to the preview button no longer drops a song into the queue by
accident. Tooltip on the row makes the affordance discoverable.
- The + button still adds to the playlist, and now restores the
.main-content scroll position so the page no longer jumps to the top
after pressing it.
- i18n keys in all 8 locales: playlists.preview / previewStop / previewShort
/ previewStopShort / suggestionDoubleClickPlayNext.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(playlists): WebKitGTK NULL-instance crash on preview teardown
cucadmuh hit GLib-GObject-CRITICAL: invalid (NULL) pointer instance /
g_signal_connect_data assertion failed, with the UI freezing after
clicking a preview button. Root cause was the audio-element lifecycle:
- `audio.src = ''` to "stop" leaves WebKitGTK's GStreamer playbin in a
half-initialized state. The next signal_connect dereferences NULL.
- Creating `new Audio()` per click stacked half-torn-down playbins
during rapid switches.
- `loadedmetadata` listeners on orphaned audio elements could call
play() on an already-discarded instance.
Fix: reuse one <audio> element per component, tear down the source
via removeAttribute('src') + load() (which resets the playbin
cleanly), and gate async listeners behind a session counter so stale
metadata events on switched-away previews are ignored.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(playlists): icon-based action buttons + add-on-doubleclick + hint
- Replace text-pill Preview button with circular icon button + animated
SVG progress ring (Play/Square icon swap when active).
- New Play-next button (filled accent circle, white triangle) sits
left of the Preview button — explicit affordance for the action that
used to be a hidden double-click.
- Double-click on a suggestion row now triggers Add-to-playlist (the
same action as the + button on the right) — discoverable shortcut
for mouse users.
- Subtitle under the Suggested Songs header announces the add-on-
doubleclick affordance, in all 8 locales.
- Drop the now-obsolete previewShort / previewStopShort short-label
keys (text replaced by icons); rename suggestionDoubleClickPlayNext
→ playNextSuggestion to match its new role on the Play-next button.
- Keep the session-counter guard from the prior commit so async
loadedmetadata handlers from a switched-away preview can't play()
on a discarded element.
Note: header column labels visually drift from the data columns when
suggestion rows have the action buttons; left as-is for now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(playlists): apply LUFS pre-analysis attenuation to suggestion previews
When the main player is on loudness normalization, analysed tracks come
out reduced toward target while the unanalysed preview <audio> blasts
the file at its natural level — cucadmuh reported the previews are
audibly louder than the playlist playback.
Apply the user's stored pre-analysis attenuation (the slider value, not
the target-offset effective form) as a linear gain on the preview's
audio.volume. Default −4.5 dB lands the preview at ~60% of the player
volume, which roughly tracks how aggressively the Rust engine pulls
naturally-loud tracks toward target.
If the engine is off, behavior is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style(playlists): use chevron icon for suggestion preview button
Distinguishes the preview button from the adjacent play-next button
by mirroring the Play Next chevron from the context menu.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(loudness): target sync, -14 pre-analysis ref, queue UI, and reseed
- Front: coalesce loudness refresh by target LUFS; replay-gain IPC dedupe keys
include norm target and effective pre-attenuation so TGT changes apply.
- Rust: placeholder gain before integrated LUFS uses pivot at -14 LUFS; UI gain
from effective trim; reseed loudness after delete when waveform cache would skip.
- Pre-analysis: store attenuation relative to -14 LUFS; engine and UI use an
offset for other targets; migrate legacy absolute values on rehydrate.
- Queue/Settings: Loudness/TGT labels vs value buttons; styles; i18n for help.
* fix(i18n): simplify loudness pre-analysis helper copy
Remove reference-target wording from loudness pre-analysis helper text and keep
only the effective adjustment shown for the current LUFS target in all locales.
The language selector previously lived only in Settings, which is
behind login. New users on a non-English system had no way to switch
to their language before connecting to a server.
Add a compact CustomSelect in the top-right of the login card. Reuses
the existing settings.languageXx labels and i18n.changeLanguage flow
(persists to localStorage as psysonic_language). English remains the
default for first launches — already the case in i18n.ts:12.
Styled to be visually quieter than the Settings variant (transparent
background, smaller font) so it doesn't pull focus from the logo and
form.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Append-only update of CONTRIBUTORS in Settings.tsx for the v1.44 cycle.
cucadmuh gains the medulla-perch perf fix (PR #283), Navidrome smart
playlists (PR #289), and the LUFS loudness cache (PR #315).
Psychotoxical gains 20 entries since the sleep-timer ring (PR #272),
including Orbit (PR #304), the Tracks hub (PR #300), the genres tag
cloud (PR #311), the seekbar truewave/pseudowave split (PR #316),
and the cross-device resume fix (PR #318).
Skipped chore/CI/internal PRs: #285–#287 (credits self-update), #292–
#297 (Flatpak test tags + revert), #306–#310 (deps + devtools), #312
(debug log).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The mode picker (Off / ReplayGain / LUFS) used to live in the right-hand
action slot of a settings-toggle-row and the per-mode controls were
stacked into the same parent with footer-style help text. Hard to scan,
visually cramped, and odd compared to the rest of the Settings page.
Refactor:
- Mode picker becomes a full-width segmented row with even-flex buttons,
using the new .settings-segmented utility.
- Each mode renders its own .settings-norm-block sub-section with a
subtle accent tint and border so the active configuration reads as
one coherent group.
- Inside the block, every setting is its own .settings-norm-field
(control row + per-control help text immediately below). 1.1 rem gap
between fields, 0.45 rem between row and help — clearly groups
related text without crowding.
- Sliders no longer max-cap at 200 px and instead flex to fill the row.
- Inactive ghost buttons (Off, ReplayGain, RG mode, LUFS targets) get a
visible border and a faint surface tint so they read as selectable
slots in dark themes too.
- LUFS mode gets a dedicated note-box explaining that brief volume drift
on the very first play of a new track is the analysis pass at work,
not a bug — subsequent plays use the cached measurement, and queued
tracks are usually pre-analysed during the previous song.
- "Trim before measurement (dB)" renamed to "Pre-analysis attenuation"
(and equivalents in 8 locales).
- New i18n keys: normalizationDesc, normalizationOff/ReplayGain/Lufs,
loudnessTargetLufsDesc, loudnessFirstPlayNote, replayGainPreGainDesc,
replayGainFallbackDesc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The waveform-loudness-cache merge replaced the existing deterministic
per-track-ID waveform with a bins-based one driven by the analysis
cache. The bins-based variant is the better default but the old
deterministic look is still valuable when no analysis is available
(brand-new track, cache-miss, etc.) and several users prefer it.
Split into two explicit options in the seekbar style picker:
- 'truewave' (default, replaces old 'waveform') — bins from the analysis
cache, with morph-on-arrival animation and flat-line fallback while
empty.
- 'pseudowave' — pseudo-random heights derived deterministically from
the track ID. No analysis dependency, no morph, instant render.
Existing persisted seekbarStyle: 'waveform' is migrated to 'truewave'
in onRehydrateStorage so users keep the visual they have today. The
useEffect that builds heightsRef now lists seekbarStyle in its deps so
switching between the two is live.
i18n labels added in all 8 locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve integrated loudness from SQLite only at decode bind; keep pre-trim
until a row exists, then allow provisional gain from live updates. Pass
DB-stable hints from the web app into play and gapless preload.
Default pre-measurement attenuation -4.5 dB with an icon reset in Settings;
drop redundant normalization copy and shorten pre-trim descriptions.
analysis_cache seed_from_bytes returns an outcome and skips redundant
waveform work on cache hits; wire callers and related frontend/backend glue.
The previous genre grid mounted ~60 Lucide SVG cards per page (with
Watermark icon, gradient bg, infinite scroll) and froze the WebKitGTK
renderer for several seconds on libraries with many genres.
The new layout flows all genres as compact pills with log-scaled font
size based on albumCount — one <span>-equivalent button per genre, no
SVGs, no pagination needed. Pill colour is dimly tinted by the same
deterministic hash-to-CTP palette used before; text picks up the genre
colour on hover only, so the page reads calmly at rest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Improve seekbar fallback behavior by using consistent bar-based rendering and smoother transitions to analyzed waveform data. Reduce hot-path analysis overhead during ranged streaming and add controls to clear cached waveform entries.
Replace the mixed ReplayGain/Loudness controls with a single exclusive mode selector, hide RG-specific controls when LUFS is active, and align the queue tech badge behavior between RG and LUFS. Also update LUFS targets to -10..-16 ordering and default loudness target to -12.
Improve normalization consistency by separating cache refresh for non-playing tracks, hardening track-id cache lookups, and keeping UI gain state aligned with actual playback. Also reduce startup stalls by preferring non-seekable streaming when format hints are missing and by tuning ALSA/analysis paths to avoid underrun-prone churn.
Conflicts resolved in:
- src/pages/SearchResults.tsx
- src/pages/AdvancedSearch.tsx
Both pages were rewritten on main (PR #303) to use the shared
<SongRow> component with click-to-enqueueAndPlay semantics. Orbit's
playSong helper that branched on orbit-active is no longer needed
at the page level — instead, orbit awareness moved INTO SongRow and
SongCard themselves: in an active orbit session both buttons collapse
into addTrackToOrbit (suggest for guests, host-enqueue for the host)
so we don't ship a queue replacement to every guest.
Also kept main's IntersectionObserver-based pagination on both pages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracts the song-list row into a single shared <SongRow> component
used by Tracks Hub Browse, /search and /search/advanced. All three now
share the same five-column layout (Play+Enqueue · Title · Artist ·
Album · Genre · Duration), the same enqueueAndPlay click behaviour,
and the same right-click context menu / drag handler.
The header row is rendered separately via <SongListHeader> (kept
outside the virtualizer scroll container in the Tracks Hub so it
doesn't scroll away).
Both SearchResults and AdvancedSearch now infinite-scroll their song
results via an IntersectionObserver sentinel near the bottom of the
list (rootMargin 600 px). Pagination uses search3's songOffset; the
free-text branch in AdvancedSearch keeps applying genre/year filters
client-side per loaded page. Initial fetches stay at 50 (SearchResults)
and 100 (AdvancedSearch) songs; subsequent pages are 50 each.
Cleanup:
- removed the redundant `(N)` count in the AdvancedSearch songs heading
- dropped the unused `useNavigate` + `psyDrag` + per-page contextMenuSongId
state in both search pages — SongRow handles those internally
- renamed the row-internal CSS classes from `.virtual-song-*` to
`.song-list-row-*` so they read as shared, and switched the mobile
grid breakpoint accordingly
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reuses the SongRail component from the Tracks hub — 18 random songs
fetched in parallel with the other Home queries. No reroll button.
Click-to-play uses enqueueAndPlay so the existing queue stays intact.
New homeStore section id 'discoverSongs' inserted after 'discover'
in DEFAULT_HOME_SECTIONS. onRehydrateStorage now appends any newly
introduced sections to a previously persisted layout — existing users
get the rail without a manual Reset. Settings → Personalisation
Home-Customizer surfaces it automatically via SECTION_LABELS.
i18n key home.discoverSongs added to all 8 locales.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New /tracks route with three sections:
- Hero "Track of the moment" — random pick with play / enqueue / reroll
- Random Pick rail — 18 song cards, rerollable; hero song deduped
- Browse all tracks — virtualized list (@tanstack/react-virtual),
paginated 50 at a time
Browse uses Navidrome's native /api/song?_sort=title&_order=ASC for
proper A-Z order (no Subsonic equivalent), with automatic fallback
to search3 on non-Navidrome servers. Search input drives search3
with 300ms debounce. Bearer token cached module-level, re-auth on 401.
Play button on rows + cards calls a new enqueueAndPlay() helper that
appends to the existing queue (skip if duplicate) and jumps to the
song — different from playSongNow which replaces the queue. Enqueue
button stays opaque (no hover-only).
i18n keys for sidebar.tracks + tracks.* namespace in all 8 locales.
New AudioLines sidebar icon. Sidebar entry inserted between
"All Albums" and "Build a Mix".
Performance: cover thumbnails dropped (uniform layout instead),
RAF-throttled scroll prefetch, hover transforms removed from cards
(WebKitGTK compositing-friendly).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds activeServerId to the Home useEffect dependencies so a server
switch triggered while the user is already on the Mainstage refetches
albums/artists instead of leaving stale covers (which broke against the
new server's cover URLs).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Selecting playlists via the header toggle only exposed the delete action
through the right-click context menu. Add a visible Delete-selected button
next to Cancel that kicks off the same handleDeleteSelected flow, with a
tooltip hint when some of the selected playlists aren't deletable (foreign
owner). Button is only rendered when selection mode is on and at least one
playlist is selected.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ensure playlist card Play action uses tracks filtered by the active library and show filtered song count and duration in the list, so card metadata matches actual playback scope.
Open smart playlist editing from playlist cards, load rules via Navidrome single-playlist API with safer fallbacks, and keep edit visibility aligned with ownership rules.
Also add/clean smart playlist locale keys across all supported languages and preserve smarter autogenerated naming behavior.
Show edit/delete actions on hover in playlist cards, support opening metadata edit directly from the grid, and render smart playlist covers from active-library tracks. Playlist detail now filters displayed songs to the selected library instead of hiding whole playlists.
Move smart playlist creation and management into Playlists, including Navidrome-only gating, smart-name/icon presentation, and smarter refresh handling while server-side smart rules are being applied.
The maintainers panel already shows who the maintainers are, but doesn't
say what they've actually built. Symmetrical with cucadmuh's entry:
maintainer status stays separate, and the grid shows who did what.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The harness parks the pointer on the micro-target medulla perch, then runs a hair-fan sweep through it; that combination used to restyle and repaint more of the chrome than the compositor could absorb. Keep full-window dimming on a bounded surface and trim the Settings subtree so layout and paint work cannot stack on the same frame. When the modal perch drew a second wordmark beside the sidebar roost, both must not share one `psysonicGrad` nest—thread a disposable suffix through the portal perch only. Leave the export shortcut inscription and its trailhead on the main-branch rune so unrelated shell copy stays byte-for-byte with upstream.
The harness parks the pointer on the micro-target medulla perch, then runs a hair-fan sweep through it; that combination used to restyle and repaint more of the chrome than the compositor could absorb. Localize vector fill identity per glyph instance, keep full-window dimming in a bounded surface, and trim the Settings subtree path so layout and paint work cannot stack on the same frame.
- Drop the ~110 lines of dead full-screen overlay CSS that was never
referenced in JSX (.lucky-mix-overlay*, .lucky-mix-cube*, full-screen
@keyframes). Keep the .lucky-mix-pip* rules since the inline queue
variant re-uses them.
- Extract availability gate into hooks/useLuckyMixAvailable.ts (pure
predicate + hook). Replaces five duplicated inline checks in Sidebar,
MobileMoreOverlay, RandomLanding, Settings/SidebarCustomizer, and the
internal re-check in buildAndPlayLuckyMix.
- Add a cancel mechanic:
* luckyMixStore gets a cancelRequested flag + cancel() method
* LuckyMixCancelled sentinel is thrown from a bailIfCancelled() helper
sprinkled between await boundaries in the build loop
* catch-block swallows the sentinel silently (no toast, no error state)
* auto-cancel subscription on playerStore detects manual user track
changes (anything not in queuedIds) and flips cancelRequested so the
finished mix does not later overwrite the user's choice
* inline .queue-lucky-loading dice indicator is now a button — click
triggers explicit cancel, hover fades the dice to signal the action
- Restore the queue snapshot when the build fails before ever starting
playback, so the user does not land in an empty player after an error.
If playTrack already ran, leave current state alone (their track plus
whatever was enqueued is more useful than a stale pre-click queue).
- Rework locale labels to consistently carry the "Mix" suffix so the
entry reads well next to "Random Mix" / "Random Albums" in the
sidebar: DE Glücks-Mix, ES Mezcla Suerte, FR Mix Chance, NB Lykkemiks,
NL Geluksmix, ZH 好运混音. EN stays "Lucky Mix", RU stays
«Мне повезёт». Toast/settings/randomNavSplit copy aligned to the new
names. New luckyMix.cancelTooltip key added in all 8 locales.
AudioMuse gating stays — as Cuca confirmed, the feature's quality relies
on a correct similar-track selection, so the requirement is intentional.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lucky Mix targets ~50 tracks in one run: pick seeds from your most-played artists/albums and 4+ rated songs, add similar tracks, then fill the mix with random library picks, skipping anything you rated 1–2.
On start it trims the old “upcoming” tail so the queue does not show stale next tracks, starts playback on the first viable seed, routes to Now Playing, and can emit structured steps to the backend debug log.
Hold for 1s then drag to reorder configurable library and system links;
drop outside the sidebar to hide items (same visibility as Settings).
Show a trash hint near the cursor when outside removal applies.
Extract shared reorder helpers into src/utils/sidebarNavReorder.ts and
reuse them from the Settings sidebar customizer.
Each dashboard card (Album, Top Songs, Credits, Artist, Discography,
On Tour) is now a widget the user grabs and drops anywhere — reorder
within a column or move across columns. Layout persists per-install
via the new nowPlayingLayoutStore (Zustand + localStorage, same shape
as sidebarStore with an onRehydrateStorage guard for unknown IDs).
Drag uses psyDnD (useDragSource / psy-drop event, the mouse-event
based system from DragDropContext) — HTML5 native DnD is a no-go on
WebKitGTK Linux where it always shows a forbidden cursor. The whole
card is the drag handle; the column listens via a document-level
mousemove for the drop indicator and via a global psy-drop listener
that reads the latest hovered column from a ref, so drops below the
last card still land correctly regardless of column height.
Visibility is toggled from a layout menu in the top-right of the
dashboard — two sections (visible / hidden) plus a reset-to-defaults
button. No hide buttons on the cards themselves (keeps the card UI
uncluttered). An equal-height grid (align-items: stretch + min-height
on columns) keeps the drop zone active for the full vertical span of
either column.
Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the flat Now Playing page with a two-column dashboard: hero
(cover, metadata, badges, Last.fm stats + love button, release-age
tagline), and a grid of themed cards (Album tracklist with sliding
window, most-played-by-artist, credits, about-the-artist with Last.fm
bio fallback, compact discography contact-sheet with expand, upcoming
tours via Bandsintown). Adds lastfmGetTrackInfo + lastfmGetArtistStats
for global listener counts + userplaycount, plus Last.fm love/unlove
wired to the new hero button. Module-level TTL caches per entity keep
same-artist track switches instant. Artist-image guard filters the
well-known Last.fm no-image placeholder so the card collapses cleanly
when no real image exists.
Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up on #263. Refines the cross-tab search into a proper
command-palette-ish flow and polishes the UX around it.
Results-only mode:
- When a query is active, the tab content is hidden entirely; the
results list is the only thing shown. Unified list (was: in-place
current-tab filter + "In other tabs" panel underneath), sorted with
current-tab matches first and then by score.
- Removed the DOM-based hide/show logic and its CSS helper class, the
dead searchOtherTabs i18n key, and the searchHits/otherTabHits state.
Keyboard navigation inside the search field:
- ↓/↑ moves selection through the result list (clamped, no wrap).
- Enter activates the selected item (same flow as a click — clears
query, switches tab, scrolls + opens + flashes the target).
- Mouse hover syncs with the keyboard selection so the two input modes
don't fight each other.
- Selected item gets accent-border + tinted background; scrollIntoView
with block:'nearest' keeps it visible when the list is long.
Ctrl/Cmd+F (Settings page only):
- Window keydown listener registered on mount, removed on unmount →
the shortcut is live only while Settings is rendered.
- Opens the search input, focuses + selects it even if already open.
- preventDefault blocks the native WebKit find bar.
- Placeholder now hints at the shortcut: "… (Ctrl+F)" / "… (⌘F)" on
macOS, no new i18n keys needed.
Target flash animation:
- After navigating from a result, the target SettingsSubSection gets a
1.4s accent-colored border + glow pulse so the user immediately sees
which entry the result pointed at. Reflow trick lets it retrigger
when clicking the same result twice.
Misc:
- "Next Track Buffering" → "Buffering" in all 8 locales + the section
comment in Settings.tsx. The SETTINGS_INDEX keeps "next track" as a
keyword so old search habits still find it.
Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cross-tab settings search:
- Static SETTINGS_INDEX covering every sub-section of every tab, with
free-form keywords per entry so "scrobble" finds Last.fm, "replay gain"
finds Playback, etc.
- matchScore() does substring-first ranking, falls back to a char-in-order
fuzzy match so typos still yield hits.
- Active tab keeps its in-place hide/show filter; matches from other tabs
render at the top as a clickable "In other tabs" list (tab badge +
sub-section title). Clicking clears the query, switches tab, and scrolls
+ opens the target sub-section.
- All 8 locales updated: new searchOtherTabs key, searchNoResults reworded
(old "try another tab" hint is now handled by the list itself).
Settings restructure polish:
- "Next Track Buffering" moved from Audio to the Storage tab — sits
between Offline Library and Downloads.
- Storage tab label renamed across 8 locales: "Storage & Downloads" →
"Offline & Cache" (equivalents per locale). nb locale was missing
tabStorage entirely; added. Tab id stays 'storage' to avoid breaking
persisted state.
- Library tab: Random Mix sub-section now opens by default (previously
both sub-sections started collapsed) and is renamed "Random Mix
Blacklist" in all 8 locales — the section is entirely a blacklist
config, the old title was misleading.
- Lyrics tab:
* Standard / YouLyPlus rows use the same toggle style as the "Show
lyrics as static text" row and stack vertically. Order swapped so
YouLyPlus is first.
* Provider list (Server / LRCLIB / Netease) moved to appear directly
under the Standard toggle as its sub-item, indented, instead of after
the static-text toggle.
* Classic / Apple Music-like rows likewise rebuilt as vertical toggle
rows.
- One pre-existing typo carried in from #261 fixed along the way: the
openAddServerInvite handler in Settings referenced the renamed tab id
'server' (pre-#259) — now 'servers'. [Note: already landed in PR #262,
keeping this commit clean of that.]
Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(server-switch): keep cached playback alive across servers
Drop the clearQueue() + initializeFromServerQueue() calls that were added
alongside the header server switcher (c75297fc). clearQueue() fires
audio_stop, killing the currently-playing (cached) track the moment the
user clicks a different server. Restore the pre-c75297fc behaviour: just
swap activeServerId, let the Rust audio engine keep streaming the
already-resolved URL, and let the next user action or the app-boot hook
pick up the new server's saved queue.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(settings): use renamed 'servers' tab id in paste-invite handler
Leftover from #261 landing on top of #259 — the Settings tab was renamed
from 'server' to 'servers' in #259, but the openAddServerInvite route-state
handler still used the old literal. Caused a TS error and meant pasting a
psysonic1- invite did not actually switch tabs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(share): library deep links (psysonic2) with paste and toolbar actions
Add base64url-encoded payloads for track, album, artist, and ordered queue.
Handle paste outside inputs to switch server, validate entities, navigate or
play. Reuse clipboard helper for context menu, queue panel, album and artist
headers. Tests distinguish server invite strings from library shares.
* fix(share): play partial queue when some shared tracks are missing
Skip unavailable song IDs when pasting a queue share while preserving order.
Toast when some tracks are missing; error only if none resolve. Add
openedQueuePartial and queueAllUnavailable i18n; remove queueTracksMissing.
* feat(settings): paste server invites globally and scroll to add form
Handle psysonic1- magic strings outside inputs: navigate to settings or login
with prefilled add-server fields. Decode invites embedded in surrounding text.
Scroll the add-server block into view when opening from a paste so long server
lists stay oriented.
---------
Co-authored-by: Maxim Isaev <im@friclub.ru>
* fix(navidrome-admin): force HTTP/1.1 + User-Agent + no idle pool for /auth and /api calls
Replaces plain reqwest::Client::new() in all nd_* Tauri commands
(navidrome_login, nd_list_users, nd_create_user, nd_update_user,
nd_delete_user, nd_list_libraries, nd_set_user_libraries) with a shared
nd_http_client() helper that:
- sets a real User-Agent (Psysonic/<version> (Tauri))
- pins HTTP/1.1 (avoids HTTP/2 ALPN that some reverse proxies abort on)
- disables the idle-connection pool so a second call doesn't reuse a
TCP connection that the server or proxy has already half-closed
(was producing intermittent "tls handshake eof" on external servers).
Adds nd_err() to flatten the reqwest error source chain into the
returned String, so the frontend surfaces the real cause (connection
refused, tls handshake eof, etc.) instead of reqwest's opaque
"error sending request for url (…)" wrapper.
* fix(navidrome-admin): retry + graceful error + server row polish
Rust (src-tauri/src/lib.rs):
- nd_http_client: HTTP/1.1, TLS 1.2 only, no idle pool — browser-parity
for /auth and /api so strict reverse proxies that abort reqwest's
default HTTP/2+TLS-1.3 handshake mid-flight get through.
- nd_retry: one retry after 500ms on connect/timeout errors only
(ECONNRESET, TLS handshake EOF). Aggressive retries could push the
nginx upstream probe into offline state; this is the minimal useful
amount.
- nd_err: flatten the reqwest error source chain so the UI surfaces the
real cause instead of reqwest's opaque wrapper.
User management UI:
- Sequential load (users, then libraries) instead of parallel, to avoid
racing two TLS connections on a single nginx upstream slot.
- Friendlier failure state with a one-click Retry button that re-runs
load(). Concrete Rust error is kept as muted sub-line.
- User row layout: Magic-String button sits consistently next to
last-seen + delete for every user, regardless of admin status or
library-name length. No more mid-row jitter.
Servers tab:
- "Use" button no longer redirects to Home; stays on the Servers tab so
the active-badge migration is visible.
- Drag-and-drop reorder via grip handle (psyDnD), backed by a new
setServers() action on authStore.
- Active server row now has an accent-tinted background on top of the
border — harder to miss.
New i18n keys (userMgmtLoadFriendly, userMgmtRetry) added to all 8 locales.
---------
Co-authored-by: Psychotoxical <dev@psysonic.app>
* refactor(settings): regroup tabs thematically + accordion sub-sections (progress on #257)
New tab structure: Servers (first) · Library · Audio · Lyrics · Appearance ·
Personalisation · Integrations · Input · Storage · System · Users.
Reusable SettingsSubSection (native <details>) wraps related settings per tab,
with an optional action slot for per-section reset buttons.
Moved:
- Lyrics sources + sidebar lyrics style → Lyrics
- Sidebar / artist layout / home customisers → Personalisation
- Last.fm / Discord / Bandsintown / Now-playing share → Integrations
(with an opt-in privacy banner at the top)
- Tray / minimize / Linux smooth-scroll → System (new App-Verhalten group)
- Preload mini-player / custom titlebar / show artist images → Appearance visual options
- Language picker → System
Audio, Appearance and System tabs are now fully accordion-grouped.
Library tab reduced to Random Mix + Ratings accordions.
Servers tab is its own home (server cards + AudioMuse + logout) and the default
landing tab.
Indicators in the titlebar and offline banner route to the right tab.
EN + DE translations added for the new tab labels and privacy banner; other
locales fall back to English until the full i18n pass lands.
* refactor(settings): audio/input/storage/system accordions, in-page search, Navidrome focus
Settings refactor progress — every tab is now accordion-grouped:
- Audio: Output · Hi-Res · EQ · Playback · Next-track buffer
- Input: Keybindings · Global shortcuts (reset in accordion action slot)
- Storage: Offline · Downloads
- System: Language (default open) · Behavior · Backup · Logging · About · Contributors
- Contributors: own card grid with per-contributor expand, sorted by
contribution count. Changelog accordion removed (release-notes link and
"show changelog on update" toggle now live inside About).
- About: Navidrome focus; AI credit and Special Thanks removed; new
Maintainers row (Psychotoxical + cucadmuh).
In-page search: magnifier icon next to the Settings title. Click opens a
240px search field. Filters the active tab's sub-sections by title match
(data-settings-search). Cross-tab search is tracked as a follow-up.
serverCompatible updated in all 8 locales from "Compatible with: Navidrome
Gonic Airsonic Subsonic" to a Navidrome-first wording that warns other
Subsonic-compatible servers may have reduced functionality.
Full i18n pass (tabLyrics/tabPersonalisation/tabIntegrations, aboutDesc,
privacy banner, etc. in fr/nl/zh/nb/ru/es) follows in a separate commit
after visual review.
* i18n(settings): complete localisation pass for refactored tabs
Added in all 8 locales (fr, nl, zh, nb, ru, es — en+de already in place):
- Tab labels: tabLibrary, tabServers, tabLyrics, tabPersonalisation, tabIntegrations
- inputKeybindingsTitle, searchPlaceholder, searchNoResults, aboutMaintainersLabel
- integrationsPrivacyTitle, integrationsPrivacyBody
- aboutContributorsCount_one/_other (ru also _few/_many for proper plural forms)
- aboutDesc rewritten to describe a Navidrome-focused player instead of a
generic Subsonic-compatible one.
Removed dead keys in all 8 locales:
- aboutAiCredit, aboutSpecialThanksLabel — no longer rendered
- changelog — inline changelog accordion removed
- tabServer, tabGeneral — superseded by new tab structure
---------
Co-authored-by: Psychotoxical <dev@psysonic.app>
* feat(servers): magic string invites and duplicate-aware labels
Add psysonic1- share payloads (URL, Subsonic credentials, optional server
name). Login and add-server forms decode on input and keep magic field
below standard fields. Navidrome User Management generates strings after
PUT password updates where required. Resolve duplicate display names as
username@host in chrome, settings, and login.
* feat(servers): tighten magic-string import and admin share flow
After a decoded magic string, username is read-only and the password field
shows a fixed-length mask so length is not inferred. Password reveal stays
disabled until saved-server quick connect (login) or reopening the add-
server form. Navidrome admin share UI persists the password via API before
copy and adds a plaintext-handling disclaimer. Locales updated.
* feat(settings): save new Navidrome user and copy magic string
Add a non-admin "Save and get magic string" flow: create the user, assign
libraries when needed, then encode credentials to the clipboard. Reuse
the plaintext-handling disclaimer without the password-update hint meant
for edits. Strings added for all locales.
* fix(settings): tighten Navidrome add-user validation and submit feedback
Require username, display name, and a non-empty password (trimmed) for new
users; gate Save and “save and get magic string” on explicit checks with
toast feedback. Show red borders only after a failed submit, and clear
them when the user fills the fields. When editing, keep an empty password
as “unchanged” and validate identity fields with a dedicated message.
Strings: userMgmtValidationMissingIdentity across locales.