Feed: eager-load only the year shards needed for the active date filter

This commit is contained in:
Davide Scaini
2026-05-15 09:32:12 +02:00
parent 8a06227243
commit d3bce49445
+73 -47
View File
@@ -132,57 +132,83 @@
$: if (sport || datePre || query || customFrom || customTo) shown = PAGE_SIZE; // reset pagination on filter change $: if (sport || datePre || query || customFrom || customTo) shown = PAGE_SIZE; // reset pagination on filter change
// When a search query or any date filter is active, eagerly load all // Eager-load shards / feed-pages when a filter needs data not yet in memory.
// remaining shards so results aren't limited to the initially-loaded year. let loadingAllShards = false;
// Use only primary let-variables here — Svelte 5 doesn't reliably track
// derived $: variables as dependencies of side-effect $: blocks.
let loadingAllShards = false;
let loadingAllFeedPages = false; let loadingAllFeedPages = false;
$: if ((query.trim() || customFrom || customTo || datePre !== 'all') && pendingShards.length > 0 && !loadingAllShards) { function _yearFromShard(url: string): number | null {
loadingAllShards = true; const m = url.match(/index-(\d{4})\.json$/);
(async () => { return m ? parseInt(m[1], 10) : null;
while (pendingShards.length > 0) {
const url = pendingShards[0];
pendingShards = pendingShards.slice(1);
try {
const fresh = await loadShardActivities(url);
const existing = new Map(all.map(a => [a.id, a]));
for (const a of fresh) if (!existing.has(a.id)) existing.set(a.id, a);
all = [...existing.values()].sort((a, b) =>
(b.started_at ?? '').localeCompare(a.started_at ?? ''),
);
} catch { /* ignore — partial results still useful */ }
}
loadingAllShards = false;
})();
} }
$: if ((query.trim() || customFrom || customTo || datePre !== 'all') && feedNextPage > 0 && !loadingAllFeedPages) { // Returns [minYear, maxYear] for year-specific filters (year preset or custom
loadingAllFeedPages = true; // date range). Returns null for open-ended filters (all, 7d, 30d, 6mo).
(async () => { function _neededYearRange(pre: string, from: string, to: string): [number, number] | null {
// Snapshot at loop start — customFrom may change while we're fetching. if (from || to) {
const fromFilter = customFrom; const fy = from ? parseInt(from.slice(0, 4), 10) : 0;
while (feedNextPage > 0) { const ty = to ? parseInt(to.slice(0, 4), 10) : 9999;
const page = feedNextPage; return [fy, ty];
feedNextPage = page < feedTotalPages ? page + 1 : 0; }
try { if (/^\d{4}$/.test(pre)) { const y = parseInt(pre, 10); return [y, y]; }
const fresh = await loadCombinedFeedPage(base, page); return null;
const existing = new Map(all.map(a => [a.id, a])); }
for (const a of fresh) if (!existing.has(a.id)) existing.set(a.id, a);
all = [...existing.values()].sort((a, b) => $: {
(b.started_at ?? '').localeCompare(a.started_at ?? ''), const yr = _neededYearRange(datePre, customFrom, customTo);
); const needEager = !!query.trim() || yr !== null;
// Feed is sorted newest-first. Once the oldest activity in this page if (needEager && pendingShards.length > 0 && !loadingAllShards) {
// predates our from-filter, everything needed is already loaded. loadingAllShards = true;
if (fromFilter && fresh.length > 0) { // When year-specific filter (no search), load only shards that cover
const oldest = fresh.reduce((m, a) => (a.started_at ?? '') < (m.started_at ?? '') ? a : m); // the needed range; unneeded shards stay in pendingShards for "Load more".
if ((oldest.started_at ?? '') < fromFilter) { feedNextPage = 0; break; } // When search is active, load everything so full-text search works.
} const toLoad = (yr && !query.trim())
} catch { /* ignore — partial results still useful */ } ? pendingShards.filter(url => { const y = _yearFromShard(url); return y !== null && y >= yr[0] && y <= yr[1]; })
} : [...pendingShards];
loadingAllFeedPages = false; (async () => {
})(); for (const url of toLoad) {
pendingShards = pendingShards.filter(u => u !== url);
try {
const fresh = await loadShardActivities(url);
const existing = new Map(all.map(a => [a.id, a]));
for (const a of fresh) if (!existing.has(a.id)) existing.set(a.id, a);
all = [...existing.values()].sort((a, b) =>
(b.started_at ?? '').localeCompare(a.started_at ?? ''),
);
} catch { /* ignore — partial results still useful */ }
}
loadingAllShards = false;
})();
}
}
$: {
const yr = _neededYearRange(datePre, customFrom, customTo);
if ((!!query.trim() || yr !== null) && feedNextPage > 0 && !loadingAllFeedPages) {
loadingAllFeedPages = true;
// Capture at loop start — dateFrom is reactive and may change mid-fetch.
const effectiveFrom = dateFrom;
(async () => {
while (feedNextPage > 0) {
const page = feedNextPage;
feedNextPage = page < feedTotalPages ? page + 1 : 0;
try {
const fresh = await loadCombinedFeedPage(base, page);
const existing = new Map(all.map(a => [a.id, a]));
for (const a of fresh) if (!existing.has(a.id)) existing.set(a.id, a);
all = [...existing.values()].sort((a, b) =>
(b.started_at ?? '').localeCompare(a.started_at ?? ''),
);
// Feed is sorted newest-first. Once the oldest activity in this page
// predates our from-filter, everything needed is already loaded.
if (effectiveFrom && fresh.length > 0) {
const oldest = fresh.reduce((m, a) => (a.started_at ?? '') < (m.started_at ?? '') ? a : m);
if ((oldest.started_at ?? '') < effectiveFrom) { feedNextPage = 0; break; }
}
} catch { /* ignore — partial results still useful */ }
}
loadingAllFeedPages = false;
})();
}
} }
$: if (mounted) { $: if (mounted) {