fix(lens): feed the live run from the reviews endpoint and keep View run open

LiveRun now gets its reviews from useJobReviews instead of the list, which no longer carries them. View run stays clickable while a run is queued or running, and before the first trace the opened view says what the worker is doing.
This commit is contained in:
Ishaan Jaff 2026-10-03 17:40:35 -07:00
parent 2f5fb3ef44
commit 7ef76eabff
No known key found for this signature in database
8 changed files with 65 additions and 12 deletions

View file

@ -282,6 +282,7 @@ export function InvestigationsView({
results={results}
agents={Array.isArray(agents.data) ? agents.data : []}
queue={{
api,
lenses,
workers: query.data?.workers ?? [],
onConnect: () => setWorkerSetup(true),

View file

@ -7,6 +7,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { RunsTab } from "./RunsTab";
import { InvestigationProgress } from "../InvestigationProgress";
import { LiveRun } from "../live/LiveRun";
import { LiveRunLoader } from "../live/LiveRunLoader";
import { type QueueContext } from "../useQueueReason";
import { liveJob } from "../../model/live";
import { RunNowDialog } from "./RunNowDialog";
@ -109,7 +110,12 @@ export function InvestigationDetail({
onCancel={readOnly ? undefined : () => void update((api) => api.cancelRun(lens.id))}
/>
)}
{live && <LiveRun key={live.id} job={live} reviews={live.reviews} name={lens.settings.name} queue={queue} />}
{live &&
(queue ? (
<LiveRunLoader key={live.id} lensId={lens.id} job={live} name={lens.settings.name} queue={queue} />
) : (
<LiveRun key={live.id} job={live} reviews={live.reviews} name={lens.settings.name} />
))}
{runNow && (
<RunNowDialog
lens={lens}

View file

@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useState, type ReactNode } from "react";
import { StatusBadge, type StatusTone } from "@/components/shared/table_cells";
import { Button } from "@/components/ui/button";
@ -38,9 +38,11 @@ export function LiveDrawer({
groups,
decided,
scope,
waiting,
onPick,
onFollow,
}: {
waiting?: ReactNode;
open: boolean;
onClose: () => void;
name: string;
@ -86,7 +88,10 @@ export function LiveDrawer({
phase={following ? phase : SETTLED}
/>
) : (
<p className="py-6 text-[12px] text-muted-foreground">Waiting for the first trace review.</p>
<div className="flex flex-col gap-1 py-6 text-[12px] text-muted-foreground">
<p className="text-[13px] text-foreground">Waiting for the first trace…</p>
{waiting}
</div>
)}
</section>
<section aria-label="Conclusions so far" className="flex min-h-0 flex-col gap-5 overflow-y-auto px-5 py-4">

View file

@ -50,6 +50,12 @@ export function LiveRun({
if (review) pin(review);
setDrawerOpen(true);
};
const waiting = reason && (
<>
<QueueReasonText reason={reason} onConnect={queue?.onConnect} />
<WorkerTasks reason={reason} queue={queue} />
</>
);
return (
<>
@ -57,14 +63,7 @@ export function LiveRun({
<LiveStrip
model={model}
state={stripState(withReviews, model, reason ? queueReasonText(reason) : undefined)}
waiting={
reason && (
<>
<QueueReasonText reason={reason} onConnect={queue?.onConnect} />
<WorkerTasks reason={reason} queue={queue} />
</>
)
}
waiting={waiting}
playback={playback}
reviewed={reviewed}
selected={job.coverage.selected}
@ -97,6 +96,7 @@ export function LiveRun({
groups={conclusions(decided, job.settings.checks)}
decided={decided.length}
scope={job.reviewed > reviews.length ? `From the latest ${reviews.length} of ${job.reviewed} reviewed traces` : ""}
waiting={waiting}
onPick={pin}
onFollow={() => setPinned(null)}
/>

View file

@ -0,0 +1,21 @@
"use client";
import type { Job } from "../../model/types";
import type { QueueContext } from "../useQueueReason";
import { LiveRun } from "./LiveRun";
import { useJobReviews } from "./useJobReviews";
export function LiveRunLoader({
lensId,
job,
name,
queue,
}: {
lensId: string;
job: Job;
name: string;
queue: QueueContext;
}) {
const reviews = useJobReviews(queue.api, lensId, job);
return <LiveRun job={job} reviews={reviews} name={name} queue={queue} />;
}

View file

@ -112,7 +112,7 @@ export function LiveStrip({
)}
</div>
<div className="flex shrink-0 items-center gap-1">
<Button variant="outline" size="xs" onClick={() => onOpen()} disabled={!recent.length}>
<Button variant="outline" size="xs" onClick={() => onOpen()} disabled={state.kind === "done" && !recent.length}>
<PanelRight />
View run
</Button>

View file

@ -0,0 +1,18 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { lensQueries } from "../../api/queries";
import type { LensApi } from "../../api/service";
import { appendPage, EMPTY_FEED, type ReviewFeed } from "../../model/live";
import type { Job, Review } from "../../model/types";
export function useJobReviews(api: LensApi, lensId: string, job: Pick<Job, "id" | "status">): readonly Review[] {
const [feed, setFeed] = useState<ReviewFeed>(EMPTY_FEED);
const live = job.status === "queued" || job.status === "running";
const { data } = useQuery(lensQueries.reviews(api, { lensId, jobId: job.id, after: feed.cursor, live }));
const next = data ? appendPage(feed, data) : feed;
if (next !== feed) setFeed(next);
return next.reviews;
}

View file

@ -2,10 +2,12 @@
import { useNow } from "@/hooks/useNow";
import type { LensApi } from "../api/service";
import { queueReason, type QueueReason } from "../model/status";
import type { Job, Lens, LensList } from "../model/types";
export interface QueueContext {
api: LensApi;
lenses: readonly Lens[];
workers: readonly LensList["workers"][number][];
onConnect: () => void;