commit 3da64705ccff7146af9abc5904711b32c66e22de
parent d417d8f27be891f23be89b43eb431f2b766398d8
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sat, 29 Aug 2026 18:31:45 -0400
operations: the sync page reads the scheduler payload once
The cadence-console comment claimed buildSchedulerStatusPayload ran only in
that arm, and it was false: buildSyncRow in the Promise.all above calls it too,
so /operations/sync listed every channel config twice for one screen.
buildSyncRow takes an optional payload now. The sync page starts ONE and does
not await it: the rail row folds it, the console awaits the same already-settled
promise, and the three reads beside it still run in parallel. Every other page
passes nothing and builds the payload inside buildSyncRow exactly as before —
that read is the whole cost there. The comments say what is true.
Also the one preserve line on /settings that had none: syncScheduler is edited
on /operations/sync, in the shape of the workers line above it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat:
3 files changed, 40 insertions(+), 13 deletions(-)
diff --git a/editor/app/operations/[id]/page.tsx b/editor/app/operations/[id]/page.tsx
@@ -155,10 +155,22 @@ export default async function OperationPage({
// OperationDetail renders that as "no console here" rather than borrowing one.
const laneId = sweepLaneIdFor(op);
+ // THE SCHEDULER PAYLOAD, READ ONCE PER RENDER. The rail's sync row is a fold
+ // of it and the cadence console below is the whole of it, so the sync page
+ // would otherwise list every channel config twice for one screen. Started
+ // here and NOT awaited: the row and the console await the same promise, and
+ // the three reads below still run in parallel.
+ const schedulerPayload =
+ op.trigger === "cadence" ? buildSchedulerStatusPayload() : null;
+
const [initial, channels, sync] = await Promise.all([
buildAutoQueueStatusPayload(),
listChannelConfigs(getPaths()),
- buildSyncRow(),
+ // Every other page wants only the row, and builds the payload inside
+ // buildSyncRow — which is the whole cost there, and unchanged.
+ schedulerPayload
+ ? schedulerPayload.then((payload) => buildSyncRow(payload))
+ : buildSyncRow(),
]);
const channelOptions = channels.map((c) => ({
slug: c.slug,
@@ -193,11 +205,12 @@ export default async function OperationPage({
// its schedule. `runner` cannot say this (it is typed AutoQueueKind, and the
// heartbeat is not one), so `trigger` does. ONE such operation exists; a
// second would need the descriptor to name its console, not this file to
- // branch on an id. buildSchedulerStatusPayload runs ONLY in this arm.
- const cadenceConsole =
- op.trigger === "cadence" ? (
- <SyncConsole key="sync" initial={await buildSchedulerStatusPayload()} />
- ) : null;
+ // branch on an id. The same promise the rail row folded, awaited a second
+ // time and therefore already settled — one scheduler read on this page, and
+ // none at all on any other.
+ const cadenceConsole = schedulerPayload ? (
+ <SyncConsole key="sync" initial={await schedulerPayload} />
+ ) : null;
const laneJobKinds =
runnerKind || !laneId ? [] : JOB_KINDS_BY_LANE[laneId];
diff --git a/editor/app/operations/syncRow.ts b/editor/app/operations/syncRow.ts
@@ -3,12 +3,20 @@ import {
SYNC_OPERATION,
operationLabel,
} from "yt-dlp-transcript-common/lib/operations";
-import { buildSchedulerStatusPayload } from "../scheduler/status";
+import {
+ buildSchedulerStatusPayload,
+ type SchedulerStatusPayload,
+} from "../scheduler/status";
// THE SYNC ROW ON THE OPERATIONS RAIL, folded down to what a one-line row can
// say. Sync is channel-scoped, so these are channel figures and there is no
// band to draw — see SyncRailRow. The full per-channel schedule is the sync
// operation's own page, /operations/sync.
+//
+// The row and that page's console are two readings of ONE scheduler payload,
+// which is why buildSyncRow takes an optional one: /operations/sync builds the
+// payload itself (the console needs the whole thing) and hands it here, so the
+// page reads it once rather than listing every channel config twice.
export type SyncRowView = {
// Off the descriptor, so the row's link and its name cannot drift from the
// catalog the rest of the rail is built from. `label` travels as a prop
@@ -32,16 +40,20 @@ export type SyncRowView = {
heartbeatSeconds: number;
};
-export async function buildSyncRow(): Promise<SyncRowView> {
- const payload = await buildSchedulerStatusPayload();
- const eligible = payload.channels.filter((c) => c.autoSyncEligible);
+export async function buildSyncRow(
+ // Absent on every page that wants only the row — the board and the non-sync
+ // operation pages — where building the payload here is the whole cost.
+ payload?: SchedulerStatusPayload,
+): Promise<SyncRowView> {
+ const status = payload ?? (await buildSchedulerStatusPayload());
+ const eligible = status.channels.filter((c) => c.autoSyncEligible);
return {
id: SYNC_OPERATION.id,
label: operationLabel(SYNC_OPERATION.id),
- enabled: payload.scheduler.enabled,
+ enabled: status.scheduler.enabled,
eligible: eligible.length,
due: eligible.filter(
- (c) => c.nextDueAt !== null && c.nextDueAt <= payload.now,
+ (c) => c.nextDueAt !== null && c.nextDueAt <= status.now,
).length,
overdue: eligible.filter((c) => c.overdue).length,
running: getRegistry()
@@ -50,6 +62,6 @@ export async function buildSyncRow(): Promise<SyncRowView> {
(j) =>
j.kind === "sync" && (j.status === "running" || j.status === "queued"),
).length,
- heartbeatSeconds: payload.heartbeatSeconds,
+ heartbeatSeconds: status.heartbeatSeconds,
};
}
diff --git a/editor/app/settings/actions.ts b/editor/app/settings/actions.ts
@@ -226,6 +226,8 @@ export async function saveSettingsAction(
archiveStorage,
reportDebouncePreset,
autoRefreshIntervalSeconds: autoRefreshParsed,
+ // syncScheduler: edited on /operations/sync, below the schedule, by
+ // saveSchedulerSettingsAction.
syncScheduler: getSettings().syncScheduler,
// Preserve the existing auto-queue policy on an unrelated settings save
// (this form doesn't edit it; the Auto-queue page does). writeSettings