Archilyzer · Source

archilyzer

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

commit 72ac50ae460720f6d3851696ef81c6f92453461a
parent 664a68d30365dfc22e8a37ba2bb5ec49f5b82f4f
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  1 Oct 2026 20:59:33 -0400

editor: the per-channel Refresh report runs on the refresh-report queue (D0 review L3)

refreshChannelSnapshotAction (Refresh report, ops {slug}, e2e generateReport)
starts a refresh-report job through startRefreshReport and waits for it (or for
the one already queued for the channel), keeping the unmounted-drive sentence as
its error. No walk runs outside the queue.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/app/api/ops/refresh-report/route.ts | 5+++--
Meditor/app/channels/actions.ts | 54++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/editor/app/api/ops/refresh-report/route.ts b/editor/app/api/ops/refresh-report/route.ts @@ -16,8 +16,9 @@ export const dynamic = "force-dynamic"; // POST { slug: string } | { all: true } // -// The single-channel form REGENERATES SYNCHRONOUSLY (it is a filesystem scan, -// not a job) and returns `{ ok: true }` once snapshot.json is on disk. The +// The single-channel form queues a refresh-report job (the one serial queue +// every walk runs on, release 17) and returns `{ ok: true }` once snapshot.json +// is on disk — it waits for the job, behind any other channel's. The // `all` form queues one refresh-report job per channel and returns the bulk // { queued, skipped } the /channels header button shows. export async function POST(request: Request) { diff --git a/editor/app/channels/actions.ts b/editor/app/channels/actions.ts @@ -23,12 +23,13 @@ import { import { renameChannel } from "yt-dlp-transcript-common/controller/renameChannel"; import { inspectChannelMedia } from "yt-dlp-transcript-common/lib/channelMedia"; import { channelMediaBusyReason } from "./lib/mediaBusy"; -import { generateChannelSnapshot } from "yt-dlp-transcript-common/controller/channelSnapshot"; import { REFRESH_REPORT_ACTIVE, + queuedRefreshReportId, requestChannelSnapshot, startRefreshReport, } from "yt-dlp-transcript-common/jobs/snapshotScheduler"; +import { getRegistry } from "yt-dlp-transcript-common/jobs/registry"; import { drainStream } from "yt-dlp-transcript-common/jobs/drainStream"; import { siteChannelIndex, @@ -348,6 +349,16 @@ export async function gotoVideoAction( redirect(`/channels/${slug}/videos/${encodeURIComponent(id)}`); } +// Poll a registry job until it is terminal (or gone from the registry). The +// registry hands out no completion promise for a job someone else started. +async function waitForJobToEnd(id: string): Promise<void> { + for (;;) { + const job = getRegistry().get(id); + if (!job || (job.status !== "queued" && job.status !== "running")) return; + await new Promise((resolve) => setTimeout(resolve, 250)); + } +} + export async function refreshChannelSnapshotAction( slug: string, ): Promise<ActionResult> { @@ -355,17 +366,36 @@ export async function refreshChannelSnapshotAction( if (!(await channelExists(paths, slug))) { return { error: `Channel "${slug}" not found` }; } - // generateChannelSnapshot now THROWS on a channel whose media is not - // reachable (guard 3) rather than writing a snapshot that says every video is - // undownloaded. That is the right behaviour and the wrong exception to let - // out of a server action: an uncaught throw here reaches the client as a - // digest-only "an error occurred", and the one thing the operator needs is - // the sentence naming the unmounted drive. Every sibling in this file returns - // { error }; so does this. - try { - await generateChannelSnapshot(paths, slug); - } catch (e) { - return { error: (e as Error).message }; + // THROUGH THE REFRESH-REPORT QUEUE, like every other walk (release 17 slice + // D0): a walk run here, in the request, beside a queued one was two walks + // side by side — half of the 2026-10-01 outage — and could land an older + // read over a newer one. The action still answers when the report is on + // disk: it waits for the job it started, or for the one already queued for + // this channel (which has not started reading, so it is as fresh). + // + // generateChannelSnapshot THROWS on a channel whose media is not reachable + // (guard 3) rather than writing a snapshot that says every video is + // undownloaded. The job fails with that sentence in its log; it comes back + // here through `onError`, because an uncaught throw from a server action + // reaches the client as a digest-only "an error occurred", and the one thing + // the operator needs is the sentence naming the unmounted drive. Every + // sibling in this file returns { error }; so does this. + let failure: string | null = null; + const started = await startRefreshReport(paths, slug, { + onError: (message) => { + failure = message; + }, + }); + if (started.ok) { + const { status } = await started.done; + if (status !== "done") { + return { error: failure ?? `Refresh report ${status} (job ${started.jobId})` }; + } + } else if (!started.info) { + return { error: started.error }; + } else { + const queued = queuedRefreshReportId(slug); + if (queued) await waitForJobToEnd(queued); } revalidatePath(`/channels/${slug}`); // The Report column on /channels is read off this snapshot, and the row