fix(chat): stop echo downgrading delivered/read tick (#6); set self-chat apart (#3)

- #6: the server echoes your own sent message back over the socket; onChatMessage reset
  last_status='sent', overwriting a delivered/read that arrived first → the chat-list tick
  flipped back to single. Track last_msg_id and never downgrade the same message's tick.
- #3: pin the 'You' note-to-self chat in its own slot — tinted row + 'Note to self' tag +
  a divider separating it from the conversation list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 16:17:30 +05:30
parent 515411fc83
commit d515a562a0
+14 -5
View File
@@ -110,6 +110,10 @@
.chatcol{width:312px;flex:0 0 312px;background:var(--card);border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0;}
.chatcol.hidden{display:none;}
.side-sec{font-size:.68rem;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);font-weight:700;padding:.6rem .9rem .25rem;}
/* Pinned "You" (note-to-self) set apart from the conversation list (#3): tinted + a divider. */
.side-div{height:1px;background:var(--line);margin:.4rem .7rem .3rem;}
.chat-row.self{background:var(--blue-soft);}
.chat-row.self .chat-name::after{content:"Note to self";font-size:.58rem;font-weight:700;color:var(--blue);background:#fff;border:1px solid var(--line);border-radius:99px;padding:.03rem .35rem;margin-left:.4rem;vertical-align:middle;text-transform:none;}
.contact-row,.dir-row{display:flex;align-items:center;gap:.6rem;padding:.5rem .9rem;cursor:pointer;}
.contact-row:hover,.dir-row:hover{background:var(--blue-soft);}
.contact-row .cr-name,.dir-row .cr-name{font-size:.9rem;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
@@ -753,7 +757,7 @@
<body>
<script src="/icons.js?v=4"></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-02-batch32';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
<script>window.__BUILD='2026-07-02-batch33';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>
@@ -1089,7 +1093,7 @@ function avatarHTML(it, big){
}
function rowHTML(it){
const active=selected&&selected.kind===it.kind&&selected.id===it.id;
const cls=['chat-row']; if(active)cls.push('active'); if(it.unread>0)cls.push('unread');
const cls=['chat-row']; if(active)cls.push('active'); if(it.unread>0)cls.push('unread'); if(it.self)cls.push('self');
const isG=it.kind==='group';
const draft=getDraft(it.kind,it.id);
let inner;
@@ -1111,7 +1115,8 @@ function renderChats(filter){
if(!q){ // "You" pinned at top, then favourites, then the rest
const self=rows.find(r=>r.self), others=rows.filter(r=>!r.self);
const favs=others.filter(r=>r.favorite), rest=others.filter(r=>!r.favorite);
let h = self ? rowHTML(self) : '';
// "You" (note to self) sits in its own pinned slot, set apart from the conversation list.
let h = self ? (rowHTML(self)+(others.length?'<div class="side-div"></div>':'')) : '';
if(favs.length){ h+='<div class="side-sec">'+ic('star',12)+' Favourites</div>'+favs.map(rowHTML).join(''); if(rest.length) h+='<div class="side-sec">All chats</div>'; }
h+=rest.map(rowHTML).join('');
html = (self||others.length) ? h : '<div class="no-results">No conversations yet.</div>';
@@ -1866,7 +1871,7 @@ async function sendMessage(){
clearReply(); pendingAttach=null; hideAttach();
{ const ck=selected.kind+':'+selected.id; if(THREAD_CACHE.has(ck)){ const a=THREAD_CACHE.get(ck); if(!a.some(x=>x.id===m.id)) a.push(m); } }
const it=rowFor(selected.kind,selected.id);
if(it){ it.last_body=m.body||(m.attachment?'📎 '+(m.attachment.name||'Attachment'):''); it.last_at=m.created_at; it.last_from_me=true; it.last_status='sent'; it.unread=0; }
if(it){ it.last_body=m.body||(m.attachment?'📎 '+(m.attachment.name||'Attachment'):''); it.last_at=m.created_at; it.last_from_me=true; it.last_status='sent'; it.last_msg_id=m.id; it.unread=0; }
renderChats(searchVal());
}catch(e){ inp.value=text; toast(e.message||'Could not send'); }
}
@@ -2081,7 +2086,11 @@ function onChatMessage(m){
else if(kind==='dm' && wasNew) addNotif({icon:'chat', text:'New chat from '+pEsc(m.fromName||'someone'), link:{kind:'dm', id:rid}});
}
if(it){
it.last_body=isSys?m.body:(m.body||(m.attachment?'📎 '+(m.attachment.name||'Attachment'):'')); it.last_at=m.created_at; it.last_from_me=(m.from===ME.id); it.last_status=(m.from===ME.id)?'sent':null;
it.last_body=isSys?m.body:(m.body||(m.attachment?'📎 '+(m.attachment.name||'Attachment'):'')); it.last_at=m.created_at; it.last_from_me=(m.from===ME.id);
// #6: the server echoes my own sent message back — don't let that echo DOWNGRADE a delivered/read
// tick that already arrived for this same message (it would flip the list back to a single tick).
if(m.from===ME.id){ if(!(it.last_msg_id===m.id && (it.last_status==='delivered'||it.last_status==='read'))) it.last_status='sent'; it.last_msg_id=m.id; }
else it.last_status=null;
if(m.from!==ME.id && !isOpen && !isSys) it.unread=(it.unread||0)+1;
}
if(isOpen){