From d351843ab8a0652e3349c4822d6e42fa2cd51f4b Mon Sep 17 00:00:00 2001 From: James Date: Tue, 12 May 2026 02:33:51 +0000 Subject: [PATCH] refactor(producer): move updateJobStatus to render/shared.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First of several focused PRs that flatten the runtime cycle between the capture stages and `renderOrchestrator.ts` (documented as a known follow-up across PRs 1.6 / 1.7 / 1.8 / 1.9). `updateJobStatus` was the most-imported orchestrator helper: 5 of the 6 capture / encode / assemble stages reach back into the orchestrator for it. Moving it to `render/shared.ts` (where the other small cross-cutting utilities already live) breaks the runtime cycle for five stages in one move: - captureStage - captureStreamingStage - captureHdrStage - encodeStage - assembleStage Each of those stages now imports `updateJobStatus` from `../shared.js` at runtime, and the only thing they pull from `renderOrchestrator.js` is type-only (`RenderJob`, `ProgressCallback`, etc.) — type imports are erased at runtime, so no cycle. The orchestrator's own internal call sites (`updateJobStatus(...)` for the inline progress updates and the `complete` / `failed` / `cancelled` transitions) are unchanged in body; they now import the function from the same shared module. Follow-up PRs will move: - `executeDiskCaptureWithAdaptiveRetry` + capture-retry helpers (breaks the captureStage cycle entirely) - The six HDR helpers + `resolveCompositeTransfer` (breaks captureHdrStage) - `collectVideoMetadataHints`, `collectVideoReadinessSkipIds`, `materializeExtractedFramesForCompiledDir` (breaks extractVideosStage) No behavior change. Verified inside `Dockerfile.test`: font-variant-numeric, many-cuts, gsap-letters-render-compat, hdr-regression — 4/4 PASS with identical audio correlations. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../producer/src/services/render/shared.ts | 24 +++++++++++++++++++ .../services/render/stages/assembleStage.ts | 7 ++---- .../services/render/stages/captureHdrStage.ts | 3 +-- .../services/render/stages/captureStage.ts | 2 +- .../render/stages/captureStreamingStage.ts | 7 ++---- .../src/services/render/stages/encodeStage.ts | 7 ++---- .../src/services/renderOrchestrator.ts | 15 +----------- 7 files changed, 33 insertions(+), 32 deletions(-) diff --git a/packages/producer/src/services/render/shared.ts b/packages/producer/src/services/render/shared.ts index f9f4ac487..1998b123b 100644 --- a/packages/producer/src/services/render/shared.ts +++ b/packages/producer/src/services/render/shared.ts @@ -17,6 +17,7 @@ import type { AudioElement, EngineConfig, ImageElement, VideoElement } from "@hy import type { CompiledComposition } from "../htmlCompiler.js"; import { defaultLogger, type ProducerLogger } from "../../logger.js"; import { isPathInside } from "../../utils/paths.js"; +import type { ProgressCallback, RenderJob, RenderStatus } from "../renderOrchestrator.js"; export interface CompositionMetadata { duration: number; @@ -202,3 +203,26 @@ export function applyRenderModeHints( reasons: compiled.renderModeHints.reasons.map((reason) => reason.message), }); } + +/** + * Mutate the `RenderJob` view of the pipeline's progress and fire the + * caller's `onProgress` callback. Hoisted here (out of `renderOrchestrator.ts`) + * so the stage modules can call it without forming a runtime cycle. + * + * `completedAt` is stamped on the terminal `"failed"` / `"complete"` + * transitions so callers that poll the job state can tell when the + * pipeline finished. + */ +export function updateJobStatus( + job: RenderJob, + status: RenderStatus, + stage: string, + progress: number, + onProgress?: ProgressCallback, +): void { + job.status = status; + job.currentStage = stage; + job.progress = progress; + if (status === "failed" || status === "complete") job.completedAt = new Date(); + if (onProgress) onProgress(job, stage); +} diff --git a/packages/producer/src/services/render/stages/assembleStage.ts b/packages/producer/src/services/render/stages/assembleStage.ts index 0697c5322..004dad3e6 100644 --- a/packages/producer/src/services/render/stages/assembleStage.ts +++ b/packages/producer/src/services/render/stages/assembleStage.ts @@ -17,11 +17,8 @@ */ import { applyFaststart, muxVideoWithAudio } from "@hyperframes/engine"; -import { - updateJobStatus, - type ProgressCallback, - type RenderJob, -} from "../../renderOrchestrator.js"; +import type { ProgressCallback, RenderJob } from "../../renderOrchestrator.js"; +import { updateJobStatus } from "../shared.js"; export interface AssembleStageInput { job: RenderJob; diff --git a/packages/producer/src/services/render/stages/captureHdrStage.ts b/packages/producer/src/services/render/stages/captureHdrStage.ts index ee48c7452..0d7420c87 100644 --- a/packages/producer/src/services/render/stages/captureHdrStage.ts +++ b/packages/producer/src/services/render/stages/captureHdrStage.ts @@ -86,9 +86,8 @@ import { compositeHdrFrame, createHdrPerfCollector, resolveCompositeTransfer, - updateJobStatus, } from "../../renderOrchestrator.js"; -import { type CompositionMetadata } from "../shared.js"; +import { updateJobStatus, type CompositionMetadata } from "../shared.js"; export interface CaptureHdrStageInput { job: RenderJob; diff --git a/packages/producer/src/services/render/stages/captureStage.ts b/packages/producer/src/services/render/stages/captureStage.ts index b0ce1c069..2bc468bf0 100644 --- a/packages/producer/src/services/render/stages/captureStage.ts +++ b/packages/producer/src/services/render/stages/captureStage.ts @@ -51,11 +51,11 @@ import type { FileServerHandle } from "../../fileServer.js"; import type { ProducerLogger } from "../../../logger.js"; import { executeDiskCaptureWithAdaptiveRetry, - updateJobStatus, type CaptureAttemptSummary, type ProgressCallback, type RenderJob, } from "../../renderOrchestrator.js"; +import { updateJobStatus } from "../shared.js"; export interface CaptureStageInput { fileServer: FileServerHandle; diff --git a/packages/producer/src/services/render/stages/captureStreamingStage.ts b/packages/producer/src/services/render/stages/captureStreamingStage.ts index 75bc3a8e0..d1a87eccc 100644 --- a/packages/producer/src/services/render/stages/captureStreamingStage.ts +++ b/packages/producer/src/services/render/stages/captureStreamingStage.ts @@ -58,11 +58,8 @@ import { } from "@hyperframes/engine"; import type { FileServerHandle } from "../../fileServer.js"; import type { ProducerLogger } from "../../../logger.js"; -import { - updateJobStatus, - type ProgressCallback, - type RenderJob, -} from "../../renderOrchestrator.js"; +import type { ProgressCallback, RenderJob } from "../../renderOrchestrator.js"; +import { updateJobStatus } from "../shared.js"; /** * Pre-built ffmpeg streaming-encoder options, exactly matching the diff --git a/packages/producer/src/services/render/stages/encodeStage.ts b/packages/producer/src/services/render/stages/encodeStage.ts index eca96c0cc..aa5b39df7 100644 --- a/packages/producer/src/services/render/stages/encodeStage.ts +++ b/packages/producer/src/services/render/stages/encodeStage.ts @@ -34,11 +34,8 @@ import { getEncoderPreset, } from "@hyperframes/engine"; import type { ProducerLogger } from "../../../logger.js"; -import { - updateJobStatus, - type ProgressCallback, - type RenderJob, -} from "../../renderOrchestrator.js"; +import type { ProgressCallback, RenderJob } from "../../renderOrchestrator.js"; +import { updateJobStatus } from "../shared.js"; export interface EncodeStageInput { job: RenderJob; diff --git a/packages/producer/src/services/renderOrchestrator.ts b/packages/producer/src/services/renderOrchestrator.ts index 22c330e6e..60bfd2467 100644 --- a/packages/producer/src/services/renderOrchestrator.ts +++ b/packages/producer/src/services/renderOrchestrator.ts @@ -92,6 +92,7 @@ import { type CompiledComposition } from "./htmlCompiler.js"; import { defaultLogger, type ProducerLogger } from "../logger.js"; import { isPathInside } from "../utils/paths.js"; import { type HdrImageTransferCache } from "./hdrImageTransferCache.js"; +import { updateJobStatus } from "./render/shared.js"; import { runCompileStage } from "./render/stages/compileStage.js"; import { runProbeStage } from "./render/stages/probeStage.js"; import { runExtractVideosStage } from "./render/stages/extractVideosStage.js"; @@ -544,20 +545,6 @@ export class RenderCancelledError extends Error { } } -export function updateJobStatus( - job: RenderJob, - status: RenderStatus, - stage: string, - progress: number, - onProgress?: ProgressCallback, -): void { - job.status = status; - job.currentStage = stage; - job.progress = progress; - if (status === "failed" || status === "complete") job.completedAt = new Date(); - if (onProgress) onProgress(job, stage); -} - function installDebugLogger(logPath: string, log: ProducerLogger = defaultLogger): () => void { const origLog = console.log; const origError = console.error;