mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-22 03:05:32 +02:00
fix(frontend): guard j/k uncached-child fetch with monotonic token
Rapid j/k over child rows that aren't in entryCacheRef triggers fetchEntryDetail calls in parallel. Without a guard the last one to settle wins, desyncing selectedUids (highlight) from selectedEntry (detail panel/URL). Fix: - jkSeqRef (monotonic counter) incremented before each server fetch; the .then() guard tok === jkSeqRef.current drops results from superseded navigations - handleRowClick increments jkSeqRef.current so any click also cancels an in-flight j/k fetch
This commit is contained in:
parent
dea8b27fba
commit
bd90a4b77f
3 changed files with 23 additions and 17 deletions
File diff suppressed because one or more lines are too long
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Archivr</title>
|
||||
<script type="module" crossorigin src="/assets/index-B0h3YYO-.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BOyLHdsN.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D8ic-z4p.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@ export default function App() {
|
|||
const pendingSearchFocus = useRef(false)
|
||||
const firstArchiveLoad = useRef(true)
|
||||
const lastAnchorIndexRef = useRef(null)
|
||||
// Monotonic token for j/k keyboard navigation; cancels stale fetchEntryDetail calls.
|
||||
const jkSeqRef = useRef(0)
|
||||
// uid → entry object cache; populated on every row click so ctrl/shift
|
||||
// selections can resolve child entries that aren't in the root entries array.
|
||||
const entryCacheRef = useRef(new Map())
|
||||
|
|
@ -267,6 +269,8 @@ export default function App() {
|
|||
// Cache every clicked entry so shift/ctrl can resolve child entries
|
||||
// that are not present in the root `entries` array.
|
||||
entryCacheRef.current.set(entry.entry_uid, entry)
|
||||
// Invalidate any in-flight j/k uncached-child fetch.
|
||||
++jkSeqRef.current
|
||||
|
||||
if (e.shiftKey && lastAnchorIndexRef.current !== null) {
|
||||
e.preventDefault()
|
||||
|
|
@ -479,9 +483,11 @@ export default function App() {
|
|||
const nextUid = nextNode.dataset.entryUid
|
||||
|
||||
lastAnchorIndexRef.current = nextUid
|
||||
const tok = ++jkSeqRef.current
|
||||
setSelectedUids(new Set([nextUid]))
|
||||
|
||||
// Resolve entry object: cache → root entries array → server fetch
|
||||
// Resolve entry object: cache → root entries array → server fetch.
|
||||
// Token guards against a slow fetch settling after the user has moved on.
|
||||
const cached = entryCacheRef.current.get(nextUid)
|
||||
?? entries.find(x => x.entry_uid === nextUid)
|
||||
?? null
|
||||
|
|
@ -489,7 +495,7 @@ export default function App() {
|
|||
selectEntry(cached)
|
||||
} else {
|
||||
fetchEntryDetail(archiveId, nextUid)
|
||||
.then(det => { if (det?.summary) selectEntry(det.summary) })
|
||||
.then(det => { if (tok === jkSeqRef.current && det?.summary) selectEntry(det.summary) })
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue