fix: DP missing in 1:1 for un-messaged contacts; make web updates fully silent (batch80)

DP bug — real cause found (client-side, not the DB):
loadSidebar builds a DM row for every contact you haven't messaged yet, but it copied only
{name, online} from the contact and DROPPED `avatar` (and status/email). So an un-messaged
contact always rendered initials in the 1:1, while the SAME person showed their photo in a
group (which reads /api/groups/members). Carry the whole contact through.
Kept a server-side safety net: avatarsFor() now indexes known photos under person-id, email
AND name, so a duplicate row missing a photo can match on any of them (the previous single
composite key missed twins with different emails).

Web updates are now completely silent: no banner, no toast. A web build is an implementation
detail — surfacing it makes users reason about "web build vs app version", which is exactly
the confusion to avoid. New code simply applies itself as soon as it's safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 16:06:28 +05:30
parent 2aeeb0a096
commit c1f01e796a
2 changed files with 26 additions and 10 deletions
+18 -5
View File
@@ -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.