// THE VIDEO PAGE'S CHORES, hand-listed, in the order VideoPanel renders them. // // The video page draws two kinds of card. The OPERATION panels (digest, // diarization, attribution) are enumerated from the registry by // videoOperationPanels.ts and rendered after VideoPanel. Everything VideoPanel // itself draws is a CHORE: a per-video action or marker that no registry entry // owns. This list is the video page's twin of the channel page's hand-listed // bookends (configure/playlist … cleanup/diagnostics/storage/danger around // GROUP_STAGES in common/views/pipeline/stageStatus.ts): enumerated in ONE // place beside the registry's panels, and deliberately NOT folded into the // registry — they are not operations, and inventing entries for them would give // the registry states and outputs it has no way to compute. // // Two rows are the per-video run surface of the EXTERNAL operations (download, // transcription). They are hand-listed for the reason videoOperations.ts gives // for leaving the externals out of its reader: these cards already are their // per-video surface, with actions of their own. // // VideoPanel keeps its JSX literal (the gating booleans read better inline); // videoChoreCards.test.ts pins that its render order is exactly this list, so // a card added there without a row here fails a test rather than going unlisted. // Not a card, so not listed: VideoNavStrip, PipelineStatusStrip, the umtool // link and the one-line "media archived" status, which is the archive-media // card's marker restated at the top. // // No directive: a plain data module. Nothing server-side imports it; a server // component that did would get client references for `component`, not values. import type { ComponentType } from "react"; import { AvailabilityHistoryList } from "../components/cards/AvailabilityHistoryList"; import { DeleteVideoDirSection } from "../components/cards/DeleteVideoDirSection"; import { DoNotCleanSection } from "../components/cards/DoNotCleanSection"; import { DownloadOutcomeBadge } from "../components/cards/DownloadOutcomeBadge"; import { ExcludeTruncatedCheckSection } from "../components/cards/ExcludeTruncatedCheckSection"; import { FetchedWindows } from "../components/cards/FetchedWindows"; import { FilesList } from "../components/cards/FilesList"; import { IncompleteTranscriptBanner } from "../components/cards/IncompleteTranscriptBanner"; import { MarkUntranscribableSection } from "../components/cards/MarkUntranscribableSection"; import { PerFileTranscodeRow } from "../components/cards/PerFileTranscodeRow"; import { PerFileTranscribeRow } from "../components/cards/PerFileTranscribeRow"; import { RedownloadSection } from "../components/cards/RedownloadSection"; import { ShortAudioBanner } from "../components/cards/ShortAudioBanner"; import { SourceVideoSection } from "../components/cards/SourceVideoSection"; import { TranscriptSourceSection } from "../components/cards/TranscriptSourceSection"; export type VideoChoreCard = { // The PipelineStageCard id where the chore has a card; a banner's own name // where it does not. id: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- a list of heterogeneous props component: ComponentType; // When VideoPanel draws it (the gate is VideoPanel's; this is its gloss). shown: string; // Why it is a chore, not an operation. why: string; }; export const VIDEO_CHORE_CARDS: readonly VideoChoreCard[] = [ { id: "download-outcome", component: DownloadOutcomeBadge, shown: "a download-outcome.json exists and is not plain ok", why: "a report of the last download attempt; it runs nothing", }, { id: "incomplete-transcript", component: IncompleteTranscriptBanner, shown: "always mounted; draws only while coverage says truncated, or after a run here", why: "a repair of the download operation's output, not an output of its own", }, { id: "short-audio", component: ShortAudioBanner, shown: "always mounted; draws only while the outcome is failed-short-audio, or after a run here", why: "a repair of the download operation's output, not an output of its own", }, { id: "availability-history", component: AvailabilityHistoryList, shown: "always", why: "a read-only history the sync writes; nothing to run", }, { id: "download", component: RedownloadSection, shown: "always", why: "the external download operation's per-video run surface (videoOperations.ts leaves externals out)", }, { id: "transcode", component: PerFileTranscodeRow, shown: "one row per transcodable source file, when there is one", why: "a per-file media utility; the transcode operation was retired 2026-08-30", }, { id: "transcribe", component: PerFileTranscribeRow, shown: "always; one row per audio file", why: "the external transcription operation's per-video run surface (videoOperations.ts leaves externals out)", }, { id: "transcript-source", component: TranscriptSourceSection, shown: "at least one transcript..vtt is on disk", why: "an operator choice of which caption track is canonical; produces no derived data", }, { id: "mark-untranscribable", component: MarkUntranscribableSection, shown: "no transcript yet", why: "a marker that stops the pipeline retrying; it is a verdict, not work", }, { id: "source-video-persistence", component: SourceVideoSection, shown: "always, with its controls on every channel (youtube handling too since release 10 slice N)", why: "a storage move into the saved-video store; not derived from the transcript", }, { id: "fetched-windows", component: FetchedWindows, shown: "clips/ holds at least one window", why: "a read-only listing of a cache another tool filled", }, { id: "archive-media", component: DoNotCleanSection, shown: "always", why: "a do-not-clean marker the cleanup lanes read", }, { id: "truncated-check", component: ExcludeTruncatedCheckSection, shown: "always", why: "an opt-out marker the truncated check reads", }, { id: "files", component: FilesList, shown: "always", why: "the directory listing, with per-file preview and delete", }, { id: "danger", component: DeleteVideoDirSection, shown: "always", why: "deletes the whole video directory", }, ];