#8 root cause: listByTenant omitted last_seen/status → contacts/conversations sent lastSeen:null

repos.listByTenant selected id,email,name,role,active,avatar_url,created_at but NOT
last_seen or status. So /api/messages/contacts and /api/messages/conversations
always sent lastSeen:null (and status:'active'). Last-seen only ever appeared via
LIVE presence events (broadcastPresence reads the full row) — which is why it
"worked on desktop" (caught live), not on a fresh iOS load, and why round-2's
loadSidebar-on-focus then clobbered the live value → "Offline for all". Verified
locally: contacts now returns the real lastSeen timestamp; db-smoke 22/22.

Also lightened refreshPresenceOnResume: reconnect the socket if it's dead (its
onopen already resyncs the sidebar) but no longer force an unconditional
loadSidebar on every focus — that churn caused the #8 regression and could
momentarily reset an unread badge (#3). Session sliding (touchSession) stays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 17:28:39 +05:30
parent a677675b8f
commit c3c4178227
2 changed files with 11 additions and 3 deletions
+7 -2
View File
@@ -4537,8 +4537,13 @@ let _chatReconnectT=null;
let _lastPresenceRefresh=0;
function refreshPresenceOnResume(){
try{
if(!chatWs || chatWs.readyState>1){ connectChatWs(); } // dead/closed → force reconnect (skip if OPEN/CONNECTING)
const t=Date.now(); if(t-_lastPresenceRefresh>4000){ _lastPresenceRefresh=t; try{ loadSidebar(); }catch(_){} try{ touchSession(); }catch(_){} } // throttle so rapid tab switches don't spam; also slide the session forward
// If the socket is dead/closed, reconnect — its onopen runs resyncChat → loadSidebar, which refreshes
// presence AND unread from the server. We deliberately do NOT force a loadSidebar when the socket is fine:
// the initial payload now carries last-seen (repos.listByTenant fix) and live presence events keep it
// current, so an unconditional focus-refresh only risked clobbering fresh state (it caused the #8 "Offline
// for all" regression and could momentarily reset an unread badge).
if(!chatWs || chatWs.readyState>1){ connectChatWs(); }
const t=Date.now(); if(t-_lastPresenceRefresh>4000){ _lastPresenceRefresh=t; try{ touchSession(); }catch(_){} } // slide the session forward (throttled)
}catch(_){}
}
function connectChatWs(){