revert: remove View Path bridge-repeater highlighting

The "relays for 2+ distinct region scopes" definition doesn't hold up
in practice: most nodes end up carrying both a broad national/regional
scope (e.g. #dk, #eu) and several local ones, so the vast majority of
repeaters would eventually qualify as "bridge" -- the flag stops
meaning anything useful.

Removes IsBridge from PacketPathPoint/PacketPathObserver, the
markBridgeRepeaters handler step, the purple-outline styling, and the
dedicated test file -- keeps PublicKey on PacketPathObserver, since
the click-to-node-detail feature depends on it independently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
dborup
2026-07-24 19:27:56 +02:00
co-authored by Claude Sonnet 5
parent 6718c246b9
commit 4b4dc9f27f
7 changed files with 11 additions and 185 deletions
+2 -12
View File
@@ -1540,13 +1540,6 @@ type PacketPathPoint struct {
// those neighbors -- 0 (and omitted) with a single contributor;
// larger means the neighbors disagree more about where "nearby" is.
ApproxSpreadKm *float64 `json:"approxSpreadKm,omitempty"`
// IsBridge is true when this node has been confirmed relaying
// traffic for 2+ distinct region scopes -- the same "Bridge" badge
// definition as the Foreign Traffic tab (ScopeStatsResponse.
// bridgeRepeaters). Set by the handler (routes.go), not GetPacketPath
// itself, since the underlying data lives in the in-memory store, not
// SQL. Absent/false when the store isn't available.
IsBridge bool `json:"isBridge,omitempty"`
}
// PacketPathObserver is the station that produced a given branch's
@@ -1558,9 +1551,8 @@ type PacketPathPoint struct {
type PacketPathObserver struct {
// PublicKey is the observer's mesh pubkey (observers.id for v3,
// observer_id for legacy), when it has one -- lets callers link out
// to the node detail page or cross-reference other per-node data
// (e.g. IsBridge, filled in by the handler). Empty for an observer
// whose id never resembled a pubkey.
// to the node detail page. Empty for an observer whose id never
// resembled a pubkey.
PublicKey string `json:"publicKey,omitempty"`
Name string `json:"name"`
IATA string `json:"iata,omitempty"`
@@ -1573,8 +1565,6 @@ type PacketPathObserver struct {
Approx bool `json:"approx,omitempty"`
ApproxNeighborCount int `json:"approxNeighborCount,omitempty"`
ApproxSpreadKm *float64 `json:"approxSpreadKm,omitempty"`
// IsBridge -- see PacketPathPoint.IsBridge.
IsBridge bool `json:"isBridge,omitempty"`
}
// PacketPathBranch is one station's route to a packet: how far it
-2
View File
@@ -345,7 +345,6 @@ func componentSchemas() map[string]interface{} {
"approx": map[string]interface{}{"type": "boolean", "description": "True when lat/lon are not this node's own position but a count-weighted centroid of its positioned neighbor_edges neighbors instead -- a last-resort stand-in, not a real fix."},
"approxNeighborCount": map[string]interface{}{"type": "integer", "description": "Present only when approx=true. How many positioned neighbors fed the centroid -- a rough confidence signal, higher is more confident."},
"approxSpreadKm": map[string]interface{}{"type": "number", "nullable": true, "description": "Present only when approx=true and approxNeighborCount>1. Widest distance (km) between any two contributing neighbors -- larger means they disagree more about where 'nearby' is."},
"isBridge": map[string]interface{}{"type": "boolean", "description": "True when this node has been confirmed relaying traffic for 2+ distinct region scopes -- same definition/data source as the Foreign Traffic tab's Bridge badge. Set by the handler from the in-memory store; always false when that store isn't available."},
},
},
"PacketPathObserver": map[string]interface{}{
@@ -361,7 +360,6 @@ func componentSchemas() map[string]interface{} {
"approx": map[string]interface{}{"type": "boolean", "description": "True when lat/lon are not this station's own position but a count-weighted centroid of its positioned neighbors instead -- a last-resort stand-in, not a real fix."},
"approxNeighborCount": map[string]interface{}{"type": "integer", "description": "Present only when approx=true. See PacketPathPoint.approxNeighborCount."},
"approxSpreadKm": map[string]interface{}{"type": "number", "nullable": true, "description": "Present only when approx=true and approxNeighborCount>1. See PacketPathPoint.approxSpreadKm."},
"isBridge": map[string]interface{}{"type": "boolean", "description": "See PacketPathPoint.isBridge."},
},
},
"PacketPathBranch": map[string]interface{}{
-76
View File
@@ -1,76 +0,0 @@
package main
import (
"testing"
"time"
)
// TestMarkBridgeRepeaters_NilStore covers the graceful no-op when the
// in-memory store isn't available (s.store can legitimately be nil --
// see handlePacketPath). Must not panic and must leave every IsBridge
// at its zero value (false).
func TestMarkBridgeRepeaters_NilStore(t *testing.T) {
resp := &PacketPathResponse{
Hash: "deadbeef",
Branches: []PacketPathBranch{
{Points: []PacketPathPoint{{PublicKey: "pk1"}}, Observer: &PacketPathObserver{PublicKey: "obs1"}},
},
}
markBridgeRepeaters(resp, nil, &Config{})
if resp.Branches[0].Points[0].IsBridge {
t.Errorf("Points[0].IsBridge = true, want false -- store is nil, nothing should be marked")
}
if resp.Branches[0].Observer.IsBridge {
t.Errorf("Observer.IsBridge = true, want false -- store is nil, nothing should be marked")
}
}
// TestMarkBridgeRepeaters_TwoScopesIsBridge covers the actual
// determination: a pubkey relaying 2+ distinct region scopes (the same
// definition/data source as the Foreign Traffic tab's "Bridge" badge,
// ScopeStatsResponse.bridgeRepeaters) gets IsBridge=true on every point
// and observer entry referencing it; a pubkey with 0 or 1 scope, or one
// missing from the relay map entirely, stays false.
func TestMarkBridgeRepeaters_TwoScopesIsBridge(t *testing.T) {
store := &PacketStore{
repeaterRelayCache: map[string]RepeaterRelayInfo{
"bridgepk": {TransportedScopes: []string{"#dk", "#se"}},
"regionalpk": {TransportedScopes: []string{"#dk"}},
"unknownscopepk": {TransportedScopes: nil},
},
repeaterRelayCacheWin: 24,
repeaterRelayAt: time.Now(),
}
resp := &PacketPathResponse{
Hash: "deadbeef",
Branches: []PacketPathBranch{
{
Points: []PacketPathPoint{
{PublicKey: "bridgepk"},
{PublicKey: "regionalpk"},
{PublicKey: "unknownscopepk"},
{PublicKey: "notinrelaymap"},
},
Observer: &PacketPathObserver{PublicKey: "BridgePK"}, // case-insensitive match
},
},
}
markBridgeRepeaters(resp, store, &Config{})
pts := resp.Branches[0].Points
if !pts[0].IsBridge {
t.Errorf("Points[0] (bridgepk, 2 scopes) IsBridge = false, want true")
}
if pts[1].IsBridge {
t.Errorf("Points[1] (regionalpk, 1 scope) IsBridge = true, want false")
}
if pts[2].IsBridge {
t.Errorf("Points[2] (unknownscopepk, 0 scopes) IsBridge = true, want false")
}
if pts[3].IsBridge {
t.Errorf("Points[3] (notinrelaymap, absent from relay map) IsBridge = true, want false")
}
if !resp.Branches[0].Observer.IsBridge {
t.Errorf("Observer (BridgePK, case-insensitive match on bridgepk) IsBridge = false, want true")
}
}
-37
View File
@@ -3164,46 +3164,9 @@ func (s *Server) handlePacketPath(w http.ResponseWriter, r *http.Request) {
writeError(w, 500, err.Error())
return
}
markBridgeRepeaters(resp, s.store, s.cfg)
writeJSON(w, resp)
}
// markBridgeRepeaters sets IsBridge on every point/observer in resp whose
// pubkey has been confirmed relaying traffic for 2+ distinct region
// scopes -- the same "Bridge" definition/data source as the Foreign
// Traffic tab's badge (ScopeStatsResponse.bridgeRepeaters), just applied
// to whichever handful of pubkeys this one packet's path touches instead
// of scanning the whole mesh. No-op when the in-memory store isn't
// available (IsBridge stays false/omitted on every entry).
func markBridgeRepeaters(resp *PacketPathResponse, store *PacketStore, cfg *Config) {
if resp == nil || store == nil || cfg == nil {
return
}
relayMap := store.GetRepeaterRelayInfoMap(cfg.GetHealthThresholds().RelayActiveHours)
isBridge := func(pubkey string) bool {
if pubkey == "" {
return false
}
info, ok := relayMap[strings.ToLower(pubkey)]
return ok && len(info.TransportedScopes) >= 2
}
mark := func(b *PacketPathBranch) {
if b == nil {
return
}
for i := range b.Points {
b.Points[i].IsBridge = isBridge(b.Points[i].PublicKey)
}
if b.Observer != nil {
b.Observer.IsBridge = isBridge(b.Observer.PublicKey)
}
}
for i := range resp.Branches {
mark(&resp.Branches[i])
}
mark(resp.First)
}
var iataCoords = map[string]IataCoord{
"SJC": {Lat: 37.3626, Lon: -121.929},
"SFO": {Lat: 37.6213, Lon: -122.379},
+4 -13
View File
@@ -100,7 +100,7 @@
var chain = located.map(function (p, hi) {
return {
lat: p.lat, lon: p.lon, name: p.name, label: 'hop ' + (hi + 1) + ' of ' + b.hops, approx: !!p.approx,
approxNeighborCount: p.approxNeighborCount, approxSpreadKm: p.approxSpreadKm, role: p.role, isBridge: !!p.isBridge,
approxNeighborCount: p.approxNeighborCount, approxSpreadKm: p.approxSpreadKm, role: p.role,
publicKey: p.publicKey,
};
});
@@ -112,7 +112,7 @@
lat: b.observer.lat, lon: b.observer.lon, name: b.observer.name,
label: observerLabel, isObserver: true, approx: !!b.observer.approx,
approxNeighborCount: b.observer.approxNeighborCount, approxSpreadKm: b.observer.approxSpreadKm,
role: b.observer.role, isBridge: !!b.observer.isBridge, publicKey: b.observer.publicKey,
role: b.observer.role, publicKey: b.observer.publicKey,
});
}
return { chain: chain, missing: (b.points || []).length - located.length };
@@ -129,7 +129,7 @@
'<button type="button" id="packetPathClose" aria-label="Close" ' +
'style="position:absolute;top:8px;right:8px;background:none;border:none;cursor:pointer;font-size:22px;line-height:1;color:var(--text-muted)">&times;</button>' +
'<h3 style="margin:0 0 4px;padding-right:24px">Relay Path</h3>' +
'<p class="text-muted" style="margin:0 0 10px;font-size:12px">How far and how wide this packet spread. The highlighted route is the farthest-traveled branch; every other station that heard it is shown too. The green ring marks whoever heard it first. Dashed markers are approximate -- estimated from nearby positioned neighbors, not the station\'s own position. A bold purple outline marks a confirmed bridge repeater. Click a marker to open that node\'s detail page.</p>' +
'<p class="text-muted" style="margin:0 0 10px;font-size:12px">How far and how wide this packet spread. The highlighted route is the farthest-traveled branch; every other station that heard it is shown too. The green ring marks whoever heard it first. Dashed markers are approximate -- estimated from nearby positioned neighbors, not the station\'s own position. Click a marker to open that node\'s detail page.</p>' +
'<div id="packetPathMapContainer" style="height:360px;border-radius:8px;overflow:hidden;background:var(--surface-1)"></div>' +
'<div id="packetPathStatus" style="margin-top:8px;font-size:12px;color:var(--text-muted)">Loading…</div>' +
'</div>';
@@ -217,24 +217,15 @@
fillColor: color, fillOpacity: approxFillOpacity(pt.approxNeighborCount), dashArray: '5,4',
}
: { radius: radius, color: outline, weight: p.primary ? 2 : 1, fillColor: color, fillOpacity: p.primary ? 1 : 0.8 };
if (pt.isBridge) {
// Bridge repeaters (confirmed relaying for 2+ regions -- same
// definition as the Foreign Traffic tab's badge) get a bold
// purple outline on top of whatever primary/approx styling
// already applies, so they stand out as the mesh's backbone
// nodes regardless of which branch they're in.
markerOpts = Object.assign({}, markerOpts, { color: cssVar('--status-purple'), weight: markerOpts.weight + 1 });
}
var approxNote = '';
if (pt.approx) {
approxNote = ', approx. position';
if (pt.approxNeighborCount) approxNote += ' from ' + pt.approxNeighborCount + ' neighbor' + (pt.approxNeighborCount === 1 ? '' : 's');
}
var bridgeNote = pt.isBridge ? ', bridge repeater' : '';
var clickNote = pt.publicKey ? ' — click for node detail' : '';
var marker = L.circleMarker([pt.lat, pt.lon], markerOpts)
.addTo(map)
.bindTooltip(roleIcon(pt.role) + escapeHtml(pt.name) + ' (' + pt.label + approxNote + bridgeNote + ')' + clickNote, { className: 'packet-path-tooltip' });
.bindTooltip(roleIcon(pt.role) + escapeHtml(pt.name) + ' (' + pt.label + approxNote + ')' + clickNote, { className: 'packet-path-tooltip' });
if (pt.publicKey) {
// Same #/nodes/{pubkey} hash route the rest of the app already
// links to (see e.g. public/channels.js's node-detail links).
+4 -4
View File
@@ -2645,10 +2645,10 @@ button.ch-item:hover .ch-icon-btn { opacity: 1; }
.leaflet-popup-content { color: var(--text) !important; font-size: 13px !important; }
/* packet-path-map.js: tooltips got long once they started carrying role,
approx/confidence, bridge, and distance/timing info together --
Leaflet's default tooltip is white-space:nowrap, which stretched a
long one into an unreadable single line spanning the whole map.
Wrap it onto multiple lines within a fixed width instead. */
approx/confidence, and distance/timing info together -- Leaflet's
default tooltip is white-space:nowrap, which stretched a long one
into an unreadable single line spanning the whole map. Wrap it onto
multiple lines within a fixed width instead. */
.leaflet-tooltip.packet-path-tooltip {
white-space: normal;
max-width: 220px;
+1 -41
View File
@@ -40,7 +40,7 @@ test('escapes node/observer names before interpolating into tooltip HTML (operat
assert.ok(/escapeHtml\(pt\.name\)/.test(src), 'point tooltips must escape the name');
});
test('tooltips use a wrapping CSS class -- Leaflet\'s default nowrap tooltip becomes unreadable once role/approx/bridge/distance/timing info are all combined', () => {
test('tooltips use a wrapping CSS class -- Leaflet\'s default nowrap tooltip becomes unreadable once role/approx/distance/timing info are all combined', () => {
const bindCalls = (src.match(/\.bindTooltip\(/g) || []).length;
const classNameUses = (src.match(/className:\s*'packet-path-tooltip'/g) || []).length;
assert.ok(bindCalls > 0, 'expected at least one bindTooltip call');
@@ -454,46 +454,6 @@ function makeSandbox(apiImpl) {
} catch (e) { failed++; console.log(' ❌ nodes with a known role get a role icon in their tooltip: ' + e.message); }
})();
await (async () => {
try {
// isBridge=true should get a bold purple outline (overriding the
// normal stroke color/weight) and a "bridge repeater" tooltip note.
const ctx = makeSandbox(() => Promise.resolve({
hash: 'deadbeef',
branches: [
{
hops: 1,
points: [
{ publicKey: 'pk1', name: 'PlainRepeater', lat: 56.0, lon: 10.0, isBridge: false },
{ publicKey: 'pk2', name: 'BridgeRepeater', lat: 56.1, lon: 10.1, isBridge: true },
],
observer: null,
},
],
}));
const optsByTooltip = {};
ctx.L = {
map: () => ({ setView() { return this; }, fitBounds() {}, invalidateSize() {}, remove() {} }),
tileLayer: () => ({ addTo() { return this; } }),
circleMarker: (latlng, opts) => ({ addTo() { return this; }, bindTooltip(t) { optsByTooltip[t] = opts; return this; }, on() { return this; } }),
polyline: () => ({ addTo() { return this; } }),
};
await ctx.window.PacketPathMap.open('deadbeef');
const plainKey = Object.keys(optsByTooltip).find((k) => k.includes('PlainRepeater'));
const bridgeKey = Object.keys(optsByTooltip).find((k) => k.includes('BridgeRepeater'));
assert.ok(plainKey, 'expected a tooltip for PlainRepeater');
assert.ok(bridgeKey, 'expected a tooltip for BridgeRepeater');
assert.ok(!plainKey.includes('bridge repeater'), 'PlainRepeater tooltip should not mention bridge, got: ' + plainKey);
assert.ok(bridgeKey.includes('bridge repeater'), 'BridgeRepeater tooltip should mention bridge, got: ' + bridgeKey);
assert.notStrictEqual(optsByTooltip[bridgeKey].color, optsByTooltip[plainKey].color, 'expected the bridge marker to use a distinct outline color');
assert.ok(optsByTooltip[bridgeKey].weight > optsByTooltip[plainKey].weight, 'expected the bridge marker outline to be thicker');
passed++;
console.log(' ✅ bridge repeaters get a distinct outline and tooltip note');
} catch (e) { failed++; console.log(' ❌ bridge repeaters get a distinct outline and tooltip note: ' + e.message); }
})();
await (async () => {
try {
// A marker with a publicKey should register a click handler that