Skip to content

fix(studio): export the composition the user has selected - #3550

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/studio-export-active-composition
Aug 29, 2026
Merged

fix(studio): export the composition the user has selected#3550
miguel-heygen merged 1 commit into
mainfrom
fix/studio-export-active-composition

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

Exporting from Studio now renders the composition the user currently has selected, instead of always falling back to index.html.

Why

Reported in #3549: in a project with a root index.html plus parts/part-N.html sub-compositions, selecting a part in the Comps panel correctly showed that part's canvas and timeline, but clicking Export rendered the full-length root file. hyperframes render -c parts/part-1.html from the CLI produced the right file, so the gap was Studio-side only.

The server already honours the request: POST /api/projects/:id/render reads body.composition, validates it resolves inside the project, and forwards it to the render adapter (packages/studio-server/src/routes/render.ts:93-101). It falls back to index.html when the field is absent, which is the correct behaviour for a request that names nothing. No server change was needed.

How

The render target had three owners, one per control that can start a render:

  • the Renders panel resolved it from the active composition,
  • the header's Export button passed no options at all, so the field was simply missing,
  • the sidebar's per-composition Render button named one explicitly.

Only the middle one was wrong, but patching it would have left the same decision duplicated in two places, free to drift again. Instead the default now lives in startRender, which all three controls route through, next to the FFmpeg gate that is there for the same reason:

const composition = opts.composition ?? activeCompPathRef.current ?? undefined;

An explicit argument still wins, so the sidebar keeps rendering the card the user clicked rather than the one on screen. With nothing selected (master view) the field stays absent and the server's index.html fallback still applies. useRenderQueue takes the active composition as a ref so the target is read at click time without rebuilding every callback on each composition switch. The panel's now-redundant copy of the logic is deleted.

No composition picker was added: defaulting to the active composition is the fix, and a picker would be new surface for a problem the selection already answers.

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)

Unit — new useRenderQueueComposition.test.tsx asserts the POST body directly: it carries the active composition when the caller names none, keeps an explicit argument when one is given, and omits the field entirely when nothing is selected. Verified non-vacuous by reverting the one-line default, which fails the first case with expected undefined to be 'parts/part-1.html'.

Full package suite: bun run --filter @hyperframes/studio test → 409 files, 4553 tests passing.

End to end — scaffolded a project matching the report's shape (root index.html at 20s, parts/part-1.html at 4s), ran the real preview server against it, drove Studio in Puppeteer, selected parts/part-1 in the Comps panel, clicked the header's Export, and let the render finish.

Before, with the default removed:

RENDER POST BODY: {"fps":30,"quality":"standard","format":"mp4",...}
$ ffprobe ... renders/<job>.mp4
duration=20.000000        # the root, not the part

After:

RENDER POST BODY: {"fps":30,"quality":"standard","format":"mp4",...,"composition":"parts/part-1.html"}
$ ffprobe ... renders/<job>.mp4
duration=4.000000         # the selected part

Fixes #3549

The header's Export button started renders with no options at all, so the
request carried no `composition` and the server fell back to index.html.
Selecting a sub-composition in the Comps panel showed its canvas and timeline
but exported the root file instead.

Studio starts renders from three controls, and the render target was owned by
each of them separately: the Renders panel resolved it, the header omitted it,
the sidebar's per-composition button named one explicitly. Give it one owner
in `startRender`, which all three route through, defaulting to the active
composition and leaving an explicit argument to win.

Fixes #3549
@miguel-heygen
miguel-heygen merged commit b71f459 into main Aug 29, 2026
68 of 69 checks passed
@miguel-heygen
miguel-heygen deleted the fix/studio-export-active-composition branch August 29, 2026 17:54
@miguel-heygen miguel-heygen mentioned this pull request Aug 29, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio: Export renders index.html even when a non-root composition (parts/part-1.html) is selected

1 participant