From 5488255c33af1cecd1295ccf2df5f042c6ab3188 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Thu, 30 Jul 2026 11:21:28 -0700 Subject: [PATCH] fix: stop mutating refs during render for React Doctor Replace render-time ref writes in TasksPane sticky auto-expand and active project skill runtime identity caching with setState-during-render so static analysis can pass without changing behavior. --- .../src/components/settings/TasksPane.tsx | 16 ++++++++-------- .../src/hooks/useActiveProjectSkillRuntime.ts | 17 +++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/renderer/src/components/settings/TasksPane.tsx b/src/renderer/src/components/settings/TasksPane.tsx index 325628adbb2..c5b547b3850 100644 --- a/src/renderer/src/components/settings/TasksPane.tsx +++ b/src/renderer/src/components/settings/TasksPane.tsx @@ -1,4 +1,4 @@ -import { useRef } from 'react' +import { useState } from 'react' import { Github, Gitlab } from 'lucide-react' import type { GlobalSettings, TaskProvider } from '../../../../shared/types' import { @@ -103,17 +103,17 @@ export function TasksPane({ settings, updateSettings }: TasksPaneProps): React.J // Warn only about started-then-stalled setup; untouched providers are the default. const stalledVisible = getStalledVisibleTaskProviders(TASK_PROVIDERS, readinessByProvider) // Sticky across rechecks so expanded Linear install terminals are not unmounted. - const previousAutoExpandedRef = useRef(null) + // Claim during render (not an effect): a layout effect elsewhere can force a + // sync re-render before passive effects flush and collapse the open card. + // useState (not a ref write) keeps render pure for React Doctor. + const [previousAutoExpanded, setPreviousAutoExpanded] = useState(null) const autoExpandedProvider = resolveStickyAutoExpandedTaskProvider({ providers: TASK_PROVIDERS, readinessByProvider, - previousAutoExpanded: previousAutoExpandedRef.current + previousAutoExpanded }) - // Claim during render, not in an effect: a layout effect elsewhere can force a - // sync re-render before passive effects flush, and that render would see an - // unclaimed slot and collapse the open card. - if (autoExpandedProvider !== null && previousAutoExpandedRef.current === null) { - previousAutoExpandedRef.current = autoExpandedProvider + if (autoExpandedProvider !== null && previousAutoExpanded === null) { + setPreviousAutoExpanded(autoExpandedProvider) } const toggleProvider = (provider: TaskProvider): void => { diff --git a/src/renderer/src/hooks/useActiveProjectSkillRuntime.ts b/src/renderer/src/hooks/useActiveProjectSkillRuntime.ts index 3494ced3edb..9c78fa81e0b 100644 --- a/src/renderer/src/hooks/useActiveProjectSkillRuntime.ts +++ b/src/renderer/src/hooks/useActiveProjectSkillRuntime.ts @@ -1,4 +1,4 @@ -import { useMemo, useRef } from 'react' +import { useMemo, useState } from 'react' import { useShallow } from 'zustand/react/shallow' import type { ProjectExecutionRuntimeResolution } from '../../../shared/project-execution-runtime' import type { SkillDiscoveryTarget } from '../../../shared/skills' @@ -86,14 +86,15 @@ export function useActiveProjectSkillRuntime(): ActiveProjectSkillRuntime { } }, [currentPlatform, runtimeState, windowsCapabilities]) - const stableRef = useRef(resolved) - if ( - activeProjectSkillRuntimeIdentity(stableRef.current) !== - activeProjectSkillRuntimeIdentity(resolved) - ) { - stableRef.current = resolved + // Content-equal runtimes keep one reference so effect keys do not thrash. + // Adjust during render (not a ref write) when serialized identity changes. + const [stable, setStable] = useState(resolved) + const stableIdentity = activeProjectSkillRuntimeIdentity(stable) + const resolvedIdentity = activeProjectSkillRuntimeIdentity(resolved) + if (stableIdentity !== resolvedIdentity) { + setStable(resolved) } - return stableRef.current + return stableIdentity === resolvedIdentity ? stable : resolved } function getCurrentPlatform(): NodeJS.Platform {