commit d70dceccc4a3215a35381704782b902863fe9c44
parent 735984720c1a253ad8f8a262b05ffcd2ab11e7cb
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 21 Sep 2026 01:45:04 -0400
deliver: refresh the counts the job just changed, and stat instead of reading
Three things found reading it back.
The panel's counts are SERVER-RENDERED off the directory and the job is what
changes the directory, so a finished cut left "cut 2 confirmed clips" on screen
until somebody reloaded. Refreshed once, when the job reaches a terminal state
-- not on a timer, which would fight the log being read.
`exists()` was a readFile. It is asked about `orig/<clip>.mp4` twice per clip
in a batch, so a 53-clip batch read a hundred megabytes to answer a yes/no.
And a timeline with no clips renders no panel at all: a card-only cut has
nothing to deliver, and a row of zeroes on every such project is noise on a
page that is already long.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
3 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/umtool/components/projects/DeliverActions.tsx b/umtool/components/projects/DeliverActions.tsx
@@ -1,6 +1,7 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
+import { useRouter } from "next/navigation";
import { buttonVariants } from "@/components/ui/button";
// ---------------------------------------------------------------------------
@@ -65,6 +66,7 @@ export default function DeliverActions({
const [partial, setPartial] = useState(false);
const [name, setName] = useState(nextName);
const since = useRef(0);
+ const router = useRouter();
// Adopt a job already running, so a reload does not lose one. Also how a
// second tab sees the first tab's cut.
@@ -90,9 +92,15 @@ export default function DeliverActions({
const j = (await r.json()) as JobView;
since.current = j.next;
setJob((prev) => (prev ? { ...j, log: [...prev.log, ...j.log] } : j));
+ // The counts above this panel are SERVER-RENDERED off the directory, and
+ // the job just changed the directory: a cut that finished leaves "cut 2
+ // confirmed clips" on screen until something re-reads it. Refreshed
+ // here, once, when the job ends -- not on a timer, which would fight the
+ // log the operator is reading.
+ if (j.state !== "running") router.refresh();
}, 700);
return () => clearInterval(t);
- }, [job]);
+ }, [job, router]);
const post = useCallback(
async (action: string, extra: Record<string, unknown> = {}) => {
diff --git a/umtool/components/projects/DeliverSection.tsx b/umtool/components/projects/DeliverSection.tsx
@@ -57,7 +57,9 @@ export default async function DeliverSection({
entries: unknown[];
}) {
const state = (await deliverStateOf(project, { manifest, entries })) as State | null;
- if (!state) return null;
+ // A timeline with no clips has nothing to deliver, and a panel of zeroes on
+ // every card-only cut is noise on a page that is already long.
+ if (!state || !state.review.total) return null;
const { review } = state;
return (
diff --git a/umtool/lib/report/deliver.mjs b/umtool/lib/report/deliver.mjs
@@ -14,7 +14,7 @@
// Plain ESM with no Next imports, because the batch builder is also spawned as
// a script by lib/jobs.ts -- one implementation, whether a button or a terminal
// asked for it.
-import { copyFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
+import { copyFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
import { execFile } from "node:child_process";
import path from "node:path";
import { promisify } from "node:util";
@@ -279,7 +279,8 @@ export async function deliverStateOf(project, { manifest = null, entries = null
};
}
-const exists = (p) => readFile(p).then(() => true, () => false);
+/** Is there a file here? `stat`, not a read: an `orig/` mp4 is megabytes. */
+const exists = (p) => stat(p).then((st) => st.isFile(), () => false);
/** The batch's own manifest, in the shape the first one shipped. */
export function renderListMd(project, name, sections, { excluded }) {