refactor(playback): move genreBrowsePlayback into the feature; utils/library now feature-free

genreBrowsePlayback is a "play X" queue builder -- it turns a genre seed into a
Track[] for the player, the genre analogue of playArtistShuffled which already
lives in features/playback/utils/playback. Consumed only by the 3 genre pages,
by no utils/library sibling, so the whole-file move is clean (no split). Its
Track/songToTrack edges become intra-feature; its remaining utils/library
siblings (advancedSearchLocal, albumBrowseSort, genreCatalogCountsCache,
genreAlbumBrowse, libraryReady) are plain infra deps.

utils/library now has ZERO @/features importers (source AND tests) -- the bulk
is ready to relocate to lib/library. tsc 0, lint 0, genre suite green.
This commit is contained in:
Psychotoxical
2026-06-30 17:00:57 +02:00
parent edab32d7ee
commit e5705f853e
5 changed files with 14 additions and 14 deletions
@@ -22,23 +22,23 @@ vi.mock('@/lib/api/subsonicClient', () => ({
libraryScopeForServer: vi.fn(() => 'music'), libraryScopeForServer: vi.fn(() => 'music'),
})); }));
vi.mock('./libraryReady', () => ({ vi.mock('@/utils/library/libraryReady', () => ({
libraryIsReady: vi.fn(), libraryIsReady: vi.fn(),
})); }));
// Spread the real leaf module so other consumers pulled in transitively (the // Spread the real leaf module so other consumers pulled in transitively (the
// album barrel reaches this via the artist↔album edge → useGenreAlbumBrowse needs // album barrel reaches this via the artist↔album edge → useGenreAlbumBrowse needs
// GENRE_ALBUM_FIRST_PAGE); only fetchGenreAlbumTotal is stubbed here. // GENRE_ALBUM_FIRST_PAGE); only fetchGenreAlbumTotal is stubbed here.
vi.mock('./genreAlbumBrowse', async (importOriginal) => ({ vi.mock('@/utils/library/genreAlbumBrowse', async (importOriginal) => ({
...(await importOriginal<typeof import('./genreAlbumBrowse')>()), ...(await importOriginal<typeof import('@/utils/library/genreAlbumBrowse')>()),
fetchGenreAlbumTotal: vi.fn(), fetchGenreAlbumTotal: vi.fn(),
})); }));
import { libraryAdvancedSearch, libraryGetGenreAlbumCounts } from '@/lib/api/library'; import { libraryAdvancedSearch, libraryGetGenreAlbumCounts } from '@/lib/api/library';
import { fetchAllSongsByGenre, getGenres } from '@/lib/api/subsonicGenres'; import { fetchAllSongsByGenre, getGenres } from '@/lib/api/subsonicGenres';
import { fetchGenreAlbumTotal } from './genreAlbumBrowse'; import { fetchGenreAlbumTotal } from '@/utils/library/genreAlbumBrowse';
import { resetGenreCatalogCountsCacheForTests } from './genreCatalogCountsCache'; import { resetGenreCatalogCountsCacheForTests } from '@/utils/library/genreCatalogCountsCache';
import { libraryIsReady } from './libraryReady'; import { libraryIsReady } from '@/utils/library/libraryReady';
describe('genreBrowsePlayback', () => { describe('genreBrowsePlayback', () => {
beforeEach(() => { beforeEach(() => {
@@ -8,8 +8,8 @@ import { libraryScopeForServer } from '@/lib/api/subsonicClient';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { shuffleArray } from '@/lib/util/shuffleArray'; import { shuffleArray } from '@/lib/util/shuffleArray';
import { trackToSong } from './advancedSearchLocal'; import { trackToSong } from '@/utils/library/advancedSearchLocal';
import { type AlbumBrowseSort } from './albumBrowseSort'; import { type AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
import { import {
genreCatalogCacheKey, genreCatalogCacheKey,
getInflightGenreCatalog, getInflightGenreCatalog,
@@ -17,9 +17,9 @@ import {
peekGenreCatalogCache, peekGenreCatalogCache,
trackInflightGenreCatalog, trackInflightGenreCatalog,
writeGenreCatalogCache, writeGenreCatalogCache,
} from './genreCatalogCountsCache'; } from '@/utils/library/genreCatalogCountsCache';
import { fetchGenreAlbumTotal } from './genreAlbumBrowse'; import { fetchGenreAlbumTotal } from '@/utils/library/genreAlbumBrowse';
import { libraryIsReady } from './libraryReady'; import { libraryIsReady } from '@/utils/library/libraryReady';
/** Drop genres with no indexed albums/tracks (stale server list or orphan rows). */ /** Drop genres with no indexed albums/tracks (stale server list or orphan rows). */
export function filterGenresWithContent(genres: SubsonicGenre[]): SubsonicGenre[] { export function filterGenresWithContent(genres: SubsonicGenre[]): SubsonicGenre[] {
+1 -1
View File
@@ -22,7 +22,7 @@ import { usePlayerStore } from '@/features/playback/store/playerStore';
import { import {
fetchGenreAlbumCount, fetchGenreAlbumCount,
fetchGenreTracksForPlayback, fetchGenreTracksForPlayback,
} from '../utils/library/genreBrowsePlayback'; } from '@/features/playback/utils/playback/genreBrowsePlayback';
import { lookupGenreAlbumCount } from '../utils/library/genreCatalogCountsCache'; import { lookupGenreAlbumCount } from '../utils/library/genreCatalogCountsCache';
import { libraryScopeForServer } from '@/lib/api/subsonicClient'; import { libraryScopeForServer } from '@/lib/api/subsonicClient';
import { import {
+1 -1
View File
@@ -7,7 +7,7 @@ import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../constants/appScroll';
import { subscribeLibrarySyncIdle } from '@/lib/api/library'; import { subscribeLibrarySyncIdle } from '@/lib/api/library';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useLibraryIndexStore } from '../store/libraryIndexStore'; import { useLibraryIndexStore } from '../store/libraryIndexStore';
import { fetchGenreCatalog, filterGenresWithContent } from '../utils/library/genreBrowsePlayback'; import { fetchGenreCatalog, filterGenresWithContent } from '@/features/playback/utils/playback/genreBrowsePlayback';
import { libraryScopeForServer } from '@/lib/api/subsonicClient'; import { libraryScopeForServer } from '@/lib/api/subsonicClient';
import { peekGenreCatalogCache } from '../utils/library/genreCatalogCountsCache'; import { peekGenreCatalogCache } from '../utils/library/genreCatalogCountsCache';
import { resolveIndexKey } from '../utils/server/serverIndexKey'; import { resolveIndexKey } from '../utils/server/serverIndexKey';
+1 -1
View File
@@ -13,7 +13,7 @@ import {
fetchRandomMixSongsUntilFull, fetchRandomMixSongsUntilFull,
getMixMinRatingsConfigFromAuth, getMixMinRatingsConfigFromAuth,
} from '../utils/mix/mixRatingFilter'; } from '../utils/mix/mixRatingFilter';
import { fetchGenreCatalog } from '../utils/library/genreBrowsePlayback'; import { fetchGenreCatalog } from '@/features/playback/utils/playback/genreBrowsePlayback';
import { AUDIOBOOK_GENRES, filterRandomMixSongs } from '../utils/componentHelpers/randomMixHelpers'; import { AUDIOBOOK_GENRES, filterRandomMixSongs } from '../utils/componentHelpers/randomMixHelpers';
import RandomMixHeader from '../components/randomMix/RandomMixHeader'; import RandomMixHeader from '../components/randomMix/RandomMixHeader';
import RandomMixFiltersPanel from '../components/randomMix/RandomMixFiltersPanel'; import RandomMixFiltersPanel from '../components/randomMix/RandomMixFiltersPanel';