Skip to content

Commit 33b2c0d

Browse files
resolve
1 parent 66bfb46 commit 33b2c0d

File tree

4 files changed

+52
-51
lines changed

4 files changed

+52
-51
lines changed

mcpjam-inspector/.env.local

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
VITE_CONVEX_URL=https://proper-clownfish-150.convex.cloud
2-
CONVEX_URL=https://proper-clownfish-150.convex.cloud
1+
VITE_CONVEX_URL=https://quiet-woodpecker-801.convex.cloud
2+
CONVEX_URL=https://quiet-woodpecker-801.convex.cloud
33
VITE_WORKOS_CLIENT_ID=client_01K4C1TVA6CMQ3G32F1P301A9G
44
VITE_WORKOS_REDIRECT_URI=mcpjam://oauth/callback
5-
CONVEX_HTTP_URL=https://proper-clownfish-150.convex.site
5+
CONVEX_HTTP_URL=https://quiet-woodpecker-801.convex.site
66
ENVIRONMENT=local
77
VITE_DISABLE_POSTHOG_LOCAL=true

mcpjam-inspector/client/src/components/evals/ci-suite-list-sidebar.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,18 @@ export function CiSuiteListSidebar({
114114
? suites.filter((e) => e.suite.tags?.includes(filterTag))
115115
: suites;
116116

117-
// Group suites by name, keeping the most recent one as the "primary" entry
117+
// Group suites by base name (strip trailing timestamps/parenthetical suffixes
118+
// that some SDK users append, e.g. "Suite Name (2026-03-12 15:20:43)")
118119
const groupedSuites = useMemo(() => {
119120
const groups = new Map<string, EvalSuiteOverviewEntry[]>();
120121
for (const entry of filteredSuites) {
121-
const name = entry.suite.name || "Untitled suite";
122-
if (!groups.has(name)) {
123-
groups.set(name, []);
122+
const rawName = entry.suite.name || "Untitled suite";
123+
// Strip trailing " (YYYY-MM-DD ...)" or " (timestamp)" patterns
124+
const baseName = rawName.replace(/\s*\(\d{4}-\d{2}-\d{2}[^)]*\)\s*$/, "").trim() || rawName;
125+
if (!groups.has(baseName)) {
126+
groups.set(baseName, []);
124127
}
125-
groups.get(name)!.push(entry);
128+
groups.get(baseName)!.push(entry);
126129
}
127130
// Sort each group by latest run time (most recent first)
128131
for (const entries of groups.values()) {

mcpjam-inspector/client/src/components/evals/overview-panel.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import { useCommitTriage } from "./use-ai-triage";
3333
// Helpers
3434
// ---------------------------------------------------------------------------
3535

36+
/** Strip trailing timestamp suffixes from suite names for display, e.g. "Suite (2026-03-12 15:20:43)" → "Suite" */
37+
function stripTimestampSuffix(name: string): string {
38+
return name.replace(/\s*\(\d{4}-\d{2}-\d{2}[^)]*\)\s*$/, "").trim() || name;
39+
}
40+
3641
function toPercent(value: number): number {
3742
const n = value <= 1 ? value * 100 : value;
3843
return Math.max(0, Math.min(100, Math.round(n)));
@@ -277,12 +282,18 @@ export function OverviewPanel({
277282

278283
const aiOverviewTriage = useCommitTriage(failedOverviewRunIds);
279284

280-
// Auto-request triage when failures exist (skip if already unavailable)
285+
// Auto-request triage when failures exist (skip if already unavailable or errored)
281286
useEffect(() => {
282-
if (failedOverviewRunIds.length > 0 && !aiOverviewTriage.summary && !aiOverviewTriage.loading && !aiOverviewTriage.unavailable) {
287+
if (
288+
failedOverviewRunIds.length > 0 &&
289+
!aiOverviewTriage.summary &&
290+
!aiOverviewTriage.loading &&
291+
!aiOverviewTriage.unavailable &&
292+
!aiOverviewTriage.error
293+
) {
283294
aiOverviewTriage.requestTriage();
284295
}
285-
}, [failedOverviewRunIds.length, aiOverviewTriage.summary, aiOverviewTriage.loading, aiOverviewTriage.unavailable, aiOverviewTriage.requestTriage]);
296+
}, [failedOverviewRunIds.length, aiOverviewTriage.summary, aiOverviewTriage.loading, aiOverviewTriage.unavailable, aiOverviewTriage.error, aiOverviewTriage.requestTriage]);
286297

287298
// Pre-compute inline failure tags for the failure feed
288299
// Tags suites with failed cases OR failed result
@@ -670,7 +681,7 @@ export function OverviewPanel({
670681
<div className="min-w-0 flex-1">
671682
<div className="flex items-center gap-1.5">
672683
<span className="text-sm font-medium truncate">
673-
{entry.suite.name}
684+
{stripTimestampSuffix(entry.suite.name)}
674685
</span>
675686
{isFailed &&
676687
failureTagMap.get(entry.suite._id)?.map((tag) => (
@@ -861,7 +872,7 @@ export function OverviewPanel({
861872
{/* Suite name */}
862873
<div className="min-w-0">
863874
<div className="text-sm font-medium truncate">
864-
{entry.suite.name || "Untitled suite"}
875+
{stripTimestampSuffix(entry.suite.name) || "Untitled suite"}
865876
</div>
866877
</div>
867878

mcpjam-inspector/client/src/components/evals/use-ai-triage.ts

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -40,49 +40,31 @@ export function useCommitTriage(
4040
const [error, setError] = useState<string | null>(null);
4141
const [summary, setSummary] = useState<string | null>(null);
4242
const [unavailable, setUnavailable] = useState(false);
43+
const hasAttemptedRef = useRef(false);
4344

44-
// Track whether the mutation exists at the module level (survives re-renders)
45-
let requestTriageMutation: ReturnType<typeof useMutation> | null = null;
46-
let mutationExists = true;
47-
try {
48-
// eslint-disable-next-line react-hooks/rules-of-hooks
49-
requestTriageMutation = useMutation("testSuites:requestTriage" as any);
50-
} catch {
51-
// Mutation not registered yet — backend not deployed
52-
mutationExists = false;
53-
}
45+
// Always call useMutation (React hooks rules) — if the function doesn't
46+
// exist on the backend, the call itself will fail, which we handle below.
47+
const requestTriageMutation = useMutation("testSuites:requestTriage" as any);
5448

55-
// Once we know the mutation doesn't exist, mark unavailable permanently
56-
// (no state update needed on subsequent renders since unavailable is already true)
57-
const mutationExistsRef = useRef(mutationExists);
58-
mutationExistsRef.current = mutationExists;
59-
60-
useEffect(() => {
61-
if (!mutationExistsRef.current) {
62-
setUnavailable(true);
63-
}
64-
}, []);
65-
66-
// Reset state when the run IDs change (navigating to a different commit),
67-
// but preserve unavailable if the mutation doesn't exist
49+
// Reset state when the run IDs actually change (navigating to a different commit)
6850
const runKey = failedRunIds.join(",");
51+
const prevRunKeyRef = useRef(runKey);
6952
useEffect(() => {
70-
setSummary(null);
71-
setError(null);
72-
setLoading(false);
73-
// Only reset unavailable if the mutation actually exists
74-
if (mutationExistsRef.current) {
75-
setUnavailable(false);
53+
if (prevRunKeyRef.current !== runKey) {
54+
prevRunKeyRef.current = runKey;
55+
setSummary(null);
56+
setError(null);
57+
setLoading(false);
58+
hasAttemptedRef.current = false;
59+
// Keep unavailable sticky — if the mutation doesn't exist, it won't
60+
// magically appear when switching commits
7661
}
7762
}, [runKey]);
7863

7964
const requestTriage = useCallback(() => {
80-
if (failedRunIds.length === 0 || unavailable) return;
81-
if (!requestTriageMutation) {
82-
setUnavailable(true);
83-
return;
84-
}
65+
if (failedRunIds.length === 0 || unavailable || hasAttemptedRef.current) return;
8566

67+
hasAttemptedRef.current = true;
8668
setLoading(true);
8769
setError(null);
8870

@@ -96,16 +78,21 @@ export function useCommitTriage(
9678
setLoading(false);
9779
} else {
9880
// Backend will generate async — for now show as pending
99-
// In future, a reactive query subscription will update this
10081
setLoading(false);
101-
setError("Triage requested — results will appear when backend processing completes.");
82+
setSummary("Triage requested — results will appear when backend processing completes.");
10283
}
10384
})
10485
.catch((err: unknown) => {
10586
const message = err instanceof Error ? err.message : String(err);
106-
// Detect "function not found" errors from Convex
107-
if (message.includes("Could not find") || message.includes("not found")) {
87+
// Detect backend errors that mean triage isn't available — mark permanently unavailable
88+
if (
89+
message.includes("Could not find") ||
90+
message.includes("not found") ||
91+
message.includes("is not a function") ||
92+
message.includes("Server Error")
93+
) {
10894
setUnavailable(true);
95+
setError(null);
10996
} else {
11097
setError(message);
11198
}

0 commit comments

Comments
 (0)