diff --git a/public/live.js b/public/live.js index 357e476b..f5f2923d 100644 --- a/public/live.js +++ b/public/live.js @@ -845,7 +845,13 @@ resolved_path: pkt.resolved_path, _ts: new Date(pkt.timestamp || pkt.created_at).getTime(), decoded: { header: { payloadTypeName: typeName }, payload: raw, path: { hops } }, - snr: pkt.snr, rssi: pkt.rssi, observer: pkt.observer_name + snr: pkt.snr, rssi: pkt.rssi, observer: pkt.observer_name, + // #1898: the region filter matches on observer_id (packetMatchesRegion, + // line ~85). Without it every replayed packet has observer_id undefined, + // so the filter skips them all and drops the whole group. observer_iata + // is carried too so obsIataBadgeHtml does not have to fall back to the + // roster map for replayed packets. + observer_id: pkt.observer_id, observer_iata: pkt.observer_iata }; } diff --git a/public/packets.js b/public/packets.js index d70ce03a..d209c38e 100644 --- a/public/packets.js +++ b/public/packets.js @@ -3449,7 +3449,11 @@ id: o.id, hash: pkt.hash, raw: o.raw_hex || pkt.raw_hex, _ts: new Date(o.timestamp).getTime(), decoded: { header: { payloadTypeName: typeName }, payload: oDec, path: { hops: oPath } }, - snr: o.snr, rssi: o.rssi, observer: obsName(o.observer_id) + snr: o.snr, rssi: o.rssi, observer: obsName(o.observer_id), + // #1900: carry the id itself, not just the resolved name. The Live + // region filter matches on observer_id, so without it the replay + // silently renders nothing whenever a region is selected. + observer_id: o.observer_id, observer_iata: o.observer_iata }); } } else { @@ -3457,7 +3461,8 @@ id: pkt.id, hash: pkt.hash, raw: pkt.raw_hex, _ts: new Date(pkt.timestamp).getTime(), decoded: { header: { payloadTypeName: typeName }, payload: decoded, path: { hops: pathHops } }, - snr: pkt.snr, rssi: pkt.rssi, observer: obsName(pkt.observer_id) + snr: pkt.snr, rssi: pkt.rssi, observer: obsName(pkt.observer_id), + observer_id: pkt.observer_id, observer_iata: pkt.observer_iata }); } sessionStorage.setItem('replay-packet', JSON.stringify(replayPackets)); diff --git a/test-live-region-filter.js b/test-live-region-filter.js index cf914727..5a512636 100644 --- a/test-live-region-filter.js +++ b/test-live-region-filter.js @@ -14,7 +14,13 @@ function test(name, fn) { function makeSandbox() { const ctx = { - window: { addEventListener: () => {}, dispatchEvent: () => {}, devicePixelRatio: 1 }, + window: { + addEventListener: () => {}, dispatchEvent: () => {}, devicePixelRatio: 1, + // live.js:14 captures these at load time from packet-helpers.js, which + // this sandbox does not load. They must exist before live.js is evaluated. + getParsedDecoded: (p) => { try { return p && p.decoded_json ? JSON.parse(p.decoded_json) : {}; } catch (e) { return {}; } }, + getParsedPath: (p) => { try { return p && p.path_json ? JSON.parse(p.path_json) : []; } catch (e) { return []; } }, + }, document: { readyState: 'complete', createElement: () => ({ style:{}, classList:{add(){},remove(){},contains(){return false;}}, setAttribute(){}, addEventListener(){}, getContext: () => ({clearRect(){},fillRect(){},beginPath(){},arc(){},fill(){},scale(){},fillText(){}}) }), @@ -135,7 +141,42 @@ test('observer iata map can be updated and used by filter', () => { assert.strictEqual(fn([{observer_id:'newobs'}], { 'newobs': 'LAX' }, ['LAX']), true); }); +// --- #1898 / #1900: replayed packets must carry observer_id --- +// dbPacketToLive() used to return only observer (the resolved name), so every +// VCR-replayed packet had observer_id undefined. packetMatchesRegion skips a +// packet whose observer_id is null and returns false when none match, so with a +// region selected the replay rendered nothing at all, and the Replay button on +// packet detail silently no-opped. Both issues are the same omission. +const toLive = ctx.window._liveDbPacketToLive; +assert.ok(toLive, '_liveDbPacketToLive must be exposed'); + +test('#1898: dbPacketToLive carries observer_id through', () => { + const live = toLive({ id: 1, hash: 'h', timestamp: '2026-01-01T00:00:00Z', + observer_id: 'obs1', observer_name: 'Obs One' }); + assert.strictEqual(live.observer_id, 'obs1', + 'observer_id must survive; the region filter matches on it'); +}); + +test('#1898: a replayed packet survives an active region filter', () => { + const live = toLive({ id: 1, hash: 'h', timestamp: '2026-01-01T00:00:00Z', + observer_id: 'obs1', observer_name: 'Obs One' }); + assert.strictEqual(fn([live], { obs1: 'BRU' }, ['BRU']), true, + 'with observer_id carried the group matches and is rendered'); +}); + +test('#1898: dropping observer_id is what broke it (guards the regression)', () => { + assert.strictEqual(fn([{ observer: 'Obs One' }], { obs1: 'BRU' }, ['BRU']), false, + 'a packet with no observer_id is skipped, so the group is dropped'); +}); + +test('#1898: observer_iata is carried so the badge needs no roster lookup', () => { + const live = toLive({ id: 1, hash: 'h', timestamp: '2026-01-01T00:00:00Z', + observer_id: 'obs1', observer_iata: 'BRU' }); + assert.strictEqual(live.observer_iata, 'BRU'); +}); + console.log(`\n${'═'.repeat(40)}`); console.log(` live region filter tests: ${passed} passed, ${failed} failed`); + console.log(`${'═'.repeat(40)}\n`); if (failed > 0) process.exit(1);