diff --git a/packages/producer/src/services/renderOrchestrator.test.ts b/packages/producer/src/services/renderOrchestrator.test.ts
index 1af6be66b0..07608b26c6 100644
--- a/packages/producer/src/services/renderOrchestrator.test.ts
+++ b/packages/producer/src/services/renderOrchestrator.test.ts
@@ -109,6 +109,44 @@ describe("extractStandaloneEntryFromIndex", () => {
expect(extracted).toBeNull();
});
+
+ it("re-points the wrapper duration at the scene's own, not the master's", () => {
+ const indexHtml = `
+
+
+
+
+`;
+ const sceneHtml = ``;
+
+ const extracted = extractStandaloneEntryFromIndex(
+ indexHtml,
+ "compositions/scene1.html",
+ sceneHtml,
+ );
+
+ // The extracted standalone advertises the scene's 2s, not the master's 12s.
+ expect(extracted).toContain('data-duration="2"');
+ expect(extracted).not.toContain('data-duration="12"');
+ });
+
+ it("falls back to the mount's data-duration when the scene file isn't supplied", () => {
+ const indexHtml = `
+
+
+
+
+`;
+
+ const extracted = extractStandaloneEntryFromIndex(indexHtml, "compositions/scene1.html");
+
+ expect(extracted).toContain('data-duration="2"');
+ expect(extracted).not.toContain('data-duration="12"');
+ });
});
describe("captureAttemptMadeProgress", () => {
diff --git a/packages/producer/src/services/renderOrchestrator.ts b/packages/producer/src/services/renderOrchestrator.ts
index 4e241748d5..7028b64e64 100644
--- a/packages/producer/src/services/renderOrchestrator.ts
+++ b/packages/producer/src/services/renderOrchestrator.ts
@@ -931,7 +931,27 @@ function normalizeCompositionSrcPath(srcPath: string): string {
return srcPath.replace(/\\/g, "/").replace(/^\.\//, "");
}
-function createStandaloneEntryRenderClone(root: Element, host: Element): Element {
+/**
+ * Read the `data-duration` off a scene file's `` root — the scene's
+ * own authored length. linkedom does not implement inert `` content,
+ * so we re-parse `template.innerHTML` (the pattern htmlBundler uses) to reach
+ * the composition root inside it. Returns null when the file has no template
+ * or the root declares no duration.
+ */
+function readSceneRootDuration(entryHtml: string | undefined): string | null {
+ if (!entryHtml) return null;
+ const { document } = parseHTML(entryHtml);
+ const template = document.querySelector("template");
+ const scope = template ? parseHTML(template.innerHTML).document : document;
+ const root = scope.querySelector("[data-composition-id]") as Element | null;
+ return root?.getAttribute("data-duration") ?? null;
+}
+
+function createStandaloneEntryRenderClone(
+ root: Element,
+ host: Element,
+ sceneDuration: string | null,
+): Element {
// linkedom's cloneNode returns `any` (not `Node`), so the Element cast
// is needed to access setAttribute/appendChild without losing type safety.
const hostClone = host.cloneNode(true) as Element;
@@ -940,6 +960,18 @@ function createStandaloneEntryRenderClone(root: Element, host: Element): Element
if (root === host) return hostClone;
const rootClone = root.cloneNode(false) as Element;
+ // The standalone composition IS the mounted scene, not the master shell that
+ // wraps it. A shallow clone of the master root otherwise keeps the master's
+ // data-duration (the whole project's length), so `render -c ` rendered
+ // the scene for the entire project duration — or threw "Composition has zero
+ // duration" when the master derived its length from siblings now removed.
+ // Re-point the wrapper's duration at the scene's own; drop it (derive from the
+ // single child) only when the scene declared none.
+ if (sceneDuration != null) {
+ rootClone.setAttribute("data-duration", sceneDuration);
+ } else {
+ rootClone.removeAttribute("data-duration");
+ }
rootClone.appendChild(hostClone);
return rootClone;
}
@@ -1086,6 +1118,7 @@ export function shouldDiscardProbeSessionForPageSideCompositing(args: {
export function extractStandaloneEntryFromIndex(
indexHtml: string,
entryFile: string,
+ entryHtml?: string,
): string | null {
const normalizedEntryFile = normalizeCompositionSrcPath(entryFile);
const { document } = parseHTML(indexHtml);
@@ -1111,7 +1144,12 @@ export function extractStandaloneEntryFromIndex(
) ?? null;
if (!root) return null;
- const renderClone = createStandaloneEntryRenderClone(root, host);
+ // The scene file is the source of truth for its own duration; fall back to the
+ // mount's data-duration (its window in the master timeline) when the scene
+ // file content isn't supplied.
+ const sceneDuration = readSceneRootDuration(entryHtml) ?? host.getAttribute("data-duration");
+
+ const renderClone = createStandaloneEntryRenderClone(root, host, sceneDuration);
replaceBodyWithRenderClone(body, renderClone);
return document.toString();
@@ -1285,6 +1323,7 @@ export async function executeRenderJob(
const standaloneHtml = extractStandaloneEntryFromIndex(
readFileSync(projectIndexPath, "utf-8"),
entryFile,
+ rawEntry,
);
if (!standaloneHtml) {
throw new Error(