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 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 16:56:33 +03:00
parent 370dfd95bc
commit e93240ff79
2 changed files with 118 additions and 1 deletions
+37
View File
@@ -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<T> {
/** 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<SectionKey, Snapshot<unknown>>();
export function saveSection<T>(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<T>(key: SectionKey): { scrollTop: number; data: T } | null {
const snap = cache.get(key) as Snapshot<T> | undefined;
if (!snap) return null;
cache.delete(key);
return { scrollTop: snap.scrollTop, data: snap.data };
}
export function clearSection(key: SectionKey): void {
cache.delete(key);
}