GetHopDepthAnalytics now buckets its scoped/unscoped hop-index tallies by
time (same 5min/1h/6h bucketing as GetScopeStats' TimeSeries) and computes
a per-bucket median for each series, exposed as a new TimeSeries field.
Median is a *int, nil when a bucket had no traffic of that kind -- 0 is a
valid median hop, so absence has to stay distinguishable from zero.
Frontend renders it as a two-line SVG trend chart on the Scopes > Hop
Depth sub-tab (same visual language as Overview's scoped/unscoped
time-series), answering "is containment getting better or worse over the
window" rather than just a single window-wide snapshot. Each series'
polyline is built from contiguous non-null segments so a gap renders as
a visible break instead of silently interpolating through missing data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends the #1812 per-node relay hop-count work with a network-wide view:
GET /api/analytics/hop-depth answers (1) whether scoped traffic actually
travels fewer hops than unscoped before hitting a repeater's flood.max cap
(Scopes tab Overview: new "Flood Containment" comparison), and (2) which
repeaters relay unscoped traffic that already traveled far vs merely
locally (Foreign Traffic tab: min/median/max hop columns joined onto the
existing unscoped-relay table by public key).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>