diff --git a/src/features/album/hooks/useAlbumBrowseData.ts b/src/features/album/hooks/useAlbumBrowseData.ts index d7199f36..8105c7b0 100644 --- a/src/features/album/hooks/useAlbumBrowseData.ts +++ b/src/features/album/hooks/useAlbumBrowseData.ts @@ -247,6 +247,7 @@ export function useAlbumBrowseData({ const loadGenerationRef = useRef(0); const pageRef = useRef(0); const catalogOffsetRef = useRef(0); + const catalogCursorRef = useRef(null); const catalogLoadingRef = useRef(false); const loadingRef = useRef(false); const loadPendingRef = useRef(false); @@ -295,6 +296,7 @@ export function useAlbumBrowseData({ offset, CATALOG_CHUNK_SIZE, starredOverrides, + append ? catalogCursorRef.current : undefined, ); if (generation !== loadGenerationRef.current || chunk == null) return; setAlbums(prev => { @@ -303,6 +305,7 @@ export function useAlbumBrowseData({ return next; }); setCatalogHasMore(chunk.hasMore); + catalogCursorRef.current = chunk.nextCursor ?? null; } finally { catalogLoadingRef.current = false; if (generation === loadGenerationRef.current) { @@ -383,7 +386,8 @@ export function useAlbumBrowseData({ const cached = readAlbumBrowseCatalogCache(catalogLoadKey); if (!cached) return; pageRef.current = 0; - catalogOffsetRef.current = cached.albums.length; + catalogOffsetRef.current = cached.albums.length; + catalogCursorRef.current = cached.nextCursor ?? null; loadPendingRef.current = false; catalogLoadingRef.current = false; // React Compiler set-state-in-effect rule: local state synced from the catalog cache before paint. @@ -419,6 +423,7 @@ export function useAlbumBrowseData({ if (!joinInflight) { pageRef.current = 0; catalogOffsetRef.current = 0; + catalogCursorRef.current = null; loadPendingRef.current = false; catalogLoadingRef.current = false; // React Compiler set-state-in-effect rule: state set from an async result resolved in this effect. @@ -512,6 +517,7 @@ export function useAlbumBrowseData({ setBrowseMode('slice'); setAlbums(preview.albums); catalogOffsetRef.current = preview.albums.length; + catalogCursorRef.current = preview.nextCursor ?? null; const needsTail = preview.hasMore && preview.albums.length < CATALOG_CHUNK_SIZE; setCatalogHasMore(needsTail || preview.hasMore); @@ -541,8 +547,9 @@ export function useAlbumBrowseData({ serverId, indexEnabled, browseQuery, - tailOffset, - tailSize, + tailOffset, + tailSize, + preview.nextCursor, ), { offset: tailOffset, chunkSize: tailSize }, ), @@ -558,10 +565,12 @@ export function useAlbumBrowseData({ storeAlbumBrowseCatalogCache(loadKey, { albums: merged, hasMore: tail.hasMore, + nextCursor: tail.nextCursor, }); return merged; }); - setCatalogHasMore(tail.hasMore); + setCatalogHasMore(tail.hasMore); + catalogCursorRef.current = tail.nextCursor ?? null; emitAlbumBrowseDebug('catalog_tail_done', { albumCount: tail.albums.length, totalOffset: tailOffset + tail.albums.length, @@ -595,8 +604,9 @@ export function useAlbumBrowseData({ if (cancelled || generation !== loadGenerationRef.current) return; if (first != null) { setBrowseMode('slice'); - setAlbums(first.albums); - catalogOffsetRef.current = first.albums.length; + setAlbums(first.albums); + catalogOffsetRef.current = first.albums.length; + catalogCursorRef.current = first.nextCursor ?? null; setCatalogHasMore(first.hasMore); setLoading(false); emitAlbumBrowseDebug('loading_false', { source: 'slice', albumCount: first.albums.length }); diff --git a/src/features/album/utils/albumBrowseCatalogChunk.ts b/src/features/album/utils/albumBrowseCatalogChunk.ts index 7b745a2f..0ffff962 100644 --- a/src/features/album/utils/albumBrowseCatalogChunk.ts +++ b/src/features/album/utils/albumBrowseCatalogChunk.ts @@ -8,6 +8,7 @@ import { fetchLocalAlbumCatalogChunk } from '@/lib/library/albumBrowseLoad'; export type AlbumCatalogChunk = { albums: SubsonicAlbum[]; hasMore: boolean; + nextCursor?: string | null; }; export function mergeAlbumCatalogChunk( @@ -30,6 +31,7 @@ export async function fetchAlbumBrowseCatalogChunk( offset: number, chunkSize: number, starredOverrides: Record, + cursor?: string | null, ): Promise { if (isOfflineBrowseActive()) { return loadOfflineAlbumCatalogChunk( @@ -40,5 +42,5 @@ export async function fetchAlbumBrowseCatalogChunk( starredOverrides, ); } - return fetchLocalAlbumCatalogChunk(serverId, indexEnabled, query, offset, chunkSize); + return fetchLocalAlbumCatalogChunk(serverId, indexEnabled, query, offset, chunkSize, cursor); } diff --git a/src/lib/library/albumBrowseLoad.ts b/src/lib/library/albumBrowseLoad.ts index e917c62e..f820e703 100644 --- a/src/lib/library/albumBrowseLoad.ts +++ b/src/lib/library/albumBrowseLoad.ts @@ -16,10 +16,10 @@ export { filterAlbumsByCompilation, filterAlbumsByStarred, } from './albumBrowseFilters'; -export { runLocalAlbumBrowse } from './albumBrowseLocal'; +export { runLocalAlbumBrowse, runLocalAlbumScopeBrowse } from './albumBrowseLocal'; import { albumBrowseHasServerFilters, countGenresFromAlbums, filterAlbumsByCompilation } from './albumBrowseFilters'; -import { runLocalAlbumBrowse } from './albumBrowseLocal'; +import { runLocalAlbumBrowse, runLocalAlbumScopeBrowse } from './albumBrowseLocal'; import { fetchAlbumBrowseNetwork } from './albumBrowseNetwork'; import { fetchStarredAlbumBrowse } from './albumBrowseStarredFetch'; import { librarySelectionForServer } from '@/lib/api/subsonicClient'; @@ -47,6 +47,7 @@ export async function fetchLocalAlbumCatalogChunk( query: AlbumBrowseQuery, offset: number, chunkSize: number, + cursor?: string | null, ): Promise { if (query.starredOnly) { return fetchAlbumBrowsePage(serverId, indexEnabled, query, offset, chunkSize); @@ -60,6 +61,10 @@ export async function fetchLocalAlbumCatalogChunk( : query.genres.length > 0 && offset === 0 ? GENRE_ALBUM_FETCH_LIMIT : chunkSize; + if (albumBrowseBootstrapEligible(query)) { + const scoped = await runLocalAlbumScopeBrowse(serverId, query.sort, limit, cursor); + if (scoped) return scoped; + } return runLocalAlbumBrowse(serverId, query, offset, limit); } diff --git a/src/lib/library/albumBrowseTypes.ts b/src/lib/library/albumBrowseTypes.ts index 45d13f8e..7eb56a67 100644 --- a/src/lib/library/albumBrowseTypes.ts +++ b/src/lib/library/albumBrowseTypes.ts @@ -17,6 +17,7 @@ export type AlbumBrowseQuery = { export type AlbumBrowsePageResult = { albums: SubsonicAlbum[]; hasMore: boolean; + nextCursor?: string | null; }; export type AlbumBrowseFetchCallbacks = {