feat(chat): per-message group read receipts (seen-by + read-by-all ticks)
Thread: every group message I send now carries a receipt tick — sent (1 grey)
-> seen-by-some (2 grey) -> seen-by-all (2 yellow) — computed from the per-message
seenBy the server already returns and kept live by onGroupRead. Tap the tick to see
exactly who ('Seen by X, Y') with an 'N of M' tooltip. Replaces the old last-message-
only 'Seen by' line with a universal, tappable per-message receipt.
Sidebar: the group row tick now reflects real read state (read/delivered/sent) via
memberReads vs member count, instead of a hardcoded 'sent' — and refreshes live when
the open group is read by all.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-2
@@ -717,12 +717,21 @@ route('GET', '/api/messages/conversations', async (req, res) => {
|
||||
const groupItems = R.conversations.listForUser(u.team_id, u.id).map((g) => {
|
||||
const last = R.messages.lastInConversation(g.id);
|
||||
const since = R.conversations.lastReadAt(g.id, u.id);
|
||||
const members = R.conversations.members(g.id);
|
||||
// Group read tick for MY last message: read = every other member has read it, delivered = some
|
||||
// have, else sent. Same three states as DMs, so the sidebar renders them identically.
|
||||
let gStatus = null;
|
||||
if (last && last.sender_id === u.id) {
|
||||
const others = members.filter((id) => id !== u.id).length;
|
||||
const seen = R.conversations.memberReads(g.id).filter((r) => r.user_id !== u.id && r.last_read_at >= last.created_at).length;
|
||||
gStatus = (others > 0 && seen >= others) ? 'read' : (seen > 0 ? 'delivered' : 'sent');
|
||||
}
|
||||
return {
|
||||
kind: 'group', id: g.id, name: g.name || 'Group', members: R.conversations.members(g.id).length, avatar: g.avatar_id ? ('/files/' + g.avatar_id) : null, favorite: favs.has('group:' + g.id),
|
||||
kind: 'group', id: g.id, name: g.name || 'Group', members: members.length, avatar: g.avatar_id ? ('/files/' + g.avatar_id) : null, favorite: favs.has('group:' + g.id),
|
||||
callActive: groupCalls.has(g.id), callRoom: (groupCalls.get(g.id) || {}).room || null,
|
||||
last_body: last ? (last.body || (last.attachment_id ? '📎 Attachment' : '')) : '', last_at: last ? last.created_at : g.created_at,
|
||||
last_from_me: last ? last.sender_id === u.id : false, unread: last ? R.messages.unreadInConversation(g.id, u.id, since) : 0,
|
||||
last_status: (last && last.sender_id === u.id) ? 'sent' : null, // groups: simple sent tick (per-member receipts aren't tracked here)
|
||||
last_status: gStatus,
|
||||
};
|
||||
});
|
||||
json(res, 200, [...dmItems, ...groupItems].sort((a, b) => b.last_at - a.last_at));
|
||||
|
||||
Reference in New Issue
Block a user