100 Commits
Author SHA1 Message Date
3382d3dc44 Add grayscale map toggle (#29) (#53)
Add a "Grayscale map" option to the map settings popover that desaturates
the base tiles so colored route lines and node markers stand out. The CSS
filter is scoped to Leaflet's tile pane only, so overlays stay in color,
and it works with any selected tileset. The setting persists in
mapLayerSettings (localStorage).

Co-authored-by: Alex Vanderpot <alex@Alexs-MacBook-Pro-2.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 16:34:18 -04:00
7528eac3a2 Derive more neighbor edges: hash-size-aware paths, anchors, confidence tiers (#52)
* Derive more neighbor edges: hash-size-aware paths, anchors, confidence tiers

Rework the meshcore_all_neighbor_edges materialized view (migration 008)
so the map's "show all neighbors" layer surfaces many more, and
higher-quality, edges:

- Parse routing paths at the packet's real hash_size (1/2/3 bytes per hop)
  instead of assuming 1-byte hops, so extended-hash packets are no longer
  mis-sliced, and wide (2-/3-byte) prefixes resolve uniquely.
- Add anchored edges: resolve a hop against a fully-known endpoint (the
  advert originator or the uploading gateway) when exactly one in-region
  repeater of the right prefix sits within a plausible LoRa-hop distance.
- Tag every edge with a derivation `method` and a `confidence` score, and
  aggregate undirected edges across observations.

Frontend: expose method/confidence on the all-neighbors API and color edges
by method / fade by confidence. The old "only MQTT neighbors" checkbox
becomes the top notch of a single confidence selector (MQTT-direct only ->
high -> standard -> all).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Tighten neighbor edges: 100km anchor cap + region-unique anchor binds

Two precision fixes after reviewing cross-metro over-linking:

- Cap anchored edges at 100 km (was 206 km). Anchors bind a hop to the
  single nearby repeater of a prefix, so a wrong bind is indistinguishable
  from a real hop -> a loose cap admitted ~200 km false links. path-uniq
  and direct edges (observed consecutive hops / zero-hop adverts) keep the
  206 km backstop.
- Require a region-unique prefix for anchor binds. Without it, a 1-byte hop
  whose true (unlocated) forwarder is out of set mis-binds to a coincidental
  same-prefix repeater that merely happens to be the lone nearby candidate
  (~22% of 1-byte anchors). Region-uniqueness removes the proximity guess;
  the 100 km check becomes a sanity confirm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Demote anchored edges to substandard confidence

Anchored edges are geographically inferred (resolve a hop to the region-unique
repeater near a known endpoint), not observed consecutive hops, so they are less
trustworthy than any path-uniq edge. Re-rank confidence to:
direct 1.0 > path-uniq-3b 0.8 > path-uniq-2b 0.6 > anchor 0.45 > path-uniq-1b 0.4.

Anchors now fall below the 0.5 "Standard" default (hidden by default, just above
the noisy 1-byte tier). Add an "Include anchored (lower confidence)" UI tier at
0.45 so they can be opted into without the 1-byte noise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Unify neighbor inference: global confidence setting, hover + node page

- Move the neighbor-confidence preference from per-map-layer settings to global
  Settings (ConfigContext); the map all-neighbors layer, map hover, and node
  page all read it. Default "Standard" (0.5).
- Map hover and the node detail page now derive neighbors from the unified graph
  (meshcore_all_neighbor_edges) via a new getMeshcoreNodeAllEdges + a mode=all
  option on the node neighbors route/hook, so they match "show all neighbors".
- Node page shows direct and inferred neighbors in one list; inferred cards carry
  an "Inferred · <method>" + confidence indicator instead of direction arrows.
- Color map hover lines by derivation method (purple/blue/teal), fade by confidence.
- Remove the "Manage Channel Keys" button from the Settings popover (still reachable
  via the + on the messages page).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Keep neighbor lines when either endpoint is in view

Both the server bbox filter (getAllNodeNeighbors) and the client filter
(AllNeighborLines) required BOTH endpoints inside the viewport, so any line
with one end off-screen vanished when zooming in. Switch both to "either
endpoint in view": the server returns an edge if source OR target falls in the
bbox, and the client keeps it if either endpoint is a visible node. Each edge
carries both endpoints' coordinates, so the off-screen end still draws.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Alex Vanderpot <alex@Alexs-MacBook-Pro-2.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 16:33:51 -04:00
Alex Vanderpot 53cc82c38f Merge pull request #51 from ajvpot/ajvpot/add-cascadiamesh-broker
Accept numeric SNR/len/RSSI in MeshCore packets messages
2026-06-19 03:08:06 -04:00
Alex Vanderpot 51307402e8 Merge pull request #50 from ajvpot/ajvpot/refetch-neighbors-on-region-change
Refetch map neighbors when the selected region changes
2026-06-19 02:15:35 -04:00
Alex Vanderpot 5d3d83c318 Merge pull request #48 from ajvpot/ajvpot/show-all-neighbors-pnw-only
Fix "show all neighbors" rendering edges only in the PNW
2026-06-19 02:03:58 -04:00
Alex Vanderpot 421ab43153 Merge pull request #47 from ajvpot/ajvpot/show-mqtt-neighbors-only
Add "Only show MQTT neighbors" map option
2026-06-19 01:52:38 -04:00
Alex Vanderpot 819b81d899 Merge pull request #46 from ajvpot/ajvpot/fix-packet-parsing
Support 2- and 3-byte path hashes and transport-routed packets
2026-06-19 01:25:12 -04:00
2a0e238e06 clickhouse: speed up node search & chat region filter, fix stats dashboards (#45)
Node search now scans/merges meshcore_adverts_latest once and fans each row
out to matching query branches via arrayJoin + a window function, instead of
one UNION ALL branch per query each re-merging the full AggregatingMergeTree.

Chat message queries push the region filter onto the raw table's stored
`region` column before the GROUP BY (instead of a post-aggregation arrayExists
over origin_path_info), eliminating the multi-second full-table scans.

Grafana: rewrite the nodes-over-time panel to drop the rolling self-join, and
fix the messages-by-channel-hash panel which referenced a nonexistent
`regions` column (ClickHouse error code 47).

Co-authored-by: Alex Vanderpot <alex@Alexs-MacBook-Pro-2.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 01:23:23 -04:00
c7bfce1268 Add node privacy: hide nodes whose name contains ⛔️ 🛑 🚫 (#44)
Operators can opt a node out of the public-facing surfaces by putting an
opt-out emoji in the node name. Hidden nodes are removed from the map,
neighbor edges/lists, search, and their own detail page (server-side
ClickHouse filters), and chat messages from a hidden sender are dropped
client-side after decryption. Matching keys on the base codepoint so the
variation-selector form (⛔️) is caught too.

Co-authored-by: Alex Vanderpot <alex@Alexs-MacBook-Pro-2.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:06:07 -04:00
7cea182c6d ingest: batch ClickHouse inserts to stop MQTT flapping & packet loss (#41)
* ingest: batch ClickHouse inserts to stop MQTT flapping & packet loss

The meshcore handler did a synchronous per-message ClickHouse insert on
paho's single inbound goroutine. At ~86ms/insert (single-row inserts +
async_insert wait + materialized views) the goroutine couldn't keep up
with the high-volume letsmesh feed, so it stalled past PingTimeout and
paho declared "pingresp not received" and reconnected — ~847 cycles in
19.5h, ~45% downtime, ~50% of letsmesh packets lost. The low-volume
davekeogh broker never saturated the goroutine and was unaffected.

Decouple receipt from insertion: the handler now enqueues decoded rows
onto a buffered channel and a single background writer flushes them to
meshcore_packets in batched native inserts (every MESHCORE_BATCH_FLUSH_
SECONDS or MESHCORE_BATCH_MAX_ROWS rows). The inbound goroutine never
blocks, so PINGRESP is always processed in time.

- New batch writer with env-configurable flush interval / max rows /
  buffer size (MESHCORE_BATCH_* ), wired in docker-compose.
- Drop server-side async_insert (redundant once we batch app-side).
- Bump PingTimeout 10s -> 20s (env MQTT_PING_TIMEOUT_SECONDS) for margin
  against Cloudflare WebSocket buffering jitter.
- Enqueue is non-blocking; rows are dropped+counted only if the buffer
  fills (ClickHouse unavailable). A failed batch is dropped and retried
  by the next flush (native blocks commit atomically).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ingest: make MQTT KeepAlive configurable (MQTT_KEEPALIVE_SECONDS)

As a near-silent subscriber, paho emits a PINGREQ roughly every KeepAlive
seconds; lowering it sends client->server frames more often to keep the
Cloudflare-proxied WebSocket path warm in both directions, a lever for the
residual mid-stream "pingresp not received" stalls on the letsmesh broker.
Default unchanged (30s); wired through docker-compose.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ingest: add configurable MQTT write timeout (MQTT_WRITE_TIMEOUT_SECONDS)

Bounds PINGREQ/SUBSCRIBE writes so a stalled write through the Cloudflare
WebSocket proxy can't hang the client. Default 0 (paho's existing no-timeout
behavior); wired through docker-compose. Recommended ~20s when behind a
buffering reverse proxy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Alex Vanderpot <alex@Alexs-MacBook-Pro-2.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:42:46 -04:00
27c94e1aee node page: map-based location history + collapsible recent adverts (#43)
Replace the Location History coordinate table with an embedded Leaflet
map (new LocationHistoryMap component): one circle marker per position
with timestamp/coord popups, the most-recent point highlighted, and a
subtle polyline connecting points chronologically, auto-fit to bounds.

Cap Recent Adverts at the 5 most recent with a "Show more"/"Show less"
toggle and a count in the subtitle.

Co-authored-by: Alex Vanderpot <alex@Alexs-MacBook-Pro-2.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:38:16 -04:00
eaaf729b15 map: render node popovers lazily and skip needless marker re-renders (#42)
The map page slowed the browser at high node counts (~5k) because every
marker eagerly rendered its popover to an HTML string at creation, and the
visual-update effects re-rendered every marker's icon and popup on each
selection change. Hovering a meshcore node (the default type) re-rendered
all markers.

Bind popups lazily so PopupContent is only rendered when a popup actually
opens, drop the now-unnecessary popup setContent calls, and re-skin only the
markers whose selected state changed instead of the whole set.

Co-authored-by: Alex Vanderpot <alex@Alexs-MacBook-Pro-2.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:02:40 -04:00
Alex Vanderpot 166ef69f51 Merge pull request #40 from ajvpot/ajvpot/clickhouse-live-materialized-views
clickhouse: back map, node search, and chat with live materialized views
2026-06-15 00:13:44 -04:00
Alex Vanderpot ee65644fe9 Merge pull request #39 from ajvpot/ajvpot/region-system-iata-codes
Region system: IATA codes, data-driven groups, Grafana filtering
2026-06-14 17:42:26 -04:00
72aa6be3d3 ingest: resubscribe on reconnect + staleness watchdog for zombie MQTT conns (#38)
The letsmesh broker was migrated behind Cloudflare and changed its topic
layout on 2026-06-02, which left prod's MQTT client in a zombie state:
connected per paho's IsConnected() (so the 30s monitor never rebuilt it) but
receiving zero messages, because the subscription was established only once
after the initial connect and never re-applied on paho auto-reconnects. Result:
12 days of silently missing letsmesh ingestion while davekeogh masked the loss.

Make reconnection robust instead of relying on broker-side session persistence:

- Subscribe inside the OnConnect handler so every (re)connect — including paho
  auto-reconnects — restores delivery. Use CleanSession(true)+ResumeSubs(false)
  so we never depend on the broker remembering our session.
- Add a per-broker data-staleness watchdog: a broker that reports connected but
  delivers no messages for MQTT_STALE_AFTER_SECONDS (default 300) is treated as a
  zombie and force-rebuilt (disconnect + fresh connect/subscribe). This catches
  exactly the failure IsConnected() misses.
- Reduce the external monitor to that watchdog role; transient drops are left to
  paho auto-reconnect rather than racing it with a brand-new client.
- Stable per-broker client IDs (by index) and pre-sized MQTTClients slice so
  indices stay aligned when an earlier broker fails; guard BrokerStatus/lastActivity
  with a mutex; promote connect/subscribe logs to Info for visibility.

Adds unit tests for the watchdog and env parsing; documents the new env var.

Co-authored-by: Alex Vanderpot <alex@Alexs-MacBook-Pro-2.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:55:33 -04:00
Alex Vanderpot 5966564b29 Merge pull request #35 from ajvpot/ajvpot/slow-chat-api-query
chat: fix slow /api/chat query (full-history aggregation)
2026-05-29 03:36:08 -04:00
Alex Vanderpot 1940f995dc Merge pull request #34 from ajvpot/neighbor-refreshable-mvs
Speed up neighbors with hourly refreshable materialized views
2026-05-29 03:03:10 -04:00
Alex Vanderpot 49847c46af Merge pull request #33 from ajvpot/release-ingest
add ingest code: monorepo + one-command deploy
2026-05-29 01:51:20 -04:00
ajvpot 815d465566 Bump nextjs 2026-01-06 03:11:33 +01:00
ajvpot e6e74589c1 save map position, stroke width slider 2025-09-29 18:06:11 +02:00
ajvpot b173351011 path display overhaul 2025-09-23 04:04:17 +02:00
ajvpot 665b91f1fa add analyze links to PathVisualization 2025-09-23 03:13:10 +02:00
Alex Vanderpot 580c315a20 Merge pull request #24 from CoryNQ1E/main
Support Lowercase Public Keys in Node API Endpoints
2025-09-22 19:38:14 -04:00
ajvpot b12d570142 analyze link in discord bot relay 2025-09-23 01:28:47 +02:00
ajvpot add14fa37b fix concurrency in bot 2025-09-23 00:05:56 +02:00
ajvpot b296017331 Add anchors to stats pages (closes #26) 2025-09-19 17:43:00 +02:00
ajvpot a1416bcc05 handle numeric values in useQueryParams (closes #25) 2025-09-19 17:34:12 +02:00
ajvpot f34836763c min packet thresh 2025-09-18 01:58:48 +02:00
ajvpot 4faa6491c2 ui rearranging 2025-09-18 01:53:15 +02:00
ajvpot 0f1327469d hide meshcore overlay setting 2025-09-18 01:41:52 +02:00
ajvpot f3ad947b9a All neighbors, map layer settings 2025-09-18 01:40:45 +02:00
ajvpot fe6e44cc80 path magic 2025-09-17 17:46:43 +02:00
ajvpot ea32ccea77 support # channels 2025-09-15 23:03:29 +02:00
ajvpot ed9943e091 missing file 2025-09-15 22:52:25 +02:00
ajvpot 4c7e7d8e1c highlights for discord 2025-09-15 04:36:14 +02:00
ajvpot 0aac045c1b discord bot: support threads 2025-09-15 04:14:58 +02:00
ajvpot 9f1056095b all neighbors 2025-09-15 04:14:50 +02:00
ajvpot 7addbb3165 update stats and path viz to use 2 day last seen time 2025-09-14 18:26:47 +02:00
ajvpot e41dfe83d3 stuff 2025-09-14 18:25:56 +02:00
ajvpot 3ade624a51 emoji 2025-09-13 03:31:30 +02:00
ajvpot 8ac7d5eece Discord bot, profile picture endpoint, channel management ux, streaming apis, refactor region logic 2025-09-13 02:57:52 +02:00
ajvpot 2afbe80c1b fix clickhouse constructor 2025-09-11 18:29:27 +02:00
ajvpot 1dd6781113 override region for node info page with detected region info 2025-09-11 18:28:12 +02:00
ajvpot 46606abbf9 Replies/highlights 2025-09-11 17:53:13 +02:00
ajvpot cdd66725ec fix build 2025-09-10 03:52:26 +02:00
ajvpot 93cfd969a8 link target for embed view 2025-09-10 03:49:39 +02:00
ajvpot fbe31eb092 create embeddable map view, finish migration to route groups 2025-09-10 03:42:31 +02:00
ajvpot 6171bf812a restructure: use app route group and replace relative imports with @ imports 2025-09-10 03:21:40 +02:00
ajvpot 6fab8a112a move stats to tanstack query, implement cancelation 2025-09-10 02:47:44 +02:00
ajvpot a5638fe924 fix search params for path vis 2025-09-10 02:17:24 +02:00
ajvpot f92895ca8f optimize neighbors, fix aggregation 2025-09-10 02:12:18 +02:00
ajvpot 83b41619ac Store map lat/lng, fix state handling 2025-09-10 01:58:26 +02:00
ajvpot e43dd51336 persist selectedtab in usequeryparams 2025-09-10 01:47:45 +02:00
ajvpot 901cec1820 cache decryption results 2025-09-10 01:41:42 +02:00
ajvpot 9eb1c4d62c Search batching, display nodes on graph, spinner for loading neighbors 2025-09-10 01:33:29 +02:00
ajvpot 256886f2e8 Node info page improvements, search, neighbors 2025-09-09 04:01:19 +02:00
ajvpot 8b9ae36072 path display 2025-09-08 17:32:38 +02:00
ajvpot abd421a2a1 some kind of node info page 2025-09-07 02:22:20 +02:00
ajvpot 8a4b591776 Server side message decryption and api docs 2025-09-06 03:10:34 +02:00
Alex Vanderpot 3d9f2cca21 Merge pull request #20 from bryantkelley/fix/7-region-selection-too-big
Update styling for region selection and chat box
2025-09-05 14:27:13 -07:00
ajvpot b03aef4f74 First seen time 2025-09-01 10:16:41 +02:00
ajvpot fb3338d1d8 word wrap messages 2025-08-25 22:59:09 +02:00
ajvpot 93d11b08a0 change styling of messages that failed to decrypt 2025-08-25 22:55:35 +02:00
ajvpot 8382a583a5 repeaters and room servers in stats 2025-08-23 07:40:04 +02:00
ajvpot de010fc4ac clickable links 2025-08-23 03:49:47 +02:00
ajvpot 7bc035d4c7 types 2025-08-23 03:47:49 +02:00
ajvpot 98a745039e fullscreen graph 2025-08-23 03:43:20 +02:00
ajvpot 3d73e51ad7 message prop graph 2025-08-23 02:17:27 +02:00
ajvpot 4e8af83e41 add origin pubkey prefix 2025-08-23 00:53:55 +02:00
ajvpot 0ca5e55ba3 todo 2025-08-19 08:24:15 +02:00
ajvpot 6091f1b43a change default zoom to include portland 2025-08-19 07:28:31 +02:00
ajvpot 6e7e62806a add region filtering to stats 2025-08-19 06:51:32 +02:00
ajvpot 881174dce7 add region filtering to stats 2025-08-19 06:48:41 +02:00
ajvpot 3aec06782d todo 2025-08-19 06:23:52 +02:00
ajvpot 4d48589cef chat regions 2025-08-19 06:19:14 +02:00
ajvpot 02e1e5c908 fix nodes query 2025-08-18 21:55:13 +02:00
Alex Vanderpot 76b75d4637 Merge pull request #5 from bryantkelley/feat/dark-mode
Enable Dark Mode with Some Overall Style Changes
2025-08-16 01:11:32 -07:00
ajvpot 6847076717 repeater prefixes 2025-08-04 05:50:57 +02:00
Alex Vanderpot 80f283a495 Merge pull request #4 from bryantkelley/fix/map-chat-dom-order
Align ChatBox Visual and DOM Order
2025-08-02 01:34:48 -07:00
Alex Vanderpot bc6e53654a Merge pull request #3 from bryantkelley/feat/filter-empty-configs
Filter empty meshcoreKeys when closing config popover
2025-08-01 22:54:45 -07:00
ajvpot 78a9857fec fix modal shade 2025-08-02 04:35:17 +02:00
ajvpot 6c297b4daf support for changing the app name, info modal 2025-08-02 04:27:36 +02:00
ajvpot b990123256 info modal 2025-08-02 04:21:32 +02:00
ajvpot 9f741da550 format the node id and short name meshcore style 2025-08-01 07:58:33 +02:00
ajvpot 527cc526b5 format the node id 2025-08-01 07:53:15 +02:00
ajvpot 63188c3b2c track markers better so the popup doesnt get closed when the data reloads 2025-08-01 07:45:26 +02:00
ajvpot cac9d8f7b8 attribution update 2025-08-01 07:38:39 +02:00
ajvpot 01fda2feef actually actually fix chat 2025-08-01 07:28:48 +02:00
Alex Vanderpot f2aac4fe69 Merge pull request #1 from ajvpot/fix_chat_refresh
Fix Build Issues and Improve ChatBox Component
2025-07-31 22:17:51 -07:00
ajvpot bbe3441630 actually fix chat 2025-08-01 07:13:57 +02:00
ajvpot b0480ec484 maybe fix chat 2025-08-01 07:03:59 +02:00
ajvpot 8c05904bc4 re enable important eslint rules 2025-08-01 06:47:25 +02:00
ajvpot f450686ae3 make public tab the default on the messages page 2025-08-01 04:48:24 +02:00
ajvpot 47af653db6 prepare for oss release: docker compose 2025-08-01 00:00:00 +00:00
ajvpot f8584f8e1c prep oss release: remove meshtastic from default config, set last seen default to 1w 2025-08-01 00:00:00 +00:00
ajvpot de9d231542 prep oss release: allow development with remote api 2025-08-01 00:00:00 +00:00
ajvpot 571ee122f6 auto refresh after time is still messed up 2025-07-28 00:00:00 +00:00
ajvpot 01a296b704 refactor 2025-07-28 00:00:00 +00:00
ajvpot 3192354695 move map icon and popover to react components 2025-07-28 00:00:00 +00:00
ajvpot 0e483fec16 remove redundant typecheck 2025-07-28 00:00:00 +00:00