@hyperframes/producer 0.9.19 → 0.9.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/server.ts
4
4
  import {
5
- existsSync as existsSync23,
5
+ existsSync as existsSync24,
6
6
  mkdirSync as mkdirSync13,
7
7
  statSync as statSync11,
8
8
  mkdtempSync as mkdtempSync4,
@@ -26,7 +26,7 @@ import {
26
26
  // src/services/renderOrchestrator.ts
27
27
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
28
28
  import {
29
- existsSync as existsSync20,
29
+ existsSync as existsSync21,
30
30
  mkdirSync as mkdirSync12,
31
31
  readdirSync as readdirSync6,
32
32
  rmSync as rmSync12,
@@ -932,7 +932,7 @@ function resolveDeliveryEncoderOptions(config) {
932
932
  import {
933
933
  copyFileSync,
934
934
  cpSync,
935
- existsSync as existsSync3,
935
+ existsSync as existsSync4,
936
936
  mkdirSync,
937
937
  rmSync,
938
938
  symlinkSync,
@@ -952,7 +952,9 @@ import {
952
952
  relative as nodeRelative,
953
953
  isAbsolute as nodeIsAbsolute
954
954
  } from "node:path";
955
+ import { existsSync as existsSync3 } from "node:fs";
955
956
  import { fileURLToPath as fileURLToPath2 } from "node:url";
957
+ import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
956
958
  var DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? // fileURLToPath (not URL.pathname): on Windows .pathname is "/D:/..." which
957
959
  // resolves to a bogus renders dir.
958
960
  nodeResolve(fileURLToPath2(import.meta.url), "../../..", "renders");
@@ -990,6 +992,13 @@ function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS
990
992
  const absoluteOutputPath = nodeResolve(resolvedOutputPath);
991
993
  return { absoluteProjectDir, absoluteOutputPath };
992
994
  }
995
+ function resolveCompositionSrc(projectDir, src) {
996
+ const rooted = (value) => nodeResolve(projectDir, value.replace(/^(?:\.?[\\/])+/, ""));
997
+ const decoded = rooted(decodedUrlPath(src));
998
+ if (!isPathInside2(decoded, projectDir)) return null;
999
+ const raw = rooted(src);
1000
+ return !existsSync3(decoded) && isPathInside2(raw, projectDir) && existsSync3(raw) ? raw : decoded;
1001
+ }
993
1002
 
994
1003
  // src/services/render/shared.ts
995
1004
  var BROWSER_MEDIA_EPSILON = 1e-4;
@@ -1193,7 +1202,7 @@ var materializePathModule = {
1193
1202
  isAbsolute
1194
1203
  };
1195
1204
  var materializeFileSystem = {
1196
- existsSync: existsSync3,
1205
+ existsSync: existsSync4,
1197
1206
  mkdirSync,
1198
1207
  symlinkSync,
1199
1208
  cpSync,
@@ -1495,7 +1504,7 @@ var RenderExecutionContext = class {
1495
1504
  };
1496
1505
 
1497
1506
  // src/services/render/artifactTransaction.ts
1498
- import { closeSync as closeSync2, existsSync as existsSync5, fstatSync as fstatSync2, openSync as openSync2, readSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
1507
+ import { closeSync as closeSync2, existsSync as existsSync6, fstatSync as fstatSync2, openSync as openSync2, readSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
1499
1508
  import { basename as basename3, dirname as dirname4, extname as extname2, join as join5, resolve as resolve4 } from "node:path";
1500
1509
  import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, renameWithRetry } from "@hyperframes/engine";
1501
1510
 
@@ -1508,7 +1517,7 @@ import {
1508
1517
 
1509
1518
  // src/services/render/renderDirOwner.ts
1510
1519
  import {
1511
- existsSync as existsSync4,
1520
+ existsSync as existsSync5,
1512
1521
  lstatSync,
1513
1522
  mkdtempSync,
1514
1523
  readFileSync as readFileSync2,
@@ -1583,7 +1592,7 @@ function newestWriteMs(path) {
1583
1592
  }
1584
1593
  function isAbandoned(dir, ownerlessIdleMs, now) {
1585
1594
  try {
1586
- if (!lstatSync(dir).isDirectory() || existsSync4(join4(dir, TRANSACTION_BACKUP))) return false;
1595
+ if (!lstatSync(dir).isDirectory() || existsSync5(join4(dir, TRANSACTION_BACKUP))) return false;
1587
1596
  const owner = ownerState(dir);
1588
1597
  if (owner === "gone") return true;
1589
1598
  return owner === "none" && ownerlessIdleMs !== void 0 && now - newestWriteMs(dir) >= ownerlessIdleMs;
@@ -1615,7 +1624,7 @@ function createRenderWorkDir(prefix, outputDir) {
1615
1624
 
1616
1625
  // src/services/render/artifactTransaction.ts
1617
1626
  var defaultFileSystem = {
1618
- existsSync: existsSync5,
1627
+ existsSync: existsSync6,
1619
1628
  renameSync: (source, destination) => renameWithRetry(source, destination),
1620
1629
  rmSync: rmSync3
1621
1630
  };
@@ -1769,7 +1778,7 @@ var ArtifactTransaction = class {
1769
1778
  async assertDirectoryExpectation(expected, files) {
1770
1779
  for (const required of expected.requiredFiles ?? []) {
1771
1780
  const path = join5(this.stagingPath, required);
1772
- if (!existsSync5(path)) {
1781
+ if (!existsSync6(path)) {
1773
1782
  throw new Error(`Render artifact directory is missing ${required}: ${this.stagingPath}`);
1774
1783
  }
1775
1784
  assertReadableNonEmptyFile(path);
@@ -2018,7 +2027,7 @@ function replanAfterFailure(plan, failure) {
2018
2027
  }
2019
2028
 
2020
2029
  // src/services/render/stages/captureSegmentedStage.ts
2021
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync6, statSync as statSync3 } from "node:fs";
2030
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync as rmSync6, statSync as statSync3 } from "node:fs";
2022
2031
  import { join as join10 } from "node:path";
2023
2032
  import {
2024
2033
  captureFrameToBuffer as captureFrameToBuffer2,
@@ -2315,6 +2324,19 @@ function aggregateBeginFrameReuse(perfs) {
2315
2324
  if (noDamageFrames + hasDamageFrames === 0) return void 0;
2316
2325
  return { noDamageFrames, hasDamageFrames };
2317
2326
  }
2327
+ function aggregateMotionBlur(perfs) {
2328
+ const ran = perfs.flatMap((p) => p.motionBlur ?? []).filter((m) => m.tier || m.failedFrames > 0);
2329
+ const first = ran[0];
2330
+ if (!first) return void 0;
2331
+ return {
2332
+ tier: first.tier ?? ran.find((m) => m.tier)?.tier ?? null,
2333
+ reasonCode: first.reasonCode,
2334
+ movers: first.movers,
2335
+ failedFrames: ran.reduce((sum, m) => sum + m.failedFrames, 0),
2336
+ readEvents: ran.some((m) => m.readEvents),
2337
+ fingerprint: ran.find((m) => m.fingerprint)?.fingerprint ?? null
2338
+ };
2339
+ }
2318
2340
  function buildRenderPerfSummary(input) {
2319
2341
  return {
2320
2342
  renderId: input.job.id,
@@ -2365,6 +2387,7 @@ function buildRenderPerfSummary(input) {
2365
2387
  staticDedup: aggregateDedup(input.dedupPerfs),
2366
2388
  chromeMemory: aggregateChromeMemory(input.dedupPerfs),
2367
2389
  beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
2390
+ motionBlur: aggregateMotionBlur(input.dedupPerfs),
2368
2391
  drawElement: aggregateDrawElement(
2369
2392
  input.dedupPerfs,
2370
2393
  input.drawElement ?? { selfVerifyFallback: false }
@@ -3839,6 +3862,21 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
3839
3862
  );
3840
3863
  });
3841
3864
  }
3865
+ function createSessionInitBudget(input) {
3866
+ const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
3867
+ const setup = createStallClock();
3868
+ return (work, stalled) => raceAgainstStall(
3869
+ work,
3870
+ initStallMs - setup.idle(0),
3871
+ {
3872
+ session: stalled,
3873
+ frameIndex: input.frameIndex,
3874
+ totalFrames: input.totalFrames,
3875
+ stallTimeoutMs: initStallMs
3876
+ },
3877
+ input.abortSignal
3878
+ );
3879
+ }
3842
3880
  function createDrainFrameGuard(args) {
3843
3881
  const { log, stats, frameTime } = args;
3844
3882
  const verifyMinDb = resolveDeVerifyMinDb();
@@ -4311,14 +4349,11 @@ async function runCaptureStreamingStage(input) {
4311
4349
  let resumes = 0;
4312
4350
  for (; ; ) {
4313
4351
  const videoInjector = createRenderVideoFrameInjector();
4314
- const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
4315
- const setup = createStallClock();
4316
- const withinInitBudget = (work, stalled) => raceAgainstStall(
4317
- work,
4318
- initStallMs - setup.idle(0),
4319
- { session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
4352
+ const withinInitBudget = createSessionInitBudget({
4353
+ frameIndex: resume.from,
4354
+ totalFrames,
4320
4355
  abortSignal
4321
- );
4356
+ });
4322
4357
  const openSession = async () => {
4323
4358
  const opening = createCaptureSession(
4324
4359
  fileServer.url,
@@ -4596,7 +4631,7 @@ async function captureSegmentToEncoder(ctx, segment) {
4596
4631
  path: segmentPath,
4597
4632
  // Recorded so resume can prove the file on disk is the one that
4598
4633
  // finished; absent only in unit tests, whose fake encoder writes none.
4599
- bytes: existsSync6(segmentPath) ? statSync3(segmentPath).size : 0
4634
+ bytes: existsSync7(segmentPath) ? statSync3(segmentPath).size : 0
4600
4635
  });
4601
4636
  return encodeResult;
4602
4637
  } finally {
@@ -4626,14 +4661,29 @@ function defaultSessionFactory(input) {
4626
4661
  });
4627
4662
  probeSession = null;
4628
4663
  }
4629
- const session = probeSession ?? await openSegmentedSession(input, injector);
4664
+ const withinInitBudget = createSessionInitBudget({
4665
+ frameIndex: input.job.framesRendered ?? 0,
4666
+ totalFrames: input.totalFrames,
4667
+ abortSignal: input.abortSignal
4668
+ });
4669
+ const openSession = async () => {
4670
+ const opening = openSegmentedSession(input, injector);
4671
+ try {
4672
+ return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
4673
+ } catch (error) {
4674
+ void opening.then(closeCaptureSession2, () => {
4675
+ });
4676
+ throw error;
4677
+ }
4678
+ };
4679
+ const session = probeSession ?? await openSession();
4630
4680
  if (probeSession) {
4631
4681
  openSegmentedSessionReuse(input, probeSession, injector);
4632
4682
  probeSession = null;
4633
4683
  }
4634
4684
  try {
4635
- if (!session.isInitialized) await initializeSession2(session);
4636
- await completeDeferredDrawElementInit2(session);
4685
+ if (!session.isInitialized) await withinInitBudget(initializeSession2(session), session);
4686
+ await withinInitBudget(completeDeferredDrawElementInit2(session), session);
4637
4687
  } catch (error) {
4638
4688
  await closeCaptureSession2(session).catch(() => {
4639
4689
  });
@@ -5565,7 +5615,7 @@ function trackSegmentFramePeak(frames, rootDir, onPeak) {
5565
5615
 
5566
5616
  // src/services/render/segmentManifest.ts
5567
5617
  import { createHash as createHash2 } from "node:crypto";
5568
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
5618
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
5569
5619
  import { join as join15 } from "node:path";
5570
5620
  import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
5571
5621
  var MANIFEST_FILENAME = "segments.json";
@@ -5613,7 +5663,7 @@ function isManifest(value) {
5613
5663
  }
5614
5664
  function readSegmentManifest(segmentDir) {
5615
5665
  const path = join15(segmentDir, MANIFEST_FILENAME);
5616
- if (!existsSync7(path)) return null;
5666
+ if (!existsSync8(path)) return null;
5617
5667
  try {
5618
5668
  const parsed = JSON.parse(readFileSync4(path, "utf-8"));
5619
5669
  return isManifest(parsed) ? parsed : null;
@@ -5642,7 +5692,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
5642
5692
  const ok = /* @__PURE__ */ new Set();
5643
5693
  if (manifest.planHash !== expectedHash) return ok;
5644
5694
  for (const entry of manifest.completed) {
5645
- if (!existsSync7(entry.path)) continue;
5695
+ if (!existsSync8(entry.path)) continue;
5646
5696
  const size = statSync5(entry.path).size;
5647
5697
  if (size <= 0 || size !== entry.bytes) continue;
5648
5698
  const frames = await probeFrames(entry.path);
@@ -5782,7 +5832,7 @@ var ALLOWED_STRING_DATA_KEYS = /* @__PURE__ */ new Set([
5782
5832
  "stagePhase",
5783
5833
  // Full-fidelity fast-capture fallback trigger — the specific reason
5784
5834
  // drawElement gated off ("filter:blur", "filter:drop-shadow",
5785
- // "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", …).
5835
+ // "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", "webgl_unavailable", …).
5786
5836
  // Populated on the `capture_fallback_profile` checkpoint emitted by
5787
5837
  // `fallbackCaptureProfile.ts` when the operator opts into
5788
5838
  // `HF_PROFILE_FALLBACK_CAPTURE=true` so downstream metric consumers can
@@ -6221,17 +6271,17 @@ function countAuthoredTimedClips(html) {
6221
6271
  }
6222
6272
 
6223
6273
  // src/services/render/stages/compileStage.ts
6224
- import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "node:fs";
6274
+ import { existsSync as existsSync12, writeFileSync as writeFileSync5 } from "node:fs";
6225
6275
  import { join as join19 } from "node:path";
6226
6276
  import { suggestMatchingPreset } from "@hyperframes/core";
6227
6277
  import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
6228
6278
  import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
6229
6279
 
6230
6280
  // src/services/htmlCompiler.ts
6231
- import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
6281
+ import { decodedUrlPath as decodedUrlPath2 } from "@hyperframes/parsers/asset-paths";
6232
6282
  import { createHash as createHash6 } from "node:crypto";
6233
- import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync7 } from "fs";
6234
- import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
6283
+ import { createReadStream as createReadStream2, existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync7 } from "fs";
6284
+ import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2, sep as sep2 } from "path";
6235
6285
  import { parseHTML as parseHTML5 } from "linkedom";
6236
6286
  import {
6237
6287
  COMPOSITION_SOURCE_URL,
@@ -6477,7 +6527,7 @@ import {
6477
6527
 
6478
6528
  // src/services/animatedGifPrep.ts
6479
6529
  import { createHash as createHash4 } from "node:crypto";
6480
- import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync5, rmSync as rmSync9, statSync as statSync6 } from "node:fs";
6530
+ import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync5, rmSync as rmSync9, statSync as statSync6 } from "node:fs";
6481
6531
  import { dirname as dirname6, isAbsolute as isAbsolute2, join as join16, resolve as resolve5 } from "node:path";
6482
6532
  import { parseHTML as parseHTML3 } from "linkedom";
6483
6533
  import { parseAnimatedGifMetadata } from "@hyperframes/core";
@@ -6516,14 +6566,14 @@ function resolveLocalImageSourcePath(src, options) {
6516
6566
  const { basePath } = splitUrlSuffix(trimmed);
6517
6567
  const normalizedBase = normalizeRelPath(basePath);
6518
6568
  const mapped = options.sourceAssets?.get(trimmed) ?? options.sourceAssets?.get(basePath) ?? options.sourceAssets?.get(normalizedBase);
6519
- if (mapped && existsSync8(mapped)) return mapped;
6569
+ if (mapped && existsSync9(mapped)) return mapped;
6520
6570
  if (isHttpUrl(trimmed)) return null;
6521
6571
  const projectRelative = basePath.startsWith("/") ? basePath.slice(1) : basePath;
6522
6572
  const candidates = [
6523
6573
  isAbsolute2(basePath) ? basePath : resolve5(options.projectDir, projectRelative),
6524
6574
  resolve5(options.downloadDir, normalizedBase)
6525
6575
  ];
6526
- return candidates.find((candidate) => existsSync8(candidate)) ?? null;
6576
+ return candidates.find((candidate) => existsSync9(candidate)) ?? null;
6527
6577
  }
6528
6578
  function isUsableFile(path) {
6529
6579
  try {
@@ -6634,10 +6684,10 @@ async function runAnimatedGifTranscode(request) {
6634
6684
  throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
6635
6685
  }
6636
6686
  async function ensurePreparedWebm(input) {
6637
- if (!existsSync8(dirname6(input.cachePath))) {
6687
+ if (!existsSync9(dirname6(input.cachePath))) {
6638
6688
  mkdirSync7(dirname6(input.cachePath), { recursive: true });
6639
6689
  }
6640
- if (!existsSync8(dirname6(input.outputPath))) {
6690
+ if (!existsSync9(dirname6(input.outputPath))) {
6641
6691
  mkdirSync7(dirname6(input.outputPath), { recursive: true });
6642
6692
  }
6643
6693
  if (!isUsableFile(input.cachePath)) {
@@ -7112,7 +7162,7 @@ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-serve
7112
7162
  import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
7113
7163
 
7114
7164
  // src/services/assetMediaType.ts
7115
- import { existsSync as existsSync9 } from "node:fs";
7165
+ import { existsSync as existsSync10 } from "node:fs";
7116
7166
  import {
7117
7167
  fingerprintElementId,
7118
7168
  isNotMediaPayload,
@@ -7185,7 +7235,7 @@ async function preflightCompositionAssetMediaTypes(input) {
7185
7235
  input.projectDir,
7186
7236
  input.compiledDir
7187
7237
  );
7188
- if (!existsSync9(resolvedPath)) continue;
7238
+ if (!existsSync10(resolvedPath)) continue;
7189
7239
  byPath.set(resolvedPath, [...byPath.get(resolvedPath) ?? [], reference]);
7190
7240
  }
7191
7241
  const mismatches = [];
@@ -7273,6 +7323,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
7273
7323
  this.problems = problems;
7274
7324
  }
7275
7325
  };
7326
+ function normalizeCompositionSrcs(html, projectDir) {
7327
+ return html.replace(/(\bdata-composition-src=)(["'])([^"']*)\2/g, (whole, name, quote, value) => {
7328
+ if (!value.includes("%") && !value.startsWith("/")) return whole;
7329
+ const file = resolveCompositionSrc(projectDir, value);
7330
+ if (file === null) return whole;
7331
+ return `${name}${quote}${relative2(projectDir, file).split(sep2).join("/")}${quote}`;
7332
+ });
7333
+ }
7276
7334
  function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
7277
7335
  const { document: document2 } = parseHTML5(html);
7278
7336
  const hosts = [...document2.querySelectorAll("[data-composition-src]")];
@@ -7281,7 +7339,11 @@ function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */
7281
7339
  const srcPath = el.getAttribute("data-composition-src");
7282
7340
  if (!srcPath) continue;
7283
7341
  if (isUnresolvedAssetPlaceholder(srcPath)) continue;
7284
- const filePath = resolve7(projectDir, srcPath);
7342
+ const filePath = resolveCompositionSrc(projectDir, srcPath);
7343
+ if (filePath === null) {
7344
+ problems.push({ srcPath, detail: "it is outside the project folder" });
7345
+ continue;
7346
+ }
7285
7347
  if (visited.has(filePath)) continue;
7286
7348
  const read = readProjectFile(filePath);
7287
7349
  if (read.kind === "missing") {
@@ -7427,7 +7489,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
7427
7489
  let filePath = src;
7428
7490
  if (isHttpUrl(src)) {
7429
7491
  const remoteDir = join18(downloadDir, REMOTE_MEDIA_SUBDIR);
7430
- if (!existsSync10(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
7492
+ if (!existsSync11(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
7431
7493
  try {
7432
7494
  filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
7433
7495
  onTelemetry: logRemoteDownloadTelemetry
@@ -7436,11 +7498,11 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
7436
7498
  return { duration: null, resolvedPath: src };
7437
7499
  }
7438
7500
  } else {
7439
- filePath = decodedUrlPath(src);
7501
+ filePath = decodedUrlPath2(src);
7440
7502
  if (!filePath.startsWith("/")) filePath = join18(baseDir, filePath);
7441
7503
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
7442
7504
  }
7443
- if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
7505
+ if (!isHttpUrl(filePath) && !existsSync11(filePath)) {
7444
7506
  return { duration: null, resolvedPath: filePath };
7445
7507
  }
7446
7508
  const withSrcContext = (error) => {
@@ -7581,7 +7643,8 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
7581
7643
  for (const el of compEls) {
7582
7644
  const srcPath = el.getAttribute("data-composition-src");
7583
7645
  if (!srcPath) continue;
7584
- const filePath = resolve7(projectDir, srcPath);
7646
+ const filePath = resolveCompositionSrc(projectDir, srcPath);
7647
+ if (filePath === null) continue;
7585
7648
  if (visited.has(filePath)) {
7586
7649
  continue;
7587
7650
  }
@@ -7589,7 +7652,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
7589
7652
  if (read.kind !== "file") {
7590
7653
  continue;
7591
7654
  }
7592
- const rawSubHtml = read.text;
7655
+ const rawSubHtml = normalizeCompositionSrcs(read.text, projectDir);
7593
7656
  const nestedVisited = new Set(visited);
7594
7657
  nestedVisited.add(filePath);
7595
7658
  workItems.push({ srcPath, filePath, rawSubHtml, nestedVisited });
@@ -7777,8 +7840,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
7777
7840
  resolveHtml: (srcPath) => {
7778
7841
  let compHtml = subCompositions.get(srcPath) || null;
7779
7842
  if (!compHtml) {
7780
- const read = readProjectFile(resolve7(projectDir, srcPath));
7781
- if (read.kind === "file") {
7843
+ const filePath = resolveCompositionSrc(projectDir, srcPath);
7844
+ const read = filePath ? readProjectFile(filePath) : null;
7845
+ if (read?.kind === "file") {
7782
7846
  compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
7783
7847
  }
7784
7848
  }
@@ -7787,7 +7851,7 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
7787
7851
  parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
7788
7852
  // Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
7789
7853
  // against its own directory, project-root refs stay as authored.
7790
- assetExists: (path) => existsSync10(resolve7(projectDir, path)),
7854
+ assetExists: (path) => existsSync11(resolve7(projectDir, path)),
7791
7855
  // Preserve the authored root wrapper as a child of the host, matching
7792
7856
  // the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
7793
7857
  // which the runtime compositionLoader mirrors with its own copy for the
@@ -7978,7 +8042,7 @@ function collectExternalAssets(html, projectDir) {
7978
8042
  if (isPathInside2(absPath, absProjectDir)) {
7979
8043
  return null;
7980
8044
  }
7981
- if (!existsSync10(absPath)) return null;
8045
+ if (!existsSync11(absPath)) return null;
7982
8046
  const safeKey = toExternalAssetKey(absPath);
7983
8047
  externalAssets.set(safeKey, absPath);
7984
8048
  return safeKey;
@@ -8028,7 +8092,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
8028
8092
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
8029
8093
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
8030
8094
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
8031
- if (!existsSync10(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
8095
+ if (!existsSync11(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
8032
8096
  const urlToLocal = /* @__PURE__ */ new Map();
8033
8097
  await Promise.all(
8034
8098
  [...urlSet].map(async (url) => {
@@ -8210,14 +8274,14 @@ function projectStylesheetFontFamilies(html, projectDir) {
8210
8274
  for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
8211
8275
  const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
8212
8276
  if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
8213
- const sheet = resolve7(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
8214
- if (!isPathInside2(sheet, projectDir) || !existsSync10(sheet)) continue;
8277
+ const sheet = resolve7(projectDir, decodedUrlPath2(href).replace(/^(?:\.?\/)+/, ""));
8278
+ if (!isPathInside2(sheet, projectDir) || !existsSync11(sheet)) continue;
8215
8279
  for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
8216
8280
  const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
8217
8281
  const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
8218
- const path = decodedUrlPath(url);
8282
+ const path = decodedUrlPath2(url);
8219
8283
  const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname8(sheet), path);
8220
- return isPathInside2(file, projectDir) && existsSync10(file);
8284
+ return isPathInside2(file, projectDir) && existsSync11(file);
8221
8285
  });
8222
8286
  if (family && shipsFile) families.push(family);
8223
8287
  }
@@ -8352,7 +8416,7 @@ function renderGsapDist() {
8352
8416
  }
8353
8417
  function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
8354
8418
  let gsapDist;
8355
- const exists = (path) => existsSync10(resolve7(projectDir, path));
8419
+ const exists = (path) => existsSync11(resolve7(projectDir, path));
8356
8420
  return html.replace(
8357
8421
  /(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
8358
8422
  (full, prefix, src, file, suffix) => {
@@ -8369,7 +8433,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
8369
8433
  const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
8370
8434
  if (!entryPath.includes("/")) return html;
8371
8435
  const { document: document2 } = parseHTML5(html);
8372
- const assetExists = (path) => existsSync10(resolve7(projectDir, path));
8436
+ const assetExists = (path) => existsSync11(resolve7(projectDir, path));
8373
8437
  rewriteAssetPaths(
8374
8438
  document2.querySelectorAll("[src], [href], [srcset]"),
8375
8439
  entryPath,
@@ -8391,7 +8455,10 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
8391
8455
  }
8392
8456
  async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
8393
8457
  const entryHtml = rebaseDirectEntryAssetPaths(readTextFile(htmlPath), projectDir, htmlPath);
8394
- const rawHtml = rewriteUnresolvableGsapToCdn(entryHtml, projectDir);
8458
+ const rawHtml = normalizeCompositionSrcs(
8459
+ rewriteUnresolvableGsapToCdn(entryHtml, projectDir),
8460
+ projectDir
8461
+ );
8395
8462
  assertSubCompositionsUsable(rawHtml, projectDir);
8396
8463
  const probes = /* @__PURE__ */ new Map();
8397
8464
  const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
@@ -8936,7 +9003,7 @@ function resolveEntryHtmlPath(input) {
8936
9003
  const rawEntry = readTextFile2(htmlPath);
8937
9004
  if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
8938
9005
  const projectIndexPath = join19(projectDir, "index.html");
8939
- if (!existsSync11(projectIndexPath)) {
9006
+ if (!existsSync12(projectIndexPath)) {
8940
9007
  throw new Error(
8941
9008
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
8942
9009
  );
@@ -9770,7 +9837,7 @@ function validateRenderDuration(input) {
9770
9837
  }
9771
9838
 
9772
9839
  // src/services/render/stages/extractVideosStage.ts
9773
- import { existsSync as existsSync12 } from "node:fs";
9840
+ import { existsSync as existsSync13 } from "node:fs";
9774
9841
  import { join as join21 } from "node:path";
9775
9842
  import {
9776
9843
  classifyVideoExtractionError,
@@ -10040,7 +10107,7 @@ async function runExtractVideosStage(input) {
10040
10107
  const probeFailures = await Promise.all(
10041
10108
  composition.videos.map(async (v) => {
10042
10109
  const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
10043
- if (!existsSync12(videoPath)) return null;
10110
+ if (!existsSync13(videoPath)) return null;
10044
10111
  try {
10045
10112
  const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
10046
10113
  signal: abortSignal,
@@ -10078,7 +10145,7 @@ async function runExtractVideosStage(input) {
10078
10145
  const probed = await Promise.all(
10079
10146
  composition.images.map(async (img) => {
10080
10147
  const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
10081
- if (!existsSync12(imgPath)) return null;
10148
+ if (!existsSync13(imgPath)) return null;
10082
10149
  const colorSpace = await probeColorSpaceSafely(imgPath, log);
10083
10150
  if (isHdrColorSpace2(colorSpace)) {
10084
10151
  nativeHdrImageIds.add(img.id);
@@ -10198,7 +10265,7 @@ async function appendVideoAudioFromProbe(input) {
10198
10265
  videos.map(async (video) => {
10199
10266
  const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
10200
10267
  const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
10201
- if (!isHttpUrl2(readFrom) && !existsSync12(readFrom)) return null;
10268
+ if (!isHttpUrl2(readFrom) && !existsSync13(readFrom)) return null;
10202
10269
  const metadata = await extractMediaMetadata3(readFrom);
10203
10270
  const window2 = await resolveFinalFrameExtractionWindow2(
10204
10271
  readFrom,
@@ -10218,7 +10285,7 @@ async function appendVideoAudioFromProbe(input) {
10218
10285
  }
10219
10286
 
10220
10287
  // src/services/render/videoLayerPassthroughPlan.ts
10221
- import { existsSync as existsSync13 } from "node:fs";
10288
+ import { existsSync as existsSync14 } from "node:fs";
10222
10289
  import {
10223
10290
  buildVideoLayerInput,
10224
10291
  isHttpUrl as isHttpUrl3,
@@ -10286,7 +10353,7 @@ async function planVideoLayerPassthrough(input) {
10286
10353
  const { video } = q;
10287
10354
  if (isHttpUrl3(video.src)) return { reason: "remote_source" };
10288
10355
  const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
10289
- if (!existsSync13(path)) return { reason: "source_missing" };
10356
+ if (!existsSync14(path)) return { reason: "source_missing" };
10290
10357
  const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
10291
10358
  if ("reason" in layout) return layout;
10292
10359
  const layer = await buildVideoLayerInput(
@@ -10301,7 +10368,7 @@ async function planVideoLayerPassthrough(input) {
10301
10368
  }
10302
10369
 
10303
10370
  // src/services/render/stages/audioStage.ts
10304
- import { existsSync as existsSync15 } from "node:fs";
10371
+ import { existsSync as existsSync16 } from "node:fs";
10305
10372
  import { join as join24 } from "node:path";
10306
10373
  import {
10307
10374
  MIXED_AUDIO_FILENAME,
@@ -10312,7 +10379,7 @@ import {
10312
10379
  } from "@hyperframes/engine";
10313
10380
 
10314
10381
  // src/services/render/audioDeliveryLimit.ts
10315
- import { existsSync as existsSync14 } from "node:fs";
10382
+ import { existsSync as existsSync15 } from "node:fs";
10316
10383
  import { dirname as dirname10, join as join23 } from "node:path";
10317
10384
  import {
10318
10385
  applyAudioFxChain,
@@ -10821,7 +10888,7 @@ async function limitDeliveryPeaks(input, deps = {
10821
10888
  applyChain: applyAudioFxChain,
10822
10889
  encode: encodeAac2,
10823
10890
  rename: renameWithRetry6,
10824
- sourceExists: existsSync14
10891
+ sourceExists: existsSync15
10825
10892
  }) {
10826
10893
  const { audioPath, sourcePath, signal, timeoutMs } = input;
10827
10894
  if (!deps.sourceExists(sourcePath)) return { success: true };
@@ -10916,7 +10983,7 @@ async function runAudioStageScoped(input) {
10916
10983
  );
10917
10984
  audioLoweredDb = guard.loweredDb;
10918
10985
  input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
10919
- if (input.keepDeliverySource && !guard.sourceStale && existsSync15(pcmSourcePath)) {
10986
+ if (input.keepDeliverySource && !guard.sourceStale && existsSync16(pcmSourcePath)) {
10920
10987
  deliverySourcePath = pcmSourcePath;
10921
10988
  }
10922
10989
  }
@@ -11044,7 +11111,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
11044
11111
  }
11045
11112
 
11046
11113
  // src/services/render/stages/captureStage.ts
11047
- import { existsSync as existsSync16, readdirSync as readdirSync4, statfsSync as statfsSync2 } from "node:fs";
11114
+ import { existsSync as existsSync17, readdirSync as readdirSync4, statfsSync as statfsSync2 } from "node:fs";
11048
11115
  import { join as join25 } from "node:path";
11049
11116
  import {
11050
11117
  classifyCaptureFailure as classifyCaptureFailure2,
@@ -11115,7 +11182,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
11115
11182
  }
11116
11183
  function measureCaptureFrameBytes(workDir, framesDir) {
11117
11184
  let total = sampleDirectoryBytes(framesDir);
11118
- if (!existsSync16(workDir)) return total;
11185
+ if (!existsSync17(workDir)) return total;
11119
11186
  for (const attempt of readdirSync4(workDir)) {
11120
11187
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
11121
11188
  total += sampleDirectoryBytes(join25(workDir, attempt));
@@ -11300,7 +11367,11 @@ async function runSequentialDiskCapture(input, captureOptions, captureCfg, check
11300
11367
  prepareCaptureSessionForReuse3(session, framesDir, videoInjector);
11301
11368
  probeSession = null;
11302
11369
  }
11303
- await ensureSessionReady(session);
11370
+ await ensureSessionReady(session, {
11371
+ frameIndex: resumeFrom,
11372
+ totalFrames,
11373
+ abortSignal: input.abortSignal
11374
+ });
11304
11375
  assertAttemptActive();
11305
11376
  lastBrowserConsole = session.browserConsoleBuffer;
11306
11377
  await captureSessionFrames(session, {
@@ -11352,13 +11423,13 @@ function planTransientResume(error, ctx) {
11352
11423
  if (!firstMissing) return null;
11353
11424
  return isTransientCaptureRetryEligible(failure.kind, missing, ctx.retriesUsed) ? firstMissing.startFrame : null;
11354
11425
  }
11355
- async function ensureSessionReady(session) {
11426
+ async function ensureSessionReady(session, budget) {
11356
11427
  if (!session.isInitialized) {
11357
11428
  await initializeSession5(session);
11358
11429
  return;
11359
11430
  }
11360
11431
  if (process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true") {
11361
- await completeDeferredDrawElementInit3(session);
11432
+ await createSessionInitBudget(budget)(completeDeferredDrawElementInit3(session), session);
11362
11433
  }
11363
11434
  }
11364
11435
  async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resumeFrom, checkDiskProjection }) {
@@ -11418,7 +11489,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
11418
11489
  }
11419
11490
 
11420
11491
  // src/services/render/stages/captureHdrStage.ts
11421
- import { existsSync as existsSync18, mkdirSync as mkdirSync10 } from "node:fs";
11492
+ import { existsSync as existsSync19, mkdirSync as mkdirSync10 } from "node:fs";
11422
11493
  import { join as join29 } from "node:path";
11423
11494
  import {
11424
11495
  closeCaptureSession as closeCaptureSession7,
@@ -11664,7 +11735,7 @@ import { Worker } from "node:worker_threads";
11664
11735
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
11665
11736
  import { dirname as dirname11, join as join27 } from "node:path";
11666
11737
  import { createRequire } from "node:module";
11667
- import { existsSync as existsSync17 } from "node:fs";
11738
+ import { existsSync as existsSync18 } from "node:fs";
11668
11739
  import { cpus as cpus2 } from "node:os";
11669
11740
  function resolveWorkerEntry(explicit) {
11670
11741
  if (explicit && explicit.length > 0) {
@@ -11677,7 +11748,7 @@ function resolveWorkerEntry(explicit) {
11677
11748
  }
11678
11749
  const moduleDir = dirname11(fileURLToPath3(import.meta.url));
11679
11750
  const jsPath = join27(moduleDir, "shaderTransitionWorker.js");
11680
- if (existsSync17(jsPath)) return { path: jsPath, isTs: false };
11751
+ if (existsSync18(jsPath)) return { path: jsPath, isTs: false };
11681
11752
  const tsPath = join27(moduleDir, "shaderTransitionWorker.ts");
11682
11753
  return { path: tsPath, isTs: true };
11683
11754
  }
@@ -12238,7 +12309,7 @@ async function runCaptureHdrStage(input) {
12238
12309
  }
12239
12310
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
12240
12311
  const debugDumpDir = debugDumpEnabled ? join29(framesDir, "debug-composite") : null;
12241
- if (debugDumpDir && !existsSync18(debugDumpDir)) {
12312
+ if (debugDumpDir && !existsSync19(debugDumpDir)) {
12242
12313
  mkdirSync10(debugDumpDir, { recursive: true });
12243
12314
  }
12244
12315
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
@@ -12395,7 +12466,7 @@ async function runCaptureHdrStage(input) {
12395
12466
  // src/services/render/stages/encodeStage.ts
12396
12467
  import {
12397
12468
  copyFileSync as copyFileSync4,
12398
- existsSync as existsSync19,
12469
+ existsSync as existsSync20,
12399
12470
  mkdirSync as mkdirSync11,
12400
12471
  readdirSync as readdirSync5,
12401
12472
  readFileSync as readFileSync8,
@@ -12529,7 +12600,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
12529
12600
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
12530
12601
  if (failure) return failure;
12531
12602
  }
12532
- const fileSize = existsSync19(outputPath) ? statSync8(outputPath).size : 0;
12603
+ const fileSize = existsSync20(outputPath) ? statSync8(outputPath).size : 0;
12533
12604
  return {
12534
12605
  success: true,
12535
12606
  outputPath,
@@ -12690,7 +12761,7 @@ async function runEncodeStage(input) {
12690
12761
  const stage5Start = Date.now();
12691
12762
  if (isPngSequence) {
12692
12763
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
12693
- if (!existsSync19(outputPath)) mkdirSync11(outputPath, { recursive: true });
12764
+ if (!existsSync20(outputPath)) mkdirSync11(outputPath, { recursive: true });
12694
12765
  const captured = readdirSync5(framesDir).filter((name) => name.endsWith(".png")).sort();
12695
12766
  if (captured.length === 0) {
12696
12767
  throw new Error(
@@ -12701,7 +12772,7 @@ async function runEncodeStage(input) {
12701
12772
  const dst = join30(outputPath, formatExportFrameName(i, "png"));
12702
12773
  copyFileSync4(join30(framesDir, name), dst);
12703
12774
  });
12704
- if (hasAudio && audioOutputPath && existsSync19(audioOutputPath)) {
12775
+ if (hasAudio && audioOutputPath && existsSync20(audioOutputPath)) {
12705
12776
  copyFileSync4(audioOutputPath, join30(outputPath, MIXED_AUDIO_FILENAME2));
12706
12777
  log.info(
12707
12778
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
@@ -13090,7 +13161,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
13090
13161
  let rangeStart = null;
13091
13162
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
13092
13163
  const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
13093
- const missing = !existsSync20(framePath) || statSync9(framePath).size <= 8;
13164
+ const missing = !existsSync21(framePath) || statSync9(framePath).size <= 8;
13094
13165
  if (missing && rangeStart === null) {
13095
13166
  rangeStart = frameIndex;
13096
13167
  } else if (!missing && rangeStart !== null) {
@@ -13215,7 +13286,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
13215
13286
  let captured = 0;
13216
13287
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
13217
13288
  const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
13218
- if (existsSync20(framePath)) captured++;
13289
+ if (existsSync21(framePath)) captured++;
13219
13290
  }
13220
13291
  return captured;
13221
13292
  }
@@ -13880,7 +13951,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
13880
13951
  validateDeliveryEncoderConfig(job.config);
13881
13952
  const debugDir = resolveRenderDebugDir();
13882
13953
  const outputDir = dirname13(outputPath);
13883
- if (!existsSync20(outputDir)) mkdirSync12(outputDir, { recursive: true });
13954
+ if (!existsSync21(outputDir)) mkdirSync12(outputDir, { recursive: true });
13884
13955
  const workDir = job.config.debug ? join31(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
13885
13956
  const pipelineStart = Date.now();
13886
13957
  const baseLog = job.config.logger ?? defaultLogger;
@@ -14022,7 +14093,7 @@ async function executeRenderPipeline(input) {
14022
14093
  job.startedAt = /* @__PURE__ */ new Date();
14023
14094
  assertNotAborted();
14024
14095
  assertConfiguredFfmpegBinariesExist();
14025
- if (!existsSync20(workDir)) mkdirSync12(workDir, { recursive: true });
14096
+ if (!existsSync21(workDir)) mkdirSync12(workDir, { recursive: true });
14026
14097
  if (job.config.debug) {
14027
14098
  log.info("[Render] Debug artifacts enabled", { workDir, logPath });
14028
14099
  }
@@ -14052,7 +14123,7 @@ async function executeRenderPipeline(input) {
14052
14123
  });
14053
14124
  const entryFile = job.config.entryFile || "index.html";
14054
14125
  const htmlPath = join31(projectDir, entryFile);
14055
- if (!existsSync20(htmlPath)) {
14126
+ if (!existsSync21(htmlPath)) {
14056
14127
  throw new Error(`Entry file not found: ${htmlPath}`);
14057
14128
  }
14058
14129
  assertNotAborted();
@@ -14367,9 +14438,11 @@ async function executeRenderPipeline(input) {
14367
14438
  );
14368
14439
  pendingAudio = audioStage.catch(() => void 0);
14369
14440
  const stage4Start = Date.now();
14370
- updateJobStatus(job, "rendering", "Starting frame capture", 25, onProgress, {
14441
+ const EFFECT_SCAN_STAGE = "Checking effects";
14442
+ const startingFrameCapture = () => updateJobStatus(job, "rendering", "Starting frame capture", 25, onProgress, {
14371
14443
  code: "start_browsers"
14372
14444
  });
14445
+ startingFrameCapture();
14373
14446
  if (!fileServer) {
14374
14447
  const fileServerStart = observability.stageStart("file_server", { reused: false });
14375
14448
  try {
@@ -14394,7 +14467,7 @@ async function executeRenderPipeline(input) {
14394
14467
  throw new Error("File server failed to initialize before frame capture");
14395
14468
  }
14396
14469
  const framesDir = join31(workDir, "captured-frames");
14397
- if (!existsSync20(framesDir)) mkdirSync12(framesDir, { recursive: true });
14470
+ if (!existsSync21(framesDir)) mkdirSync12(framesDir, { recursive: true });
14398
14471
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
14399
14472
  code: "start_browsers"
14400
14473
  });
@@ -14402,6 +14475,7 @@ async function executeRenderPipeline(input) {
14402
14475
  chromePath: resolveHeadlessShellPath(cfg),
14403
14476
  browserTimeout: cfg.browserTimeout
14404
14477
  });
14478
+ startingFrameCapture();
14405
14479
  captureForceScreenshot = applyConcreteGpuScreenshotClamp(
14406
14480
  captureForceScreenshot,
14407
14481
  resolvedBrowserGpuMode,
@@ -14450,10 +14524,13 @@ async function executeRenderPipeline(input) {
14450
14524
  onEffectScanProgress: (scanned, total) => updateJobStatus(
14451
14525
  job,
14452
14526
  "rendering",
14453
- `Checking effects, ${scanned} of ${total} frames`,
14527
+ `${EFFECT_SCAN_STAGE}, ${scanned} of ${total} frames`,
14454
14528
  25,
14455
14529
  onProgress
14456
14530
  ),
14531
+ onEffectScanEnd: () => {
14532
+ if (job.currentStage.startsWith(EFFECT_SCAN_STAGE)) startingFrameCapture();
14533
+ },
14457
14534
  requiresWebGpu: compositionRequiresWebGpu2(compiled.html),
14458
14535
  // Live route for Chrome memory (spec §5 Phase −1). The aggregate route
14459
14536
  // through CapturePerfSummary only exists on success; a mid-capture
@@ -14721,6 +14798,7 @@ async function executeRenderPipeline(input) {
14721
14798
  } finally {
14722
14799
  calibrationFrames?.release();
14723
14800
  }
14801
+ startingFrameCapture();
14724
14802
  captureCalibration = outcome.calibration;
14725
14803
  calibrationEvaluateTimedOut = outcome.calibrationEvaluateTimedOut;
14726
14804
  captureForceScreenshot = outcome.forceScreenshot;
@@ -15779,7 +15857,7 @@ async function executeRenderPipeline(input) {
15779
15857
  })
15780
15858
  );
15781
15859
  const totalElapsed = Date.now() - pipelineStart;
15782
- const tmpPeakBytes = existsSync20(workDir) ? sampleDirectoryBytes(workDir) : 0;
15860
+ const tmpPeakBytes = existsSync21(workDir) ? sampleDirectoryBytes(workDir) : 0;
15783
15861
  recordTransientRetryObservability();
15784
15862
  observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
15785
15863
  const observabilitySummary = observability.summary({
@@ -15860,7 +15938,7 @@ async function executeRenderPipeline(input) {
15860
15938
  }
15861
15939
  }
15862
15940
  if (job.config.debug) {
15863
- if (!isPngSequence && !isHls && existsSync20(stagedOutputPath)) {
15941
+ if (!isPngSequence && !isHls && existsSync21(stagedOutputPath)) {
15864
15942
  const debugOutput = join31(workDir, `output${videoExt}`);
15865
15943
  copyFileSync5(stagedOutputPath, debugOutput);
15866
15944
  }
@@ -15972,7 +16050,7 @@ async function executeRenderPipeline(input) {
15972
16050
 
15973
16051
  // src/services/hyperframeLint.ts
15974
16052
  import {
15975
- existsSync as existsSync21,
16053
+ existsSync as existsSync22,
15976
16054
  readFileSync as readFileSync9,
15977
16055
  statSync as statSync10,
15978
16056
  openSync as openSync5,
@@ -15980,7 +16058,7 @@ import {
15980
16058
  closeSync as closeSync5,
15981
16059
  constants as constants3
15982
16060
  } from "node:fs";
15983
- import { resolve as resolve9, join as join32, relative as relative3, isAbsolute as isAbsolute3, sep as sep2 } from "node:path";
16061
+ import { resolve as resolve9, join as join32, relative as relative3, isAbsolute as isAbsolute3, sep as sep3 } from "node:path";
15984
16062
  import { lintHyperframeHtml } from "@hyperframes/lint";
15985
16063
  function isStringRecord(value) {
15986
16064
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -16026,7 +16104,7 @@ function readEntryHtml(filePath) {
16026
16104
  }
16027
16105
  function readProjectEntryFile(projectDir, preferredEntryFile) {
16028
16106
  const absProjectDir = resolve9(projectDir);
16029
- if (!existsSync21(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
16107
+ if (!existsSync22(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
16030
16108
  return { error: `Project directory not found: ${absProjectDir}` };
16031
16109
  }
16032
16110
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -16035,7 +16113,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
16035
16113
  for (const entryFile of entryCandidates) {
16036
16114
  const absoluteEntryPath = resolve9(absProjectDir, entryFile);
16037
16115
  const entryRelativePath = relative3(absProjectDir, absoluteEntryPath);
16038
- if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep2}`) || isAbsolute3(entryRelativePath)) {
16116
+ if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep3}`) || isAbsolute3(entryRelativePath)) {
16039
16117
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
16040
16118
  }
16041
16119
  const html = readEntryHtml(absoluteEntryPath);
@@ -16095,7 +16173,7 @@ async function runHyperframeLint(prepared) {
16095
16173
  import { Worker as Worker2 } from "node:worker_threads";
16096
16174
  import { fileURLToPath as fileURLToPath5 } from "node:url";
16097
16175
  import { dirname as dirname14, join as join33 } from "node:path";
16098
- import { existsSync as existsSync22 } from "node:fs";
16176
+ import { existsSync as existsSync23 } from "node:fs";
16099
16177
  var DEFAULT_HEALTH_PORT = 9848;
16100
16178
  async function startHealthWorker(options = {}) {
16101
16179
  const log = options.logger ?? defaultLogger2();
@@ -16172,7 +16250,7 @@ function resolveWorkerEntry2() {
16172
16250
  const here = dirname14(fileURLToPath5(import.meta.url));
16173
16251
  const candidates = [join33(here, "healthWorkerThread.js"), join33(here, "healthWorkerThread.ts")];
16174
16252
  for (const candidate of candidates) {
16175
- if (existsSync22(candidate)) return candidate;
16253
+ if (existsSync23(candidate)) return candidate;
16176
16254
  }
16177
16255
  return void 0;
16178
16256
  }
@@ -16212,7 +16290,7 @@ import {
16212
16290
  createReferenceBackgroundRecorder as createReferenceBackgroundRecorder2
16213
16291
  } from "@hyperframes/engine";
16214
16292
  import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
16215
- import { extname as extname4, isAbsolute as isAbsolute4, join as join34, relative as relative4, sep as sep3 } from "node:path";
16293
+ import { extname as extname4, isAbsolute as isAbsolute4, join as join34, relative as relative4, sep as sep4 } from "node:path";
16216
16294
  import {
16217
16295
  fpsToNumber as fpsToNumber9,
16218
16296
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
@@ -16720,7 +16798,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
16720
16798
  const { input, cleanupProjectDir } = prepared;
16721
16799
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
16722
16800
  const outputDir = dirname15(absoluteOutputPath);
16723
- if (!existsSync23(outputDir)) mkdirSync13(outputDir, { recursive: true });
16801
+ if (!existsSync24(outputDir)) mkdirSync13(outputDir, { recursive: true });
16724
16802
  return { input, cleanupProjectDir, absoluteOutputPath };
16725
16803
  }
16726
16804
  function validateRenderOverrides(body) {
@@ -16761,11 +16839,11 @@ function prepareProjectDirectory(projectDir, options) {
16761
16839
  const candidate = nonEmptyString(projectDir);
16762
16840
  if (!candidate) return null;
16763
16841
  const absProjectDir = resolve10(candidate);
16764
- if (!existsSync23(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
16842
+ if (!existsSync24(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
16765
16843
  return { error: `Project directory not found: ${absProjectDir}` };
16766
16844
  }
16767
16845
  const entry = options.entryFile || "index.html";
16768
- if (!existsSync23(resolve10(absProjectDir, entry))) {
16846
+ if (!existsSync24(resolve10(absProjectDir, entry))) {
16769
16847
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
16770
16848
  }
16771
16849
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -16857,7 +16935,7 @@ function cleanupTempDir(dir, log) {
16857
16935
  }
16858
16936
  }
16859
16937
  function outputFileSize(path) {
16860
- return existsSync23(path) ? statSync11(path).size : 0;
16938
+ return existsSync24(path) ? statSync11(path).size : 0;
16861
16939
  }
16862
16940
  function createBlockingProgressReporter(log, requestId) {
16863
16941
  let lastLoggedPct = -10;
@@ -17161,7 +17239,7 @@ function createRenderHandlers(options = {}) {
17161
17239
  if (!artifact) {
17162
17240
  return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
17163
17241
  }
17164
- if (!existsSync23(artifact.path)) {
17242
+ if (!existsSync24(artifact.path)) {
17165
17243
  store.delete(token);
17166
17244
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
17167
17245
  }