diff --git a/server/public/home.html b/server/public/home.html index 8ba787f..e8d484d 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -1002,7 +1002,7 @@ - @@ -1649,7 +1649,10 @@ async function loadSidebar(){ CONTACTS=(Array.isArray(contacts)?contacts:[]).filter(c=>c.id!==(ME&&ME.id)); // self isn't a normal contact (#4) const items=Array.isArray(convos)?convos.slice():[]; const dmIds=new Set(items.filter(i=>i.kind==='dm').map(i=>i.id)); - for(const c of CONTACTS){ if(!dmIds.has(c.id)) items.push({ kind:'dm', id:c.id, name:c.name, online:!!c.online, last_body:'', last_at:0, last_from_me:false, unread:0 }); } + // A contact you haven't messaged YET still needs their profile photo/status: this row was being built + // with only name+online, silently dropping `avatar` — so the same person showed their DP in a group + // (which reads /api/groups/members) but fell back to initials in the 1:1. Carry the whole contact. + for(const c of CONTACTS){ if(!dmIds.has(c.id)) items.push({ kind:'dm', id:c.id, name:c.name, email:c.email||'', avatar:c.avatar||null, online:!!c.online, status:c.status||'active', last_body:'', last_at:0, last_from_me:false, unread:0 }); } const onlineById={}; CONTACTS.forEach(c=>onlineById[c.id]=!!c.online); items.forEach(it=>{ if(it.kind==='dm') it.online=!!onlineById[it.id]; }); // Always-available "You" chat (note to self), pinned to the top (#4). @@ -2814,8 +2817,7 @@ function scheduleAutoRefresh(){ if(!_newBuild) return; if(!safeToRefresh()) return; // busy → try again shortly clearInterval(_autoRefreshTimer); _autoRefreshTimer=null; - try{ sessionStorage.setItem('bzc_autoref','1'); }catch(_){} // so we can confirm after reload - hardReloadApp(); + hardReloadApp(); // silent: no banner, no toast }; _autoRefreshTimer=setInterval(tryNow, 15000); setTimeout(tryNow, 1200); // usually applies right away @@ -2832,7 +2834,8 @@ setInterval(checkWebBuild, 5*60*1000); // every 5 min window.addEventListener('focus', checkWebBuild); // and whenever the user comes back to the app document.addEventListener('visibilitychange', ()=>{ if(!document.hidden) checkWebBuild(); }); setTimeout(checkWebBuild, 3000); // shortly after boot -try{ if(sessionStorage.getItem('bzc_autoref')){ sessionStorage.removeItem('bzc_autoref'); setTimeout(()=>toast('Updated to the latest version'),1200); } }catch(_){} +// Deliberately NO banner and NO toast: a web build is an implementation detail. Surfacing it would make +// users think about "web build vs app version". It just updates. // Small 'i' badge on the profile button when an update is pending; clicking opens Settings. function applyUpdateBadge(){ document.querySelectorAll('.upd-dot').forEach(x=>x.remove()); diff --git a/server/routes.js b/server/routes.js index 78a91a1..b426311 100644 --- a/server/routes.js +++ b/server/routes.js @@ -22,12 +22,25 @@ function namesFor(teamId){ const o = {}; for (const x of R.users.listByTenant(te // person id, else email, else name) and let a photo-less row borrow its twin's photo. function avatarsFor(teamId) { const users = R.users.listByTenant(teamId); - const personKey = (x) => (x.bizgaze_user_id ? 'bz:' + x.bizgaze_user_id - : (x.email ? 'em:' + String(x.email).toLowerCase() : 'nm:' + String(x.name || '').trim().toLowerCase())); - const byPerson = {}; - for (const x of users) { if (x.avatar_url) { const k = personKey(x); if (!byPerson[k]) byPerson[k] = x.avatar_url; } } + const em = (x) => (x.email ? String(x.email).toLowerCase() : ''); + const nm = (x) => String(x.name || '').trim().toLowerCase(); + // Index every KNOWN photo under all three identities, then let a photo-less row match on ANY of them — + // a single composite key missed the common case where the twin rows have different emails. + const byBz = {}, byEmail = {}, byName = {}; + for (const x of users) { + if (!x.avatar_url) continue; + if (x.bizgaze_user_id && !byBz[x.bizgaze_user_id]) byBz[x.bizgaze_user_id] = x.avatar_url; + if (em(x) && !byEmail[em(x)]) byEmail[em(x)] = x.avatar_url; + if (nm(x) && !byName[nm(x)]) byName[nm(x)] = x.avatar_url; + } const out = {}; - for (const x of users) out[x.id] = x.avatar_url || byPerson[personKey(x)] || null; + for (const x of users) { + out[x.id] = x.avatar_url + || (x.bizgaze_user_id && byBz[x.bizgaze_user_id]) + || (em(x) && byEmail[em(x)]) + || (nm(x) && byName[nm(x)]) + || null; + } return out; } // Next future occurrence (same time-of-day) of a weekly-recurring meeting; searches 14 days ahead.