- 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).
- node-types: new /stats/node-types endpoint + useNodeTypes; donutOption
replaces the "Coming soon" card with a live donut
- stats endpoints take iatas[] instead of a single iata, so multi-IATA
regions filter instead of falling back to all (drops useStatsIata)
- radio presets keep the node/observer split via stacked presetBarsOption
- drop client-side telemetry ms-normalization (backend now emits epoch ms
on both paths); charts pick delta-vs-raw counters off the response interval
- MeshTab: range-driven charts lead, all-time charts follow; KPI window
from overview.windowHours instead of a hardcoded 24h
- ci: docker-publish builds a :dev image on dev-branch pushes
The donut counted types among the top-10 nodes only, so the center
read '10 NODES' as if the region had ten nodes, and its center text
never sat quite right (ECharts titles anchor the main text, the
subtext hangs below). There's no /stats/node-types endpoint to feed it
properly yet (ticket filed), so the card shows 'Coming soon' and the
donut builder goes away rather than lingering as dead code.
textAlign/textVerticalAlign make left/top the anchor point, so the
value+label block centers on (35%, 50%) geometrically instead of the
hand-tuned top offset that drifted with card size.
- fresh-row highlights leaked under a steady stream (each new batch
cancelled the previous batch's clear timer); per-batch timers in a
small useFreshHashes hook now
- closing the analyzer drops ?hash and the row selection, so the same
row reopens on the next click and a closed analyzer stays closed
after reload/share
- sf=path / sf=payload from the URL fall back to hash search (only
field matchesFilters implements; the UI options were already disabled)
- remove the summary preview line no backend path ever populates
- channel message rows key on packetHash (live WS messages carry no id)
- footer read 'v0.1' forever; it's injected from package.json at build
time now
- region picker shows 'Failed to load' instead of an eternal Loading
when /iatas errors
- selecting text in a modal and releasing over the backdrop no longer
closes it (close requires the press to start on the backdrop)
- Escape in a dropdown inside the mobile filter sheet closes just the
dropdown, not the whole sheet
- Shift+Tab right after a modal opens wraps to the last control instead
of escaping the focus trap
- formatCount rolls 999,999 over to 1M instead of printing 1000k
- TABS list moved to lib/constants so AppShell only exports components
The WS observerStatus event never says offline (the backend only
derives it at REST read time, 5-minute window) and the list never
refetches, so badges stayed green all session once an observer went
quiet. Status is now re-derived on a tick from lastStatusAt — the WS
patch keeps the timestamp fresh — using the same 5-minute rule as the
server.
A multi-IATA region filters globally-paged rows client-side, and scroll
was the only trigger for the next page — an empty or too-short filtered
list could never fire it, so routes deeper in the cursor stream were
unreachable ('No routes' with routes that exist). Pull pages until the
region has a screenful or the cap calls it sparse.
The donut suits node types after all — few slices, and the total reads
better in the ring than on three bars. The center text is a title block
with textAlign center now, which ECharts actually centers (the old
graphic text anchored its left edge at the ring's x and clipped);
TitleComponent registered in the tree-shaken setup. Payload types stays
a bar chart.
Leaderboard names (top nodes/observers, radio presets) are left-aligned
at the card edge and ellipsized instead of running off the grid's left
side.
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).
The payload-types donut was buckling: 10+ slivers, a scrollable legend
with truncated names, and a center total that clipped at narrower
widths. Bars label every category inline, show counts on top, and the
total moved into the card header. Node types gets the same treatment
for consistency.
Also fix charts starting on the fallback palette when a saved theme
loads: themeId is already the saved id before the CSS vars land, so
useChartColors now keys on a paletteRev that bumps on every applyTheme.
The active tab was state seeded from ?tab once, so back/forward moved
the URL without moving the UI. The URL is the source of truth now, and
unknown ?tab values fall back to Packets instead of a blank pane.
Packets history also healed wrong: while the tab is unmounted its WS
handler is down, and staleTime Infinity meant the gap never closed.
Mounting now resets to a fresh first page. Same for lag notices, which
used to invalidate (and sequentially replay) up to 20 cached pages —
together with the ws reconnect notice this covers outage recovery too.
useLiveOverview keyed its setQueryData on the whole RegionFilter object
while the overview query keys on the regionKey string, so every WS bump
silently missed the cache and the counters only moved on the 60s
refetch. Also drop pending counts when the region changes so old-region
increments don't bleed into the new KPIs.
scripts/ was caught by a stray .gitignore entry, so the npm start/stop
helpers pointed at a file fresh clones never got. Also sweep the last
tower-* references out of comments, compose and the map test fixture.
Integrate the stats feature onto main: live mesh KPIs, observations
time-series, payload/node-type donuts, top-nodes/observers leaderboards,
radio presets, scope stats, and per-observer telemetry (battery, airtime,
noise, queue, receive errors) with 1h/6h/24h bucketing.
- adapt to the multi-IATA region model (useRegion -> single iata for /stats/*)
- rename stats getScopes -> getStatsScopes (avoids /scopes name-list collision)
- donut legend kept right but scrollable + truncating so it never clips
- balanced mesh grid; observer empty-state for telemetry-less observers
- normalize telemetry t to ms (raw path emits seconds, bucketed emits ms)
- add echarts 5.5.1; lazy-load StatsOverview chunk
* 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
- usePackets pulls a bigger first page (INITIAL_PACKET_PAGE_SIZE)
- ModalOverlay + useFocusTrap, used by NodeDetailOverlay and PacketAnalyzerOverlay
- PathData popovers for path resolutions; ObserverIcon and Tooltip
- tests for packet structure and the focus trap; tidied types in api.ts/enums.ts