From ec4dd58cb6ff87dfa1f96c994cdaf07cfc150fcb Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Wed, 1 Apr 2026 10:48:08 -0700 Subject: [PATCH] fix: null-guard pathHops to prevent detail pane crash (#451) (#454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes #451 — packet detail pane crash on direct routed packets where `pathHops` is `null`. ## Root Cause `JSON.parse(pkt.path_json)` can return literal `null` when the DB stores `"null"` for direct routed packets. The existing code only had a catch block for parse errors, but `null` is valid JSON — so the parse succeeds and `pathHops` ends up `null` instead of `[]`. ## Changes - **`public/packets.js`**: Added `|| []` after `JSON.parse(...)` in both `buildFlatRowHtml` (table rows) and the detail pane (`selectPacket`), ensuring `pathHops` is always an array. - **`test-frontend-helpers.js`**: Added 2 regression tests verifying the null guards exist in both code paths. - **`public/index.html`**: Cache buster bump. ## Testing - All 229 frontend helper tests pass - All 62 packet filter tests pass - All 29 aging tests pass Co-authored-by: you --- public/index.html | 56 ++++++++++++++++++++-------------------- public/packets.js | 4 +-- test-frontend-helpers.js | 16 ++++++++++++ 3 files changed, 46 insertions(+), 30 deletions(-) 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');