Commit Graph

793 Commits

Author SHA1 Message Date
Frank Stellmacher 7064ca500e feat(stats): shareable Top-Albums card export (#425)
* feat(stats): add shareable Top-Albums card export

Adds a shareable PNG export of the user's most-played albums,
available from the Statistics page under the "Most Played Albums"
section header.

Layout:
* 3 formats — Story (1080×1920, 9:16), Square (1080×1080), Twitter
  Card (1200×675, 16:9). Story and Square stack a centered URL footer;
  Twitter packs logo + label + URL into a single header row.
* 3 grid sizes — 3×3 (default), 4×4, 5×5. Modal fetches up to 25
  frequent albums on open so larger grids work even when the entry
  surface only loaded a handful.
* Cover tiles render the rank + play count in a thin black info strip
  at the bottom of the cover (rank left, "N Plays" right).
* Header label is hardcoded English ("Top Albums") so a shared image
  remains legible to followers who don't share the user's UI language.
  Caller can override via `opts.meta`.

Source path is local-only — `getAlbumList(type='frequent')` from
Subsonic. Last.fm is intentionally not used.

Implementation:
* `src/utils/exportAlbumCard.ts` — pure Canvas-API renderer. Reads
  theme accent / bg from the document's CSS variables so the export
  matches the active theme. Cover art is loaded through the existing
  `getCachedBlob` IndexedDB cache, so repeated exports are cheap.
  Wordmark is rendered from the `PsysonicLogo` React component via
  `renderToStaticMarkup`, with both gradient stops baked to a single
  accent color for crisp single-tone branding.
* `src/components/StatsExportModal.tsx` — UI: format + grid pickers,
  live downscaled preview, native save dialog via
  `@tauri-apps/plugin-dialog` + `plugin-fs`.
* `src/components/AlbumRow.tsx` — adds optional `headerExtra` slot so
  the share button sits next to the existing scroll-nav arrows.
* i18n keys added under `statistics.*` (`en`, `de`) — other locales
  fall back to English.

* docs(changelog): add Top-Albums card export for PR #425
2026-05-02 16:44:29 +02:00
Frank Stellmacher 297c9f1125 fix(preview): sync audio start, ring animation, and download timeout (#423)
* fix(preview): sync audio start, ring animation, and download timeout

Three coupled fixes for the track-preview engine:

1. Audio sync. `Sink::try_seek` was running on a worker thread after
   `sink.append(source)`, so the sink began playing position 0 while
   the seek was still iterating to the mid-track target. With the
   30 s `take_duration` cap counting wall-clock from append, audio
   could only become audible ~25% into the preview window. The seek
   now runs on the bare source before append, then `take_duration`
   wraps it — playback starts at the seek position with the cap
   measured from there.

2. Ring animation gating. The CSS progress-ring animation was
   bound to `is-previewing` (set on click), so the ring sprinted
   ahead of any download/decode/seek warmup and didn't reset
   cleanly when switching from one preview to another. Added an
   `audioStarted` flag in `previewStore` that flips on
   `audio:preview-start` from the engine; CSS animation is now
   gated on `audio-started` instead. `is-previewing` still drives
   tooltip/icon for instant click feedback. Same SVG is reused for
   a 25%-arc rotating loading spinner while waiting for audio,
   with a 150 ms delay so cached/short previews don't flash.

3. Download timeout. The shared `audio_http_client` caps at 30 s,
   which aborts mid-download on multi-hundred-MB uncompressed
   files (e.g. 18-min Hi-Res WAV ~600 MB). The preview engine now
   builds a dedicated client with a 5 min timeout for the bytes
   fetch. Watchdog still bounds the playback window at 30 s once
   the audio actually starts.

Touches `audio/preview.rs`, `previewStore.ts`, `components.css`
plus the eight tracklist/player-bar components that render the
preview button.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(changelog): add preview audio sync fix for PR #423

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:44:49 +02:00
Psychotoxical 9cc74a7f88 style(tauri): drop redundant use super::*; imports
Five lib_commands submodules either had `use super::*;` duplicated
during the split (core.rs, offline.rs, mini.rs — leftover line 3) or
didn't reference the parent scope at all (navidrome.rs, bandsintown.rs).
Removing them silences all five `unused import` warnings; cargo check
is now warning-free.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:44:25 +03:00
Maxim Isaev cfeec22c82 refactor(tauri): decompose lib command surface into domain modules
Split the large lib command module into nested app_api, cache, sync, and ui submodules while preserving command signatures and behavior. This keeps command responsibilities isolated and makes further extraction safer.
2026-05-02 13:44:25 +03:00
Maxim Isaev c917ee1071 refactor(audio): split monolithic audio module into submodules
Move audio engine functionality from a single large file into focused Rust modules while preserving behavior and command surface. This keeps boundaries clearer for future extraction and the upcoming lib.rs split.
2026-05-02 13:44:25 +03:00
cucadmuh 274ac5b3b4 feat(queue): drag rows outside queue to remove; trash ghost outside bounds (#420)
* feat(queue): drag rows outside queue to remove; trash ghost outside bounds

- Document-level psy-drop when cursor leaves queue rect (main + mini)
- DragDropContext: drop coordinates on psy-detail; optional wrapRef on OverlayScrollArea
- Trash icon on drag ghost only outside registered queue hit-test regions

* docs(changelog): add queue drag-out removal for PR #420
2026-05-02 02:36:35 +03:00
cucadmuh fca084acf9 fix(ci): upgrade github-script action to v9 (#418)
Move reusable channel publish release step from actions/github-script v7 to v9 to stay compatible with Node 24 on GitHub runners.
2026-05-01 21:09:59 +00:00
cucadmuh 267f732397 fix(tray): gate playback_state cfg for Windows and Linux only (#416)
playback_state is only used for the Windows tray tooltip icon and the Linux
now-playing menu row; compiling it on macOS triggered an unused_variables
warning in CI.
2026-05-01 21:03:19 +00:00
cucadmuh 3f225951e6 fix(release): restore known-good publish workflow baseline (#415)
Reset reusable channel publish to the last known-good release flow from b83c0f5 and keep build_platform_artifacts gating for source-only dispatch compatibility.
2026-05-01 23:51:00 +03:00
cucadmuh 32e249c411 fix(release): tolerate delayed tag ref visibility checks (#414)
When release and target_commitish are already valid, treat temporary git ref 404 responses as a warning so release jobs continue instead of failing on API propagation lag.
2026-05-01 20:38:37 +00:00
cucadmuh 47f88374e5 fix(release): avoid pre-creating tags before release API call (#413)
Let createRelease own tag creation with target_commitish and remove preflight tag-ref waits to reduce tag visibility races that lead to untagged release behavior.
2026-05-01 20:29:21 +00:00
cucadmuh ade4bd8675 fix(release): restore github-script release creation flow (#412)
Use GitHub REST create/update via actions/github-script with target_commitish and release id from API responses to avoid tag lookup races in the gh CLI path.
2026-05-01 20:21:57 +00:00
cucadmuh 484068e83b fix(release): remove untagged fallback and validate binding by id (#411)
Create and update releases through explicit API payloads, include target_commitish, and verify tag binding through release id to avoid transient tag-endpoint inconsistencies.
2026-05-01 20:14:08 +00:00
cucadmuh 61430e147d fix(release): create releases from verified tags without target_commitish (#410)
Use gh release create --verify-tag and remove target_commitish from release payload
now that tags are created upfront. This avoids server-side untagged release fallback
while preserving deterministic tag-to-release publication.
2026-05-01 20:06:27 +00:00
cucadmuh 4609a09a74 fix(release): create/update releases by id with gh api payloads (#409)
Stop depending on tag lookup right after publish. Resolve release by tag with a
name fallback, then patch/create by id using the same payload and force a final
id-based patch to heal untagged placeholders before downstream upload steps.
2026-05-01 22:57:33 +03:00
cucadmuh d35d199f8d fix(release): switch release create/edit to gh CLI path (#408)
Replace github-script release creation with gh release create/edit plus explicit
release-id resolution and patching. This avoids inconsistent REST createRelease
untagged behavior and keeps publication pinned to app-v tags.
2026-05-01 19:50:47 +00:00
cucadmuh 9ad345e43d Fix/release create tag before verify (#407)
* fix(release): rebind untagged draft releases to expected app-v tag

After create/update, fetch release by id and force tag_name/target_commitish when
GitHub returns an unexpected tag (including untagged placeholders). This self-heals
release metadata before downstream upload steps consume release_id.

* fix(release): remove tauri-action release publishing side effects

Build macOS/Windows bundles with tauri CLI and upload artifacts explicitly via
gh release upload to the expected app-v tag. This prevents tauri-action from
creating untagged releases while keeping release assets deterministic.

* fix(release): wait for tag ref visibility before release API calls

Before create/update release, poll GitHub REST git.getRef(tags/app-v...) until the
new tag is visible. This avoids release API timing gaps where tag exists in git
but release endpoints still return inconsistent not-found/untagged behavior.
2026-05-01 19:43:36 +00:00
cucadmuh 951f2d6163 fix(release): rebind untagged draft releases to expected app-v tag (#406)
After create/update, fetch release by id and force tag_name/target_commitish when
GitHub returns an unexpected tag (including untagged placeholders). This self-heals
release metadata before downstream upload steps consume release_id.
2026-05-01 19:19:11 +00:00
cucadmuh 0646f6884f Fix/release create tag before verify (#405)
* fix(release): create missing app-v tag before release verification

Ensure reusable publish creates and pushes the expected app-v tag when absent,
then validates it points to source_ref. This prevents release records with tag_name
but no git refs/tags object, which previously broke Source code archives.

* fix(release): pin tauri publish to tagged release metadata

Pass tagName/releaseName/releaseDraft/prerelease to tauri-action in addition to
releaseId, so asset upload fallback remains bound to the expected app-v release
instead of creating untagged releases.

* chore(ci): align workflow action versions for release flow

Update github-script usage to v9 across release-related workflows and keep
the current tauri-action release binding changes in the same branch for testing.

* fix(release): harden tagged release resolution for source-only promote

Canonicalize release_id via getReleaseByTag after create/update and fail fast on
invalid tag/commit inputs to avoid untagged release binding. Also propagate
source-only marker through promote push events so push-triggered channel runs
skip platform artifacts and verify-nix consistently.

* fix(release): avoid tag lookup hard-fail during release creation

Remove immediate getReleaseByTag canonicalization after create/update because
GitHub can return 404 for tag-based release lookup while the release id is valid.
Keep release_id flow and downstream release-id validation to prevent regressions.
2026-05-01 19:13:09 +00:00
cucadmuh 8d424fbc98 Fix/release create tag before verify (#404)
* fix(release): create missing app-v tag before release verification

Ensure reusable publish creates and pushes the expected app-v tag when absent,
then validates it points to source_ref. This prevents release records with tag_name
but no git refs/tags object, which previously broke Source code archives.

* fix(release): pin tauri publish to tagged release metadata

Pass tagName/releaseName/releaseDraft/prerelease to tauri-action in addition to
releaseId, so asset upload fallback remains bound to the expected app-v release
instead of creating untagged releases.

* chore(ci): align workflow action versions for release flow

Update github-script usage to v9 across release-related workflows and keep
the current tauri-action release binding changes in the same branch for testing.

* fix(release): harden tagged release resolution for source-only promote

Canonicalize release_id via getReleaseByTag after create/update and fail fast on
invalid tag/commit inputs to avoid untagged release binding. Also propagate
source-only marker through promote push events so push-triggered channel runs
skip platform artifacts and verify-nix consistently.
2026-05-01 19:04:26 +00:00
cucadmuh 090d129283 Fix/release create tag before verify (#403)
* fix(release): create missing app-v tag before release verification

Ensure reusable publish creates and pushes the expected app-v tag when absent,
then validates it points to source_ref. This prevents release records with tag_name
but no git refs/tags object, which previously broke Source code archives.

* fix(release): pin tauri publish to tagged release metadata

Pass tagName/releaseName/releaseDraft/prerelease to tauri-action in addition to
releaseId, so asset upload fallback remains bound to the expected app-v release
instead of creating untagged releases.

* chore(ci): align workflow action versions for release flow

Update github-script usage to v9 across release-related workflows and keep
the current tauri-action release binding changes in the same branch for testing.
2026-05-01 18:44:44 +00:00
cucadmuh dbc814da4c Fix/release create tag before verify (#402)
* fix(release): create missing app-v tag before release verification

Ensure reusable publish creates and pushes the expected app-v tag when absent,
then validates it points to source_ref. This prevents release records with tag_name
but no git refs/tags object, which previously broke Source code archives.

* fix(release): pin tauri publish to tagged release metadata

Pass tagName/releaseName/releaseDraft/prerelease to tauri-action in addition to
releaseId, so asset upload fallback remains bound to the expected app-v release
instead of creating untagged releases.
2026-05-01 18:37:03 +00:00
cucadmuh 59f3a194d8 fix(release): create missing app-v tag before release verification (#401)
Ensure reusable publish creates and pushes the expected app-v tag when absent,
then validates it points to source_ref. This prevents release records with tag_name
but no git refs/tags object, which previously broke Source code archives.
2026-05-01 18:24:27 +00:00
cucadmuh f91c57ca68 fix(release): retry tag ref visibility checks after publish (#400)
GitHub ref APIs can briefly return 404 right after create/update release.
Retry getRef(tags/...) before failing binding verification to avoid false
negatives that interrupt next/release channel publishing.
2026-05-01 21:19:07 +03:00
cucadmuh fef13fefd1 Fix/release untagged guard (#399)
* fix(release): prevent untagged fallback from tauri publish

Set explicit github-script output for release_id and validate it before build jobs.
This prevents tauri-action from creating untagged releases when release id wiring
breaks and keeps assets attached to the intended app-v tag release.

* fix(release): add source-only promote mode and harden release binding

Add a source_only dispatch option to promote workflows and propagate it into
channel publish so maintainers can skip platform builds and verify-nix when needed.
Also validate release/tag/commit binding to prevent untagged fallback and ensure
source archives stay aligned with the intended app-v tag.
2026-05-01 21:09:09 +03:00
cucadmuh 9ad0f8af6d feat(ui): UI refinements — sidebar indicators, adaptive header, and interaction polish (#397)
* feat(ui): unify queue toggle handle behavior

Show the queue toggle in the header when the queue is collapsed and use a seam-aligned drag handle when it is open. Hide the seam handle while the main content is actively scrolling to reduce accidental interactions.

* feat(ui): add adaptive header search collapse behavior

Collapse header search to a magnifier when top controls get crowded and expand it as an overlay only while active. Use measured header space with hysteresis to avoid flicker and keep neighboring controls stable.

* chore(ui): remove leftover search prototype artifacts

Drop an unused icon import from live search and remove an unused header container-type rule left from an earlier layout experiment.

* feat(ui): persist sidebar and queue visibility state

Save left sidebar collapse and right queue open/closed visibility in local storage helpers so both panel modes are restored after app restart.

* feat(ui): unify player overflow menu behavior

Use a single overflow menu for click and wheel interactions, with a volume-only mode that keeps the same layout and volume controls as the full menu.

* feat(ui): add wheel seek controls to waveform

Apply 10-second wheel seek steps with trailing 1-second debounce and keep the waveform preview stable so the playhead moves smoothly during rapid scroll input.

* fix(now-playing): stabilize narrow dashboard layout

Switch now-playing responsiveness to container-based breakpoints and prevent stacked widgets from overlapping when width is constrained.

* fix(search): reduce collapse jitter and avoid header overlap

Add a short collapse-state cooldown to prevent threshold flicker and hide conflicting header controls while collapsed search expands as an overlay.

* fix(i18n): localize player overflow controls across locales

Replace hardcoded player overflow labels with translation keys and add the missing keys for all shipped locale files.

* fix(search): keep advanced control clickable in collapsed mode

Prevent focus loss on the advanced search button in collapsed overlay mode so its click handler consistently runs.

* fix(i18n): restore queue translation in offline library

Use the existing queue.appendToQueue key for the offline enqueue button tooltip and label instead of a missing key and hardcoded English text.

* fix(ui): apply overlay scrollbar to right-panel text tabs

Switch now-playing content, lyrics, and info panes to OverlayScrollArea and harden tour-item layout so long concert metadata stays within panel bounds.

* fix(ui): add unread indicator for new releases and guard sidebar drag clicks

Track unread new-release IDs per server/library scope and clear the badge when opening the New Releases page. Also prevent click-through navigation after sidebar drag release and keep related i18n/responsive sidebar-adjacent refinements in this snapshot.

* fix(ui): stabilize live dropdown layering and unread reset flow

Render the topbar Live dropdown via a portal so it consistently overlays sidebar layers. Rework new-releases unread tracking to handle library scope baselines, ignore stale refresh races, and mark items as seen after a 5-second stay on the New Releases page.

* feat(ui): add localized New badges for recently added albums

Show a theme-consistent New badge on album cards and album detail for albums created within the last 48 hours. Localize the badge label across all supported locales and centralize recency logic in a shared utility to avoid duplication.

* fix(album): prevent tracklist jump when entering multiselect

Move bulk selection actions from the tracklist body into the album toolbar next to the track filter. Keep selection controls stable in the header area so enabling multiselect no longer shifts the tracklist content downward.

* fix(tray): add playback-state badge and finalize queue handle tooltip

Show play/pause/stop icons in the Linux tray now-playing entry and persist state safely in Tauri managed state.
Also switch the queue-resize handle tooltip to the dedicated localized key across all locales.

* fix(header): prioritize search collapse before Live/Orbit labels

Make topbar compression deterministic by collapsing search first and compacting Live/Orbit labels only in sustained low-space mode.
Add sticky hysteresis-based header compact state to prevent oscillation while resizing.

* fix(ui): stabilize header compaction and show tray state icons

Prevent topbar flicker in the narrow-width range by tightening compact-mode thresholds, gating on real overflow, and removing width transitions from live search.
Also include playback state icons in tray tooltip text across platforms while preserving tooltip length limits.

* fix(tray): keep tooltip iconization Windows-only

Revert Linux tray tooltip/title fallback attempts and keep state icons only in Windows tray tooltips, while Linux continues to show playback state in the now-playing menu entry.

* fix(ui): restore queue resize response after overlay scroll interactions

Hide the queue handle while scrolling on both the main route viewport and the now-playing viewport, and clear stale thumb-drag state before starting queue resize.
Also ignore inactive/faded overlay thumbs in resizer suppression so horizontal pointer transitions no longer leave the queue seam unresponsive.

* docs(changelog): summarize ui-refinements branch features

Document the branch-level feature additions in 1.45.0 as separate changelog sections and group remaining branch-local fixes under a single polish entry.

* docs(changelog): add PR #397 references for ui-refinements

Attach PR metadata to the new 1.45.0 ui-refinement sections and the polish entry so release notes map directly to the merged branch discussion.
2026-05-01 15:42:40 +03:00
Frank Stellmacher 2ea22635e5 feat(tray): show now-playing track in tray + i18n menu labels (#395)
* feat(tray): show now-playing track in tray icon tooltip

Mirrors the current track to the tray tooltip ("Artist – Title") on every
play/pause/track change, falling back to "Psysonic" when nothing is playing.
The cached value survives tray hide/show via a new TrayTooltip state, and
is truncated to 127 chars for Windows NOTIFYICONDATA.szTip.

Closes #383


* feat(tray): linux fallback — disabled menu item shows now-playing track

AppIndicator on Linux has no hover-tooltip API, so the tooltip command was
a silent no-op there. Adds a disabled menu entry at the top of the tray
right-click menu that mirrors the same text. Updated in lockstep with the
Win/macOS tooltip via set_tray_tooltip.

* i18n(tray): localize tray menu labels

Adds a `tray` namespace in all 8 locales (en/de/fr/nl/zh/nb/ru/es) for
Play/Pause, Next/Previous, Show/Hide, Exit, and the Linux-only
"Nothing playing" placeholder.

Rust side: TrayMenuLabels state holds the cached translations and
TrayMenuItems holds the live MenuItem handles. New set_tray_menu_labels
command pushes new strings + applies them via set_text without rebuilding
the tray icon.

Frontend pushes the labels on mount and on every i18n.on('languageChanged').
2026-05-01 12:58:16 +02:00
Frank Stellmacher 20a083a9a6 feat(player): preview indicator in player bar + smart stop semantics (#394)
* feat(player): preview-active state on play button (ring + stop icon)

Checkpoint: play button mirrors the inline preview button from tracklists
during preview playback — hollow circle, accent ring depleting over the
preview duration, Square (stop) icon. Click still resumes main playback,
which the Rust audio engine cancels the preview for.

i18n key player.previewActive in all 8 locales for tooltip + aria-label.


* feat(player): show preview track in player bar + smart stop semantics

The player-bar info cell (cover, title, artist) now mirrors the previewing
track during preview playback, with a small accent "Preview" pill above
the title and an accent top-border on the bar. Rating, fullscreen hint
and album/artist link clicks are suppressed while previewing — they
target the queued track, not the preview.

Stop semantics for the two transport buttons during preview:
- Big play button (Square+ring visual): stops preview, main auto-resumes
  if it was playing before. Matches the tracklist preview-button behaviour.
- Small Stop button: new audio_preview_stop_silent Rust command — stops
  preview AND leaves main paused, so "Stop = silence" actually goes silent.

previewStore now stores the full PreviewingTrack (id, title, artist,
coverArt) — the seven startPreview call sites pass it through.
i18n key player.previewLabel in all 8 locales.
2026-05-01 12:57:34 +02:00
Frank Stellmacher 9cef3da1bb Update CHANGELOG.md (#396) 2026-05-01 12:55:46 +02:00
Frank Stellmacher 33473808ae Update CHANGELOG.md (#393) 2026-05-01 09:14:27 +02:00
Frank Stellmacher a14dba8167 feat(audio): rust track preview engine + inline play/preview buttons (#392)
* feat(audio): rust preview engine with secondary sink

Adds a parallel rodio Sink on the existing OutputStream for 30s
mid-track previews. Two new Tauri commands (audio_preview_play,
audio_preview_stop) plus three events (audio:preview-start /
-progress / -end). The main sink is paused with Sink::pause() and
auto-resumed on preview end iff it was playing beforehand.


* feat(playlists): migrate suggestion preview to rust audio engine

Replaces the HTML5 <audio> path with the new rust preview engine.
previewStore mirrors the engine's start/progress/end events so any
tracklist row can render preview UI from a single source of truth.

Spacebar redirects to stopPreview while a preview plays, hardware
mediakeys are silently dropped (Q5), and tray clicks cancel the
preview before forwarding the original action.


* feat(albums): inline play + preview buttons in tracklist rows

Track number stays static on hover instead of swapping to a play
icon — the dedicated Play and Preview buttons in the title cell
take over click-to-play and click-to-preview. Active+playing rows
keep the eq-bars (also on hover), active+paused rows fall back to
the static accent-coloured number.

Pilot for the wider rollout to other tracklists.

* feat(tracklists): roll out inline play + preview buttons

Mirrors the AlbumTrackList pilot across the remaining track-row
based lists: PlaylistDetail main tracks, Favorites, ArtistDetail
top tracks, RandomMix (both genre-mix and filtered-songs lists).

Track number stays static, the dedicated Play + Preview buttons
in the title cell take over click-to-play and click-to-preview.

* feat(settings): track preview toggle + configurable position and duration

Adds an opt-out switch and two sliders to Settings → Audio:
start position (0-90 % of track length, default 33 %) and preview
duration (5-60 s, default 30 s). The progress-ring animation
follows the duration via a CSS variable so the visual matches the
engine's auto-stop. Disabling the feature hides every inline
preview button via a single root-level data attribute, no per-row
conditional rendering required.

i18n keys added in all 8 locales.

* fix(audio): cancel preview when main playback (re)starts

audio_play, audio_play_radio, audio_resume and audio_stop did not
know about the parallel preview sink, so clicking Play on a track
that was currently being previewed left the preview running on top
of the freshly started main playback. New helper clears the resume
flag, bumps the preview generation, drops the sink and emits an
'interrupted' end event before any of those commands touches the
main sink.


* feat(settings): per-location track preview toggles

Splits the single trackPreviewsEnabled toggle into a master + 6 per-location
sub-toggles (suggestions, albums, playlists, favorites, artist, randomMix).
Master remains the kill switch; sub-toggles are only honoured when master
is on. Each tracklist container is marked with `data-preview-loc="<id>"` and
hidden via scoped CSS when the matching root attribute is "off".
startPreview now takes a location argument so the store can guard logic too.
i18n added in all 8 locales.


* fix(contextmenu): use ChevronsRight for Play Next to distinguish from preview
2026-05-01 09:11:28 +02:00
Frank Stellmacher ff47170736 Update CHANGELOG.md (#391) 2026-05-01 08:56:13 +02:00
Frank Stellmacher 225f7c1406 feat(themes): add Kanagawa, Atom One, 1984 palettes; regroup OSS Classics by family (#390)
* 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
2026-05-01 08:51:34 +02:00
cucadmuh b83c0f5e50 fix(release): retarget mismatched tags instead of failing (#389)
When an existing app-v tag points to a different commit than source_ref,
re-point it to the checked out source commit and continue publishing.
This preserves Source code archive correctness without blocking artifact release.
2026-04-30 22:12:45 +03:00
Frank Stellmacher 51413c80da Update CHANGELOG.md (#387) 2026-04-30 20:42:14 +02:00
Frank Stellmacher 41476ad846 Update CHANGELOG.md (#386) 2026-04-30 20:37:17 +02:00
Frank Stellmacher be4e214133 fix(equalizer): bail out when canvas has no laid-out dimensions (#384)
drawCurve read offsetWidth/offsetHeight unconditionally. When the canvas
sits inside a collapsed <details> (SettingsSubSection) on first paint,
both are 0, innerW becomes negative, the points-loop never runs, and
points[0][0] throws. Linux WebKitGTK happens to lay out the details
expanded on first render; macOS WebKit does not, so the same code path
unmounted the entire React tree because there is no ErrorBoundary above
Settings.

Bail early when innerW or innerH is non-positive — the ResizeObserver
already triggers a redraw once the canvas gets real dimensions.

Closes #382.
2026-04-30 20:19:10 +02:00
cucadmuh 316ff1d43b fix(release): pin tag creation to source_ref commit (#385)
Ensure GitHub release tags are created from the checked out channel ref and fail
early if an existing tag points to a different commit. This keeps Source code
archives aligned with built artifacts and prevents mixed-release snapshots.
2026-04-30 21:18:49 +03:00
cucadmuh 3501a82df6 rdocs(nix): refresh NixOS install guide for channels and CI (#380)
* docs(nix): refresh NixOS install guide for channels and CI

Align nixos-install.md with current automation: reusable verify-nix on
next/release, npmDepsHash sync on main, devShell vs shell.nix, nix run,
and pinning by branch or app-v tag. Link maintainers to RELEASE_PROCESS.

* docs(nix): steer production installs to release channel

Add a stability note and clarify pinning: prefer the release branch or
`app-v*` tags for everyday use; reserve main/next for contributors.
2026-04-30 03:29:28 +03:00
Frank Stellmacher 1846e4a1a6 fix(aur): use app-v prefixed source dir in build/package (#379)
GitHub's tag archive folder is named after the tag (with only a
leading 'v' stripped). Tags like 'app-v1.44.0' produce
'psysonic-app-v1.44.0/', not 'psysonic-1.44.0/', so the previous
'cd "psysonic-$pkgver"' failed with "Datei oder Verzeichnis nicht
gefunden". This was hotfixed on AUR as 1.44.0-2; mirror here so
the next bump doesn't regress.
2026-04-30 02:02:20 +02:00
github-actions[bot] dd73e2b36b chore(release): bump main to 1.45.0-dev (#375)
* chore(release): bump main to 1.45.0-dev

* chore(nix): sync npmDepsHash with package-lock.json

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-30 02:41:53 +03:00
Frank Stellmacher 18bf7d0837 chore(aur): bump pkgver to 1.44.0 (#377)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:34:38 +02:00
cucadmuh ef45b726ad fix(changelog): resolve embedded notes by semver core (#373)
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.
app-v1.44.0
2026-04-29 22:36:57 +00:00
Frank Stellmacher 1d569f6282 Update CHANGELOG.md (#371) 2026-04-29 23:51:21 +02:00
cucadmuh 106baec8fe chore: align Tauri package version with package.json on main (#370)
Cargo.toml and tauri.conf.json had stale RC-style semver; match root
package.json (1.44.0-dev) so local builds and artifact names stay consistent.
2026-04-29 21:46:59 +00:00
Frank Stellmacher 98644d859f Update CHANGELOG.md (#369) 2026-04-29 23:46:49 +02:00
cucadmuh 10ca1bc051 Feat/promote sync tauri version (#368)
* ci(release): sync Cargo/tauri versions after npm version on promote

Keep bundle artifact names aligned with package.json by updating
src-tauri/Cargo.toml and tauri.conf.json when promoting channel branches.

Made-with: Cursor

* ci(release): sync Cargo/tauri in post-release dev bump PR

Run the same package.json→Tauri sync after bumping main to the next -dev
version so local builds match auto-generated PR contents.
2026-04-29 21:38:48 +00:00
Frank Stellmacher e3ac4500ae Update CHANGELOG.md (#367) 2026-04-29 23:16:20 +02:00
Frank Stellmacher 4b9806ccc0 Update CHANGELOG.md (#366) 2026-04-29 23:13:37 +02:00
Frank Stellmacher 2bea55bedd feat(playlists): suggestion-row preview UX (#365)
* 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>
2026-04-29 23:02:31 +02:00