feat(lens): add a reading ticker line and replay for finished runs

This commit is contained in:
Ishaan Jaff 2026-10-03 16:49:50 -07:00
parent 9669c2454d
commit d2e96c41f2
No known key found for this signature in database
2 changed files with 28 additions and 1 deletions

View file

@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
analysisModel,
conclusions,
tickerLine,
liveJob,
liveStats,
outcome,
@ -71,6 +72,11 @@ describe("review outcome", () => {
expect(outcome(review("a", { cannot_assess: true, verdicts: [issue("i")] }))).toBe("unknown");
});
it("names the agent and short trace id in the ticker, falling back to the run name", () => {
expect(tickerLine(review("a", { trace_id: "a91f3c02deadbeef" }))).toBe("reading support-bot · a91f3c02");
expect(tickerLine(review("a", { agent: "", name: "refund", trace_id: "7d21" }))).toBe("reading refund · 7d21");
});
it("leads the verdict line with the issue summary over patterns", () => {
expect(verdictLine(review("a", { verdicts: [pattern("p", "fine"), issue("i", "made it up")] }))).toBe(
"made it up",
@ -174,6 +180,15 @@ describe("playback queue", () => {
expect(playbackReducer(second, { type: "tick", now: 1e9 })).toBe(second);
});
it("replays a finished run from its first review", () => {
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("a");
});
it("settles everything at once for reduced motion", () => {
const start = startPlayback(reviews, true);
const settled = playbackReducer(start, { type: "settle" });

View file

@ -51,6 +51,10 @@ export function outcome(review: Pick<Review, "cannot_assess" | "verdicts">): Out
return review.verdicts.some((v) => v.kind === "issue") ? "issue" : "clear";
}
export function tickerLine(review: Pick<Review, "agent" | "name" | "trace_id">): string {
return `reading ${review.agent || review.name} · ${review.trace_id.slice(0, 8)}`;
}
export function verdictLine(review: Pick<Review, "cannot_assess" | "verdicts">): string {
const issue = review.verdicts.find((v) => v.kind === "issue");
if (issue) return issue.summary;
@ -140,7 +144,8 @@ export interface Playback {
export type PlaybackAction =
| { type: "enqueue"; reviews: readonly Review[] }
| { type: "tick"; now: number }
| { type: "settle" };
| { type: "settle" }
| { type: "replay" };
const PLAYED_LIMIT = 200;
@ -193,6 +198,11 @@ function settle(state: Playback): Playback {
};
}
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":
@ -201,6 +211,8 @@ export function playbackReducer(state: Playback, action: PlaybackAction): Playba
return advance(state, action.now);
case "settle":
return settle(state);
case "replay":
return replay(state);
}
}