From 0ee6867298725c3213f70d9a5350633816ec3939 Mon Sep 17 00:00:00 2001 From: ItsMeVino <249552181+vfortin99-ctrl@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:44:22 -0400 Subject: [PATCH 1/3] Add "View on map" button to node detail panel's Location section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jumps from a node's detail panel straight to that node on the Map tab, framed close on it (zoom 14) via the existing lat/lng/zoom deep-link params MapView already reads on mount — no changes needed there. Only shown when the node has a location. selectedNodeId is preserved across the switch (mirrors handleViewObserverStats), so the same detail panel stays open, now over the map. Verified against a live production Beacon instance: clicking the button from a node in the Nodes tab correctly switches to Map, centers/zooms on that node's exact coordinates, and keeps its highlighted marker and detail panel in view. Co-Authored-By: Claude Sonnet 5 --- src/App.tsx | 18 ++++++++++++++++++ src/features/nodes/NodeDetailPanel.tsx | 12 +++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 28fcb79..1841a0e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -256,6 +256,23 @@ function AppInner() { if (id === null) dropSelectionParam("observer"); }, [dropSelectionParam]); + // Jump from a node's detail panel to its location on the Map tab, framed close on that node. + // Bypasses handleTabChange (like handleViewObserverStats) so selectedNodeId survives the switch on + // mobile too — the point of this action is to keep the same node's panel open, now over the map. + const handleViewOnMap = useCallback( + (lat: number, lng: number) => { + setSearchParams((prev) => { + const next = new URLSearchParams(prev); + next.set("tab", "Map"); + next.set("lat", lat.toFixed(5)); + next.set("lng", lng.toFixed(5)); + next.set("zoom", "14"); + return next; + }); + }, + [setSearchParams], + ); + // Jump from an observer's detail panel to its telemetry on the Stats tab (Stats → Observer, preselected). const handleViewObserverStats = useCallback( (id: string) => { @@ -340,6 +357,7 @@ function AppInner() { }} onViewNode={setSelectedNodeId} onAnalyzePacket={setOverlayPacketHash} + onViewOnMap={handleViewOnMap} /> )} {overlayNodeId && ( diff --git a/src/features/nodes/NodeDetailPanel.tsx b/src/features/nodes/NodeDetailPanel.tsx index 2073738..7e9be46 100644 --- a/src/features/nodes/NodeDetailPanel.tsx +++ b/src/features/nodes/NodeDetailPanel.tsx @@ -73,9 +73,10 @@ interface NodeDetailPanelProps { onViewObserver: (observerId: string) => void; onViewNode?: (nodeId: string) => void; onAnalyzePacket?: (hash: string) => void; + onViewOnMap?: (lat: number, lng: number) => void; } -export function NodeDetailPanel({ nodeId, onClose, onViewObserver, onViewNode, onAnalyzePacket }: NodeDetailPanelProps) { +export function NodeDetailPanel({ nodeId, onClose, onViewObserver, onViewNode, onAnalyzePacket, onViewOnMap }: NodeDetailPanelProps) { const { data: node, isLoading } = useQuery({ queryKey: ["node", nodeId], queryFn: () => getNode(nodeId), @@ -145,6 +146,15 @@ export function NodeDetailPanel({ nodeId, onClose, onViewObserver, onViewNode, o {node.lng != null && } {node.locationSource && } + {hasLocation && onViewOnMap && ( + + )} )} From 6abe716600b6491315656ff2f3bda34bc6b8cbbb Mon Sep 17 00:00:00 2001 From: ItsMeVino <249552181+vfortin99-ctrl@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:56:39 -0400 Subject: [PATCH 2/3] Extend "View on map" to the node overlay opened from a path hop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Packet Analyzer's PATH DATA lets you click a hop to peek at that node via NodeDetailOverlay (a modal, separate component from the regular NodeDetailPanel used by the Nodes/Map tabs) — that overlay didn't get the button in the first commit. Wiring it through required a bit more than passing the prop down: handleViewOnMap deliberately bypasses handleTabChange (see its own comment) so it doesn't clear selectedNodeId on mobile, but that also means it won't clear overlayNodeId as a side effect the way onViewObserver's handleTabChange call does above it. So the overlay closes itself explicitly and promotes the node into the shared selection, so NodeDetailPanel — not the overlay — is what reopens on the Map tab. Verified live: clicking a path hop in the packet analyzer opens the overlay, its "View on map" button switches to Map and centers/zooms on that hop's node, same as from the Nodes tab. Co-Authored-By: Claude Sonnet 5 --- src/App.tsx | 9 +++++++++ src/features/nodes/NodeDetailOverlay.tsx | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 1841a0e..a39ee0b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -369,6 +369,15 @@ function AppInner() { setSelectedObserverId(observerId); }} onViewNode={setOverlayNodeId} + onViewOnMap={(lat, lng) => { + // handleViewOnMap bypasses handleTabChange (see its own comment), so the overlay + // won't be cleared as a side effect here the way onViewObserver's handleTabChange + // call clears it above — close it and promote the node into the shared selection + // explicitly, so NodeDetailPanel (not this overlay) is what reopens on the Map tab. + handleViewOnMap(lat, lng); + setSelectedNodeId(overlayNodeId); + setOverlayNodeId(null); + }} /> )} {overlayPacketHash && ( diff --git a/src/features/nodes/NodeDetailOverlay.tsx b/src/features/nodes/NodeDetailOverlay.tsx index 514c949..ef448de 100644 --- a/src/features/nodes/NodeDetailOverlay.tsx +++ b/src/features/nodes/NodeDetailOverlay.tsx @@ -4,11 +4,12 @@ import { ModalOverlay } from "../../components/ModalOverlay"; // Node detail shown as a modal over the packet analyzer: the panel sits where the packet drawer is // (right side) and the rest dims, so a user can peek at a path hop's node and close back to the packet. -export function NodeDetailOverlay({ nodeId, onClose, onViewObserver, onViewNode }: { +export function NodeDetailOverlay({ nodeId, onClose, onViewObserver, onViewNode, onViewOnMap }: { nodeId: string; onClose: () => void; onViewObserver: (observerId: string) => void; onViewNode?: (nodeId: string) => void; + onViewOnMap?: (lat: number, lng: number) => void; }) { useEffect(() => { function onKey(e: KeyboardEvent) { @@ -20,7 +21,7 @@ export function NodeDetailOverlay({ nodeId, onClose, onViewObserver, onViewNode return ( - + ); } From c0dbb101a42f3ac59318e579800c10fc8c62e27d Mon Sep 17 00:00:00 2001 From: ItsMeVino <249552181+vfortin99-ctrl@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:57:50 -0400 Subject: [PATCH 3/3] Match "View on map" button styling to "View path on map" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was a small text link like "View observer →"; switched to the same full-width bordered pill + folded-map icon as the packet analyzer's "View path on map" button, since both are "jump to a location on the map" actions and read as a matched pair now. Co-Authored-By: Claude Sonnet 5 --- src/features/nodes/NodeDetailPanel.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/features/nodes/NodeDetailPanel.tsx b/src/features/nodes/NodeDetailPanel.tsx index 7e9be46..72245c3 100644 --- a/src/features/nodes/NodeDetailPanel.tsx +++ b/src/features/nodes/NodeDetailPanel.tsx @@ -150,9 +150,13 @@ export function NodeDetailPanel({ nodeId, onClose, onViewObserver, onViewNode, o )}