"use client"; import type { PauseLane } from "yt-dlp-transcript-common/lib/pauseGates"; import { LaneActionButton } from "./LaneActionButton"; import type { LaneControl } from "./LaneCard"; import { pauseLaneAction, resumeLaneAction } from "../../operations/actions"; // ONE PAUSE CONTROL, drawn wherever a lane is: the dashboard band, the monitor // widget's rail, /workers, /jobs, both sweep panels and the runner pages. // There were three copies of this button and four inline descriptors, and they // had already drifted — one disabled the RESUME side, one used the opposite // emphasis, one had no importers at all. // // A TYPE-ONLY IMPORT OF PauseLane, deliberately. This is a client module; a // value import from pauseGates.ts would drag operations.ts and settings.ts — // and with them `fs` — into the client graph. // // THE LABELS BELOW ARE THE ARIA CONTRACT and are byte-identical to what the // dashboard shipped before this file existed. backfill.spec, widget.spec, // dashboard.spec, auto-queue.spec, workers.spec and parakeet-partial.spec select // these buttons by accessible name, and Playwright's name match is a // case-insensitive SUBSTRING unless `exact` is passed (none of them pass it), // which is why a spec asking for "Pause Digest" finds "pause digests". // // THE TWO OPERATION LANES SAY *HOLD* IN THEIR TOOLTIPS, on purpose. The lane's // arm switch RUNS it and the gate HOLDS it: on a card that carries both, "Run // every channel" and "Pause backfill" are two acts whose costs to undo differ by // a week of GPU time, and different verbs are the cheapest thing that keeps them // apart. A lane with no arm switch beside it has nothing to be confused with, so // it keeps the plain word. type PauseControlInput = { lane: PauseLane; held: boolean; onChange?: () => void | Promise; // Applies to the PAUSE side only — see below. disabled?: boolean; }; type PauseCopy = { label: string; ariaLabel: string; title?: string; }; const PAUSE_COPY: Record = { transcription: { free: { label: "Pause Transcriptions", ariaLabel: "Pause Transcriptions", title: "Stop taking new work; in-flight parakeet jobs finish the current segment, cache it, and resume on the next run", }, held: { label: "Resume Transcriptions", ariaLabel: "Resume Transcriptions" }, }, download: { free: { label: "Pause Downloads", ariaLabel: "Pause Downloads", title: "Stop new downloads: the auto-download runner idles and manual sync/download actions are blocked until resumed. Enumeration (store-playlist) still runs.", }, held: { label: "Resume Downloads", ariaLabel: "Resume Downloads" }, }, digest: { free: { label: "Hold the lane", ariaLabel: "pause digests", title: "Hold digest generation without stopping the lane. The runner idles at zero and resumes instantly.", }, held: { label: "Resume the lane", ariaLabel: "resume digests", title: "Resume digest generation. The running job picks up where it left off — it was holding, not stopped.", }, }, // The publish lane (release 18): a hold stops it dispatching the next stage; // the stage in flight finishes. publish: { free: { label: "Hold the lane", ariaLabel: "pause publishing", title: "Hold the publish lane: the stage in flight finishes and no next one starts. Manual Build, Deploy and Publish now still work.", }, held: { label: "Resume the lane", ariaLabel: "resume publishing", title: "Resume the publish lane. Its next check decides whether a pass is due.", }, }, backfill: { free: { label: "Hold the lane", ariaLabel: "pause backfill", title: "Hold the backfill lane without ending anything. A running job idles at zero and keeps its place; nothing is re-derived on resume. Survives a restart.", }, held: { label: "Resume the lane", ariaLabel: "resume backfill", title: "Resume the backfill lane. A held job picks up within a few seconds — it was holding, not stopped.", }, }, }; // The descriptor, for a LaneCard's `controls` array. export function pauseLaneControl({ lane, held, onChange, disabled, }: PauseControlInput): LaneControl { const copy = held ? PAUSE_COPY[lane].held : PAUSE_COPY[lane].free; return { key: `${lane}-gate`, label: copy.label, glyph: held ? "▶" : "❙❙", ariaLabel: copy.ariaLabel, title: copy.title, // `active` is warning-filled, so a HELD lane is visible from across the // room. The sweep panels used to do the opposite — tint the button while the // lane was running fine — and now match the dashboard. variant: held ? "active" : "idle", // A RESUME IS ALWAYS REACHABLE. The caller's `disabled` is a statement about // the pause ("there are no workers to pause", "no operation on this lane is // switched on"); applying it to the resume side would make a paused pool // with zero enabled workers impossible to release. disabled: held ? undefined : disabled, action: () => (held ? resumeLaneAction(lane) : pauseLaneAction(lane)), onChange, }; } // The same control as a standalone button, for a surface that is not a LaneCard. // LaneCard supplies `dense` itself from useSectionFrame; here it is a prop. export function PauseLaneButton({ dense, ...props }: PauseControlInput & { dense?: boolean }) { const { key: _key, ...control } = pauseLaneControl(props); return ; }