import type { JobStatus, JobProgressMetric, JobTaskKind, } from "../jobs/registry"; // A ROW IS ONE JOB, whichever of three places knows about it: the registry (a // live JobRecord), the .jobs directory (a .log + .meta.json sidecar the // registry has forgotten), or the scheduler (a slot whose record was evicted — // a phantom, and the one row with no id the registry can answer for). Three // surfaces used to draw three shapes; every field name the card renderer's row // type had is kept, so the widget and the dashboard read what they read before. // TYPES ONLY: this module is imported from "use client" files, and the builder // that fills it (active/buildActiveJobs.ts) reaches the registry and the // filesystem. export type JobRowStatus = JobStatus | "archived" | "evicted"; export type StuckReason = | "record-evicted" | "terminal-but-holding-slot" | "possibly-stalled"; export type JobRowTask = { id: string; label: string; // Imported for the same reason as `metric` below: a re-spelled literal here // would not fail the build when JobTaskKind grew a member. kind: JobTaskKind; fraction?: number; detail?: string; // Epoch ms when this sub-operation started, for the live "running for" timer. startedAt: number; // The worker running this transcription (transcribe tasks only). workerId?: string; workerName?: string; // Audio-integrity probe state (download tasks only). When phase is "probing" // yt-dlp is paused for an ffmpeg integrity check; the bar fills against the // estimated probe duration instead of the (frozen) download fraction. phase?: "downloading" | "probing"; probeStartedAt?: number; probeEtaSeconds?: number; }; export type JobRowProgress = { // Imported, NOT re-spelled: a literal copy here silently drifted from // JobProgressMetric and would not fail the build when the union grew. metric: JobProgressMetric; initial: number; current: number; target: number; pct: number; // Estimated seconds remaining (remaining tasks × measured avg per task). // Undefined until the first task completes, or when no work remains. etaSeconds?: number; }; export type JobRowView = { id: string; kind: string; // "" for an old log with no sidecar; the cell renders "—" status: JobRowStatus; queueKey?: string; // undefined = unknown (old log); "" = parallel ("(immediate)") channelSlug?: string; videoId?: string; progress?: JobRowProgress; tasks?: JobRowTask[]; draining?: boolean; drainable?: boolean; // Background work (e.g. an auto-download unit) queues BEHIND a manual job on // the same platform queue. Shown as an "auto" badge, and — when queued — as a // hint that a foreground job (a clicked Sync) is being let through first. background?: boolean; // Reorder affordances for a QUEUED job: whether it can move toward (up/promote) // or away from (down) the head of its queue. Both false for running jobs and // for parallel ("") jobs that aren't serialized. canMoveUp?: boolean; canMoveDown?: boolean; // The history side (listJobs.ts). queuedAt?: number; startedAt?: number; endedAt?: number; exitCode?: number; logSize?: number; inRegistry?: boolean; replayable?: boolean; // The slot side (the scheduler's view of the same id). position?: number; // 0 = running head, 1.. = queued; absent for parallel/history heldMs?: number; stuck?: { reason: StuckReason; heldMs: number }; pid?: number; lastLogLine?: string; // stuck rows only — see readLastLogLine // A short phrase naming what THIS job is for, when the kind alone does not // say it — "umtool · elfpire-eva/c03" on a fetch-window row. Built by // 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; // Why a `cancelled` row was cancelled when no person pressed Cancel — the // boot pass's reason for a job a restart left queued ("server restarted; // the scheduler re-derives syncs"). History rows only; see listJobs.ts. cancelReason?: string; // 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"; };