From 904fd5bf42429e7ce8de0d99e8dcf0f8490d7dda Mon Sep 17 00:00:00 2001 From: dborup Date: Sun, 19 Jul 2026 21:30:24 +0200 Subject: [PATCH] feat: split the Scopes tab into Overview/Regions/Hygiene sub-tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tab grew to 12 stacked sections as this branch's work landed (windowed adoption stats, all-time region breakdowns, and the two new node/repeater hygiene lists) — one long scroll with no way to jump to a specific concern. Grouped into 3 sub-tabs: - Overview: the 1h/24h/7d-windowed stuff (stat cards, channel messages, scope adoption by channel, per-region table, time series, hour-of-day heatmap). The window picker now lives inside this panel, not above the sub-tab bar, since it only affects this group — the other two ignore it entirely. - Regions: all-time region breakdowns (utilization, repeaters by region, nodes running this region, bridge repeaters). - Hygiene: the two adoption-gap lists (nodes not using any scope, repeaters never relaying any scope). Purely presentational — all three groups' data still loads together in one pass (one /scope-stats fetch + the hygiene sections' own fetchAllNodes calls); only DOM visibility toggles on the active sub-tab, so there's no new fetch/render logic to get wrong. Selection persists in sessionStorage (scopes_subtab), same pattern as the existing window-picker persistence. Co-Authored-By: Claude Sonnet 5 --- public/analytics.js | 82 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/public/analytics.js b/public/analytics.js index 162cd50b..743139bc 100644 --- a/public/analytics.js +++ b/public/analytics.js @@ -4482,6 +4482,19 @@ function destroy() { _stopRolesRefresh(); _stopScopesRefresh(); _stopForeignTraf var winKey = 'scopes_window'; var selectedWindow = (typeof sessionStorage !== 'undefined' && sessionStorage.getItem(winKey)) || '24h'; + // #1852: the tab grew to 12 stacked sections (windowed adoption stats, + // all-time region breakdowns, all-time node/repeater hygiene lists) — + // grouped into 3 sub-tabs so a visitor sees one coherent screen at a + // time instead of one long scroll. Purely presentational: all three + // groups' data still loads together (one #scope-stats fetch + the two + // hygiene sections' own fetchAllNodes calls), only DOM visibility is + // gated on the active sub-tab. The 1h/24h/7d window picker only + // affects the Overview group (the all-time Regions/Hygiene sections + // ignore it entirely), so it lives inside that panel, not above the + // sub-tab bar. + var subtabKey = 'scopes_subtab'; + var selectedSubtab = (typeof sessionStorage !== 'undefined' && sessionStorage.getItem(subtabKey)) || 'overview'; + // Fix 5: write static frame only once if (!el.querySelector('#scopes-cards')) { el.innerHTML = @@ -4489,27 +4502,58 @@ function destroy() { _stopRolesRefresh(); _stopScopesRefresh(); _stopForeignTraf '

' + 'Denominator is all observed transmissions. Only TRANSPORT_FLOOD (0) and TRANSPORT_DIRECT (3) routes carry a scope; FLOOD (1) and DIRECT (2) are inherently unscoped per MeshCore protocol.' + '

' + - '
' + - ['1h', '24h', '7d'].map(function(v) { - return ''; + '
' + + [ + { key: 'overview', label: 'Overview' }, + { key: 'regions', label: 'Regions' }, + { key: 'hygiene', label: 'Hygiene' }, + ].map(function(t) { + return ''; }).join('') + '
' + - '
' + - '
' + - '
' + - '
Loading scope stats…
' + - '' + - '' + - '' + - '
RegionMessages% of Scoped
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
'; + '
' + + '
' + + ['1h', '24h', '7d'].map(function(v) { + return ''; + }).join('') + + '
' + + '
' + + '
' + + '
' + + '
Loading scope stats…
' + + '' + + '' + + '' + + '
RegionMessages% of Scoped
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
'; + + // Sub-tab click listener (once) — pure visibility toggle, no re-fetch. + var subtabsEl = el.querySelector('#scopesSubtabs'); + if (subtabsEl) { + subtabsEl.addEventListener('click', function(e) { + var btn = e.target.closest('[data-subtab]'); + if (!btn) return; + selectedSubtab = btn.dataset.subtab; + if (typeof sessionStorage !== 'undefined') sessionStorage.setItem(subtabKey, selectedSubtab); + subtabsEl.querySelectorAll('[data-subtab]').forEach(function(b) { b.classList.toggle('active', b.dataset.subtab === selectedSubtab); }); + ['overview', 'regions', 'hygiene'].forEach(function(key) { + var panel = document.getElementById('scopes-panel-' + key); + if (panel) panel.style.display = key === selectedSubtab ? '' : 'none'; + }); + }); + } // Attach window-button click listeners (once) el.querySelectorAll('[data-win]').forEach(function(btn) {