From f79575ffa7e762f29c893957858f66b754704726 Mon Sep 17 00:00:00 2001
From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com>
Date: Mon, 2 Mar 2026 01:16:58 +0100
Subject: [PATCH] fix: consistent avatar gradient colors across all 17 call
sites
Avatar fallback gradients were hashed by display name alone when no
user prop was passed, causing the same person to appear in different
colors across messages, replies, member lists, voice panels, etc.
Added userId prop to Avatar and wired it through all 17 call sites
so the gradient always hashes by user ID.
---
packages/web/src/components/chat/FriendsPage.tsx | 4 ++--
packages/web/src/components/chat/MentionPopover.tsx | 1 +
packages/web/src/components/chat/Message.tsx | 2 +-
packages/web/src/components/chat/MessageList.tsx | 2 +-
packages/web/src/components/layout/ActivityPanel.tsx | 1 +
packages/web/src/components/layout/ChannelSidebar.tsx | 4 ++--
packages/web/src/components/layout/MemberSidebar.tsx | 1 +
packages/web/src/components/modals/AddDmMemberModal.tsx | 2 +-
packages/web/src/components/modals/NewDmModal.tsx | 2 +-
packages/web/src/components/modals/ServerSettings.tsx | 1 +
packages/web/src/components/modals/UserSettings.tsx | 1 +
packages/web/src/components/ui/Avatar.tsx | 5 +++--
packages/web/src/components/voice/PictureInPicture.tsx | 1 +
packages/web/src/components/voice/StreamTile.tsx | 2 +-
packages/web/src/components/voice/VoiceChannel.tsx | 1 +
packages/web/src/components/voice/VoiceUser.tsx | 1 +
16 files changed, 20 insertions(+), 11 deletions(-)
diff --git a/packages/web/src/components/chat/FriendsPage.tsx b/packages/web/src/components/chat/FriendsPage.tsx
index 2b3cd0eb..8e5be27e 100644
--- a/packages/web/src/components/chat/FriendsPage.tsx
+++ b/packages/web/src/components/chat/FriendsPage.tsx
@@ -231,7 +231,7 @@ function FriendItem({ friend, onRemove, onDm }: { friend: Friend, onRemove: () =
return (
-
+
{friend.displayName ?? friend.username}
@@ -277,7 +277,7 @@ function RequestItem({ request, type, onAccept, onDecline, onCancel }: {
return (
-
+
{user.displayName ?? user.username}
diff --git a/packages/web/src/components/chat/MentionPopover.tsx b/packages/web/src/components/chat/MentionPopover.tsx
index 1b745dc6..b2579ea1 100644
--- a/packages/web/src/components/chat/MentionPopover.tsx
+++ b/packages/web/src/components/chat/MentionPopover.tsx
@@ -71,6 +71,7 @@ export function MentionPopover({ query, selectedIndex, onSelect }: MentionPopove
name={displayName}
size={24}
status={member.user.status}
+ userId={member.user.id}
/>
{message.replyTo && (
-
+
{displayName}
diff --git a/packages/web/src/components/layout/ActivityPanel.tsx b/packages/web/src/components/layout/ActivityPanel.tsx
index c8a1a4e5..698217b3 100644
--- a/packages/web/src/components/layout/ActivityPanel.tsx
+++ b/packages/web/src/components/layout/ActivityPanel.tsx
@@ -54,6 +54,7 @@ export function ActivityPanel() {
size={32}
status={isOffline ? 'offline' : friend.status}
className={isOffline ? 'opacity-60' : undefined}
+ userId={friend.id}
/>
diff --git a/packages/web/src/components/layout/ChannelSidebar.tsx b/packages/web/src/components/layout/ChannelSidebar.tsx
index 47cb5827..029f75c7 100644
--- a/packages/web/src/components/layout/ChannelSidebar.tsx
+++ b/packages/web/src/components/layout/ChannelSidebar.tsx
@@ -215,12 +215,12 @@ export function ChannelSidebar() {
zIndex: 2 - i,
}}
>
-
+
))}
) : (
-
+
)}
-
+
{user.displayName ?? user.username}
diff --git a/packages/web/src/components/modals/NewDmModal.tsx b/packages/web/src/components/modals/NewDmModal.tsx
index f47ed057..36c04110 100644
--- a/packages/web/src/components/modals/NewDmModal.tsx
+++ b/packages/web/src/components/modals/NewDmModal.tsx
@@ -100,7 +100,7 @@ export function NewDmModal() {
onClick={() => handleSelectUser(user)}
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-interactive-hover transition-colors text-left"
>
-
+
{user.displayName ?? user.username}
diff --git a/packages/web/src/components/modals/ServerSettings.tsx b/packages/web/src/components/modals/ServerSettings.tsx
index 9b4f9323..b6ff1849 100644
--- a/packages/web/src/components/modals/ServerSettings.tsx
+++ b/packages/web/src/components/modals/ServerSettings.tsx
@@ -428,6 +428,7 @@ export function ServerSettingsModal() {
name={displayName}
size={32}
status={member.user.status}
+ user={member.user}
/>
{displayName}
diff --git a/packages/web/src/components/modals/UserSettings.tsx b/packages/web/src/components/modals/UserSettings.tsx
index 093f393f..baee273b 100644
--- a/packages/web/src/components/modals/UserSettings.tsx
+++ b/packages/web/src/components/modals/UserSettings.tsx
@@ -64,6 +64,7 @@ export function UserSettingsModal() {
name={user.displayName ?? user.username}
size={64}
status={user.status}
+ userId={user.id}
/>
{user.displayName ?? user.username}
diff --git a/packages/web/src/components/ui/Avatar.tsx b/packages/web/src/components/ui/Avatar.tsx
index 3c5105b0..f970419a 100644
--- a/packages/web/src/components/ui/Avatar.tsx
+++ b/packages/web/src/components/ui/Avatar.tsx
@@ -11,6 +11,7 @@ interface AvatarProps {
className?: string;
onClick?: (e: React.MouseEvent) => void;
user?: User;
+ userId?: string;
}
const statusColors: Record
= {
@@ -20,11 +21,11 @@ const statusColors: Record = {
offline: 'bg-status-offline',
};
-export function Avatar({ src, name, size = 40, status, className = '', onClick, user }: AvatarProps) {
+export function Avatar({ src, name, size = 40, status, className = '', onClick, user, userId }: AvatarProps) {
const openUserProfile = useUIStore((s) => s.openUserProfile);
const initials = name.charAt(0).toUpperCase();
const fontSize = size < 32 ? 'text-xs' : size < 48 ? 'text-sm' : 'text-lg';
- const gradient = getAvatarGradient(user?.id, name);
+ const gradient = getAvatarGradient(userId ?? user?.id, name);
const handleClick = (e: React.MouseEvent) => {
if (onClick) {
diff --git a/packages/web/src/components/voice/PictureInPicture.tsx b/packages/web/src/components/voice/PictureInPicture.tsx
index d5433597..fd7ff3c7 100644
--- a/packages/web/src/components/voice/PictureInPicture.tsx
+++ b/packages/web/src/components/voice/PictureInPicture.tsx
@@ -269,6 +269,7 @@ export function PictureInPicture() {
{speakingParticipantIds.has(displayParticipant.identity) && (
diff --git a/packages/web/src/components/voice/StreamTile.tsx b/packages/web/src/components/voice/StreamTile.tsx
index 2f2ba951..b9dc25a9 100644
--- a/packages/web/src/components/voice/StreamTile.tsx
+++ b/packages/web/src/components/voice/StreamTile.tsx
@@ -146,7 +146,7 @@ export function StreamTile({ tile, large }: StreamTileProps) {
) : (
diff --git a/packages/web/src/components/voice/VoiceChannel.tsx b/packages/web/src/components/voice/VoiceChannel.tsx
index e66c9ba2..11854d2b 100644
--- a/packages/web/src/components/voice/VoiceChannel.tsx
+++ b/packages/web/src/components/voice/VoiceChannel.tsx
@@ -67,6 +67,7 @@ export function VoiceChannel({ channelId, channelName, onClick }: VoiceChannelPr
name={displayName}
size={20}
status={status}
+ userId={userId}
/>
{displayName}
{/* Status badges */}
diff --git a/packages/web/src/components/voice/VoiceUser.tsx b/packages/web/src/components/voice/VoiceUser.tsx
index f32618f8..d538ec7c 100644
--- a/packages/web/src/components/voice/VoiceUser.tsx
+++ b/packages/web/src/components/voice/VoiceUser.tsx
@@ -95,6 +95,7 @@ export function VoiceUser({ tile, large }: VoiceUserProps) {
src={null}
name={participant.username}
size={large ? 100 : 64}
+ userId={participant.userId}
/>
{isSpeaking && (