fix: guard create project state (#3680)

This commit is contained in:
Neil
2026-05-30 05:57:56 -07:00
committed by GitHub
parent 2e280f2571
commit 3fabdcb584
@@ -1,14 +1,9 @@
/**
* Step for AddRepoDialog (orca#763).
*
* Split from AddRepoDialog and AddRepoSteps to keep both under the 400-line
* oxlint limit, following the same pattern as useRemoteRepo.
*/
// Step for AddRepoDialog (orca#763), split out so create-project state stays scoped.
import React, { useCallback, useRef, useState } from 'react'
import { toast } from 'sonner'
import { Folder, GitBranch, Home, Pencil } from 'lucide-react'
import { useAppStore } from '@/store'
import { useMountedRef } from '@/hooks/useMountedRef'
import { DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
@@ -33,6 +28,7 @@ export function useCreateRepo(
const [createKind, setCreateKind] = useState<RepoKind>('git')
const [createError, setCreateError] = useState<string | null>(null)
const [isCreating, setIsCreating] = useState(false)
const mountedRef = useMountedRef()
// Why: monotonic ID so stale create callbacks can detect they were superseded
// when the user clicks Back or closes the dialog mid-create. Mirrors the
@@ -57,11 +53,11 @@ export function useCreateRepo(
}
const gen = createGenRef.current
const dir = await window.api.repos.pickDirectory()
if (dir && gen === createGenRef.current) {
if (dir && gen === createGenRef.current && mountedRef.current) {
setCreateParent(dir)
setCreateError(null)
}
}, [])
}, [mountedRef])
const handleCreate = useCallback(async () => {
const name = createName.trim()
@@ -73,8 +69,7 @@ export function useCreateRepo(
setIsCreating(true)
setCreateError(null)
try {
const settings = useAppStore.getState().settings
const target = getActiveRuntimeTarget(settings)
const target = getActiveRuntimeTarget(useAppStore.getState().settings)
const result =
target.kind === 'environment'
? await callRuntimeRpc<{ repo: Repo } | { error: string }>(
@@ -94,7 +89,7 @@ export function useCreateRepo(
})
// Why: if the user closed the dialog or clicked Back mid-create,
// createGenRef was bumped by resetCreateState. Ignore stale results.
if (gen !== createGenRef.current) {
if (gen !== createGenRef.current || !mountedRef.current) {
return
}
if ('error' in result) {
@@ -133,7 +128,7 @@ export function useCreateRepo(
setAddedRepo(repo)
setExistingWorkspaceSource?.('create_project')
await fetchWorktrees(repo.id)
if (gen !== createGenRef.current) {
if (gen !== createGenRef.current || !mountedRef.current) {
return
}
setStep('setup')
@@ -141,7 +136,7 @@ export function useCreateRepo(
// Why: folder repos skip the Git setup step, so activate the synthetic
// root workspace before closing. Matches addNonGitFolder's behavior.
await fetchWorktrees(repo.id)
if (gen !== createGenRef.current) {
if (gen !== createGenRef.current || !mountedRef.current) {
return
}
const folderWorktree = useAppStore.getState().worktreesByRepo[repo.id]?.[0]
@@ -151,15 +146,14 @@ export function useCreateRepo(
closeModal()
}
} catch (err) {
if (gen !== createGenRef.current) {
if (gen !== createGenRef.current || !mountedRef.current) {
return
}
const message = err instanceof Error ? err.message : String(err)
setCreateError(message)
setCreateError(err instanceof Error ? err.message : String(err))
} finally {
// Why: only clear the loading state if this invocation is still current;
// a superseded create must not flip the flag back off for a new flow.
if (gen === createGenRef.current) {
if (gen === createGenRef.current && mountedRef.current) {
setIsCreating(false)
}
}
@@ -168,6 +162,7 @@ export function useCreateRepo(
createParent,
createKind,
fetchWorktrees,
mountedRef,
setStep,
setAddedRepo,
closeModal,
@@ -327,8 +322,7 @@ export function CreateStep({
})
}, [cancelRadioFocusFrame, createKind, onKindChange])
const trimmedName = createName.trim()
const canSubmit = trimmedName.length > 0 && createParent.trim().length > 0 && !isCreating
const canSubmit = createName.trim().length > 0 && createParent.trim().length > 0 && !isCreating
return (
<>