Add per-snapshot review link and snapshot picker to Reviews tab

This commit is contained in:
Ian Gulliver
2026-10-15 17:46:41 -07:00
parent 525f11923d
commit a19067a665
11 changed files with 105 additions and 32 deletions
+27 -8
View File
@@ -1,7 +1,7 @@
import { get, type ByreEvent, type Schemas } from "./api.js";
import { ago, errorText, h, replace } from "./dom.js";
import { markdown } from "./markdown.js";
import { onEach, state } from "./state.js";
import { onEach, pickReview, state } from "./state.js";
type Review = Schemas["Review"];
@@ -80,7 +80,8 @@ function reviewResult(r: Review, taskId: string | undefined): HTMLElement {
}
export function mountReviews(root: HTMLElement): () => void {
const list = h("div", { class: "review-list" });
const head = h("header", { class: "reviews-header" });
const body = h("div", { class: "review-list" });
let reviews: Review[] | undefined;
let error = "";
let target = 0;
@@ -112,23 +113,40 @@ export function mountReviews(root: HTMLElement): () => void {
const render = () => {
const taskId = state.selectedTaskId;
replace(head);
head.hidden = true;
if (!taskId) {
replace(list, h("div", { class: "empty" }, "No task selected."));
replace(body, h("div", { class: "empty" }, "No task selected."));
return;
}
if (error) {
replace(list, h("div", { class: "empty error-text" }, error));
replace(body, h("div", { class: "empty error-text" }, error));
return;
}
if (!reviews) {
replace(list, h("div", { class: "empty" }, "Loading…"));
replace(body, h("div", { class: "empty" }, "Loading…"));
return;
}
if (reviews.length === 0) {
replace(list, h("div", { class: "empty" }, "No reviews yet. Each new snapshot of the task is reviewed by an agent from the other vendor."));
replace(body, h("div", { class: "empty" }, "No reviews yet. Each new snapshot of the task is reviewed by an agent from the other vendor."));
return;
}
replace(list, ...[...reviews].reverse().map((r) => reviewCard(r, taskId)));
const n = state.reviewed ?? reviews.at(-1)!.snapshot_n;
const shown = reviews.findLast((r) => r.snapshot_n === n);
const ns = [...new Set([n, ...reviews.map((r) => r.snapshot_n)])].sort((a, b) => b - a);
const picker = h("select", { title: "Snapshot whose review to show" }, ...ns.map((s) => h("option", { value: String(s) }, `snapshot #${s}`)));
picker.value = String(n);
picker.onchange = () => pickReview(Number(picker.value));
const step = (to: number, label: string) => {
const b = h("button", { type: "button", class: "snap-step", disabled: !ns.includes(to) }, label);
b.onclick = () => pickReview(to);
return b;
};
const next = ns.filter((s) => s > n).at(-1);
const prev = ns.find((s) => s < n);
head.hidden = false;
replace(head, picker, step(prev ?? n - 1, `← #${prev ?? n - 1}`), step(next ?? n + 1, `#${next ?? n + 1} →`));
replace(body, shown ? reviewCard(shown, taskId) : h("div", { class: "empty" }, `No review of snapshot #${n} yet.`));
};
const offs = [
@@ -144,9 +162,10 @@ export function mountReviews(root: HTMLElement): () => void {
void load();
}
}),
onEach(["review"], render),
];
replace(root, h("div", { class: "reviews-view" }, list));
replace(root, h("div", { class: "reviews-view" }, head, body));
render();
void load();
return () => {