diff --git a/public/nodes.js b/public/nodes.js
index 5d3355e..14200ee 100644
--- a/public/nodes.js
+++ b/public/nodes.js
@@ -815,6 +815,41 @@
* Shared between the full-screen detail page and the side panel (#813, #690).
* No-op if the container is missing, the API errors, or the response lacks severity.
*/
+ /** Build collapsible evidence panel for node clock skew card */
+ function buildEvidencePanel(cs) {
+ var evidence = cs.recentHashEvidence;
+ if (!evidence || evidence.length === 0) return '';
+
+ var calSum = cs.calibrationSummary || {};
+ var calLine = calSum.totalSamples
+ ? '
Last ' + calSum.totalSamples + ' samples: ' + (calSum.calibratedSamples || 0) + ' corrected via observer calibration, ' + (calSum.uncalibratedSamples || 0) + ' uncorrected (single-observer).
'
+ : '';
+
+ // Severity reason.
+ var skewVal = window.currentSkewValue(cs);
+ var sampleCount = (cs.samples || []).length;
+ var sevLabel = SKEW_SEVERITY_LABELS[cs.severity] || cs.severity;
+ var reasonLine = 'Recent ' + sampleCount + ' adverts median ' + formatSkew(skewVal) + ' → ' + sevLabel + '
';
+
+ var hashBlocks = evidence.map(function(ev) {
+ var shortHash = (ev.hash || '').substring(0, 8) + '…';
+ var obsCount = ev.observers ? ev.observers.length : 0;
+ var header = 'Hash ' + shortHash + ' · ' + obsCount + ' observer' + (obsCount !== 1 ? 's' : '') + ' · median corrected: ' + formatSkew(ev.medianCorrectedSkewSec) + '
';
+ var lines = (ev.observers || []).map(function(o) {
+ var name = o.observerName || o.observerID;
+ return '' +
+ name + ' raw=' + formatSkew(o.rawSkewSec) + ' corrected=' + formatSkew(o.correctedSkewSec) + ' (observer offset ' + formatSkew(o.observerOffsetSec) + ')' +
+ '
';
+ }).join('');
+ return header + lines;
+ }).join('');
+
+ return 'Evidence (' + evidence.length + ' hashes)
' +
+ '' +
+ reasonLine + calLine + hashBlocks +
+ '
';
+ }
+
async function loadClockSkewInto(container, pubkey) {
if (!container) return;
try {
@@ -841,7 +876,8 @@
'' +
driftHtml +
(sparkHtml ? '' + sparkHtml + '
Skew over time (' + (cs.samples || []).length + ' samples)
' : '') +
- bimodalWarning;
+ bimodalWarning +
+ buildEvidencePanel(cs);
} catch (e) {
// Non-fatal — section stays hidden
}