Fix network map mobile overflow and infer MQTT hops from SNR=0 (#2)

* Fix network map mobile overflow and infer MQTT hops from SNR=0

- Add min-h-0 and flex-shrink-0 to map layout so legend/actions don't
  overflow the viewport on mobile
- Infer viaMqtt=true for traceroute hops with SNR exactly 0
- Change MQTT hop color from purple to orange across map, legend, and
  node detail badges

https://claude.ai/code/session_01Ffqq7YPCJE28uUFR88eK7C

* Revert MQTT color to purple; keep SNR=0 MQTT inference

The color change was unintended — MQTT hops should stay purple. The
SNR=0 inference in traceroute processing correctly marks those hops
as viaMqtt so they render as purple dashed lines.

https://claude.ai/code/session_01Ffqq7YPCJE28uUFR88eK7C

* Fix test setup: mock URL.createObjectURL for maplibre-gl

maplibre-gl calls URL.createObjectURL during import for its worker
setup, which doesn't exist in jsdom. Add the mock to test setup.

https://claude.ai/code/session_01Ffqq7YPCJE28uUFR88eK7C

* Run go fmt on unformatted files

https://claude.ai/code/session_01Ffqq7YPCJE28uUFR88eK7C

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Daniel Pupius
2026-03-15 23:06:20 -07:00
committed by GitHub
parent ededf5c93d
commit 26a2bba441
6 changed files with 30 additions and 18 deletions

View File

@@ -26,7 +26,7 @@ function MapPage() {
return ( return (
<PageWrapper> <PageWrapper>
<div className="flex flex-col h-[calc(100vh-7rem)] md:h-[calc(100vh-5rem)]"> <div className="flex flex-col h-[calc(100vh-7rem)] md:h-[calc(100vh-5rem)]">
<div className="flex-1 flex flex-col"> <div className="flex-1 flex flex-col min-h-0">
<NetworkMap <NetworkMap
fullHeight fullHeight
ref={mapRef as any} ref={mapRef as any}
@@ -34,7 +34,7 @@ function MapPage() {
showLinks={showLinks} showLinks={showLinks}
/> />
<div className="mt-2 rounded-lg p-2 text-xs flex items-center justify-between effect-inset"> <div className="mt-2 rounded-lg p-2 text-xs flex items-center justify-between effect-inset flex-shrink-0">
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<div className="flex items-center gap-x-3 gap-y-1 flex-wrap"> <div className="flex items-center gap-x-3 gap-y-1 flex-wrap">
<span className={`inline-flex items-center px-2 py-0.5 rounded ${getNodeColors(ActivityLevel.RECENT, false).textClass} ${getNodeColors(ActivityLevel.RECENT, false).background}`}> <span className={`inline-flex items-center px-2 py-0.5 rounded ${getNodeColors(ActivityLevel.RECENT, false).textClass} ${getNodeColors(ActivityLevel.RECENT, false).background}`}>

View File

@@ -156,6 +156,8 @@ function processTopology(
for (let i = 0; i < forwardPath.length - 1; i++) { for (let i = 0; i < forwardPath.length - 1; i++) {
const cap = Math.min(snrTowards.length, forwardPath.length - 1); const cap = Math.min(snrTowards.length, forwardPath.length - 1);
const snr = i < cap ? snrTowards[i] / 4 : undefined; const snr = i < cap ? snrTowards[i] / 4 : undefined;
// SNR of exactly 0 indicates an MQTT-bridged hop
const hopViaMqtt = snr === 0 || !!data.viaMqtt;
upsertObservation( upsertObservation(
state, state,
forwardPath[i], // sender forwardPath[i], // sender
@@ -163,7 +165,7 @@ function processTopology(
snr, snr,
undefined, undefined,
"traceroute", "traceroute",
!!data.viaMqtt, hopViaMqtt,
timestamp, timestamp,
hopCount hopCount
); );
@@ -176,6 +178,8 @@ function processTopology(
for (let i = 0; i < returnPath.length - 1; i++) { for (let i = 0; i < returnPath.length - 1; i++) {
const cap = Math.min(snrBack.length, returnPath.length - 1); const cap = Math.min(snrBack.length, returnPath.length - 1);
const snr = i < cap ? snrBack[i] / 4 : undefined; const snr = i < cap ? snrBack[i] / 4 : undefined;
// SNR of exactly 0 indicates an MQTT-bridged hop
const hopViaMqtt = snr === 0 || !!data.viaMqtt;
upsertObservation( upsertObservation(
state, state,
returnPath[i], returnPath[i],
@@ -183,7 +187,7 @@ function processTopology(
snr, snr,
undefined, undefined,
"traceroute", "traceroute",
!!data.viaMqtt, hopViaMqtt,
timestamp, timestamp,
hopCount hopCount
); );

View File

@@ -1,6 +1,14 @@
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { vi } from 'vitest'; import { vi } from 'vitest';
// Mock URL.createObjectURL (required by maplibre-gl worker setup)
if (typeof window.URL.createObjectURL === 'undefined') {
window.URL.createObjectURL = vi.fn(() => '');
}
if (typeof window.URL.revokeObjectURL === 'undefined') {
window.URL.revokeObjectURL = vi.fn();
}
// Mock for window.matchMedia // Mock for window.matchMedia
Object.defineProperty(window, 'matchMedia', { Object.defineProperty(window, 'matchMedia', {
writable: true, writable: true,