Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 66c121a27d667c40e3f834b7e8ab82add70b079a
parent ac6e6a0068ea1678f579b5cd94330ac60aa2ed48
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Wed, 26 Aug 2026 00:49:45 -0400

stages: extract the shell the speakers and digest cards share

BackfillStage.tsx:117-226 and DigestStage.tsx:106-168 were the same markup twice
— the labelled section wrapper, the count heading, the per-population lines, and
VideoIdList with its four aria labels. OperationWork is that, presentational and
nothing else.

NOT ONE COMPONENT FOR BOTH CARDS, deliberately. They overlap in what they SHOW
and diverge in everything that ACTS: digest holds four useState vars and a
lane-coupled queue-key follower (picking "metered" must swap the default key, or
the two lanes serialize on one key and the network lane idles while the GPU
works), a six-argument trigger, and a SECOND conditional StreamActionLog on a
third queue for "Normalize transcripts"; the speakers card has one trigger and
one QueueControl. None of that is expressible as data. Merging them would mean
one component branching on its own identity — the shape this whole slice is
undoing — so each card keeps its own trigger block, passed as children so it
still renders inside the labelled section the e2e suite scopes buttons by. The
criterion is written in the file: if the shell ever branches on an operation id,
stop.

Nor is the shell fed `kinds[]` with digest as a row. Two traps if it were:
digestWorkOf's `source` has no BackfillKindView field, and `kinds.length` drives
the heading pluralization while allReachableIds is a Set union — so a digest row
would move both on every channel.

THE ARIA LABELS ARE THE E2E CONTRACT and pass through unchanged: verified
one-for-one against HEAD on both files. `ariaLabel` is optional on a line
because digest's lead paragraph is prose with no label while the speakers card's
lead IS a population ("backfill reachable") — a difference in the contract, not
in the layout. The shell owns exactly one piece of layout, the `mt-1` on every
line but the first, which is the only reason the two cards' lead markup differed.

VideoIdList's Props type is exported as VideoIdListProps so the shell can name
what it forwards.

DigestStage's props are unchanged — the noDigest fallback (digestWorkOf,
channelSnapshot.ts, source: "bucket") is the server's concern and never reaches
the component.

tsc clean. e2e runs next, once, for the slice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Diffstat:
Meditor/app/channels/[slug]/components/VideoIdList.tsx | 4++--
Meditor/app/channels/[slug]/components/stages/BackfillStage.tsx | 216++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Meditor/app/channels/[slug]/components/stages/DigestStage.tsx | 152+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Aeditor/app/channels/[slug]/components/stages/OperationWork.tsx | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 298 insertions(+), 170 deletions(-)

diff --git a/editor/app/channels/[slug]/components/VideoIdList.tsx b/editor/app/channels/[slug]/components/VideoIdList.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; -type Props = { +export type VideoIdListProps = { slug: string; ids: string[]; ariaLabel: string; @@ -16,7 +16,7 @@ export function VideoIdList({ emptyAriaLabel, emptyMessage, itemAriaLabel, -}: Props) { +}: VideoIdListProps) { if (ids.length === 0) { return ( <p className="text-sm text-muted-foreground" aria-label={emptyAriaLabel}> diff --git a/editor/app/channels/[slug]/components/stages/BackfillStage.tsx b/editor/app/channels/[slug]/components/stages/BackfillStage.tsx @@ -24,7 +24,7 @@ import { StreamActionLog } from "yt-dlp-transcript-common/components/StreamActio import { QueueControl } from "../../../../components/QueueControl"; import { cancelJobAction } from "../../../../jobs/actions"; import { backfillChannelAction } from "../../backfillActions"; -import { VideoIdList } from "../VideoIdList"; +import { OperationWork } from "./OperationWork"; export type BackfillKindView = { id: string; @@ -114,22 +114,24 @@ export function BackfillStage({ ].sort(); return ( - // RENAMED WITH THE STAGE. The POPULATION labels below ("backfill reachable", - // "backfill kind …") are deliberately NOT renamed here: they name the LANE's - // populations, appear on 16 spec lines, and go in the vocabulary pass that - // follows this slice. Fewer hand-edited spec lines is less e2e risk for the - // same result. - <div aria-label="speakers section" className="flex flex-col gap-3"> - <div> - <h3 className="text-base font-semibold"> + <OperationWork + // RENAMED WITH THE STAGE. The POPULATION labels below ("backfill + // reachable", "backfill kind …") are deliberately NOT renamed: they name + // the LANE's populations, appear on 16 spec lines, and go in the + // vocabulary pass that follows this slice. Fewer hand-edited spec lines is + // less e2e risk for the same result. + sectionLabel="speakers section" + heading={ + <> {heading} &middot; {kinds.length}{" "} {kinds.length === 1 ? "operation" : "operations"} - </h3> - <p - aria-label="backfill reachable" - className="text-sm text-muted-foreground" - > - {anyEnabled ? ( + </> + } + lines={[ + { + key: "reachable", + ariaLabel: "backfill reachable", + node: anyEnabled ? ( <> {reachable.toLocaleString()}{" "} {reachable === 1 ? "video" : "videos"} can be worked on now. @@ -140,92 +142,102 @@ export function BackfillStage({ </> ) : ( "Nothing here is enabled. Turn an operation on in Settings and this card will report what the existing corpus is missing." - )} - </p> - {missingInput > 0 && ( - <p - aria-label="backfill needs re-acquiring" - className="mt-1 text-sm text-muted-foreground" - > - {missingInput.toLocaleString()} more{" "} - {missingInput === 1 ? "video needs" : "videos need"} their media - re-acquired first - {allowRedownload - ? " — re-download is on, so this run will fetch and then delete it, bounded by the free-disk floor." - : " — re-download is off, so this run skips them."} - </p> - )} - {deferredKinds.map((k) => ( - <p - key={k.id} - aria-label="backfill deferred" - data-kind={k.id} - className="mt-1 text-sm text-muted-foreground" - > - {k.deferred.toLocaleString()}{" "} - {k.deferred === 1 ? "video is" : "videos are"}{" "} - {k.deferredHint ?? - `being skipped by ${k.label} under the current configuration`} - . - </p> - ))} - {blocked > 0 && ( - <p - aria-label="backfill blocked" - className="mt-1 text-sm text-muted-foreground" - > - {blocked.toLocaleString()}{" "} - {blocked === 1 ? "video is" : "videos are"} waiting on{" "} - {blockedOn.length > 0 - ? blockedOn.join(" and ") - : "an earlier backfill"}{" "} - and will become available as{" "} - {blockedOn.length === 1 ? "it runs" : "those run"} — nothing to do - here. - </p> - )} - </div> - - {/* ONE ROW PER OPERATION, always — not only when there are several. - A single-operation lane still has to say WHICH operation, because the - card above it no longer does: the heading names a group. */} - {kinds.map((k) => ( - <p - key={k.id} - aria-label={`backfill kind ${k.id}`} - className="text-xs text-muted-foreground" - > - <span className="font-medium">{k.label}</span>:{" "} - {k.reachableIds.length} reachable - {k.stale > 0 && ` (${k.stale} stale)`} ·{" "} - {k.missingInput.toLocaleString()} needing media - {k.deferred > 0 && ` · ${k.deferred.toLocaleString()} deferred`} - {k.blocked > 0 && ` · ${k.blocked.toLocaleString()} blocked`} - {/* WHAT ONE UNIT COSTS, beside the backlog and never as a judgement. - 11,337 reachable videos means ~194,000 model calls when the unit - is the transcript chunk and ~11,337 when it is the video, and no - other line on this page can tell you which. Stated flat: a "this - is a lot" threshold would be a magic number the next operation - gets wrong, and what is affordable is the operator's call. */} - {k.costBasis && ( - <span className="italic"> &mdash; {k.costBasis}</span> - )} - </p> - ))} - - <VideoIdList - slug={slug} - ids={allReachableIds} - ariaLabel="videos needing a backfill list" - emptyAriaLabel="videos needing a backfill empty" - emptyMessage={ - anyEnabled - ? `Nothing reachable for ${actionLabel}.` - : "Nothing here is enabled." - } - itemAriaLabel={(id) => `video needing a backfill ${id}`} - /> - + ), + }, + ...(missingInput > 0 + ? [ + { + key: "missing-input", + ariaLabel: "backfill needs re-acquiring", + node: ( + <> + {missingInput.toLocaleString()} more{" "} + {missingInput === 1 ? "video needs" : "videos need"} their + media re-acquired first + {allowRedownload + ? " — re-download is on, so this run will fetch and then delete it, bounded by the free-disk floor." + : " — re-download is off, so this run skips them."} + </> + ), + }, + ] + : []), + // ONE LINE PER KIND, not one summed line: each kind defers for its own + // reason and the fix differs, so a single count under a single sentence + // would attach one kind's remedy to another kind's videos. + ...deferredKinds.map((k) => ({ + key: `deferred-${k.id}`, + ariaLabel: "backfill deferred", + dataKind: k.id, + node: ( + <> + {k.deferred.toLocaleString()}{" "} + {k.deferred === 1 ? "video is" : "videos are"}{" "} + {k.deferredHint ?? + `being skipped by ${k.label} under the current configuration`} + . + </> + ), + })), + ...(blocked > 0 + ? [ + { + key: "blocked", + ariaLabel: "backfill blocked", + node: ( + <> + {blocked.toLocaleString()}{" "} + {blocked === 1 ? "video is" : "videos are"} waiting on{" "} + {blockedOn.length > 0 + ? blockedOn.join(" and ") + : "an earlier backfill"}{" "} + and will become available as{" "} + {blockedOn.length === 1 ? "it runs" : "those run"} — nothing + to do here. + </> + ), + }, + ] + : []), + ]} + // ONE ROW PER OPERATION, always — not only when there are several. A + // single-operation lane still has to say WHICH operation, because the + // heading above it no longer does: it names a group. + rows={kinds.map((k) => ({ + key: k.id, + ariaLabel: `backfill kind ${k.id}`, + node: ( + <> + <span className="font-medium">{k.label}</span>:{" "} + {k.reachableIds.length} reachable + {k.stale > 0 && ` (${k.stale} stale)`} ·{" "} + {k.missingInput.toLocaleString()} needing media + {k.deferred > 0 && ` · ${k.deferred.toLocaleString()} deferred`} + {k.blocked > 0 && ` · ${k.blocked.toLocaleString()} blocked`} + {/* WHAT ONE UNIT COSTS, beside the backlog and never as a + judgement. 11,337 reachable videos means ~194,000 model calls + when the unit is the transcript chunk and ~11,337 when it is the + video, and no other line on this page can tell you which. Stated + flat: a "this is a lot" threshold would be a magic number the + next operation gets wrong, and what is affordable is the + operator's call. */} + {k.costBasis && ( + <span className="italic"> &mdash; {k.costBasis}</span> + )} + </> + ), + }))} + list={{ + slug, + ids: allReachableIds, + ariaLabel: "videos needing a backfill list", + emptyAriaLabel: "videos needing a backfill empty", + emptyMessage: anyEnabled + ? `Nothing reachable for ${actionLabel}.` + : "Nothing here is enabled.", + itemAriaLabel: (id: string) => `video needing a backfill ${id}`, + }} + > <StreamActionLog trigger={() => backfillChannelAction(slug, queue)} cancelAction={cancelJobAction} @@ -242,6 +254,6 @@ export function BackfillStage({ /> } /> - </div> + </OperationWork> ); } diff --git a/editor/app/channels/[slug]/components/stages/DigestStage.tsx b/editor/app/channels/[slug]/components/stages/DigestStage.tsx @@ -19,7 +19,7 @@ import { type DigestLaneChoice, } from "../../digestActions"; import { normalizeChannelAction } from "../../normalizeActions"; -import { VideoIdList } from "../VideoIdList"; +import { OperationWork } from "./OperationWork"; type Props = { slug: string; @@ -102,71 +102,91 @@ export function DigestStage({ Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : undefined; return ( - <div - aria-label="digest section" - className="flex flex-col gap-3" + <OperationWork + sectionLabel="digest section" + heading={<>Generate digests ({noDigestIds.length})</>} + lines={[ + { + key: "lead", + // NO aria-label, deliberately: this is prose, not a population, and + // nothing scopes to it. The speakers card's lead IS a population + // ("backfill reachable"), which is why the shell makes the label + // optional rather than inventing one here. + node: ( + <> + Chapters (and optionally topic tags) derived from each transcript + by a local model, written to <code>ai-digest.json</code> next to + it. A re-run regenerates only what the current model and prompt + version have not already produced, so running this twice costs + nothing the second time. Hand corrections live in a separate{" "} + <code>ai-digest.overrides.json</code> and are never overwritten. + </> + ), + }, + ...(partial > 0 + ? [ + { + key: "partial", + ariaLabel: "digest partial", + node: ( + <> + {partial.toLocaleString()} of them already{" "} + {partial === 1 ? "has" : "have"} some sections at the + current settings — a re-run generates only the ones still + outstanding, so {partial === 1 ? "it costs" : "they cost"} a + fraction of an undigested video. + </> + ), + }, + ] + : []), + ...(deferred > 0 + ? [ + { + key: "deferred", + ariaLabel: "digest deferred", + node: ( + <> + {deferred.toLocaleString()} more{" "} + {deferred === 1 ? "video has" : "videos have"} a transcript + but no current <code>transcript.cues.json</code>, so{" "} + {deferred === 1 ? "it is" : "they are"} held back from the + digest lane. Nothing produces one automatically — a channel + whose subtitles are downloaded rather than transcribed never + runs the normalizer — so this does not clear itself. Run{" "} + <em>Normalize transcripts</em> below to make{" "} + {deferred === 1 ? "it" : "them"} digestable. + </> + ), + }, + ] + : []), + ...(blocked > 0 + ? [ + { + key: "blocked", + ariaLabel: "digest blocked", + node: ( + <> + {blocked.toLocaleString()}{" "} + {blocked === 1 ? "video has" : "videos have"} no transcript + yet and {blocked === 1 ? "is" : "are"} waiting on + transcription — nothing to do here. + </> + ), + }, + ] + : []), + ]} + list={{ + slug, + ids: noDigestIds, + ariaLabel: "videos without a digest list", + emptyAriaLabel: "videos without a digest empty", + emptyMessage: "Every transcript has a current digest.", + itemAriaLabel: (id: string) => `video without a digest ${id}`, + }} > - <div> - <h3 className="text-base font-semibold"> - Generate digests ({noDigestIds.length}) - </h3> - <p className="text-sm text-muted-foreground"> - Chapters (and optionally topic tags) derived from each transcript by a - local model, written to <code>ai-digest.json</code> next to it. A - re-run regenerates only what the current model and prompt version have - not already produced, so running this twice costs nothing the second - time. Hand corrections live in a separate{" "} - <code>ai-digest.overrides.json</code> and are never overwritten. - </p> - {partial > 0 && ( - <p - aria-label="digest partial" - className="mt-1 text-sm text-muted-foreground" - > - {partial.toLocaleString()} of them already{" "} - {partial === 1 ? "has" : "have"} some sections at the current - settings — a re-run generates only the ones still outstanding, so{" "} - {partial === 1 ? "it costs" : "they cost"} a fraction of an - undigested video. - </p> - )} - {deferred > 0 && ( - <p - aria-label="digest deferred" - className="mt-1 text-sm text-muted-foreground" - > - {deferred.toLocaleString()} more{" "} - {deferred === 1 ? "video has" : "videos have"} a transcript but no - current <code>transcript.cues.json</code>, so{" "} - {deferred === 1 ? "it is" : "they are"} held back from the digest - lane. Nothing produces one automatically — a channel whose subtitles - are downloaded rather than transcribed never runs the normalizer — - so this does not clear itself. Run <em>Normalize transcripts</em>{" "} - below to make {deferred === 1 ? "it" : "them"} digestable. - </p> - )} - {blocked > 0 && ( - <p - aria-label="digest blocked" - className="mt-1 text-sm text-muted-foreground" - > - {blocked.toLocaleString()}{" "} - {blocked === 1 ? "video has" : "videos have"} no transcript yet and{" "} - {blocked === 1 ? "is" : "are"} waiting on transcription — nothing to - do here. - </p> - )} - </div> - - <VideoIdList - slug={slug} - ids={noDigestIds} - ariaLabel="videos without a digest list" - emptyAriaLabel="videos without a digest empty" - emptyMessage="Every transcript has a current digest." - itemAriaLabel={(id) => `video without a digest ${id}`} - /> - <StreamActionLog trigger={() => digestChannelAction(slug, lane, queue, order, limitCount) @@ -245,6 +265,6 @@ export function DigestStage({ label="Normalize transcripts" /> )} - </div> + </OperationWork> ); } diff --git a/editor/app/channels/[slug]/components/stages/OperationWork.tsx b/editor/app/channels/[slug]/components/stages/OperationWork.tsx @@ -0,0 +1,96 @@ +"use client"; + +// THE PRESENTATION TWO STAGE CARDS SHARE: a labelled section, a count heading, +// the per-population lines, and the id list. +// +// NOT ONE COMPONENT FOR BOTH CARDS. BackfillStage and DigestStage overlap in +// what they SHOW and diverge in everything that ACTS, so merging them would mean +// one component branching on its own identity — the shape this whole slice is +// undoing. What actually diverges: +// +// - Digest holds four useState vars and a lane-coupled queue-key follower +// (picking "metered" must swap the default key, or the two lanes serialize +// on one key and the network lane idles while the GPU works). +// - Digest's trigger takes six arguments; the speakers card's takes two. +// - Digest renders a SECOND conditional StreamActionLog, on a third queue, for +// "Normalize transcripts". +// +// None of that is expressible as data, so the shell is presentational only and +// each card keeps its own trigger block as `children`. +// +// THE CRITERION, for whoever extends this: if the shell ever branches on an +// operation id, stop — two honest files beat one dishonest one. +// +// THE ARIA LABELS ARE THE E2E CONTRACT and pass straight through unchanged. The +// shell supplies no label of its own and invents none. + +import type { ReactNode } from "react"; +import { VideoIdList, type VideoIdListProps } from "../VideoIdList"; + +// One population line. `ariaLabel` is optional because the digest card's lead +// paragraph is prose with no label, while the speakers card's is +// "backfill reachable" — a difference in the e2e contract, not in the layout. +export type OperationWorkLine = { + key: string; + ariaLabel?: string; + // Emitted as data-kind, for a line that belongs to one operation among + // several (the per-kind `deferred` lines, which each carry their own remedy). + dataKind?: string; + node: ReactNode; +}; + +export function OperationWork({ + sectionLabel, + heading, + lines, + rows, + list, + children, +}: { + sectionLabel: string; + heading: ReactNode; + // The population lines, in order. The first gets no top margin and the rest + // do — the one piece of layout the shell owns, because it is the only reason + // the two cards' markup differed on the lead paragraph. + lines: OperationWorkLine[]; + // Per-operation rows, below the intro block and above the list. Absent on a + // card that runs one operation and has nothing to enumerate. + rows?: OperationWorkLine[]; + list: Omit<VideoIdListProps, "slug"> & { slug: string }; + // The card's own trigger block. Inside the labelled section, because that is + // what the e2e suite scopes its button lookups by. + children?: ReactNode; +}) { + return ( + <div aria-label={sectionLabel} className="flex flex-col gap-3"> + <div> + <h3 className="text-base font-semibold">{heading}</h3> + {lines.map((line, i) => ( + <p + key={line.key} + aria-label={line.ariaLabel} + data-kind={line.dataKind} + className={`${i === 0 ? "" : "mt-1 "}text-sm text-muted-foreground`} + > + {line.node} + </p> + ))} + </div> + + {rows?.map((row) => ( + <p + key={row.key} + aria-label={row.ariaLabel} + data-kind={row.dataKind} + className="text-xs text-muted-foreground" + > + {row.node} + </p> + ))} + + <VideoIdList {...list} /> + + {children} + </div> + ); +}