refactor(lens): drop client-side replay in favour of real in-flight rows

Removes the playback reducer and its pacing. liveRows lists the traces the worker is reading, from job.reading, followed by completed reviews newest first, keyed by execution_id so a trace keeps its row when it finishes.
This commit is contained in:
Ishaan Jaff 2026-10-03 17:55:10 -07:00
parent b00cb8d733
commit 0376a8d7c6
No known key found for this signature in database
3 changed files with 77 additions and 315 deletions

View file

@ -1,51 +0,0 @@
"use client";
import { useCallback, useEffect, useReducer, useState } from "react";
import { playbackPhase, playbackReducer, startPlayback, type Phase, type Playback } from "../../model/live";
import type { Review } from "../../model/types";
const FRAME_MS = 40;
function prefersReducedMotion(): boolean {
return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
export function useReviewPlayback(
reviews: readonly Review[],
live: boolean,
): Playback & { phase: Phase; replay: () => void } {
const [still] = useState(prefersReducedMotion);
const [state, dispatch] = useReducer(playbackReducer, undefined, () =>
playbackReducer(startPlayback(reviews, live), still ? { type: "settle" } : { type: "tick", now: Date.now() }),
);
const [now, setNow] = useState(Date.now);
useEffect(() => {
dispatch({ type: "enqueue", reviews });
if (still) dispatch({ type: "settle" });
}, [reviews, still]);
const busy = state.pending.length > 0 || now - state.startedAt < state.duration;
useEffect(() => {
if (!busy || still) return;
const timer = window.setInterval(() => {
const at = Date.now();
setNow(at);
dispatch({ type: "tick", now: at });
}, FRAME_MS);
return () => window.clearInterval(timer);
}, [busy, still]);
const replay = useCallback(() => {
dispatch({ type: "replay" });
if (still) dispatch({ type: "settle" });
}, [still]);
const current = state.current;
const phase =
still || !current
? { span: -1, typed: current?.reasoning.length ?? 0, verdict: true }
: playbackPhase(now - state.startedAt, state.duration, current.spans.length, current.reasoning.length);
return { ...state, phase, replay };
}

View file

@ -4,8 +4,11 @@ import {
briefReasoning,
checkLabel,
conclusions,
traceRows,
decidedReviews,
durationLabel,
newestFirst,
nowLine,
inFlight,
liveRows,
grownGroups,
inGroup,
share,
@ -17,16 +20,10 @@ import {
liveJob,
liveStats,
outcome,
playbackPhase,
playbackReducer,
providerOf,
queueRows,
rateLabel,
reviewKey,
secondsToFinishReading,
shownCount,
startPlayback,
stepDuration,
tokenLabel,
} from "./live";
import type { Job, Review } from "./types";
@ -145,141 +142,12 @@ describe("conclusions", () => {
expect([...grownGroups(before, after)]).toEqual(["y"]);
});
it("lists upcoming traces above the one being read and finished ones below, newest first", () => {
const [a, b, c, d] = ["a", "b", "c", "d"].map((id) => review(id));
const rows = traceRows({ played: [a], current: b, pending: [c, d] }, 10);
expect(rows.map((row) => [row.review.execution_id, row.state])).toEqual([
["d", "queued"],
["c", "queued"],
["b", "reviewing"],
["a", "done"],
]);
expect(traceRows({ played: [a], current: b, pending: [c, d] }, 2)).toHaveLength(2);
});
it("gives a bar share bounded to the total", () => {
expect(share(3, 12)).toBe(0.25);
expect(share(5, 0)).toBe(0);
expect(share(9, 4)).toBe(1);
});
it("only counts the trace being read once its verdict is on screen", () => {
const [a, b] = [review("a"), review("b")];
expect(decidedReviews({ played: [a], current: b }, false)).toEqual([a]);
expect(decidedReviews({ played: [a], current: b }, true)).toEqual([a, b]);
});
});
describe("playback pacing", () => {
it("slows to a full window for one review and speeds up as the backlog grows", () => {
expect(stepDuration(1)).toBe(2400);
expect(stepDuration(0)).toBe(2400);
expect(stepDuration(2)).toBeLessThan(stepDuration(1));
});
it("keeps every trace on screen long enough to read, however large the backlog", () => {
expect(stepDuration(3)).toBeGreaterThanOrEqual(1200);
expect(stepDuration(10_000)).toBeGreaterThanOrEqual(1200);
});
it("highlights spans one at a time, then types reasoning, then leaves the verdict up", () => {
const duration = 2000;
expect(playbackPhase(0, duration, 4, 100)).toEqual({ span: 0, typed: 0, verdict: false });
expect(playbackPhase(duration * 0.29, duration, 4, 100).span).toBe(3);
const typing = playbackPhase(duration * 0.475, duration, 4, 100);
expect(typing).toEqual({ span: -1, typed: 50, verdict: false });
const done = playbackPhase(duration * 0.65, duration, 4, 100);
expect(done).toEqual({ span: -1, typed: 100, verdict: true });
expect(playbackPhase(duration * 0.99, duration, 4, 100)).toEqual(done);
});
it("shows a settled review whole", () => {
expect(playbackPhase(0, 0, 4, 100)).toEqual({ span: -1, typed: 100, verdict: true });
});
});
describe("playback queue", () => {
const reviews = ["a", "b", "c", "d", "e"].map((id) => review(id));
it("opens a live job replaying the last few reviews and a finished job on its final review", () => {
const live = startPlayback(reviews, true);
expect(live.played.map((r) => r.execution_id)).toEqual(["a", "b"]);
expect(live.current).toBeNull();
expect(live.pending.map((r) => r.execution_id)).toEqual(["c", "d", "e"]);
const done = startPlayback(reviews, false);
expect(done.played.map((r) => r.execution_id)).toEqual(["a", "b", "c", "d"]);
expect(done.current?.execution_id).toBe("e");
expect(done.pending).toEqual([]);
});
it("enqueues only reviews it has not seen, so repeated polls do not replay", () => {
const start = startPlayback(reviews.slice(0, 2), true);
const polled = playbackReducer(start, { type: "enqueue", reviews: reviews.slice(0, 4) });
expect(polled.pending.map((r) => r.execution_id)).toEqual(["a", "b", "c", "d"]);
expect(playbackReducer(polled, { type: "enqueue", reviews: reviews.slice(0, 4) })).toBe(polled);
});
it("treats a re-reviewed execution at a new time as new", () => {
const start = startPlayback([reviews[0]], false);
const again = review("a", { at: "2026-10-03T17:00:00Z" });
expect(reviewKey(again)).not.toBe(reviewKey(reviews[0]));
expect(playbackReducer(start, { type: "enqueue", reviews: [again] }).pending).toEqual([again]);
});
it("advances one review per step and holds until the step finishes", () => {
const start = startPlayback(reviews.slice(0, 2), true);
expect(start.pending).toHaveLength(2);
const first = playbackReducer(start, { type: "tick", now: 1000 });
expect(first.current?.execution_id).toBe("a");
expect(first.duration).toBe(stepDuration(2));
expect(playbackReducer(first, { type: "tick", now: 1000 + first.duration - 1 })).toBe(first);
const second = playbackReducer(first, { type: "tick", now: 1000 + first.duration });
expect(second.current?.execution_id).toBe("b");
expect(second.played.map((r) => r.execution_id)).toEqual(["a"]);
expect(playbackReducer(second, { type: "tick", now: 1e9 })).toBe(second);
});
it("catches up on a big backlog by skipping to the newest few, never by shortening steps", () => {
const many = Array.from({ length: 40 }, (_, n) => review(`r${n}`));
const step = playbackReducer(startPlayback([], true), { type: "enqueue", reviews: many });
const first = playbackReducer(step, { type: "tick", now: 0 });
expect(first.current?.execution_id).toBe("r37");
expect(first.pending.map((r) => r.execution_id)).toEqual(["r38", "r39"]);
expect(first.played.map((r) => r.execution_id)).toEqual(many.slice(0, 37).map((r) => r.execution_id));
expect(first.duration).toBeGreaterThanOrEqual(1200);
expect(playbackReducer(first, { type: "tick", now: 1199 })).toBe(first);
});
it("replays a finished run, jumping to the last few", () => {
const done = startPlayback(reviews, false);
const again = playbackReducer(done, { type: "replay" });
expect(again.current).toBeNull();
expect(again.played).toEqual([]);
expect(again.pending.map((r) => r.execution_id)).toEqual(["a", "b", "c", "d", "e"]);
expect(playbackReducer(again, { type: "tick", now: 0 }).current?.execution_id).toBe("c");
});
it("settles everything at once for reduced motion", () => {
const start = startPlayback(reviews, true);
const settled = playbackReducer(start, { type: "settle" });
expect(settled.current?.execution_id).toBe("e");
expect(settled.played.map((r) => r.execution_id)).toEqual(["a", "b", "c", "d"]);
expect(settled.pending).toEqual([]);
});
it("lists the newest review first, starting with the one being read", () => {
const start = startPlayback(reviews, false);
expect(queueRows(start, 3).map((r) => r.execution_id)).toEqual(["e", "d", "c"]);
const live = playbackReducer(startPlayback(reviews, true), { type: "tick", now: 0 });
expect(queueRows(live, 10).map((r) => r.execution_id)).toEqual(["c", "b", "a"]);
});
it("counts reviews beyond the capped list without counting the unplayed backlog", () => {
const start = startPlayback(reviews, true);
expect(shownCount(5, start)).toBe(2);
expect(shownCount(120, start)).toBe(117);
});
});
describe("which job the live run shows", () => {
@ -462,3 +330,49 @@ describe("live stats", () => {
expect(tokenLabel(3_400_000)).toBe("3.4M tok");
});
});
describe("honest live list", () => {
it("lists completed reviews newest first in the order they finished, never re-sorted by time", () => {
const reviews = [review("a", { at: "2026-10-03T16:05:00Z" }), review("b", { at: "2026-10-03T16:01:00Z" }), review("c")];
expect(newestFirst(reviews, 10).map((r) => r.execution_id)).toEqual(["c", "b", "a"]);
expect(newestFirst(reviews, 2).map((r) => r.execution_id)).toEqual(["c", "b"]);
expect(reviews.map((r) => r.execution_id)).toEqual(["a", "b", "c"]);
});
it("says how many traces are in flight and how many are done", () => {
const job = (reviewed: number, selected: number) => ({ reviewed, coverage: { selected } }) as unknown as Job;
expect(nowLine(job(18, 30), 4)).toBe("Reviewing 4 at a time · 18 of 30 done");
expect(nowLine(job(30, 30), 0)).toBe("30 of 30 done");
expect(nowLine(job(3, 0), 1)).toBe("Reviewing 1 at a time · 3 done");
});
it("keeps a trace as the same row from in flight to finished", () => {
const reading = (id: string) => ({ execution_id: id, trace_id: `t-${id}`, agent: "bot", started_at: "2026-10-03T16:00:00Z" });
const before = liveRows([reading("x"), reading("y")], [review("a")], 10);
expect(before.map((row) => [row.kind, row.key])).toEqual([
["reading", "x"],
["reading", "y"],
["done", "a"],
]);
const after = liveRows([reading("x"), reading("y")], [review("a"), review("y")], 10);
expect(after.map((row) => [row.kind, row.key])).toEqual([
["reading", "x"],
["done", "y"],
["done", "a"],
]);
});
it("only shows in-flight traces while the job runs", () => {
const item = { execution_id: "x", trace_id: "t", agent: "bot", started_at: "2026-10-03T16:00:00Z" };
const job = (status: Job["status"]) => ({ status, reading: [item] }) as unknown as Job;
expect(inFlight(job("running"))).toEqual([item]);
expect(inFlight(job("completed"))).toEqual([]);
expect(inFlight({ status: "running" } as Job)).toEqual([]);
});
it("formats review time", () => {
expect(durationLabel(420)).toBe("420ms");
expect(durationLabel(1420)).toBe("1.4s");
expect(durationLabel(83_000)).toBe("1m 23s");
});
});

View file

@ -12,12 +12,6 @@ export interface Conclusion {
issue: boolean;
}
export interface Phase {
span: number;
typed: number;
verdict: boolean;
}
export interface LiveStats {
perSecond: number | null;
tokens: number;
@ -25,13 +19,6 @@ export interface LiveStats {
elapsedSeconds: number;
}
const PACE_WINDOW_MS = 2400;
const MIN_VISIBLE_MS = 1500;
const CATCH_UP_BACKLOG = 3;
const READ_SHARE = 0.3;
const TYPE_SHARE = 0.35;
const REPLAY_ON_OPEN = 3;
export function liveJob(jobs: readonly Job[]): Job | undefined {
const active = jobs.find((job) => job.status === "queued" || job.status === "running");
if (active) return active;
@ -116,23 +103,10 @@ export function unseen(reviews: readonly Review[], seen: ReadonlySet<string>): R
return reviews.filter((review) => !seen.has(reviewKey(review)));
}
export function stepDuration(backlog: number): number {
return Math.max(MIN_VISIBLE_MS, Math.min(PACE_WINDOW_MS, Math.round(PACE_WINDOW_MS / Math.max(1, backlog))));
}
export function analysisModel(candidates: readonly string[]): string {
return candidates.find((model) => providerOf(model)) ?? candidates.find(Boolean) ?? "";
}
export function playbackPhase(elapsed: number, duration: number, spans: number, chars: number): Phase {
if (duration <= 0) return { span: -1, typed: chars, verdict: true };
const t = Math.max(0, elapsed) / duration;
const reading = t < READ_SHARE;
const span = reading && spans > 0 ? Math.min(spans - 1, Math.floor((t / READ_SHARE) * spans)) : -1;
const typing = Math.min(1, Math.max(0, (t - READ_SHARE) / TYPE_SHARE));
return { span, typed: Math.round(typing * chars), verdict: t >= READ_SHARE + TYPE_SHARE };
}
const SHORT_LABEL = 48;
function humanize(checkId: string): string {
@ -192,23 +166,6 @@ export function grownGroups(before: readonly Conclusion[], after: readonly Concl
);
}
export type TraceRowState = "queued" | "reviewing" | "done";
export function traceRows(
playback: Pick<Playback, "played" | "current" | "pending">,
limit: number,
): { review: Review; state: TraceRowState }[] {
return [
...[...playback.pending].reverse().map((review) => ({ review, state: "queued" as const })),
...(playback.current ? [{ review: playback.current, state: "reviewing" as const }] : []),
...[...playback.played].reverse().map((review) => ({ review, state: "done" as const })),
].slice(0, limit);
}
export function decidedReviews(playback: Pick<Playback, "played" | "current">, verdictShown: boolean): Review[] {
return [...playback.played, ...(playback.current && verdictShown ? [playback.current] : [])];
}
export function readingStart(job: Pick<Job, "steps" | "created_at">): string {
return job.steps.find((step) => step.kind === "stage" && step.label === "Reading executions")?.at ?? job.created_at;
}
@ -245,98 +202,40 @@ export function tokenLabel(tokens: number): string {
return tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k tok` : `${tokens} tok`;
}
export interface Playback {
played: readonly Review[];
current: Review | null;
pending: readonly Review[];
seen: ReadonlySet<string>;
startedAt: number;
duration: number;
export function newestFirst(reviews: readonly Review[], limit: number): Review[] {
return [...reviews].reverse().slice(0, limit);
}
export type PlaybackAction =
| { type: "enqueue"; reviews: readonly Review[] }
| { type: "tick"; now: number }
| { type: "settle" }
| { type: "replay" };
const PLAYED_LIMIT = 200;
export function startPlayback(reviews: readonly Review[], live: boolean): Playback {
const replay = live ? Math.min(REPLAY_ON_OPEN, reviews.length) : 0;
const shown = reviews.slice(0, reviews.length - replay);
return {
played: live ? shown : shown.slice(0, -1),
current: live ? null : shown.at(-1) ?? null,
pending: reviews.slice(shown.length),
seen: new Set(reviews.map(reviewKey)),
startedAt: Number.NEGATIVE_INFINITY,
duration: 0,
};
export interface InFlight {
execution_id: string;
trace_id: string;
agent: string;
started_at: string;
}
function enqueue(state: Playback, reviews: readonly Review[]): Playback {
const fresh = unseen(reviews, state.seen);
if (!fresh.length) return state;
return {
...state,
pending: [...state.pending, ...fresh],
seen: new Set([...state.seen, ...fresh.map(reviewKey)]),
};
export function inFlight(job: Job): readonly InFlight[] {
const reading = (job as Job & { reading?: readonly InFlight[] }).reading;
return job.status === "running" ? (reading ?? []) : [];
}
function advance(state: Playback, now: number): Playback {
if (!state.pending.length) return state;
if (state.current && now - state.startedAt < state.duration) return state;
const skip = Math.max(0, state.pending.length - CATCH_UP_BACKLOG);
const [next, ...rest] = state.pending.slice(skip);
const finished = [...(state.current ? [state.current] : []), ...state.pending.slice(0, skip)];
return {
...state,
played: [...state.played, ...finished].slice(-PLAYED_LIMIT),
current: next,
pending: rest,
startedAt: now,
duration: stepDuration(rest.length + 1),
};
export type LiveRow = { kind: "reading"; key: string; item: InFlight } | { kind: "done"; key: string; review: Review };
export function liveRows(reading: readonly InFlight[], reviews: readonly Review[], limit: number): LiveRow[] {
const finished = new Set(reviews.map((review) => review.execution_id));
const open = reading.filter((item) => !finished.has(item.execution_id));
return [
...open.map((item) => ({ kind: "reading" as const, key: item.execution_id, item })),
...newestFirst(reviews, limit).map((review) => ({ kind: "done" as const, key: review.execution_id, review })),
];
}
function settle(state: Playback): Playback {
const all = [...state.played, ...(state.current ? [state.current] : []), ...state.pending];
return {
...state,
played: all.slice(0, -1).slice(-PLAYED_LIMIT),
current: all.at(-1) ?? null,
pending: [],
startedAt: Number.NEGATIVE_INFINITY,
duration: 0,
};
export function nowLine(job: Pick<Job, "coverage" | "reviewed">, reading: number): string {
const { selected } = job.coverage;
const done = selected ? `${Math.min(job.reviewed, selected)} of ${selected} done` : `${job.reviewed} done`;
return reading ? `Reviewing ${reading} at a time · ${done}` : done;
}
function replay(state: Playback): Playback {
const all = [...state.played, ...(state.current ? [state.current] : []), ...state.pending];
return { ...state, played: [], current: null, pending: all, startedAt: Number.NEGATIVE_INFINITY, duration: 0 };
}
export function playbackReducer(state: Playback, action: PlaybackAction): Playback {
switch (action.type) {
case "enqueue":
return enqueue(state, action.reviews);
case "tick":
return advance(state, action.now);
case "settle":
return settle(state);
case "replay":
return replay(state);
}
}
export function queueRows(playback: Pick<Playback, "played" | "current">, limit: number): Review[] {
const newest = playback.current ? [playback.current] : [];
return [...newest, ...[...playback.played].reverse()].slice(0, limit);
}
export function shownCount(reviewed: number, playback: Pick<Playback, "played" | "current" | "pending">): number {
const local = playback.played.length + (playback.current ? 1 : 0);
return Math.max(local, reviewed - playback.pending.length);
export function durationLabel(ms: number): string {
if (ms < 1000) return `${Math.max(0, Math.round(ms))}ms`;
return ms < 60_000 ? `${(ms / 1000).toFixed(1)}s` : `${Math.floor(ms / 60_000)}m ${Math.round((ms % 60_000) / 1000)}s`;
}