fix(shipwrecks): rebuild unless the snapshot has a version string; a failed page refresh marks the wrecks stale and clears them after seven days

- A source without a version (undefined, not null) compared as unchanged, so
  an injected source's new snapshots were never drawn.
- When the page could not refresh (the proxy answers 502 once both sources
  are past seven days), it kept showing the old wrecks as fresh. They are now
  marked stale, and removed once older than the proxy's own seven-day limit.
This commit is contained in:
Sameh Khamis committed 2026-11-10 13:36:18 -08:00
1 parent 66fba3da47
commit c885825f2b
2 files changed
+61 -2

No files matched your search

+13 -1
View File
@@ -29,6 +29,8 @@ const CARD_HOST_OPTIONS = Object.freeze({
moving: false,
});
const MAX_CARD_LINES = 14;
/** Wrecks not refreshed for this long leave the map (the proxy's own limit). */
const MAX_SHOWN_AGE_MS = 7 * 24 * 60 * 60 * 1000;
/**
* Marker size in pixels: grows with the death toll, from 16 px (no deaths known) to 36 px (about a thousand or more).
*/
@@ -67,6 +69,7 @@ export function createShipwrecksLayer({
openExternal = null,
// One collection for every wreck marker (thousands of them): far cheaper to
// draw and rebuild than an entity each. Tests pass a fake.
now = () => Date.now(),
createMarkers = (scene) =>
new Cesium.BillboardCollection({
scene,
@@ -378,7 +381,7 @@ export function createShipwrecksLayer({
// The page checks every 6 h; the proxy refreshes once a day. Only a
// new snapshot rebuilds the markers.
const unchanged =
snapshot.version !== null &&
typeof snapshot.version === 'string' &&
snapshot.version === _snapshot?.version &&
_pins.length > 0;
_snapshot = snapshot;
@@ -396,6 +399,15 @@ export function createShipwrecksLayer({
return false;
console.warn('[Data:Shipwrecks] Fetch error:', error);
_lastError = error?.message || msg('Shipwrecks unavailable');
// What is on the map was not refreshed: say so, and drop it once it
// is older than the proxy itself would serve.
if (_snapshot) {
const age = now() - (_snapshot.fetchedAt ?? now());
if (age > MAX_SHOWN_AGE_MS) {
_snapshot = null;
rebuild([]);
} else _snapshot = { ..._snapshot, stale: true };
}
notifyRow();
return false;
} finally {
+48 -1
View File
@@ -51,7 +51,7 @@ function fakeMarkers() {
return collection;
}
function harness({ snapshots } = {}) {
function harness({ snapshots, now = () => 1 } = {}) {
const markers = fakeMarkers();
const primitives = [];
const cards = [];
@@ -81,6 +81,7 @@ function harness({ snapshots } = {}) {
getSnapshot({ signal }) {
requests.push(signal);
const next = queue.shift();
if (next instanceof Error) return Promise.reject(next);
return next instanceof Promise ? next : Promise.resolve(next);
},
},
@@ -105,6 +106,7 @@ function harness({ snapshots } = {}) {
picking,
pointer: { isPointerFree: () => true },
openExternal: (url) => opened.push(url),
now: () => now(),
});
layer.init(viewer);
return {
@@ -244,3 +246,48 @@ test('the year pickers list only years with a wreck, and each era is counted', a
[0, 2, 0, 0],
);
});
test('a source without a version rebuilds on every new snapshot', async () => {
const bare = (records) => {
const value = snapshot(records);
delete value.version;
return value;
};
const h = harness({
snapshots: [
bare([wreck('a')]),
bare([wreck('a'), wreck('b')]),
{ ...snapshot([wreck('a')]), version: null },
{ ...snapshot([wreck('a'), wreck('b'), wreck('c')]), version: null },
],
});
h.layer.enable();
for (const expected of [1, 2, 1, 3]) {
await h.layer.update();
assert.equal(h.markers.billboards.length, expected);
}
});
test('a failed refresh marks the shown wrecks stale, and clears them after seven days', async () => {
const DAY = 24 * 60 * 60 * 1000;
let clock = 1;
const h = harness({
now: () => clock,
snapshots: [
snapshot([wreck('a')]),
new Error('Shipwrecks unavailable (HTTP 502)'),
new Error('Shipwrecks unavailable (HTTP 502)'),
],
});
h.layer.enable();
await h.layer.update();
assert.equal(h.layer.getStats().stale, false);
clock = 1 + 2 * DAY;
assert.equal(await h.layer.update(), false);
assert.equal(h.layer.getStats().stale, true);
assert.equal(h.layer.getStats().count, 1, 'still shown, as stale');
clock = 1 + 8 * DAY;
await h.layer.update();
assert.equal(h.layer.getStats().count, 0, 'expired wrecks leave the map');
assert.equal(h.markers.billboards.length, 0);
});