Adds a new "uplinks" mode to the network map links toggle, cycling
through links → uplinks → off. Uplinks draws a purple dotted line
from each gateway to every node it has uploaded packets for, making
the MQTT upload graph visible as a separate view from RF topology.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* 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>
Map:
- Fix __publicField error by setting esbuild target to esnext
- Replace react-map-gl with direct maplibre-gl imperative API across all map components
- Add dark theme styling for MapLibre popup, attribution, and navigation controls
- Add NavigationControl to NetworkMap and NodeLocationMap
- Fix auto-zoom race condition using mapLoaded state
- Fix node click popup being immediately dismissed
- Add MQTT links as separate dashed purple layer
- Reduce node/dot sizes on NetworkMap
- Switch glyph CDN to fonts.openmaptiles.org
- Extend map legend with SNR line color key and MQTT indicator
Node detail:
- Extract shared ConnectionRow and ConnectionList components
- Compact connections table: single row per entry with color-coded SNR, badges, short time
- Apply same compact style to NeighborInfoPacket neighbor list
- Move Connections section into right column below Last Activity
- Split Gateway Node out of Device Information into its own card
- Fold observed node count into "Recently observed nodes (N)" subtext
- Add formatLastSeenShort for compact time display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use a frosted-glass white tint instead of near-black so controls
don't disappear against the dark map. Replace the bright button
separator with a subtle rgba white border.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Style .maplibregl-ctrl-group with dark semi-transparent background
and invert the SVG icons to match the dark map aesthetic. Add
NavigationControl to NetworkMap alongside NodeLocationMap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
maplibre-gl v5 bundles reference __publicField and other esbuild
class-field helpers without defining them — a known upstream bug
(maplibre-gl-js issue #6680). Setting esbuildOptions.target and
build.target to 'esnext' tells esbuild to emit native class fields
instead of helper-based transforms, eliminating the missing symbol.
Remove all previous workaround attempts (define rewrite, globalThis
polyfill in index.html).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous index.html polyfill set window.__publicField but Vite's
dep optimizer pre-bundles in Node.js where that global is never set,
so the error persisted.
Two-part fix:
- vite.config.ts define: rewrite bare __publicField identifiers to
globalThis.__publicField (a valid entity name esbuild accepts)
- index.html: set globalThis.__publicField using the exact
Object.defineProperty signature esbuild expects, before modules run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
react-map-gl's Source/Layer components were silently dropping layers
even after onLoad gating — likely a compatibility issue with
maplibre-gl v5. Switch all three map components to the imperative API
(new maplibregl.Map, map.on('load', ...), source.setData()) which is
the approach shown in MapLibre's own docs and has no wrapper layer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Source/Layer components mounted before the map style finishes loading
fail silently. Add mapLoaded state + onLoad callback to LocationMap,
NodeLocationMap, and NetworkMap so GeoJSON sources and layers are only
added after the style is ready.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pass fullHeight to NodeLocationMap in NodeDetail so it fills the
h-[400px] wrapper rather than rendering its own h-[300px] and
leaving dead space below
- Always show the center dot in NodeLocationMap: the previous threshold
(accuracyMeters < 100) suppressed the dot for typical Meshtastic
precision values (10-13 bits → 2-10km accuracy), making the map
appear empty even when a location was known
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Default attribution renders as a white pill on dark maps.
Override with dark semi-transparent background and muted text,
consistent across all map components.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both were missing attributionControl so they rendered MapLibre's
default (full text). Now consistent with LocationMap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
esbuild's define only accepts literals/identifiers, not arrow functions.
Inject the polyfill as a plain script tag before the module entry point
so it is available when any pre-compiled dependency uses it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix __publicField runtime error from pre-compiled npm packages by
defining the helper in vite optimizeDeps esbuildOptions
- Compact attribution control on LocationMap thumbnails so it doesn't
dominate small maps
- Remove duplicate accuracy overlay in MapReportPacket (caption +
accuracy div both at bottom-0 overlapping); accuracy still shown
in the KeyValuePair section below
- Fix NodeLocationMap height: min-h-[300px] doesn't give ReactMap a
resolved height, changed to h-[300px]
- Add short name lookup to TraceroutePacket hops (same pattern as
NeighborInfoPacket)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migrate all three map components (Map, GoogleMap, NetworkMap) to MapLibre GL JS
- Extract shared CARTO_DARK_STYLE constants into lib/mapStyle.ts
- Move buildCircleCoords to lib/mapUtils.ts (was duplicated across components)
- Rename exports: Map → LocationMap, GoogleMap → NodeLocationMap
- Remove dead props (width, height, nightMode) from LocationMap interface
- Lazy-mount GL contexts via IntersectionObserver to prevent WebGL exhaustion
- Fix Math.spread RangeError in NetworkMap bounds calculation
- Remove showLinks conditional render in favour of visibility layout property
- Remove cursor state; set canvas cursor style directly on map interactions
- Remove Google Maps API key env vars from .env.example and .env.local
- Move Vite dev server to port 5747 (avoids cached redirect on 3000)
- Fix CORS/404: set VITE_API_BASE_URL="" so browser uses Vite proxy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert plain function declarations inside component body to useCallback
with correct deps, eliminating the eslint-disable-react-hooks banner.
Extract buildMarkerContent to module level (pure, no state/refs).
Reorder fitMapToBounds before resetAutoZoom to avoid TDZ reference.
Replace innerHTML SVG in marker with DOM-API construction.
Replace showInfoWindow innerHTML template with .textContent DOM APIs
to eliminate XSS risk from node name/status fields.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unused mustParseDuration from main.go
- Replace http.Error calls after SSE headers with plain returns;
skip bad packets instead of killing the stream on marshal error
- Change processedPackets/seenPackets from boolean to timestamp values
and prune entries older than 24h on each packet to prevent unbounded
memory growth in long-running sessions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Split routers on dashboard and extend retention periods
- Add Router node type with 12-hour stale timeout (vs 30 min for regular nodes)
- Create RouterList component to display router nodes separately
- Update NodeList to filter out routers (similar to gateways)
- Add yellow color scheme for router nodes to distinguish from gateways (green) and nodes (blue)
- Extend mesh traffic retention across the board:
- Client-side packet age filter: 12h → 24h
- Broker cache size: 50 → 200 packets
- Messages per channel: 100 → 500
* Update regular node activity threshold to 60 minutes
* Fix TypeScript errors in router filtering logic
* Fix docker-build to use buildx explicitly with --load flag
* Add default empty value for MESHSTREAM_GOOGLE_MAPS_API_KEY in docker-build
* Restructure docker buildx command to fix path argument parsing
* Remove trailing backslash before build context path in docker-build
* Quote build args and separate path argument in docker-build