aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/recipient-search.js15
1 files changed, 12 insertions, 3 deletions
diff --git a/src/recipient-search.js b/src/recipient-search.js
index 5909078..4853c66 100644
--- a/src/recipient-search.js
+++ b/src/recipient-search.js
@@ -132,7 +132,8 @@ onCacheReady(loadSearchProfiles);
try {
const now = Date.now();
for (const [q, e] of Object.entries(JSON.parse(localStorage.getItem(QUERY_KEY) || '{}')))
- if (e && Array.isArray(e.rows) && now - (e.t || 0) < QUERY_TTL) { queryCache.set(q, e.rows); queryAt.set(q, e.t); }
+ // cached rows are final: their profile lookup already finished
+ if (e && Array.isArray(e.rows) && now - (e.t || 0) < QUERY_TTL) { queryCache.set(q, e.rows.map((r) => ({ ...r, known: true }))); queryAt.set(q, e.t); }
} catch {}
let persistTimer = null;
function persistCaches() {
@@ -199,11 +200,13 @@ export async function searchRecipients(qRaw, onPartial = null) {
// Within a source, a name that actually contains the query outranks the
// search relay's looser relevance matches.
const score = (r) => ((r.name || '').toLowerCase().replace(/\s+/g, ' ').includes(q) || (r.address || r.nip05 || '').toLowerCase().includes(q)) ? 0 : 1;
+ // A row is `known` once its profile has been looked up (or arrived with the
+ // search itself): only then does a missing picture mean "has none".
const snapshot = () => {
const rows = [...out.values()].sort((a, b) => a.pri - b.pri || score(a) - score(b)).slice(0, MAX_RESULTS);
for (const r of rows) {
const p = profileCache.get(r.pk);
- if (p) { r.picture ||= p.picture; r.name ||= p.name; }
+ if (p) { r.picture ||= p.picture; r.name ||= p.name; r.known = true; }
}
return rows;
};
@@ -239,6 +242,7 @@ export async function searchRecipients(qRaw, onPartial = null) {
const rows = snapshot();
await fillProfiles(rows).catch(() => {});
+ for (const r of rows) r.known = true; // looked: no picture now means none
if (queryCache.size > 200) { queryCache.clear(); queryAt.clear(); }
queryCache.set(q, rows);
queryAt.set(q, Date.now());
@@ -281,7 +285,12 @@ export function resultRows(h, rows, onPick, wrapAvatar) {
// background, not <img>: paints synchronously from cache, so rows
// re-rendered per keystroke don't flash their avatars
? h('div', { class: 'chat-avatar ava-img', style: `background-image:url(${JSON.stringify(r.picture)})` })
- : fallbackAvatar(h, r.pk, r.name, 'chat-avatar');
+ // A registrar hit lands before its profile does. Punk art means "has
+ // no picture", never "picture not here yet" — a new account that
+ // picked another punk or uploaded a photo would flash the wrong face.
+ : !r.known
+ ? h('div', { class: 'chat-avatar fallback loading' })
+ : fallbackAvatar(h, r.pk, r.name, 'chat-avatar');
return h('div', { class: 'item chat-thread-row', onClick: () => onPick(r) },
(wrapAvatar && wrapAvatar(r.pk, ava)) || ava,
h('div', { class: 'col grow', style: 'min-width:0;gap:1px' },