From 2d8966a6a6e79734084e0886dfbca3220c6b305c Mon Sep 17 00:00:00 2001 From: 777genius Date: Sun, 31 May 2026 01:45:57 +0300 Subject: [PATCH] perf(renderer): reduce team page render work --- .../components/sidebar/GlobalTaskList.tsx | 27 +++++++++++++++++++ .../components/sidebar/SidebarTaskItem.tsx | 22 +++++++++++---- .../team/activity/LeadThoughtsGroup.tsx | 16 ++++++----- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/renderer/components/sidebar/GlobalTaskList.tsx b/src/renderer/components/sidebar/GlobalTaskList.tsx index 658a1c52..b3cec963 100644 --- a/src/renderer/components/sidebar/GlobalTaskList.tsx +++ b/src/renderer/components/sidebar/GlobalTaskList.tsx @@ -10,6 +10,7 @@ import { cn } from '@renderer/lib/utils'; import { markTaskUnread } from '@renderer/services/commentReadStorage'; import { useStore } from '@renderer/store'; import { getCurrentProvisioningProgressForTeam } from '@renderer/store/slices/teamSlice'; +import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; import { normalizePath } from '@renderer/utils/pathNormalize'; import { projectColor } from '@renderer/utils/projectColor'; import { @@ -212,6 +213,7 @@ interface GlobalTaskRowProps { onRenameComplete: (teamName: string, taskId: string, newSubject: string) => void; onRenameCancel: () => void; getDisplaySubject: TaskDisplaySubjectResolver; + ownerColorName?: string | null; } const GlobalTaskRow = memo(function GlobalTaskRow({ @@ -232,6 +234,7 @@ const GlobalTaskRow = memo(function GlobalTaskRow({ onRenameComplete, onRenameCancel, getDisplaySubject, + ownerColorName, }: GlobalTaskRowProps): React.JSX.Element { const taskRenamingKey = `${task.teamName}:${task.id}`; const effectiveRenamingKey = renamingKey === taskRenamingKey ? renamingKey : null; @@ -278,6 +281,7 @@ const GlobalTaskRow = memo(function GlobalTaskRow({ onRenameComplete={onRenameComplete} onRenameCancel={onRenameCancel} getDisplaySubject={getDisplaySubject} + ownerColorName={ownerColorName} /> @@ -493,6 +497,25 @@ export const GlobalTaskList = memo(function GlobalTaskList({ teams, ]); + const memberColorByTeam = useMemo(() => { + const result = new Map>(); + for (const team of teams) { + if (team.members && team.members.length > 0) { + result.set(team.teamName, buildMemberColorMap(team.members)); + } + } + return result; + }, [teams]); + + const getOwnerColorName = useCallback( + (task: GlobalTask): string | null | undefined => { + if (!task.owner) return null; + const teamColorMap = memberColorByTeam.get(task.teamName); + return teamColorMap ? (teamColorMap.get(task.owner) ?? null) : undefined; + }, + [memberColorByTeam] + ); + const setGroupingMode = (mode: TaskGroupingMode): void => { setGroupingModeState(mode); saveGroupingMode(mode); @@ -839,6 +862,7 @@ export const GlobalTaskList = memo(function GlobalTaskList({ isNew={isNewTask(task)} showTeamName teamOffline={offlineTeamNames.has(task.teamName)} + ownerColorName={getOwnerColorName(task)} renamingKey={renamingTaskKey} onTogglePin={handleToggleTaskPin} onToggleArchive={handleToggleTaskArchive} @@ -942,6 +966,7 @@ export const GlobalTaskList = memo(function GlobalTaskList({ isNew={isNewTask(task)} showTeamName teamOffline={offlineTeamNames.has(task.teamName)} + ownerColorName={getOwnerColorName(task)} renamingKey={renamingTaskKey} onTogglePin={handleToggleTaskPin} onToggleArchive={handleToggleTaskArchive} @@ -1023,6 +1048,7 @@ export const GlobalTaskList = memo(function GlobalTaskList({ hideTeamName hideProjectName teamOffline={offlineTeamNames.has(task.teamName)} + ownerColorName={getOwnerColorName(task)} renamingKey={renamingTaskKey} onTogglePin={handleToggleTaskPin} onToggleArchive={handleToggleTaskArchive} @@ -1121,6 +1147,7 @@ export const GlobalTaskList = memo(function GlobalTaskList({ isArchived={taskLocalState.isArchived(task.teamName, task.id)} isNew={isNewTask(task)} teamOffline={offlineTeamNames.has(task.teamName)} + ownerColorName={getOwnerColorName(task)} renamingKey={renamingTaskKey} onTogglePin={handleToggleTaskPin} onToggleArchive={handleToggleTaskArchive} diff --git a/src/renderer/components/sidebar/SidebarTaskItem.tsx b/src/renderer/components/sidebar/SidebarTaskItem.tsx index fdbe3be9..99d4b7bd 100644 --- a/src/renderer/components/sidebar/SidebarTaskItem.tsx +++ b/src/renderer/components/sidebar/SidebarTaskItem.tsx @@ -79,6 +79,7 @@ interface SidebarTaskItemProps { onRenameCancel?: () => void; /** Returns a custom display subject if the task was renamed locally */ getDisplaySubject?: (task: GlobalTask) => string | undefined; + ownerColorName?: string | null; } export const SidebarTaskItem = memo(function SidebarTaskItem({ @@ -91,11 +92,17 @@ export const SidebarTaskItem = memo(function SidebarTaskItem({ onRenameComplete, onRenameCancel, getDisplaySubject, + ownerColorName, }: SidebarTaskItemProps): React.JSX.Element { const { t } = useAppTranslation('team'); const { t: tCommon } = useAppTranslation('common'); const openGlobalTaskDetail = useStore((s) => s.openGlobalTaskDetail); - const teamMembers = useStore(useShallow((s) => s.teamByName[task.teamName]?.members)); + const shouldResolveOwnerColorFromStore = ownerColorName === undefined; + const teamMembers = useStore( + useShallow((s) => + shouldResolveOwnerColorFromStore ? s.teamByName[task.teamName]?.members : undefined + ) + ); const unreadCount = useUnreadCommentCount(task.teamName, task.id, task.comments); const { isLight } = useTheme(); @@ -142,12 +149,17 @@ export const SidebarTaskItem = memo(function SidebarTaskItem({ ); const dateLabel = updatedLabel ?? formatTaskDate(task.createdAt, tCommon('tasks.date.yesterday')); + const resolvedOwnerColorName = useMemo(() => { + if (!task.owner) return null; + if (!shouldResolveOwnerColorFromStore) return ownerColorName; + if (!teamMembers) return null; + return buildMemberColorMap(teamMembers).get(task.owner) ?? null; + }, [ownerColorName, shouldResolveOwnerColorFromStore, task.owner, teamMembers]); + const ownerColorSet = useMemo(() => { - if (!teamMembers || !task.owner) return null; - const colorMap = buildMemberColorMap(teamMembers); - const colorName = colorMap.get(task.owner); + const colorName = resolvedOwnerColorName; return colorName ? getTeamColorSet(colorName) : null; - }, [teamMembers, task.owner]); + }, [resolvedOwnerColorName]); const ownerTextColor = useMemo(() => { if (!ownerColorSet) return undefined; diff --git a/src/renderer/components/team/activity/LeadThoughtsGroup.tsx b/src/renderer/components/team/activity/LeadThoughtsGroup.tsx index fd2154e9..d0537dd1 100644 --- a/src/renderer/components/team/activity/LeadThoughtsGroup.tsx +++ b/src/renderer/components/team/activity/LeadThoughtsGroup.tsx @@ -357,8 +357,16 @@ const LeadThoughtItem = memo( useLayoutEffect(() => { const wrapper = wrapperRef.current; + if (!wrapper) return; + + if (!shouldAnimateOnMount) { + initialAnimationCompletedRef.current = true; + resetWrapperStyles(); + return; + } + const content = contentRef.current; - if (!wrapper || !content) return; + if (!content) return; const applyTransition = (targetHeight: number): void => { wrapper.style.transition = [ @@ -406,12 +414,6 @@ const LeadThoughtItem = memo( const previousHeight = previousHeightRef.current; previousHeightRef.current = nextHeight; - if (!shouldAnimateOnMount) { - initialAnimationCompletedRef.current = true; - resetWrapperStyles(); - return; - } - if (previousHeight === null) { if (nextHeight > 0 && animateFromZero) { animateHeight(nextHeight, 0, 0);