From da0a744caa717bf4b0ca1e431d093be240dda611 Mon Sep 17 00:00:00 2001 From: Mojtaba Chenani Date: Wed, 12 Aug 2026 23:40:06 +0200 Subject: [PATCH] feat: attribute chat-cli group messages to their sender --- bin/chat-cli/src/app.rs | 34 ++++++++++++++++++++++++++++++---- bin/chat-cli/src/ui.rs | 13 +++++++++++-- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/bin/chat-cli/src/app.rs b/bin/chat-cli/src/app.rs index 1d5cf9c..a04e49f 100644 --- a/bin/chat-cli/src/app.rs +++ b/bin/chat-cli/src/app.rs @@ -7,7 +7,7 @@ use arboard::Clipboard; use crossbeam_channel::Receiver; use logos_chat::{ AccountDirectory, ChatClient, ChatStore, ConversationClass, Event, GroupMetadata, - RegistrationService, Transport, + MessageSender, RegistrationService, Transport, }; use serde::{Deserialize, Serialize}; @@ -18,6 +18,21 @@ pub struct DisplayMessage { pub from_self: bool, pub content: String, pub timestamp: u64, + /// Short label of the sender for an incoming message (`None` for our own + /// messages and system output). Used to attribute messages in groups. + #[serde(default)] + pub sender: Option, +} + +/// Short display label for a message's sender: the account (or device) id, +/// truncated. Friendly naming (contacts/aliases) is a later phase. +fn sender_label(sender: &MessageSender) -> String { + let id = sender + .account + .as_ref() + .map(|a| a.as_str()) + .unwrap_or_else(|| sender.local_identity.as_str()); + id[..8.min(id.len())].to_string() } /// Which kind of MLS conversation this is. `Dm` is a DirectV1 1:1 — no members @@ -222,9 +237,12 @@ where self.start_session(chat_id, kind, None); } Event::MessageReceived { - convo_id, content, .. + convo_id, + content, + sender, } => { let chat_id = convo_id.to_string(); + let label = sender_label(&sender); let Some(session) = self.state.chats.get_mut(&chat_id) else { return; }; @@ -232,6 +250,7 @@ where from_self: false, content: String::from_utf8_lossy(&content).into_owned(), timestamp: now(), + sender: Some(label), }); } Event::ConversationMembersChanged { convo_id } => { @@ -263,6 +282,7 @@ where from_self: true, content: content.to_string(), timestamp: now(), + sender: None, }); } self.save_state()?; @@ -275,6 +295,7 @@ where from_self: true, content: content.to_string(), timestamp: now(), + sender: None, }); } @@ -350,7 +371,10 @@ where let msg = if members.is_empty() { format!("Group created ({label}).") } else { - format!("Group created ({label}); {} invite(s) pending.", members.len()) + format!( + "Group created ({label}); {} invite(s) pending.", + members.len() + ) }; self.status = msg.clone(); Ok(Some(msg)) @@ -451,7 +475,9 @@ where "/chats" => { let sessions: Vec<_> = self.state.chats.values().cloned().collect(); if sessions.is_empty() { - Ok(Some("No chats yet. Use /dm or /new to start one.".to_string())) + Ok(Some( + "No chats yet. Use /dm or /new to start one.".to_string(), + )) } else { self.add_system_message(&format!("── Your Chats ({}) ──", sessions.len())); for s in &sessions { diff --git a/bin/chat-cli/src/ui.rs b/bin/chat-cli/src/ui.rs index 2c5387c..f0ab65b 100644 --- a/bin/chat-cli/src/ui.rs +++ b/bin/chat-cli/src/ui.rs @@ -98,6 +98,12 @@ where .current_session() .map(|s| s.display_name()) .unwrap_or("Them"); + // In a group every incoming message is attributed to its own sender; in a + // DM the single peer's label is enough. + let is_group = matches!( + app.current_session().map(|s| s.kind), + Some(crate::app::ChatKind::Group) + ); // Inner width: area minus borders (2) for wrapping long content. let inner_width = area.width.saturating_sub(2) as usize; @@ -107,9 +113,12 @@ where .iter() .flat_map(|msg| { let (prefix, style) = if msg.from_self { - ("You", Style::default().fg(Color::Green)) + ("You".to_string(), Style::default().fg(Color::Green)) + } else if is_group { + let label = msg.sender.as_deref().unwrap_or(remote_name); + (label.to_string(), Style::default().fg(Color::Yellow)) } else { - (remote_name, Style::default().fg(Color::Yellow)) + (remote_name.to_string(), Style::default().fg(Color::Yellow)) }; let prefix_str = format!("{}: ", prefix);