fix(shipwrecks): the page expires each source's wrecks by its own clock, from the age the proxy reports

The page compared the proxy's fetchedAt with its own clock, so a skewed
browser clock cleared or kept wrecks wrongly; a snapshot without a timestamp
never expired; and one old source cleared the other's fresh wrecks. The proxy
now reports each source's age on its own clock and its seven-day limit; the
page turns them into per-source deadlines when the snapshot arrives (seven
days from arrival when no age is given) and, on a failed refresh, removes only
the sources past theirs. A refused short refresh is logged.
This commit is contained in:
Sameh Khamis committed 2026-11-10 13:38:31 -08:00
1 parent 6e4dd6299e
commit 7704de5075
6 files changed
+119 -8

No files matched your search

+11
View File
@@ -120,16 +120,25 @@ export function shipwrecksProxy({
if (result.status === 'fulfilled' && !shrunk)
state.good = { records: result.value, fetchedAt: done };
else state.retryAt = done + RETRY_MS;
if (shrunk)
console.warn(
`[shipwrecks] ${names[i]} refresh refused: ${result.value.length} ` +
`records against ${state.good.records.length} last good`,
);
});
}
/** The served snapshot: every source with usable records, merged once. */
function compose(at) {
const sourceFlags = {};
// How old each source's records are, on this clock, so the page can
// expire them by its own clock without comparing the two.
const ages = {};
let stale = false;
let fetchedAt = null;
for (const [name, state] of Object.entries(sources)) {
sourceFlags[name] = usable(state, at);
ages[name] = sourceFlags[name] ? at - state.good.fetchedAt : null;
if (!sourceFlags[name]) continue;
if (!fresh(state, at)) stale = true;
fetchedAt = Math.min(fetchedAt ?? Infinity, state.good.fetchedAt);
@@ -155,6 +164,8 @@ export function shipwrecksProxy({
fetchedAt,
version: key,
sources: sourceFlags,
ages,
maxAgeMs: STALE_MAX_MS,
records: snapshot.records,
},
stale,