111 of 122 top-level src/utils/ files move into 16 topic folders (audio,
cache, cover, share, server, playback, playlist, deviceSync, waveform,
mix, format, export, changelog, ui, perf, componentHelpers). True
singletons with no cluster stay at the utils/ root.
Pure file-move: a path-aware codemod rewrote 539 relative-import
specifiers across 275 files; no logic touched. The hot-path coverage
gate list (.github/frontend-hot-path-files.txt) is updated to the new
paths for the 11 gated utils files — a mechanical consequence of the
move, not a CI change. tsc is green.
Four more domain-eng modules out of `api/subsonic.ts`:
- `subsonicPlaylists.ts` — `getPlaylists`/`getPlaylist`/`createPlaylist`
/`updatePlaylist`/`updatePlaylistMeta`/`uploadPlaylistCoverArt`/
`uploadArtistImage`/`deletePlaylist`. The two upload helpers use
the Tauri-side CORS bypass (`upload_playlist_cover` /
`upload_artist_image`).
- `subsonicPlayQueue.ts` — `getPlayQueue`/`savePlayQueue`.
- `subsonicRadio.ts` — Internet Radio CRUD (4 fns), Tauri-side cover
art ops (3 fns), RadioBrowser search/top + `fetchUrlBytes`.
- `subsonicStatistics.ts` — `fetchStatisticsLibraryAggregates`/
`fetchStatisticsOverview`/`fetchStatisticsFormatSample` with their
three per-server-folder caches and the `statisticsPageCacheKey`
helper. `STATS_CACHE_TTL` renamed from the misleadingly-shared
`RATING_CACHE_TTL` constant.
Also: drop ~20 now-unused type/value imports from `subsonic.ts`, trim
three orphan jsdoc comments left behind by earlier slices, fix four
`vi.mock` targets (`queueSync`, `playerStore.persistence`) plus the
dynamic `await import('../api/subsonic')` calls in `ContextMenu.tsx`
to point at the new module paths.
Pure code-move. subsonic.ts: 561 → 144 LOC (−417). What's left:
ping/pingWithCredentials/probeInstantMix/scheduleInstantMixProbe +
internal `apiWithCredentials`/`restBaseFromUrl`.
Three domain-eng modules peel ~316 LOC of fetch/mapping out of
`api/subsonic.ts`:
- `subsonicLibrary.ts` — browse + random + per-song fetch
(`getMusicDirectory`, `getMusicIndexes`, `getMusicFolders`,
`getRandomAlbums`, `getAlbumList`, `getRandomSongs`,
`getRandomSongsFiltered`, `getSong`, `getAlbum`,
`filterSongsToActiveLibrary`, `similarSongsRequestCount`, plus
the private `albumIdsInActiveLibraryScope` cache).
- `subsonicArtists.ts` — artist endpoints (`getArtists`, `getArtist`,
`getArtistInfo`, `getTopSongs`, `getSimilarSongs2`,
`getSimilarSongs`). Uses Library's `filterSongsToActiveLibrary` and
`similarSongsRequestCount` for the per-library scoping fallback.
- `subsonicRatings.ts` — `parseSubsonicEntityStarRating` parser plus
`prefetchArtistUserRatings` and `prefetchAlbumUserRatings` workers
with the shared 7-min cache. Calls back into Library/Artists for
the per-id fetch.
51 external call sites migrated to direct imports. No re-export
shims in `subsonic.ts`. Statistics endpoints still in `subsonic.ts`
keep their `RATING_CACHE_TTL` constant locally (same 7-min window).
subsonic.ts: 1078 → 762 LOC (−316).
First Phase F slice. Splits the 1333-LOC `api/subsonic.ts` along its
two most obvious axes:
- `subsonicTypes.ts` — all ~24 exported interfaces + type aliases
(album/song/artist/playlist/directory/genre/now-playing/radio,
random-songs filters, three statistics shapes, search + starred
results, AlbumInfo, structured-lyrics types, etc.) plus the
`RADIO_PAGE_SIZE` constant.
- `subsonicClient.ts` — token-auth + `getClient` + `api<T>()` +
`libraryFilterParams` + `secureRandomSalt` / `getAuthParams` /
`SUBSONIC_CLIENT`. The credential-bearing API helpers
(`pingWithCredentials`, `apiWithCredentials`, `restBaseFromUrl`,
`probeInstantMixWithCredentials`) stay in `subsonic.ts` for now —
they could move into the client module in a follow-up.
66 external call sites migrated to direct imports from the new
modules (no re-export shims in `subsonic.ts`). Pure code-move;
contract test stays green.
subsonic.ts: 1333 → 1078 LOC (−255).
The squash-merge of PR #419 was performed against an outdated PR base
that predated several main-side refactors and features. The resulting
squash inadvertently re-introduced files that had already been removed
(`src-tauri/src/audio.rs` monolith, `app-icon.png`) and reverted main's
content for ~20 files (`src-tauri/src/lib.rs` decompose, `src/App.tsx`
animation-pause, `src/components/AlbumRow.tsx` headerExtra, etc).
This commit:
* Restores all collateral-damage files to their pre-#419 main state
(5662dafe), including the audio module split, lib decompose,
Cargo.toml `windows` dep, animation-pause-on-blur logic, and
`reducedAnimations` toggle plumbing in WaveformSeek/Settings.
* Keeps the genuine #419 work intact: tri-state duration toggle,
position counter, persistent Now Playing collapse, animated EQ
indicator (in QueuePanel.tsx, 8 locales, authStore, components.css).
* Merges authStore.ts so both `reducedAnimations` (from #426) and
`queueNowPlayingCollapsed` (from #419) coexist.
* Adds the `.eq-bars.paused` CSS rule manually since components.css
needed a 5662dafe base + the single #419 addition.
* Fixes a latent type error in OverlayScrollArea.tsx (`useRef<T>(null)`
is `RefObject<T>` / read-only under current `@types/react`; widened to
`useRef<T | null>(null)` so the existing `wrapRef.current = el`
assignment compiles).
Verified locally with `cargo check` and `npm run build` — both green.
* feat(queue): add ETA display, equalizer indicator and collapsible now playing
* deleted endsAt and showDuration strings, changed eta update to 30s
* feat(queue): ETA tooltip, persistent Now Playing collapse, EQ bar pause, remove redundant Play icon
* feat(queue): fold ETA into existing total/remaining toggle as third mode
The standalone ETA span next to the track counter is removed; instead the
clickable duration label in the queue header now rotates through three
modes per click: total → remaining → eta → total. Counter (N/M) stays
where it was.
ETA mode keeps the live-feel treatment from the original PR (accent
colour while playing, muted at 50% opacity when paused). The other two
modes use plain accent.
i18n: queue.etaTooltip removed (no longer a separate descriptive label),
queue.showEta added as the action tooltip ('Show estimated end time')
in all 8 locales — matches the showRemaining / showTotal pattern.
* docs(changelog): add #419 queue UX improvements entry
Adds the [1.45.0] / Added entry for this PR's queue panel refinements
(position counter, tri-state duration toggle including ETA, collapsible
Now Playing section, animated EQ indicator).
---------
Co-authored-by: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com>
* 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
Remove the 10-page cap in fetchStatisticsLibraryAggregates — the loop now runs until
the server returns a partial page, so albums/songs/playtime reflect the full library
regardless of size (previously capped at 5,000 albums). Switched sort type from
'newest' to 'alphabeticalByName' for a stable pagination order.
Add a tooltip on the Artists stat card explaining it shows album artists only (Subsonic
API limitation — track-level featured/guest artists without their own album are not
included). Tooltip added in all 8 locales. Labels with a tooltip get a dotted underline
and cursor:help as visual hint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 7-minute TTL caches (same idea as rating prefetch) for overview
album strips, random-song format sample, and paginated library
aggregates; key by server and music folder.
Introduce formatHumanHoursMinutes with common.duration* strings in all
locales for Statistics and playlist duration labels.
Refresh RU/ZH statistics and nav wording; fix zh playtime label.
getGenres() ignores musicFolderId. Derive genre counts from the same
paginated getAlbumList('newest') scan used for playtime and totals,
using each album's genre and songCount.
getGenres() is not musicFolder-scoped, so summing genre counts showed
global totals while other statistics respected the library filter.
Derive album and track counts from the same paginated getAlbumList
pass used for playtime, with the same 5000-album cap and ≥ prefix when
capped.
The implementation was unreliable (depended on starred songs + session-only
overrides) so the sections are removed entirely. All PR#130 rating logic
(StarRating, setRating API calls, userRatingOverrides) is untouched.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Song ratings in context menu + player bar, entity ratings (PR #130),
5 new seekbar styles, custom Linux title bar, album multi-select,
mix rating filter, top-rated stats, compilation filter, scroll reset.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apply musicFolderId across Subsonic requests, bump a filter version so library views reload, and add a fixed-position sidebar dropdown (with capped height when there are many folders).
Internet Radio full release (HTML5 engine, card UI, RadioDirectoryModal,
cover upload), Backup/Restore, Albums year filter, Statistics Library
Insights (playtime/genres/formats), Playlist cover upload, resizable
tracklist columns for Playlists & Favorites, crossfade fine control,
Settings Storage tab redesign, various fixes and UI polish.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Statistics page: library stat cards, recently played, most played, highest rated, genre chart
- Playlists page: list layout with sort (Name/Tracks/Duration) and filter input
- Favorites songs: full tracklist layout with artist column, context menu, enqueue-all button
- AlbumDetail: extracted AlbumHeader and AlbumTrackList components
- Artist cards: square cover, same sizing as album cards (clamp 140-180px)
- Random Albums: removed renderKey remount, added loadingRef guard, fixed manual refresh race
- Context menu: "Go to Album" option for song and queue-item types
- Queue panel meta box: artist → artist page, album → album page, removed year
- Random Mix: hover persistence via .context-active class while context menu is open
- i18n: "Warteschlange" consistently used for queue in German
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>