fix: auto-apply new web builds; DP missing in DM but shown in group (batch79)
Auto-update (no manual step, no version confusion): - New code now applies ITSELF. The client polls /api/build and, as soon as it's SAFE, silently hard-reloads onto the new build. Safe = not in a call, no live screen session, no dialog open, nothing half-typed; if the user is busy we wait and apply the moment they're free. A brief "Updated to the latest version" toast confirms it. - Removed the "web build" row from Settings: users must never have to reason about an app version vs a web build. The only version surfaced is the desktop app's (auto-updater). DP bug: a contact showed their photo in a GROUP but fell back to initials in the 1:1. Two causes, both handled: - Duplicate rows for one person (signed in by email once and by mobile another time before the bizgaze_user_id merge landed) — only one row carries the DP, and the group happened to reference the row WITH the photo. avatarsFor() now keys rows by stable person identity (bizgaze person id → email → name) so a photo-less row borrows its twin's photo. Applied to contacts, conversations, group members and group info. - A DM whose counterparty was merged away is now keyed by the SURVIVING account, so the row carries that account's name/photo/presence (and split threads collapse into one). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+39
-16
@@ -1002,7 +1002,7 @@
|
||||
<body>
|
||||
<script src="/icons.js?v=6"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@twemoji/api@15.1.0/dist/twemoji.min.js" crossorigin="anonymous"></script>
|
||||
<script>window.__BUILD='2026-07-14-batch78';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||
<script>window.__BUILD='2026-07-14-batch79';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||
// Render modern (Twemoji) emojis in place of the OS's flat ones. No-op if the CDN didn't load
|
||||
// (emojis stay as plain Unicode). (#5)
|
||||
function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, { folder:'svg', ext:'.svg' }); }catch(_){} }</script>
|
||||
@@ -1225,13 +1225,13 @@ function openSettings(){
|
||||
+sw('setDm','Direct message notifications', notifOn('dm'))
|
||||
+'<label class="gi-setting"><span>Notifications <span class="perm-state '+perm+'">'+permLabel+'</span><div style="font-size:.72rem;color:var(--muted);font-weight:400;margin-top:.15rem">Pop up messages & calls even when this tab isn’t open</div></span>'+(granted?'':'<button class="btn sm" id="setPerm">'+(perm==='denied'?'Allow':'Enable')+'</button>')+'</label>'
|
||||
+(window.bizConnectNative?('<label class="gi-setting"><span>Biz Connect for Desktop<div style="font-size:.72rem;color:var(--muted);font-weight:400;margin-top:.15rem" id="setVer">Version '+pEsc(window.bizConnectNative.version||'—')+(_pendingUpdate?('<span class="set-ver-i" title="Update available">i</span> '+(_pendingUpdate.phase==='ready'?'Update ready':'Update '+pEsc(_pendingUpdate.version||'')+' available')):'')+'</div></span><button class="btn sm'+(_pendingUpdate?' has-update':'')+'" id="setUpd">'+(_pendingUpdate?(_pendingUpdate.phase==='ready'?'Restart now':'Downloading…'):'Check for updates')+'</button></label>'):'')
|
||||
// Always-available escape hatch: force the newest web build (clears cache / service worker).
|
||||
+'<label class="gi-setting"><span>App version<div style="font-size:.72rem;color:var(--muted);font-weight:400;margin-top:.15rem">Build '+pEsc(window.__BUILD||'—')+(_newBuild?' <span class="set-ver-i" title="A newer build is available">i</span> update available':'')+'</div></span><button class="btn sm'+(_newBuild?' has-update':'')+'" id="setRefresh">Refresh app</button></label>'
|
||||
// NOTE: deliberately NO "web build" row here — users must not have to reason about an app version vs a
|
||||
// web build. New code applies itself (see checkWebBuild/scheduleAutoRefresh); the only version shown is
|
||||
// the desktop app's, which is what the auto-updater manages.
|
||||
+'<div class="hint" style="margin-top:.4rem">These preferences are saved on this device.</div></div>';
|
||||
document.body.appendChild(ov);
|
||||
ov.onclick=e=>{ if(e.target===ov) ov.remove(); };
|
||||
ov.querySelector('#setClose').onclick=()=>ov.remove();
|
||||
{ const rb=ov.querySelector('#setRefresh'); if(rb) rb.onclick=()=>{ ov.remove(); hardReloadApp(); }; }
|
||||
const updBtn=ov.querySelector('#setUpd'); // #12: desktop version + manual update check
|
||||
if(updBtn) updBtn.onclick=async()=>{
|
||||
if(_pendingUpdate&&_pendingUpdate.phase==='ready'){ try{ window.bizConnectNative.restartToUpdate&&window.bizConnectNative.restartToUpdate(); }catch(_){} return; } // update already downloaded → restart
|
||||
@@ -2782,34 +2782,57 @@ function wireUpdateBanner(){
|
||||
// The desktop app closes to TRAY, so it can stay open for weeks and keep running the page it loaded on
|
||||
// day one — a deploy would never reach it (that's why fixes "worked on mobile but not on desktop").
|
||||
// Poll the server's build marker; when it changes, offer a Refresh. Also exposed in Settings.
|
||||
let _newBuild=null;
|
||||
// The user should NEVER have to think about "web build" vs "app version" — a new deploy applies ITSELF.
|
||||
// We poll the server's build marker and, as soon as it's SAFE, silently reload onto the new code. Safe =
|
||||
// not in a call, no live screen session, nothing half-typed, no dialog open. If the user is busy we simply
|
||||
// keep waiting and apply it the moment they're free (or when the window is next hidden).
|
||||
let _newBuild=null, _autoRefreshTimer=null;
|
||||
async function checkWebBuild(){
|
||||
try{
|
||||
const r=await fetch('/api/build',{cache:'no-store'}); if(!r.ok) return;
|
||||
const d=await r.json();
|
||||
if(d && d.build && window.__BUILD && d.build!==window.__BUILD && d.build!==_newBuild){ _newBuild=d.build; showRefreshBanner(); }
|
||||
if(d && d.build && window.__BUILD && d.build!==window.__BUILD){
|
||||
if(_newBuild!==d.build){ _newBuild=d.build; console.log('[build] new version on server:', d.build, '(running', window.__BUILD+')'); }
|
||||
scheduleAutoRefresh();
|
||||
}
|
||||
}catch(_){}
|
||||
}
|
||||
function showRefreshBanner(){
|
||||
if(document.getElementById('refreshBanner')) return;
|
||||
const el=document.createElement('div'); el.id='refreshBanner'; el.className='upd-banner show ready';
|
||||
el.innerHTML=ic('download',15)+' <span>A new version of Biz Connect is available</span> <button id="rbGo">Refresh</button>';
|
||||
document.body.appendChild(el);
|
||||
el.querySelector('#rbGo').onclick=()=>hardReloadApp();
|
||||
// Never yank the page out from under someone mid-task.
|
||||
function safeToRefresh(){
|
||||
try{
|
||||
if(typeof meetState!=='undefined' && meetState==='call') return false; // in a meeting/call
|
||||
if(document.querySelector('.railbtn.live')) return false; // live share/connect session
|
||||
if(document.querySelector('.modal-ov, .call-invite, .guest-prejoin, .lightbox')) return false; // a dialog is open
|
||||
const inp=document.getElementById('msgInput'); if(inp && inp.value.trim()) return false; // half-typed message
|
||||
const mc=document.getElementById('mcInput'); if(mc && mc.value.trim()) return false;
|
||||
}catch(_){}
|
||||
return true;
|
||||
}
|
||||
function scheduleAutoRefresh(){
|
||||
if(_autoRefreshTimer) return;
|
||||
const tryNow=()=>{
|
||||
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();
|
||||
};
|
||||
_autoRefreshTimer=setInterval(tryNow, 15000);
|
||||
setTimeout(tryNow, 1200); // usually applies right away
|
||||
}
|
||||
// Hard refresh: on desktop this clears the shell's HTTP cache and reloads ignoring cache. In a browser we
|
||||
// also drop any service-worker + CacheStorage entries, then reload — so there's no stale-code dead end.
|
||||
async function hardReloadApp(){
|
||||
if(meetState==='call' && !(await bzConfirm('You are in a call. Refreshing will leave it.', {title:'Refresh now?', okText:'Refresh'}))) return;
|
||||
try{ const n=window.bizConnectNative; if(n && n.hardReload){ n.hardReload(); return; } }catch(_){}
|
||||
try{ if('serviceWorker' in navigator){ const rs=await navigator.serviceWorker.getRegistrations(); await Promise.all(rs.map(r=>r.unregister().catch(()=>{}))); } }catch(_){}
|
||||
try{ if(window.caches && caches.keys){ const ks=await caches.keys(); await Promise.all(ks.map(k=>caches.delete(k).catch(()=>{}))); } }catch(_){}
|
||||
try{ location.replace(location.pathname+'?_r='+Date.now()); }catch(_){ location.reload(); }
|
||||
try{ location.replace(location.pathname+location.search); }catch(_){ location.reload(); }
|
||||
}
|
||||
setInterval(checkWebBuild, 10*60*1000); // every 10 min
|
||||
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, 4000); // shortly after boot
|
||||
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(_){}
|
||||
// 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());
|
||||
|
||||
Reference in New Issue
Block a user