# Repository Guidelines Current version: `1.8.4` (see `VERSIONS.md`). ## Project Structure & Module Organization - `backend/app.py` wires FastAPI routes, MQTT lifecycle, and websocket broadcast flow. - `backend/config.py` centralizes env configuration. - `backend/state.py` holds shared in-memory state + dataclasses. - `backend/decoder.py` contains payload parsing, multibyte MeshCore decoding, and route helpers. - `backend/los.py` contains LOS math + elevation fetch helpers. - `backend/history.py` handles 24h route history persistence/cleanup. - `backend/static/index.html` is the HTML shell + template placeholders. - `backend/static/styles.css` holds all UI styles. - `backend/static/app.js` holds all client-side map logic. - `backend/static/sw.js` is the PWA service worker. - `backend/requirements.txt` and `backend/Dockerfile` define Python and Node dependencies. - `docker-compose.yaml` runs the service as `meshmap-live`. - `data/` stores persisted state (`state.json`), route history (`route_history.jsonl`), role overrides (`device_roles.json`), and optional neighbor overrides (`neighbor_overrides.json`). - `.env` holds dev runtime settings; `.env.example` mirrors template defaults. - `VERSION.txt` tracks the current version (now `1.8.4`); append changes in `VERSIONS.md`. ## Build, Test, and Development Commands - `docker compose up -d --build` rebuilds and restarts the backend (preferred workflow). - `docker compose logs -f meshmap-live` follows server logs for MQTT activity. - `curl -s http://localhost:8080/snapshot` checks current device state. - `curl -s http://localhost:8080/stats` shows ingest/route counters. - `curl -s http://localhost:8080/debug/last` inspects recent decoded packets. ## Coding Style & Naming Conventions - Python in `backend/*.py` uses **2-space indentation**; keep it consistent. - The project enforces an **80-character column limit** for Python code to maintain readability. - Formatting is handled by `yapf` using the config in `backend/.style.yapf`. - To format code manually: `yapf --in-place --recursive --style backend/.style.yapf backend/` - HTML/CSS/JS in `backend/static/index.html` uses 2 spaces as well. - Use lowercase, underscore-separated names for Python variables/functions. - Prefer small helper functions for parsing/normalization; keep logging concise. ## Testing Guidelines - Run automated tests with `pip install -r requirements-dev.txt && pytest -q`. - Validate runtime behavior manually with `/snapshot`, `/stats`, and `/debug/last`. ## Commit & Pull Request Guidelines - No git history is available in this workspace, so there is no established commit convention. - If you add git later, use short, imperative commit messages and describe behavioral changes in PRs. ## Configuration & Operations - Most behavior is controlled by `.env` (MQTT host, TLS, topics, TTLs, map start lat/lon/zoom, MQTT online window, default map layer). - Current dev defaults: `DEVICE_TTL_HOURS=96`, `PATH_TTL_SECONDS=172800`, `MQTT_ONLINE_SECONDS=600`, `ROUTE_TTL_SECONDS=60`, `TRAIL_LEN=0`, `DISTANCE_UNITS=mi`. - Node size default is `NODE_MARKER_RADIUS` (pixels); users can override via the HUD slider. - History link size default is `HISTORY_LINK_SCALE`; users can override via the History panel slider. - Map radius filter: `MAP_RADIUS_KM=0` disables filtering; `.env.example` uses `241.4` km (150mi). Applies to nodes, trails, routes, and history edges. - `MAP_RADIUS_SHOW=true` draws a debug circle centered on `MAP_START_LAT/LON`. - Set `TRAIL_LEN=0` to disable trails entirely; the HUD trail hint is removed when trails are off. - Coverage button only appears when `COVERAGE_API_URL` is set. - `QR_CODE_BUTTON_ENABLED=true` adds a `Generate QR Code` button to node popups that opens a theme-aware MeshCore-compatible contact QR modal; default is off. - Geographic filtering defaults to radius mode; polygon mode is optional via `MAP_BOUNDARY_MODE=polygon` and `MAP_BOUNDARY_FILE`. - Standalone boundary builder: `tools/map-boundary-builder.html` outputs the JSON consumed by `MAP_BOUNDARY_FILE`; hosted copy: [https://yellowcooln.com/map-boundary-builder/](https://yellowcooln.com/map-boundary-builder/). - Radar country-bounds controls: `WEATHER_RADAR_COUNTRY_BOUNDS_ENABLED` and `WEATHER_RADAR_COUNTRY_LOOKUP_URL`. `WEATHER_RADAR_COUNTRY_LOOKUP_URL` defaults to `/weather/radar/country-bounds` and is an HTTP route path, not a filesystem directory. - Weather wind controls: `WEATHER_WIND_ENABLED`, `WEATHER_WIND_API_URL`, `WEATHER_WIND_GRID_SIZE`, `WEATHER_WIND_REFRESH_SECONDS`. - LOS curvature controls: `LOS_CURVATURE_ENABLED` and `LOS_CURVATURE_FACTOR`. When unset, LOS curvature defaults to `true` with factor `1.333333`. - `DEVICE_COORDS_FILE` points to optional coordinate overrides (default `/data/device_coords.json`). - `NEIGHBOR_OVERRIDES_FILE` can point at a JSON map/list of neighbor pairs to resolve hash collisions. - Auto-neighbor overrides are controlled by: `AUTO_NEIGHBOR_OVERRIDES_ENABLED`, `AUTO_NEIGHBOR_OVERRIDES_FILE`, `AUTO_NEIGHBOR_ACTIVE_DAYS`, `AUTO_NEIGHBOR_MIN_EDGE_COUNT`, and `AUTO_NEIGHBOR_REFRESH_SECONDS`. - Optional custom HUD link appears when `CUSTOM_LINK_URL` is set. - Update banner uses `GIT_CHECK_ENABLED` (compare local vs upstream) with `GIT_CHECK_PATH` pointing at a git repo. - `GIT_CHECK_FETCH` controls whether the server fetches before comparing; `GIT_CHECK_INTERVAL_SECONDS` sets the recheck interval. - Route history modes default to `path` via `ROUTE_HISTORY_ALLOWED_MODES`. - `ROUTE_PATH_MAX_LEN` caps oversized path-hash lists (prevents bogus long routes). - `ROUTE_ALLOW_AMBIGUOUS_ONE_BYTE_FALLBACK=true` restores pre-`v1.7.0` closest/time-based fallback for colliding 1-byte route prefixes; default is conservative `false`. - Persisted state in `data/state.json` is loaded on startup; edit with care. - After editing `backend/*.py` or `backend/static/*`, rebuild with `docker compose up -d --build`. - History tool visibility is not persisted; it always loads off unless `history=on` is in the URL. ## Feature Notes - MQTT supports WSS/TLS or TCP; the official `@michaelhart/meshcore-decoder` runs via a Node helper for advert/location parsing, 1/2/3-byte path decoding, and conservative MQTT role extraction. - Routes are rendered as trace/message/advert lines with TTL cleanup; 0,0 coords (including stringy zeros) are filtered from trails/routes. - Route IDs are observer-aware (`message_hash:receiver_id`) so multi-observer receptions do not overwrite each other. - Dev route debug: in non-prod mode (`PROD_MODE=false`), clicking a route line logs hop-by-hop details to the browser console (distance, hashes, origin/receiver, timestamps). - Turnstile protection only activates when `PROD_MODE=true` and requires `TURNSTILE_ENABLED`, `TURNSTILE_SITE_KEY`, and `TURNSTILE_SECRET_KEY`. - Turnstile browser auth (`meshmap_auth`/`?auth=`) is used for map + WebSocket sessions; protected API routes still require `PROD_TOKEN`. - Discord/social embeds can be allowlisted under Turnstile via `TURNSTILE_BOT_BYPASS` and `TURNSTILE_BOT_ALLOWLIST`. - Route hash collisions prefer known neighbors (and optional overrides); long path lists are skipped via `ROUTE_PATH_MAX_LEN`. - Route collisions fall back to closest-hop selection and drop hops beyond `ROUTE_MAX_HOP_DISTANCE`. - `ROUTE_INFRA_ONLY` restricts route lines to repeaters/rooms (companions still show as markers). - Heatmap shows recent traffic points (TTL controlled). - LOS uses `/los/elevations` for client-side realtime updates (with `/los` fallback). - LOS uses Earth-curvature-aware obstruction checks by default in both the frontend realtime path and the backend `/los` fallback. - LOS UI includes peak markers, a relay suggestion marker, elevation profile hover, and map-line hover sync. - LOS legend items (clear/blocked/peaks/relay) are hidden until the LOS tool is active. - Mobile LOS supports long-press on nodes (Shift+click on desktop); endpoints can be dragged or click-selected and moved via map click. - MQTT online status is derived from `/status` + `/internal` TTL windows; `/packets` is tracked as feed activity. - Devices that remain MQTT-online keep their last known coordinates on the map until MQTT presence expires, even if fresh location packets stop. - `MQTT_ONLINE_FORCE_NAMES` (comma-separated device names) forces selected nodes to always appear MQTT online. - Service worker fetches navigations with `no-store` to avoid stale UI/env toggles (e.g., radius debug ring). - Node search + labels toggle (persisted in localStorage) and a GitHub link in the HUD. - Hide-nodes toggle hides markers, trails, heat, routes, and history layers. - Heat toggle hides the heatmap; it defaults on and the button turns green when heat is off. - History line weight was reduced for a lighter map overlay. - HUD logo uses `SITE_ICON`; if missing/invalid it falls back to a small "Map" badge to keep the toggle usable. - Route styling now keys off payload type: 2/5 = Message (blue), 8/9 = Trace (orange), 4 = Advert (green). - 24h route history persists to `data/route_history.jsonl`, renders as a volume heatline, and defaults off (History tool panel). - History tool opens a right-side panel with a 5-step heat filter slider: All, Blue, Yellow, Yellow+Red, Red; legend swatch hides unless active. - History panel can be dismissed with the X button while leaving history lines visible (History tool brings it back). - History records route modes from `ROUTE_HISTORY_ALLOWED_MODES` (default: `path`). - Propagation render stays visible until a new render; origin changes only mark it dirty. - Propagation now has an adjustable TX antenna gain (dBi) control, and Rx AGL defaults to 1m. - Preview image endpoint renders in-bounds device dots for shared links. - Peers tool opens a right-side panel showing incoming/outgoing neighbors (counts + %) based on rolling peer-history buckets; selecting a node draws peer lines on the map. - Peer-history buckets are also updated from route `point_ids` when a hop cannot be drawn as a visible segment, so peer counts still reflect real adjacency for non-drawable hops. - Peers tool ignores nodes listed in `MQTT_ONLINE_FORCE_NAMES` (used for observer listeners). - Units toggle (km/mi) is stored in localStorage and defaults to `DISTANCE_UNITS`. - PWA support is enabled via `/manifest.webmanifest` + `/sw.js` so mobile browsers can install the app. - Clicking the logo toggles the left HUD panel while LOS/Propagation panels remain open. - Node popups do not auto-pan; dragging the map won’t snap back to keep a popup in view. - Node popups let users click the short key under the node name to copy the full public key, and can optionally show a MeshCore-compatible contact QR modal for that node with the node name plus a clickable truncated key that still copies the full public key. - MQTT disconnect handler tolerates extra Paho args so the loop doesn’t crash; reconnects resume ingest. - Share button copies a URL with `lat`, `lon`, `zoom`, `layer`, `history`, `heat`, `coverage`, `weather`, `labels`, `nodes`, `legend`, `menu`, `units`, and `history_filter` params. - Weather state is not persisted in localStorage; it defaults off unless `weather=on` is in the URL. - URL params override localStorage on load (`history=on` is the only way to load History open). - Node size slider persists in localStorage (`meshmapNodeRadius`) and can be reset by clearing site data. - MeshMapper coverage viewport sync reuses cached rectangles instead of rebuilding all visible squares on every pan/zoom, which keeps the Coverage layer responsive on busy maps.