import Link from "next/link"; import { formatBytes } from "yt-dlp-transcript-common/lib/format"; import { STATION_DOT, STATION_TEXT, formatCount, } from "yt-dlp-transcript-common/views/pipeline/tone"; import type { StageTone } from "yt-dlp-transcript-common/views/pipeline/stageStatus"; import type { CleanupSummary } from "../lib/loadCleanup"; // THE SIEVE. The cleanup sweep is a cascade of `continue`s — a video leaves at // the FIRST gate it hits — so this is the shape the page takes: all the audio on // disk enters at the top, each gate siphons off its share, and the remainder // steps down the column to the reclaimable figure the hero already prints. // // The gates are NUMBERED because they genuinely are a sequence. The order IS the // attribution rule, and it is the only thing stopping the five figures from // double-counting each other. // // No new colours, no new type. The rail and its station dots are the transit // vocabulary the channel page established (flow/tone.ts, which the two maps are // imported from rather than copied — laneState.ts warns that a third copy would // guarantee drift), and the bars use the same --chart-N tokens as the hero bar // directly above. type Gate = { n: number; label: string; // What clears it. Split so the prescription can be a link where one exists. clears: React.ReactNode; bytes: number; count: number; // Bytes inside this gate that NOTHING queued will ever release. neverClears?: number; }; export function HoldSieve({ summary }: { summary: CleanupSummary }) { const { totalAudioBytes, heldBytes, heldCounts, measuredReclaimBytes, atRiskBytes, staleSnapshotCount, } = summary; // Nothing measured at all: every figure would be a zero nobody computed. if (totalAudioBytes <= 0) { return (
Audio on disk

—

{staleSnapshotCount > 0 ? ( <> No report yet says what is holding this audio.{" "} to measure it. ) : ( <>No audio on disk. )}

); } const gates: Gate[] = [ { n: 1, label: "no transcript yet", clears: "run: Transcribe", bytes: heldBytes.noTranscript, count: heldCounts.noTranscript, }, { n: 2, label: "keep-latest window", clears: "setting: keepLatest", bytes: heldBytes.keepLatest, count: heldCounts.keepLatest, }, { n: 3, label: "do-not-clean pins", clears: "unpin on the video", bytes: heldBytes.doNotClean, count: heldCounts.doNotClean, }, { n: 4, label: "awaiting diarization", clears: "run: Diarize", bytes: heldBytes.awaitingDiarization, count: heldCounts.awaitingDiarization, neverClears: heldBytes.diarizationNeverClears, }, ]; // The remainder after each gate, which is what the column steps down. let remainder = totalAudioBytes; const steps = gates.map((gate) => { remainder -= gate.bytes; return { gate, remainder: Math.max(0, remainder) }; }); const pct = (n: number) => (n / totalAudioBytes) * 100; return (
{/* Everything on disk enters here. */}
Audio on disk

~{formatBytes(totalAudioBytes)}

{/* The rail. One gate per station, in the sweep's own order. */}
    {steps.map(({ gate, remainder: left }) => ( ))}
{/* What survives all four — the number the page already leads with. */}

~{formatBytes(measuredReclaimBytes)}

{atRiskBytes > 0 && (

the upstream check may still refuse ~{formatBytes(atRiskBytes)} cached as gone-from-source

)} {staleSnapshotCount > 0 && (

{staleSnapshotCount} channel{staleSnapshotCount === 1 ? "" : "s"} not in this measurement — to include{" "} {staleSnapshotCount === 1 ? "it" : "them"}.

)}
); } function GateRow({ gate, remainder, pct, }: { gate: Gate; remainder: number; pct: (n: number) => number; }) { const held = gate.bytes > 0; const never = gate.neverClears ?? 0; // A gate holding nothing is an outline, not a filled dot — the tone map's own // rule. A gate holding audio nothing will ever release is the one danger on // the page, and it earns it. const tone: StageTone = never > 0 ? "danger" : held ? "attention" : "neutral"; const clearsBytes = Math.max(0, gate.bytes - never); return (
  • {/* The tick joining this station to the rail. */}
  • ); } function Bar({ children }: { children: React.ReactNode }) { return (
    {children}
    ); } function Eyebrow({ children }: { children: React.ReactNode }) { return (

    {children}

    ); } function RefreshLink() { return ( Refresh reports ); }