diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBPresentationRuntime.swift b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBPresentationRuntime.swift index 9d34f6e..e35593d 100644 --- a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBPresentationRuntime.swift +++ b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBPresentationRuntime.swift @@ -160,6 +160,11 @@ final class RDEPUBPresentationRuntime { ) { let currentLocation = locationCoordinator.currentVisibleLocation() let livePageBeforeApply = readerView.currentPage + 1 + // Resolved against the outgoing page map. When it round-trips to the live + // page, the location faithfully describes what is on screen, so whatever + // page it resolves to in the new map is authoritative even if the two maps + // number pages differently (partial-window -> full-book takeover). + let oldResolvedPage = currentLocation.flatMap { controller.pageNumber(for: $0) } context.textBook = nil applyPageMapToLiveModel(newPageMap) @@ -172,11 +177,12 @@ final class RDEPUBPresentationRuntime { let resolvedTargetPage = controller.pageNumber(for: currentLocation) let shouldTrustResolvedLocation = shouldTrustFullReplaceResolvedPage( resolvedTargetPage, - livePageBeforeApply: livePageBeforeApply + livePageBeforeApply: livePageBeforeApply, + locationMatchesLivePage: oldResolvedPage == livePageBeforeApply ) RDEPUBBackgroundTrace.log( "Reconciliation", - "applyFullPageMapReplacement decision livePage=\(livePageBeforeApply) resolvedTargetPage=\(resolvedTargetPage ?? -1) trustResolved=\(shouldTrustResolvedLocation) href=\(currentLocation.href)" + "applyFullPageMapReplacement decision livePage=\(livePageBeforeApply) oldResolvedPage=\(oldResolvedPage ?? -1) resolvedTargetPage=\(resolvedTargetPage ?? -1) trustResolved=\(shouldTrustResolvedLocation) href=\(currentLocation.href)" ) if shouldTrustResolvedLocation, @@ -433,9 +439,16 @@ final class RDEPUBPresentationRuntime { private func shouldTrustFullReplaceResolvedPage( _ resolvedTargetPage: Int?, - livePageBeforeApply: Int + livePageBeforeApply: Int, + locationMatchesLivePage: Bool ) -> Bool { guard let resolvedTargetPage else { return false } + if locationMatchesLivePage { + return true + } + // The location did not round-trip to the live page in the outgoing map + // (stale persisted location or mid-transition), so only follow it when it + // stays next to the page the user is actually looking at. return abs(resolvedTargetPage - livePageBeforeApply) <= 1 }