From 1f8765f75792586567cdf69799d2aa40c48c7925 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Thu, 30 Apr 2026 17:34:38 -0500 Subject: [PATCH] =?UTF-8?q?fix(weather):=20tolerate=20stale=20`map=5Fbound?= =?UTF-8?q?s`=20from=20/map=20=E2=86=92=20/weather=20navigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /map's PropagationMap hook pushes `map_bounds` on every moveend/zoomend. When a user live-navigates from /map to /weather, an in-flight bounds push can land on the new WeatherMapLive (the same LV channel survives the navigation), and WeatherMapLive's `handle_event/3` had no clause for it — the LV process crashed with FunctionClauseError, taking the user's session down. WeatherMapLive doesn't need viewport bounds — the /weather/cells HTTP endpoint reads them off the URL the client builds — so the safe fix is a no-op handler. Tested. --- .../live/weather_map_live.ex | 8 +++++++ .../live/weather_map_live_test.exs | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/microwaveprop_web/live/weather_map_live.ex b/lib/microwaveprop_web/live/weather_map_live.ex index ffbe9f6c..8db5644c 100644 --- a/lib/microwaveprop_web/live/weather_map_live.ex +++ b/lib/microwaveprop_web/live/weather_map_live.ex @@ -232,6 +232,14 @@ defmodule MicrowavepropWeb.WeatherMapLive do end end + # `/map`'s PropagationMap hook pushes `map_bounds` on moveend/zoomend. + # When a user navigates /map → /weather, an in-flight bounds push from + # the old hook can land on the new WeatherMapLive (LV reuses sockets + # across live navigation). WeatherMapLive doesn't need bounds — the + # /weather/cells HTTP endpoint computes them client-side — so swallow + # the event instead of crashing the LV process. + def handle_event("map_bounds", _params, socket), do: {:noreply, socket} + def handle_event("point_detail", %{"lat" => lat, "lon" => lon}, socket) do detail = if socket.assigns.valid_time do diff --git a/test/microwaveprop_web/live/weather_map_live_test.exs b/test/microwaveprop_web/live/weather_map_live_test.exs index be1eb5b5..95b79d8a 100644 --- a/test/microwaveprop_web/live/weather_map_live_test.exs +++ b/test/microwaveprop_web/live/weather_map_live_test.exs @@ -319,4 +319,26 @@ defmodule MicrowavepropWeb.WeatherMapLiveTest do assert html =~ ~s(data-selected-layer=) end end + + describe "stale client events" do + test "ignores `map_bounds` from a stale browser tab without crashing", %{conn: conn} do + # A user can navigate /map → /weather while the propagation map's + # JS hook still has a queued moveend → pushEvent("map_bounds", …) + # in flight. The WeatherMapLive doesn't need viewport bounds (it + # fetches /weather/cells over HTTP using the *client* viewport), + # but it MUST tolerate the rogue event instead of crashing the + # LV process. + {:ok, lv, _html} = live(conn, ~p"/weather") + + bounds = %{ + "south" => 32.57, + "north" => 33.62, + "west" => -97.37, + "east" => -95.76 + } + + html = render_hook(lv, "map_bounds", bounds) + assert is_binary(html) + end + end end