From 90ebed728ae0cfb1aad76be48968a8fea487bbea Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Fri, 4 Sep 2026 16:47:39 +0700 Subject: [PATCH] fix(debug): draw the region a coverage switch asked for Switching region fires three requests for it. Two carry the previous viewport: the badge row resizes, the map's ResizeObserver calls invalidateSize, and the moveend that follows reports the old bounds under the new region. Only the third carries the boundary, and it is the oldest of the three, so the stale-response guard threw it away -- leaving the outline undrawn, the map parked on the region before it, and every panorama out of view. The boundary is now applied on the region it describes rather than on request order. Also adds a project .ckignore so src/app/debug/coverage/ reads as the application source it is rather than a test-coverage report directory. --- .claude/.ckignore | 5 +++++ src/app/debug/coverage/page.js | 20 ++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 .claude/.ckignore diff --git a/.claude/.ckignore b/.claude/.ckignore new file mode 100644 index 0000000..2d452a2 --- /dev/null +++ b/.claude/.ckignore @@ -0,0 +1,5 @@ +# Project override for the shipped scout-block baseline. +# +# src/app/debug/coverage/ is application source -- the debug page that draws +# the panorama index on a map -- not a test-coverage report directory. +!coverage diff --git a/src/app/debug/coverage/page.js b/src/app/debug/coverage/page.js index d3d50a6..4476926 100644 --- a/src/app/debug/coverage/page.js +++ b/src/app/debug/coverage/page.js @@ -50,6 +50,10 @@ export default function CoveragePage() { // Only the newest response may update the view: panning fires requests faster // than they come back, and an older one landing last would redraw stale dots. const requestIdRef = useRef(0); + // Which region a response is allowed to draw for. The boundary is applied on + // this rather than on request order -- see load(). Kept current by the effect + // that switches region, which is the only thing that changes it. + const regionRef = useRef(region); const load = useCallback(async (regionCode, viewport) => { const requestId = ++requestIdRef.current; @@ -61,6 +65,19 @@ export default function CoveragePage() { if (viewport) params.set('bbox', viewport.join(',')); const response = await fetch(`/api/debug/region-coverage?${params}`); const json = await response.json(); + + // The boundary rides on a region's first request only, and that request + // is the oldest of the burst a switch sets off: changing region resizes + // the badge row, the map's ResizeObserver calls invalidateSize, and the + // moveend that follows reports the *previous* viewport under the new + // region. Those reports outrank the boundary by request order, so the + // guard below used to discard it -- leaving the region undrawn, the map + // parked on the region before it, and every point out of view. Order + // cannot decide this one; the region it describes can. + if (json.success && json.boundary && regionCode === regionRef.current) { + setBoundary(json.boundary); + } + if (requestId !== requestIdRef.current) return; if (!json.success) { @@ -74,8 +91,6 @@ export default function CoveragePage() { return; } - // Present only on the first request for a region. - if (json.boundary) setBoundary(json.boundary); setPanos(json.panos); setCounts(json.counts); setGeneratedAt(json.generatedAt); @@ -87,6 +102,7 @@ export default function CoveragePage() { }, []); useEffect(() => { + regionRef.current = region; setBoundary(null); setPanos(null); setCounts(null);