Commit Graph

10 Commits

Author SHA1 Message Date
Maxim Isaev e7431b94b8 feat(search): queue pasted share links from Live Search and mobile search
Implement share-link detection in search (track, queue, album, artist,
composer): enqueue tracks/queues without interrupting playback; preview
album/artist/composer without switching the active server; queue preview
modal with scrollable track list. Based on community PR #551.

Co-authored-by: Daniel Wagner <daniel.iuser@icloud.com>
2026-05-15 13:38:35 +03:00
Frank Stellmacher 7a7a9f5e6b refactor(utils): group utils/ files into topic folders (Phase L, part 1) (#689)
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.
2026-05-14 14:27:44 +02:00
Frank Stellmacher 9606a99efb refactor(api): F.50 — extract 7 small domain modules (#615)
Seven domain-eng splits peel ~200 LOC of read endpoints out of
`api/subsonic.ts`:

- `subsonicStreamUrl.ts` — `buildStreamUrl`, `coverArtCacheKey`,
  `buildCoverArtUrl`, `buildDownloadUrl` (token-signed URL builders
  for the four /rest endpoints we hand to the browser).
- `subsonicStarRating.ts` — `getStarred`, `star`, `unstar`,
  `setRating`, `probeEntityRatingSupport`. `setRating` still triggers
  the lazy `navidromeBrowse` cache invalidation; the same-folder
  lazy import path is preserved.
- `subsonicSearch.ts` — `search`, `searchSongsPaged`.
- `subsonicScrobble.ts` — `scrobbleSong`, `reportNowPlaying`,
  `getNowPlaying`.
- `subsonicAlbumInfo.ts` — `getAlbumInfo2`.
- `subsonicLyrics.ts` — `getLyricsBySongId`.
- `subsonicGenres.ts` — `getGenres`, `getAlbumsByGenre`.

63 external call sites migrated to direct imports. Four `vi.mock`
targets in the store-level tests pointed at `../api/subsonic` and
were updated to the new module paths.

Pure code-move. subsonic.ts: 762 → 561 LOC (−201).
2026-05-13 00:46:13 +02:00
Frank Stellmacher 72030f17fd refactor(api): F.48 — extract subsonic types + client primitives (#613)
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).
2026-05-13 00:05:58 +02:00
Frank Stellmacher 9fac6eb490 refactor(player): E.42 — migrate playerStore re-exports to direct imports (#607)
Migrates ~74 call sites away from the playerStore re-export shims that
were kept during M0–E.41 to avoid touching 30+ imports per PR. Now
that the bigger refactor work is done, each helper goes back to its
real home:

- `initAudioListeners`, `installQueueUndoHotkey`, `flushPlayQueuePosition`
  → from their own store modules
- `getPlaybackProgressSnapshot`, `subscribePlaybackProgress`,
  `PlaybackProgressSnapshot` → from `playbackProgress`
- `resolveReplayGainDb`, `shuffleArray`, `songToTrack`
  → from `utils/*`
- `_resetQueueUndoStacksForTest`, `consumePendingQueueListScrollTop`,
  `registerQueueListScrollTopReader` → from `queueUndo`
- `PlayerState`, `Track` types → from `playerStoreTypes`

Drops the corresponding 13 re-export stubs from `playerStore.ts` and
the now-unused imports. Also drops dead section banners + per-wrapper
comments above one-line action delegates. Trims one stale "(separate
PR)" note in `transportLightActions.ts` since that follow-up landed
in E.39.

`playerStore.ts`: 180 → 112 LOC (−68). Down from Phase E's starting
3732 LOC.

`bootstrap.test.ts` mock target updated from `../store/playerStore`
to `../store/queueUndoHotkey` to keep the spy reachable after the
import change.
2026-05-12 22:46:13 +02:00
cucadmuh c3d37546cf Feat/search improvements (#470)
* feat(covers): race sibling downscale vs fetch, search thumb priorities

Run getCoverArt and client downscale in parallel when another size of the
same cover is cached; first successful result wins and aborts the other path.
Await both branches so inflight bookkeeping does not detach early.

Extend the cover cache size roster so provisional siblings resolve for sizes
used in the UI (e.g. 400/600/800, 48/96).

CachedImage: fetchQueueBias for live/mobile search (artist thumbnails ahead of
albums in fetch-slot ordering); configurable observeRootMargin with a wider
default to prepare priority slightly before elements enter view.

Mobile search adds round artist-thumb styling; add shared cover blob downscale
helper.

* perf(image-cache): batch sibling IDB reads and guard cover size registry

Use one read transaction when probing IndexedDB for sibling cover keys.
Extract COVER_ART_REGISTERED_SIZES and add Vitest coverage so every literal
coverArtCacheKey(_, size) in src stays aligned with sibling invalidation.
Honor AbortSignal during JPEG encode in downscaleCoverBlob.
2026-05-06 01:26:30 +03:00
Frank Stellmacher 93b724fc65 fix(search,ctx): enqueue on live-search click + reposition CM with right-click (#298)
Two coupled UX fixes around the header live search and the global context
menu, born from the same testing pass.

LiveSearch / MobileSearchOverlay
- Single click on a song no longer calls playTrack(track) without a
  queue argument. The store fell back to the existing queue, didn't
  find the new track, and ended up playing something the user couldn't
  navigate or see in the queue list — the player header showed metadata
  but the queue itself didn't contain it.
- New behaviour: enqueue([track]) + "Added to queue" toast. Whatever was
  playing keeps playing; the new track lands at the bottom and is
  visible/navigable. Mobile gets the same behaviour (tap-only).
- Desktop also gets a right-click context menu on song rows for users
  who want immediate playback or different routing (Play Now, Play Next,
  Add to Playlist, etc.). The dropdown stays open while the CM is up,
  and the row picks up the .context-active highlight so the user can
  see which song the menu refers to.

ContextMenu
- Removed the transparent fullscreen backdrop (z-index 998) that
  previously caught outside clicks. It also blocked right-clicks from
  reaching elements *underneath* it — so right-clicking a different
  song row to reposition the menu hit the backdrop instead, closed the
  menu, and never opened a new one for the row the user actually
  pointed at.
- Replaced with a document-level mousedown listener (gated on
  `contextMenu.isOpen`) that closes the menu when the click lands
  outside `menuRef`. Standard outside-click pattern, doesn't occlude
  the underlying UI, and right-clicking another row now naturally
  pivots the CM to that row.

i18n: new search.addedToQueueToast key in 8 locales.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:32:18 +02:00
Frank Stellmacher 9da1d643f7 perf(search): use CachedImage for result thumbs to stop re-render download storms (#245)
The sidebar LiveSearch and mobile search overlay rendered cover thumbs via
raw <img src={buildCoverArtUrl(...)}> instead of going through the
IndexedDB-backed CachedImage path used everywhere else in the app.

buildCoverArtUrl() mints a fresh URL on every call (random salt + a new
MD5 token per Subsonic spec), so the browser cache was permanently
defeated for these thumbs — every re-render of the dropdown produced
new URLs and the browser dispatched HTTP requests for every "new"
image.

While typing in the search field this multiplied: each keystroke
triggered a re-render with fresh URLs for every visible cover, and
after the 300 ms debounce a fresh search() result triggered another
wave. Five visible album thumbs × five keystrokes ≈ 50 redundant cover
fetches in 1.5 s, manifesting as periodic typing delays.

Switch all three call sites to CachedImage with the stable
coverArtCacheKey() — same pattern CLAUDE.md explicitly recommends:

    buildCoverArtUrl() generates a new ephemeral URL every call —
    browser cache is useless. Use coverArtCacheKey(id, size) +
    CachedImage for <img> tags.

Confirmed locally: typing in the search field is noticeably smoother.

Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:37:47 +02:00
Maxim Isaev f08619fb3d feat(subsonic): per-server music folder filter and sidebar picker
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).
2026-04-07 13:59:59 +03:00
Psychotoxical 50ac1b8284 feat: v1.34.0 — Mobile UI Early Preview, Russian 2, macOS network fix
Co-authored-by: kilyabin <kilyabin@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:46:13 +02:00