From 9bec5721d0240ba8f75d68d13a8c05c6fa114fa8 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sun, 3 May 2026 16:20:45 -0500 Subject: [PATCH] ux(rover-planning): table rows are real links Each path-table cell now wraps its content in <.link navigate={...}>, so the row behaves as a native anchor: hovering shows the destination URL in the browser status bar, right-click 'open in new tab' works, middle-click works, and the rendered HTML is meaningful to assistive tech / scrapers / link-validators. Replaces the phx-click={JS.navigate(...)} pattern, which gave none of those affordances and required the user to trust that the row was clickable. Whole-row hover highlight is preserved via a CSS :has(a:hover) selector so cell-hover still tints the row. --- .../live/rover_planning_live/show.ex | 86 ++++++++++++++----- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/lib/microwaveprop_web/live/rover_planning_live/show.ex b/lib/microwaveprop_web/live/rover_planning_live/show.ex index 275178f3..634c660f 100644 --- a/lib/microwaveprop_web/live/rover_planning_live/show.ex +++ b/lib/microwaveprop_web/live/rover_planning_live/show.ex @@ -14,7 +14,6 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do alias Microwaveprop.RoverPlanning alias Microwaveprop.RoverPlanning.Mission alias MicrowavepropWeb.LocationResolver - alias Phoenix.LiveView.JS @impl true def mount(%{"id" => id}, _session, socket) do @@ -329,6 +328,29 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do defp path_url(_, _), do: nil + # Wraps a single table cell's content in a real when an href is + # available, so the browser shows the destination on hover and + # right-click / middle-click work natively. Falls back to a plain + #
when the row has no path (e.g. compute hasn't started). + attr :href, :string, default: nil + slot :inner_block, required: true + + defp path_cell(%{href: href} = assigns) when is_binary(href) do + ~H""" + <.link navigate={@href} class="block px-3 py-2 text-inherit no-underline"> + {render_slot(@inner_block)} + + """ + end + + defp path_cell(assigns) do + ~H""" +
+ {render_slot(@inner_block)} +
+ """ + end + @impl true def render(assigns) do summary = progress_summary(assigns.paths) @@ -513,38 +535,58 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do + <%!-- Each cell wraps its content in <.link>, so the row + behaves as a real anchor: native href hover preview, + right-click "open in new tab", middle-click, etc. The + row-hover highlight is preserved via :has(a:hover) so + hovering any cell still tints the whole row. --%> - + <% {primary, secondary} = station_lines(path) %> -
{primary}
-
{secondary}
+ <.path_cell href={path_url(@mission, path)}> +
{primary}
+
{secondary}
+ - - {band_label(path.band_mhz || @mission.band_mhz)} + + <.path_cell href={path_url(@mission, path)}> + {band_label(path.band_mhz || @mission.band_mhz)} + - {status_badge(path.status)} - - {if path.result, do: format_distance(path.result["distance_km"]), else: "—"} + + <.path_cell href={path_url(@mission, path)}> + {status_badge(path.status)} + - - {if path.result, - do: format_loss(path.result["total_baseline_loss_db"]), - else: "—"} + + <.path_cell href={path_url(@mission, path)}> + {if path.result, do: format_distance(path.result["distance_km"]), else: "—"} + - - {if path.result, do: verdict_badge(path.result["verdict"]), else: "—"} + + <.path_cell href={path_url(@mission, path)}> + {if path.result, + do: format_loss(path.result["total_baseline_loss_db"]), + else: "—"} + - - {if path.result, - do: propagation_badge(path.result["propagation_score"]), - else: "—"} + + <.path_cell href={path_url(@mission, path)}> + {if path.result, do: verdict_badge(path.result["verdict"]), else: "—"} + + + + <.path_cell href={path_url(@mission, path)}> + {if path.result, + do: propagation_badge(path.result["propagation_score"]), + else: "—"} +