Merge pull request #2 from parththakkar106/fix-prepend-autoscroll

Fix: loading earlier turns jumped to the end of the story
This commit is contained in:
Parth
2026-08-17 15:23:40 +05:30
committed by GitHub
+15 -1
View File
@@ -1324,6 +1324,9 @@ export default function Play() {
// the reader back where they were. See the layout effect below. // the reader back where they were. See the layout effect below.
const restoreScrollRef = useRef(null) const restoreScrollRef = useRef(null)
const loadingOlderRef = useRef(false) const loadingOlderRef = useRef(false)
// Earliest time another attempt is allowed after a failure. See the catch
// in loadOlder.
const retryAfterRef = useRef(0)
// The drop cap belongs to the story's first narrated beat. `start` is the // The drop cap belongs to the story's first narrated beat. `start` is the
// scenario's opening prompt, so it's usually that; an adventure begun blank // scenario's opening prompt, so it's usually that; an adventure begun blank
@@ -1367,6 +1370,7 @@ export default function Play() {
// and two in-flight requests would fetch the same page twice. // and two in-flight requests would fetch the same page twice.
const loadOlder = useCallback(async () => { const loadOlder = useCallback(async () => {
if (loadingOlderRef.current || !hasMore) return if (loadingOlderRef.current || !hasMore) return
if (Date.now() < retryAfterRef.current) return
const oldest = actions[0] const oldest = actions[0]
if (!oldest) return if (!oldest) return
loadingOlderRef.current = true loadingOlderRef.current = true
@@ -1374,6 +1378,12 @@ export default function Play() {
try { try {
const page = await api.getActions(id, { beforeId: oldest.id }) const page = await api.getActions(id, { beforeId: oldest.id })
if (page.actions.length) { if (page.actions.length) {
// Reading backwards is the opposite of following along, so stop
// autoscrolling. Without this the bottom-pinning effect below fires on
// the same `actions` change and throws the reader to the end of the
// story — worst exactly where the button matters, on a window short
// enough that it never scrolled and so never un-pinned itself.
pinnedRef.current = false
// Record the height before the prepend; the layout effect below uses // Record the height before the prepend; the layout effect below uses
// it to keep the reader looking at the same paragraph. // it to keep the reader looking at the same paragraph.
restoreScrollRef.current = { restoreScrollRef.current = {
@@ -1391,7 +1401,11 @@ export default function Play() {
setHasMore(page.has_more) setHasMore(page.has_more)
} catch { } catch {
// Leave hasMore alone: a failed fetch should let the reader try again // Leave hasMore alone: a failed fetch should let the reader try again
// by scrolling, not permanently hide the rest of their story. // by scrolling, not permanently hide the rest of their story. But hold
// off briefly first — parked near the top, momentum scrolling fires this
// dozens of times a second, and against an endpoint that is failing that
// is a retry storm rather than a retry.
retryAfterRef.current = Date.now() + 3000
} finally { } finally {
loadingOlderRef.current = false loadingOlderRef.current = false
setLoadingOlder(false) setLoadingOlder(false)