diff --git a/web/src/features/messages/ConversationView.tsx b/web/src/features/messages/ConversationView.tsx index adf58de1..3eec38a4 100644 --- a/web/src/features/messages/ConversationView.tsx +++ b/web/src/features/messages/ConversationView.tsx @@ -35,6 +35,7 @@ import { closeConversation, ensureTopicAttached, forgetTopic, + getP2PPeerUserId, markConfirmedRead, openConversation, setActiveConversationTopic, @@ -463,16 +464,23 @@ export function ConversationView() { // `['me']` key NavRail/ChatListColumn/SettingsLayout already keep warm, so this is a cache read in // the normal case, not a new request. const meQuery = useQuery({ queryKey: ['me'], queryFn: getMe }); + // NOT topicId -- see getP2PPeerUserId's own doc comment. A P2P topic with any real history is + // addressed by a real, opaque `p2p...` name, not the peer's `usr...` id (that only holds true in + // the brief window before the very first subscribe) -- confirmed live: without this, this query + // 404s (`GET /users/{p2p...}/profile`) for every established conversation, and only ever appeared + // to work in earlier testing because that test happened to use a conversation opened for the first + // time in the same session, before the SDK's own rename kicked in. + const peerUserId = !isGroup && !isSelf && topicId ? getP2PPeerUserId(tinode.getTopic(topicId), myUserId) : undefined; // Real gap this was written to close: the chat header/message-row avatars only ever drew the // letter-tile fallback, even after a real custom avatar was uploaded and correctly showing up on // the contacts pages (FriendProfileView/ContactsListColumn) -- those read this exact same REST // profile, the conversation screen just never fetched it. Same query DetailColumn already runs - // (`['user-profile', topicId]`), so this dedupes with it whenever the detail panel happens to be + // (`['user-profile', peerUserId]`), so this dedupes with it whenever the detail panel happens to be // open, and only fetches for a real P2P peer, never for a group or `slf`. const peerProfileQuery = useQuery({ - queryKey: ['user-profile', topicId], - queryFn: () => getUserProfile(topicId || ''), - enabled: !!topicId && !isGroup && !isSelf, + queryKey: ['user-profile', peerUserId], + queryFn: () => getUserProfile(peerUserId || ''), + enabled: !!peerUserId, }); const membersQuery = useQuery({ queryKey: ['group-members', topicId], @@ -525,6 +533,11 @@ export function ConversationView() { const myAvatarFileId = meQuery.data?.profile.avatar_file_id; const peerAvatarFileId = isSelf ? myAvatarFileId : peerProfileQuery.data?.avatar_file_id; const headerAvatarFileId = isGroup ? groupProfileQuery.data?.avatar_file_id : peerAvatarFileId; + // Color-hash id, not the file lookup above -- topicId is right for a group (its own identity), but + // for P2P this should be the peer's stable usr id (peerUserId), matching the same hash ContactsListColumn/ + // FriendProfileView use for this same person, rather than the p2p topic name (which isP2P/self also + // never had a usr-shaped id for in the first place). + const headerAvatarId = isGroup ? topicId : peerUserId || topicId; // `peerName` is the GROUP's own name for a group topic, not any member's -- reusing it to label // "who is being quoted" (as both the reply-composer preview and the actual sent quote block used // to) meant every single group-chat reply permanently displayed "回复 <群名>:…" for every member, @@ -824,7 +837,7 @@ export function ConversationView() {