diff --git a/src/features/packets/PacketRow.tsx b/src/features/packets/PacketRow.tsx index 1c941b0..2fcba53 100644 --- a/src/features/packets/PacketRow.tsx +++ b/src/features/packets/PacketRow.tsx @@ -54,6 +54,8 @@ export function PacketRow({ packet, expanded, isFresh, onToggle }: PacketRowProp + {packet.summary &&
{packet.summary}
} +
{packet.routeTypeName || "Unknown"} diff --git a/src/features/packets/PacketTableHeader.tsx b/src/features/packets/PacketTableHeader.tsx index 97ae3da..e84f7ec 100644 --- a/src/features/packets/PacketTableHeader.tsx +++ b/src/features/packets/PacketTableHeader.tsx @@ -14,7 +14,7 @@ export function PacketTableHeader() { Obs Hops Hash Size - Src → Dst + Summary / Src → Dst IATA Age
diff --git a/src/features/packets/PacketTableRow.tsx b/src/features/packets/PacketTableRow.tsx index 95a430f..2033903 100644 --- a/src/features/packets/PacketTableRow.tsx +++ b/src/features/packets/PacketTableRow.tsx @@ -58,6 +58,7 @@ export function PacketTableRow({ packet, expanded, isFresh, onToggle }: PacketTa {pathLength?.hopCount ?? na} {pathLength?.hashSize ?? na} + {packet.summary && {packet.summary}} diff --git a/src/features/packets/usePackets.ts b/src/features/packets/usePackets.ts index ef453ee..3f03b55 100644 --- a/src/features/packets/usePackets.ts +++ b/src/features/packets/usePackets.ts @@ -163,6 +163,7 @@ export function usePackets(frozen: boolean = false, serverFilter: PacketServerFi lastHeardAt: data.observation.heardAt, observationCount: data.packet.observationCount, scope: data.packet.scope, + summary: data.packet.summary, latestObserver: { id: data.observation.observerId, displayName: data.observation.observerName, diff --git a/src/types/api.ts b/src/types/api.ts index f50ac80..e8fbf13 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -35,6 +35,7 @@ export interface PacketSummary { observationCount: number; latestObserver?: LatestObserver; scope?: string; // matched transport scope name, e.g. "#bc" + summary?: string; // packet-derived display text, currently the advertised name } export interface ResolvedNode { diff --git a/src/types/ws.ts b/src/types/ws.ts index 7701ccd..3b72345 100644 --- a/src/types/ws.ts +++ b/src/types/ws.ts @@ -51,6 +51,7 @@ export interface WsPacketObservation { isFirstObservation: boolean; observationCount: number; scope?: string; // matched transport scope name; omitted when none matched + summary?: string; }; observation: { observerId: string; diff --git a/tests/features/packets/PacketTableHeader.test.tsx b/tests/features/packets/PacketTableHeader.test.tsx index de254c0..4eaa633 100644 --- a/tests/features/packets/PacketTableHeader.test.tsx +++ b/tests/features/packets/PacketTableHeader.test.tsx @@ -6,7 +6,7 @@ import { GRID_TEMPLATE } from "../../../src/features/packets/packet-grid"; describe("PacketTableHeader", () => { it("declares every column heading", () => { render(); - for (const h of ["Hash", "Type", "Route", "Obs", "Hops", "Hash Size", "Src → Dst", "IATA", "Age"]) { + for (const h of ["Hash", "Type", "Route", "Obs", "Hops", "Hash Size", "Summary / Src → Dst", "IATA", "Age"]) { expect(screen.getByText(h)).toBeInTheDocument(); } }); diff --git a/tests/features/packets/PacketTableRow.test.tsx b/tests/features/packets/PacketTableRow.test.tsx index 27435ae..2e11485 100644 --- a/tests/features/packets/PacketTableRow.test.tsx +++ b/tests/features/packets/PacketTableRow.test.tsx @@ -23,6 +23,13 @@ const observer = ( }; describe("PacketTableRow", () => { + it("keeps endpoints visible alongside a payload summary", () => { + render( {}} />); + expect(screen.getByText("Packet summary")).toBeInTheDocument(); + expect(screen.getByText("Source node")).toBeInTheDocument(); + expect(screen.getByText("Destination node")).toBeInTheDocument(); + }); + it("exposes one button carrying the expansion state", () => { render( {}} />); const btn = screen.getByRole("button"); diff --git a/tests/features/packets/packet-summary.test.tsx b/tests/features/packets/packet-summary.test.tsx new file mode 100644 index 0000000..ca7129f --- /dev/null +++ b/tests/features/packets/packet-summary.test.tsx @@ -0,0 +1,26 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { PacketRow } from "../../../src/features/packets/PacketRow"; +import { PacketTableRow } from "../../../src/features/packets/PacketTableRow"; + +const packet = { + packetHash: "AA11BB22", payloadType: 4, payloadTypeName: "ADVERT", + routeType: 1, routeTypeName: "FLOOD", firstHeardAt: 1, lastHeardAt: 2, observationCount: 1, +}; + +describe.each([["mobile", PacketRow], ["desktop", PacketTableRow]] as const)("%s packet summary", (_name, Row) => { + it("shows the packet's advertised name and keeps row selection working", () => { + const toggle = vi.fn(); + render(); + const summary = screen.getByText("MD00-Repeater 📡"); + expect(summary).toHaveAttribute("title", "MD00-Repeater 📡"); + fireEvent.click(summary); + expect(toggle).toHaveBeenCalledTimes(1); + }); + + it("supports rows from an older server without summaries", () => { + render( {}} />); + expect(screen.getByText(/AA11/)).toBeInTheDocument(); + expect(screen.queryByText("MD00-Repeater 📡")).not.toBeInTheDocument(); + }); +}); diff --git a/tests/features/packets/usePackets.test.tsx b/tests/features/packets/usePackets.test.tsx index ba98a8f..b0198d1 100644 --- a/tests/features/packets/usePackets.test.tsx +++ b/tests/features/packets/usePackets.test.tsx @@ -360,6 +360,20 @@ describe("usePackets live heard window", () => { const flushRaf = () => rafCallbacks.splice(0).forEach((cb) => cb(0)); + it("keeps summaries from history and passes summaries through live observations", async () => { + getPackets.mockResolvedValue({ items: [{ ...packet("history"), summary: "Historical advert" }], nextCursor: null }); + const { result } = renderHook(() => usePackets(), { wrapper }); + await waitFor(() => expect(result.current.allPackets.find((p) => p.packetHash === "history")?.summary).toBe("Historical advert")); + const event = observation("live"); + event.packet.summary = "Live advert 📡"; + act(() => { + result.current.handlePacketObservation(event); + flushRaf(); + }); + expect(result.current.allPackets.find((p) => p.packetHash === "live")?.summary).toBe("Live advert 📡"); + expect(result.current.allPackets.find((p) => p.packetHash === "history")?.summary).toBe("Historical advert"); + }); + // Each WS message carries only its own heardAt, so a second observation of the same packet used to // collapse the window to a single instant — the expanded row then read "spread 0.000s". it("widens the heard window across observations instead of collapsing it", async () => {