diff --git a/cmd/server/routes.go b/cmd/server/routes.go
index 0a9fcad0..a05c99ca 100644
--- a/cmd/server/routes.go
+++ b/cmd/server/routes.go
@@ -2386,9 +2386,6 @@ func mapSliceToObservations(maps []map[string]interface{}) []ObservationResp {
obs.SNR = m["snr"]
obs.RSSI = m["rssi"]
obs.PathJSON = m["path_json"]
- obs.ResolvedPath = m["resolved_path"]
- obs.Direction = m["direction"]
- obs.RawHex = m["raw_hex"]
obs.Timestamp = m["timestamp"]
result = append(result, obs)
}
diff --git a/cmd/server/types.go b/cmd/server/types.go
index 165ecffd..fc4cbd1d 100644
--- a/cmd/server/types.go
+++ b/cmd/server/types.go
@@ -277,9 +277,6 @@ type ObservationResp struct {
SNR interface{} `json:"snr"`
RSSI interface{} `json:"rssi"`
PathJSON interface{} `json:"path_json"`
- ResolvedPath interface{} `json:"resolved_path,omitempty"`
- Direction interface{} `json:"direction,omitempty"`
- RawHex interface{} `json:"raw_hex,omitempty"`
Timestamp interface{} `json:"timestamp"`
}
diff --git a/public/packets.js b/public/packets.js
index b644b354..fbbf9946 100644
--- a/public/packets.js
+++ b/public/packets.js
@@ -387,7 +387,7 @@
const obs = data.observations.find(o => String(o.id) === String(obsTarget));
if (obs) {
expandedHashes.add(h);
- const obsPacket = {...data.packet, observer_id: obs.observer_id, observer_name: obs.observer_name, snr: obs.snr, rssi: obs.rssi, path_json: obs.path_json, resolved_path: obs.resolved_path, direction: obs.direction, timestamp: obs.timestamp, first_seen: obs.timestamp};
+ const obsPacket = {...data.packet, observer_id: obs.observer_id, observer_name: obs.observer_name, snr: obs.snr, rssi: obs.rssi, path_json: obs.path_json, resolved_path: obs.resolved_path, timestamp: obs.timestamp, first_seen: obs.timestamp};
clearParsedCache(obsPacket);
selectPacket(obs.id, h, {packet: obsPacket, breakdown: data.breakdown, observations: data.observations}, obs.id);
} else {
@@ -1246,7 +1246,7 @@
const child = group?._children?.find(c => String(c.id) === String(value));
if (child) {
const parentData = group._fetchedData;
- const obsPacket = parentData ? {...parentData.packet, observer_id: child.observer_id, observer_name: child.observer_name, snr: child.snr, rssi: child.rssi, path_json: child.path_json, resolved_path: child.resolved_path, direction: child.direction, timestamp: child.timestamp, first_seen: child.timestamp} : child;
+ const obsPacket = parentData ? {...parentData.packet, observer_id: child.observer_id, observer_name: child.observer_name, snr: child.snr, rssi: child.rssi, path_json: child.path_json, resolved_path: child.resolved_path, timestamp: child.timestamp, first_seen: child.timestamp} : child;
if (parentData) { clearParsedCache(obsPacket); }
selectPacket(child.id, parentHash, {packet: obsPacket, breakdown: parentData?.breakdown, observations: parentData?.observations}, child.id);
}
@@ -1797,7 +1797,7 @@
panel.innerHTML = isMobileNow ? '' : '
' + PANEL_CLOSE_HTML;
const content = document.createElement('div');
panel.appendChild(content);
- await renderDetail(content, data, selectedObservationId);
+ await renderDetail(content, data);
if (!isMobileNow) initPanelResize();
} catch (e) {
panel.innerHTML = `Error: ${e.message}
`;
diff --git a/test-frontend-helpers.js b/test-frontend-helpers.js
index 3c06ba9d..2610a7b5 100644
--- a/test-frontend-helpers.js
+++ b/test-frontend-helpers.js
@@ -6116,144 +6116,6 @@ console.log('\n=== analytics.js: renderCollisionsFromServer collision table ==='
});
}
-// ===== Issue #866: Full-page obs-switch — hex + path must update per observation =====
-{
- console.log('\n=== Issue #866: Full-page observation switch ===');
-
- const ctx866 = makeSandbox();
- loadInCtx(ctx866, 'public/roles.js');
- loadInCtx(ctx866, 'public/app.js');
- loadInCtx(ctx866, 'public/packet-helpers.js');
-
- test('#866: switching observation updates effectivePkt path_json', () => {
- const pkt = { id: 1, hash: 'abc123', observer_id: 'obs-agg', path_json: '["A","B","C","D"]', raw_hex: '0484A1B1C1D1', route_type: 1, timestamp: '2026-01-01T00:00:00Z' };
- const obs1 = { id: 10, observer_id: 'obs-1', path_json: '["A","B"]', snr: 5, rssi: -80, timestamp: '2026-01-01T00:01:00Z' };
- const obs2 = { id: 20, observer_id: 'obs-2', path_json: '["A","B","C","D"]', snr: 8, rssi: -75, timestamp: '2026-01-01T00:02:00Z' };
-
- // Simulate renderDetail logic: pick obs1
- const eff1 = ctx866.clearParsedCache({...pkt, ...obs1, _isObservation: true});
- const path1 = ctx866.getParsedPath(eff1);
- assert.deepStrictEqual(path1, ['A', 'B']);
- assert.strictEqual(eff1.observer_id, 'obs-1');
- assert.strictEqual(eff1.snr, 5);
-
- // Switch to obs2
- const eff2 = ctx866.clearParsedCache({...pkt, ...obs2, _isObservation: true});
- const path2 = ctx866.getParsedPath(eff2);
- assert.deepStrictEqual(path2, ['A', 'B', 'C', 'D']);
- assert.strictEqual(eff2.observer_id, 'obs-2');
- assert.strictEqual(eff2.snr, 8);
- });
-
- test('#866: effectivePkt preserves raw_hex from packet when obs has none', () => {
- const pkt = { id: 1, hash: 'h1', raw_hex: '0482AABB', route_type: 1 };
- const obs = { id: 10, observer_id: 'obs-1', path_json: '["AA"]', snr: 3, rssi: -90, timestamp: '2026-01-01T00:00:00Z' };
- const eff = ctx866.clearParsedCache({...pkt, ...obs, _isObservation: true});
- // obs doesn't have raw_hex, so packet's raw_hex survives spread
- assert.strictEqual(eff.raw_hex, '0482AABB');
- });
-
- test('#866: effectivePkt uses obs raw_hex when available (API now returns it)', () => {
- const pkt = { id: 1, hash: 'h1', raw_hex: '0482AABB', route_type: 1 };
- const obs = { id: 10, observer_id: 'obs-1', raw_hex: '0441CC', path_json: '["CC"]', snr: 3, rssi: -90, timestamp: '2026-01-01T00:00:00Z' };
- const eff = ctx866.clearParsedCache({...pkt, ...obs, _isObservation: true});
- // obs has raw_hex from API, should override
- assert.strictEqual(eff.raw_hex, '0441CC');
- });
-
- test('#866: direction field carried through observation spread', () => {
- const pkt = { id: 1, hash: 'h1', direction: 'rx', route_type: 1 };
- const obs = { id: 10, observer_id: 'obs-1', direction: 'tx', path_json: '[]', timestamp: '2026-01-01T00:00:00Z' };
- const eff = {...pkt, ...obs, _isObservation: true};
- assert.strictEqual(eff.direction, 'tx');
- });
-
- test('#866: resolved_path carried through observation spread', () => {
- const pkt = { id: 1, hash: 'h1', resolved_path: '["aaa","bbb","ccc"]', route_type: 1 };
- const obs = { id: 10, observer_id: 'obs-1', resolved_path: '["aaa"]', path_json: '["AA"]', timestamp: '2026-01-01T00:00:00Z' };
- const eff = ctx866.clearParsedCache({...pkt, ...obs, _isObservation: true});
- const rp = ctx866.getResolvedPath(eff);
- assert.deepStrictEqual(rp, ['aaa']);
- });
-
- test('#866: getPathLenOffset used for hop count cross-check', () => {
- // Flood route: offset 1
- assert.strictEqual(ctx866.getPathLenOffset(1), 1);
- assert.strictEqual(ctx866.getPathLenOffset(2), 1);
- // Transport route: offset 5
- assert.strictEqual(ctx866.getPathLenOffset(0), 5);
- assert.strictEqual(ctx866.getPathLenOffset(3), 5);
- });
-
- test('#866: URL hash should encode obs parameter for deep linking', () => {
- // Simulate the URL construction pattern from renderDetail obs click
- const pktHash = 'abc123def456';
- const obsId = '42';
- const url = `#/packets/${pktHash}?obs=${obsId}`;
- assert.strictEqual(url, '#/packets/abc123def456?obs=42');
- // Parse back
- const qIdx = url.indexOf('?');
- const qs = new URLSearchParams(url.substring(qIdx));
- assert.strictEqual(qs.get('obs'), '42');
- });
-}
-
-// ===== #872 — hop-display unreliable badge =====
-{
- console.log('\n--- #872: hop-display unreliable warning badge ---');
-
- function makeHopDisplaySandbox() {
- const sb = {
- window: { addEventListener: () => {}, dispatchEvent: () => {} },
- document: {
- readyState: 'complete',
- createElement: () => ({ id: '', textContent: '', innerHTML: '' }),
- head: { appendChild: () => {} },
- getElementById: () => null,
- addEventListener: () => {},
- querySelectorAll: () => [],
- querySelector: () => null,
- },
- console,
- Date, Math, Array, Object, String, Number, JSON, RegExp, Map, Set,
- encodeURIComponent, parseInt, parseFloat, isNaN, Infinity, NaN, undefined,
- setTimeout: () => {}, setInterval: () => {}, clearTimeout: () => {}, clearInterval: () => {},
- };
- sb.window.document = sb.document;
- sb.self = sb.window;
- sb.globalThis = sb.window;
- const ctx = vm.createContext(sb);
- const hopSrc = fs.readFileSync(__dirname + '/public/hop-display.js', 'utf8');
- vm.runInContext(hopSrc, ctx);
- return ctx;
- }
-
- const hopCtx = makeHopDisplaySandbox();
-
- test('#872: unreliable hop renders warning badge, not strikethrough', () => {
- const html = hopCtx.window.HopDisplay.renderHop('AABB', {
- name: 'TestNode', pubkey: 'pk123', unreliable: true,
- ambiguous: false, conflicts: [], globalFallback: false,
- }, {});
- // Must contain unreliable warning badge button
- assert.ok(html.includes('hop-unreliable-btn'), 'should have unreliable badge button');
- assert.ok(html.includes('⚠️'), 'should have ⚠️ icon');
- assert.ok(html.includes('Unreliable name resolution'), 'should have tooltip text');
- // Must NOT contain line-through in inline style (CSS class no longer has it)
- assert.ok(!html.includes('line-through'), 'should not contain line-through');
- // Should still have hop-unreliable class for subtle styling
- assert.ok(html.includes('hop-unreliable'), 'should have hop-unreliable class');
- });
-
- test('#872: reliable hop does NOT render unreliable badge', () => {
- const html = hopCtx.window.HopDisplay.renderHop('CCDD', {
- name: 'GoodNode', pubkey: 'pk456', unreliable: false,
- ambiguous: false, conflicts: [], globalFallback: false,
- }, {});
- assert.ok(!html.includes('hop-unreliable-btn'), 'should not have unreliable badge');
- });
-}
-
// ===== SUMMARY =====
Promise.allSettled(pendingTests).then(() => {
console.log(`\n${'═'.repeat(40)}`);