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'),
}));
vi.mock('./libraryReady', () => ({
vi.mock('@/utils/library/libraryReady', () => ({
libraryIsReady: vi.fn(),
}));
// Spread the real leaf module so other consumers pulled in transitively (the
// album barrel reaches this via the artist↔album edge → useGenreAlbumBrowse needs
// GENRE_ALBUM_FIRST_PAGE); only fetchGenreAlbumTotal is stubbed here.
vi.mock('./genreAlbumBrowse', async (importOriginal) => ({
...(await importOriginal<typeof import('./genreAlbumBrowse')>()),
vi.mock('@/utils/library/genreAlbumBrowse', async (importOriginal) => ({
...(await importOriginal<typeof import('@/utils/library/genreAlbumBrowse')>()),
fetchGenreAlbumTotal: vi.fn(),
}));
import { libraryAdvancedSearch, libraryGetGenreAlbumCounts } from '@/lib/api/library';
import { fetchAllSongsByGenre, getGenres } from '@/lib/api/subsonicGenres';
import { fetchGenreAlbumTotal } from './genreAlbumBrowse';
import { resetGenreCatalogCountsCacheForTests } from './genreCatalogCountsCache';
import { libraryIsReady } from './libraryReady';
import { fetchGenreAlbumTotal } from '@/utils/library/genreAlbumBrowse';
import { resetGenreCatalogCountsCacheForTests } from '@/utils/library/genreCatalogCountsCache';
import { libraryIsReady } from '@/utils/library/libraryReady';
describe('genreBrowsePlayback', () => {
beforeEach(() => {
@@ -8,8 +8,8 @@ import { libraryScopeForServer } from '@/lib/api/subsonicClient';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { shuffleArray } from '@/lib/util/shuffleArray';
import { trackToSong } from './advancedSearchLocal';
import { type AlbumBrowseSort } from './albumBrowseSort';
import { trackToSong } from '@/utils/library/advancedSearchLocal';
import { type AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
import {
genreCatalogCacheKey,
getInflightGenreCatalog,
@@ -17,9 +17,9 @@ import {
peekGenreCatalogCache,
trackInflightGenreCatalog,
writeGenreCatalogCache,
} from './genreCatalogCountsCache';
import { fetchGenreAlbumTotal } from './genreAlbumBrowse';
import { libraryIsReady } from './libraryReady';
} from '@/utils/library/genreCatalogCountsCache';
import { fetchGenreAlbumTotal } from '@/utils/library/genreAlbumBrowse';
import { libraryIsReady } from '@/utils/library/libraryReady';
/** Drop genres with no indexed albums/tracks (stale server list or orphan rows). */
export function filterGenresWithContent(genres: SubsonicGenre[]): SubsonicGenre[] {
+1 -1
View File
@@ -22,7 +22,7 @@ import { usePlayerStore } from '@/features/playback/store/playerStore';
import {
fetchGenreAlbumCount,
fetchGenreTracksForPlayback,
} from '../utils/library/genreBrowsePlayback';
} from '@/features/playback/utils/playback/genreBrowsePlayback';
import { lookupGenreAlbumCount } from '../utils/library/genreCatalogCountsCache';
import { libraryScopeForServer } from '@/lib/api/subsonicClient';
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 { useAuthStore } from '../store/authStore';
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 { peekGenreCatalogCache } from '../utils/library/genreCatalogCountsCache';
import { resolveIndexKey } from '../utils/server/serverIndexKey';
+1 -1
View File
@@ -13,7 +13,7 @@ import {
fetchRandomMixSongsUntilFull,
getMixMinRatingsConfigFromAuth,
} 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 RandomMixHeader from '../components/randomMix/RandomMixHeader';
import RandomMixFiltersPanel from '../components/randomMix/RandomMixFiltersPanel';