import BrowseHeader from "@/components/BrowseHeader"; import FaceDeck from "@/components/FaceDeck"; import { faceQueue, readFaceVerdicts, rejectedVideos } from "@/lib/faces"; export const dynamic = "force-dynamic"; // THE FACE JUDGER: every corner that has ever been cut into a cover, one at a // time, with the crop as something you can see and move. // // A STATIC segment under app/browse/, so it wins over app/browse/[song]/ -- // which would otherwise catch /browse/faces, fail readSong() and 404. The same // trap /browse/decisions and /browse/find both document, and the suite asserts // it here too, because when this regresses the page still renders perfectly in // isolation and only the URL stops working. // // It is NOT a tenth nav item. AppNav is at nine and says why: a tenth wraps the // header on a laptop, and a nav that wraps stops reading as one row of places. // This is reached from the cover-art bench on a song page -- which is where // somebody is looking at the four faces when they notice one is wrong. // // The page reads the queue directly rather than fetching its own API, like // /browse/find does: a render that waited on a round trip to itself would be // serialising for nothing. /api/face exists so the suite and a script can ask // the same question of the same function. type Params = Record; const one = (v: string | string[] | undefined) => (Array.isArray(v) ? v[0] : v) ?? null; export default async function FacesPage({ searchParams }: { searchParams: Promise }) { const params = await searchParams; const [queue, verdicts, rejected] = await Promise.all([ faceQueue(), readFaceVerdicts(), rejectedVideos(), ]); // Two ways in, because the two callers know different things. The bench knows // a COVER and wants its four faces; a link written in a note knows the exact // corner. Neither is made to learn the other's identifier. const wantKey = one(params.key); const wantCover = one(params.cover); const start = (wantKey && queue.find((e) => e.key === wantKey)?.key) ?? (wantCover && queue.find((e) => e.covers.includes(wantCover))?.key) ?? null; const onAccepted = queue.filter((e) => e.onAccepted).length; const unrecorded = queue.filter((e) => !e.crop).length; return (

Every corner on record, deduped by episode and moment, with the ones that ship first. The dashed amber box is what facecrop.py would cut; the solid one is yours. Judging writes{" "} face-verdicts.json, which{" "} make-thumb.mjs reads — this page never touches the thumb manifests. {unrecorded > 0 && ( <> {" "} {unrecorded} of them have no box recorded at all, so what they were cut from is not known — only what they were cut near. )}

); }