diff --git a/src/renderer/components/sidebar/GlobalTaskList.tsx b/src/renderer/components/sidebar/GlobalTaskList.tsx index 3749a364..46404ca6 100644 --- a/src/renderer/components/sidebar/GlobalTaskList.tsx +++ b/src/renderer/components/sidebar/GlobalTaskList.tsx @@ -210,12 +210,14 @@ interface SidebarTeamsDerived { } let cachedSidebarTeamsSignature: string | null = null; +let cachedSidebarTeamsSource: readonly TeamSummary[] | null = null; let cachedSidebarTeamsDerived: SidebarTeamsDerived = { identityKey: '', filterTeams: [], statusSummaries: [], memberColorByTeam: new Map(), }; +let cachedLeadOfflineTeamsSource: Partial> | null = null; let cachedLeadOfflineTeamsSignature = ''; let cachedLeadOfflineTeamNames: string[] = []; @@ -255,6 +257,10 @@ function buildTeamNamesIdentityKey(teams: readonly TeamSummary[]): string { function selectLeadOfflineTeamNames( leadActivityByTeam: Partial> ): string[] { + if (leadActivityByTeam === cachedLeadOfflineTeamsSource) { + return cachedLeadOfflineTeamNames; + } + const offlineTeamNames: string[] = []; for (const [teamName, activity] of Object.entries(leadActivityByTeam)) { if (activity === 'offline') { @@ -269,17 +275,24 @@ function selectLeadOfflineTeamNames( } if (signature === cachedLeadOfflineTeamsSignature) { + cachedLeadOfflineTeamsSource = leadActivityByTeam; return cachedLeadOfflineTeamNames; } + cachedLeadOfflineTeamsSource = leadActivityByTeam; cachedLeadOfflineTeamsSignature = signature; cachedLeadOfflineTeamNames = offlineTeamNames; return cachedLeadOfflineTeamNames; } function selectSidebarTeamsDerived(teams: readonly TeamSummary[]): SidebarTeamsDerived { + if (teams === cachedSidebarTeamsSource) { + return cachedSidebarTeamsDerived; + } + const signature = buildSidebarTeamsSignature(teams); if (signature === cachedSidebarTeamsSignature) { + cachedSidebarTeamsSource = teams; return cachedSidebarTeamsDerived; } @@ -290,6 +303,7 @@ function selectSidebarTeamsDerived(teams: readonly TeamSummary[]): SidebarTeamsD } } + cachedSidebarTeamsSource = teams; cachedSidebarTeamsSignature = signature; cachedSidebarTeamsDerived = { identityKey: buildTeamNamesIdentityKey(teams), diff --git a/src/renderer/components/team/MemberBadge.tsx b/src/renderer/components/team/MemberBadge.tsx index a45dfc4c..fb5981c2 100644 --- a/src/renderer/components/team/MemberBadge.tsx +++ b/src/renderer/components/team/MemberBadge.tsx @@ -34,6 +34,18 @@ interface MemberBadgeProps { } const EMPTY_TEAM_MEMBERS: readonly ResolvedTeamMember[] = []; +const memberAvatarMapCache = new WeakMap>(); + +function getCachedMemberAvatarMap(members: readonly ResolvedTeamMember[]): Map { + const cached = memberAvatarMapCache.get(members); + if (cached) { + return cached; + } + + const next = buildMemberAvatarMap(members); + memberAvatarMapCache.set(members, next); + return next; +} /** * Reusable member avatar + colored name badge. @@ -61,7 +73,7 @@ export const MemberBadge = memo( ? selectResolvedMembersForTeamName(s, effectiveAvatarTeamName) : EMPTY_TEAM_MEMBERS ); - const avatarMap = useMemo(() => buildMemberAvatarMap(teamMembers), [teamMembers]); + const avatarMap = useMemo(() => getCachedMemberAvatarMap(teamMembers), [teamMembers]); const avatarSize = size === 'md' ? 32 : size === 'sm' ? 24 : 18; const avatarClass = size === 'md' ? 'size-6' : size === 'sm' ? 'size-5' : 'size-4'; const textClass = size === 'md' ? 'text-xs' : size === 'sm' ? 'text-[10px]' : 'text-[9px]'; diff --git a/src/renderer/components/team/messages/MessagesPanel.tsx b/src/renderer/components/team/messages/MessagesPanel.tsx index a0e4e449..be000af3 100644 --- a/src/renderer/components/team/messages/MessagesPanel.tsx +++ b/src/renderer/components/team/messages/MessagesPanel.tsx @@ -114,6 +114,7 @@ interface TeamMentionEntry { } let cachedTeamMentionSignature = ''; +let cachedTeamMentionSource: readonly TeamSummary[] | null = null; let cachedTeamMentionMeta: TeamMentionMeta = { teamNames: EMPTY_TEAM_NAMES, teamColorByName: EMPTY_TEAM_COLOR_MAP, @@ -142,8 +143,13 @@ function getTeamMentionSignature(teams: readonly TeamSummary[]): string { } function selectMessagesPanelTeamMentionMeta(teams: readonly TeamSummary[]): TeamMentionMeta { + if (teams === cachedTeamMentionSource) { + return cachedTeamMentionMeta; + } + const signature = getTeamMentionSignature(teams); if (signature === cachedTeamMentionSignature) { + cachedTeamMentionSource = teams; return cachedTeamMentionMeta; } @@ -157,6 +163,7 @@ function selectMessagesPanelTeamMentionMeta(teams: readonly TeamSummary[]): Team .sort(compareTeamMentionEntries); if (entries.length === 0) { + cachedTeamMentionSource = teams; cachedTeamMentionSignature = signature; cachedTeamMentionMeta = { teamNames: EMPTY_TEAM_NAMES, @@ -180,6 +187,7 @@ function selectMessagesPanelTeamMentionMeta(teams: readonly TeamSummary[]): Team } } + cachedTeamMentionSource = teams; cachedTeamMentionSignature = signature; cachedTeamMentionMeta = { teamNames, teamColorByName }; return cachedTeamMentionMeta;