From 462a167438937e2603cc57307fcc2d4c2eafccda Mon Sep 17 00:00:00 2001 From: sravan Date: Thu, 2 Jul 2026 12:05:32 +0530 Subject: [PATCH] =?UTF-8?q?feat(chat):=20self-chat=20'You'=20=E2=80=94=20n?= =?UTF-8?q?ote-to-self=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- server/public/home.html | 23 ++++++++++++++++------- server/routes.js | 6 +++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/server/public/home.html b/server/public/home.html index 497e4e5..883abed 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -733,7 +733,7 @@ - +
Loading…
@@ -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?''+(it.members>99?'99+':it.members)+'':'') + const corner=it.self?'' + :isG?(it.members?''+(it.members>99?'99+':it.members)+'':'') :''; 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?'
'+ic('star',12)+' Favourites
'+favs.map(rowHTML).join('')+(rest.length?'
All chats
':''):'')+rest.map(rowHTML).join('')) : '
No conversations yet.
'; + 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+='
'+ic('star',12)+' Favourites
'+favs.map(rowHTML).join(''); if(rest.length) h+='
All chats
'; } + h+=rest.map(rowHTML).join(''); + html = (self||others.length) ? h : '
No conversations yet.
'; } else { html = rows.length ? rows.map(rowHTML).join('') : '
No chats match “'+pEsc(filter)+'”.
'; } @@ -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 '
' + '
' + '' diff --git a/server/routes.js b/server/routes.js index 3097ac7..9f8d3c6 100644 --- a/server/routes.js +++ b/server/routes.js @@ -1234,9 +1234,9 @@ route('POST', '/api/messages', async (req, res) => { const dto = buildMsgDTO(R.messages.byId(id), namesFor(u.team_id), u.id); const push = { type: 'chat-message', message: { ...dto, fromName: u.name || u.email } }; try { CHAT.pushToUser(to, push); } catch (_) {} - try { CHAT.pushToUser(u.id, push); } catch (_) {} // sync the sender's other devices - // Background/closed-tab push to the recipient (opens the DM with this sender). - PUSH.sendToUser(to, { title: (u.name || u.email), body: (text ? (text.length > 80 ? text.slice(0, 80) + '…' : text) : '📎 Attachment'), kind: 'dm', id: u.id, tag: 'dm:' + u.id }); + if (to !== u.id) try { CHAT.pushToUser(u.id, push); } catch (_) {} // sync the sender's other devices (skip for self-notes) + // Background/closed-tab push to the recipient (opens the DM). Not for a note-to-self. + if (to !== u.id) PUSH.sendToUser(to, { title: (u.name || u.email), body: (text ? (text.length > 80 ? text.slice(0, 80) + '…' : text) : '📎 Attachment'), kind: 'dm', id: u.id, tag: 'dm:' + u.id }); json(res, 200, dto); });