From 90edc73735c43fbf45a7789312d123545eb499d2 Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Tue, 11 Aug 2026 05:07:35 -0400 Subject: [PATCH] fix(phase26): debounce trip clicks, guard against stale renders --- static/js/history.js | 90 +++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 47 deletions(-) diff --git a/static/js/history.js b/static/js/history.js index 1fce36b..80f3b24 100644 --- a/static/js/history.js +++ b/static/js/history.js @@ -28,6 +28,7 @@ let replayPoints = []; let replayTimer = null; let replayController = null; + let _replayDebounce = null; const recenterBtn = document.getElementById("btn-recenter-history"); const replayLocation = document.getElementById("trip-replay-location"); @@ -193,48 +194,48 @@ replayController.abort(); } replayController = new AbortController(); - const response = await fetch( - `/api/trip/${tripId}`, - { signal: replayController.signal } - ); - const result = await response.json(); - if (!response.ok) { - throw new Error(result.error || "Could not load trip route."); - } + const signal = replayController.signal; + try { + const response = await fetch(`/api/trip/${tripId}`, { signal }); + const result = await response.json(); + if (!response.ok) throw new Error(result.error || "Could not load trip route."); + if (signal.aborted) return; - if (routeLayer) { - routeLayer.remove(); - } - const coordinates = result.points.map(function (point) { - return [point.latitude, point.longitude]; - }); - routeLayer = L.featureGroup(); - L.polyline(coordinates, { color: "#4e73df", weight: 5 }).addTo(routeLayer); - if (coordinates.length) { - L.marker(coordinates[0]).bindPopup("Trip start").addTo(routeLayer); - 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 (routeLayer) routeLayer.remove(); + const coordinates = result.points.map(p => [p.latitude, p.longitude]); + routeLayer = L.featureGroup(); + L.polyline(coordinates, { color: "#4e73df", weight: 5 }).addTo(routeLayer); + if (coordinates.length) { + L.marker(coordinates[0]).bindPopup("Trip start").addTo(routeLayer); + L.marker(coordinates[coordinates.length - 1]).bindPopup("Trip end").addTo(routeLayer); + } + routeLayer.addTo(map); + if (signal.aborted) { routeLayer.remove(); return; } if (coordinates.length > 1) { map.fitBounds(routeLayer.getBounds(), { padding: [24, 24] }); } else if (coordinates.length === 1) { map.setView(coordinates[0], 15); } - }; - 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`; - preloadGeocodes(result.points); - initializeTimeline(result.points); + if (signal.aborted) { routeLayer.remove(); return; } + recenterBtn.hidden = false; + recenterBtn.onclick = function () { + if (coordinates.length > 1) { + map.fitBounds(routeLayer.getBounds(), { padding: [24, 24] }); + } else if (coordinates.length === 1) { + map.setView(coordinates[0], 15); + } + }; + 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"); @@ -351,18 +352,13 @@ } } - // Replay button click (desktop) or row click (mobile) - const button = event.target.closest("[data-trip-id]"); - const row = event.target.closest("tr[data-trip-id]"); + // Replay button click (desktop) or row click (mobile) — debounced const tripId = button ? button.dataset.tripId : (row ? row.dataset.tripId : null); - if (!tripId) { - return; - } - replayTrip(tripId).catch(function (replayError) { - if (replayError.name !== "AbortError") { - showError(replayError.message); - } - }); + if (!tripId) return; + clearTimeout(_replayDebounce); + _replayDebounce = setTimeout(() => { + replayTrip(tripId); + }, 100); }); scrubber.addEventListener("input", function () {