@@ -335,14 +335,61 @@ export const useAlbumStore = defineStore("album-store", {
335335 return this . loadPhotos ( 1 , false ) ;
336336 } ,
337337
338+ /**
339+ * Search for a photo across pages that have not yet been loaded.
340+ *
341+ * The search first goes backward (prepending pages from startPage-1 down to 1),
342+ * then forward (appending pages from startPage+1 up to photos_last_page), stopping
343+ * as soon as the photo is found. Each iteration guards against concurrent
344+ * navigation by comparing this.albumId with requestedAlbumId.
345+ *
346+ * @param photoId - The photo ID to search for
347+ * @param startPage - The page that was already loaded (search starts around it)
348+ * @param requestedAlbumId - The album ID captured at the start of load(); used to
349+ * abort the search when the user navigates away
350+ */
351+ async _searchPhotoInPages ( photoId : string , startPage : number , requestedAlbumId : string ) : Promise < void > {
352+ const photosState = usePhotosStore ( ) ;
353+
354+ // Clamp to the actual last page so an out-of-range `?page=N` doesn't trigger
355+ // a long chain of requests for pages that do not exist.
356+ const effectiveStart = Math . min ( startPage , this . photos_last_page ) ;
357+
358+ // Search backward: prepend pages from startPage-1 down to 1
359+ for ( let p = effectiveStart - 1 ; p >= 1 ; p -- ) {
360+ if ( this . albumId !== requestedAlbumId ) {
361+ return ;
362+ }
363+ await this . loadPhotos ( p , false , true ) ;
364+ if ( photosState . photos . some ( ( ph ) => ph . id === photoId ) ) {
365+ return ;
366+ }
367+ }
368+
369+ // Search forward: append pages from startPage+1 up to photos_last_page
370+ for ( let p = effectiveStart + 1 ; p <= this . photos_last_page ; p ++ ) {
371+ if ( this . albumId !== requestedAlbumId ) {
372+ return ;
373+ }
374+ await this . loadPhotos ( p , true , false ) ;
375+ if ( photosState . photos . some ( ( ph ) => ph . id === photoId ) ) {
376+ return ;
377+ }
378+ }
379+ } ,
380+
338381 /**
339382 * Load the album metadata and first batch of photos.
340383 *
341384 * @param startPage - The page to load first. When provided (>1), that page is loaded
342385 * immediately so a directly linked photo can be displayed, then
343386 * pages 1…startPage-1 are loaded in the background (prepended).
387+ * @param photoId - Optional photo ID expected on this page. When given and not
388+ * found in startPage, other pages are searched sequentially
389+ * (backward then forward) before giving up and showing the album.
390+ * The ?page=N query parameter is treated as a hint, not a guarantee.
344391 */
345- async load ( startPage : number = 1 ) : Promise < void > {
392+ async load ( startPage : number = 1 , photoId ?: string ) : Promise < void > {
346393 const togglableState = useTogglablesStateStore ( ) ;
347394 const photosState = usePhotosStore ( ) ;
348395 const albumsStore = useAlbumsStore ( ) ;
@@ -396,17 +443,29 @@ export const useAlbumStore = defineStore("album-store", {
396443 this . smartAlbum = data . data . resource as App . Http . Resources . Models . HeadSmartAlbumResource ;
397444 }
398445
446+ // When a specific photo is expected but was not found in the initial page,
447+ // search other pages sequentially (backward then forward). This is run
448+ // concurrently with album loading so the album header appears quickly.
449+ const photoFoundInInitialPage = photoId !== undefined && photosState . photos . some ( ( p ) => p . id === photoId ) ;
450+ if ( photoId !== undefined && ! photoFoundInInitialPage ) {
451+ loader . push ( this . _searchPhotoInPages ( photoId , resolvedStart , requestedAlbumId ) ) ;
452+ }
453+
399454 await Promise . all ( loader ) ;
400455
401456 // Fire off background loading of immediately preceding pages (prepend).
457+ // Only done when we are NOT searching (the sequential search already covers
458+ // those pages, and running both would load them twice).
402459 // Capped at the 5 most-recent previous pages to avoid issuing too many
403460 // concurrent requests when jumping to a high page number (e.g. page 50).
404461 // These are intentionally NOT awaited so the photo panel can render
405462 // while earlier pages stream in, without blocking albumStore.load().
406- const backgroundPagesLimit = 5 ;
407- const firstBackgroundPage = Math . max ( 1 , resolvedStart - backgroundPagesLimit ) ;
408- for ( let p = resolvedStart - 1 ; p >= firstBackgroundPage ; p -- ) {
409- void this . loadPhotos ( p , false , true ) ;
463+ if ( photoId === undefined || photoFoundInInitialPage ) {
464+ const backgroundPagesLimit = 5 ;
465+ const firstBackgroundPage = Math . max ( 1 , resolvedStart - backgroundPagesLimit ) ;
466+ for ( let p = resolvedStart - 1 ; p >= firstBackgroundPage ; p -- ) {
467+ void this . loadPhotos ( p , false , true ) ;
468+ }
410469 }
411470 } )
412471 . catch ( ( error ) => {
0 commit comments