From bfda5d9160aa1be6418f82c6fe92d64b7d368f41 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:10:13 -0700 Subject: [PATCH] fix(renderer): satisfy composer static analysis --- .../composer-state/async-composer-state.ts | 27 ++++++++++--------- .../composer-state/composer-drop-listener.ts | 4 ++- .../composer-state/host-runtime-effects.ts | 2 -- .../composer-state/initial-target-state.ts | 17 ++++++------ .../runtime-target-selection.ts | 6 +++-- .../source-selection-decisions.ts | 6 ++--- 6 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/renderer/src/hooks/composer-state/async-composer-state.ts b/src/renderer/src/hooks/composer-state/async-composer-state.ts index c0c2c5c78f5..2980576e3f7 100644 --- a/src/renderer/src/hooks/composer-state/async-composer-state.ts +++ b/src/renderer/src/hooks/composer-state/async-composer-state.ts @@ -82,7 +82,9 @@ export function useComposerAsyncState(input: ComposerAsyncStateInput) { const setupAgentStartupPolicyRef = useRef(setupAgentStartupPolicy) - setupAgentStartupPolicyRef.current = setupAgentStartupPolicy + useEffect(() => { + setupAgentStartupPolicyRef.current = setupAgentStartupPolicy + }, [setupAgentStartupPolicy]) const setupAgentStartupPolicySaveRef = useRef<{ repoId: string @@ -137,8 +139,6 @@ export function useComposerAsyncState(input: ComposerAsyncStateInput) { const nameRef = useRef(name) - nameRef.current = name - const branchAutoNameRef = useRef('') // Why: the note we auto-prefilled from a Start-from PR pick, so a later PR change can replace it without clobbering user-typed text. @@ -147,7 +147,10 @@ export function useComposerAsyncState(input: ComposerAsyncStateInput) { // Why: let handleBaseBranchPrSelect read the latest note without adding it to deps (would rebuild the callback on every keystroke). const noteRef = useRef(note) - noteRef.current = note + useEffect(() => { + nameRef.current = name + noteRef.current = note + }, [name, note]) // Why: PR checkout refs resolve async, so submit can still see the linked PR as a checkout source if Create fires before the resolver settles. const smartGitHubPrStartPointSelectionRef = useRef( @@ -187,16 +190,10 @@ export function useComposerAsyncState(input: ComposerAsyncStateInput) { // Why: mirror agentPrompt into a ref so the once-mounted file-drop listener reads it fresh without re-subscribing, which would reorder composerDropStack. const agentPromptRef = useRef(agentPrompt) - agentPromptRef.current = agentPrompt - const connectionIdRef = useRef(connectionId) - connectionIdRef.current = connectionId - const selectedRepoConnectionIdRef = useRef(selectedRepoConnectionId) - selectedRepoConnectionIdRef.current = selectedRepoConnectionId - // Why: compare the full host-aware identity before linking a pasted PR URL to this repo. const [selectedRepoSlug, setSelectedRepoSlug] = useState(null) @@ -204,11 +201,15 @@ export function useComposerAsyncState(input: ComposerAsyncStateInput) { const selectedRepoPathRef = useRef(selectedRepoPath) - selectedRepoPathRef.current = selectedRepoPath - const selectedRepoSettingsRef = useRef(selectedRepoSettings) - selectedRepoSettingsRef.current = selectedRepoSettings + useEffect(() => { + agentPromptRef.current = agentPrompt + connectionIdRef.current = connectionId + selectedRepoConnectionIdRef.current = selectedRepoConnectionId + selectedRepoPathRef.current = selectedRepoPath + selectedRepoSettingsRef.current = selectedRepoSettings + }, [agentPrompt, connectionId, selectedRepoConnectionId, selectedRepoPath, selectedRepoSettings]) return { yamlHooks, diff --git a/src/renderer/src/hooks/composer-state/composer-drop-listener.ts b/src/renderer/src/hooks/composer-state/composer-drop-listener.ts index b793e4653f6..145fe1a3c35 100644 --- a/src/renderer/src/hooks/composer-state/composer-drop-listener.ts +++ b/src/renderer/src/hooks/composer-state/composer-drop-listener.ts @@ -7,7 +7,9 @@ export function useComposerDropListener( applyDrop: (paths: string[], isCurrentOwner: () => boolean) => void ): void { const applyDropRef = useRef(applyDrop) - applyDropRef.current = applyDrop + useEffect(() => { + applyDropRef.current = applyDrop + }, [applyDrop]) const instanceIdRef = useRef(Symbol('composer')) useEffect(() => { diff --git a/src/renderer/src/hooks/composer-state/host-runtime-effects.ts b/src/renderer/src/hooks/composer-state/host-runtime-effects.ts index d8b7302d48d..e6fba662ea8 100644 --- a/src/renderer/src/hooks/composer-state/host-runtime-effects.ts +++ b/src/renderer/src/hooks/composer-state/host-runtime-effects.ts @@ -170,8 +170,6 @@ export function useHostRuntimeEffects(input: HostRuntimeEffectsInput) { return () => { cancelled = true } - // Why: repo identity fields stay stable when updateRepo replaces the repo object by reference. - // eslint-disable-next-line react-hooks/exhaustive-deps }, [ commitHookCheckIfCurrent, createGateMode, diff --git a/src/renderer/src/hooks/composer-state/initial-target-state.ts b/src/renderer/src/hooks/composer-state/initial-target-state.ts index 407bb2cd0df..32584d6992b 100644 --- a/src/renderer/src/hooks/composer-state/initial-target-state.ts +++ b/src/renderer/src/hooks/composer-state/initial-target-state.ts @@ -131,15 +131,11 @@ export function useComposerInitialTargetState(input: ComposerInitialTargetStateI [actionableHostIds, projectGroups, selectedProjectGroupId] ) - useEffect(() => { - if (selectedProjectGroupId && !selectedProjectGroup) { - setSelectedProjectGroupId(null) - } - }, [selectedProjectGroup, selectedProjectGroupId]) + const effectiveSelectedProjectGroupId = selectedProjectGroup ? selectedProjectGroupId : null useEffect(() => { if ( - selectedProjectGroupId || + effectiveSelectedProjectGroupId || !initialFolderProjectGroupId || initialProjectGroupAppliedRef.current ) { @@ -154,7 +150,12 @@ export function useComposerInitialTargetState(input: ComposerInitialTargetStateI initialProjectGroupAppliedRef.current = true setSelectedProjectGroupId(nextGroup.id) } - }, [actionableHostIds, initialFolderProjectGroupId, projectGroups, selectedProjectGroupId]) + }, [ + actionableHostIds, + effectiveSelectedProjectGroupId, + initialFolderProjectGroupId, + projectGroups + ]) return { draftRepoId, @@ -172,7 +173,7 @@ export function useComposerInitialTargetState(input: ComposerInitialTargetStateI setSelectedProjectHostSetupOverrideId, initialFolderProjectGroupId, initialFolderProjectGroup, - selectedProjectGroupId, + selectedProjectGroupId: effectiveSelectedProjectGroupId, setSelectedProjectGroupId, initialProjectGroupAppliedRef, projectError, diff --git a/src/renderer/src/hooks/composer-state/runtime-target-selection.ts b/src/renderer/src/hooks/composer-state/runtime-target-selection.ts index 18780575448..7d6fe1c4a2d 100644 --- a/src/renderer/src/hooks/composer-state/runtime-target-selection.ts +++ b/src/renderer/src/hooks/composer-state/runtime-target-selection.ts @@ -1,6 +1,6 @@ import type { ComposerRuntimeTargetSelectionInput } from './composer-target-input-contracts' -import { useMemo, useRef } from 'react' +import { useEffect, useMemo, useRef } from 'react' import { getFolderSourceRepos } from '@/components/sidebar/folder-workspace-composer-helpers' import { parseExecutionHostId, getRepoExecutionHostId } from '../../../../shared/execution-host' import { getSelectedRepoSshGate } from '@/lib/new-workspace-ssh-gate' @@ -240,7 +240,9 @@ export function useComposerRuntimeTargetSelection(input: ComposerRuntimeTargetSe const repoIdRef = useRef(repoId) - repoIdRef.current = repoId + useEffect(() => { + repoIdRef.current = repoId + }, [repoId]) return { isProjectGroupTarget, diff --git a/src/renderer/src/hooks/composer-state/source-selection-decisions.ts b/src/renderer/src/hooks/composer-state/source-selection-decisions.ts index 47012e5f25c..5977e7bf2c4 100644 --- a/src/renderer/src/hooks/composer-state/source-selection-decisions.ts +++ b/src/renderer/src/hooks/composer-state/source-selection-decisions.ts @@ -1,13 +1,11 @@ -import type { GitHubPrStartPoint } from '../../../../shared/worktree/types' +import type { GitHubPrStartPoint, GitPushTarget } from '../../../../shared/worktree/types' import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' -import type { LinkedWorkItemSummary } from '@/lib/new-workspace' +import { getLinkedWorkItemProvider, type LinkedWorkItemSummary } from '@/lib/new-workspace' import type { SmartGitHubSubmitResolution } from '@/lib/smart-github-submit' import { resolveGitHubWorkItemIdentity, type GitHubWorkItemIdentity } from '@/lib/github-work-item-identity' -import { getLinkedWorkItemProvider } from '@/lib/new-workspace' -import type { GitPushTarget } from '../../../../shared/worktree/types' export type PendingSmartGitHubSubmitResolution = | { kind: 'none' }