aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-29 09:08:24 -0700
committerAdam Soltys <[email protected]>2026-09-29 09:08:24 -0700
commit9763e3f0b2b9de23231a45274bd534889a7d478c (patch)
tree8f71455e670d30f4b04abd0d5aabcdceb50450b8 /src/features
parentd2b3c05995b0d54cfe586128fd2e958a563ac861 (diff)
downloadcoinosv3-9763e3f0b2b9de23231a45274bd534889a7d478c.tar.gz
coinosv3-9763e3f0b2b9de23231a45274bd534889a7d478c.zip
A face still being looked up shows its punk after a second at most; a picture found later replaces it.
The quiet loading circle is capped at 1 s per face (a timer repaints when it runs out); one miss is enough to remember someone as faceless; and a room's warm-up adds members whose messages arrived after it first ran, instead of reusing a warm-up that covered only the early history. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js42
1 files changed, 29 insertions, 13 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index a86d0a9..00f66b9 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -953,9 +953,9 @@ export function messagesFeature(ctx) {
const prev = profiles.get(pk);
const miss = ((prev && prev.miss) || 0) + 1;
profiles.set(pk, { ...(prev || {}), t: Date.now(), miss });
- // twice running: not a cold relay, they have none (never someone
- // whose face we do know)
- if (miss >= 2 && !(prev && (prev.name || prev.picture))) markFaceless(pk, true);
+ // remembered, so the next load paints their punk at once (never
+ // someone whose face we do know; a face found later replaces it)
+ if (!(prev && (prev.name || prev.picture))) markFaceless(pk, true);
}
scheduleRepaint();
} finally {
@@ -2918,6 +2918,18 @@ export function messagesFeature(ctx) {
return (big ? `url(${JSON.stringify(p.picture)}),` : '') + `url(${JSON.stringify(local)})`;
}
+ const FACE_WAIT_MS = 1000;
+ const faceAsked = new Map(); // pk -> when its face was first wanted this session
+ let faceTimer = null;
+ function faceWaiting(pk) {
+ let at = faceAsked.get(pk);
+ if (at == null) { at = Date.now(); faceAsked.set(pk, at); }
+ const left = at + FACE_WAIT_MS - Date.now();
+ if (left <= 0) return false;
+ // repaint when the wait runs out, so the punk shows without an event
+ if (!faceTimer) faceTimer = setTimeout(() => { faceTimer = null; scheduleRepaint(); }, left + 20);
+ return true;
+ }
const avatar = (pk, cls = 'chat-avatar', clickable = true) => {
const p = profileOf(pk);
// Someone we've never cached used to get an empty circle until a relay
@@ -2931,12 +2943,15 @@ export function messagesFeature(ctx) {
// `loading` is different and still gets the quiet circle: a name lookup
// is in flight for that specific person, so a picture is expected and
// punk art must not flash in front of it.
+ // A face still being looked up gets a quiet circle for a second at
+ // most, then its punk — a picture found later replaces it.
+ const waiting = (p === null || (p && p.loading && !p.picture)) && faceWaiting(pk);
const node = feedPaint && !p.picture
? h('div', { class: cls + ' fallback' }, (p.name || npubOf(pk) || '??').slice(0, 2))
- : p && p.loading && !p.picture
- ? h('div', { class: cls + ' fallback loading' })
- : p === null && (ui.noteThread || (ui.chatOpen && ui.msgView === 'room'))
+ : waiting && (p || ui.noteThread || (ui.chatOpen && ui.msgView === 'room'))
? h('div', { class: cls + ' fallback loading' })
+ : p && p.loading && !p.picture
+ ? fallbackAvatar(h, pk, p.name, cls)
: p === null
? fallbackAvatar(h, pk, null, cls)
: p.picture
@@ -8743,15 +8758,16 @@ export function messagesFeature(ctx) {
.sort((a, b) => eventMs(b.rumor) - eventMs(a.rumor));
return [...new Set(msgs.map((m) => m.author))].slice(0, 150);
}
+ // Each face is warmed once per half minute; a later call (the history
+ // still streaming in when the list first warmed) adds whoever is new.
function warmRoom(jm) {
- const w = roomWarm.get(jm.community_id);
- if (w && Date.now() - w.at < 30_000) return w.promise;
+ let w = roomWarm.get(jm.community_id);
+ if (!w || Date.now() - w.at > 30_000) { w = { at: Date.now(), faces: new Map() }; roomWarm.set(jm.community_id, w); }
const deadline = Date.now() + 8000;
- let promise;
- try { promise = Promise.all(roomFaces(jm).map((pk) => warmAvatar(pk, deadline).catch(() => {}))); }
- catch { promise = Promise.resolve(); }
- roomWarm.set(jm.community_id, { at: Date.now(), promise });
- return promise;
+ try {
+ for (const pk of roomFaces(jm)) if (!w.faces.has(pk)) w.faces.set(pk, warmAvatar(pk, deadline).catch(() => {}));
+ } catch {}
+ return Promise.all(w.faces.values());
}
async function openRoomWarm(jm, go) {
const seq = ++roomOpenSeq;