diff --git a/frontend/src/components/RawPacketDetailModal.tsx b/frontend/src/components/RawPacketDetailModal.tsx index 2f3d721..9b40518 100644 --- a/frontend/src/components/RawPacketDetailModal.tsx +++ b/frontend/src/components/RawPacketDetailModal.tsx @@ -166,6 +166,10 @@ function formatPathMode(hashSize: number | undefined, hopCount: number): string return `${hopCount} hop${hopCount === 1 ? '' : 's'} · ${hashSize} byte hash${hashSize === 1 ? '' : 'es'}`; } +function formatTransportCodes(codes: [number, number]): string { + return codes.map((c) => `0x${c.toString(16).padStart(4, '0')}`).join(', '); +} + function buildGroupTextResolutionCandidates(channels: Channel[]): GroupTextResolutionCandidate[] { return channels.map((channel) => ({ key: channel.key, @@ -647,7 +651,14 @@ export function RawPacketInspectionPanel({ ) : null} -
+
+ {inspection.decoded?.transportCodes ? ( + + ) : null} {(() => { const sig = formatSignal(packet, signalOverride); return ( diff --git a/frontend/src/test/rawPacketDetailModal.test.tsx b/frontend/src/test/rawPacketDetailModal.test.tsx index ee51d20..0710d15 100644 --- a/frontend/src/test/rawPacketDetailModal.test.tsx +++ b/frontend/src/test/rawPacketDetailModal.test.tsx @@ -39,6 +39,19 @@ const BOT_PACKET: RawPacket = { decrypted_info: null, }; +// TransportFlood ACK: header 0C (route=0 TransportFlood, type=3 ACK, ver=0), +// transport codes 3412 7856 (LE: 0x1234, 0x5678), path_len 00, ACK checksum AABBCCDD +const SCOPED_PACKET: RawPacket = { + id: 2, + timestamp: 1_700_000_000, + data: '0C3412785600AABBCCDD', + decrypted: false, + payload_type: 'Ack', + rssi: -80, + snr: 3.0, + decrypted_info: null, +}; + describe('RawPacketDetailModal', () => { it('copies the full packet hex to the clipboard', async () => { const writeText = vi.fn().mockResolvedValue(undefined); @@ -77,4 +90,18 @@ describe('RawPacketDetailModal', () => { fireEvent.mouseLeave(pathFieldBox as HTMLElement); expect(pathRun.className).toBe(idleClassName); }); + + it('shows scope card with transport codes for scoped packets', () => { + render(); + + expect(screen.getByText('Scope')).toBeInTheDocument(); + expect(screen.getByText('Regional')).toBeInTheDocument(); + expect(screen.getByText('0x1234, 0x5678')).toBeInTheDocument(); + }); + + it('does not show scope card for non-transport packets', () => { + render(); + + expect(screen.queryByText('Scope')).not.toBeInTheDocument(); + }); });