feat(repeater): add map-based neighbors tab#182
Merged
Conversation
Extract a reusable BaseLeafletMap (nodes + optional SNR-labeled edges) from the monolithic Map view, moving nodeIcon/escapeHtml into lib/map/leafletIcon and the category legend into a shared MapLegend. Add NeighborsMap, which anchors the administered repeater and plots each locatable neighbor with an SNR link, and make the repeater Neighbors tab map-only with the refresh control on the map. The map fills the full tab height.
There was a problem hiding this comment.
Pull request overview
Adds a reusable Leaflet map layer and a spatial repeater-neighbors view with SNR links.
Changes:
- Extracts shared map, marker, legend, and edge infrastructure.
- Replaces the neighbors table with a map and localized fallback states.
- Refactors the main Map page to use the shared layer.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
app/globals.css |
Styles neighbor links and SNR labels. |
components/BaseLeafletMap.tsx |
Adds reusable Leaflet map infrastructure. |
components/MapLegend.tsx |
Extracts the shared map legend. |
components/MapView.tsx |
Composes the main map from shared components. |
components/NeighborsMap.tsx |
Builds the repeater-neighbor spatial view. |
components/RepeaterView.tsx |
Integrates the map-only neighbors tab. |
lib/map/config.ts |
Defines edge styling and viewport types. |
lib/map/leafletIcon.ts |
Extracts shared Leaflet icon helpers. |
lib/map/nodes.ts |
Adds map edges and neighbor-node resolution. |
locales/de.json |
Updates German neighbor states. |
locales/en.json |
Defines authoritative neighbor states. |
locales/es.json |
Updates Spanish neighbor states. |
locales/fr.json |
Updates French neighbor states. |
Address PR review: the map-only Neighbors tab hid unlocatable neighbors and dropped the admin neighbor.remove action. Restore the map + list together (issue #178's recommended layout) — the map stays primary with every neighbor kept in the list below, and the per-row admin Remove (with its non-empty-prefix guard) returns. Also fix locateNeighborNode to fall back to a matched node's cached advert coordinates when its contact record has no fix, so a locatable neighbor is no longer dropped.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
components/RepeaterView.tsx:660
- This count includes a row that resolves back to the repeater anchor, but
buildNeighborMapintentionally drops that self-edge. If that is the only resolvable row,showMapbecomes true and renders an anchor-only map with no neighbor. Apply the same anchor exclusion here.
const mappableCount = useMemo(() => {
if (!contact.advLat || !contact.advLon) return 0;
return (neighbors ?? []).filter((n) =>
locateNeighborNode(n.prefix, contacts, advertCache),
).length;
Per maintainer decision, the Neighbors tab shows only the map (filling the tab), with the Refresh control on it. Unlocatable neighbors and the admin neighbor.remove action are intentionally out of scope for this tab; issue #178's design questions are resolved that way in the ticket. The locateNeighborNode advert-coordinate fallback is kept, so a contact lacking its own fix still plots from its cached advert.
3 tasks
The permanent SNR edge labels are produced by formatSnr, which reads the active locale, but the memo that builds the map data didn't depend on it — so a language switch left the labels in the previous decimal format until neighbor/contact data changed. Subscribe to the locale and add it to the memo dependencies.
Address PR review: (1) locateNeighborNode now backs up an unlocated contact only with an advert for the exact same node (full-key match), so a 4-byte neighbor-prefix collision can't lend another node's coordinates; (2) the placeholder now shows a distinct message when the repeater itself has no advertised location (vs. neighbors being unlocatable); (3) update a stale comment that still described the removed table fallback.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Turns the repeater Neighbors tab into a spatial, map-based view, and does
the map-layer refactor that #178 called out as a prerequisite.
Reusable map layer (refactor)
BaseLeafletMapfrom the monolithic Map view — a store-decoupledcomponent that takes an arbitrary
nodesset plus an optionaledgesset(
{ from, to, label }, drawn as polylines with a permanent midpoint label).It owns only the shared machinery (tiles, single-world bounds, min-zoom
clamp, theme swap, marker + edge layers).
nodeIcon/escapeHtmlprimitives into a client-onlylib/map/leafletIconmodule, and the category legend into a sharedMapLegendcomponent (with achildrenslot; the Map page supplies thefavorites-only toggle there).
MapViewas a thin Map-page wrapper that composesBaseLeafletMapand keeps the page-only concerns (location-pick mode, viewport persistence,
favorites filter, marker cap). No behavior change to the Map page.
Neighbors map (feature)
NeighborsMap(loaded viadynamic/ssr: false), which anchors theadministered repeater and plots each neighbor that resolves to a saved
contact/advert with a GPS fix, drawing an SNR-labeled link from the repeater
to each. Coordinates are resolved by matching the neighbor's pubkey prefix in
new
lib/map/nodeshelpers (repeaterAnchorNode,locateNeighborNode).Refresh control pinned to its top-right corner. When nothing can be mapped
(the repeater or its neighbors have no location, none were heard, or a read
errored), a same-sized placeholder explains why. Clicking a neighbor marker
opens its manage panel; the shared legend documents the marker categories.
Reuses the existing
neighborsCLI command — no protocol-layer changes.Issues
Type of Change
!-- Breaking change (fix or feature that would cause existingfunctionality to change)
feat-- New feature (non-breaking change which adds functionality)fix-- Bug fix (non-breaking change which fixes an issue)docs-- Additional or updated documentationstyle-- Code style changes (formatting, missing semi colons, etc)refactor-- Code refactorperf-- Performance improvementtest-- Tests (adding or updating tests)build-- Build system or external dependencies changesci-- Build configuration changechore-- Grunt tasksrevert-- Revert previous changes