commit 7f7ac6a868e50250ebaa90271b4ed8613a652289
parent 65eb4949aa8a78f0858eb52c2d7ef05c28404f80
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sat, 19 Sep 2026 22:23:59 -0400
clip bench: a yes can be taken back
"No" (`x`) only ever armed the note box, and the note's save deliberately left a
confirmed clip's verdict alone -- so a clip confirmed WITH a note could never be
flipped to incorrect on re-audition. Now `x` on such a clip writes `incorrect` at
once (the note is already the answer), and on a confirmed clip with no note the
next saved note carries the verdict with it, as it does on an unreviewed one.
Spec added for the confirmed-with-note case; clip-bench 43 passed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/umtool/components/projects/ClipBench.tsx b/umtool/components/projects/ClipBench.tsx
@@ -599,7 +599,10 @@ export default function ClipBench({ data }: { data: ClipBenchData }) {
// Armed and still empty: keep the cue and write nothing. Blurring out
// of a box you were asked to fill is not an answer.
if (!text && needNote && clip.verdict !== "incorrect") return;
- if (text && clip.verdict !== "confirmed") {
+ // `needNote` is the "no" button having been pressed: on a confirmed clip
+ // the note is only a note UNLESS the walk just said no, in which case
+ // the verdict flips with it.
+ if (text && (clip.verdict !== "confirmed" || needNote)) {
void save({ correction: draft.correction, verdict: "incorrect" }).then((ok) => {
if (ok) setNeedNote(false);
});
@@ -628,11 +631,20 @@ export default function ClipBench({ data }: { data: ClipBenchData }) {
const rejectClip = useCallback(() => {
setNeedNote(true);
+ // A confirmed clip that already carries a note (a "confirmed · with note")
+ // has its answer written: "no" flips the verdict NOW, rather than waiting
+ // for an edit to a note that may already say everything. Otherwise saying
+ // no and changing your mind on a yes would be impossible.
+ if (clip.verdict === "confirmed" && (clip.correction ?? "").trim()) {
+ void save({ verdict: "incorrect" }).then((ok) => {
+ if (ok) setNeedNote(false);
+ });
+ }
const el = correctionBox.current;
if (!el) return;
el.focus();
el.setSelectionRange(el.value.length, el.value.length);
- }, []);
+ }, [clip, save]);
// ---- keyboard -----------------------------------------------------------
useEffect(() => {
diff --git a/umtool/e2e/clip-bench.spec.ts b/umtool/e2e/clip-bench.spec.ts
@@ -567,6 +567,22 @@ test("`y` on an incorrect clip keeps the note — it becomes a note, not a defec
await expect(page.locator("[data-verdict-state]")).toHaveText("confirmed · with note");
});
+test("`x` on a confirmed clip that already has a note takes the yes back", async ({ page }) => {
+ // The operator's case: a clip confirmed WITH a note ("this is another speaker,
+ // but it does contain what you say") re-auditioned and found wrong after all.
+ // The note already says what is wrong, so "no" must not wait for an edit to
+ // it -- the verdict flips on the button, and the note stays.
+ await page.goto(bench("c03"));
+ await expect(page.locator("[data-verdict-state]")).toHaveText("confirmed · with note");
+
+ await page.locator("[data-verdict-no]").click();
+ await expect.poll(() => readClip("c03").verdict).toBe("incorrect");
+ expect(readClip("c03").correction).toContain("the caller");
+ await expect(page.locator("[data-verdict-state]")).toHaveText("incorrect");
+ // Stays put, like every "no": the note is there to be read back.
+ await expect(page.locator("[data-bench=c03]")).toBeVisible();
+});
+
test("an incorrect verdict cannot have its note taken away, or arrive without one", async ({
page,
request,