commit bc0161c26db215b060048898e9808fec4cbcc4cf
parent 4b391c6f9e8e32883550e235d4dd64861da65a0e
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 24 Sep 2026 13:07:13 -0400
operations: in-flight units are job rows (inFlight.jobId, fromInFlight)
`AutoRunnerInFlight` gains `jobId?`, set inside the runner's two existing
`onChildJob` callbacks (the private childJobIds map stays private), so a
download unit names the registry job running it; transcription / digest /
backfill units are tasks on the runner's job and carry none.
`common/views/jobRows.ts` gains the pure adapter `fromInFlight(unit, {kind,
runnerJobId, detail})` → a `JobRowView` with the new `source: "runner"`:
id = the unit's jobId, else `<runnerJobId|kind>:<videoId>`; `inRegistry`
says whether it has a job page; a unit's `note` replaces the caller's
"rule N · leaf" detail and drops the synthetic videoId. InFlightList keeps
its "In flight" box and draws each unit as `<JobRow variant="compact">`
(links, elapsed; no actions, no tasks). A compact / card row links
`Job <id>` only when a runner row is `inRegistry`.
The `autoQueueStatus` payload gains that optional field; the
phase3-view-numbers tool does not cover that view.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
6 files changed, 136 insertions(+), 51 deletions(-)
diff --git a/common/controller/autoRunner.ts b/common/controller/autoRunner.ts
@@ -184,6 +184,9 @@ export type AutoRunnerInFlight = {
// note renders the note instead of a video link. Absent for every ordinary
// unit, which is how the surfaces stay unchanged.
note?: string;
+ // The registry job running this unit, when it is one (a download unit is a
+ // child job on its platform queue); absent for a task on the runner's job.
+ jobId?: string;
};
// Why the runner is up but dispatching nothing. Every one of these was already
@@ -1890,7 +1893,7 @@ async function runLoop(
targets: picked.scan.targets,
tracker,
onLog,
- onChildJob: (jid) => childJobIds.set(pick.videoId, jid),
+ onChildJob: (jid) => { childJobIds.set(pick.videoId, jid); const u = live.inFlight.get(pick.videoId); if (u) u.jobId = jid; },
});
return;
}
@@ -1905,7 +1908,7 @@ async function runLoop(
onLog,
signal: runSignal,
drainSignal: ctx.drainSignal,
- onChildJob: (jid) => childJobIds.set(pick.videoId, jid),
+ onChildJob: (jid) => { childJobIds.set(pick.videoId, jid); const u = live.inFlight.get(pick.videoId); if (u) u.jobId = jid; },
});
} catch (err) {
result = { outcome: "failed" };
diff --git a/common/views/jobRowView.ts b/common/views/jobRowView.ts
@@ -94,6 +94,8 @@ export type JobRowView = {
// jobs/jobDetail.ts from the job's own replay spec; absent for every kind
// that has nothing to add, so no existing row changes.
detail?: string;
- // Which adapter built it. Never rendered; tests and the merge read it.
- source: "registry" | "archive" | "slot";
+ // Which adapter built it. Never rendered; tests and the merge read it —
+ // except "runner": an auto-queue lane's in-flight unit (fromInFlight), which
+ // links to a job page only when `inRegistry` says it has one.
+ source: "registry" | "archive" | "slot" | "runner";
};
diff --git a/common/views/jobRows.test.ts b/common/views/jobRows.test.ts
@@ -5,6 +5,7 @@ import type { JobListEntry } from "../jobs/listJobs";
import { createScheduler } from "../jobs/scheduler";
import {
fromEntry,
+ fromInFlight,
fromRecord,
fromSlot,
isLive,
@@ -340,3 +341,61 @@ test("fromSlot builds the phantom, and a stuck row sorts above a running one", (
]);
assert.equal(ordered[0].id, phantom.id);
});
+
+test("fromInFlight: a download unit carries its registry job id", () => {
+ const row = fromInFlight(
+ {
+ videoId: "v1",
+ leafId: "leaf-a",
+ channelSlug: "alpha",
+ startedAt: 1_000,
+ jobId: "01JOBDOWNLOAD",
+ },
+ { kind: "download", runnerJobId: "01RUNNER", detail: "rule 1 · alpha" },
+ );
+ assert.equal(row.id, "01JOBDOWNLOAD");
+ assert.equal(row.inRegistry, true);
+ assert.equal(row.source, "runner");
+ assert.equal(row.status, "running");
+ assert.equal(row.background, true);
+ assert.equal(row.kind, "download");
+ assert.equal(row.channelSlug, "alpha");
+ assert.equal(row.videoId, "v1");
+ assert.equal(row.startedAt, 1_000);
+ assert.equal(row.detail, "rule 1 · alpha");
+});
+
+test("fromInFlight: a task unit gets a synthetic id and no job page", () => {
+ const unit = {
+ videoId: "v2",
+ leafId: "leaf-b",
+ channelSlug: "beta",
+ startedAt: 2_000,
+ };
+ const withRunner = fromInFlight(unit, {
+ kind: "transcription",
+ runnerJobId: "01RUNNER",
+ });
+ assert.equal(withRunner.id, "01RUNNER:v2");
+ assert.equal(withRunner.inRegistry, false);
+ // No runner job known (a lane between runs): the lane kind keys it.
+ const bare = fromInFlight(unit, { kind: "transcription", runnerJobId: null });
+ assert.equal(bare.id, "transcription:v2");
+ assert.equal(bare.detail, undefined);
+});
+
+test("fromInFlight: a note replaces the detail and drops the synthetic video id", () => {
+ const row = fromInFlight(
+ {
+ videoId: "metadata-scan alpha",
+ leafId: "metadata-scan",
+ channelSlug: "alpha",
+ startedAt: 3_000,
+ note: "metadata scan · 12 titles",
+ },
+ { kind: "download", detail: "rule 1 · alpha" },
+ );
+ assert.equal(row.detail, "metadata scan · 12 titles");
+ assert.equal(row.videoId, undefined);
+ assert.equal(row.id, "download:metadata-scan alpha");
+});
diff --git a/common/views/jobRows.ts b/common/views/jobRows.ts
@@ -5,6 +5,7 @@ import type { JobListEntry } from "../jobs/listJobs";
import { jobSpecDetail } from "../jobs/jobDetail";
import type { ChannelStat } from "../controller/channels";
import type { QueueView } from "../jobs/scheduler";
+import type { AutoRunnerInFlight } from "../controller/autoRunner";
import type { JobRowView, StuckReason } from "./jobRowView";
// THE ADAPTERS, THE ORDER AND THE MERGE — pure, and directive-free by
@@ -332,3 +333,33 @@ export function isLive(r: JobRowView): boolean {
Boolean(r.stuck)
);
}
+
+// AN AUTO-QUEUE LANE'S IN-FLIGHT UNIT, as a job row — so the operations board
+// draws what the runner is doing with the same row every other surface draws
+// a job with. A download unit is a real registry job (its id rides on the unit
+// as `jobId`, set when the runner's child job starts); a transcription, digest
+// or backfill unit is a task on the runner's own job and has no page of its
+// own, so its id is synthetic and `inRegistry` is false.
+//
+// `detail` ("rule 3 · newest first from alpha") is the caller's: the leaf
+// sentence is an editor helper over the lane's policy, not a pure fact of the
+// unit. A unit with a `note` (the download lane's channel-scoped metadata
+// scan, whose `videoId` names no directory) says the note instead, and carries
+// no videoId, so nothing links to a video that is not there.
+export function fromInFlight(
+ unit: AutoRunnerInFlight,
+ ctx: { kind: string; runnerJobId?: string | null; detail?: string },
+): JobRowView {
+ return {
+ id: unit.jobId ?? `${ctx.runnerJobId ?? ctx.kind}:${unit.videoId}`,
+ kind: ctx.kind,
+ status: "running",
+ channelSlug: unit.channelSlug,
+ videoId: unit.note ? undefined : unit.videoId,
+ startedAt: unit.startedAt,
+ background: true,
+ inRegistry: Boolean(unit.jobId),
+ detail: unit.note ?? ctx.detail,
+ source: "runner",
+ };
+}
diff --git a/editor/app/jobs/components/JobRow.tsx b/editor/app/jobs/components/JobRow.tsx
@@ -118,9 +118,11 @@ export function statusColor(status: string): string {
}
}
-// A row with a page at /jobs/<id>.
-function linksToJob(_job: JobRowView): boolean {
- return true;
+// A row with a page at /jobs/<id>. A runner's in-flight unit that is a task
+// on its runner's job (a transcription, a digest) is not a registry job, so it
+// does not link to one; a download unit is, and carries its id.
+function linksToJob(job: JobRowView): boolean {
+ return job.source !== "runner" || Boolean(job.inRegistry);
}
function videoHref(job: JobRowView): string | null {
diff --git a/editor/app/operations/components/InFlightList.tsx b/editor/app/operations/components/InFlightList.tsx
@@ -1,12 +1,18 @@
"use client";
-import Link from "next/link";
import type { AutoQueueKindStatus } from "yt-dlp-transcript-common/views/autoQueueStatus";
-import { type Channel, formatElapsed, leafOrder, leafSentence } from "./dispatch";
+import { fromInFlight } from "yt-dlp-transcript-common/views/jobRows";
+import { JobRow } from "../../jobs/components/JobRow";
+import { type Channel, leafOrder, leafSentence } from "./dispatch";
// What the runner is doing RIGHT NOW. `runner.inFlight` has always been in the
// status payload — video id, owning channel, the leaf that claimed it and when
// it started — and the old page rendered a count and threw the rest away.
+//
+// Each unit is drawn as a JOB ROW (jobs/components/JobRow.tsx, compact), off
+// the pure adapter `fromInFlight` — the same row the widget and /jobs draw. A
+// download unit is a registry job and links to its page; a transcription,
+// digest or backfill unit is a task on the runner's job and does not.
export function InFlightList({
status,
@@ -30,52 +36,34 @@ export function InFlightList({
{[...items]
.sort((a, b) => a.startedAt - b.startedAt)
.map((item) => {
+ // WHICH RULE CLAIMED IT, as the ladder numbers them. A unit that
+ // is not a video (the download lane's metadata-scan unit, whose
+ // `videoId` is a synthetic key naming no directory) carries a
+ // `note`, which fromInFlight says instead — no "rule N" for a leaf
+ // that exists in no tree, and no link to a video that is not there.
const at = leaves.findIndex((l) => l.id === item.leafId);
const leaf = at >= 0 ? leaves[at] : null;
+ const detail = `${at >= 0 ? `rule ${at + 1}` : item.leafId}${
+ leaf ? ` · ${leafSentence(leaf, channels)}` : ""
+ }`;
+ const row = fromInFlight(item, {
+ kind: status.kind,
+ runnerJobId: status.runner.jobId,
+ detail,
+ });
return (
- <li
- key={item.videoId}
- className="flex flex-wrap items-baseline gap-x-2 gap-y-0.5 text-sm"
- >
- <span
- aria-hidden="true"
- className="size-1.5 shrink-0 self-center rounded-full bg-info animate-pulse motion-reduce:animate-none"
+ <li key={item.videoId} className="text-sm">
+ <JobRow
+ job={row}
+ variant="compact"
+ show={{
+ actions: false,
+ tasks: false,
+ links: true,
+ elapsed: true,
+ }}
+ now={now}
/>
- {/* A UNIT THAT IS NOT A VIDEO SAYS SO INSTEAD OF LINKING.
- The download lane's metadata-scan unit is channel-scoped:
- its `videoId` is a synthetic key naming no directory, so the
- usual link would be a 404 and the usual "rule N" would name
- a leaf that exists in no tree. It carries a `note`, and a
- unit with one renders the note — see AutoRunnerInFlight. */}
- {item.note ? (
- <>
- <Link
- href={`/channels/${item.channelSlug}`}
- className="font-mono text-foreground underline underline-offset-2 hover:text-brand"
- >
- {item.channelSlug}
- </Link>
- <span className="text-xs text-muted-foreground">
- {item.note}
- </span>
- </>
- ) : (
- <>
- <Link
- href={`/channels/${item.channelSlug}/videos/${item.videoId}`}
- className="font-mono text-foreground underline underline-offset-2 hover:text-brand"
- >
- {item.channelSlug}/{item.videoId}
- </Link>
- <span className="text-xs text-muted-foreground">
- {at >= 0 ? `rule ${at + 1}` : item.leafId}
- {leaf ? ` · ${leafSentence(leaf, channels)}` : ""}
- </span>
- </>
- )}
- <span className="ml-auto tabular-nums text-xs text-muted-foreground">
- {now !== null ? formatElapsed(now - item.startedAt) : "—"}
- </span>
</li>
);
})}