diff --git a/public/nodes.js b/public/nodes.js
index 2547541..fd69754 100644
--- a/public/nodes.js
+++ b/public/nodes.js
@@ -628,34 +628,9 @@
headerSelector: '#fullNeighborsHeader'
});
- // #690 — Clock Skew detail section
- (async function loadClockSkew() {
- var container = document.getElementById('node-clock-skew');
- if (!container) return;
- try {
- var cs = await api('/nodes/' + encodeURIComponent(n.public_key) + '/clock-skew', { ttl: 30000 });
- if (!cs || !cs.severity) return;
- container.style.display = '';
- var severityColor = SKEW_SEVERITY_COLORS[cs.severity] || 'var(--text-muted)';
- var severityLabel = SKEW_SEVERITY_LABELS[cs.severity] || cs.severity;
- var driftHtml = cs.driftPerDaySec ? '
Drift: ' + formatDrift(cs.driftPerDaySec) + '
' : '';
- var sparkHtml = renderSkewSparkline(cs.samples, 200, 32);
- var skewDisplay = cs.severity === 'no_clock'
- ? 'No Clock'
- : '' + formatSkew(cs.medianSkewSec) + '';
- container.innerHTML =
- '⏰ Clock Skew
' +
- '' +
- skewDisplay +
- renderSkewBadge(cs.severity, cs.medianSkewSec) +
- (cs.calibrated ? ' ✓ calibrated' : '') +
- '
' +
- driftHtml +
- (sparkHtml ? '' + sparkHtml + '
Skew over time (' + (cs.samples || []).length + ' samples)
' : '');
- } catch (e) {
- // Non-fatal — section stays hidden
- }
- })();
+ // #690 — Clock Skew detail section (full-screen view)
+ loadClockSkewInto(document.getElementById('node-clock-skew'), n.public_key);
+
// Affinity debug panel — show if debugAffinity is enabled
(function loadAffinityDebug() {
@@ -812,6 +787,36 @@
let _allNodes = null; // cached full node list
let _fleetSkew = null; // cached clock skew map: pubkey → {severity, medianSkewSec, ...}
+ /**
+ * Fetch per-node clock skew and render into the given container element.
+ * 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.
+ */
+ async function loadClockSkewInto(container, pubkey) {
+ if (!container) return;
+ try {
+ var cs = await api('/nodes/' + encodeURIComponent(pubkey) + '/clock-skew', { ttl: 30000 });
+ if (!cs || !cs.severity) return;
+ container.style.display = '';
+ var driftHtml = cs.driftPerDaySec ? 'Drift: ' + formatDrift(cs.driftPerDaySec) + '
' : '';
+ var sparkHtml = renderSkewSparkline(cs.samples, 200, 32);
+ var skewDisplay = cs.severity === 'no_clock'
+ ? 'No Clock'
+ : '' + formatSkew(cs.medianSkewSec) + '';
+ container.innerHTML =
+ '⏰ Clock Skew
' +
+ '' +
+ skewDisplay +
+ renderSkewBadge(cs.severity, cs.medianSkewSec) +
+ (cs.calibrated ? ' ✓ calibrated' : '') +
+ '
' +
+ driftHtml +
+ (sparkHtml ? '' + sparkHtml + '
Skew over time (' + (cs.samples || []).length + ' samples)
' : '');
+ } catch (e) {
+ // Non-fatal — section stays hidden
+ }
+ }
+
/** Fetch fleet clock skew once, return map keyed by pubkey */
async function getFleetSkew() {
if (_fleetSkew) return _fleetSkew;
@@ -1194,6 +1199,8 @@
+
+
${observers.length ? `
${(() => { const regions = [...new Set(observers.map(o => o.iata).filter(Boolean))]; return regions.length ? `
Regions: ${regions.join(', ')}
` : ''; })()}
Heard By (${observers.length} observer${observers.length > 1 ? 's' : ''})
@@ -1287,6 +1294,10 @@
viewAllPubkey: n.public_key
});
+ // #813 — Clock Skew section in side panel (mirrors full-screen view)
+ loadClockSkewInto(document.getElementById('node-clock-skew'), n.public_key);
+
+
// Fetch paths through this node
api('/nodes/' + encodeURIComponent(n.public_key) + '/paths', { ttl: CLIENT_TTL.nodeDetail }).then(pathData => {
const el = document.getElementById('pathsContent');