refactor(lib): relocate the local-library subsystem utils/library -> lib/library

utils/library is the local-index query/browse/sync engine -- feature-free shared
infra consumed by the album, search, genre, artist and offline features. With
its last feature edges removed (queue resolver, shuffleArray, albumBrowseCatalogChunk,
genreBrowsePlayback all relocated earlier this branch), the whole directory is
pure infra and belongs under lib/.

Whole-directory move (58 files, 76 consumers rewritten) via resolver-based
rewrite: cross-cluster relatives absolutized to @/, intra-cluster siblings kept
relative. utils/library is gone; lib/library imports zero @/features. Three
stale doc comments in the album/search/artist barrels repointed.

tsc 0, lint 0, full suite 2353/2353, production build OK.
This commit is contained in:
Psychotoxical
2026-06-30 17:06:03 +02:00
parent e5705f853e
commit 2756cb7698
129 changed files with 139 additions and 139 deletions
@@ -20,8 +20,8 @@ import { sanitizeHtml } from '@/lib/util/sanitizeHtml';
import { OpenArtistRefInline } from '@/features/artist';
import { tooltipAttrs } from '@/ui/tooltipAttrs';
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
import { deriveAlbumGenreTags } from '@/utils/library/genreTags';
import { genreColor } from '@/utils/library/genreColor';
import { deriveAlbumGenreTags } from '@/lib/library/genreTags';
import { genreColor } from '@/lib/library/genreColor';
/** True when the album artist label means "no single artist" — `getArtistInfo`
* has nothing meaningful to return for these, so the Artist Bio entry is hidden.
@@ -5,8 +5,8 @@ import { ndListLosslessAlbumsPage } from '@/lib/api/navidromeBrowse';
import AlbumRow from '@/features/album/components/AlbumRow';
import { useAuthStore } from '@/store/authStore';
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { runLocalLosslessAlbums } from '@/utils/library/browseTextSearch';
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
import { runLocalLosslessAlbums } from '@/lib/library/browseTextSearch';
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
interface Props {
disableArtwork?: boolean;
@@ -13,8 +13,8 @@ import {
} from '@/cover/coverTraffic';
import { coverEnsureQueueBacklog, coverEnsureResumePump, coverEnsureSubscribeBacklogDrain } from '@/cover/ensureQueue';
import { dedupeById } from '@/lib/util/dedupeById';
import { albumBrowseCompScanComplete, albumBrowseCompFilterClientOnly } from '@/utils/library/albumCompilation';
import type { AlbumCompFilter } from '@/utils/library/albumCompilation';
import { albumBrowseCompScanComplete, albumBrowseCompFilterClientOnly } from '@/lib/library/albumCompilation';
import type { AlbumCompFilter } from '@/lib/library/albumCompilation';
import {
albumBrowseHasGenreFilter,
albumBrowseHasServerFilters,
@@ -24,12 +24,12 @@ import {
fetchLocalAlbumCatalogChunk,
type AlbumBrowseQuery,
type GenreFilterOption,
} from '@/utils/library/albumBrowseLoad';
} from '@/lib/library/albumBrowseLoad';
import { libraryScopeForServer } from '@/lib/api/subsonicClient';
import {
ALBUM_YEAR_FILTER_DEBOUNCE_MS,
resolveAlbumYearBounds,
} from '@/utils/library/albumYearFilter';
} from '@/lib/library/albumYearFilter';
import { loadOfflineAlbumBrowseInitial } from '@/features/offline';
import { useOfflineBrowseReloadToken } from '@/features/offline';
import {
@@ -14,7 +14,7 @@ import {
isAlbumDetailPath,
useAlbumBrowseSessionStore,
} from '@/features/album/store/albumBrowseSessionStore';
import type { AlbumBrowseSort } from '@/utils/library/browseTextSearch';
import type { AlbumBrowseSort } from '@/lib/library/browseTextSearch';
import { shouldRestoreAlbumBrowseSession } from '@/utils/navigation/albumDetailNavigation';
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
@@ -3,8 +3,8 @@ import {
ALBUM_YEAR_MAX,
ALBUM_YEAR_MIN,
type AlbumCatalogYearRange,
} from '@/utils/library/albumYearFilter';
import { fetchAlbumCatalogYearBounds } from '@/utils/library/albumCatalogYearBounds';
} from '@/lib/library/albumYearFilter';
import { fetchAlbumCatalogYearBounds } from '@/lib/library/albumCatalogYearBounds';
const DEFAULT: AlbumCatalogYearRange = { min: ALBUM_YEAR_MIN, max: ALBUM_YEAR_MAX };
@@ -17,7 +17,7 @@ import {
offlineLocalBrowseEnabled,
} from '@/features/offline';
import { readDetailServerId } from '@/utils/navigation/detailServerScope';
import { libraryIsReady } from '@/utils/library/libraryReady';
import { libraryIsReady } from '@/lib/library/libraryReady';
import {
shouldAttemptSubsonicForActiveServer,
shouldAttemptSubsonicForServer,
@@ -7,7 +7,7 @@ import {
raceBrowseWithLocalFallback,
runLocalBrowseAlbums,
runNetworkBrowseAlbums,
} from '@/utils/library/browseTextSearch';
} from '@/lib/library/browseTextSearch';
import { useOfflineBrowseContext } from '@/features/offline';
import { offlineLocalBrowseEnabled, searchOfflineLocalAlbums } from '@/features/offline';
@@ -1,12 +1,12 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
import { dedupeById } from '@/lib/util/dedupeById';
import type { AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
import type { AlbumBrowseSort } from '@/lib/library/albumBrowseSort';
import {
fetchGenreAlbumPage,
GENRE_ALBUM_CATALOG_CHUNK,
GENRE_ALBUM_FIRST_PAGE,
} from '@/utils/library/genreAlbumBrowse';
} from '@/lib/library/genreAlbumBrowse';
import { useClientSliceInfiniteScroll } from '@/hooks/useClientSliceInfiniteScroll';
import { useInpageScrollSentinel } from '@/hooks/useInpageScrollSentinel';
+1 -1
View File
@@ -5,7 +5,7 @@
* the album session store, per-album offline status, and album export helpers.
*
* Stays OUT (library-core / cross-cutting, consumed by this feature, not owned):
* the `utils/library/album*` browse query engine (shared with offline + other
* the `lib/library/album*` browse query engine (shared with offline + other
* browse surfaces → M4), `albumDetailNavigation` (cross-cutting nav → M4),
* `playAlbum` (playback action → playback), `starredAlbumIndexSync` (index
* sync, core), the album context-menu items (context-menu subsystem), the
+2 -2
View File
@@ -43,8 +43,8 @@ import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
import { albumGridWarmCovers } from '@/cover/layoutSizes';
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
import LosslessModeBanner from '@/components/LosslessModeBanner';
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
import { isLosslessMode } from '@/utils/library/losslessMode';
import { isLosslessSuffix } from '@/lib/library/losslessFormats';
import { isLosslessMode } from '@/lib/library/losslessMode';
import { readDetailServerId } from '@/utils/navigation/detailServerScope';
import { useOfflineBrowseContext } from '@/features/offline';
import { offlineActionPolicy } from '@/features/offline';
+4 -4
View File
@@ -43,15 +43,15 @@ import { peekAlbumBrowseScrollRestore } from '@/features/album/store/albumBrowse
import { readAlbumBrowseRestore } from '@/utils/navigation/albumDetailNavigation';
import { albumArtistDisplayName } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
import { useAlbumCatalogYearBounds } from '@/features/album/hooks/useAlbumCatalogYearBounds';
import type { AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
import { resolveAlbumYearBounds } from '@/utils/library/albumYearFilter';
import type { AlbumBrowseSort } from '@/lib/library/albumBrowseSort';
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
import { resolveAlbumYearBounds } from '@/lib/library/albumYearFilter';
import {
filterAlbumsByCompilation,
filterAlbumsByGenres,
filterAlbumsByStarred,
filterAlbumsByYearBounds,
} from '@/utils/library/albumBrowseFilters';
} from '@/lib/library/albumBrowseFilters';
import { useScopedBrowseSearchQuery } from '@/store/liveSearchScopeStore';
type SortType = AlbumBrowseSort;
+2 -2
View File
@@ -4,7 +4,7 @@ import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import AlbumCard from '@/features/album/components/AlbumCard';
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
import { ndListLosslessAlbumsPage } from '@/lib/api/navidromeBrowse';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '@/store/authStore';
@@ -38,7 +38,7 @@ import {
runLocalAlbumBrowsePage,
sortSubsonicAlbums,
type AlbumBrowseSort,
} from '@/utils/library/browseTextSearch';
} from '@/lib/library/browseTextSearch';
/** Local index page size — SQLite is cheap; larger pages than the network walk. */
const LOCAL_PAGE_SIZE = 30;
+1 -1
View File
@@ -15,7 +15,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
import { useAuthStore } from '@/store/authStore';
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '@/utils/mix/mixRatingFilter';
import { runLocalRandomAlbums, runLocalAlbumsByGenres } from '@/utils/library/browseTextSearch';
import { runLocalRandomAlbums, runLocalAlbumsByGenres } from '@/lib/library/browseTextSearch';
import { useOfflineStore } from '@/features/offline';
import { useDownloadModalStore } from '@/features/offline';
import { invoke } from '@tauri-apps/api/core';
@@ -1,6 +1,6 @@
import { create } from 'zustand';
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
import type { AlbumBrowseSort } from '@/utils/library/browseTextSearch';
import type { AlbumBrowseSort } from '@/lib/library/browseTextSearch';
export const DEFAULT_ALBUM_BROWSE_SORT: AlbumBrowseSort = 'alphabeticalByName';
@@ -2,8 +2,8 @@ import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
import { dedupeById } from '@/lib/util/dedupeById';
import { isOfflineBrowseActive } from '@/features/offline';
import { loadOfflineAlbumCatalogChunk } from '@/features/offline';
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes';
import { fetchLocalAlbumCatalogChunk } from '@/utils/library/albumBrowseLoad';
import type { AlbumBrowseQuery } from '@/lib/library/albumBrowseTypes';
import { fetchLocalAlbumCatalogChunk } from '@/lib/library/albumBrowseLoad';
export type AlbumCatalogChunk = {
albums: SubsonicAlbum[];
@@ -11,8 +11,8 @@ import { loadArtistFromLibraryIndex } from '@/features/offline';
import { useOfflineBrowseContext } from '@/features/offline';
import { loadArtistFromLocalPlayback, offlineLocalBrowseEnabled } from '@/features/offline';
import { readDetailServerId } from '@/utils/navigation/detailServerScope';
import { runLocalArtistLosslessBrowse } from '@/utils/library/browseTextSearch';
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
import { runLocalArtistLosslessBrowse } from '@/lib/library/browseTextSearch';
import { isLosslessSuffix } from '@/lib/library/losslessFormats';
export interface UseArtistDetailDataOptions {
/** When true, albums and top tracks are limited to lossless containers (local index preferred). */
@@ -5,7 +5,7 @@ import { dedupeById } from '@/lib/util/dedupeById';
import {
fetchLocalArtistCatalogChunk,
fetchNetworkStarredArtists,
} from '@/utils/library/browseTextSearch';
} from '@/lib/library/browseTextSearch';
import { useOfflineBrowseContext } from '@/features/offline';
import { useOfflineBrowseReloadToken } from '@/features/offline';
import {
@@ -8,7 +8,7 @@ import {
runLocalBrowseArtists,
runNetworkBrowseArtists,
type LibrarySearchSurface,
} from '@/utils/library/browseTextSearch';
} from '@/lib/library/browseTextSearch';
import { useOfflineBrowseContext } from '@/features/offline';
import { offlineLocalBrowseEnabled, searchOfflineLocalArtists } from '@/features/offline';
+1 -1
View File
@@ -3,7 +3,7 @@
* `pages/*` paths, not re-exported), artist cards/rows/avatars, the detail hero +
* similar-artists + top-tracks UI, the artist Subsonic API, browse catalog/
* filter/scroll hooks, per-artist offline status, and the open-artist-ref nav
* cluster. `sortArtistAlbums` moved in from `utils/library/` (artist-only sort,
* cluster. `sortArtistAlbums` moved in from `lib/library/` (artist-only sort,
* not the shared index query engine).
*
* Stays OUT (other owners): `deriveAlbumHeaderArtistRefs` (album header),
+1 -1
View File
@@ -36,7 +36,7 @@ import { artistDetailCoverWarmAlbums } from '@/features/artist/components/topSon
import { useLibraryCoverPrefetch } from '@/cover/useLibraryCoverPrefetch';
import { useWarmGridCovers } from '@/hooks/useWarmGridCovers';
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
import { sortArtistAlbumsByYear } from '@/features/artist/utils/sortArtistAlbums';
import { readDetailServerId } from '@/utils/navigation/detailServerScope';
@@ -22,8 +22,8 @@ import { getAlbumForServer, getSongForServer } from '@/lib/api/subsonicLibrary';
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
import { loadAlbumFromLibraryIndex, loadArtistFromLibraryIndex } from '@/features/offline';
import { trackToSong } from '@/utils/library/advancedSearchLocal';
import { libraryIsReady } from '@/utils/library/libraryReady';
import { trackToSong } from '@/lib/library/advancedSearchLocal';
import { libraryIsReady } from '@/lib/library/libraryReady';
const TOP_SONGS_LIMIT = 5;
@@ -1,5 +1,5 @@
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes';
import type { AlbumBrowseQuery } from '@/lib/library/albumBrowseTypes';
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
import {
fetchOfflineLocalAlbumCatalogChunk,
@@ -7,7 +7,7 @@ import type { LocalPlaybackEntry, PinnedGroup, PinSource } from '@/store/localPl
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
import { findLocalPlaybackEntry, hasLocalLibraryBytes } from '@/store/localPlaybackResolve';
import { useOfflineStore, type OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
import { resolveTrackCoverArtId, trackToSong } from '@/utils/library/advancedSearchLocal';
import { resolveTrackCoverArtId, trackToSong } from '@/lib/library/advancedSearchLocal';
import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
@@ -8,7 +8,7 @@ import {
albumToAlbum,
artistToArtist,
trackToSong,
} from '@/utils/library/advancedSearchLocal';
} from '@/lib/library/advancedSearchLocal';
export async function loadAlbumFromLibraryIndex(
serverId: string,
@@ -9,17 +9,17 @@ import {
artistToArtist,
resolveTrackCoverArtId,
trackToSong,
} from '@/utils/library/advancedSearchLocal';
import { albumIsCompilationFromTrackDtos } from '@/utils/library/albumCompilation';
} from '@/lib/library/advancedSearchLocal';
import { albumIsCompilationFromTrackDtos } from '@/lib/library/albumCompilation';
import {
filterAlbumsByCompilation,
filterAlbumsByGenres,
filterAlbumsByStarred,
filterAlbumsByYearBounds,
} from '@/utils/library/albumBrowseFilters';
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes';
import { sortSubsonicAlbums } from '@/utils/library/albumBrowseSort';
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
} from '@/lib/library/albumBrowseFilters';
import type { AlbumBrowseQuery } from '@/lib/library/albumBrowseTypes';
import { sortSubsonicAlbums } from '@/lib/library/albumBrowseSort';
import { isLosslessSuffix } from '@/lib/library/losslessFormats';
import { entryBelongsToServer } from '@/store/localPlaybackResolve';
function sortBrowsableSongs(songs: SubsonicSong[]): SubsonicSong[] {
@@ -62,7 +62,7 @@ vi.mock('@/lib/api/subsonicPlaylists', () => ({
getPlaylistForServerMock(serverId, playlistId),
}));
vi.mock('@/utils/library/libraryReady', () => ({
vi.mock('@/lib/library/libraryReady', () => ({
libraryIsReady: (serverId: string) => libraryIsReadyMock(serverId),
}));
@@ -10,7 +10,7 @@ import type {
import { useAuthStore } from '@/store/authStore';
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
import { libraryIsReady } from '@/utils/library/libraryReady';
import { libraryIsReady } from '@/lib/library/libraryReady';
import { registerMediaResolver } from '@/store/mediaResolver';
import {
loadAlbumFromLibraryIndex,
@@ -3,7 +3,7 @@ import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes';
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import type { PinnedGroup } from '@/store/localPlaybackStore';
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
import { trackToSong } from '@/utils/library/advancedSearchLocal';
import { trackToSong } from '@/lib/library/advancedSearchLocal';
import { isManualOfflinePlaylist } from '@/features/offline/utils/pinnedOfflineSync';
import {
hasLocalLibraryBytes,
@@ -10,7 +10,7 @@ import { isActiveServerReachable } from '@/utils/network/activeServerReachabilit
import {
albumToAlbum,
trackToSong,
} from '@/utils/library/advancedSearchLocal';
} from '@/lib/library/advancedSearchLocal';
import { dedupeById } from '@/lib/util/dedupeById';
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse';
@@ -4,7 +4,7 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { useAuthStore } from '@/store/authStore';
import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
import { useOfflineStore } from '@/features/offline/store/offlineStore';
import { trackToSong } from '@/utils/library/advancedSearchLocal';
import { trackToSong } from '@/lib/library/advancedSearchLocal';
import { isActiveServerReachable, onActiveServerBecameReachable } from '@/utils/network/activeServerReachability';
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
@@ -14,7 +14,7 @@ import {
} from '@/features/playback/store/playListenSession';
import { onPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded';
vi.mock('@/utils/library/libraryReady', () => ({
vi.mock('@/lib/library/libraryReady', () => ({
libraryIsReady: vi.fn(async () => true),
}));
@@ -178,7 +178,7 @@ describe('playListenSession', () => {
});
it('skips when library is not ready', async () => {
const { libraryIsReady } = await import('@/utils/library/libraryReady');
const { libraryIsReady } = await import('@/lib/library/libraryReady');
vi.mocked(libraryIsReady).mockResolvedValueOnce(false);
vi.useFakeTimers();
await playListenSessionOpen(testTrack, 'server-1');
@@ -5,7 +5,7 @@ import {
libraryRecordPlaySession,
type PlaySessionEndReason,
} from '@/lib/api/library';
import { libraryIsReady } from '@/utils/library/libraryReady';
import { libraryIsReady } from '@/lib/library/libraryReady';
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { emitPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded';
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
@@ -5,8 +5,8 @@ import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreT
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
import { trackToSong } from '@/utils/library/advancedSearchLocal';
import { libraryIsReady } from '@/utils/library/libraryReady';
import { trackToSong } from '@/lib/library/advancedSearchLocal';
import { libraryIsReady } from '@/lib/library/libraryReady';
/**
* Queue track resolver (thin-state phase 2). Resolves `QueueItemRef`s to full
@@ -22,23 +22,23 @@ vi.mock('@/lib/api/subsonicClient', () => ({
libraryScopeForServer: vi.fn(() => 'music'),
}));
vi.mock('@/utils/library/libraryReady', () => ({
vi.mock('@/lib/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('@/utils/library/genreAlbumBrowse', async (importOriginal) => ({
...(await importOriginal<typeof import('@/utils/library/genreAlbumBrowse')>()),
vi.mock('@/lib/library/genreAlbumBrowse', async (importOriginal) => ({
...(await importOriginal<typeof import('@/lib/library/genreAlbumBrowse')>()),
fetchGenreAlbumTotal: vi.fn(),
}));
import { libraryAdvancedSearch, libraryGetGenreAlbumCounts } from '@/lib/api/library';
import { fetchAllSongsByGenre, getGenres } from '@/lib/api/subsonicGenres';
import { fetchGenreAlbumTotal } from '@/utils/library/genreAlbumBrowse';
import { resetGenreCatalogCountsCacheForTests } from '@/utils/library/genreCatalogCountsCache';
import { libraryIsReady } from '@/utils/library/libraryReady';
import { fetchGenreAlbumTotal } from '@/lib/library/genreAlbumBrowse';
import { resetGenreCatalogCountsCacheForTests } from '@/lib/library/genreCatalogCountsCache';
import { libraryIsReady } from '@/lib/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 '@/utils/library/advancedSearchLocal';
import { type AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
import { trackToSong } from '@/lib/library/advancedSearchLocal';
import { type AlbumBrowseSort } from '@/lib/library/albumBrowseSort';
import {
genreCatalogCacheKey,
getInflightGenreCatalog,
@@ -17,9 +17,9 @@ import {
peekGenreCatalogCache,
trackInflightGenreCatalog,
writeGenreCatalogCache,
} from '@/utils/library/genreCatalogCountsCache';
import { fetchGenreAlbumTotal } from '@/utils/library/genreAlbumBrowse';
import { libraryIsReady } from '@/utils/library/libraryReady';
} from '@/lib/library/genreCatalogCountsCache';
import { fetchGenreAlbumTotal } from '@/lib/library/genreAlbumBrowse';
import { libraryIsReady } from '@/lib/library/libraryReady';
/** Drop genres with no indexed albums/tracks (stale server list or orphan rows). */
export function filterGenresWithContent(genres: SubsonicGenre[]): SubsonicGenre[] {
@@ -10,7 +10,7 @@ import {
} from '@/utils/componentHelpers/queuePanelHelpers';
import { loudnessGainPlaceholderUntilCacheDb } from '@/features/playback/utils/audio/loudnessPlaceholder';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider';
import { formatQueueBpmTech, formatQueueMoodLabels } from '@/utils/library/trackEnrichment';
import { formatQueueBpmTech, formatQueueMoodLabels } from '@/lib/library/trackEnrichment';
import { useQueueTrackEnrichment } from '@/features/queue/hooks/useQueueTrackEnrichment';
import { QueueLufsTargetMenu } from '@/features/queue/components/QueueLufsTargetMenu';
import { PlaybackBufferingOverlay } from '@/components/playback/PlaybackBufferingOverlay';
@@ -8,8 +8,8 @@ import {
OXIMEDIA_MOOD_UI_ENABLED,
parseTrackEnrichmentFacts,
type ParsedTrackEnrichment,
} from '@/utils/library/trackEnrichment';
import { libraryIsReady } from '@/utils/library/libraryReady';
} from '@/lib/library/trackEnrichment';
import { libraryIsReady } from '@/lib/library/libraryReady';
import { normalizeAnalysisTrackId } from '@/features/playback/utils/playback/queueIdentity';
const EMPTY: ParsedTrackEnrichment = {
@@ -9,17 +9,17 @@ import {
mergeLiveSearchResults,
runLocalLiveSearch,
runNetworkLiveSearch,
} from '@/utils/library/liveSearchLocal';
import { raceLiveSearch } from '@/utils/library/searchRace';
import { libraryIsReady } from '@/utils/library/libraryReady';
} from '@/lib/library/liveSearchLocal';
import { raceLiveSearch } from '@/lib/library/searchRace';
import { libraryIsReady } from '@/lib/library/libraryReady';
import {
emitLiveSearchDebug,
searchHitCounts,
searchResultSamples,
} from '@/utils/library/liveSearchDebug';
} from '@/lib/library/liveSearchDebug';
import {
logLibrarySearch,
} from '@/utils/library/libraryDevLog';
} from '@/lib/library/libraryDevLog';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { useNavigateToAlbum } from '@/features/album';
+1 -1
View File
@@ -3,7 +3,7 @@
* search/browse page (lazy via the deep path `pages/SearchBrowsePage`), live
* search scope UI/state, and the share-link search/queue-preview surfaces.
*
* The local-index query engine (`utils/library/*search*`), the shared Subsonic
* The local-index query engine (`lib/library/*search*`), the shared Subsonic
* search API, the cross-cutting `liveSearchScopeStore`, and the
* `advancedSearch*` session/scroll state live outside this feature
* (library-core / cross-cutting) this feature consumes them.
@@ -42,19 +42,19 @@ import {
runNetworkAdvancedTextSearch,
runNetworkAdvancedYearAlbums,
tryRunLocalAdvancedSearch,
} from '@/utils/library/advancedSearchLocal';
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
import { OXIMEDIA_MOOD_SEARCH_ENABLED } from '@/utils/library/trackEnrichment';
import { raceSearchSources } from '@/utils/library/searchRace';
import { logLibrarySearch } from '@/utils/library/libraryDevLog';
} from '@/lib/library/advancedSearchLocal';
import { isLosslessSuffix } from '@/lib/library/losslessFormats';
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
import { OXIMEDIA_MOOD_SEARCH_ENABLED } from '@/lib/library/trackEnrichment';
import { raceSearchSources } from '@/lib/library/searchRace';
import { logLibrarySearch } from '@/lib/library/libraryDevLog';
import {
browseRaceCountsFullSearch,
loadMoreLocalBrowseSongs,
raceBrowseWithLocalFallback,
runLocalBrowseFullSearch,
runNetworkBrowseFullSearch,
} from '@/utils/library/browseTextSearch';
} from '@/lib/library/browseTextSearch';
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { MOOD_GROUP_IDS } from '@/config/moodGroups';
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
@@ -29,7 +29,7 @@ import {
ADVANCED_PARALLELISM_MAX,
ADVANCED_PARALLELISM_MIN,
type AnalyticsStrategy,
} from '@/utils/library/analysisStrategy';
} from '@/lib/library/analysisStrategy';
type ClearTarget = {
serverId: string;
@@ -19,11 +19,11 @@ import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
import { showToast } from '@/utils/ui/toast';
import { formatBytes } from '@/lib/format/formatBytes';
import { clearImageCache, getImageCacheSize } from '@/utils/imageCache';
import { wakeLibraryCoverBackfill } from '@/utils/library/coverBackfillWake';
import { wakeLibraryCoverBackfill } from '@/lib/library/coverBackfillWake';
import {
COVER_CACHE_STRATEGIES,
type CoverCacheStrategy,
} from '@/utils/library/coverStrategy';
} from '@/lib/library/coverStrategy';
type ClearTarget =
| { kind: 'image' }
@@ -4,7 +4,7 @@ import type { SyncStateDto } from '@/lib/api/library';
import {
libraryStatusDisplayTrackCount,
libraryStatusIsReady,
} from '@/utils/library/libraryReady';
} from '@/lib/library/libraryReady';
import type { LibraryServerConnection } from '@/hooks/useLibraryIndexSync';
interface ServerLibraryIndexControlsProps {
@@ -8,7 +8,7 @@ import { formatServerSoftware, isNavidromeAudiomuseSoftwareEligible, type Instan
import { buildCapabilityContext } from '@/serverCapabilities/context';
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { libraryDeleteServerData, librarySyncClearSession } from '@/lib/api/library';
import { bootstrapIndexedServer } from '@/utils/library/librarySession';
import { bootstrapIndexedServer } from '@/lib/library/librarySession';
import { useLibraryIndexSync } from '@/hooks/useLibraryIndexSync';
import ServerLibraryIndexControls from '@/features/settings/components/ServerLibraryIndexControls';
import type { ServerProfile } from '@/store/authStoreTypes';