commit 3ca9da34bfc7f485ecce18f7008a42b7468746b6
parent 9c59d06c0e3aa7942f00ce6610bcc4f1483c8b69
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Wed, 30 Sep 2026 21:17:24 -0400
deck S4: buildSteps' chromeOnly → --chrome-only, "re-render on-screen"
The driver maps options.chromeOnly to the build's --chrome-only flag with the
step label "re-render on-screen". It skips the availability preflight and the
dry resolve (no source is touched) but keeps the build's own timeout and the
verify step: it re-renders the deck and re-concats the whole cut.
POST /api/report/build accepts options.chromeOnly, refuses it beside
chaptersOnly or preview, and does not apply the overwrite guard to it — like
chaptersOnly it rewrites the deliverable in place because it was asked to.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 59 insertions(+), 9 deletions(-)
diff --git a/umtool/app/api/report/build/route.ts b/umtool/app/api/report/build/route.ts
@@ -76,7 +76,13 @@ export async function POST(request: Request) {
// ---- the options the pipeline has and the driver used to hide ----------
const raw = (body.options ?? {}) as Record<string, unknown>;
- const options: { variant?: string; xfade?: boolean; chaptersOnly?: boolean; preview?: { at: number; dur: number } | null } = {};
+ const options: {
+ variant?: string;
+ xfade?: boolean;
+ chaptersOnly?: boolean;
+ chromeOnly?: boolean;
+ preview?: { at: number; dur: number } | null;
+ } = {};
if (raw.variant !== undefined && raw.variant !== "") {
const v = String(raw.variant);
if (!(VARIANTS as string[]).includes(v)) {
@@ -86,6 +92,10 @@ export async function POST(request: Request) {
}
if (raw.xfade === false) options.xfade = false;
if (raw.chaptersOnly) options.chaptersOnly = true;
+ // Re-render the on-screen deck over the segments on disk ("Re-render
+ // on-screen"). Like chaptersOnly it rewrites the deliverable in place because
+ // somebody asked it to, so the overwrite guard below does not apply.
+ if (raw.chromeOnly) options.chromeOnly = true;
if (raw.preview && typeof raw.preview === "object") {
const pv = raw.preview as Record<string, unknown>;
const at = Number(pv.at);
@@ -95,8 +105,11 @@ export async function POST(request: Request) {
}
options.preview = { at, dur };
}
- if (options.chaptersOnly && options.preview) {
- return Response.json({ error: "chaptersOnly and preview are different runs — pick one" }, { status: 400 });
+ if ([options.chaptersOnly, options.chromeOnly, options.preview].filter(Boolean).length > 1) {
+ return Response.json(
+ { error: "chaptersOnly, chromeOnly and preview are different runs — pick one" },
+ { status: 400 },
+ );
}
const project = await projectRef(projectId);
@@ -137,7 +150,7 @@ export async function POST(request: Request) {
// the same file in place by design, and a preview writes a different one, so
// neither is guarded.
const finalPath = variantPaths(path.join(project.dir, "out"), manifest.slug, options.variant ?? DEFAULT_VARIANT).final;
- if (!only && !options.chaptersOnly && !options.preview) {
+ if (!only && !options.chaptersOnly && !options.chromeOnly && !options.preview) {
const [fin, man] = await Promise.all([
stat(finalPath).catch(() => null),
stat(path.join(project.dir, "video.manifest.json")).catch(() => null),
diff --git a/umtool/lib/report/driver.mjs b/umtool/lib/report/driver.mjs
@@ -53,11 +53,15 @@ export const buildTimeoutMs = (clipCount, xfade) =>
* chaptersOnly `--chapters-only` -- retitle the chapters from the segments
* already on disk; no fetch, no encode
* preview `--preview <at> <dur>` -- the rail alone over a window
+ * chromeOnly `--chrome-only` -- re-render the on-screen deck over the
+ * segments already on disk and re-concat; no segment rebuilt
*
- * chaptersOnly and preview SKIP the preflight and the dry resolve: neither
- * touches a source, and both are seconds of work under a five-minute cap.
+ * chaptersOnly, preview and chromeOnly SKIP the preflight and the dry resolve:
+ * none of them touches a source. The first two are seconds of work under a
+ * five-minute cap; a deck re-render is a render plus a concat of the whole
+ * cut, so it keeps the build's own timeout.
*
- * @typedef {{ variant?: string, xfade?: boolean, chaptersOnly?: boolean, preview?: { at: number, dur: number } | null }} BuildOptions
+ * @typedef {{ variant?: string, xfade?: boolean, chaptersOnly?: boolean, chromeOnly?: boolean, preview?: { at: number, dur: number } | null }} BuildOptions
*/
/** The step-1 preflight alone, reused by the check-sources job. */
@@ -84,7 +88,7 @@ export function buildSteps(project, { preset = "fast", only = null, skipFetch =
const base = { cwd: PIPELINE_DIR, env };
const quick = !!(options.chaptersOnly || options.preview);
- const steps = quick
+ const steps = quick || options.chromeOnly
? []
: [
{
@@ -117,11 +121,18 @@ export function buildSteps(project, { preset = "fast", only = null, skipFetch =
if (skipFetch) buildArgv.push("--skip-fetch");
if (p.only && only) buildArgv.push("--only", only);
if (options.chaptersOnly) buildArgv.push("--chapters-only");
+ if (options.chromeOnly) buildArgv.push("--chrome-only");
if (options.preview) buildArgv.push("--preview", String(options.preview.at), String(options.preview.dur));
steps.push({
...base,
- label: options.chaptersOnly ? "retitle the chapters (no encode)" : options.preview ? `rail preview at ${options.preview.at}s` : p.label,
+ label: options.chaptersOnly
+ ? "retitle the chapters (no encode)"
+ : options.chromeOnly
+ ? "re-render on-screen"
+ : options.preview
+ ? `rail preview at ${options.preview.at}s`
+ : p.label,
argv: buildArgv,
ndjson: true,
timeoutMs: quick ? 5 * 60_000 : buildTimeoutMs(clipCount, p.xfade),
diff --git a/umtool/lib/report/driver.test.mjs b/umtool/lib/report/driver.test.mjs
@@ -0,0 +1,26 @@
+// buildSteps' `chromeOnly`: the deck re-rendered over the segments on disk.
+//
+// Run with: pnpm test:scripts
+import assert from "node:assert/strict";
+import test from "node:test";
+
+import { buildSteps } from "./driver.mjs";
+
+const project = { id: "p", dir: "/r/p" };
+
+test("chromeOnly: no preflight, --chrome-only on the build, labelled, then the verify", () => {
+ const steps = buildSteps(project, { preset: "final", options: { chromeOnly: true, variant: "full" } });
+ assert.deepEqual(steps.map((s) => s.label), ["re-render on-screen", "verify the file that came out"]);
+ const argv = steps[0].argv;
+ assert.ok(argv.includes("--chrome-only"));
+ assert.deepEqual(argv.slice(argv.indexOf("--variant"), argv.indexOf("--variant") + 2), ["--variant", "full"]);
+ assert.ok(!argv.includes("--chapters-only"));
+ // A render plus a concat of the whole cut: the build's timeout, not the quick cap.
+ assert.ok(steps[0].timeoutMs > 5 * 60_000);
+});
+
+test("without chromeOnly nothing changes: the preflight leads and no --chrome-only", () => {
+ const steps = buildSteps(project, { preset: "final" });
+ assert.equal(steps[0].label, "check every source is still fetchable");
+ assert.ok(steps.every((s) => !s.argv.includes("--chrome-only")));
+});