import Link from "next/link"; import type { Provenance } from "@/lib/provenance"; import type { BuildStatus } from "@/lib/manifest"; import { fmtDur } from "@/lib/format"; import NoteField from "@/components/NoteField"; import CopyButton from "@/components/CopyButton"; import PlanDiffPanel from "@/components/PlanDiffPanel"; import type { NoteMap } from "@/lib/notes"; import { concentrationLine, shares } from "@/lib/concentration"; // Where a cut came from: the recipe that built it, and every clip it plays. // // Server-rendered on purpose. The largest plan here is ~880 notes, and handing // those to a client component would put them on the wire twice -- once as JSON // for the props and again as the HTML they turn into. // How many note rows to render by default. // // Not a cosmetic limit. Each row is six cells of Tailwind class strings and two // links, and every one of them is ALSO serialised into the RSC flight payload -- // so it lands on the wire roughly twice. Pokemon's 269 notes already make a // 2.1 MB page; mario-rpg's 880-note plan would be over 7 MB, on a force-dynamic // route that re-renders on every navigation. // // The cap is stated rather than silent, and `?notes=all` renders the lot. A // truncation you cannot see reads as "this is the whole plan", which is exactly // the wrong thing for a provenance table to imply. const ROW_LIMIT = 250; export default function ProvenancePanel({ song, cut, prov, build, planParam, notes, allNotes = false, }: { song: string; cut: string; prov: Provenance; build: BuildStatus; planParam: string | null; notes: NoteMap; allNotes?: boolean; }) { const href = (plan: string | null) => `/browse/${song}/${cut}${plan ? `?plan=${encodeURIComponent(plan)}` : ""}`; const shown = allNotes ? prov.rows : prov.rows.slice(0, ROW_LIMIT); const hidden = prov.rows.length - shown.length; return (
{build.stale} — treat everything below as a lead, not as fact
)}
Not recorded. The builder for this song does not name the plan it used, and guessing
would put a recipe you cannot trust next to one you can — pick the plan below instead.
A future build records itself with{" "}
video-dir.mjs --song {song} --rel ….
{concentrationLine(shares(prov.videos.map((v) => v.clips)))} ·{" "} across every song
)} {prov.available.length > 0 && (No clips.csv here and no plan chosen — pick one above.
) : ( <>| source | clips | date | first moment |
|---|---|---|---|
|
{v.title || v.video}
|
{v.clips} | {v.date} | {v.link ? ( {fmtDur(v.firstSrcStart)} ) : ( {fmtDur(v.firstSrcStart)} )} |
re-trimming a clip in the deck gives it a new key — a plan built on the old window will no longer find it
{hidden > 0 && (showing the first {shown.length} by song time — {hidden} not shown.{" "} show all {prov.rows.length}
)}| at | voice | word | shift | pile | clip |
|---|---|---|---|---|---|
| {r.songTime.toFixed(2)} | {r.voice} | {r.word || "—"} | {r.shift.toFixed(2)} | {r.pile || "—"} |
{r.link && (
archive
)}
open in deck
{/* The song-time of the note, cited straight out of
the arrangement -- no need to find it in a
player first. */}
|