import { notFound } from "next/navigation"; import BrowseHeader from "@/components/BrowseHeader"; import ClaimBench, { type ClaimBenchData } from "./ClaimBench"; import { manifestToken } from "@/lib/report/manifest.mjs"; import { readClaimDetail, readManifest } from "@/lib/projects/report.mjs"; import { ledgerTotals } from "umtool-report-to-video/ledger-totals"; import type { ProjectRef } from "@/lib/project-types"; // The server half of the claim bench. // // Same rule as the clip bench: the id is validated as a MEMBER of the ledger, // never as a path. The difference is what gets read -- a clip wants its window // and its waveform, a claim wants ROOM. Ninety seconds either side, because a // first-person quote is routinely the host reading someone else's words or // being sarcastic, and the quote alone cannot show you which. export default async function ClaimBenchPage({ project, claimId, }: { project: ProjectRef; claimId: string; }) { const manifest = await readManifest(project.dir); if (!manifest) notFound(); const detail = await readClaimDetail(project.dir, claimId, { manifest }); if (!detail) notFound(); const ledger = (manifest.ledger ?? []) as Array>; const i = ledger.findIndex((e) => e.id === claimId); // Which predicates fire on THIS claim, computed over whatever has been ruled // on so far. `strict: false` because the ledger is by definition half-worked // while somebody is standing on this page. let fired: Array<{ rule: string; text: string }> = []; try { const totals = ledgerTotals(ledger, { strict: false }); fired = totals.steps.find((s: { id: string }) => s.id === claimId)?.flags ?? []; } catch { /* a malformed ledger shows as gaps, not as a broken page */ } const data: ClaimBenchData = { project: project.id, claim: { id: String(detail.claim.id), date: String(detail.claim.date ?? ""), company: (detail.claim.company as string) ?? null, value: (detail.claim.value as number | null) ?? null, display: (detail.claim.display as string) ?? null, label: (detail.claim.label as string) ?? null, quote: (detail.claim.quote as string) ?? null, src: (detail.claim.src as string) ?? null, video: (detail.claim.video as string) ?? null, channel: (detail.claim.channel as string) ?? null, cite: (detail.claim.cite as number | null) ?? null, scope: (detail.claim.scope as string) ?? null, scopeBasis: (detail.claim.scopeBasis as string) ?? null, scopeConfidence: (detail.claim.scopeConfidence as string) ?? null, population: (detail.claim.population as string) ?? null, valueKind: (detail.claim.valueKind as string) ?? null, flags: Array.isArray(detail.claim.flags) ? (detail.claim.flags as string[]) : null, roles: Array.isArray(detail.claim.roles) ? (detail.claim.roles as ClaimBenchData["claim"]["roles"]) : null, }, at: detail.at, view: detail.view, pad: detail.pad, cues: detail.cues, source: detail.source, noCues: detail.noCues, windows: detail.windows, gaps: detail.gaps, fired, prev: i > 0 ? String(ledger[i - 1].id) : null, next: i >= 0 && i < ledger.length - 1 ? String(ledger[i + 1].id) : null, token: await manifestToken(project.dir), }; const outstanding = ledger.filter( (e) => !e.scope || !e.scopeBasis || !e.scopeConfidence || !e.population || !e.valueKind || !Array.isArray(e.flags), ).length; return (
); }