From 26a2bba4418f658b84bb2c5d90e1c077de00ae98 Mon Sep 17 00:00:00 2001 From: Daniel Pupius Date: Sun, 15 Mar 2026 23:06:20 -0700 Subject: [PATCH] Fix network map mobile overflow and infer MQTT hops from SNR=0 (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- main.go | 8 ++++---- mqtt/broker.go | 10 +++++----- mqtt/broker_test.go | 8 ++++---- web/src/routes/map.tsx | 6 +++--- web/src/store/slices/topologySlice.ts | 8 ++++++-- web/src/test/setup.ts | 8 ++++++++ 6 files changed, 30 insertions(+), 18 deletions(-) diff --git a/main.go b/main.go index 635e7d6..b86dda3 100644 --- a/main.go +++ b/main.go @@ -40,10 +40,10 @@ type Config struct { ChannelKeys []string // Statistics configuration - StatsInterval time.Duration - CacheSize int - CacheRetention time.Duration - VerboseLogging bool + StatsInterval time.Duration + CacheSize int + CacheRetention time.Duration + VerboseLogging bool } // getEnv retrieves an environment variable with the given prefix or returns the default value diff --git a/mqtt/broker.go b/mqtt/broker.go index c628078..83bcd55 100644 --- a/mqtt/broker.go +++ b/mqtt/broker.go @@ -30,11 +30,11 @@ var typePriority = map[pb.PortNum]int{ pb.PortNum_TEXT_MESSAGE_COMPRESSED_APP: 5, pb.PortNum_NEIGHBORINFO_APP: 4, // rare; protect from eviction pb.PortNum_TRACEROUTE_APP: 3, - pb.PortNum_POSITION_APP: 3, - pb.PortNum_NODEINFO_APP: 2, // frequent; lower priority - pb.PortNum_TELEMETRY_APP: 2, - pb.PortNum_ROUTING_APP: 2, - pb.PortNum_MAP_REPORT_APP: 2, + pb.PortNum_POSITION_APP: 3, + pb.PortNum_NODEINFO_APP: 2, // frequent; lower priority + pb.PortNum_TELEMETRY_APP: 2, + pb.PortNum_ROUTING_APP: 2, + pb.PortNum_MAP_REPORT_APP: 2, } // defaultTypePriority applies to any port type not listed above. diff --git a/mqtt/broker_test.go b/mqtt/broker_test.go index 6babda5..73f71ff 100644 --- a/mqtt/broker_test.go +++ b/mqtt/broker_test.go @@ -60,10 +60,10 @@ func TestNodeAwareCacheBasicOrdering(t *testing.T) { func TestPressureEvictsOldestOfLowestPriority(t *testing.T) { c := NewNodeAwareCache(3, time.Hour) - c.Add(pkt(1, 1, pb.PortNum_NODEINFO_APP)) // priority 2 - c.Add(pkt(2, 1, pb.PortNum_NEIGHBORINFO_APP)) // priority 4 - c.Add(pkt(3, 1, pb.PortNum_NODEINFO_APP)) // priority 2 — at cap - c.Add(pkt(4, 1, pb.PortNum_NODEINFO_APP)) // pressure: evicts oldest pri=2 (ID 1) + c.Add(pkt(1, 1, pb.PortNum_NODEINFO_APP)) // priority 2 + c.Add(pkt(2, 1, pb.PortNum_NEIGHBORINFO_APP)) // priority 4 + c.Add(pkt(3, 1, pb.PortNum_NODEINFO_APP)) // priority 2 — at cap + c.Add(pkt(4, 1, pb.PortNum_NODEINFO_APP)) // pressure: evicts oldest pri=2 (ID 1) got := c.GetAll() if len(got) != 3 { diff --git a/web/src/routes/map.tsx b/web/src/routes/map.tsx index 28e1469..a3d315e 100644 --- a/web/src/routes/map.tsx +++ b/web/src/routes/map.tsx @@ -26,15 +26,15 @@ function MapPage() { return (
-
+
- -
+ +
diff --git a/web/src/store/slices/topologySlice.ts b/web/src/store/slices/topologySlice.ts index 4a68218..50b0fa9 100644 --- a/web/src/store/slices/topologySlice.ts +++ b/web/src/store/slices/topologySlice.ts @@ -156,6 +156,8 @@ function processTopology( for (let i = 0; i < forwardPath.length - 1; i++) { const cap = Math.min(snrTowards.length, forwardPath.length - 1); const snr = i < cap ? snrTowards[i] / 4 : undefined; + // SNR of exactly 0 indicates an MQTT-bridged hop + const hopViaMqtt = snr === 0 || !!data.viaMqtt; upsertObservation( state, forwardPath[i], // sender @@ -163,7 +165,7 @@ function processTopology( snr, undefined, "traceroute", - !!data.viaMqtt, + hopViaMqtt, timestamp, hopCount ); @@ -176,6 +178,8 @@ function processTopology( for (let i = 0; i < returnPath.length - 1; i++) { const cap = Math.min(snrBack.length, returnPath.length - 1); const snr = i < cap ? snrBack[i] / 4 : undefined; + // SNR of exactly 0 indicates an MQTT-bridged hop + const hopViaMqtt = snr === 0 || !!data.viaMqtt; upsertObservation( state, returnPath[i], @@ -183,7 +187,7 @@ function processTopology( snr, undefined, "traceroute", - !!data.viaMqtt, + hopViaMqtt, timestamp, hopCount ); diff --git a/web/src/test/setup.ts b/web/src/test/setup.ts index 572681e..fe190bc 100644 --- a/web/src/test/setup.ts +++ b/web/src/test/setup.ts @@ -1,6 +1,14 @@ import '@testing-library/jest-dom'; 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 Object.defineProperty(window, 'matchMedia', { writable: true,