From 67b405251beb903ac026dcf0d20f1e4b3c3dbf2b Mon Sep 17 00:00:00 2001 From: MarekWo Date: Sun, 19 Jul 2026 19:19:00 +0200 Subject: [PATCH] feat(pathanalyzer): path hash size (HB) column and filter - HB column in the message table: distinct hash sizes (bytes per hop) across the message's routed echoes, e.g. '1' or '1,2'; em dash when no routed echo - HB filter (Any/1-byte/2-byte/3-byte): matches when any routed echo uses that hash size; 0-hop echoes are excluded since their stored hash_size is meaningless; combines with the other filters and is reset by Clear Verified live via Playwright: 1B/2B/3B filters each return only messages with a matching routed echo (183/166/9 of 606, zero false positives), combined hops+HB filtering works, Clear resets. Co-Authored-By: Claude Fable 5 --- app/static/js/path-analyzer.js | 22 +++++++++++++++++++--- app/templates/path-analyzer.html | 8 ++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/static/js/path-analyzer.js b/app/static/js/path-analyzer.js index 10686a5..d7458c5 100644 --- a/app/static/js/path-analyzer.js +++ b/app/static/js/path-analyzer.js @@ -83,7 +83,7 @@ function showNotification(message, type = 'info') { // ================================================================ let paMessages = []; -let paFilters = { hops: 'any', token: '', sender: '' }; +let paFilters = { hops: 'any', hashSize: 'any', token: '', sender: '' }; let paCurrentView = 'messages'; // 'messages' | 'stats' let paContacts = []; // /api/contacts/cached?format=full let paStatsSort = { key: 'relayed', dir: -1 }; @@ -126,6 +126,12 @@ function paMessageMatchesFilters(msg) { want === '4+' ? e.hops >= 4 : e.hops === parseInt(want, 10)); if (!ok) return false; } + if (paFilters.hashSize !== 'any') { + const want = parseInt(paFilters.hashSize, 10); + // Only routed echoes carry a meaningful hash size + const ok = msg.echoView.some(e => e.hops > 0 && (e.hash_size || 1) === want); + if (!ok) return false; + } if (paFilters.token) { const t = paFilters.token; const ok = msg.echoView.some(e => e.tokens.some(tok => tok.startsWith(t))); @@ -138,7 +144,8 @@ function paMessageMatchesFilters(msg) { } function paFiltersActive() { - return paFilters.hops !== 'any' || paFilters.token !== '' || paFilters.sender !== ''; + return paFilters.hops !== 'any' || paFilters.hashSize !== 'any' + || paFilters.token !== '' || paFilters.sender !== ''; } function paFormatTime(msg) { @@ -258,6 +265,12 @@ function paRenderTable() { tdHops.textContent = (msg.hop_count === null || msg.hop_count === undefined) ? '—' : msg.hop_count; tr.appendChild(tdHops); + const tdHb = document.createElement('td'); + tdHb.className = 'text-end'; + const hbSizes = [...new Set(msg.echoView.filter(e => e.hops > 0).map(e => e.hash_size || 1))].sort(); + tdHb.textContent = hbSizes.length > 0 ? hbSizes.join(',') : '—'; + tr.appendChild(tdHb); + const tdEchoes = document.createElement('td'); tdEchoes.className = 'text-end'; tdEchoes.textContent = msg.echoView.length; @@ -270,7 +283,7 @@ function paRenderTable() { detailTr.className = 'd-none'; const detailTd = document.createElement('td'); detailTd.className = 'pa-echo-cell'; - detailTd.colSpan = 8; + detailTd.colSpan = 9; for (const echo of msg.echoView) { detailTd.appendChild(paBuildEchoLine(echo)); } @@ -699,6 +712,7 @@ async function paLoadMessages() { function paReadFilters() { paFilters.hops = document.getElementById('paHopsFilter').value; + paFilters.hashSize = document.getElementById('paHashSizeFilter').value; // Normalize token input to hex characters only (matches chip display casing) paFilters.token = document.getElementById('paTokenFilter').value .replace(/[^0-9a-fA-F]/g, '').toUpperCase(); @@ -715,6 +729,7 @@ function paApplyFilters() { function paClearFilters() { document.getElementById('paHopsFilter').value = 'any'; + document.getElementById('paHashSizeFilter').value = 'any'; document.getElementById('paTokenFilter').value = ''; document.getElementById('paSenderFilter').value = ''; paApplyFilters(); @@ -735,6 +750,7 @@ document.addEventListener('DOMContentLoaded', () => { filterDebounce = setTimeout(paApplyFilters, 150); }; document.getElementById('paHopsFilter').addEventListener('change', paApplyFilters); + document.getElementById('paHashSizeFilter').addEventListener('change', paApplyFilters); document.getElementById('paTokenFilter').addEventListener('input', debouncedApply); document.getElementById('paSenderFilter').addEventListener('input', debouncedApply); document.getElementById('paClearFiltersBtn').addEventListener('click', paClearFilters); diff --git a/app/templates/path-analyzer.html b/app/templates/path-analyzer.html index 4f69e0d..e2a1c04 100644 --- a/app/templates/path-analyzer.html +++ b/app/templates/path-analyzer.html @@ -344,6 +344,13 @@ + Message Hash Hops + HB Echoes