Drops the Open analyzer and Copy link buttons from the expansion; the analyzer popup keeps its own copy link. Table rows butt up against each other now, so the whole strip is a click target.
The header and the rows are separate grids, so the shared track list can't use ch or auto — each resolved them differently and the columns drifted apart. Observer moves into the expansion to free the column.
The analyzer drawer and path-map restore both read the live ?hash and
fetch by hash directly, independent of whether the packet is in the
loaded list. Deleting the param once the first page settles without a
match unmounts the drawer and can drop path-map restores, for any
shared link more than a couple of minutes old.
- Stop resetting selectedObservationId when opening the analyzer, so picking an
observation inside an expanded row lands the drawer on it instead of observations[0]
- Disable View path on map in the expanded row when the detail has no resolvable path,
matching the drawer's existing button
- Strip a ?hash deep link once the first page has loaded and it matches no packet
- 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).
- 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)
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.
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.
* 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
- 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