Archilyzer · Source

archilyzer

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

commit ae2c0f42c3c2e240af6d0efbc3605a8416595b52
parent 6cb327e2121ce66203f5b156824a011aacb1aa9a
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Wed, 29 Jul 2026 11:20:16 -0400

Wire the duplicate-cluster confirm path — it was built and never called

`updateDuplicateOverride` has existed since duplicate review was built, with the
`confirmed` flag, the read-modify-write, the atomic rename, and the careful rule
that an empty patch clears a decision without deleting a confirmation. It had
**zero callers anywhere in the repo**.

That matters because `clusterMaySharePartial` fails CLOSED: a `needsReview`
cluster — title and near-identical runtime, but nothing compared the content —
shares no derived work and reaches no built site until a human records
`confirmed: true`. With no caller there was no way for a human to record it, so
the review queue asked a question that could not be answered. Corpus-wide that
is ~166 clusters permanently stuck.

/actionable now reads the overrides alongside the report (so a decided cluster
stops asking), each card carries Confirm / Not a duplicate / Undo, and the
badges distinguish "needs review" from "confirmed" and "not a duplicate".

Confirming also runs the share immediately, via the equally-uncalled
`shareClusterFromCanonical`. The point of confirming is to let derived work
flow; making the operator wait for the canonical member's next sweep to find out
whether it would have makes the button feel inert. It is a no-op when the
canonical has no digest yet, which today is almost always.

The buttons are disabled until mounted. A server-rendered button has no handler
until React hydrates, so a click before then fires nothing at all — no request,
no job, no error. That is the recorded root cause of the digest pilot's
"un-created job", and /actionable renders a page heavy enough to hit the same
window.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Diffstat:
Meditor/app/actionable/actions.ts | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Aeditor/app/actionable/components/DuplicateClusterReview.tsx | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Meditor/app/actionable/lib/loadActionable.ts | 20+++++++++++++++++---
Meditor/app/actionable/page.tsx | 46+++++++++++++++++++++++++++++++++++++++++-----
4 files changed, 241 insertions(+), 9 deletions(-)

diff --git a/editor/app/actionable/actions.ts b/editor/app/actionable/actions.ts @@ -10,7 +10,12 @@ import { import { getRegistry } from "yt-dlp-transcript-common/jobs/registry"; import { runManagedFunction } from "yt-dlp-transcript-common/jobs/streamCommand"; import { drainStream } from "yt-dlp-transcript-common/jobs/drainStream"; -import { detectDuplicateShorts } from "yt-dlp-transcript-common/controller/duplicateShorts"; +import { + detectDuplicateShorts, + readDuplicateReport, + updateDuplicateOverride, +} from "yt-dlp-transcript-common/controller/duplicateShorts"; +import { shareClusterFromCanonical } from "yt-dlp-transcript-common/controller/digestSharing"; import { clearIncompleteTranscriptsAction, enableAutoRunners, @@ -130,6 +135,69 @@ export async function runDuplicateDetectionAction( return { ok: true, clusters, videosInClusters }; } +// What a human can say about a cluster the detector could not decide. +// confirmed — "I looked; these really are the same video." Unblocks +// sharing AND publication for a title+duration suspect. +// not-duplicate — "They are not." Suppresses the cluster entirely. +// clear — undo, back to awaiting review. +export type DuplicateClusterDecision = "confirmed" | "not-duplicate" | "clear"; + +export type ReviewDuplicateClusterResult = + | { ok: true; shared: number; misaligned: number } + | { ok: false; error: string }; + +// Record a review decision for one cluster. +// +// `updateDuplicateOverride` has existed — with the `confirmed` flag, the +// read-modify-write, the atomic rename and the "an empty patch clears the +// decision" rule — since duplicate review was built, and until now **nothing in +// the repo called it**. `clusterMaySharePartial` fails closed, so every +// needsReview cluster shared nothing and there was no way for a human to change +// that. This is that missing caller. +// +// Confirming also attempts the share immediately, via the equally-uncalled +// `shareClusterFromCanonical`: the point of confirming is to let derived work +// flow, and making the operator wait for the canonical member's next sweep to +// find out whether it would have would make the button feel inert. It is a +// no-op when the canonical has no digest yet, which today is almost always. +export async function reviewDuplicateClusterAction( + clusterId: string, + decision: DuplicateClusterDecision, +): Promise<ReviewDuplicateClusterResult> { + const paths = getPaths(); + try { + const overrides = await updateDuplicateOverride( + paths, + clusterId, + decision === "confirmed" + ? { confirmed: true, notDuplicate: false } + : decision === "not-duplicate" + ? { notDuplicate: true, confirmed: false } + : { confirmed: false, notDuplicate: false }, + ); + + let shared = 0; + let misaligned = 0; + if (decision === "confirmed") { + const report = await readDuplicateReport(paths); + const cluster = report?.clusters.find((c) => c.clusterId === clusterId); + if (cluster) { + for (const outcome of await shareClusterFromCanonical(paths, cluster, { + overrides, + })) { + if (outcome.status === "shared") shared++; + else if (outcome.status === "misaligned") misaligned++; + } + } + } + + revalidatePath("/actionable"); + return { ok: true, shared, misaligned }; + } catch (e) { + return { ok: false, error: (e as Error)?.message ?? String(e) }; + } +} + export type GlobalIncompleteResult = | { ok: true; channels: number; affected: number } | { ok: false; error: string }; diff --git a/editor/app/actionable/components/DuplicateClusterReview.tsx b/editor/app/actionable/components/DuplicateClusterReview.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + reviewDuplicateClusterAction, + type DuplicateClusterDecision, + type ReviewDuplicateClusterResult, +} from "../actions"; +import type { DuplicateClusterOverride } from "yt-dlp-transcript-common/lib/duplicates"; + +type Status = + | { kind: "idle" } + | { kind: "running"; decision: DuplicateClusterDecision } + | { kind: "done"; result: ReviewDuplicateClusterResult } + | { kind: "error"; message: string }; + +export function DuplicateClusterReview({ + clusterId, + override, +}: { + clusterId: string; + override: DuplicateClusterOverride | undefined; +}) { + const [status, setStatus] = useState<Status>({ kind: "idle" }); + // Disabled until mounted, deliberately. A server-rendered button has no + // handler until React hydrates, so a click before then fires NOTHING — no + // request, no error, nothing to debug. That is the exact failure that made + // the digest pilot's job look "un-created", and /actionable renders a page + // heavy enough to hit the same window. See StreamActionLog. + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); + + async function decide(decision: DuplicateClusterDecision) { + setStatus({ kind: "running", decision }); + try { + setStatus({ + kind: "done", + result: await reviewDuplicateClusterAction(clusterId, decision), + }); + } catch (e) { + setStatus({ kind: "error", message: (e as Error).message }); + } + } + + const busy = status.kind === "running"; + const disabled = busy || !mounted; + const confirmed = override?.confirmed === true; + const rejected = override?.notDuplicate === true; + + return ( + <div className="flex items-center gap-2 flex-wrap"> + <button + type="button" + onClick={() => decide("confirmed")} + disabled={disabled || confirmed} + aria-label={`confirm duplicate cluster ${clusterId}`} + className="px-2 py-1 rounded-md border border-border text-xs font-medium hover:bg-muted disabled:opacity-50" + > + {busy && status.decision === "confirmed" ? "Confirming…" : "Confirm"} + </button> + <button + type="button" + onClick={() => decide("not-duplicate")} + disabled={disabled || rejected} + aria-label={`reject duplicate cluster ${clusterId}`} + className="px-2 py-1 rounded-md border border-border text-xs font-medium hover:bg-muted disabled:opacity-50" + > + {busy && status.decision === "not-duplicate" ? "Rejecting…" : "Not a duplicate"} + </button> + {(confirmed || rejected) && ( + <button + type="button" + onClick={() => decide("clear")} + disabled={disabled} + aria-label={`clear duplicate cluster decision ${clusterId}`} + className="px-2 py-1 rounded-md text-xs text-muted-foreground underline hover:text-foreground disabled:opacity-50" + > + {busy && status.decision === "clear" ? "Clearing…" : "Undo"} + </button> + )} + {status.kind === "done" && status.result.ok && ( + <span + aria-label={`duplicate cluster review result ${clusterId}`} + className="text-xs text-muted-foreground" + > + {status.result.shared > 0 || status.result.misaligned > 0 + ? `${status.result.shared} digest(s) shared` + + (status.result.misaligned > 0 + ? `, ${status.result.misaligned} misaligned` + : "") + : "Saved."} + </span> + )} + {status.kind === "done" && !status.result.ok && ( + <span + role="alert" + aria-label={`duplicate cluster review error ${clusterId}`} + className="text-xs text-destructive" + > + {status.result.error} + </span> + )} + {status.kind === "error" && ( + <span + role="alert" + aria-label={`duplicate cluster review error ${clusterId}`} + className="text-xs text-destructive" + > + {status.message} + </span> + )} + </div> + ); +} diff --git a/editor/app/actionable/lib/loadActionable.ts b/editor/app/actionable/lib/loadActionable.ts @@ -8,8 +8,14 @@ import { readChannelSnapshot, type ChannelSnapshot, } from "yt-dlp-transcript-common/controller/channelSnapshot"; -import { readDuplicateReport } from "yt-dlp-transcript-common/controller/duplicateShorts"; -import type { DuplicateReport } from "yt-dlp-transcript-common/lib/duplicates"; +import { + readDuplicateOverrides, + readDuplicateReport, +} from "yt-dlp-transcript-common/controller/duplicateShorts"; +import type { + DuplicateOverrides, + DuplicateReport, +} from "yt-dlp-transcript-common/lib/duplicates"; export type ActionableRow = { channel: ChannelStat; @@ -26,6 +32,12 @@ export type ActionableSummary = { cleanExtraFormats: ActionableRow[]; staleOrMissing: ActionableRow[]; duplicates: DuplicateReport | null; + // The human decisions kept alongside the report — a cluster's canonical + // choice, "not a duplicate", and the `confirmed` flag that is the only thing + // letting a needsReview cluster share derived work. Loaded here because the + // review UI cannot show what has already been decided without it, and a + // review queue that forgets its own answers re-asks every question. + duplicateOverrides: DuplicateOverrides; }; export function isStaleOrMissing(row: ActionableRow): boolean { @@ -100,7 +112,7 @@ export async function loadActionableSummary( paths: Paths, ): Promise<ActionableSummary> { const channels = await listChannels(paths); - const [rows, duplicates] = await Promise.all([ + const [rows, duplicates, duplicateOverrides] = await Promise.all([ Promise.all( channels.map(async (channel) => ({ channel, @@ -108,6 +120,7 @@ export async function loadActionableSummary( })), ), readDuplicateReport(paths), + readDuplicateOverrides(paths), ]); const undownloaded = rows @@ -163,5 +176,6 @@ export async function loadActionableSummary( cleanExtraFormats, staleOrMissing, duplicates, + duplicateOverrides, }; } diff --git a/editor/app/actionable/page.tsx b/editor/app/actionable/page.tsx @@ -18,8 +18,10 @@ import { InlineActionButton } from "./components/InlineActionButton"; import { FixAllIncompleteButton } from "./components/FixAllIncompleteButton"; import { RefreshAllReportsButton } from "./components/RefreshAllReportsButton"; import { RunDuplicateDetectionButton } from "./components/RunDuplicateDetectionButton"; +import { DuplicateClusterReview } from "./components/DuplicateClusterReview"; import type { DuplicateCluster, + DuplicateOverrides, DuplicateReport, } from "yt-dlp-transcript-common/lib/duplicates"; @@ -232,12 +234,21 @@ export default async function ActionablePage() { <Section key={config.id} config={config} rows={rows} /> )) )} - <DuplicatesSection report={summary.duplicates} /> + <DuplicatesSection + report={summary.duplicates} + overrides={summary.duplicateOverrides} + /> </div> ); } -function DuplicatesSection({ report }: { report: DuplicateReport | null }) { +function DuplicatesSection({ + report, + overrides, +}: { + report: DuplicateReport | null; + overrides: DuplicateOverrides; +}) { const clusters = report?.clusters ?? []; return ( <section aria-label="duplicate-shorts" className="flex flex-col gap-2"> @@ -275,7 +286,11 @@ function DuplicatesSection({ report }: { report: DuplicateReport | null }) { ) : ( <ul className="flex flex-col gap-3"> {clusters.map((cluster) => ( - <DuplicateClusterCard key={cluster.clusterId} cluster={cluster} /> + <DuplicateClusterCard + key={cluster.clusterId} + cluster={cluster} + override={overrides.clusters[cluster.clusterId]} + /> ))} </ul> )} @@ -283,7 +298,13 @@ function DuplicatesSection({ report }: { report: DuplicateReport | null }) { ); } -function DuplicateClusterCard({ cluster }: { cluster: DuplicateCluster }) { +function DuplicateClusterCard({ + cluster, + override, +}: { + cluster: DuplicateCluster; + override: DuplicateOverrides["clusters"][string] | undefined; +}) { const matchLabel: Record<DuplicateCluster["matchKind"], string> = { "transcript-exact": "exact transcript", "transcript-near": "near transcript", @@ -299,7 +320,11 @@ function DuplicateClusterCard({ cluster }: { cluster: DuplicateCluster }) { {/* Nothing compared these videos' content — one side has no transcript. The cluster is a suspect for a human, stays out of the built site, and shares no derived work until someone confirms it. */} - {cluster.needsReview && <Badge>needs review</Badge>} + {cluster.needsReview && !override?.confirmed && !override?.notDuplicate && ( + <Badge>needs review</Badge> + )} + {override?.confirmed && <Badge>confirmed</Badge>} + {override?.notDuplicate && <Badge>not a duplicate</Badge>} {cluster.score !== null && ( <Badge>score {cluster.score.toFixed(2)}</Badge> )} @@ -309,6 +334,17 @@ function DuplicateClusterCard({ cluster }: { cluster: DuplicateCluster }) { <span className="text-muted-foreground"> {cluster.videoRefs.length} videos · ~{cluster.durationBucket}s </span> + {/* The confirm path exists precisely for `needsReview` clusters: + clusterMaySharePartial fails closed, so until a human says + "confirmed" these share no digest and never reach a built site. + Content-confirmed clusters need no confirmation — but they can still + be rejected, which is the only way to un-assert a wrong one. */} + <span className="ml-auto"> + <DuplicateClusterReview + clusterId={cluster.clusterId} + override={override} + /> + </span> </div> <ul className="flex flex-col gap-1"> {cluster.videoRefs.map((ref) => (