From 0296c68a4da29881f6be85d7f85933d894e47707 Mon Sep 17 00:00:00 2001 From: iliya Date: Sun, 31 May 2026 01:22:47 +0300 Subject: [PATCH 1/5] fix(ci): resolve validate lint errors --- src/main/services/team/TeamDataService.ts | 2 +- src/main/services/team/TeamProvisioningService.ts | 6 +----- src/main/services/team/TeamTranscriptProjectResolver.ts | 4 ++-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/services/team/TeamDataService.ts b/src/main/services/team/TeamDataService.ts index 405a7f31..ffc1aa7a 100644 --- a/src/main/services/team/TeamDataService.ts +++ b/src/main/services/team/TeamDataService.ts @@ -256,7 +256,7 @@ async function mapLimitLocal( if (index >= items.length) { return; } - results[index] = await mapper(items[index]!); + results[index] = await mapper(items[index]); } }) ); diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 0899a99e..811c333f 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -26313,14 +26313,10 @@ export class TeamProvisioningService { } private shouldSampleMissingRuntimeUsageStatsWithPidusage(): boolean { - if (!this.isRuntimePidusageTelemetryEnabled()) { - return false; - } - // CPU/RSS telemetry already comes from the enriched process table in the // default path. If this opt-in is enabled, preserve the older fallback for // missing rows across platforms. - return true; + return this.isRuntimePidusageTelemetryEnabled(); } private isRuntimePidusageTelemetryEnabled(): boolean { diff --git a/src/main/services/team/TeamTranscriptProjectResolver.ts b/src/main/services/team/TeamTranscriptProjectResolver.ts index 9f10fc19..b6f583d5 100644 --- a/src/main/services/team/TeamTranscriptProjectResolver.ts +++ b/src/main/services/team/TeamTranscriptProjectResolver.ts @@ -72,12 +72,12 @@ interface TeamTranscriptProjectContextOptions { includeTeamSubagentSessionDiscovery?: boolean; } -type TeamTranscriptFileStat = { +interface TeamTranscriptFileStat { mtimeMs: number; size: number; ctimeMs?: number; isFile: () => boolean; -}; +} type ScannedSessionProjectMatch = Omit & { projectPath?: string; From b688800d5756c05dd6000d5bddfadb2a229f049f Mon Sep 17 00:00:00 2001 From: 777genius Date: Sun, 31 May 2026 01:17:53 +0300 Subject: [PATCH 2/5] perf(main): cache live runtime process table reads --- .../services/team/TeamProvisioningService.ts | 107 +++++++++++++----- 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 811c333f..534dca9c 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -850,6 +850,15 @@ interface RuntimeTelemetryProcessTableRow extends RuntimeProcessTableRow, Runtim runtimeTelemetrySource?: RuntimeTelemetryProcessSource; } +interface RuntimeProcessRowsCacheEntry { + expiresAtMs: number; + generation: number; + runId: string | null; + sampledAtMs: number; + rows: RuntimeTelemetryProcessTableRow[] | null; + includesWindowsHostRows: boolean; +} + class RuntimeTelemetryTimeoutError extends Error { constructor(message: string) { super(message); @@ -3378,6 +3387,8 @@ export class TeamProvisioningService { private static readonly MAX_RUNTIME_USAGE_PIDS_PER_SNAPSHOT = 512; private static readonly RUNTIME_PROCESS_TABLE_TIMEOUT_MS = 1_500; private static readonly RUNTIME_WINDOWS_PROCESS_TABLE_TIMEOUT_MS = 1_500; + private static readonly RUNTIME_LIVENESS_PROCESS_TABLE_CACHE_TTL_MS = 5_000; + private static readonly RUNTIME_LIVENESS_PROCESS_TABLE_FAILURE_CACHE_TTL_MS = 2_000; private static readonly RUNTIME_PROCESS_USAGE_CACHE_TTL_MS = 30_000; private static readonly RUNTIME_PROCESS_USAGE_CACHE_MAX_ENTRIES = 4_096; private static readonly RUNTIME_PIDUSAGE_BATCH_TIMEOUT_MS = 2_000; @@ -3490,13 +3501,7 @@ export class TeamProvisioningService { >(); private readonly runtimeProcessRowsForUsageSnapshotByTeam = new Map< string, - { - expiresAtMs: number; - generation: number; - runId: string | null; - rows: RuntimeTelemetryProcessTableRow[] | null; - includesWindowsHostRows: boolean; - } + RuntimeProcessRowsCacheEntry >(); private readonly runtimeProcessUsageStatsCacheByPid = new Map< number, @@ -3798,9 +3803,8 @@ export class TeamProvisioningService { this.liveTeamAgentRuntimeMetadataCache.delete(teamName); this.liveTeamAgentRuntimeMetadataInFlightByTeam.delete(teamName); this.persistedTeamConfigCache.delete(teamName); - // CPU/RSS telemetry is TTL-bound and does not decide liveness. Keep the - // process table cache across noisy runtime snapshot invalidations so UI - // refreshes do not respawn `ps` just to repaint resource badges. + // Process table rows are TTL-bound. Resource telemetry can use the longer + // TTL, while liveness only reuses rows through a short age gate. } private cloneMemberSpawnStatusesSnapshot( @@ -25450,35 +25454,43 @@ export class TeamProvisioningService { paneInfoById, }); if (shouldReadProcessTable) { - processRowsReadForMetadata = true; - try { - processRows = - this.normalizeRuntimeProcessRowsForTelemetry( - await this.withRuntimeTelemetryTimeout( - listRuntimeProcessTableForCurrentPlatform(), - TeamProvisioningService.RUNTIME_PROCESS_TABLE_TIMEOUT_MS, - 'process table runtime snapshot' - ), - process.platform === 'win32' ? 'wsl' : 'native' - ) ?? []; - } catch (error) { - processTableAvailable = false; - logger.debug( - `[${teamName}] Failed to read process table for runtime snapshot: ${ - error instanceof Error ? error.message : String(error) - }` - ); + const cachedRows = this.readCachedRuntimeProcessRowsForLiveRuntimeMetadata(teamName, runId); + if (cachedRows) { + processTableAvailable = cachedRows.rows !== null; + processRows = cachedRows.rows ?? []; + } else { + processRowsReadForMetadata = true; + try { + processRows = + this.normalizeRuntimeProcessRowsForTelemetry( + await this.withRuntimeTelemetryTimeout( + listRuntimeProcessTableForCurrentPlatform(), + TeamProvisioningService.RUNTIME_PROCESS_TABLE_TIMEOUT_MS, + 'process table runtime snapshot' + ), + process.platform === 'win32' ? 'wsl' : 'native' + ) ?? []; + } catch (error) { + processTableAvailable = false; + logger.debug( + `[${teamName}] Failed to read process table for runtime snapshot: ${ + error instanceof Error ? error.message : String(error) + }` + ); + } } } if (processRowsReadForMetadata) { + const sampledAtMs = Date.now(); this.runtimeProcessRowsForUsageSnapshotByTeam.set(teamName, { expiresAtMs: - Date.now() + + sampledAtMs + (processTableAvailable ? TeamProvisioningService.RUNTIME_RESOURCE_TELEMETRY_CACHE_TTL_MS : TeamProvisioningService.RUNTIME_RESOURCE_TELEMETRY_FAILURE_CACHE_TTL_MS), generation: generationAtStart, runId, + sampledAtMs, rows: processTableAvailable ? processRows : null, includesWindowsHostRows: false, }); @@ -25914,6 +25926,39 @@ export class TeamProvisioningService { return false; } + private readCachedRuntimeProcessRowsForLiveRuntimeMetadata( + teamName: string, + runId: string | null + ): { rows: RuntimeTelemetryProcessTableRow[] | null } | null { + const cached = this.runtimeProcessRowsForUsageSnapshotByTeam.get(teamName); + const nowMs = Date.now(); + if (!cached || cached.expiresAtMs <= nowMs || cached.runId !== runId) { + return null; + } + + const sampledAtMs = + typeof cached.sampledAtMs === 'number' && Number.isFinite(cached.sampledAtMs) + ? cached.sampledAtMs + : 0; + const maxAgeMs = + cached.rows === null + ? TeamProvisioningService.RUNTIME_LIVENESS_PROCESS_TABLE_FAILURE_CACHE_TTL_MS + : TeamProvisioningService.RUNTIME_LIVENESS_PROCESS_TABLE_CACHE_TTL_MS; + if (sampledAtMs <= 0 || nowMs - sampledAtMs > maxAgeMs) { + return null; + } + + if (cached.rows === null) { + return { rows: null }; + } + + const rows = + this.normalizeRuntimeProcessRowsForTelemetry(cached.rows)?.filter( + (row) => row.runtimeTelemetrySource !== 'windows-host' + ) ?? []; + return { rows }; + } + private async readRuntimeProcessRowsForUsageSnapshot( teamName: string, options: { includeWindowsHostRows?: boolean } = {} @@ -25978,14 +26023,16 @@ export class TeamProvisioningService { } const resultRows = rows && rows.length > 0 ? rows : runtimeProcessTableAvailable ? [] : null; + const sampledAtMs = Date.now(); this.runtimeProcessRowsForUsageSnapshotByTeam.set(teamName, { expiresAtMs: - Date.now() + + sampledAtMs + (resultRows === null ? TeamProvisioningService.RUNTIME_RESOURCE_TELEMETRY_FAILURE_CACHE_TTL_MS : TeamProvisioningService.RUNTIME_RESOURCE_TELEMETRY_CACHE_TTL_MS), generation: this.getRuntimeSnapshotCacheGeneration(teamName), runId: this.getTrackedRunId(teamName), + sampledAtMs, rows: resultRows, includesWindowsHostRows, }); From a7a732e226c638ac5e02a4b64834b2dbd88ddf02 Mon Sep 17 00:00:00 2001 From: iliya Date: Sun, 31 May 2026 01:39:39 +0300 Subject: [PATCH 3/5] fix(team): guard runtime process row cache invalidation --- src/main/services/team/TeamProvisioningService.ts | 7 ++++++- test/main/services/team/TeamProvisioningService.test.ts | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 534dca9c..4e1e87b5 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -25932,7 +25932,12 @@ export class TeamProvisioningService { ): { rows: RuntimeTelemetryProcessTableRow[] | null } | null { const cached = this.runtimeProcessRowsForUsageSnapshotByTeam.get(teamName); const nowMs = Date.now(); - if (!cached || cached.expiresAtMs <= nowMs || cached.runId !== runId) { + if ( + !cached || + cached.expiresAtMs <= nowMs || + cached.runId !== runId || + cached.generation !== this.getRuntimeSnapshotCacheGeneration(teamName) + ) { return null; } diff --git a/test/main/services/team/TeamProvisioningService.test.ts b/test/main/services/team/TeamProvisioningService.test.ts index 610e8d67..f7b3981f 100644 --- a/test/main/services/team/TeamProvisioningService.test.ts +++ b/test/main/services/team/TeamProvisioningService.test.ts @@ -3514,7 +3514,7 @@ describe('TeamProvisioningService', () => { expect(listRuntimeProcessTableForCurrentPlatform).toHaveBeenCalledTimes(2); }); - it('keeps the short live runtime metadata cache for tracked runs', async () => { + it('reuses process rows through the short liveness cache for tracked runs', async () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-05-03T12:00:00.000Z')); const svc = new TeamProvisioningService(); @@ -3533,6 +3533,11 @@ describe('TeamProvisioningService', () => { vi.setSystemTime(new Date('2026-05-03T12:00:03.000Z')); await (svc as any).getLiveTeamAgentRuntimeMetadata('runtime-team'); + expect(listRuntimeProcessTableForCurrentPlatform).toHaveBeenCalledTimes(1); + + vi.setSystemTime(new Date('2026-05-03T12:00:06.000Z')); + await (svc as any).getLiveTeamAgentRuntimeMetadata('runtime-team'); + expect(listRuntimeProcessTableForCurrentPlatform).toHaveBeenCalledTimes(2); }); From 38ea035fb5cf750304fe435c943867081d66072a Mon Sep 17 00:00:00 2001 From: 777genius Date: Sun, 31 May 2026 01:39:57 +0300 Subject: [PATCH 4/5] perf(renderer): lazy render task tooltip content --- src/renderer/components/team/TaskTooltip.tsx | 133 +++++++++++-------- 1 file changed, 75 insertions(+), 58 deletions(-) diff --git a/src/renderer/components/team/TaskTooltip.tsx b/src/renderer/components/team/TaskTooltip.tsx index 5b287d88..ba887fc5 100644 --- a/src/renderer/components/team/TaskTooltip.tsx +++ b/src/renderer/components/team/TaskTooltip.tsx @@ -1,4 +1,4 @@ -import { memo, useMemo } from 'react'; +import { memo, useCallback, useMemo, useState } from 'react'; import { useAppTranslation } from '@features/localization/renderer'; import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer'; @@ -65,16 +65,36 @@ interface TaskTooltipProps { side?: 'top' | 'bottom' | 'left' | 'right'; } -/** - * Tooltip that shows task summary on hover over any #taskId link. - * Reads task data from the current team in the store. - */ export const TaskTooltip = memo(function TaskTooltip({ taskId, teamName, children, side = 'top', }: TaskTooltipProps): React.JSX.Element { + const [open, setOpen] = useState(false); + const handleOpenChange = useCallback((nextOpen: boolean) => { + setOpen(nextOpen); + }, []); + + return ( + + {children} + {open ? : null} + + ); +}); + +interface TaskTooltipContentProps { + taskId: string; + teamName?: string; + side: 'top' | 'bottom' | 'left' | 'right'; +} + +const TaskTooltipContent = memo(function TaskTooltipContent({ + taskId, + teamName, + side, +}: TaskTooltipContentProps): React.JSX.Element | null { const { t } = useAppTranslation('team'); const { selectedTeamName, selectedTeamData, selectedTeamMembers, globalTasks, teamByName } = useStore( @@ -127,7 +147,7 @@ export const TaskTooltip = memo(function TaskTooltip({ ); // If task not found, render children without tooltip - if (!task) return children; + if (!task) return null; const column = getEffectiveColumn(task); const statusColor = STATUS_COLORS[column] ?? STATUS_COLORS.pending; @@ -142,63 +162,60 @@ export const TaskTooltip = memo(function TaskTooltip({ : null; return ( - - {children} - - {resolvedTeamName ? ( -
- {resolvedTeamDisplayName || resolvedTeamName} -
- ) : null} - {/* Subject */} -
- {formatTaskDisplayLabel(task)}{' '} - {task.subject} + + {resolvedTeamName ? ( +
+ {resolvedTeamDisplayName || resolvedTeamName}
+ ) : null} + {/* Subject */} +
+ {formatTaskDisplayLabel(task)}{' '} + {task.subject} +
- {/* Status badge */} -
+ {/* Status badge */} +
+ + {label} + + {isTeamTaskNeedsFixActionable(task) ? ( - {label} + {REVIEW_STATE_DISPLAY.needsFix.label} - {isTeamTaskNeedsFixActionable(task) ? ( - - {REVIEW_STATE_DISPLAY.needsFix.label} - - ) : null} - - {/* Owner */} - {task.owner && members.length > 0 ? ( - - ) : task.owner ? ( - {task.owner} - ) : ( - - {t('tasks.unassigned')} - - )} -
- - {/* Description — full markdown with scroll */} - {task.description ? ( -
- -
) : null} - - + + {/* Owner */} + {task.owner && members.length > 0 ? ( + + ) : task.owner ? ( + {task.owner} + ) : ( + + {t('tasks.unassigned')} + + )} +
+ + {/* Description — full markdown with scroll */} + {task.description ? ( +
+ +
+ ) : null} +
); }); From 2d8966a6a6e79734084e0886dfbca3220c6b305c Mon Sep 17 00:00:00 2001 From: 777genius Date: Sun, 31 May 2026 01:45:57 +0300 Subject: [PATCH 5/5] 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);