diff --git a/public/scope-audit.css b/public/scope-audit.css index 3dd6e36d..414868ab 100644 --- a/public/scope-audit.css +++ b/public/scope-audit.css @@ -103,3 +103,18 @@ region IS observed. The dotted underline says how that was established without introducing a third colour into a column that already carries two. */ .sa-chip-verified { text-decoration: underline dotted; text-underline-offset: 2px; } + +/* #2142 transport view: view toggle, region filter, carried-scope chips. */ +.sa-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } +.sa-region-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0 0 10px; font-size: 12px; } +.sa-region-bar[hidden] { display: none; } /* display:flex above would otherwise override the hidden attribute */ +.sa-region-bar label { font-weight: 600; } +/* .nodes-search sets width:100%; the region box only needs room for one code. */ +.sa-region-bar .sa-region { width: 160px; max-width: 100%; flex: none; } +.sa-chip-transported { background: var(--section-bg, var(--card-bg)); color: var(--text); border: 1px solid var(--border); } +.sa-chip-transported .sa-chip-count { color: var(--text-muted); } +/* The region being filtered on: same chip, heavier outline in the link colour, + so it stands out without adding a third status colour. */ +.sa-chip-match { border-color: var(--link-color); box-shadow: inset 0 0 0 1px var(--link-color); } +.sa-not-asked { color: var(--text-muted); font-style: italic; font-size: 12px; } +.sa-counts { font-size: 11px; white-space: nowrap; } diff --git a/public/scope-audit.js b/public/scope-audit.js index de85f358..9aacbc8a 100644 --- a/public/scope-audit.js +++ b/public/scope-audit.js @@ -17,10 +17,58 @@ { key: '7d', label: '7d' } ]; var win = DEFAULT_WINDOW; + // #2142: 'declared' is the declared-vs-observed audit; 'transport' lists every + // repeater seen forwarding, optionally split by one region (rollout tracking). + var MODES = [ + { key: 'declared', label: 'Declared' }, + { key: 'transport', label: 'Transport' } + ]; + var mode = 'declared'; + var region = ''; // transport view only; normalised (no '#', lower case) var searchQuery = ''; // free-text filter over name/pubkey/region, applied client-side var searchIndex = {}; // publicKey -> lowercased searchable haystack, rebuilt every renderBody var sortCtl = null; // tracked TableSort controller for destroy-before-reinit (mirrors observers.js) + // introHtml describes the view being shown: the declared view covers only + // repeaters that answered a declared-regions request, the transport view + // every repeater seen forwarding. + function introHtml(m) { + if (m === 'transport') { + return 'Every repeater seen forwarding in the window, with the region scopes it was seen carrying and, where it has answered, the regions it declares. ' + + 'Type a region to see which repeaters carry it and which were active but did not. Per-node detail lives on each node\'s page.'; + } + return 'Network-wide comparison of declared vs. observed region-scope forwarding, across every repeater that has declared a region list over RF. ' + + 'Per-node detail lives on each node\'s page.'; + } + + function modeBtn(key, cur, label) { + var on = key === cur; + return ''; + } + + // normRegion mirrors the server's normalisation for ?region=: no leading '#', + // lower case, trimmed. + function normRegion(r) { + r = String(r || '').trim().toLowerCase(); + return r.charAt(0) === '#' ? r.slice(1) : r; + } + + // buildHash / apiPath carry the view state, so a transport view filtered to + // one region is shareable as a link (AGENTS.md deep-linking rule). + function modeQuery(m, rgn) { + if (m !== 'transport') return ''; + var q = '&mode=transport'; + if (rgn) q += '&scope=' + encodeURIComponent(rgn); + return q; + } + function buildHash(w, m, rgn) { + return '#/scope-audit?window=' + encodeURIComponent(w) + modeQuery(m, rgn); + } + function apiPath(w, m, rgn) { + return '/scope-audit?window=' + encodeURIComponent(w) + modeQuery(m, rgn); + } + function windowBtn(key, cur, label) { var on = key === cur; return '