From e93240ff793c1eb1cca3c443f88af13c4c24924e Mon Sep 17 00:00:00 2001 From: Masahiko AMANO Date: Thu, 11 Jun 2026 16:56:33 +0300 Subject: [PATCH] feat(frontend): restore the Files grid and scroll on return via section cache Leaving the Files list for another section unmounted the page and lost the loaded grid, cursors and scroll position; returning refetched page 1 from the top. A new in-memory section cache snapshots that state on departure (beforeNavigate) and rehydrates it on the next mount when the sort/filter still match, reapplying the scroll offset after the grid paints. Combined with the navbar remembering the section URL, tapping back into Files lands you exactly where you left off. The snapshot is session-only, validated by resetKey, and skipped for in-page query changes and the shallow-routed viewer. Co-Authored-By: Claude Opus 4.8 --- frontend/src/lib/stores/sectionCache.ts | 37 +++++++++++ frontend/src/routes/files/+page.svelte | 82 ++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 frontend/src/lib/stores/sectionCache.ts diff --git a/frontend/src/lib/stores/sectionCache.ts b/frontend/src/lib/stores/sectionCache.ts new file mode 100644 index 0000000..be7b3ff --- /dev/null +++ b/frontend/src/lib/stores/sectionCache.ts @@ -0,0 +1,37 @@ +// In-memory, per-section view cache. When you leave a list (Files, Tags, …) for +// another section and come back, the page restores its loaded items, pagination +// cursors and scroll position from here instead of refetching from scratch. +// +// Kept deliberately simple: a plain module-level Map that lives for the session. +// No TTL — a snapshot is taken from the page's current state on the way out, so +// it already reflects local mutations (deletes, uploads, tag edits). It is +// dropped on a full reload, and each page validates the snapshot's `resetKey` +// (sort/filter/search) before trusting it, so a stale query never restores. + +export type SectionKey = 'files' | 'tags' | 'categories' | 'pools'; + +interface Snapshot { + /** Scroll offset of the list's scroller at capture time. */ + scrollTop: number; + /** Page-specific state blob; opaque to this module. */ + data: T; + savedAt: number; +} + +const cache = new Map>(); + +export function saveSection(key: SectionKey, scrollTop: number, data: T): void { + cache.set(key, { scrollTop, data, savedAt: Date.now() }); +} + +/** Read and remove a section's snapshot (restore consumes it). */ +export function takeSection(key: SectionKey): { scrollTop: number; data: T } | null { + const snap = cache.get(key) as Snapshot | undefined; + if (!snap) return null; + cache.delete(key); + return { scrollTop: snap.scrollTop, data: snap.data }; +} + +export function clearSection(key: SectionKey): void { + cache.delete(key); +} diff --git a/frontend/src/routes/files/+page.svelte b/frontend/src/routes/files/+page.svelte index 646d3f5..dd72e48 100644 --- a/frontend/src/routes/files/+page.svelte +++ b/frontend/src/routes/files/+page.svelte @@ -1,6 +1,7 @@