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
+43 -47
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,48 +194,48 @@
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) throw new Error(result.error || "Could not load trip route.");
if (!response.ok) { if (signal.aborted) return;
throw new Error(result.error || "Could not load trip route.");
}
if (routeLayer) { if (routeLayer) routeLayer.remove();
routeLayer.remove(); const coordinates = result.points.map(p => [p.latitude, p.longitude]);
} routeLayer = L.featureGroup();
const coordinates = result.points.map(function (point) { L.polyline(coordinates, { color: "#4e73df", weight: 5 }).addTo(routeLayer);
return [point.latitude, point.longitude]; if (coordinates.length) {
}); L.marker(coordinates[0]).bindPopup("Trip start").addTo(routeLayer);
routeLayer = L.featureGroup(); L.marker(coordinates[coordinates.length - 1]).bindPopup("Trip end").addTo(routeLayer);
L.polyline(coordinates, { color: "#4e73df", weight: 5 }).addTo(routeLayer); }
if (coordinates.length) { routeLayer.addTo(map);
L.marker(coordinates[0]).bindPopup("Trip start").addTo(routeLayer); if (signal.aborted) { routeLayer.remove(); return; }
L.marker(coordinates[coordinates.length - 1]).bindPopup("Trip end").addTo(routeLayer);
}
routeLayer.addTo(map);
if (coordinates.length > 1) {
map.fitBounds(routeLayer.getBounds(), { padding: [24, 24] });
} else if (coordinates.length === 1) {
map.setView(coordinates[0], 15);
}
recenterBtn.hidden = false;
recenterBtn.onclick = function () {
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; }
summary.textContent = `${formatDistance(result.trip.distance_km)} · ` recenterBtn.hidden = false;
+ `${formatDuration(result.trip.duration_seconds)} · ` recenterBtn.onclick = function () {
+ `avg ${formatSpeed(result.trip.average_speed_kmh)} · ` if (coordinates.length > 1) {
+ `max ${formatSpeed(result.trip.max_speed_kmh)} · ` map.fitBounds(routeLayer.getBounds(), { padding: [24, 24] });
+ `${result.points.length} route points`; } else if (coordinates.length === 1) {
preloadGeocodes(result.points); map.setView(coordinates[0], 15);
initializeTimeline(result.points); }
};
summary.textContent = `${formatDistance(result.trip.distance_km)} · `
+ `${formatDuration(result.trip.duration_seconds)} · `
+ `avg ${formatSpeed(result.trip.average_speed_kmh)} · `
+ `max ${formatSpeed(result.trip.max_speed_kmh)} · `
+ `${result.points.length} route points`;
if (signal.aborted) { routeLayer.remove(); return; }
preloadGeocodes(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 () {