Skip to content

feat: area-based visual node filter — attribute packets by transmitter GPS (#804)#839

Open
efiten wants to merge 4 commits intoKpa-clawbot:masterfrom
efiten:feat/area-filter-804
Open

feat: area-based visual node filter — attribute packets by transmitter GPS (#804)#839
efiten wants to merge 4 commits intoKpa-clawbot:masterfrom
efiten:feat/area-filter-804

Conversation

@efiten
Copy link
Copy Markdown
Contributor

@efiten efiten commented Apr 21, 2026

Summary

  • Adds configurable GPS polygon areas to config.json; nodes are attributed to an area if their last-known position falls inside the polygon
  • New Area: … dropdown filter (matching the existing region filter style) appears on all analytics, nodes, packets, map, and live screens when areas are configured
  • Backend resolves area membership with a 30s TTL cache; area filter bypasses the 500-node cap on /api/bulk-health so all area nodes are always returned
  • Includes a polygon builder tool (/area-map.html) for drawing and exporting area boundaries

Changes

Backend

  • AreaEntry type + Areas config field
  • GetNodePubkeysInArea DB query + resolveAreaNodes (30s TTL, areaNodeMu RWMutex)
  • PacketQuery.Area + filterPackets polygon check
  • ?area= param propagated through all analytics, topology, clock-health, and bulk-health routes
  • /api/config/areas endpoint

Frontend

  • area-filter.js: single-select dropdown, persists to localStorage, cleans up stale keys on load
  • Wired into analytics, nodes, packets, channels, map, and live pages
  • Live map clears node markers on area change

Docs & tools

  • docs/user-guide/area-filter.md — configuration and usage guide
  • docs/api-spec.md — updated with new endpoint and ?area= param table
  • tools/area-map.html — polygon builder for defining area boundaries
  • Demo areas added to config.example.json

Test plan

  • No areas configured → filter dropdown does not appear on any page
  • Areas configured → dropdown appears, "All" selected by default
  • Selecting an area filters nodes/packets/topology/map correctly
  • Selecting "All" restores unfiltered view
  • Selection persists across page reloads (localStorage)
  • Stale localStorage key (area removed from config) is cleared on load
  • /api/bulk-health?area=X returns all nodes in area (no 500-node cap)
  • /api/config/areas returns correct list

🤖 Generated with Claude Code

@efiten
Copy link
Copy Markdown
Contributor Author

efiten commented Apr 21, 2026

Area map can be tested here: https://staging.on8ar.eu/area-map.html
Just click on 'load' on top, and the area's are shown.

Area view can be tested: https://staging.on8ar.eu/#/analytics
For example, the 'hash issues', can be filtered by area, what the TS requested

@Kpa-clawbot
Copy link
Copy Markdown
Owner

Expert Review — PR #839: Area-Based Visual Node Filter

Judgment: COMMENT — merge-ready with notes below

Well-structured PR. Clean separation from existing ?region= filter, good test coverage, smart reuse of geofilter.PassesFilter. A few findings worth discussing:


MeshCore Perspective

1. [should-fix] Area filter silently drops ALL non-ADVERT packetsstore.go filter checks tx.ParsedDecoded()["public_key"] / ["pubKey"], but only ADVERT payloads carry the originator pubkey. GRP_TXT, TXT_MSG, REQ packets have encrypted senders — pk will be "", failing the !areaNodes[pk] check. When area filter is active, users see only ADVERTs. The design spec acknowledges "origin unknown → exclude" but this should be documented in the UI (tooltip on the pill?) and called out in the PR description, since it's a significant behavioral difference from ?region= which filters by observer and keeps all packet types.

2. [question] GPS stalenessGetNodePubkeysInArea reads nodes.lat/lon from DB. These are updated on ADVERT ingest. A node that moves between areas won't be re-attributed until its next advert (12-24h for repeaters). The 30s cache TTL on resolveAreaNodes is fine for DB freshness, but the underlying GPS data can be hours old. Worth a doc note.

3. [nit] pubKey vs public_key dual check — The d["public_key"] then d["pubKey"] fallback appears ~8 times in the diff (store.go lines ~2208, ~2218, ~3807, ~3998, ~4101, ~5198, ~5528, ~5849). Consider a tiny helper: func txOriginPubkey(tx *StoreTx) string to DRY this up.


Taleb (Failure Modes)

4. [should-fix] /api/config/areas/polygons leaks polygon data — PR description says "no polygon data exposed to clients" but routes.go registers handleConfigAreasPolygons which returns full polygon coordinates. This contradicts the stated security property. If the debug tool needs it, consider gating it behind a config flag or query param, or at minimum update the PR description to reflect reality.

5. [question] Degenerate polygon / misconfiguration — What happens with "polygon": [] or 2 points? geofilter.PassesFilter with an empty polygon — does it pass everything or nothing? If it passes everything, a typo in config silently disables the filter. Consider validating len(polygon) >= 3 at config load time and logging a warning.

6. [nit] Cross-antimeridian — Polygons spanning the 180° meridian will break with ray-casting PIP. Low priority (unlikely for current deployments) but worth a // NOTE: comment.

7. [nit] Cache race on config reload — If areas config is updated while server runs, areaNodeCache still references old s.config.Areas. Currently config is static (requires restart), so this is fine — just don't add hot-reload without addressing it.


Overall: Merge-ready. Items 1 and 4 are worth addressing but neither is a blocker — #1 is by-design but should be explicit, #4 is a description/code mismatch. Solid work by @efiten.

@Kpa-clawbot
Copy link
Copy Markdown
Owner

Follow-up to my earlier review after discussion with maintainer:

Re: should-fix #2 ("polygon data exposed" claim) — we don't consider exposed polygons a concern. They're operator-defined geographic regions, not PII/credentials/infrastructure; same info any mapping app has. The /api/config/areas/polygons endpoint is fine and useful (frontend can render them on the map).

Action: just drop the "no polygon data exposed to clients" line from the PR description so the code and description agree. No code change needed on that point.

Should-fix #1 (area filter silently drops non-ADVERT packets) still stands — worth a comment in the code or a brief note in the docs/PR body explaining that only GPS-bearing packets are attributable.

Thanks for the contribution 🎉

@efiten
Copy link
Copy Markdown
Contributor Author

efiten commented Apr 22, 2026

Area filter — screen-by-screen update

Follow-up changes to the area filter pill placement and wiring:

Screen Area filter Action
Packets NO leave as-is (already removed)
Map YES add to map controls
Live YES add to map controls
Channels (main) NO remove pill
Nodes YES leave as-is (OK)
Traces NO leave as-is (no pill)
Observers NO leave as-is (no pill)
Analytics › Overview YES leave as-is (OK)
Analytics › RF/Signal YES fix — filter not applying
Analytics › Topology YES fix — changing area doesn't update content
Analytics › Channels NO remove pill
Analytics › Hash stats YES leave as-is (OK)
Analytics › Hash issues YES leave as-is (OK)
Analytics › Route patterns NO remove pill
Analytics › Nodes YES leave as-is (OK)
Analytics › Distance NO remove pill
Analytics › Neighbor graphs NO remove pill
Analytics › RF Health NO remove pill
Analytics › Clock health YES leave as-is (OK)

Also: the 'All' pill (no-filter reset) appears empty — text is not visible, likely a CSS issue to fix at the same time.

@efiten efiten force-pushed the feat/area-filter-804 branch from bc81545 to b445f30 Compare April 22, 2026 07:23
@luckystriike22
Copy link
Copy Markdown

luckystriike22 commented Apr 22, 2026

  • Maybe make the area pills smaller and collapsable/scrollable. It uses a lot of space on all pages.

  • when a area is selected the button turns blue and hides the text. Swap text to white when selected

image

efiten and others added 3 commits April 23, 2026 10:23
… analytics, bulk-health (Kpa-clawbot#804)

- Add AreaEntry type and Areas field to Config
- Add GetNodePubkeysInArea DB query
- Add resolveAreaNodes with 30s TTL cache (areaNodeMu RWMutex)
- Add PacketQuery.Area + filterPackets polygon check
- Propagate area param through all analytics and topology functions
- Add /api/config/areas endpoint
- Add ?area= support to /api/bulk-health (bypasses 500-node cap when area active)
- Add ?area= support to topology and clock-health routes
- Add demo areas to config.example.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…es, live map (Kpa-clawbot#804)

- Add area-filter.js: single-select dropdown matching region filter style
- Wire AreaFilter into analytics, nodes, packets, channels, map, live pages
- Live map: clear node markers on area change
- style.css: area dropdown styles reusing region-dropdown-* classes
- live.css: no layout changes needed (dropdown is compact, fits live-header)
- area-map.html: polygon debug tool and builder for defining area boundaries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
)

- Add docs/user-guide/area-filter.md
- Update api-spec.md with /api/config/areas and ?area= param documentation
- Update analytics, nodes, packets, configuration user guides
- Add tools/area-map.html polygon builder tool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@efiten efiten force-pushed the feat/area-filter-804 branch from 3e5ae82 to cab1bc8 Compare April 23, 2026 08:23
efiten added a commit to efiten/meshcore-analyzer that referenced this pull request Apr 27, 2026
…y-liveness

- cmd/ingestor/db.go: keep both scope_name_v1 and multibyte_sup_v1 migrations
- cmd/server/db.go: keep both hasScopeName and hasMultibyteSupCols fields
- cmd/server/db_test.go: keep both TestGetScopeStats and TestGetNodesReturnsMultibyteSupField
- cmd/server/routes.go: enrich nodes with both multibyte and relay stats
- cmd/server/relay_liveness_test.go: pass area="" to GetBulkHealth (added in Kpa-clawbot#839)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants