import type { Metadata } from "next"; import Link from "next/link"; import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { formatBytes } from "yt-dlp-transcript-common/lib/format"; import { Alert, AlertDescription } from "yt-dlp-transcript-common/components/ui/alert"; import { buildCleanupLedger, loadCleanupSummary } from "./lib/loadCleanup"; import { ChannelCleanupCard } from "./components/ChannelCleanupCard"; import { HoldSieve } from "./components/HoldSieve"; import { ReleaseLedger } from "./components/ReleaseLedger"; import { ChannelWorkTable } from "../components/channelWork/ChannelWorkTable"; import { sectionsFor } from "../components/channelWork/sections"; import { getActionableSummary } from "../lib/actionable/loadActionable"; export const dynamic = "force-dynamic"; export const metadata: Metadata = { title: "Cleanup" }; export default async function CleanupPage() { const paths = getPaths(); const summary = await loadCleanupSummary(paths); // The same request-cached briefs loadCleanupSummary just read, folded a // second way. The two cleanup sections are the only channel-work sections // that belong to no operation: they are bytes, and bytes are this page. const actionable = await getActionableSummary(paths); const { rows, includedReclaimBytes, totalReclaimBytes, includedExtraFormatsBytes, includedForeignBytes, includedCount, excludedCount, } = summary; const excludedReclaimBytes = totalReclaimBytes - includedReclaimBytes; const hasAnything = rows.length > 0; // Breakdown bar segments (share of the three reclaimable estimates across // INCLUDED channels). They overlap — alternative sweeps, not additive — so the // bar is a relative sense of where reclaimable audio sits, not a sum. const segTotal = includedReclaimBytes + includedExtraFormatsBytes + includedForeignBytes; const seg = (n: number) => (segTotal > 0 ? (n / segTotal) * 100 : 0); return (

Cleanup

{/* Hero readout — the reclamation console. */}

Reclaimable across {includedCount} channel {includedCount === 1 ? "" : "s"}

{formatBytes(includedReclaimBytes)}

{excludedCount > 0 && (

{excludedCount} channel{excludedCount === 1 ? "" : "s"} excluded {excludedReclaimBytes > 0 && ( <> · ~{formatBytes(excludedReclaimBytes)} held back )}

)}
{segTotal > 0 && (
)} The total is the primary Clean audio reclaim (audio kept beside finished transcripts). Extra-format and wrong-format sweeps overlap it, so they're shown per channel rather than added in. Per-video do-not-clean pins and the keep-latest window are already excluded. Toggle a channel to hold its space back from the total without losing the sweep.
{/* Why the rest of it is still on disk, and what to run to get it back. The hero answers "how much can I reclaim right now"; these two answer the question an operator with weeks of queued GPU work actually has. */} {!hasAnything ? (

No channels yet.

) : (
{rows.map((r) => ( ))}
)} {/* BELOW the cards, because the page is an argument in order: hero, then the sieve, then the release ledger, then the per-channel cards — and these tables are the "do it" step at the end of it. NOT merged into ChannelCleanupCard, deliberately, and out of scope for this slice: the card is per-channel and these are per-bucket lists across channels, and collapsing one into the other is a redesign of the card, not a move. */}

Estimates come from each channel's last report — run{" "} Refresh reports {" "} to recompute. Whole-channel and per-video deletion stay on the{" "} channel {" "} pages.

); } function Legend({ swatch, label, value, }: { swatch: string; label: string; value: number; }) { return ( ); }