Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit b7fd46a8d2c66804cbec1839259d67cc1cfcfa20
parent 7f59a8f6fba08977a62f1d36a3eeb81e817a8a09
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon,  7 Sep 2026 23:57:49 -0400

editor: every control over a lane's gate writes the lane's key, and the policy save carries it

"Run the backfill lane" goes through `withGateHeld` and its checkbox reads
`isGateHeld`, so the form and the pause button beside it throw one switch;
`saveAutoQueueAction` carries `held` beside `snoozeUntil`, because the one
object in this repo that spells every policy field explicitly is the one place a
lane could be silently unheld by a rule reorder. The two unrelated saves that
merely PRESERVE a retired pause field keep preserving it — it is the migration's
input now, and rebuilding it would change how an unmigrated file reads.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/app/operations/[id]/page.tsx | 11+++++++++--
Meditor/app/operations/actions.ts | 11++++++++---
Meditor/app/operations/components/settings/LaneSettingsForm.tsx | 25++++++++++++++++---------
Meditor/app/operations/settingsActions.ts | 42++++++++++++++++++++++++++++--------------
Meditor/app/settings/actions.ts | 8++++++--
Meditor/instrumentation.ts | 7+++++--
6 files changed, 72 insertions(+), 32 deletions(-)

diff --git a/editor/app/operations/[id]/page.tsx b/editor/app/operations/[id]/page.tsx @@ -5,7 +5,10 @@ import { listChannelConfigs } from "yt-dlp-transcript-common/controller/channels import { PLATFORM_VALUES } from "yt-dlp-transcript-common/lib/platform"; import { selectableBucketsForKind } from "yt-dlp-transcript-common/jobs/autoQueuePolicy"; import { LANES } from "yt-dlp-transcript-common/lib/autoQueueTypes"; -import { pauseLaneFor } from "yt-dlp-transcript-common/lib/pauseGates"; +import { + isGateHeld, + pauseLaneFor, +} from "yt-dlp-transcript-common/lib/pauseGates"; import { operationCatalog, operationLabel, @@ -210,7 +213,11 @@ export default async function OperationPage({ // equivalent — its order lives on the lane policy the console already edits. const laneSettings = runnerKind === "backfill" ? ( - <LaneSettingsForm key="backfill-lane" initial={settings.backfill} /> + <LaneSettingsForm + key="backfill-lane" + initial={settings.backfill} + held={isGateHeld(settings, "backfill")} + /> ) : null; // THE CADENCE CONSOLE, off the descriptor's trigger. A cadence-triggered diff --git a/editor/app/operations/actions.ts b/editor/app/operations/actions.ts @@ -59,9 +59,13 @@ export async function saveAutoQueueAction( const current = getSettings(); // NOTE: this object lists every persisted policy field EXPLICITLY, so a field // added to AutoQueuePolicy and forgotten here is silently dropped on every - // save rather than failing loudly. `snoozeUntil` is deliberately carried over - // from `current` instead of taken from the form: the snooze is set by its own - // action, and a policy save (e.g. reordering rules) must not cancel it. + // save rather than failing loudly. `snoozeUntil` and `held` are deliberately + // carried over from `current` instead of taken from the form: each is set by + // its own action, and a policy save (e.g. reordering rules) must not cancel a + // snooze or UNHOLD A HELD LANE — which, since the gate moved onto the policy + // in slice 1.4, is exactly what "forgotten here" would now mean. `undefined` + // is carried as undefined on purpose: that is what keeps a lane that has + // never been written falling back to its retired field. const next: SiteSettings = { ...current, autoQueue: { @@ -72,6 +76,7 @@ export async function saveAutoQueueAction( replaceAutoSubs: input.replaceAutoSubs === true, order: input.order, snoozeUntil: current.autoQueue[kind].snoozeUntil ?? null, + held: current.autoQueue[kind].held, root: input.root, }, }, diff --git a/editor/app/operations/components/settings/LaneSettingsForm.tsx b/editor/app/operations/components/settings/LaneSettingsForm.tsx @@ -15,8 +15,15 @@ import type { SaveResult } from "../../../settings/actions"; // That is why it takes no operation: there is nothing here that belongs to one. export function LaneSettingsForm({ initial, + // THE LANE'S GATE, read by the caller through `isGateHeld`. Not + // `initial.enabled`: since slice 1.4 the gate is `autoQueue.backfill.held`, + // and `backfill.enabled` is the retired field it migrated FROM — a checkbox + // reading it would show the pre-migration answer on a file that has since + // been written, and would disagree with the pause button beside it. + held, }: { initial: SiteSettings["backfill"]; + held: boolean; }) { const [state, formAction, pending] = useActionState< SaveResult | undefined, @@ -48,27 +55,27 @@ export function LaneSettingsForm({ </p> <p className="text-xs text-muted-foreground"> <strong className="font-medium text-foreground"> - Which operations a corpus sweep runs, and over which channels, is - not set here. + Which operations the lane runs, and over which channels, is not set + here. </strong>{" "} - That scope has to be written at the moment the sweep is armed — a - scope saved separately would be resurrected as a corpus-wide run by - the next restart &mdash; so it lives in the sweep controls directly - above, beside the itinerary they draw, which is what lets you read the - commitment before making it. + That scope is the lane&apos;s rule list, on the runner console above + &mdash; where it sits beside the work each rule is holding, which is + what lets you read the commitment before making it. </p> <label className="flex items-start gap-2 text-sm"> <input type="checkbox" name="backfillEnabled" - defaultChecked={initial.enabled} + defaultChecked={!held} className="mt-1" /> <span className="flex flex-col gap-1"> <span className="font-medium">Run the backfill lane</span> <span className="text-xs text-muted-foreground"> Off means the lane still <em>reports</em> what is missing — - that is what the indicators are for — but runs nothing. + that is what the indicators are for — but runs nothing. It is + the same gate the Hold button above throws: one switch, two places + to reach it. </span> </span> </label> diff --git a/editor/app/operations/settingsActions.ts b/editor/app/operations/settingsActions.ts @@ -21,6 +21,7 @@ import { isDigestSectionKind, isDigestTimestampMode, } from "yt-dlp-transcript-common/lib/digest"; +import { withGateHeld } from "yt-dlp-transcript-common/lib/pauseGates"; import type { SaveResult } from "../settings/actions"; // Same helper as operations/actions.ts, and for the same reason — a "use server" @@ -98,7 +99,12 @@ export async function saveDigestSettingsAction( // writeSettings runs sanitizeDigestApps over it. Narrowed at exactly // this field so every OTHER field in the block stays type-checked. apps: digestApps as SiteSettings["digest"]["apps"], - // Not edited by this form — the dashboard/channel controls own the pause. + // Not edited by this form, and no longer the gate: since slice 1.4 the + // digest lane's pause is `autoQueue.digest.held`, which the `...current` + // spread above carries untouched. This field is the migration's INPUT and + // is preserved rather than rebuilt, because a save that reset it would + // change how a settings.json that has NOT yet been written through + // getSettings reads. digestsPaused: dD.digestsPaused, spendCapUsd: Number.parseFloat( String(formData.get("digestSpendCapUsd") ?? "").trim(), @@ -193,11 +199,16 @@ export async function saveDiarizationSettingsAction( // form is drawn per LANE (beside the shared pause and sweep) rather than per // operation. // -// The spread protects two things specifically: `allowRedownload`, which an -// unrelated save must never flip ON (it writes media to a 97%-full disk), and -// `sweepEnabled` + its scope, which this form does NOT render at all — those -// are owned by the sweep controls above it on the same page, and reading them -// from an absent form field would disarm a running multi-day sweep. +// The spread protects `allowRedownload` specifically, which an unrelated save +// must never flip ON (it writes media to a 97%-full disk). +// +// "RUN THE BACKFILL LANE" IS THE LANE'S GATE, and it goes through +// `withGateHeld` — a deliberate SECOND WRITER of `autoQueue.backfill.held`, +// beside the pause buttons. It has always been two controls over one switch +// (the checkbox wrote `backfill.enabled`, which `isGateHeld` read inverted); +// what slice 1.4 changed is that the switch moved, and a form still writing the +// retired field would be silently ignored by every reader — the mirror image of +// the bug `withGateHeld` writing only the new key avoids on the other side. // // LaneSettingsForm renders every field of this block and is the only form that // posts here. @@ -207,15 +218,18 @@ export async function saveBackfillLaneSettingsAction( ): Promise<SaveResult> { const current = getSettings(); const dBack = current.backfill; - const next: SiteSettings = { - ...current, - backfill: { - ...dBack, - enabled: formData.get("backfillEnabled") === "on", - concurrency: num(formData, "backfillConcurrency", dBack.concurrency), - allowRedownload: formData.get("backfillAllowRedownload") === "on", + const next: SiteSettings = withGateHeld( + { + ...current, + backfill: { + ...dBack, + concurrency: num(formData, "backfillConcurrency", dBack.concurrency), + allowRedownload: formData.get("backfillAllowRedownload") === "on", + }, }, - }; + "backfill", + formData.get("backfillEnabled") !== "on", + ); try { await writeSettings(next); } catch (e) { diff --git a/editor/app/settings/actions.ts b/editor/app/settings/actions.ts @@ -217,8 +217,12 @@ export async function saveSettingsAction( parallelTranscriptions: PARALLEL_TRANSCRIPTIONS_DEFAULT, inlineTranscribeOnFallback, skipLiveDownloads, - // Pause flags aren't edited by this form (the dashboard/widget/Workers - // controls own them). Preserve the current values on an unrelated save. + // The two RETIRED pause flags. They are not this form's business and they + // are not the gate any more either — since slice 1.4 that is + // `autoQueue[lane].held`, preserved a few lines below with the rest of the + // policy. These are the migration's INPUT for a settings.json that has not + // yet been written through getSettings, so they are preserved rather than + // rebuilt: resetting them here would change how such a file reads. transcriptionsPaused: getSettings().transcriptionsPaused, downloadsPaused: getSettings().downloadsPaused, verifyAvailabilityBeforeClean, diff --git a/editor/instrumentation.ts b/editor/instrumentation.ts @@ -65,9 +65,12 @@ export async function register() { const { isGateHeld } = await import( "yt-dlp-transcript-common/lib/pauseGates" ); - // One of exactly two places the PERSISTED transcription flag is read (the + // One of exactly two places the PERSISTED transcription gate is read (the // other is the action's "did this change anything" check). Everything that - // asks "is transcription held right now" reads the pool instead. + // asks "is transcription held right now" reads the pool instead. Since + // slice 1.4 the stored value is `autoQueue.transcription.held`; this line + // needs no edit for that, because isGateHeld is the one thing that knows + // where a lane's gate lives. if (isGateHeld(getSettings(), "transcription")) { const { getWorkerPool } = await import( "yt-dlp-transcript-common/jobs/workerPool"