fix(contact): stop loading-flash on already-enriched contacts

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.
This commit is contained in:
Graham McIntire 2026-04-18 13:56:00 -05:00
parent 871582ee09
commit cf6812f154
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 20 additions and 77 deletions

View file

@ -100,16 +100,6 @@ defmodule MicrowavepropWeb.Layouts do
North Texas Microwave Society
</a>
</div>
<div>
Found this useful?
<a
href="https://www.paypal.com/ncp/payment/53VLBD2E67JAE"
target="_blank"
class="link link-hover"
>
Donate to NTMS
</a>
</div>
<div>
<.link navigate="/privacy" class="link link-hover">Privacy</.link>
</div>

View file

@ -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 != []