Draw each active IATA's GeoJSON region border as an outline over a faint
fill beneath the node markers, behind a Map Settings toggle that's off by
default and shareable via a URL param. IATAs with no border answer 204
and are skipped.
- Discover packets decoded: DISCOVER_REQ/DISCOVER_RESP render real fields
(type-filter roles, tag, node-to-node request SNR) instead of a raw dump.
- Neighbor lines on the map: On/Selected/Off control draws links between nodes
and their known neighbors.
- Live packet flow: a play button animates packets hop-to-hop between repeaters
in real time (off by default).
- a failed lazy chunk now offers 'reload' (React.lazy caches the
rejection, so remounting could never recover Map/Stats)
- an open spiderfy fan closes on region/type-filter change instead of
showing leaves from the previous dataset
- map settings persistence moved out of the setState updater
- observer search debounces 250ms and shows 'Search failed' instead of
'No matches' on errors
- packet-detail staleTime drops to 30s so a reopened analyzer picks up
new observations
- leaving Stats clears its statsTab/observerId/range URL params
- ws types now match the wire (batteryMv omitted not null, scopes can
be null, lastObservationId isn't sent today); drop the unused
microToDeg shim now that nothing imports it
The WS patch only updated nodes already in the paged cache, and with
staleTime Infinity nothing ever refetched — a node that came up after
page load could never appear on the map. The event carries a full
summary, so unknown nodes are appended to the cache directly. The
Nodes table keeps patch-only behavior since its cache is filter-scoped.
microToDeg guessed microdegrees from integer values, but api/nodes.go
has always sent *float64 degrees — so a node sitting at an exact whole
degree got divided by 1e6 and rendered at the equator. Drop the shim
from the geojson builder, the nodes table and the detail panel, and fix
the ws.ts comment that repeated the wrong premise.
Also key neighbor rows by id+iata: the neighbors endpoint returns one
row per (neighbor, iata), so id alone collides for nodes heard in
several regions (live React duplicate-key error).
* feat: replace getScopeStats with getScopes and update related tests
bug: lastSeen/sentAt was set to string instead of number by mistake
* feat: update timestamp handling and formatting across components
- WS messages carry epoch ms now instead of ISO strings
- new Timestamp component (relative/absolute), used in RouteTable and RouteDetailPanel
- IataChip and ScopeTag chips
- TraceDetailPanel and TraceList for trace drill-down
- tests updated for the timestamp change
- LoadingPill shows loading/error state in MapView, NodeTable and ObserverTable
- new useInfinitePages hook shares the pagination logic between them
- patchNodeSummary/patchObserverSummary now keep references stable
- tests for loading states, observer updates and infinite paging