commit 1d10549159ed4661eb6c94eef4608d5e1e873a39
parent 43346eceffd001ad032710de04478ef7da57f629
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 2 Oct 2026 00:09:45 -0400
editor: the video page's subtitle-deferral line reads a GET route, not a server action
Next runs a page's server actions one at a time, so an action fired on mount
sat in front of the operator's first click; video-page.spec's "Mark
untranscribable" timed out behind it once. The line now fetches
GET /api/channels/<slug>/videos/<id>/subtitle-deferral.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 34 insertions(+), 18 deletions(-)
diff --git a/editor/app/api/channels/[slug]/videos/[id]/subtitle-deferral/route.ts b/editor/app/api/channels/[slug]/videos/[id]/subtitle-deferral/route.ts
@@ -0,0 +1,26 @@
+import { NextResponse } from "next/server";
+import { getPaths } from "yt-dlp-transcript-common/lib/paths";
+import { readSubtitleDeferrals } from "yt-dlp-transcript-common/jobs/downloadBackoff";
+
+export const dynamic = "force-dynamic";
+
+// GET → { count, lastAt, until } | null — the video's SUBTITLE deferral
+// (release 17, slice RL): how many times its subtitle fetch answered 429, when
+// last, and until when Download missing subs leaves it alone. Read by the video
+// page's one-line note.
+//
+// A ROUTE, NOT A SERVER ACTION: the note asks on mount, and Next runs a page's
+// server actions one at a time — a mount-time action would sit in front of the
+// operator's first click (Mark untranscribable, Download, …) while it ran.
+export async function GET(
+ _req: Request,
+ ctx: { params: Promise<{ slug: string; id: string }> },
+): Promise<NextResponse> {
+ const { slug, id } = await ctx.params;
+ const all = await readSubtitleDeferrals(getPaths()).catch(() => ({}));
+ const d = (all as Record<string, { count: number; lastAt: number; until: number; channelSlug: string }>)[id];
+ return NextResponse.json(
+ d && d.channelSlug === slug ? { count: d.count, lastAt: d.lastAt, until: d.until } : null,
+ { headers: { "cache-control": "no-store" } },
+ );
+}
diff --git a/editor/app/channels/[slug]/videos/[id]/components/SubtitleDeferralLine.tsx b/editor/app/channels/[slug]/videos/[id]/components/SubtitleDeferralLine.tsx
@@ -1,7 +1,6 @@
"use client";
import { useEffect, useState } from "react";
-import { subtitleDeferralAction } from "../videoActions";
// THE SUBTITLE DEFERRAL, IN ONE LINE (release 17, slice RL). A video whose
// YouTube subtitles answered HTTP 429 while its media came down: how many
@@ -19,8 +18,14 @@ export function SubtitleDeferralLine({
const [d, setD] = useState<{ count: number; lastAt: number; until: number } | null>(null);
useEffect(() => {
let live = true;
- subtitleDeferralAction(slug, videoId)
- .then((r) => {
+ // A plain GET, not a server action: Next queues a page's server actions,
+ // and this one would run ahead of the operator's first click.
+ fetch(
+ `/api/channels/${encodeURIComponent(slug)}/videos/${encodeURIComponent(videoId)}/subtitle-deferral`,
+ { cache: "no-store" },
+ )
+ .then((r) => (r.ok ? r.json() : null))
+ .then((r: { count: number; lastAt: number; until: number } | null) => {
if (live) setD(r);
})
.catch(() => {});
diff --git a/editor/app/channels/[slug]/videos/[id]/videoActions.ts b/editor/app/channels/[slug]/videos/[id]/videoActions.ts
@@ -67,7 +67,6 @@ import { applyTagAssignmentsAction } from "../../../../tags/actions";
import {
heldPlatformRefusal,
platformCooldownRemainingMs,
- readSubtitleDeferrals,
recordDownloadBackoff,
} from "yt-dlp-transcript-common/jobs/downloadBackoff";
import { getSettings } from "yt-dlp-transcript-common/lib/settings";
@@ -1148,17 +1147,3 @@ export async function fetchFullSourceAction(req: {
to: 0,
};
}
-
-// THE VIDEO'S SUBTITLE DEFERRAL (release 17, slice RL), for the line on the
-// video page: how many times its subtitle fetch answered 429, when last, and
-// until when the batch subtitle fetch leaves it alone. Null when it has none.
-// Read-only; the video page's own download never consults it.
-export async function subtitleDeferralAction(
- slug: string,
- videoId: string,
-): Promise<{ count: number; lastAt: number; until: number } | null> {
- const deferrals = await readSubtitleDeferrals(getPaths()).catch(() => ({}));
- const d = (deferrals as Record<string, { count: number; lastAt: number; until: number; channelSlug: string }>)[videoId];
- if (!d || d.channelSlug !== slug) return null;
- return { count: d.count, lastAt: d.lastAt, until: d.until };
-}