From cb3cbd8827363b2c6d25e3c6ab452e61824eda1b Mon Sep 17 00:00:00 2001 From: byGalax Date: Wed, 13 May 2026 22:20:21 +0200 Subject: [PATCH] feat(chat): show caller's name on call event pills in groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In group conversations the "Outgoing/Incoming/Missed call" system pill gave no clue WHO triggered the event — fine in a 1:1 where the only two players are obvious, useless in a group with three+ members. Discord puts the caller's name in the pill; mirror that. CallEventRow now takes a senderDisplayName prop (plumbed through from MessageBubble) and switches non-own labels to the name-aware variants: * ended + !mine + name → "{name} hat einen Anruf gestartet" * missed + !mine + name → "Verpasster Anruf von {name}" * declined + !mine + name → "Anruf von {name} abgelehnt" Own events (mine) stay generic ("Outgoing call" / "No answer") since the user already knows they were the initiator. Fallback path without a name keeps the previous generic labels so nothing regresses if the sender is unresolvable. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/desktop/src/components/MessageBubble.tsx | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/components/MessageBubble.tsx b/apps/desktop/src/components/MessageBubble.tsx index 2557f3d..4862c75 100644 --- a/apps/desktop/src/components/MessageBubble.tsx +++ b/apps/desktop/src/components/MessageBubble.tsx @@ -294,7 +294,14 @@ export function MessageBubble({ } if (parsed.kind === 'call_event') { - return ; + return ( + + ); } return ( @@ -866,10 +873,15 @@ function CallEventRow({ parsed, mine, time, + senderDisplayName, }: { parsed: { status: string; mediaKind: string; durationSec: number }; mine: boolean; time: string; + /** Discord-parity: in group chats the system pill should say WHO + * started/missed the call. Null means we don't know (fall back to the + * legacy generic labels). */ + senderDisplayName: string | null; }) { const { t } = useTranslation(['app']); const status = parsed.status; @@ -880,16 +892,36 @@ function CallEventRow({ ? 'border-rose-500/30 bg-rose-500/10 text-rose-700 dark:text-rose-200' : 'border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-200'; + // For non-own events we prefer the name-aware label so group chats + // make it clear who triggered the call event. Own events stay generic + // ("Outgoing call" / "No answer") since the user already knows they + // were the initiator. + const hasName = !mine && !!senderDisplayName; const label = status === 'ended' ? mine ? t('app:chats.call_outgoing', { defaultValue: 'Outgoing call' }) - : t('app:chats.call_incoming', { defaultValue: 'Incoming call' }) + : hasName + ? t('app:chats.call_started_by', { + name: senderDisplayName, + defaultValue: '{{name}} hat einen Anruf gestartet', + }) + : t('app:chats.call_incoming', { defaultValue: 'Incoming call' }) : status === 'missed' ? mine ? t('app:chats.call_no_answer', { defaultValue: 'No answer' }) - : t('app:chats.call_missed', { defaultValue: 'Missed call' }) - : t('app:chats.call_declined', { defaultValue: 'Call declined' }); + : hasName + ? t('app:chats.call_missed_by', { + name: senderDisplayName, + defaultValue: 'Verpasster Anruf von {{name}}', + }) + : t('app:chats.call_missed', { defaultValue: 'Missed call' }) + : hasName + ? t('app:chats.call_declined_by', { + name: senderDisplayName, + defaultValue: 'Anruf von {{name}} abgelehnt', + }) + : t('app:chats.call_declined', { defaultValue: 'Call declined' }); const duration = parsed.durationSec > 0 ? formatDuration(parsed.durationSec) : null;