From 888dd0f1a8f033eb983831c9e7b2642c8f35b07c Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Tue, 13 Jan 2026 14:48:32 -0800 Subject: [PATCH] Update CLAUDEs --- CLAUDE.md | 5 +++++ app/CLAUDE.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ frontend/CLAUDE.md | 33 +++++++++++++++++++++++++++++---- 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d96d6f9..7a9a754 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -92,6 +92,7 @@ A web interface for MeshCore mesh radio networks. The backend connects to a Mesh │ │ ├── useWebSocket.ts # WebSocket hook │ │ └── components/ │ │ ├── CrackerPanel.tsx # WebGPU key cracking +│ │ ├── MapView.tsx # Leaflet map showing node locations │ │ └── ... │ └── vite.config.ts ├── references/meshcore_py/ # MeshCore Python library @@ -206,6 +207,10 @@ All endpoints are prefixed with `/api` (e.g., `/api/health`). | POST | `/api/messages/direct` | Send direct message | | POST | `/api/messages/channel` | Send channel message | | POST | `/api/packets/decrypt/historical` | Decrypt stored packets | +| GET | `/api/packets/decrypt/progress` | Get historical decryption progress | +| POST | `/api/packets/maintenance` | Delete old packets (cleanup) | +| POST | `/api/packets/dedup` | Remove duplicate raw packets | +| GET | `/api/packets/dedup/progress` | Get deduplication progress | | POST | `/api/contacts/{key}/mark-read` | Mark contact conversation as read | | POST | `/api/channels/{key}/mark-read` | Mark channel as read | | POST | `/api/read-state/mark-all-read` | Mark all conversations as read | diff --git a/app/CLAUDE.md b/app/CLAUDE.md index f30e594..060f263 100644 --- a/app/CLAUDE.md +++ b/app/CLAUDE.md @@ -331,6 +331,50 @@ KeyStore.clear_private_key() - Never logged - Lost on server restart (must re-export from radio) +## Advertisement Parsing (`decoder.py`) + +Advertisement packets contain contact information including optional GPS coordinates. + +### Packet Structure + +``` +Bytes 0-31: Public key (32 bytes) +Bytes 32-35: Timestamp (4 bytes, little-endian Unix timestamp) +Bytes 36-99: Signature (64 bytes) +Byte 100: App flags +Bytes 101+: Optional fields (location, name) based on flags +``` + +### App Flags (byte 100) + +- Bits 0-3: Device role (1=Chat, 2=Repeater, 3=Room, 4=Sensor) +- Bit 4: Has location (lat/lon follow) +- Bit 5: Has feature 1 +- Bit 6: Has feature 2 +- Bit 7: Has name (null-terminated string at end) + +### GPS Extraction + +When bit 4 is set, latitude and longitude follow as signed int32 little-endian values, +divided by 1,000,000 to get decimal degrees: + +```python +from app.decoder import parse_advertisement + +advert = parse_advertisement(payload_bytes) +if advert: + print(f"Device role: {advert.device_role}") # 1=Chat, 2=Repeater + if advert.lat and advert.lon: + print(f"Location: {advert.lat}, {advert.lon}") +``` + +### Data Flow + +1. `event_handlers.py` receives ADVERTISEMENT event +2. `packet_processor.py` calls `parse_advertisement()` to extract data +3. Contact is upserted with location data (`lat`, `lon`) and `device_role` as `type` +4. Frontend MapView displays contacts with GPS coordinates + ## ACK and Repeat Detection The `acked` field is an integer count, not a boolean: diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index 06fa218..b8231f6 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -12,6 +12,7 @@ This document provides context for AI assistants and developers working on the R - **shadcn/ui components** - Sheet, Tabs, Button (in `components/ui/`) - **meshcore-hashtag-cracker** - WebGPU-accelerated channel key bruteforcing - **nosleep.js** - Prevents device sleep during cracking +- **leaflet / react-leaflet** - Interactive map for node locations ## Directory Structure @@ -41,9 +42,11 @@ frontend/ │ │ ├── MessageInput.tsx # Text input with imperative handle │ │ ├── ContactAvatar.tsx # Contact profile image component │ │ ├── RawPacketList.tsx # Raw packet feed display -│ │ ├── CrackerPanel.tsx # WebGPU channel key cracker +│ │ ├── MapView.tsx # Leaflet map showing node locations +│ │ ├── CrackerPanel.tsx # WebGPU channel key cracker (lazy-loads wordlist) │ │ ├── NewMessageModal.tsx -│ │ └── ConfigModal.tsx # Radio config + app settings +│ │ ├── ConfigModal.tsx # Radio config + app settings +│ │ └── MaintenanceModal.tsx # Database maintenance (cleanup, dedup) │ └── test/ │ ├── setup.ts # Test setup (jsdom, matchers) │ ├── messageParser.test.ts @@ -204,8 +207,8 @@ interface Message { } interface Conversation { - type: 'contact' | 'channel' | 'raw'; - id: string; // PublicKey for contacts, ChannelKey for channels + type: 'contact' | 'channel' | 'raw' | 'map'; + id: string; // PublicKey for contacts, ChannelKey for channels, 'raw'/'map' for special views name: string; } @@ -566,6 +569,7 @@ Deep linking to conversations via URL hash: - `#channel/RoomName` - Opens a channel (leading `#` stripped from name for cleaner URLs) - `#contact/ContactName` - Opens a DM - `#raw` - Opens the raw packet feed +- `#map` - Opens the node map ```typescript // Parse hash on initial load @@ -604,6 +608,27 @@ const maxLengthRef = useRef(6); Progress reporting shows rate in Mkeys/s or Gkeys/s depending on speed. +## MapView + +The `MapView` component displays contacts with GPS coordinates on an interactive Leaflet map. + +### Features + +- **Location filtering**: Only shows contacts with lat/lon that were heard within the last 7 days +- **Freshness coloring**: Markers colored by how recently the contact was heard: + - Bright green (`#22c55e`) - less than 1 hour ago + - Light green (`#4ade80`) - less than 1 day ago + - Yellow-green (`#a3e635`) - less than 3 days ago + - Gray (`#9ca3af`) - older (up to 7 days) +- **Node/repeater distinction**: Regular nodes have black outlines, repeaters are larger with no outline +- **Geolocation**: Tries browser geolocation first, falls back to fitting all markers in view +- **Popups**: Click a marker to see contact name, last heard time, and coordinates + +### Data Source + +Contact location data (`lat`, `lon`) is extracted from advertisement packets in the backend (`decoder.py`). +The `last_seen` timestamp determines marker freshness. + ## Sidebar Features - **Sort toggle**: Default is 'recent' (most recent message first), can toggle to alphabetical