fix(phase26): debounce trip clicks, guard against stale renders

This commit is contained in:
Mathieu Moran
2026-08-11 05:07:35 -04:00
parent cdb3fc19c7
commit 90edc73735
+20 -24
View File
@@ -28,6 +28,7 @@
let replayPoints = []; let replayPoints = [];
let replayTimer = null; let replayTimer = null;
let replayController = null; let replayController = null;
let _replayDebounce = null;
const recenterBtn = document.getElementById("btn-recenter-history"); const recenterBtn = document.getElementById("btn-recenter-history");
const replayLocation = document.getElementById("trip-replay-location"); const replayLocation = document.getElementById("trip-replay-location");
@@ -193,21 +194,15 @@
replayController.abort(); replayController.abort();
} }
replayController = new AbortController(); replayController = new AbortController();
const response = await fetch( const signal = replayController.signal;
`/api/trip/${tripId}`, try {
{ signal: replayController.signal } const response = await fetch(`/api/trip/${tripId}`, { signal });
);
const result = await response.json(); const result = await response.json();
if (!response.ok) { if (!response.ok) throw new Error(result.error || "Could not load trip route.");
throw new Error(result.error || "Could not load trip route."); if (signal.aborted) return;
}
if (routeLayer) { if (routeLayer) routeLayer.remove();
routeLayer.remove(); const coordinates = result.points.map(p => [p.latitude, p.longitude]);
}
const coordinates = result.points.map(function (point) {
return [point.latitude, point.longitude];
});
routeLayer = L.featureGroup(); routeLayer = L.featureGroup();
L.polyline(coordinates, { color: "#4e73df", weight: 5 }).addTo(routeLayer); L.polyline(coordinates, { color: "#4e73df", weight: 5 }).addTo(routeLayer);
if (coordinates.length) { if (coordinates.length) {
@@ -215,11 +210,13 @@
L.marker(coordinates[coordinates.length - 1]).bindPopup("Trip end").addTo(routeLayer); L.marker(coordinates[coordinates.length - 1]).bindPopup("Trip end").addTo(routeLayer);
} }
routeLayer.addTo(map); routeLayer.addTo(map);
if (signal.aborted) { routeLayer.remove(); return; }
if (coordinates.length > 1) { if (coordinates.length > 1) {
map.fitBounds(routeLayer.getBounds(), { padding: [24, 24] }); map.fitBounds(routeLayer.getBounds(), { padding: [24, 24] });
} else if (coordinates.length === 1) { } else if (coordinates.length === 1) {
map.setView(coordinates[0], 15); map.setView(coordinates[0], 15);
} }
if (signal.aborted) { routeLayer.remove(); return; }
recenterBtn.hidden = false; recenterBtn.hidden = false;
recenterBtn.onclick = function () { recenterBtn.onclick = function () {
if (coordinates.length > 1) { if (coordinates.length > 1) {
@@ -233,8 +230,12 @@
+ `avg ${formatSpeed(result.trip.average_speed_kmh)} · ` + `avg ${formatSpeed(result.trip.average_speed_kmh)} · `
+ `max ${formatSpeed(result.trip.max_speed_kmh)} · ` + `max ${formatSpeed(result.trip.max_speed_kmh)} · `
+ `${result.points.length} route points`; + `${result.points.length} route points`;
if (signal.aborted) { routeLayer.remove(); return; }
preloadGeocodes(result.points); preloadGeocodes(result.points);
initializeTimeline(result.points); initializeTimeline(result.points);
} catch (e) {
if (e.name !== "AbortError") showError(e.message);
}
} }
const tripList = document.getElementById("trip-list"); const tripList = document.getElementById("trip-list");
@@ -351,18 +352,13 @@
} }
} }
// Replay button click (desktop) or row click (mobile) // Replay button click (desktop) or row click (mobile) — debounced
const button = event.target.closest("[data-trip-id]");
const row = event.target.closest("tr[data-trip-id]");
const tripId = button ? button.dataset.tripId : (row ? row.dataset.tripId : null); const tripId = button ? button.dataset.tripId : (row ? row.dataset.tripId : null);
if (!tripId) { if (!tripId) return;
return; clearTimeout(_replayDebounce);
} _replayDebounce = setTimeout(() => {
replayTrip(tripId).catch(function (replayError) { replayTrip(tripId);
if (replayError.name !== "AbortError") { }, 100);
showError(replayError.message);
}
});
}); });
scrubber.addEventListener("input", function () { scrubber.addEventListener("input", function () {