mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(web): prevent duplicate session creation (#1222)
This commit is contained in:
@@ -13,6 +13,7 @@ const mocks = vi.hoisted(() => ({
|
||||
spawnSession: vi.fn(),
|
||||
onSuccess: vi.fn(),
|
||||
notification: vi.fn(),
|
||||
checkPathsExists: vi.fn(),
|
||||
codexModelsLoading: false,
|
||||
directoryExists: undefined as boolean | undefined
|
||||
}))
|
||||
@@ -44,7 +45,7 @@ vi.mock('@/hooks/useRecentPaths', () => ({
|
||||
vi.mock('@/hooks/useMachinePathsExists', () => ({
|
||||
useMachinePathsExists: () => ({
|
||||
pathExistence: { 'C:\\repo': mocks.directoryExists },
|
||||
checkPathsExists: async () => ({ 'C:\\repo': mocks.directoryExists })
|
||||
checkPathsExists: mocks.checkPathsExists
|
||||
})
|
||||
}))
|
||||
vi.mock('@/hooks/useDirectorySuggestions', () => ({
|
||||
@@ -152,6 +153,8 @@ describe('NewSession launch preferences', () => {
|
||||
mocks.spawnSession.mockReset()
|
||||
mocks.onSuccess.mockReset()
|
||||
mocks.notification.mockReset()
|
||||
mocks.checkPathsExists.mockReset()
|
||||
mocks.checkPathsExists.mockImplementation(async () => ({ 'C:\\repo': mocks.directoryExists }))
|
||||
mocks.codexModelsLoading = false
|
||||
mocks.directoryExists = true
|
||||
savePreferredAgent('codex')
|
||||
@@ -276,6 +279,34 @@ describe('NewSession launch preferences', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns only once when Create is activated twice during directory validation', async () => {
|
||||
let finishDirectoryCheck!: (result: Record<string, boolean>) => void
|
||||
mocks.checkPathsExists.mockReturnValue(new Promise((resolve) => {
|
||||
finishDirectoryCheck = resolve
|
||||
}))
|
||||
mocks.spawnSession.mockResolvedValue({ type: 'success', sessionId: 'session-1' })
|
||||
|
||||
render(
|
||||
<NewSession
|
||||
api={api}
|
||||
machines={[machine]}
|
||||
initialMachineId="machine-1"
|
||||
initialDirectory="C:\\repo"
|
||||
onSuccess={mocks.onSuccess}
|
||||
onCancel={() => {}}
|
||||
/>
|
||||
)
|
||||
|
||||
const create = screen.getByTestId('create')
|
||||
fireEvent.click(create)
|
||||
fireEvent.click(create)
|
||||
finishDirectoryCheck({ 'C:\\repo': true })
|
||||
|
||||
await waitFor(() => expect(mocks.onSuccess).toHaveBeenCalledWith('session-1'))
|
||||
expect(mocks.checkPathsExists).toHaveBeenCalledTimes(1)
|
||||
expect(mocks.spawnSession).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not save changed launch settings when creation fails', async () => {
|
||||
mocks.spawnSession.mockResolvedValue({ type: 'error', message: 'spawn failed' })
|
||||
|
||||
|
||||
@@ -147,7 +147,9 @@ export function NewSession(props: {
|
||||
const [codexImportError, setCodexImportError] = useState<string | null>(null)
|
||||
const [isImportingCodexSession, setIsImportingCodexSession] = useState(false)
|
||||
const [isCodexImportDialogOpen, setIsCodexImportDialogOpen] = useState(false)
|
||||
const isFormDisabled = Boolean(isPending || props.isLoading || isImportingCodexSession)
|
||||
const [isCreating, setIsCreating] = useState(false)
|
||||
const createInFlightRef = useRef(false)
|
||||
const isFormDisabled = Boolean(isCreating || isPending || props.isLoading || isImportingCodexSession)
|
||||
const worktreeInputRef = useRef<HTMLInputElement>(null)
|
||||
const preserveRestoredDraftRef = useRef(false)
|
||||
|
||||
@@ -877,8 +879,10 @@ export function NewSession(props: {
|
||||
}, [suggestions, selectedIndex, moveUp, moveDown, clearSuggestions, handleSuggestionSelect])
|
||||
|
||||
async function handleCreate() {
|
||||
if (!machineId || !trimmedDirectory) return
|
||||
if (!machineId || !trimmedDirectory || createInFlightRef.current) return
|
||||
|
||||
createInFlightRef.current = true
|
||||
setIsCreating(true)
|
||||
setError(null)
|
||||
try {
|
||||
const existsResult = await checkPathsExists([trimmedDirectory])
|
||||
@@ -998,6 +1002,9 @@ export function NewSession(props: {
|
||||
setIsImportingCodexSession(false)
|
||||
haptic.notification('error')
|
||||
setError(e instanceof Error ? e.message : 'Failed to create session')
|
||||
} finally {
|
||||
createInFlightRef.current = false
|
||||
setIsCreating(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1217,7 +1224,7 @@ export function NewSession(props: {
|
||||
) : null}
|
||||
|
||||
<ActionButtons
|
||||
isPending={isPending || isImportingCodexSession}
|
||||
isPending={isCreating || isPending || isImportingCodexSession}
|
||||
canCreate={canCreate}
|
||||
isDisabled={isFormDisabled}
|
||||
createLabel={createLabel}
|
||||
|
||||
Reference in New Issue
Block a user