From 53cc2c9121ebb29bbf02d1d90e87bcaea46ad006 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 18 Apr 2026 13:23:34 -0500 Subject: [PATCH] fix(map): refresh overlay on reconnect and tab visibility change When the tab was hidden or the LiveView socket dropped, the server-side bounds/scores assigns reset while the hook's scoreGrid retained its old viewport data. Tiles Leaflet created or regenerated outside that stale extent painted blank, leaving the overlay visibly truncated. --- assets/js/propagation_map_hook.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/assets/js/propagation_map_hook.ts b/assets/js/propagation_map_hook.ts index dc19c91c..7699ce1c 100644 --- a/assets/js/propagation_map_hook.ts +++ b/assets/js/propagation_map_hook.ts @@ -145,6 +145,7 @@ interface PropagationMapHook extends ViewHook { selectedTime: string | null forecastCache: Map playbackTimer: ReturnType | null + visibilityHandler: (() => void) | null showDetailPanel(this: PropagationMapHook): void hideDetailPanel(this: PropagationMapHook): void @@ -661,6 +662,7 @@ export const PropagationMap: Record & { startPlayback(this: PropagationMapHook): void stopPlayback(this: PropagationMapHook): void sendBounds(this: PropagationMapHook): void + reconnected(this: PropagationMapHook): void destroyed(this: PropagationMapHook): void } = { mounted(this: PropagationMapHook) { @@ -975,6 +977,19 @@ export const PropagationMap: Record & { requestAnimationFrame(() => this.sendBounds()) // Re-check bounds after layout settles (sidebar, fonts, etc.) setTimeout(() => { this.map.invalidateSize(); this.sendBounds() }, 500) + + // When the tab is hidden, Leaflet may create or drop score tiles while the + // server-side assigns (bounds, scores) are gone. On return, the retained + // scoreGrid covers the old viewport and any freshly-created tiles outside + // it paint blank. Re-fetch bounds so update_scores repaints all tiles with + // fresh data. reconnected() handles the socket-drop case on top of this. + this.visibilityHandler = () => { + if (document.visibilityState === "visible") { + this.map.invalidateSize() + this.sendBounds() + } + } + document.addEventListener("visibilitychange", this.visibilityHandler) this.map.on("moveend", () => { this.sendBounds() if (this.gridVisible) { @@ -1462,11 +1477,25 @@ export const PropagationMap: Record & { }) }, + reconnected(this: PropagationMapHook) { + // Server assigns (bounds, scores) reset on reconnect. Re-send bounds so + // update_scores refreshes the client scoreGrid and repaints all tiles, + // fixing partial/blank overlay after the tab comes back. + if (this.map) { + this.map.invalidateSize() + this.sendBounds() + } + }, + destroyed(this: PropagationMapHook) { if (this.playbackTimer !== null) { clearInterval(this.playbackTimer) this.playbackTimer = null } + if (this.visibilityHandler) { + document.removeEventListener("visibilitychange", this.visibilityHandler) + this.visibilityHandler = null + } if (this.map) this.map.remove() } }