diff --git a/src/components/legal/RecursionBox.tsx b/src/components/legal/RecursionBox.tsx index 04d92fdf90..da7f46c346 100644 --- a/src/components/legal/RecursionBox.tsx +++ b/src/components/legal/RecursionBox.tsx @@ -393,16 +393,19 @@ function RecursionCard({ entry, refetch, allRuns }: RecursionCardProps) { // `allRuns` is lifted from RecursionTab (via RecursionList) so the whole tab // shares one fetch-and-poll loop instead of one per card. - // Find the most recent CONSOLIDATED run for this taskSlug. + // Most recent CONSOLIDATED run for this task, any status — seeds + // effectiveConsolidatedRunId so a refresh shows either "Generating…" for an + // in-flight run, or the "View Consolidated Report" link for a completed one. + // Filtering on `pipeline` (not `runType`) is required: `runType` collapses + // EVAL/RECURSION/CONSOLIDATED down to the same "recursion" string, so it + // cannot by itself distinguish a consolidated-report run from an unrelated + // analysis/fix-proposal run for the same task. const existingConsolidated = useMemo(() => { return (allRuns ?? []) .filter( (r) => r.taskSlug === entry.id && - r.runType === "recursion" && - (r.status === WorkflowStatus.PENDING || - r.status === WorkflowStatus.IN_PROGRESS) && - !r.hasReport, + r.pipeline === StakworkRunType.LEGAL_BENCHMARK_CONSOLIDATED, ) .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0] ?? null; }, [allRuns, entry.id]); diff --git a/src/hooks/useLegalBenchmarkRunList.ts b/src/hooks/useLegalBenchmarkRunList.ts index 5cbee35a0d..fa9a90e57b 100644 --- a/src/hooks/useLegalBenchmarkRunList.ts +++ b/src/hooks/useLegalBenchmarkRunList.ts @@ -65,6 +65,15 @@ export interface BenchmarkRunListRow { generateRunReport?: boolean; /** This run has a report bundle. Derived server-side from reportUrl. */ hasReport?: boolean; + /** + * The run's raw Stakwork pipeline type (LEGAL_BENCHMARK_EVAL / + * LEGAL_BENCHMARK_RECURSION / LEGAL_BENCHMARK_CONSOLIDATED). Distinct from + * `runType`, which collapses all three into "recursion" for display — + * `pipeline` is what lets callers (e.g. RecursionCard's consolidated-report + * lookup) tell a CONSOLIDATED run apart from an EVAL or RECURSION run for + * the same task. + */ + pipeline?: StakworkRunType; } interface UseLegalBenchmarkRunListResult { @@ -152,12 +161,17 @@ export function useLegalBenchmarkRunList( const rawRecursionRows: RawRunRow[] = recursionData?.runs ?? []; const rawConsolidatedRows: RawRunRow[] = consolidatedData?.runs ?? []; - const mapSecondary = (r: RawRunRow, runType: BenchmarkRunType): BenchmarkRunListRow => { + const mapSecondary = ( + r: RawRunRow, + runType: BenchmarkRunType, + pipeline: StakworkRunType, + ): BenchmarkRunListRow => { const parsed = parseBenchmarkRunResult(r.result); return { id: r.id, workspaceId: r.workspaceId, runType, + pipeline, status: r.status as WorkflowStatus, projectId: r.projectId, taskSlug: parsed?.taskSlug ?? "", @@ -222,14 +236,14 @@ export function useLegalBenchmarkRunList( const merged = [ ...mapped, - ...rawEvalRows.map((r) => mapSecondary(r, "recursion")), - ...rawRecursionRows.map((r) => mapSecondary(r, "recursion")), + ...rawEvalRows.map((r) => mapSecondary(r, "recursion", StakworkRunType.LEGAL_BENCHMARK_EVAL)), + ...rawRecursionRows.map((r) => mapSecondary(r, "recursion", StakworkRunType.LEGAL_BENCHMARK_RECURSION)), // CONSOLIDATED rows are merged so Pusher updates for them flow through // the existing channel subscription without new polling logic, enabling // RecursionCard to surface in-flight / completed consolidated report // status after a page refresh. They are not surfaced in the Runs tab // table — the runType tag keeps them invisible there. - ...rawConsolidatedRows.map((r) => mapSecondary(r, "recursion")), + ...rawConsolidatedRows.map((r) => mapSecondary(r, "recursion", StakworkRunType.LEGAL_BENCHMARK_CONSOLIDATED)), ].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); runsRef.current = merged;