Tighten review format, badge finding counts, add per-finding fix button

This commit is contained in:
Ian Gulliver
2026-10-15 18:34:12 -07:00
parent 514a2580f7
commit c7f19f2537
12 changed files with 183 additions and 45 deletions
+47 -5
View File
@@ -1,5 +1,5 @@
import { get, type ByreEvent, type Schemas } from "./api.js";
import { ago, errorText, h, replace } from "./dom.js";
import { get, post, type ByreEvent, type Schemas } from "./api.js";
import { ago, errorText, h, replace, showAlert } from "./dom.js";
import { markdown } from "./markdown.js";
import { onEach, pickReview, state } from "./state.js";
@@ -11,9 +11,23 @@ export function isReviewEvent(ev: ByreEvent): boolean {
return ev.kind.startsWith("review.");
}
export function reviewCounts(r: Review): string {
function findingsOf(r: Review): Record<string, unknown>[] {
const res = (r.result ?? {}) as Record<string, unknown>;
const findings = Array.isArray(res["findings"]) ? (res["findings"] as Record<string, unknown>[]) : [];
return Array.isArray(res["findings"]) ? (res["findings"] as Record<string, unknown>[]) : [];
}
export function findingCount(r: Review): number {
return r.state === "done" ? findingsOf(r).length : 0;
}
export function latestFindingCount(events: ByreEvent[]): number {
const finished = events.filter((ev) => ev.kind === "review.finished").map((ev) => ev.payload as unknown as Review);
const top = finished.reduce<Review | undefined>((best, r) => (!best || r.snapshot_n >= best.snapshot_n ? r : best), undefined);
return top ? findingCount(top) : 0;
}
export function reviewCounts(r: Review): string {
const findings = findingsOf(r);
const parts = severities.map((s) => [s, findings.filter((f) => f["severity"] === s).length] as const).filter(([, n]) => n > 0);
return parts.length > 0 ? parts.map(([s, n]) => `${n} ${s}`).join(", ") : "no findings";
}
@@ -47,7 +61,7 @@ export function reviewCard(r: Review, taskId: string | undefined): HTMLElement {
function reviewResult(r: Review, taskId: string | undefined): HTMLElement {
const res = (r.result ?? {}) as Record<string, unknown>;
const findings = Array.isArray(res["findings"]) ? (res["findings"] as Record<string, unknown>[]) : [];
const findings = findingsOf(r);
const md = { taskId };
return h(
"div",
@@ -70,6 +84,7 @@ function reviewResult(r: Review, taskId: string | undefined): HTMLElement {
typeof f["category"] === "string" && f["category"] ? h("span", { class: "finding-category" }, f["category"]) : null,
h("span", { class: "finding-title" }, String(f["title"] ?? "")),
where ? h("code", { class: "finding-where" }, where) : null,
taskId ? fixButton(taskId, r, f, where) : null,
),
markdown(String(f["detail"] ?? ""), "md finding-detail", md),
);
@@ -79,6 +94,33 @@ function reviewResult(r: Review, taskId: string | undefined): HTMLElement {
);
}
function fixButton(taskId: string, r: Review, f: Record<string, unknown>, where: string): HTMLElement {
const button = h("button", { type: "button", class: "finding-fix", title: "Send this finding to the task's agent to fix" }, "fix");
button.onclick = async () => {
const text = `Fix this finding from the review of snapshot #${r.snapshot_n}:\n\n**[${String(f["severity"] ?? "")}] ${String(f["title"] ?? "")}**${where ? ` (\`${where}\`)` : ""}\n\n${String(f["detail"] ?? "")}`;
button.disabled = true;
try {
const sessions = await get("/api/tasks/{taskId}/sessions", { path: { taskId } });
const live = sessions.findLast((s) => s.state !== "completed");
if (live && live.state !== "idle") {
button.disabled = false;
void showAlert("The agent is mid-turn. Send this once its turn ends.");
return;
}
if (live) {
await post("/api/sessions/{sessionId}/messages", { path: { sessionId: live.id }, body: { text } });
} else {
await post("/api/tasks/{taskId}/sessions", { path: { taskId }, body: { prompt: text } });
}
replace(button, "sent");
} catch (err) {
button.disabled = false;
void showAlert(errorText(err));
}
};
return button;
}
export function mountReviews(root: HTMLElement): () => void {
const head = h("header", { class: "reviews-header" });
const body = h("div", { class: "review-list" });