mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-17 07:24:25 +00:00
Closes #1898. Closes #1900. Two issues, one omission. With a region filter active, VCR replay on the Live map rendered **nothing at all** (#1898), and the Replay button on packet detail **silently did nothing** (#1900). ## Cause `packetMatchesRegion` (`public/live.js:80-92`) matches a packet group by looking up `packets[i].observer_id` in the observer roster map. A packet whose `observer_id` is null is skipped, and when none match it returns `false` and the caller drops the whole group (`live.js:3374`). `dbPacketToLive()` returned `observer` (the resolved name) but never `observer_id`. So every replayed packet was skipped, and every group was dropped. The Replay button had the same gap: both branches passed `obsName(o.observer_id)` and threw the id itself away. **The value was there the whole time.** The VCR builds its entries with `Object.assign({}, p, obs, ...)`, so the observation's `observer_id` is on the input, and the server has returned `observer_id`, `observer_name` and `observer_iata` per packet since `cmd/server/db.go:345-347`. Only the object literal dropped it. ## Fix Carry `observer_id`, and `observer_iata` alongside it so `obsIataBadgeHtml` (`live.js:102-108`) can use the direct field for replayed packets instead of falling back to the roster map. Three lines of behaviour, in two files. ## Verification Four regression tests in `test-live-region-filter.js`. **Three fail without the fix**, checked by reverting `live.js` and re-running: ``` ❌ #1898: dbPacketToLive carries observer_id through ❌ #1898: a replayed packet survives an active region filter ✅ #1898: dropping observer_id is what broke it (guards the regression) ❌ #1898: observer_iata is carried so the badge needs no roster lookup ``` The one that passes either way does so on purpose: it asserts a packet carrying **no** `observer_id` is still dropped, pinning the mechanism so a future change cannot make the filter match everything. That test's sandbox needed `getParsedDecoded` and `getParsedPath`. `live.js:14` captures those from `packet-helpers.js` at load time and the sandbox does not load it, so they are stubbed in the sandbox definition rather than assigned afterwards. Assigning later is too late for that capture, which cost me two attempts. Other suites unaffected: `test-live.js` 95 passed, `test-packet-filter.js` 99, `test-frontend-helpers.js` 656. `test-1110-live-filter.js` fails identically on unmodified master with `ERR_CONNECTION_REFUSED`; it is an E2E test needing a server on port 13581. ## Note Both issues were filed separately and neither names the other. They are the same root cause in sibling code paths, which is why they are fixed together rather than in two PRs. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+7
-1
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user