refactor(producer): move updateJobStatus to render/shared.ts

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) <noreply@anthropic.com>
This commit is contained in:
James
2026-05-12 02:33:51 +00:00
co-authored by Claude Opus 4.7
parent 513775e659
commit d351843ab8
7 changed files with 33 additions and 32 deletions
@@ -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);
}
@@ -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;
@@ -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;
@@ -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;
@@ -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
@@ -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;
@@ -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;