From 07d8a875effba30f5c119ce1e353f3d5d7dd1035 Mon Sep 17 00:00:00 2001 From: n30nex Date: Sun, 6 Sep 2026 15:03:58 -0400 Subject: [PATCH] fix(nodes): remove misleading aggregate SNR headlines (#1969) Red commit: `2bf9be8` (local Chromium: 3 passed, 3 intended assertion failures). CI: [run](https://github.com/Kpa-clawbot/CoreScope/actions/runs/33988112224) awaits maintainer approval (`action_required`); 0 jobs started. Remove the unqualified aggregate Avg SNR row from node side-panel Overview and full-detail stats, following option 3 in #1149. Heard By retains each observer's SNR reading. Fixes #1149. - E2E assertion added: `test-issue-1281-location-row-e2e.js:224`. Three new browser cases cover desktop side/full and mobile full views with a numeric aggregate and distinct positive/negative observer readings. Existing packet-location assertions remain intact. - Browser verified: local Chromium; 6 cases passed after push. Screenshots: `coverage/issue-1149/issue-1149-desktop-side-panel.png`, `coverage/issue-1149/issue-1149-desktop-full-detail.png`, and `coverage/issue-1149/issue-1149-mobile-full-detail.png`. - Validation: packet filter 99/99, aging 18/18, frontend helpers 666/666; XSS, CSS-variable, syntax, whitespace and PII checks passed. - Independent reviews: adversarial, lifecycle expert and TDD reviewers found no required changes. One initial browser navigation timed out; the unchanged parent rerun passed 6/6. - Performance/config: two production row deletions; no new requests, loops, timers, settings or customizer implications. Backend unchanged; Go suites were not rerun. Fix commit: `d7c68f3`. ## Preflight overrides - External `run-all.sh` is unavailable on this host. Scoped branch, red/green, PII, CSS, XSS and whitespace checks were run directly. The diff adds no migrations, SQL attribution or image markup. (cherry picked from commit 108ea020f74a9d0a460b3e426ef0d20d0e9f0de9) Co-Authored-By: Claude Opus 5 --- public/nodes.js | 2 - test-issue-1281-location-row-e2e.js | 79 +++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/public/nodes.js b/public/nodes.js index db73c94b4..ff0ed6602 100644 --- a/public/nodes.js +++ b/public/nodes.js @@ -786,7 +786,6 @@ First Seen${renderNodeTimestampHtml(n.first_seen)} Total Packets${stats.totalTransmissions || stats.totalPackets || n.advert_count || 0}${stats.totalObservations && stats.totalObservations !== (stats.totalTransmissions || stats.totalPackets) ? ' (seen ' + stats.totalObservations + '×)' : ''} Packets Today${stats.packetsToday || 0} - ${stats.avgSnr != null ? `Avg SNR${Number(stats.avgSnr).toFixed(1)} dB` : ''} ${stats.avgHops ? `Avg Hops${stats.avgHops}` : ''} ${hasLoc ? `Location${Number(n.lat).toFixed(5)}, ${Number(n.lon).toFixed(5)}` : ''} ${hasEstLoc ? `${hasLoc ? 'Neighbor Estimate' : 'Location'} (estimated)~${Number(n.estimated_lat).toFixed(5)}, ~${Number(n.estimated_lon).toFixed(5)} (from ${n.estimated_contributor_count} neighbor${n.estimated_contributor_count === 1 ? '' : 's'}${hasLoc ? ', ' + Number(n.estimated_distance_km).toFixed(1) + ' km from reported position' : ', no real GPS fix'})` : ''} @@ -1838,7 +1837,6 @@
First Seen
${renderNodeTimestampHtml(n.first_seen)}
Total Packets
${totalPackets}
Packets Today
${stats.packetsToday || 0}
- ${stats.avgSnr != null ? `
Avg SNR
${Number(stats.avgSnr).toFixed(1)} dB
` : ''} ${stats.avgHops ? `
Avg Hops
${stats.avgHops}
` : ''} ${hasLoc ? `
Location
${Number(n.lat).toFixed(5)}, ${Number(n.lon).toFixed(5)}
` : ''} ${hasEstLoc ? `
${hasLoc ? 'Neighbor Estimate' : 'Location'} (estimated)
~${Number(n.estimated_lat).toFixed(5)}, ~${Number(n.estimated_lon).toFixed(5)} (from ${n.estimated_contributor_count} neighbor${n.estimated_contributor_count === 1 ? '' : 's'}${hasLoc ? ', ' + Number(n.estimated_distance_km).toFixed(1) + ' km from reported position' : ', no real GPS fix'})
` : ''} diff --git a/test-issue-1281-location-row-e2e.js b/test-issue-1281-location-row-e2e.js index a92be9a4d..ffde5cc6f 100644 --- a/test-issue-1281-location-row-e2e.js +++ b/test-issue-1281-location-row-e2e.js @@ -14,6 +14,8 @@ * 3. The 📍map link uses class="loc-map-link" with a themed link color * (was --accent pre-M5; now --link-color after #1668/PR #1696 AA-contrast * fix). The hard guarantee is: NOT the default UA blue rgb(0,0,238). + * 4. #1149: node side/full stats omit aggregate SNR, retaining Heard By + * readings on desktop and mobile. Node API fixtures keep SNR non-null. * * Usage: BASE_URL=http://localhost:13581 node test-issue-1281-location-row-e2e.js */ @@ -157,6 +159,83 @@ async function findPacketDetailByType(page, predicate, maxRows = 40) { 'Link color is UA-default blue — class is missing or CSS rule does not match'); }); + const pubkey = 'ab'.repeat(32); + const now = new Date().toISOString(); + const node = { + public_key: pubkey, name: 'SNR fixture node', role: 'repeater', + first_seen: now, last_seen: now, advert_count: 1005, + }; + const health = { + stats: { totalPackets: 1005, packetsToday: 7, avgSnr: 6.3, avgHops: 2, lastHeard: now }, + observers: [ + { observer_id: 'observer-a', observer_name: 'Observer A', packetCount: 1000, avgSnr: 12.5, avgRssi: -45 }, + { observer_id: 'observer-b', observer_name: 'Observer B', packetCount: 5, avgSnr: -8.5, avgRssi: -110 }, + ], + recentPackets: [], + }; + + for (const view of [ + { name: 'desktop side panel', width: 1280, full: false }, + { name: 'desktop full detail', width: 1280, full: true }, + { name: 'mobile full detail', width: 390, full: true }, + ]) { + await step('#1149 ' + view.name + ' retains observer SNR without an aggregate headline', async () => { + const nodePage = await browser.newPage({ viewport: { width: view.width, height: 900 } }); + try { + await nodePage.route('**/api/nodes**', async (route) => { + const pathname = new URL(route.request().url()).pathname; + const fixtures = { + '/api/nodes': { nodes: [node], total: 1, counts: { repeaters: 1 } }, + '/api/nodes/clock-skew': [], + ['/api/nodes/' + pubkey]: { node, recentAdverts: [] }, + ['/api/nodes/' + pubkey + '/health']: health, + ['/api/nodes/' + pubkey + '/neighbors']: { neighbors: [] }, + ['/api/nodes/' + pubkey + '/paths']: { paths: [] }, + ['/api/nodes/' + pubkey + '/clock-skew']: {}, + }; + if (Object.prototype.hasOwnProperty.call(fixtures, pathname)) { + await route.fulfill({ json: fixtures[pathname] }); + } else { + await route.continue(); + } + }); + await nodePage.goto(BASE + '/#/nodes', { waitUntil: 'domcontentloaded' }); + await nodePage.locator('#nodesBody tr[data-key="' + pubkey + '"]').click(); + if (view.full && view.width > 640) { + await nodePage.locator('#nodesRight .node-detail-btn').click(); + } + + const stats = nodePage.locator(view.full ? '#node-stats' : '#nodesRight .detail-meta'); + const readings = nodePage.locator(view.full ? '#node-observers tbody tr' : '#nodesRight .observer-row'); + await readings.first().waitFor({ state: 'visible' }); + assert(await readings.count() === 2, 'Heard By must retain both observers'); + for (const observer of health.observers) { + const row = readings.filter({ hasText: observer.observer_name }); + const text = (await row.innerText()).replace(/\s+/g, ' '); + const snr = observer.avgSnr.toFixed(1) + (view.full ? ' dB' : 'dB'); + assert(text.includes(snr), observer.observer_name + ' must retain SNR ' + snr + ', got: ' + text); + } + for (const [label, value] of [['Total Packets', '1005'], ['Packets Today', '7'], ['Avg Hops', '2']]) { + const metric = stats.getByText(label, { exact: true }); + const actual = await metric.evaluate(el => el.nextElementSibling.textContent.trim()); + assert(actual === value, label + ' must remain ' + value + ', got: ' + actual); + } + const headlineCount = await stats.getByText('Avg SNR', { exact: true }).count(); + assert(headlineCount === 0, + 'Expected no aggregate Avg SNR headline with stats.avgSnr=6.3, got ' + headlineCount); + if (process.env.SCREENSHOT_DIR) { + await stats.scrollIntoViewIfNeeded(); + await nodePage.screenshot({ + path: require('path').join(process.env.SCREENSHOT_DIR, 'issue-1149-' + view.name.replace(/ /g, '-') + '.png'), + fullPage: true, + }); + } + } finally { + await nodePage.close(); + } + }); + } + await browser.close(); console.log(`\n${passed} passed, ${failed} failed`);