Synced lyrics always fill the screen due to auto-scroll, so the
existing fsa-fade-bottom overlay naturally fades content below
the active line. Plain (unsynced) lyrics start from the top and
may not fill the viewport, leaving no content for that overlay
to fade.
Add mask-image to .fsa-lyrics-container when rendering plain
lyrics (--plain modifier class). The mask operates on the visible
scroll viewport rather than the content, so the bottom fade is
always present regardless of scroll position.
This reverts commit 77edfaa867.
Restores the full Apple Music-style lyrics visuals from PR #205 on Linux:
rAF spring scroller, per-line scale transforms, text-shadows, title glow,
album-art/play-button outer glows, mesh-blob radial gradients, and the
FS-art opacity crossfade. PR #205's author noticed the Linux codepath
lost the intended feel — the CPU tradeoff is accepted.
The trigger-ref fix from 25537f27 for the lyrics menu stays in place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace hardcoded #fff on `.folder-col-row.selected` with a relative
OKLCH expression that picks black or white based on the accent's
lightness. Keeps rows readable on bright-accent themes (Muma, pastel
sets) without per-theme overrides.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Window controls use fixed macOS-style colored dots (red/yellow/green)
so they stay visible on every theme (broke on Middle-Earth etc. where
text tokens collapsed against the sidebar bg). Song indicator becomes
a dark translucent pill with light text + text-shadow for universal
contrast. Removed the "Psysonic" label — the sidebar logo already
covers it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The custom titlebar was rendered only on Win/Linux, so macOS users had
no way to toggle the queue, pin-on-top or expand back to the main
window — the native macOS titlebar takes care of dragging + close, but
those three actions live entirely in app code.
Render a slim transparent in-page strip on macOS as well, holding just
the queue / pin / maximize buttons right-aligned. Close is omitted on
macOS since the red traffic light already does that.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the native titlebar on Windows + Linux (decorations: false in
open_mini_player); macOS keeps the system titlebar with traffic lights.
- Add a slim 26 px custom titlebar with drag region, current track
title, and the queue / pin / open-main / close action icons.
- Remove the bottom toolbar — those four buttons now live in the
titlebar where they're easier to reach.
- Refresh the player layout: cover shrinks 112 → 84 px, the right
column gets title / artist / transport in a single 84 px-tall block,
progress bar spans the full width below.
- Add a miniPlayer i18n namespace (showQueue, hideQueue, pinOnTop,
pinOff, openMainWindow, close, emptyQueue) localized for all 8
supported locales — previously the tooltips were hard-coded English.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per @cucadmuh's feedback: the presence of ReplayGain matters more than
the actual numbers. The tech bar now shows a small 'RG' pill on line 1
whenever the track has any RG metadata; hovering reveals the values via
tooltip, clicking persistently expands the second line.
- Source icon stays inline before the format string (was getting
separated from FLAC by the centred stack layout)
- Pill uses --accent-tinted color-mix so it adapts to every theme
- ChevronDown rotates on expand for a clear affordance
- New persisted state: themeStore.expandReplayGain (default collapsed)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The player bar occasionally renders fully black for one frame on Linux
(WebKitGTK with software compositing) when an unrelated layer elsewhere
in the page invalidates. `contain: layout paint` makes the bar its own
paint boundary so it can no longer be pulled into a surrounding dirty
rect.
No-op on Wayland-with-GPU and on Chromium-based webviews (Windows,
macOS) — those don't exhibit the flash to begin with.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tech info strip in the queue header was a single ellipsised line, so
ReplayGain values (Track / Album / Peak) got cut off on tracks with a
full codec + bitrate + samplerate prefix. Now the strip wraps into two
lines whenever RG values exist:
- Line 1: codec · bitrate · bit depth/sample rate (unchanged)
- Line 2: 'ReplayGain · T … dB · A … dB · Peak …' — slightly smaller
and dimmed for hierarchy, ellipsises independently if it still
overflows.
Tracks without RG metadata stay one line as before. New i18n key
`queue.replayGain` ('ReplayGain') added to all 8 locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This iteration fills in the pieces that make the mini player usable as a
daily standalone window:
- Window position persists to <app_config_dir>/mini_player_pos.json on
every WindowEvent::Moved (throttled 250 ms); first launch lands in the
bottom-right of the main window's monitor.
- Queue keeps a 260 px floor when expanded (~2 visible rows). The
user-resized expanded height is restored on next toggle via
localStorage so reopening doesn't snap back to the default 440 px.
- set_mini_player_always_on_top(true) now forces a false-then-true cycle
so the WM re-evaluates the layer after a hide/show; the frontend also
re-asserts the pin state on mount and on window focus, so the user no
longer has to click the pin button twice for it to stick.
- Native scrollbar in the queue is hidden in favour of a JS-driven
overlay thumb so items use the full width of the queue area.
- PsyDnD reorder works inside the mini queue: drag emits 'mini:reorder',
the bridge calls reorderQueue on the source-of-truth store in main.
- Localized queue-item context menu (MiniContextMenu) with Play now /
Remove from queue / Open album / Go to artist / Favorite / Song info.
Cross-window actions forward to main via new bridge events
(mini:reorder, mini:remove, mini:navigate, mini:song-info); a
psy:navigate CustomEvent picked up by AppShell handles routing.
- Theme, font and language changes in main propagate live to the mini
via a 'storage' event listener that re-hydrates the persisted Zustand
stores and calls i18n.changeLanguage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the giant startup changelog modal. After an update, a compact
neutral-palette pill now sits above Now Playing in the sidebar saying
"Changelog — vX.Y.Z". Clicking opens a proper /whats-new page in the
main content area; X dismisses. Page renders the current version's
CHANGELOG entry with inline Markdown (headings, lists, blockquotes,
hr, bold/italic/code, and real [label](url) links that open via the
Tauri shell plugin).
Visual tokens are hardcoded slate/gray/blue so the banner and page look
identical across every theme (dark, light, skeuomorphic, whatever) —
requested for consistent contrast. Auto-mark-as-seen removed from the
page; the banner only goes away on explicit dismiss, so users can
re-read as often as they want.
Settings → About gains a "Release notes" row with a link that resets
the seen-version and opens the page, so the banner can be retriggered
manually (also helpful for dev builds ahead of the current tag).
The existing "Show changelog on update" toggle now gates the banner
instead of the modal; description strings updated in all 8 locales.
fix(themes): WCAG contrast audit — mocha + winmedplayer + wista
Mocha (Catppuccin dark)
.track-size used --ctp-overlay0 directly (3.36:1 on bg-app, 2.57
on bg-card). Swapped to --text-muted → 7.37 / 5.65. Fix also lifts
macchiato/frappe/latte which shared the failure.
WinMedPlayer (Luna)
--text-muted #b8d0f8 (3.87:1 on bg-app) → #e8f0ff (5.28)
--border #2a5090 (1.31 on bg-app) → #071027 (3.12, meets 3:1 UI)
Wista (Vista Aero)
Lyrics pane sits on bg-sidebar #0e1e3e but used --text-primary
(#0d1d3c) for active lines — 1.01:1, literally invisible. Added
component overrides: .lyrics-line / .lyrics-status / word-synced
variants → #aac8f0 (9.60), .lyrics-line.active → #ffffff (16.48).
Palette: --warning #c8980c → #735a00 (2.37 → 5.91 on bg-app),
--text-muted #4870a8 → #3f6aa0 (4.23 → 4.65 on bg-card).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a queue panel that toggles via a new toolbar button and resizes the
mini window between 340×180 (collapsed) and 340×440 (expanded). Tracks
in the queue are clickable — click jumps to that index via a new
mini:jump event handled in the main-window bridge. The current track
auto-scrolls into view when the queue opens.
Resize uses a new resize_mini_player Rust command (more reliable than
JS setSize, which was silently no-op'ing because the
core:window:allow-set-size capability was missing). That capability is
now granted as well.
The mini player hydrates its initial state from the persisted
playerStore, so real content (cover, title, artist, queue) shows as
soon as React mounts instead of "—" while we wait for the first
mini:sync. Dynamic state (isPlaying, progress) still comes from
events.
Window sizing:
- inner_size bumped to 340×180 so the toolbar row isn't clipped
- mini label added to tauri-plugin-window-state denylist so old saved
sizes don't come back
- show_main_window now also hides the mini — clicking expand no longer
leaves both windows on screen
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A second webview window (label "mini") with a compact player: album art,
title, artist, prev/play/next, progress bar, pin-on-top toggle, expand
back to main, close. Main minimizes on open and restores when the mini
is hidden or closed. Spacebar toggles, arrow keys skip tracks.
Cross-window sync: main window subscribes to playerStore and pushes
`mini:sync` via emitTo on track / play-state change. Audio progress
already broadcasts to all windows via `audio:progress`. Control actions
(prev/next/toggle) come back via `mini:control`; main-window restore
goes through a direct Rust command because WebKitGTK pauses JS in a
minimized webview.
Tiling-WM detection reused from existing `is_tiling_wm()` — always-on-top
is skipped on Hyprland/Sway/i3 since it's ignored there anyway.
Early alpha: no queue expand, no lyrics, no EQ, no drag-snap. Scope kept
deliberately tight for v1; follow-ups planned.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds optional artist field to DeviceSyncSource and renders it inline
next to the album name ("Album · Artist") in the on-device list.
BrowserRow (left panel) uses the same inline format so albums in search,
random picks and under expanded artists all read consistently. Playlists
unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clicking the mic button now toggles the lyrics settings panel instead
of outside-handler closing it and click re-opening it — trigger ref is
excluded from the outside-click check.
Panel is now a solid surface (no backdrop-blur, near-opaque background,
higher-contrast button text) so settings stay readable over the busy
fullscreen background.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trigger button with count badge, portal-rendered popover with search
input and full scrollable checkbox list (no 60-item cap). Selected
genres sort to the top. Replaces the inline tagbox that ate header
space and cut off the alphabetical list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Header with search/sort/genre/year controls now pins to top while
scrolling, so filters stay reachable. Nested .content-body (App.tsx
wraps routes) was becoming the sticky anchor and swallowing the effect —
fixed with .content-body .content-body { overflow: visible }.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Tray double-click (reported by @cucadmuh's brother-in-law):
Windows fires a Click event on *both* halves of a double-click, so
our left-click handler toggled visibility twice — the window popped
up and immediately vanished. Switch the Windows branch to the
`TrayIconEvent::DoubleClick` variant (Windows-only in tray-icon);
other platforms keep the Click-on-Up behaviour. Matches the standard
Windows tray convention (Discord, Telegram, etc).
2. GPU use while minimized:
WebView2 on Windows keeps compositing infinite CSS animations
(mesh-aura-a/b, portrait-drift, eq-bounce, track-pulse, led-pulse …)
even when the window is minimized — steady visible GPU load on
systems that should be idle. App.tsx now toggles a
`data-app-hidden="true"` attribute on <html> on `visibilitychange`,
and a single CSS rule pauses every `animation` at once via
`animation-play-state: paused !important`. Zero cost when visible,
catches all current and future infinite animations without
per-element listeners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the user-configurable filename template with a fixed scheme so
syncing the same library across Linux/Windows/macOS no longer re-downloads
files just because the template string differs by OS.
Track layout on the device:
{AlbumArtist}/{Album}/{TrackNum:02d} - {Title}.{ext}
Playlists/{PlaylistName}/{Index:02d} - {Artist} - {Title}.{ext}
Playlists/{PlaylistName}/{PlaylistName}.m3u8
Playlist tracks go into a self-contained folder with a sibling-referencing
Extended M3U, instead of being scattered across the album tree with an
external .m3u8 — the stick stays tidy even for 50-artist playlists.
Rust (lib.rs):
- TrackSyncInfo: drop disc_number/year (not used in fixed schema), add
album_artist, duration, playlist_name, playlist_index
- sanitize_path_component strengthens empty-result handling via new
sanitize_or() which falls back to "Unknown Artist/Album/Title"
- build_track_path() dispatches on playlist context — playlist tracks
get the "Playlists/{Name}/…" shape, album tracks the traditional tree
- albumArtist resolution: the server's albumArtist tag, falling back to
the track artist (empty-string treated as missing)
- calculate_sync_payload: dedup key is now (source_id, track_id) instead
of track_id alone, so a track appearing in both an album and a playlist
ends up on the device in both locations; playlist context is embedded
into the response JSON as _playlistName / _playlistIndex so the follow-
up sync_batch_to_device call can route each track correctly
- write_playlist_m3u8: rewritten — writes into the playlist folder with
sibling filenames instead of a top-level .m3u8 with ../relative paths
- New rename_device_files command: atomic renames (per-entry result,
skip-if-exists, skip-if-source-missing) plus empty-dir cleanup. Used
by the migration flow
- write_device_manifest: v2 format, drops filenameTemplate field
- Drop the `template` parameter from sync_track_to_device,
compute_sync_paths, calculate_sync_payload, sync_batch_to_device
Frontend:
- DeviceSyncStore: drop filenameTemplate from state and persist
- DeviceSync.tsx: strip template editor UI (presets, token buttons, live
preview); replace with a compact read-only schema info card
- trackToSyncInfo(track, url, playlistCtx?): optional playlist context
parameter. Tracks coming back from calculate_sync_payload with embedded
_playlistName/_playlistIndex fall through via the same parameter
- compute_sync_paths / write_playlist_m3u8 / delete paths now pass the
playlist context per-source so status checks + deletions hit the right
files (album tree vs playlist folder)
- Album-Artist fallback: frontend falls back to track artist when the
server has no albumArtist tag (empty strings treated as missing) —
"Unknown Artist" is only used as a last-resort placeholder
- New migration flow: "Reorganize existing files…" button opens a modal
that reads the legacy filenameTemplate from the v1 manifest, computes
per-track (old, new) rename pairs, detects collisions (two old files
mapping onto one new path), and executes via rename_device_files.
Playlist-only tracks are left for the next sync to re-download into
the new playlist folder
- Small JS-side applyLegacyTemplate() helper: only used by the migration
preview so we can diff old paths against the current files on disk
- CSS: device-sync-schema-section / -code / -hint, migrate-modal +
summary + warning + result layout
i18n (en + de):
- Remove dead keys: filenameTemplate, templatePreview, templateHint,
templatePresetStandard/MultiDisc/AltFolder, tokenSlashHint
- Add schemaLabel, schemaHint, migrateButton/Title/Tooltip/Loading/
NothingToDo/NoTemplate/FilesToRename/Unchanged/Collisions/
PreviewNote/Executing/Success/Failed/ShowErrors/Start
Other locales (fr/nl/zh/nb/ru/es) fall through to the English defaultValue
until translated in a follow-up pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Footer buttons are now state-dependent: Skip / Remind later only show
during idle; the install button no longer leaves a gap when it
disappears, and "Remind later" stops shifting between states
- Post-install on macOS shows a 3-second visible restart countdown with
a "Restart now" button instead of triggering an invisible relaunch
that sometimes didn't actually fire
- macOS idle state now explains the in-place update model (no DMG
download) and shows trust badges: "Notarized by Apple" + "Signature
verified" as an at-a-glance trust signal
- Download state during install hides all secondary buttons so the
progress bar stands alone
- New i18n keys in en/de; fr/nl/zh/nb/ru/es fall back to the English
defaultValue until translated in a follow-up
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Document-level zoom broke portal positioning and Tauri window measurement,
so the slider had been forced to 100 %. Scope the zoom to a wrapper inside
.main-content instead — sidebar, queue, player bar and the Linux custom
title bar live in sibling grid cells of .app-shell and stay 1:1.
- App.tsx: drop the document-level zoom + auto-reset; wrap main-content
children in <div class="main-content-zoom" style={zoom: uiScale}>.
- layout.css: add .main-content-zoom (flex column, fills parent).
- Settings.tsx: re-enable the slider and snap it to the 6 preset stops
(80/90/100/110/125/150) so the thumb lands directly under each preset
button. Legacy off-preset values snap to the nearest stop on load.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
w98:
- Lift --positive (#008000 → #005000), --warning (#808000 → #5a4500),
--danger (#ff0000 → #b00000) from ~2.6:1 to AA on bg-card. Brand
--ctp-* palette preserved as canonical W98 system colours.
- --border-subtle was identical to --bg-card (1.0:1, invisible) →
#a0a0a0 (1.73:1).
- Override .col-resize-handle::after to W98 dialog grey (was 1.23:1).
- text-secondary/muted intentionally kept at #000000: AlbumDetail
renders text on bg-app teal where any non-black tone falls below AA.
stark-hud:
- Lift --text-muted (#5a718a → #7d92a8, 3.34 → 5.73:1) staying in the
slate-blue family that matches the HUD aesthetic.
- --border-subtle was identical to --bg-card (invisible) → #243246.
- Override .col-resize-handle::after to overlay1 (was 1.09:1).
- Cyan accent, all glow shadows and chromatic --ctp-* untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WebKitGTK on Linux runs with WEBKIT_DISABLE_COMPOSITING_MODE=1, so every
GPU-only effect is software-rasterised per frame. The fullscreen player
piled up large blur shadows, per-line scale transforms, oversized
radial-gradients with color-mix() and a 60 fps rAF spring on top — CPU
saturated and FPS dropped to ~10.
Linux-only via existing html.no-compositing class:
- Apple-style lyrics: drop transform: scale and 48 px / 32 px text-shadow
- Rail-style lyrics: drop 28 px text-shadow + scaleX
- Track title: 40 px glow → 6 px drop-shadow
- Album art wrap + play button: drop accent-coloured outer glows
- Mesh blobs: flatten 130 % radial-gradient + color-mix to solid #06060e
and remove the 400 ms background transition
- Portrait wrap: drop translateZ(0) GPU-layer hint
- FS art: instant cover swap (no opacity crossfade)
- SpringScroller: replace rAF spring with native scrollTo({behavior:'smooth'})
- FsSeekbar: cache last applied values, skip identical DOM writes
Dynamic accent on title / seekbar / play button is preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Raise text-muted, borders, grips and waveform tokens to AA/AAA where
they fell below threshold. Add component-level overrides (col-resize
grip, focus ring, scrollbar thumb) to themes whose --ctp-surface1
collided with --bg-card.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two independent user-facing fixes rolled together.
── Lyrics: YouLyPlus provider (issue #172) ──
Adds word-by-word synced (karaoke) lyrics as an alternative lyrics mode.
Backed by the public lyricsplus aggregator (Apple Music / Spotify /
Musixmatch / QQ Music) — no API keys on our side, subscription costs are
borne by the backend operator. Five mirrors are tried on network failure.
Settings → Lyrics now exposes a mode radio (Standard vs YouLyPlus) plus
a static-only toggle. YouLyPlus misses silently fall back to the existing
server + LRCLIB + Netease pipeline so obscure tracks still resolve. The
drag-to-reorder source list is hidden in YouLyPlus mode since the
external aggregator manages its own source priority.
Rendering: per-word spans on each line, active word highlighted with
accent-tinted glow. Subscribed imperatively via usePlayerStore.subscribe
so 500 ms progress ticks do not re-render the whole lyrics block. The
Fullscreen Player reuses the same pattern; the 5-line rail layout is
unchanged. white-space: pre on .fs-lyric-word preserves the trailing
spaces the API embeds in each syllabus token (flex context would
otherwise collapse them).
i18n: new keys in all 8 locales (de, en, fr, nl, nb, ru, es, zh).
── macOS mic-prompt suppression ──
Ships a vendored cpal 0.15.3 at patches/cpal-0.15.3/ wired via
[patch.crates-io]. The only change is in src/host/coreaudio/macos/mod.rs:
audio_unit_from_device now unconditionally uses IOType::DefaultOutput for
output streams instead of conditionally choosing HalOutput.
AUHAL (HalOutput) is classified as input-capable by macOS TCC and
triggers the microphone-permission dialog at first launch / after each
update even for playback-only apps. Previous attempts (removing
NSMicrophoneUsageDescription, setting com.apple.security.device.audio-input
false in Entitlements.plist) did not suppress the prompt because TCC
fires at AudioUnit instantiation, not at plist level. The upstream fix
in cpal PR #1070 / cpal 0.17 only helps when the pinned device equals
the system default; always-DefaultOutput covers all cases.
Tradeoff: per-device output selection is a no-op on macOS — the stream
always follows the system default. Settings.tsx surfaces this via
audioOutputDeviceMacNotice (hides the CustomSelect on macOS) and skips
audio_list_devices + device-watcher effects there. Matches the behaviour
of Apple Music / Spotify on macOS.
Also removes the now-obsolete com.apple.security.device.audio-input
entitlement (sandbox is disabled anyway, so it was ignored).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Latte: fix server dropdown dark background (--bg-card instead of hardcoded #1e1e2e), add back-button white text over album cover overlay, fix NowPlaying white-on-white text (artist/album, badges, bio, tracklist, star/heart icons use semantic vars).
GTA: bump --text-muted from #484848 to #707070 and --text-primary from #e8e8e8 to #d8d8d8 — sidebar section labels, Settings descriptions and album detail info were nearly invisible on the near-black background.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add source badges for offline, cache, and stream playback in the queue tech line, including localized tooltips across shipped locales.
Track Rust preload-ready events by stream track id and latch the selected source per track, with dev logs to debug preload/source mismatches during next-track handoff.
- Add "Reset to defaults" button to column picker in AlbumTrackList, PlaylistDetail, Favorites (all 8 locales)
- Fix device sync cross-OS status detection: store filenameTemplate in manifest, use it for path computation on import
- Redesign filename template UI: preset buttons (Standard/Multi-Disc/Alt. Folder), clickable token chips ({artist}, {album}, etc.), clear button
- Fix suggestions section in PlaylistDetail missing album column rendering
- Add PRIVACY.md documenting all third-party integrations (Last.fm, LRCLIB, Apple Music, Discord); link from README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Write psysonic-sync.json to device after sync/deletions; auto-import
on folder select when localStorage is empty (cross-platform handoff)
- Add cancel button during active sync: AtomicBool flag per job,
tasks bail after acquiring semaphore, cancelled state in UI
- Fix sync status staying "pending": normalize template path separators
to OS separator (Windows: '/' → '\') so compute_sync_paths and
list_device_dir_files produce matching strings for Set comparison
- Add Geist and JetBrains Mono as variable fonts; font picker collapsible
- Fix device mount detection on Windows: removable drive letters (E:\)
were incorrectly skipped alongside system roots; strip \?\ prefix
from canonicalized paths before mount-point comparison
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
.content-body has overflow-x:hidden — setting overflow-x:visible on
.tracklist would clip wide tables instead of scrolling them. Keep auto.
Also remove Spanish code comments (contributors are international).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Embed bash and zsh completion scripts and add a `completions` subcommand.
Extend the player CLI (library, audio device, instant mix) and surface
`music_library` in snapshots. Add a shared active-server switcher in the
header and locales; instant mix can reseed the queue from CLI and UI.
- btn-sm: add global .btn-sm modifier to theme.css (was only scoped inside device-sync)
- year filter: exclude songs without year metadata when filter is active
- showing count: only render "X of Y" label when a filter is actually active
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New two-panel layout with live album search (search3, 300ms debounce)
and 10 random albums when search is empty — replaces full paginated load
- Pre-sync summary modal: shows files to add/delete, net change, available
space; Proceed button disabled when space is insufficient
- calculate_sync_payload now filters already-synced tracks (path exists on
device) so add_bytes/add_count reflect the true delta
- Space check accounts for pending deletions: addBytes > availableBytes + delBytes
- Separate deviceSyncJobStore for ephemeral job progress state
- Removable drive detection + auto-poll every 5 s via sysinfo
- Desired-state diff: de-selecting a synced source stages it for deletion
instead of silently removing it
- Status badges (Synced / Pending / Deletion) with matching row highlights
- Live-Search badge (⚡) and Zufallsalben section label (⇌) in album browser
- Status summary row height aligned with search field (52 px)
- i18n: all new keys added to all 8 locales (en/de/fr/nl/nb/zh/ru/es)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new Device Sync page for transferring music from Navidrome to
USB drives or SD cards. Sidebar entry is hidden by default.
- Four new Tauri commands: sync_track_to_device, compute_sync_paths,
list_device_dir_files, delete_device_file
- Filename template engine with cross-platform path sanitization
- 4-concurrent-worker download, live progress via device:sync:progress event
- Persistent source list (albums/playlists/artists) with checkbox deletion
- Expandable artist tree in browser panel (per-album selection)
- i18n: DE + EN complete; other locales have stub keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In software compositing (WEBKIT_DISABLE_COMPOSITING_MODE=1), WebKit renders
entirely on the CPU. The mesh blob animations were not covered by the
no-compositing overrides: blob-a occupies 130×130% of the viewport, so each
frame the CPU had to composite a surface larger than the screen. On high-DPI
displays (e.g. 8K) this drops the fullscreen player to ~10 FPS.
- Stop mesh blob pan animations (static gradients are preserved)
- Stop portrait drift animation (filter was already removed)
- Remove box-shadow from the seekbar played bar (its width changes on every
playback tick; in software mode each change triggers a box-shadow repaint)