diff --git a/public/index.html b/public/index.html index 57884be3..55f66d9b 100644 --- a/public/index.html +++ b/public/index.html @@ -22,9 +22,9 @@ - - - + + + @@ -85,30 +85,30 @@
- - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/packets.js b/public/packets.js index 40401834..d6cbe727 100644 --- a/public/packets.js +++ b/public/packets.js @@ -1080,7 +1080,7 @@ function buildFlatRowHtml(p) { let decoded, pathHops = []; try { decoded = JSON.parse(p.decoded_json || '{}'); } catch {} - try { pathHops = JSON.parse(p.path_json || '[]'); } catch {} + try { pathHops = JSON.parse(p.path_json || '[]') || []; } catch {} const region = p.observer_id ? (observers.find(o => o.id === p.observer_id)?.iata || '') : ''; const typeName = payloadTypeName(p.payload_type); const typeClass = payloadTypeColor(p.payload_type); @@ -1420,7 +1420,7 @@ let decoded; try { decoded = JSON.parse(pkt.decoded_json); } catch { decoded = {}; } let pathHops; - try { pathHops = JSON.parse(pkt.path_json || '[]'); } catch { pathHops = []; } + try { pathHops = JSON.parse(pkt.path_json || '[]') || []; } catch { pathHops = []; } // Resolve sender GPS — from packet directly, or from known node in DB let senderLat = decoded.lat != null ? decoded.lat : (decoded.latitude || null); diff --git a/test-frontend-helpers.js b/test-frontend-helpers.js index b72c16fb..6fb128d3 100644 --- a/test-frontend-helpers.js +++ b/test-frontend-helpers.js @@ -2668,6 +2668,22 @@ console.log('\n=== packets.js: savedTimeWindowMin defaults ==='); 'buildFlatRowHtml should have null-safe decoded_json fallback'); }); + test('pathHops null guard in buildFlatRowHtml (issue #451)', () => { + const flatBuilderMatch = packetsSource.match(/function buildFlatRowHtml[\s\S]*?(?=\n function )/); + assert.ok(flatBuilderMatch, 'buildFlatRowHtml should exist'); + // The JSON.parse result must be coalesced with || [] to handle literal null from path_json + assert.ok(flatBuilderMatch[0].includes("|| '[]') || []"), + 'buildFlatRowHtml should coalesce parsed path_json with || [] to guard against null'); + }); + + test('pathHops null guard in detail pane (issue #451)', () => { + // The detail pane (selectPacket / showPacketDetail) also parses path_json + const detailMatch = packetsSource.match(/let pathHops;\s*try \{[^}]+\} catch/); + assert.ok(detailMatch, 'detail pane pathHops parsing should exist'); + assert.ok(detailMatch[0].includes("|| '[]') || []"), + 'detail pane should coalesce parsed path_json with || [] to guard against null'); + }); + test('destroy cleans up virtual scroll state', () => { assert.ok(packetsSource.includes('detachVScrollListener'), 'destroy should detach virtual scroll listener');