feat(pathanalyzer): repeater filter matches by name as well as hash

The repeater filter input now accepts either form: pure-hex input still
prefix-matches hop hash tokens, and any input also matches the names of
contacts each token resolves to (pubkey-prefix candidates, memoized per
token). With 1-byte hashes a name can match via a colliding candidate,
so the filter is intentionally inclusive: it keeps messages where the
named repeater could be on the path.

Verified live via Playwright: hex prefix regression OK, name search
returns only messages with a token resolving to the named contact,
memoized pass over 606 messages takes <1 ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-07-19 19:44:32 +02:00
parent 3522dbabef
commit 0ba0503547
2 changed files with 30 additions and 8 deletions
+27 -6
View File
@@ -93,7 +93,10 @@ async function paLoadContacts() {
const resp = await fetch('/api/contacts/cached?format=full');
if (resp.ok) {
const data = await resp.json();
if (data.success) paContacts = data.contacts || [];
if (data.success) {
paContacts = data.contacts || [];
paTokenNameCache = new Map();
}
}
} catch (e) {
console.error('Failed to load contacts:', e);
@@ -107,6 +110,19 @@ function paMatchContacts(token) {
return paContacts.filter(c => (c.public_key || '').toLowerCase().startsWith(prefix));
}
// token -> lowercase candidate contact names, memoized (contacts are
// static per page load; cache is rebuilt when they arrive)
let paTokenNameCache = new Map();
function paTokenNames(token) {
let names = paTokenNameCache.get(token);
if (!names) {
names = paMatchContacts(token).map(c => (c.name || '').toLowerCase());
paTokenNameCache.set(token, names);
}
return names;
}
// Split an echo's path hex into per-hop tokens using that echo's own
// hash_size (same logic as showPathsPopup in app.js; trailing partial kept)
function paDecodeEcho(echo) {
@@ -133,8 +149,15 @@ function paMessageMatchesFilters(msg) {
if (!ok) return false;
}
if (paFilters.token) {
const t = paFilters.token;
const ok = msg.echoView.some(e => e.tokens.some(tok => tok.startsWith(t)));
const raw = paFilters.token; // lowercase
const isHex = /^[0-9a-f]+$/.test(raw);
const hexPrefix = raw.toUpperCase();
// Hex input matches hash tokens by prefix; any input also matches
// the names of contacts the token resolves to
const ok = msg.echoView.some(e => e.tokens.some(tok =>
(isHex && tok.startsWith(hexPrefix)) ||
paTokenNames(tok).some(n => n.includes(raw))
));
if (!ok) return false;
}
if (paFilters.sender) {
@@ -754,9 +777,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();
paFilters.token = document.getElementById('paTokenFilter').value.trim().toLowerCase();
paFilters.sender = document.getElementById('paSenderFilter').value.trim().toLowerCase();
paFilters.content = document.getElementById('paContentFilter').value.trim().toLowerCase();
document.getElementById('paClearFiltersBtn').classList.toggle('d-none', !paFiltersActive());
+3 -2
View File
@@ -367,8 +367,9 @@
<option value="2">2-byte</option>
<option value="3">3-byte</option>
</select>
<input id="paTokenFilter" type="text" class="form-control form-control-sm" style="width: 9.5rem;"
placeholder="Repeater hash (3B)" title="Match a repeater hash anywhere in a path (prefix match)">
<input id="paTokenFilter" type="text" class="form-control form-control-sm" style="width: 10.5rem;"
placeholder="Repeater (3B or name)"
title="Match a repeater anywhere in a path: hash prefix (hex) or contact name">
<input id="paSenderFilter" type="text" class="form-control form-control-sm" style="width: 9.5rem;"
placeholder="Sender" title="Filter by sender name">
<input id="paContentFilter" type="text" class="form-control form-control-sm" style="width: 11rem;"