Files
meshcore-gui/docs/MAP_ARCHITECTURE.md
T
pe1hvh af74caf766 fix(route): default reply channel to message channel, add date to timestamps
WHAT
- route_page: _default_reply_channel() derives the reply select default
  from msg.channel instead of the first entry in the channel list
- models: new Message.date field, now_date() and display_timestamp();
  format_line() and incoming()/outgoing() use them
- message_archive: persist "date" alongside "time" and "timestamp_utc"
- room_server_panel + route_page info header use display_timestamp()
- VERSION 1.22.2 -> 1.22.3

WHY
The route page exists to show one specific message, so that message —
not the ordering of the channel list — is the correct source for the
reply default. The date could not be fixed at the render site because
Message had no date at all: now_timestamp() yields HH:MM:SS and the
date lived only as timestamp_utc inside the archive layer, unread by
from_dict(). A first-class field plus a single formatting helper fixes
the cause and keeps the format in one place.

NOTES
No breaking changes. The archive schema gains one additive field;
pre-1.22.3 entries have their date derived from timestamp_utc
(converted to local time) and fall back to time-only when absent.
Message.incoming() gained an optional keyword-only date parameter.
Message display lines are ~11 characters wider.
2026-08-16 08:32:24 +02:00

215 lines
3.9 KiB
Markdown

# Map Architecture — MeshCore GUI
## Overview
The MeshCore GUI map subsystem is implemented as a **browser-managed Leaflet runtime** embedded inside a NiceGUI container.
The key design decision is that the **map lifecycle is owned by the browser**, not by the Python UI update loop.
NiceGUI acts only as a container and data provider.
This architecture prevents map resets, marker flicker, and viewport jumps during the 500 ms dashboard refresh cycle.
The rationale and binding rules for this approach are recorded in **`docs/adr/ADR-001-browser-managed-leaflet.md`**.
---
# Architecture
```
NiceGUI Dashboard
│
│ snapshot (500 ms)
▼
MapPanel (Python)
│
│ JSON payload
▼
Leaflet Runtime (Browser)
│
├─ Map instance (persistent)
├─ Marker registry
├─ Contact cluster layer
├─ Theme state
└─ Viewport state
```
---
# Component Responsibilities
## MapPanel (Python)
Location:
```
meshcore_gui/gui/panels/map_panel.py
```
Responsibilities:
* provides the map container
* injects the Leaflet runtime assets
* sends compact map snapshots
* handles UI actions:
* theme toggle
* center on device
MapPanel **does NOT control the Leaflet map directly**.
It only sends data.
---
## MapSnapshotService
Location:
```
meshcore_gui/services/map_snapshot_service.py
```
Responsibilities:
* converts device/contact data into a compact JSON snapshot
* ensures stable node identifiers
* prepares payloads for the browser runtime
Example snapshot structure:
```json
{
"device": {...},
"contacts": [...],
"force_center": false
}
```
Snapshots are emitted every **500 ms** by the dashboard update loop.
---
## Leaflet Runtime
Location:
```
meshcore_gui/static/leaflet_map_panel.js
```
Responsibilities:
* initialize the Leaflet map once
* maintain persistent map instance
* manage marker registry
* maintain a persistent contact cluster layer
* keep the own-device marker outside clustering
* apply snapshots incrementally
* manage map theme and viewport state
Key design rules:
```
map is created once
markers updated incrementally
snapshots never recreate the map
clustering is attached only after maxZoom is known
```
---
# Update Flow
```
SharedData
│
▼
Dashboard update loop (500 ms)
│
▼
MapSnapshotService
│
▼
MapPanel
│
▼
Leaflet Runtime
```
Snapshots are **coalesced** so the browser applies only the newest payload.
---
# Theme Handling
Theme changes are handled via a **dedicated theme channel**.
Snapshots do **not** carry theme information.
Reason:
Embedding theme state in snapshots caused race conditions where queued snapshots overwrote explicit user selections.
Theme state is managed in the browser runtime and restored on reconnect.
---
# Marker Model
Markers are keyed by **stable node id**.
```
device marker (standalone)
contact markers (clustered)
```
Updates are applied incrementally:
```
add marker
update marker
remove marker
```
This prevents marker flicker during the refresh loop.
---
# Reconnect Behaviour
When the NiceGUI connection temporarily drops:
1. the Leaflet runtime persists in the browser
2. the map instance remains intact
3. theme and viewport state are restored
4. snapshot updates resume once the connection returns
---
# Future Extensions
Possible improvements without breaking the architecture:
* heatmap layers
* route overlays
* tile provider switching
* richer cluster icons or spiderfy tuning
All extensions must remain **browser-managed**.
---
# Summary
The MeshCore map subsystem follows a strict separation:
```
Python → data
Browser → map lifecycle
```
This prevents UI refresh cycles from interfering with map state and ensures smooth rendering even with frequent dashboard updates.
Binding constraints (what developers must and must not do) are listed in **`docs/adr/ADR-001-browser-managed-leaflet.md`** under "Consequences → Bindende uitvloeisels".