diff --git a/packages/web/src/components/layout/DmListItem.tsx b/packages/web/src/components/layout/DmListItem.tsx index 99f1cdca..47f10818 100644 --- a/packages/web/src/components/layout/DmListItem.tsx +++ b/packages/web/src/components/layout/DmListItem.tsx @@ -1,34 +1,12 @@ import type { DmChannel, User } from '@backspace/shared'; import { Avatar } from '../ui/Avatar'; +import { AvatarStack } from '../ui/AvatarStack'; import { Tooltip } from '../ui/Tooltip'; import { parseFederatedUsername, isSelf, isFederationGlobeApplicable } from '../../utils/identity'; import { useCanonicalUserView } from '../../utils/userViewLookup'; import { formatDmTimestamp, formatDmSidebarPreview } from '../../utils/dmFormatters'; import { getRejectedPeerOrigins, getAwaitingApprovalPeerOrigins } from '../../hooks/useWebSocket'; -/** - * Renders a single avatar slot in the group DM avatar pair. - * Extracted as a component so useCanonicalUserView can be called per-slot - * (hooks must not be called inside a variable-length .map()). - */ -function DmGroupAvatarSlot({ member, index }: { member: User; index: number }) { - const canonical = useCanonicalUserView(member); - const displayName = canonical.displayName ?? parseFederatedUsername(canonical.username).baseName; - return ( -
- -
- ); -} - function isMemberUnreachable(homeInstance: string | null | undefined): boolean { if (!homeInstance) return false; const normalized = homeInstance.startsWith('http') ? homeInstance : `https://${homeInstance}`; @@ -67,11 +45,17 @@ export function DmListItem({ dm, isActive, isUnread, user, onSelect, onClose, on const { baseName } = parseFederatedUsername(firstOther?.username ?? ''); const displayName = isGroup - ? (otherMembers.length > 0 + ? (dm.name ?? (otherMembers.length > 0 ? otherMembers.map(m => m.displayName ?? parseFederatedUsername(m.username).baseName).join(', ') - : 'Empty Group') + : 'Empty Group')) : firstOther?.displayName ?? baseName; + // Group globe: at least one member is federated → render once with comma-joined tooltip. + const groupFederatedMembers = isGroup + ? dm.members.filter(m => isFederationGlobeApplicable(m)) + : []; + const showGroupGlobe = isGroup && groupFederatedMembers.length > 0; + const handleClick = () => onSelect(dm.id); const handleClose = (e: React.MouseEvent) => { e.stopPropagation(); @@ -155,11 +139,7 @@ export function DmListItem({ dm, isActive, isUnread, user, onSelect, onClose, on {/* Avatar */} {isGroup ? ( -
- {otherMembers.slice(0, 2).map((m, i) => ( - - ))} -
+ ) : ( )} @@ -170,6 +150,13 @@ export function DmListItem({ dm, isActive, isUnread, user, onSelect, onClose, on {displayName} + {showGroupGlobe && ( + m.username).join(', ')} position="top"> + + + + + )} {!isGroup && firstOther && isFederationGlobeApplicable(firstOther) && (