import Link from "next/link"; import { readdir, readFile, stat } from "node:fs/promises"; import path from "node:path"; import BrowseHeader from "@/components/BrowseHeader"; import { Markdown } from "@/lib/markdown"; import { fmtAgo } from "@/lib/format"; import type { ProjectRef } from "@/lib/project-types"; // A report that is not yet a video. There is nothing to judge and nothing to // build, so the page is the report itself plus the one action that matters: // what it would take to make it a cut. const SWEEP_RE = /(^|[-_])sweep([-_]report)?\.md$|^sweep-report\.md$/i; export default async function SweepProject({ project }: { project: ProjectRef }) { const names = await readdir(project.dir).catch(() => [] as string[]); const reportName = names.find((n) => SWEEP_RE.test(n)); const file = reportName ? path.join(project.dir, reportName) : null; const [text, st] = await Promise.all([ file ? readFile(file, "utf8").catch(() => null) : null, file ? stat(file).catch(() => null) : null, ]); const citations = text ? (text.match(/\]\([^)]*[?&]v=/g) ?? []).length : 0; return (

A report, not yet a video

Turning it into one means writing a{" "} video.manifest.json beside it: one entry per clip, each with a window in absolute source seconds taken from{" "} transcript.cues.json. A report only records a single start second per citation, so the windows cannot be recovered from it alone — that matching is the work.

{citations > 0 ? `${citations} citation${citations === 1 ? "" : "s"} to work from.` : "No `?v=` citations found, so there is nothing to match windows against yet."}{" "} See umtool/docs/authoring.md.

{text ? (
{reportName} {st && {fmtAgo(st.mtimeMs)}}
) : (

No report file found under{" "} {project.id}.

)} ← every project
); }