commit f9bcb3399d8a86330baa1eea14617d791fe2fb5d
parent b352dde505625cf8fd21b3dff8d7d25e4af1ff4f
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sat, 29 Aug 2026 18:14:31 -0400
lanes: one note for why a lane is not working
The four strings — "no operation switched on", "sweep armed, lane paused",
"lane paused", "no sweep armed" — were a table in railStates.ts and a
byte-identical second table in buildActiveJobs.ts, which also re-stated
deriveLaneState's precedence beside it. Nothing pinned either, so the rail and
the Active Jobs strip could have described one lane differently and no test
would have said so.
sweepLaneNote lives beside deriveLaneState and takes ITS input, not its output:
one object, one precedence, two readings, so the word and the note cannot
disagree about which fact wins. Both consumers call the pair; both copies are
deleted. laneState.test.ts pins the four literals, the two null cases, the
precedence and the holding-implies-a-paused-note equivalence.
SweepLane keeps its sentence and says in a comment that it is the long form of
the same row — the rail needs four words in a line and the panel has room to
say what to do about it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat:
5 files changed, 119 insertions(+), 35 deletions(-)
diff --git a/editor/app/components/lanes/laneState.test.ts b/editor/app/components/lanes/laneState.test.ts
@@ -0,0 +1,64 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { deriveLaneState, sweepLaneNote } from "./laneState";
+
+// THE FOUR STRINGS, AS LITERALS. They were a table in railStates.ts (the
+// operations rail) and a byte-identical second table in buildActiveJobs.ts (the
+// Active Jobs lane strip), and nothing pinned either — so the two could drift
+// and no test would say. This is the pin: both consumers read this function
+// now, so these literals are what both of them print.
+
+test("sweepLaneNote names why a sweep lane is not working", () => {
+ assert.equal(
+ sweepLaneNote({ available: false, gateHeld: false }),
+ "no operation switched on",
+ );
+ assert.equal(
+ sweepLaneNote({ gateHeld: true, feedRunning: true }),
+ "sweep armed, lane paused",
+ );
+ assert.equal(sweepLaneNote({ gateHeld: true }), "lane paused");
+ assert.equal(sweepLaneNote({ gateHeld: false }), "no sweep armed");
+});
+
+test("a working lane has nothing to explain", () => {
+ // Null, not "" and not a cheerful sentence: the rail renders the note only
+ // when there is one, and a lane that IS working needs no excuse.
+ assert.equal(sweepLaneNote({ gateHeld: false, feedRunning: true }), null);
+ assert.equal(sweepLaneNote({ gateHeld: false, activeCount: 3 }), null);
+});
+
+test("the note follows deriveLaneState's precedence, fact for fact", () => {
+ // `available: false` beats the gate: with no operation switched on there is
+ // nothing to hold, and "lane paused" would send an operator to the wrong
+ // control.
+ assert.equal(
+ sweepLaneNote({ available: false, gateHeld: true, feedRunning: true }),
+ "no operation switched on",
+ );
+ // The gate beats the feed: a sweep armed behind a shut gate is HOLDING, and
+ // the note has to say the gate, not the sweep.
+ assert.equal(
+ sweepLaneNote({ gateHeld: true, feedRunning: true, activeCount: 2 }),
+ "sweep armed, lane paused",
+ );
+});
+
+test("the word and the note cannot disagree: holding <=> a paused note", () => {
+ // The whole reason sweepLaneNote takes deriveLaneState's input rather than
+ // its output — one object, one precedence, two readings.
+ const cases = [
+ { available: false, gateHeld: false },
+ { available: false, gateHeld: true },
+ { gateHeld: true },
+ { gateHeld: true, feedRunning: true },
+ { gateHeld: false },
+ { gateHeld: false, feedRunning: true },
+ { gateHeld: false, activeCount: 1 },
+ ];
+ for (const input of cases) {
+ const holding = deriveLaneState(input) === "holding";
+ const paused = sweepLaneNote(input)?.endsWith("lane paused") === true;
+ assert.equal(holding, paused, JSON.stringify(input));
+ }
+});
diff --git a/editor/app/components/lanes/laneState.ts b/editor/app/components/lanes/laneState.ts
@@ -45,6 +45,21 @@ export function deriveLaneState({
return "idle";
}
+// WHY A SWEEP LANE IS NOT WORKING, in words; null when it is, or when there is
+// nothing to say. ONE COPY: the operations rail (railStates.ts) and the Active
+// Jobs lane strip (buildActiveJobs.ts) carried this table twice, byte for
+// byte, and the sweep panel says the same two facts in sentences. Takes
+// deriveLaneState's input and follows its precedence, so the word and the
+// note cannot disagree about which fact wins.
+export function sweepLaneNote(
+ input: Parameters<typeof deriveLaneState>[0],
+): string | null {
+ if (input.available === false) return "no operation switched on";
+ if (input.gateHeld) return input.feedRunning ? "sweep armed, lane paused" : "lane paused";
+ if (input.feedRunning || (input.activeCount ?? 0) > 0) return null;
+ return "no sweep armed";
+}
+
// No new palette. These map onto the station tones the channel line already
// uses (see flow/tone.ts): four theme families × light/dark means a bespoke hue
// here would be wrong in eight palettes at once. Deliberately NOT a second copy
diff --git a/editor/app/jobs/active/buildActiveJobs.ts b/editor/app/jobs/active/buildActiveJobs.ts
@@ -16,6 +16,13 @@ import {
type DiskGateReason,
} from "yt-dlp-transcript-common/lib/diskSpace";
import type { RunningJobsListItem } from "../components/RunningJobsList";
+// Directive-free and value-import-free by construction (laneState.ts imports
+// only tone maps and a type), so a server module can read the same derivation
+// the client rail does.
+import {
+ deriveLaneState,
+ sweepLaneNote,
+} from "../../components/lanes/laneState";
import {
AUTO_DOWNLOAD_KIND,
AUTO_TRANSCRIBE_KIND,
@@ -350,29 +357,23 @@ function sweepLane(l: {
available: boolean;
inFlight: number;
}): ActiveLaneView {
- // Same precedence as deriveLaneState: unavailable, then the gate, then work.
- // HOLDING is the state that has no other name — a sweep armed behind a shut
- // gate is not stopped and is not working, and it looked identical to wedged.
- const state: ActiveLaneView["state"] = !l.available
- ? "unavailable"
- : l.gateHeld
- ? "holding"
- : l.sweeping || l.inFlight > 0
- ? "running"
- : "idle";
+ // THE SAME DERIVATION THE RAIL READS, not a server-side restatement of it.
+ // This file used to spell out deriveLaneState's precedence and carry a
+ // byte-identical copy of its note table, which is two chances for the strip
+ // and the rail to describe one lane differently. HOLDING is the state that
+ // has no other name — a sweep armed behind a shut gate is not stopped and is
+ // not working, and it looked identical to wedged.
+ const input = {
+ available: l.available,
+ gateHeld: l.gateHeld,
+ feedRunning: l.sweeping,
+ activeCount: l.inFlight,
+ };
return {
kind: l.kind,
label: l.label,
- state,
- note: !l.available
- ? "no operation switched on"
- : l.gateHeld
- ? l.sweeping
- ? "sweep armed, lane paused"
- : "lane paused"
- : state === "running"
- ? null
- : "no sweep armed",
+ state: deriveLaneState(input),
+ note: sweepLaneNote(input),
inFlight: l.inFlight,
};
}
diff --git a/editor/app/operations/components/SweepLane.tsx b/editor/app/operations/components/SweepLane.tsx
@@ -210,7 +210,13 @@ export function SweepLane({
{/* THE STATE SENTENCE. `holding` is the one an operator has no word for:
a sweep armed behind a shut gate looks exactly like a wedged runner
- unless something says otherwise. */}
+ unless something says otherwise.
+
+ THE LONG FORM OF sweepLaneNote's `holding` row, deliberately not a
+ third caller of it: the rail and the Active Jobs strip need four words
+ in a line, and this panel has room for the sentence that says what to
+ do about it. Same two facts, same precedence — if that row's wording
+ changes, this changes with it. */}
{state === "holding" && (
<p className="text-sm text-warning">
<span className="font-mono text-xs uppercase tracking-[0.14em]">
diff --git a/editor/app/operations/components/railStates.ts b/editor/app/operations/components/railStates.ts
@@ -1,6 +1,9 @@
import type { AutoQueueStatusPayload } from "../status";
import type { SweepLaneId, SweepLaneStatus } from "../lanes";
-import { deriveLaneState } from "../../components/lanes/laneState";
+import {
+ deriveLaneState,
+ sweepLaneNote,
+} from "../../components/lanes/laneState";
import type { RailLaneState } from "./OperationRail";
import { idleReasonText } from "./dispatch";
@@ -38,23 +41,18 @@ export function railStates(
};
}
- const sweepState = (sweep: SweepLaneStatus): RailLaneState => ({
- state: deriveLaneState({
+ // ONE INPUT, TWO READINGS. The word and the note come off the same object
+ // through the same precedence, so they cannot disagree about which fact wins
+ // — which is exactly what two hand-maintained copies of the table could.
+ const sweepState = (sweep: SweepLaneStatus): RailLaneState => {
+ const input = {
available: sweep.available,
gateHeld: sweep.gateHeld,
feedRunning: sweep.sweeping,
activeCount: sweep.inFlight.length,
- }),
- note: !sweep.available
- ? "no operation switched on"
- : sweep.gateHeld
- ? sweep.sweeping
- ? "sweep armed, lane paused"
- : "lane paused"
- : sweep.sweeping || sweep.inFlight.length > 0
- ? null
- : "no sweep armed",
- });
+ };
+ return { state: deriveLaneState(input), note: sweepLaneNote(input) };
+ };
out.digest = sweepState(data.lanes.digest);