diff --git a/mcpjam-inspector/client/src/components/evals/commit-detail-view.tsx b/mcpjam-inspector/client/src/components/evals/commit-detail-view.tsx index cd563b2e1..a2c57cd76 100644 --- a/mcpjam-inspector/client/src/components/evals/commit-detail-view.tsx +++ b/mcpjam-inspector/client/src/components/evals/commit-detail-view.tsx @@ -207,10 +207,10 @@ export function CommitDetailView({ // Auto-request triage when failures exist and no result yet useEffect(() => { - if (failedRunIds.length > 0 && !aiTriage.summary && !aiTriage.loading) { + if (failedRunIds.length > 0 && !aiTriage.summary && !aiTriage.loading && !aiTriage.unavailable) { aiTriage.requestTriage(); } - }, [failedRunIds.length, aiTriage.summary, aiTriage.loading, aiTriage.requestTriage]); + }, [failedRunIds.length, aiTriage.summary, aiTriage.loading, aiTriage.unavailable, aiTriage.requestTriage]); // Triage summary — shows when any cases failed const triageSummary = useMemo(() => { @@ -372,7 +372,7 @@ export function CommitDetailView({ {/* === AI TRIAGE PANEL === */} - {triageSummary && (aiTriage.summary || aiTriage.loading || aiTriage.error) && ( + {triageSummary && !aiTriage.unavailable && (aiTriage.summary || aiTriage.loading || aiTriage.error) && (
diff --git a/mcpjam-inspector/client/src/components/evals/overview-panel.tsx b/mcpjam-inspector/client/src/components/evals/overview-panel.tsx index 3627233d6..df26c3155 100644 --- a/mcpjam-inspector/client/src/components/evals/overview-panel.tsx +++ b/mcpjam-inspector/client/src/components/evals/overview-panel.tsx @@ -276,12 +276,12 @@ export function OverviewPanel({ const aiOverviewTriage = useCommitTriage(failedOverviewRunIds); - // Auto-request triage when failures exist + // Auto-request triage when failures exist (skip if already unavailable) useEffect(() => { - if (failedOverviewRunIds.length > 0 && !aiOverviewTriage.summary && !aiOverviewTriage.loading) { + if (failedOverviewRunIds.length > 0 && !aiOverviewTriage.summary && !aiOverviewTriage.loading && !aiOverviewTriage.unavailable) { aiOverviewTriage.requestTriage(); } - }, [failedOverviewRunIds.length, aiOverviewTriage.summary, aiOverviewTriage.loading, aiOverviewTriage.requestTriage]); + }, [failedOverviewRunIds.length, aiOverviewTriage.summary, aiOverviewTriage.loading, aiOverviewTriage.unavailable, aiOverviewTriage.requestTriage]); // Pre-compute inline failure tags for the failure feed // Tags suites with failed cases OR failed result @@ -491,7 +491,7 @@ export function OverviewPanel({
{/* AI Overview Summary — only when failures exist and triage is active */} - {failedOverviewRunIds.length > 0 && (aiOverviewTriage.summary || aiOverviewTriage.loading || aiOverviewTriage.error) && ( + {failedOverviewRunIds.length > 0 && !aiOverviewTriage.unavailable && (aiOverviewTriage.summary || aiOverviewTriage.loading || aiOverviewTriage.error) && (
@@ -627,115 +627,102 @@ export function OverviewPanel({
)} - {/* Section C: Failure Feed (Needs Attention) */} - -
- -
- {failureFeedOpen && hasFailures ? ( - - ) : ( - - )} - Needs Attention - {hasFailures && ( + {/* Section C: Failure Feed (Needs Attention) — hidden when nothing needs attention */} + {hasFailures && ( + +
+ +
+ {failureFeedOpen ? ( + + ) : ( + + )} + Needs Attention ({failureEntries.length}) - )} -
- {!hasFailures && ( -
- - All clear
- )} -
- - -
- {failureEntries.map((entry) => { - const isFailed = entry.latestRun?.result === "failed"; - const isNeverRun = !entry.latestRun; - - return ( - - ); - })} -
-
-
-
+ + ); + })} +
+ +
+
+ )} {/* Section D: Suite Table */}
diff --git a/mcpjam-inspector/client/src/components/evals/use-ai-triage.ts b/mcpjam-inspector/client/src/components/evals/use-ai-triage.ts index 0b1db3913..7c9517f1f 100644 --- a/mcpjam-inspector/client/src/components/evals/use-ai-triage.ts +++ b/mcpjam-inspector/client/src/components/evals/use-ai-triage.ts @@ -1,4 +1,4 @@ -import { useState, useCallback, useMemo, useEffect } from "react"; +import { useState, useCallback, useEffect, useRef } from "react"; import { useMutation } from "convex/react"; // --------------------------------------------------------------------------- @@ -25,7 +25,7 @@ export interface TriageResult { * The backend generates the summary asynchronously (action → AI SDK → save). * * Until the backend mutation is deployed, requests fail gracefully and the - * panel shows "AI triage unavailable" instead of crashing. + * panel stays hidden instead of flashing briefly. */ export function useCommitTriage( failedRunIds: string[], @@ -33,6 +33,7 @@ export function useCommitTriage( summary: string | null; loading: boolean; error: string | null; + unavailable: boolean; requestTriage: () => void; } { const [loading, setLoading] = useState(false); @@ -40,23 +41,41 @@ export function useCommitTriage( const [summary, setSummary] = useState(null); const [unavailable, setUnavailable] = useState(false); - // Reset state when the run IDs change (navigating to a different commit) - const runKey = failedRunIds.join(","); - useEffect(() => { - setSummary(null); - setError(null); - setLoading(false); - setUnavailable(false); - }, [runKey]); - + // Track whether the mutation exists at the module level (survives re-renders) let requestTriageMutation: ReturnType | null = null; + let mutationExists = true; try { // eslint-disable-next-line react-hooks/rules-of-hooks requestTriageMutation = useMutation("testSuites:requestTriage" as any); } catch { // Mutation not registered yet — backend not deployed + mutationExists = false; } + // Once we know the mutation doesn't exist, mark unavailable permanently + // (no state update needed on subsequent renders since unavailable is already true) + const mutationExistsRef = useRef(mutationExists); + mutationExistsRef.current = mutationExists; + + useEffect(() => { + if (!mutationExistsRef.current) { + setUnavailable(true); + } + }, []); + + // Reset state when the run IDs change (navigating to a different commit), + // but preserve unavailable if the mutation doesn't exist + const runKey = failedRunIds.join(","); + useEffect(() => { + setSummary(null); + setError(null); + setLoading(false); + // Only reset unavailable if the mutation actually exists + if (mutationExistsRef.current) { + setUnavailable(false); + } + }, [runKey]); + const requestTriage = useCallback(() => { if (failedRunIds.length === 0 || unavailable) return; if (!requestTriageMutation) { @@ -94,5 +113,5 @@ export function useCommitTriage( }); }, [failedRunIds, requestTriageMutation, unavailable]); - return { summary, loading, error, requestTriage }; + return { summary, loading, error, unavailable, requestTriage }; }