commit 45a37f16b987efe990b0ab460a84ff0ab1039931
parent 7ab623f66af7d496eaccb8bfcba6d0365aee1bb6
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sun, 20 Sep 2026 22:58:07 -0400
report fetch: the cached check must clamp at 0, because the fetch does
`--fetch-only --pad P` is [start-P, end+P] measured from the clip, and both
fetchers take Math.max(0, start - padBefore) — a recording has no seconds
before its first. The route's cache check did not, so for any clip within `pad`
seconds of the beginning it asked whether a window holds a NEGATIVE start,
which no file can ever hold. The check could not fire there, and those are
exactly the clips a sweep starts on: the route queued a job whose only finding
was that the bytes were already on the disk.
Found by the new reuse case, which is g01 at 5–25 s and g02 at 10–15 s: g02's
padded ask is [-10, 35] unclamped and [0, 35] clamped, and 0.00–45.00 holds the
second and not the first.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/plans/FACTS.md b/plans/FACTS.md
@@ -4221,6 +4221,15 @@ umtool asks the editor for a clip window instead of running yt-dlp
through libvpx-vp9 (27 s to cut a 5 s clip, measured) and writes `.webm`. The
two copies must stay byte-identical or a window one side fetched is invisible
to the other.
+- **`/api/report/fetch`'s "already cached" check must CLAMP its wanted start at
+ 0**, because the fetch does. `build-video.mjs`'s `fetchClip` and
+ `fetch-via-editor.mjs` both take `Math.max(0, start - padBefore)` — a
+ recording has no seconds before its first — so asking whether a window holds
+ a NEGATIVE start asks for something no file can hold. Unclamped, the check
+ could never fire for a clip within `pad` seconds of the beginning, which is
+ exactly where a sweep starts; the route queued a job whose only finding was
+ that the bytes were already there. Pinned by the reuse case in
+ `umtool/e2e/report-fetch-via-editor.spec.ts`.
- **`WIN_EPS = 0.02` and 2 dp naming are a cross-repo contract.**
`common/lib/clipWindow.ts` and `umtool/report-to-video/build-video.mjs` both
declare them. A request that rounds differently addresses a different file and
diff --git a/umtool/app/api/report/fetch/route.ts b/umtool/app/api/report/fetch/route.ts
@@ -56,7 +56,17 @@ export async function POST(request: Request) {
r.clip,
await projectCache(r.project, r.manifest),
)) as { from: number; to: number }[];
- const want = { from: Number(r.clip.start) - padBefore, to: Number(r.clip.end) + padAfter };
+ // CLAMPED AT 0, because the FETCH clamps at 0 — build-video.mjs's fetchClip
+ // and fetch-via-editor.mjs both do `Math.max(0, start - padBefore)`, since a
+ // recording has no seconds before its first. Asking here whether a window
+ // holds a NEGATIVE start asks for something no file can ever hold, so this
+ // check could never fire for a clip within `pad` seconds of the beginning —
+ // and those are exactly the clips a sweep starts on. The route then queued a
+ // job whose only finding was that the bytes were already there.
+ const want = {
+ from: Math.max(0, Number(r.clip.start) - padBefore),
+ to: Number(r.clip.end) + padAfter,
+ };
const holds = windows.find((w) => w.from <= want.from + WIN_EPS && w.to >= want.to - WIN_EPS);
if (holds) {
// What is already there, PER SIDE, in the pads the bench speaks in.