terry-staging #16

Merged
terry merged 96 commits from terry-staging into main 2026-06-05 16:33:12 +00:00
Showing only changes of commit b9fe7ff168 - Show all commits

View File

@@ -36,19 +36,52 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
const [pendingIds, setPendingIds] = useState<Set<string>>(() => new Set());
const pendingAfterLoginRef = useRef<string | null>(null);
const lastAddressRef = useRef<string | null>(null);
const knownIdsRef = useRef<Set<string>>(new Set());
const inFlightIdsRef = useRef<Set<string>>(new Set());
const queuedIdsRef = useRef<Set<string>>(new Set());
const batchTimerRef = useRef<number | null>(null);
const tokenRef = useRef<string | null>(null);
useEffect(() => {
tokenRef.current = status === "loggedIn" ? token : null;
}, [status, token]);
const clearFavoriteStatus = useCallback(() => {
knownIdsRef.current = new Set();
inFlightIdsRef.current = new Set();
queuedIdsRef.current = new Set();
if (batchTimerRef.current !== null) {
window.clearTimeout(batchTimerRef.current);
batchTimerRef.current = null;
}
setFavoriteIds(new Set());
setKnownIds(new Set());
setPendingIds(new Set());
}, []);
useEffect(() => {
const nextAddress = status === "loggedIn" ? address : null;
if (lastAddressRef.current === nextAddress) return;
lastAddressRef.current = nextAddress;
setFavoriteIds(new Set());
setKnownIds(new Set());
setPendingIds(new Set());
clearFavoriteStatus();
if (!nextAddress) pendingAfterLoginRef.current = null;
}, [address, status]);
}, [address, clearFavoriteStatus, status]);
useEffect(
() => () => {
if (batchTimerRef.current !== null) {
window.clearTimeout(batchTimerRef.current);
}
},
[],
);
const markFavorite = useCallback((resourceId: string, favorited: boolean) => {
setKnownIds((prev) => new Set(prev).add(resourceId));
setKnownIds((prev) => {
const next = new Set(prev).add(resourceId);
knownIdsRef.current = next;
return next;
});
setFavoriteIds((prev) => {
const next = new Set(prev);
if (favorited) next.add(resourceId);
@@ -57,17 +90,22 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
});
}, []);
const ensureFavoriteIds = useCallback(
async (resourceIds: string[]) => {
if (!token || status !== "loggedIn") return;
const missing = [...new Set(resourceIds)].filter(
(id) => !knownIds.has(id),
);
if (missing.length === 0) return;
const ids = await getFavoriteIds(token, missing);
const flushFavoriteIdBatch = useCallback(async () => {
const requestToken = tokenRef.current;
const requestIds = Array.from(queuedIdsRef.current);
queuedIdsRef.current.clear();
if (!requestToken || requestIds.length === 0) {
requestIds.forEach((id) => inFlightIdsRef.current.delete(id));
return;
}
try {
const ids = await getFavoriteIds(requestToken, requestIds);
if (tokenRef.current !== requestToken) return;
setKnownIds((prev) => {
const next = new Set(prev);
missing.forEach((id) => next.add(id));
requestIds.forEach((id) => next.add(id));
knownIdsRef.current = next;
return next;
});
setFavoriteIds((prev) => {
@@ -75,8 +113,35 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
ids.forEach((id) => next.add(id));
return next;
});
} finally {
requestIds.forEach((id) => inFlightIdsRef.current.delete(id));
if (queuedIdsRef.current.size > 0 && batchTimerRef.current === null) {
batchTimerRef.current = window.setTimeout(() => {
batchTimerRef.current = null;
void flushFavoriteIdBatch();
}, 0);
}
}
}, []);
const ensureFavoriteIds = useCallback(
async (resourceIds: string[]) => {
if (!token || status !== "loggedIn") return;
const missing = [...new Set(resourceIds)].filter(
(id) => !knownIdsRef.current.has(id) && !inFlightIdsRef.current.has(id),
);
if (missing.length === 0) return;
missing.forEach((id) => {
queuedIdsRef.current.add(id);
inFlightIdsRef.current.add(id);
});
if (batchTimerRef.current !== null) return;
batchTimerRef.current = window.setTimeout(() => {
batchTimerRef.current = null;
void flushFavoriteIdBatch();
}, 0);
},
[knownIds, status, token],
[flushFavoriteIdBatch, status, token],
);
const runFavoriteMutation = useCallback(