From 04f96b1177225d1c7bbf21e598d7630cb85f7b7e Mon Sep 17 00:00:00 2001 From: automated-signal <37887102+automated-signal@users.noreply.github.com> Date: Mon, 9 Mar 2026 14:04:21 -0500 Subject: [PATCH] Use all group members when calculating group colors Co-authored-by: Scott Nonnenberg --- ts/state/ducks/conversations.preload.ts | 2 + ts/state/selectors/conversations.dom.ts | 13 ++-- .../selectors/conversations_test.preload.ts | 71 ++++++++++++++----- ts/util/getConversation.preload.ts | 1 + 4 files changed, 66 insertions(+), 21 deletions(-) diff --git a/ts/state/ducks/conversations.preload.ts b/ts/state/ducks/conversations.preload.ts index 19906cac31..fef3b40b30 100644 --- a/ts/state/ducks/conversations.preload.ts +++ b/ts/state/ducks/conversations.preload.ts @@ -407,6 +407,8 @@ export type ConversationType = ReadonlyDeep< lastUpdated?: number; // This is used by the CompositionInput for @mentions sortedGroupMembers?: ReadonlyArray; + // Used to generate contact colors in groups - it includes every member + membersV2?: ConversationAttributesType['membersV2']; title: string; titleNoDefault?: string; titleNoNickname?: string; diff --git a/ts/state/selectors/conversations.dom.ts b/ts/state/selectors/conversations.dom.ts index 7c3652da0e..08d3607f9b 100644 --- a/ts/state/selectors/conversations.dom.ts +++ b/ts/state/selectors/conversations.dom.ts @@ -1155,7 +1155,7 @@ export const getCachedConversationMemberColorsSelector = createSelector( (conversationId: string | undefined) => { const contactNameColors: Map = new Map(); const { - sortedGroupMembers = [], + membersV2 = [], type, id: theirId, } = conversationSelector(conversationId); @@ -1168,13 +1168,18 @@ export const getCachedConversationMemberColorsSelector = createSelector( return contactNameColors; } - [...sortedGroupMembers] + [...membersV2] .sort((left, right) => - String(left.serviceId) > String(right.serviceId) ? 1 : -1 + String(left.aci) > String(right.aci) ? 1 : -1 ) .forEach((member, i) => { + const conversation = conversationSelector(member.aci); + if (conversation.id === PLACEHOLDER_CONTACT_ID) { + return; + } + contactNameColors.set( - member.id, + conversation.id, ContactNameColors[i % ContactNameColors.length] ); }); diff --git a/ts/test-node/state/selectors/conversations_test.preload.ts b/ts/test-node/state/selectors/conversations_test.preload.ts index 76e5375bd6..95ac938d70 100644 --- a/ts/test-node/state/selectors/conversations_test.preload.ts +++ b/ts/test-node/state/selectors/conversations_test.preload.ts @@ -46,7 +46,10 @@ import { noopAction } from '../../../state/ducks/noop.std.js'; import type { StateType } from '../../../state/reducer.preload.js'; import { reducer as rootReducer } from '../../../state/reducer.preload.js'; import i18n from '../../util/i18n.node.js'; -import type { ServiceIdString } from '../../../types/ServiceId.std.js'; +import type { + AciString, + ServiceIdString, +} from '../../../types/ServiceId.std.js'; import { generateAci, getAciFromPrefix } from '../../../types/ServiceId.std.js'; import { getDefaultConversation, @@ -1650,17 +1653,42 @@ describe('both/state/selectors/conversations-extra', () => { describe('#getContactNameColorSelector', () => { it('returns the right color order sorted by UUID ASC', () => { + const membersV2 = [ + { aci: 'fff' as AciString, role: 0, joinedAtVersion: 0 }, + { + aci: 'f00' as AciString, + role: 0, + joinedAtVersion: 0, + }, + { + aci: 'e00' as AciString, + role: 0, + joinedAtVersion: 0, + }, + { + aci: 'd00' as AciString, + role: 0, + joinedAtVersion: 0, + }, + { + aci: 'c00' as AciString, + role: 0, + joinedAtVersion: 0, + }, + { + aci: 'b00' as AciString, + role: 0, + joinedAtVersion: 0, + }, + { + aci: 'a00' as AciString, + role: 0, + joinedAtVersion: 0, + }, + ]; const group: ConversationType = { ...makeGroup('group'), - sortedGroupMembers: [ - makeConversationWithServiceId('fff'), - makeConversationWithServiceId('f00'), - makeConversationWithServiceId('e00'), - makeConversationWithServiceId('d00'), - makeConversationWithServiceId('c00'), - makeConversationWithServiceId('b00'), - makeConversationWithServiceId('a00'), - ], + membersV2, }; const state = { ...getEmptyRootState(), @@ -1669,18 +1697,27 @@ describe('both/state/selectors/conversations-extra', () => { conversationLookup: { group, }, + conversationsByServiceId: { + [membersV2[0].aci]: makeConversation('c0'), + [membersV2[1].aci]: makeConversation('c1'), + [membersV2[2].aci]: makeConversation('c2'), + [membersV2[3].aci]: makeConversation('c3'), + [membersV2[4].aci]: makeConversation('c4'), + [membersV2[5].aci]: makeConversation('c5'), + [membersV2[6].aci]: makeConversation('c6'), + }, }, }; const contactNameColorSelector = getContactNameColorSelector(state); - assert.equal(contactNameColorSelector('group', 'a00'), '200'); - assert.equal(contactNameColorSelector('group', 'b00'), '120'); - assert.equal(contactNameColorSelector('group', 'c00'), '300'); - assert.equal(contactNameColorSelector('group', 'd00'), '010'); - assert.equal(contactNameColorSelector('group', 'e00'), '210'); - assert.equal(contactNameColorSelector('group', 'f00'), '330'); - assert.equal(contactNameColorSelector('group', 'fff'), '230'); + assert.equal(contactNameColorSelector('group', 'c6'), '200', 'slot 1'); + assert.equal(contactNameColorSelector('group', 'c5'), '120', 'slot 2'); + assert.equal(contactNameColorSelector('group', 'c4'), '300', 'slot 3'); + assert.equal(contactNameColorSelector('group', 'c3'), '010', 'slot 4'); + assert.equal(contactNameColorSelector('group', 'c2'), '210', 'slot 5'); + assert.equal(contactNameColorSelector('group', 'c1'), '330', 'slot 6'); + assert.equal(contactNameColorSelector('group', 'c0'), '230', 'slot 7'); }); it('returns the right colors for direct conversation', () => { diff --git a/ts/util/getConversation.preload.ts b/ts/util/getConversation.preload.ts index e6ff09ba5d..a17aeca48a 100644 --- a/ts/util/getConversation.preload.ts +++ b/ts/util/getConversation.preload.ts @@ -214,6 +214,7 @@ export function getConversation(model: ConversationModel): ConversationType { markedUnread: attributes.markedUnread, membersCount: getMembersCount(attributes), memberships: getMemberships(attributes), + membersV2: attributes.membersV2, messagesDeleted: Boolean(attributes.messagesDeleted), hasMessages: (attributes.messageCount ?? 0) > 0, pendingMemberships: getPendingMemberships(attributes),