From b87fb13e870dffd027a98ec141c2846ec931ab8e Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Mon, 12 Jan 2026 22:58:14 +0000 Subject: [PATCH] Dev server starting state (#1985) **Changes:** 1. Added `useState` and `useEffect` to imports 2. Added `pendingStart` state to track when mutation has started but no running process exists yet 3. Added `useEffect` that clears `pendingStart` when `runningDevServers.length > 0` 4. Added `onMutate` callback to set `pendingStart = true` when mutation starts 5. Updated `onError` to clear `pendingStart` on failure 6. Updated `isStarting` return value to `startMutation.isPending || pendingStart` **How it works:** - When the user clicks "Start", `onMutate` fires immediately and sets `pendingStart = true` - This makes `isStarting = true`, showing the "starting" state in the UI - The `pendingStart` state persists even after the API request completes - Only when a running dev server process appears in the data (via the `useEffect`), `pendingStart` is cleared - If the mutation fails, `onError` clears `pendingStart` immediately This ensures the "starting" state is visible until the dev server process actually appears, rather than just during the brief API request time. --- frontend/src/hooks/useDevServer.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/useDevServer.ts b/frontend/src/hooks/useDevServer.ts index 152bf202..f133cdd6 100644 --- a/frontend/src/hooks/useDevServer.ts +++ b/frontend/src/hooks/useDevServer.ts @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { attemptsApi, executionProcessesApi } from '@/lib/api'; import { useAttemptExecution } from '@/hooks/useAttemptExecution'; @@ -36,13 +36,27 @@ export function useDevServer( [attemptData.processes] ); + // Track when mutation succeeded but no running process exists yet + const [pendingStart, setPendingStart] = useState(false); + + // Clear pendingStart when a running process appears + useEffect(() => { + if (runningDevServers.length > 0 && pendingStart) { + setPendingStart(false); + } + }, [runningDevServers.length, pendingStart]); + const startMutation = useMutation({ mutationKey: ['startDevServer', attemptId], mutationFn: async () => { if (!attemptId) return; await attemptsApi.startDevServer(attemptId); }, + onMutate: () => { + setPendingStart(true); + }, onSuccess: async () => { + // Don't clear pendingStart here - wait for process to appear via useEffect await queryClient.invalidateQueries({ queryKey: ['executionProcesses', attemptId], }); @@ -50,6 +64,7 @@ export function useDevServer( options?.onStartSuccess?.(); }, onError: (err) => { + setPendingStart(false); console.error('Failed to start dev server:', err); options?.onStartError?.(err); }, @@ -86,7 +101,7 @@ export function useDevServer( return { start: startMutation.mutate, stop: stopMutation.mutate, - isStarting: startMutation.isPending, + isStarting: startMutation.isPending || pendingStart, isStopping: stopMutation.isPending, runningDevServers, devServerProcesses,