From cf6812f154ee13c62ed7fdf0d7d3b6a336490f44 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 18 Apr 2026 13:56:00 -0500 Subject: [PATCH] fix(contact): stop loading-flash on already-enriched contacts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes: - Drop the global contact_enrichment:{hrrr,weather,solar} subscriptions. These topics fire for every QSO's enrichment completion, and the handlers blindly re-queried data for the currently-displayed contact, causing spurious re-renders whenever any enrichment worker finished anywhere in the system. The per-contact terrain subscription stays — that one is correctly scoped. - Only add a slot to `hydration_pending` when the contact's status column says the work is genuinely in flight (:queued or :processing). Previously every connected mount flashed spinners over all eight enrichment sections, even for contacts whose data was already cached and about to return instantly from the async task. Also drop the Donate-to-NTMS footer link. --- lib/microwaveprop_web/components/layouts.ex | 10 --- .../live/contact_live/show.ex | 87 +++++-------------- 2 files changed, 20 insertions(+), 77 deletions(-) diff --git a/lib/microwaveprop_web/components/layouts.ex b/lib/microwaveprop_web/components/layouts.ex index 1e8d020b..0af5b7ee 100644 --- a/lib/microwaveprop_web/components/layouts.ex +++ b/lib/microwaveprop_web/components/layouts.ex @@ -100,16 +100,6 @@ defmodule MicrowavepropWeb.Layouts do North Texas Microwave Society -
- Found this useful? - - Donate to NTMS - -
<.link navigate="/privacy" class="link link-hover">Privacy
diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex index 5c07f0e1..f085883c 100644 --- a/lib/microwaveprop_web/live/contact_live/show.ex +++ b/lib/microwaveprop_web/live/contact_live/show.ex @@ -74,9 +74,6 @@ defmodule MicrowavepropWeb.ContactLive.Show do socket = if connected?(socket) do Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "contact_enrichment:#{contact.id}") - Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "contact_enrichment:hrrr") - Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "contact_enrichment:weather") - Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "contact_enrichment:solar") kickoff_hydration(socket, contact) else @@ -91,19 +88,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do # clauses below update the matching slot and re-run the derived analysis. defp kickoff_hydration(socket, contact) do socket - |> assign( - :hydration_pending, - MapSet.new([ - :weather, - :solar, - :hrrr_path, - :narr_path, - :native_profile, - :terrain, - :iemre, - :radar - ]) - ) + |> assign(:hydration_pending, pending_slots(contact)) |> start_async(:weather, fn -> load_weather(contact) end) |> start_async(:solar, fn -> load_solar(contact) end) |> start_async(:hrrr_path, fn -> Weather.hrrr_profiles_for_path(contact) end) @@ -114,6 +99,25 @@ defmodule MicrowavepropWeb.ContactLive.Show do |> start_async(:radar, fn -> load_radar(contact) end) end + # A slot shows its loading spinner only while the corresponding enrichment + # column on the contact says the work is genuinely in flight. For contacts + # whose status is :complete (or :pending/:unavailable/:failed), the async + # query just returns whatever is cached without any intermediate flash. + defp pending_slots(contact) do + in_flight = [:queued, :processing] + slots = [] + slots = if contact.weather_status in in_flight, do: [:weather | slots], else: slots + + slots = + if contact.hrrr_status in in_flight, + do: [:hrrr_path, :narr_path, :native_profile | slots], + else: slots + + slots = if contact.terrain_status in in_flight, do: [:terrain | slots], else: slots + slots = if contact.iemre_status in in_flight, do: [:iemre | slots], else: slots + MapSet.new(slots) + end + defp mark_hydrated(socket, slot) do assign(socket, :hydration_pending, MapSet.delete(socket.assigns.hydration_pending, slot)) end @@ -458,57 +462,6 @@ defmodule MicrowavepropWeb.ContactLive.Show do )} end - def handle_info({:hrrr_ready, _info}, socket) do - contact = socket.assigns.contact - hrrr_path = Weather.hrrr_profiles_for_path(contact) - hrrr = List.first(hrrr_path) - - if hrrr do - contact = %{contact | hrrr_status: :complete} - soundings = socket.assigns.soundings - terrain = socket.assigns.terrain - elevation_profile = compute_elevation_profile(contact, hrrr_path, soundings) - propagation_analysis = build_propagation_analysis(contact, hrrr_path, terrain, elevation_profile, soundings) - - {:noreply, - assign(socket, - contact: contact, - hrrr: hrrr, - queue_counts: fetch_queue_counts(), - elevation_profile: elevation_profile, - propagation_analysis: propagation_analysis - )} - else - {:noreply, socket} - end - end - - def handle_info({:weather_ready, _info}, socket) do - contact = %{socket.assigns.contact | weather_status: :complete} - weather = load_weather(contact) - soundings = weather.soundings - - hrrr_path = Weather.hrrr_profiles_for_path(contact) - terrain = socket.assigns.terrain - - elevation_profile = compute_elevation_profile(contact, hrrr_path, soundings) - propagation_analysis = build_propagation_analysis(contact, hrrr_path, terrain, elevation_profile, soundings) - - {:noreply, - assign(socket, - contact: contact, - surface_observations: weather.surface_observations, - soundings: soundings, - elevation_profile: elevation_profile, - propagation_analysis: propagation_analysis - )} - end - - def handle_info({:solar_ready, _date}, socket) do - solar = load_solar(socket.assigns.contact) - {:noreply, assign(socket, solar: solar)} - end - defp maybe_enqueue_weather(weather, contact) do has_data = weather.surface_observations != [] or weather.soundings != []