- {hops.map((hop, i) => {
- const node = hop.nodes[0];
- const label = node
- ? (node.name ?? node.publicKey.slice(0, 8))
- : hop.hashBytes
- ? hop.hashBytes.toUpperCase()
- : `#${i + 1}`;
+
+ {rawPath.map((raw, i) => {
+ const snr = raw.snr;
+ const level = snr != null ? snrLevel(snr) : null;
+ const sigClass = level ? SIGNAL_LEVEL_CLASSES[level] : "text-text-normal";
return (
{i > 0 && →}
-
+
+
+ {snr != null ? (
+ {formatSnr(snr)} dB
+ ) : (
+ -
+ )}
+
);
})}
@@ -34,12 +44,25 @@ function TraceHopChain({ hops }: { hops: ResolvedHop[] }) {
// One packet in the selected trace. Clicking it opens the shared packet analyzer (the same overlay the
// other tabs use), so a trace packet drills into observations exactly like any packet elsewhere.
-function TracePacketRow({ pkt, onAnalyze }: { pkt: TracePacket; onAnalyze: (hash: string) => void }) {
+function TracePacketRow({ pkt, onAnalyze, onViewNode }: {
+ pkt: TracePacket;
+ onAnalyze: (hash: string) => void;
+ onViewNode?: (nodeId: string) => void;
+}) {
+ // A div, not a button: the hop popover nests clickable node buttons, so the row can't itself be a
+ // button. Mirrors TraceTagCard's role/tabIndex/onKeyDown; hop clicks stopPropagation so they don't analyze.
return (
-
+
);
}
@@ -61,12 +84,13 @@ interface TraceDetailPanelProps {
tag: string;
onClose: () => void;
onAnalyze: (hash: string) => void;
+ onViewNode?: (nodeId: string) => void;
}
// Right-hand detail panel for a selected trace tag, matching the other entity tabs. The trace's
// packets stand in for the packet analyzer's "Observations": a "Packets" section listing each packet,
// any of which opens the packet analyzer.
-export function TraceDetailPanel({ tag, onClose, onAnalyze }: TraceDetailPanelProps) {
+export function TraceDetailPanel({ tag, onClose, onAnalyze, onViewNode }: TraceDetailPanelProps) {
const { data: detail, isLoading } = useQuery({
queryKey: ["trace", tag],
queryFn: () => getTraceDetail(tag),
@@ -85,7 +109,7 @@ export function TraceDetailPanel({ tag, onClose, onAnalyze }: TraceDetailPanelPr
{packets.length} packet{packets.length === 1 ? "" : "s"}
{packets.map((pkt) => (
-
+
))}
) : (
diff --git a/src/features/traces/TraceList.tsx b/src/features/traces/TraceList.tsx
index b619b18..d815e43 100644
--- a/src/features/traces/TraceList.tsx
+++ b/src/features/traces/TraceList.tsx
@@ -14,6 +14,7 @@ const TRACE_LIST_LIMIT = 200;
interface TraceListProps {
onAnalyze: (hash: string | null) => void;
+ onViewNode?: (nodeId: string) => void;
}
// A trace tag as a selectable card, echoing PacketRow's look so the tab reads like the Packets tab.
@@ -48,7 +49,7 @@ function TraceTagCard({ tag, selected, onSelect }: {
);
}
-export function TraceList({ onAnalyze }: TraceListProps) {
+export function TraceList({ onAnalyze, onViewNode }: TraceListProps) {
const { iatas, regionKey } = useRegion();
const [selectedTag, setSelectedTag] = useState