import Link from "next/link"; import { Badge } from "yt-dlp-transcript-common/components/ui/badge"; import type { SnapshotBuckets, StageId } from "yt-dlp-transcript-common/views/pipeline/stageStatus"; // ANOMALIES, one chip each, every one a link to the stage that owns it. // // Not on the line: these are not a conserved quantity moving through stages, // they are things that went wrong at one of them. The line answers "where is the // work"; this answers "what is broken". // // It renders a quiet "All clear" rather than disappearing when empty. A strip // that vanishes is indistinguishable from a strip that failed to render, and the // whole point of putting it on the first screen is that you can trust its // silence. type Chip = { label: string; count?: number; stage: StageId; tone: "warning" | "destructive"; }; const DAY_MS = 24 * 60 * 60 * 1000; // A snapshot is regenerated on a ~1s debounce after any report-changing action, // so a week-old one means nothing has touched this channel in a week — worth // saying before someone reads its counts as current. const STALE_REPORT_DAYS = 7; // The sync cadence gate already spaces full sweeps out; a month is well past any // of its intervals. const STALE_SYNC_DAYS = 30; export type AttentionInput = { slug: string; buckets: SnapshotBuckets; digestWarnings: string[]; failedVideoIds: string[]; generatedAt: string; lastSyncedAt?: string; }; function ageInDays(iso: string | undefined): number | null { if (!iso) return null; const t = Date.parse(iso); if (Number.isNaN(t)) return null; return (Date.now() - t) / DAY_MS; } export function collectAttention(input: AttentionInput): Chip[] { const { buckets } = input; const chips: Chip[] = []; const add = ( label: string, count: number, stage: StageId, tone: Chip["tone"] = "warning", ) => { if (count > 0) chips.push({ label, count, stage, tone }); }; add("failed transcriptions", input.failedVideoIds.length, "transcribe", "destructive"); add("need cookies", buckets.needsCookies.length, "download"); add("partial downloads", buckets.partialDownloads.length, "download"); add("corrupt sources", buckets.corruptSource.length, "download", "destructive"); add( "corrupt full sources", buckets.corruptFullSource.length, "download", "destructive", ); add( "incomplete transcripts", buckets.incompleteTranscript.length, "transcribe", ); add("short audio", buckets.shortAudio.length, "transcribe"); add("duplicate dirs", buckets.duplicateDirs.length, "diagnostics"); add("no metadata", buckets.noMetadata.length, "diagnostics"); add("missing from archive", buckets.missingFromArchive.length, "diagnostics"); add("digest warnings", input.digestWarnings.length, "digest"); // Freshness. Both of these are about whether the numbers above can be trusted, // which is why they sit in the same strip rather than in a footnote. if (!input.generatedAt) { chips.push({ label: "no report yet", stage: "playlist", tone: "warning" }); } else { const days = ageInDays(input.generatedAt); if (days != null && days > STALE_REPORT_DAYS) { chips.push({ label: `report is ${Math.floor(days)} days old`, stage: "playlist", tone: "warning", }); } } if (!input.lastSyncedAt) { chips.push({ label: "never synced", stage: "playlist", tone: "warning" }); } else { const days = ageInDays(input.lastSyncedAt); if (days != null && days > STALE_SYNC_DAYS) { chips.push({ label: `last synced ${Math.floor(days)} days ago`, stage: "playlist", tone: "warning", }); } } return chips; } export function AttentionStrip(props: AttentionInput) { const chips = collectAttention(props); if (chips.length === 0) { return (

All clear — nothing is flagged on this channel.

); } return (
{chips.map((c) => ( {c.count != null && ( {c.count.toLocaleString()} )} {c.label} ))}
); }