diff --git a/web-ui/package.json b/web-ui/package.json index e69d02b4..d37e3a23 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -6,7 +6,7 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint .", + "lint": "eslint . --max-warnings 0", "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", diff --git a/web-ui/src/app/proof/[req_id]/page.tsx b/web-ui/src/app/proof/[req_id]/page.tsx index cbb52d76..c07b48ae 100644 --- a/web-ui/src/app/proof/[req_id]/page.tsx +++ b/web-ui/src/app/proof/[req_id]/page.tsx @@ -85,7 +85,7 @@ export default function ProofDetailPage() { setFilterGate(saved.gate); setFilterResult(saved.result); setSearch(saved.search); - }, []); + }, [reqId]); const { data: req, error: reqError, isLoading: reqLoading, mutate: mutateReq } = useSWR( diff --git a/web-ui/src/components/execution/BatchExecutionMonitor.tsx b/web-ui/src/components/execution/BatchExecutionMonitor.tsx index f5ca025b..75c2de0e 100644 --- a/web-ui/src/components/execution/BatchExecutionMonitor.tsx +++ b/web-ui/src/components/execution/BatchExecutionMonitor.tsx @@ -88,15 +88,16 @@ export function BatchExecutionMonitor({ batchId, workspacePath }: BatchExecution }, [batchId, workspacePath]); // eslint-disable-line react-hooks/exhaustive-deps // Poll every 5 seconds while batch is active + const batchStatus = batch?.status; useEffect(() => { - const isActive = batch && !['COMPLETED', 'FAILED', 'CANCELLED'].includes(batch.status); + const isActive = batchStatus && !['COMPLETED', 'FAILED', 'CANCELLED'].includes(batchStatus); if (isActive) { pollRef.current = setInterval(fetchBatch, 5000); } return () => { if (pollRef.current) clearInterval(pollRef.current); }; - }, [batch?.status, fetchBatch]); + }, [batchStatus, fetchBatch]); // Note: batch.completed / blocker.created notifications are dispatched by the // cross-page background watcher in NotificationProvider (issue #652), so they diff --git a/web-ui/src/components/prd/DiscoveryPanel.tsx b/web-ui/src/components/prd/DiscoveryPanel.tsx index 0a78a1e7..3f024670 100644 --- a/web-ui/src/components/prd/DiscoveryPanel.tsx +++ b/web-ui/src/components/prd/DiscoveryPanel.tsx @@ -48,6 +48,28 @@ export function DiscoveryPanel({ const [error, setError] = useState(null); const [pendingSession, setPendingSession] = useState(null); + // ─── Start a brand-new session ───────────────────────────────── + // Declared before initSession so it can be a stable dependency of it. + const startNewSession = useCallback(async () => { + setError(null); + setPendingSession(null); + try { + const resp = await discoveryApi.start(workspacePath); + setSessionId(resp.session_id); + setState('discovering'); + setMessages([ + { + role: 'assistant', + content: questionText(resp.question), + timestamp: now(), + }, + ]); + } catch (err) { + const apiErr = err as ApiError; + setError(apiErr.detail || 'Failed to start discovery session'); + } + }, [workspacePath]); + // ─── Initialise: check for existing session before starting ──── const initSession = useCallback(async () => { setIsThinking(true); @@ -80,28 +102,7 @@ export function DiscoveryPanel({ } finally { setIsThinking(false); } - }, [workspacePath]); - - // ─── Start a brand-new session ───────────────────────────────── - const startNewSession = useCallback(async () => { - setError(null); - setPendingSession(null); - try { - const resp = await discoveryApi.start(workspacePath); - setSessionId(resp.session_id); - setState('discovering'); - setMessages([ - { - role: 'assistant', - content: questionText(resp.question), - timestamp: now(), - }, - ]); - } catch (err) { - const apiErr = err as ApiError; - setError(apiErr.detail || 'Failed to start discovery session'); - } - }, [workspacePath]); + }, [workspacePath, startNewSession]); // ─── Resume an existing session ──────────────────────────────── const resumeSession = useCallback(() => {