terry-staging #11

Merged
terry merged 37 commits from terry-staging into main 2026-05-29 19:29:58 +00:00
Showing only changes of commit ae14b33f83 - Show all commits

View File

@@ -94,6 +94,24 @@ function buildRealUrl(params: PostStreamParams, cursor?: string): string {
return `${q ? "/api/posts/search" : "/api/posts"}?${sp.toString()}`; return `${q ? "/api/posts/search" : "/api/posts"}?${sp.toString()}`;
} }
type CachedStream = {
items: Post[];
cursor: string | undefined;
hasMore: boolean;
};
/**
* Session cache of loaded stream state, keyed by the request params. Lets a
* view that's been seen before (e.g. switching Home ⇄ All) restore instantly —
* all loaded pages, no skeleton, no refetch — instead of reloading from page 1.
* In-memory only: a full page reload starts fresh.
*/
const streamCache = new Map<string, CachedStream>();
function streamKey(params: PostStreamParams): string {
return buildRealUrl(params);
}
export function usePostStream(params: PostStreamParams): PostStreamResult { export function usePostStream(params: PostStreamParams): PostStreamResult {
const [items, setItems] = useState<Post[]>([]); const [items, setItems] = useState<Post[]>([]);
const [hasMore, setHasMore] = useState(true); const [hasMore, setHasMore] = useState(true);
@@ -172,6 +190,17 @@ export function usePostStream(params: PostStreamParams): PostStreamResult {
); );
useEffect(() => { useEffect(() => {
// Restore a previously-loaded view instantly (no reset, no refetch).
const cached = streamCache.get(streamKey(params));
if (cached) {
setItems(cached.items);
cursorRef.current = cached.cursor;
setHasMore(cached.hasMore);
hasMoreRef.current = cached.hasMore;
setIsLoading(false);
setError(null);
return;
}
setItems([]); setItems([]);
cursorRef.current = undefined; cursorRef.current = undefined;
setHasMore(true); setHasMore(true);
@@ -188,6 +217,17 @@ export function usePostStream(params: PostStreamParams): PostStreamResult {
params.lang, params.lang,
]); ]);
// Persist loaded state so returning to this view restores it from cache.
useEffect(() => {
if (items.length === 0) return;
streamCache.set(streamKey(params), {
items,
cursor: cursorRef.current,
hasMore,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [items, hasMore]);
const loadMore = useCallback(() => { const loadMore = useCallback(() => {
fetchPage(false); fetchPage(false);
}, [fetchPage]); }, [fetchPage]);