diff --git a/CHANGELOG.md b/CHANGELOG.md index ec552be..4660fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,33 @@ All notable changes to MeshCore GUI are documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/). +--- +## [1.13.2] - 2026-03-11 — Map Display Bugfix + +### Fixed +- 🛠 **MAP panel blank when contacts list is empty at startup** — dashboard update loop + had two separate conditional map-update blocks that both silently stopped firing after + tick 1 when `data['contacts']` was empty (e.g. device just booted or no contacts + stored). Map panel received no further snapshots and remained blank indefinitely. +- 🛠 **Route map not rendered when no node has GPS coordinates** — `_render_map` in + `route_page.py` returned early before creating the Leaflet container when + `payload['nodes']` was empty. `MeshCoreRouteMapBoot` handles an empty nodes list + correctly (renders map at home area), so the early return was incorrect. + +### Changed +- 🔄 `meshcore_gui/gui/dashboard.py` — Consolidated two conditional map-update blocks + into a single unconditional `self._map.update(data)` that fires on every timer tick + while the MAP panel is active. The JS runtime coalesces pending payloads so only the + newest snapshot is ever applied; the extra calls are cheap. +- 🔄 `meshcore_gui/gui/route_page.py` — Removed early `return` from `_render_map` when + no GPS nodes are present. The Leaflet container is now always created. A small inline + notice is shown when there is no location data instead of skipping the map entirely. + +### Impact +- MAP panel now renders reliably on first open regardless of contact/GPS availability +- Route map now always shows even when route nodes carry no GPS coordinates +- No breaking changes — only the two files above are modified + --- ## [1.13.1] - 2026-03-09 — Message Icon Consistency diff --git a/meshcore_gui/config.py b/meshcore_gui/config.py index 2bbd75d..7c1b8fa 100644 --- a/meshcore_gui/config.py +++ b/meshcore_gui/config.py @@ -25,7 +25,7 @@ from typing import Any, Dict, List # ============================================================================== -VERSION: str = "1.13.1" +VERSION: str = "1.13.2" # ============================================================================== diff --git a/meshcore_gui/gui/dashboard.py b/meshcore_gui/gui/dashboard.py index 72afd2d..e037aa2 100644 --- a/meshcore_gui/gui/dashboard.py +++ b/meshcore_gui/gui/dashboard.py @@ -756,16 +756,14 @@ class DashboardPage: if data['device_updated'] or is_first: self._device.update(data) - # Map updates are intentionally limited to when the map panel - # is visible. Updating Leaflet every 500 ms while hidden can - # trigger excessive tile/layer work in the browser and make the - # rest of the UI feel unresponsive (for example the hamburger - # menu appearing to do nothing). The explicit update in - # _show_panel('map') still refreshes and recenters the map when - # the user opens it. - if self._active_panel == 'map' and ( - data['device_updated'] or is_first - ): + # Map: always send a snapshot while the panel is active. + # The JS runtime coalesces pending payloads — only the newest + # is ever applied — so calling update() on every tick is cheap. + # This ensures the Leaflet runtime always gets at least one + # valid snapshot after it finishes loading, regardless of + # whether device_updated or is_first happened to be True + # on the tick that fired before MeshCoreLeafletBoot was defined. + if self._active_panel == 'map': self._map.update(data) # Channel-dependent UI: always ensure consistency when @@ -790,18 +788,6 @@ class DashboardPage: if data['contacts_updated'] or is_first: self._contacts.update(data) - # Map - if ( - self._active_panel == 'map' - and data['contacts'] - and ( - data['contacts_updated'] - or not self._map.has_markers - or is_first - ) - ): - self._map.update(data) - # Messages (always — for live filter changes) self._messages.update( data, diff --git a/meshcore_gui/gui/route_page.py b/meshcore_gui/gui/route_page.py index bd445f2..9377866 100644 --- a/meshcore_gui/gui/route_page.py +++ b/meshcore_gui/gui/route_page.py @@ -230,14 +230,24 @@ class RoutePage: @staticmethod def _render_map(data: Dict, route: Dict) -> None: - """Render the route map in browser JS using the shared MAP icons.""" + """Render the route map in browser JS using the shared MAP icons. + + The Leaflet container is always rendered. When no nodes carry GPS + coordinates a notice is shown inside the card, but the map itself + still initialises so the user sees the configured home area. + MeshCoreRouteMapBoot handles an empty nodes array gracefully by + displaying the map at payload.center with no markers. + """ with ui.card().classes('w-full'): payload = RoutePage._build_route_map_payload(data, route) + + # Show a notice when no node carries GPS, but do NOT skip the + # Leaflet container. The JS runtime renders the map at the + # configured home area (DEFAULT_MAP_CENTER) with no markers. if not payload['nodes']: ui.label( - '📍 No location data available for map display' - ).classes('text-gray-500 italic p-4') - return + '📍 No GPS location data — map shows home area' + ).classes('text-xs text-gray-400 italic px-2 pt-2') container_id = f'route-map-{uuid4().hex}' ui.html(