feat(chat): self-chat 'You' — note-to-self (#4)

Pinned 'You' chat at the top of the list (always available); messaging yourself works
(to===me), with no self push/echo notification and a 'Message yourself' header. Self is
filtered from the normal contacts and shows no status dot. build batch24.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 12:05:32 +05:30
parent 15350c691b
commit 462a167438
2 changed files with 19 additions and 10 deletions
+16 -7
View File
@@ -733,7 +733,7 @@
</head>
<body>
<script src="/icons.js?v=4"></script>
<script>window.__BUILD='2026-07-02-batch23';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);</script>
<script>window.__BUILD='2026-07-02-batch24';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);</script>
<div class="loading" id="loading">Loading…</div>
<header>
@@ -1048,7 +1048,8 @@ function statusLabel(it){ const c=statusCls(it); return c==='incall'?'In a call'
function avatarHTML(it, big){
const isG=it.kind==='group';
const sz=big?'width:38px;height:38px;flex:0 0 38px;':'';
const corner=isG?(it.members?'<span class="mcount">'+(it.members>99?'99+':it.members)+'</span>':'')
const corner=it.self?''
:isG?(it.members?'<span class="mcount">'+(it.members>99?'99+':it.members)+'</span>':'')
:'<span class="dot '+statusCls(it)+'"></span>';
const inner=isG?ic('users',big?20:18):pEsc(initials(it.name));
// Photo overlay (BizGaze profile picture, or an uploaded group image). If it fails to
@@ -1077,9 +1078,13 @@ function renderChats(filter){
const rows=ROWS.filter(it=>!q||it.name.toLowerCase().includes(q)||(it.last_body||'').toLowerCase().includes(q))
.sort((a,b)=>(b.last_at-a.last_at)||a.name.localeCompare(b.name));
let html;
if(!q){ // group favourites at the top when not searching
const favs=rows.filter(r=>r.favorite), rest=rows.filter(r=>!r.favorite);
html = rows.length ? ((favs.length?'<div class="side-sec">'+ic('star',12)+' Favourites</div>'+favs.map(rowHTML).join('')+(rest.length?'<div class="side-sec">All chats</div>':''):'')+rest.map(rowHTML).join('')) : '<div class="no-results">No conversations yet.</div>';
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) : '';
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>';
} else {
html = rows.length ? rows.map(rowHTML).join('') : '<div class="no-results">No chats match “'+pEsc(filter)+'”.</div>';
}
@@ -1137,12 +1142,16 @@ function setDesktopBadge(count){
async function loadSidebar(){
let convos=[], contacts=[];
try{ [convos, contacts]=await Promise.all([ fetch('/api/messages/conversations').then(r=>r.json()), fetch('/api/messages/contacts').then(r=>r.json()) ]); }catch(_){}
CONTACTS=Array.isArray(contacts)?contacts:[];
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 }); }
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).
const selfRow=items.find(i=>i.kind==='dm' && i.id===(ME&&ME.id));
if(selfRow){ selfRow.self=true; selfRow.name='You'; selfRow.avatar=ME.avatarUrl||selfRow.avatar||null; }
else if(ME&&ME.id){ items.push({ kind:'dm', id:ME.id, name:'You', self:true, avatar:ME.avatarUrl||null, online:false, last_body:'', last_at:0, last_from_me:false, unread:0 }); }
ROWS=items;
renderChats(searchVal());
updateRailUnread();
@@ -1163,7 +1172,7 @@ function welcomeHTML(){
function wireWelcome(){ document.querySelectorAll('#chatPanel .wcard').forEach(card=>{ card.onclick=()=>switchTab(card.dataset.go); }); }
function convoShellHTML(it){
const isG=it.kind==='group';
const sub=isG?((it.members||0)+' members'):statusLabel(it);
const sub=it.self?'Message yourself':(isG?((it.members||0)+' members'):statusLabel(it));
return '<div class="convo">'
+ '<div class="convo-head">'
+ '<button class="convo-back" id="convoBack" title="Back (Esc)" aria-label="Back">'+ic('arrowLeft',18)+'</button>'