Repository navigation
Export the timeline's sub-composition hydration helpers, or hydrate nested clips in useTimelinePlayer #6153
Description
What we're doing
We host Studio's components inside our own editor rather than running StudioApp, the way
hostMount.test.tsx and the TimelinePane export comments describe. That already works well:
ConnectedDomEditOverlay, PropertyPanel and useDomEditSession give us hand editing on the
picture, and useTimelinePlayer + Timeline mount cleanly in our page with no NLEProvider and
no EditorShell. Thank you — the host surface is genuinely usable.
Our documents are compositions whose every scene is a sub-composition (data-composition-src).
We want the timeline to show one scene's own elements, so a user who opens a scene can retime
its clips.
What's blocked
useTimelinePlayer fills usePlayerStore.elements from the manifest with the sub-composition
hosts only. The children never arrive, so an expanded host has nothing to show.
The runtime does publish the nesting. In the preview window:
window.__clipTree
// { roots: [ { id: "inner", parentId: null, children: [
// { id: "ss-hud", parentId: "inner", children: [] },
// { id: "ss-title", parentId: "inner", children: [] },
// { id: "ss-note-1", parentId: "inner", children: [] },
// { id: "ss-note-2", parentId: "inner", children: [] } ] } ] }And the store accepts the expansion — after setClipExpanded(key, true):
usePlayerStore.getState().expandedClipIds // Set { "index.html#inner" }
usePlayerStore.getState().elements // [ { id: "inner", kind: "composition", ... } ] ← host onlyThe code that bridges those two is already in the package, in
src/player/hooks/timelineSyncHydration.ts:
clipTreeParentMap(win)(L66) — walks__clipTreeinto a child → parent mapcollectSubCompositionDomChildren(...)(L122)buildTimelineElementsFromClips(...)(L200)syncManifestTimeline(...)(L222)
That file's own comment (L117-119) says it plainly: the elements "carry no data-start, so the
... DOM children + parent links is what lets the timeline expand a sub-composition." That is
exactly the behaviour we want, and it is exactly what a host cannot reach.
None of the four is exported. dist/index.d.ts and dist/player.d.ts each contain zero
occurrences of all four names, and the exports map (., ./ui, ./player,
./tailwind-preset, the stylesheets, package.json) shuts deep imports of src/.
What we tried, so you don't have to suggest it
Building the children host-side and writing them in with setElements does not hold. The
player re-syncs the store from the manifest as the preview plays, so rows added from outside are
dropped on the next tick; topping them back up fights that sync and takes the page down with
"Maximum update depth exceeded". We take the lesson to be that the store is the player's and a
host should not be writing elements into it — which is why we're asking for the hydration path
rather than a way around it.
We also cannot point a preview at the sub-composition and play it alone: in our documents a
scene's file is a <template> fragment with no <html>, <body>, <script> or runtime, so it
isn't a document. That may be particular to how we generate them, and we mention it only to show
why useCompositionStack isn't the answer for us.
The ask
Either of these unblocks us; the first is smaller.
-
Export the four helpers from
.or./player. They are already written, already tested
and already have the semantics we want. No behaviour change, no new surface to maintain
beyond the names. -
Have
useTimelinePlayerhydrate nested clips itself when a host is inexpandedClipIds—
arguably where the behaviour belongs, sinceexpandedClipIdsandsetClipExpandedare
already public and today they expand to nothing for a host.
If neither suits, a documented way for a host to contribute elements that the manifest sync will
not clobber would also do.
Environment
@hyperframes/studio@0.8.106,@hyperframes/core@0.8.106- React 19, Vite, the components hosted in our own page (no
StudioApp, noEditorShell) - Public API only: no fork, no patched
dist, no deep imports
Activity
Dante-dan commentedon Oct 23, 2026
I traced this against current main (
2512642d09d3d5a4703fd2e6af71930a52e6c7ca). There is an important distinction between hydration and the rows the timeline displays:useTimelinePlayeralready callsuseTimelineSyncCallbacks, whose manifest handler populatesclipParentMap,domClipChildren, andsubCompositionHostState. Children do not have to appear inelementsfor that hydration to have happened. Current handler.Timeline.For your fragment-based host, I would propose an opt-in scene-scoped timeline view: keep the existing preview and manifest as the timing authority, derive a selected scene's rows from its hydrated descendants, and leave the normal Studio top-level default intact. The important acceptance cases would be changing scenes, preserving rows across subsequent manifest messages, and making a child timing edit target the scene's source rather than the parent host. This needs a supported row/edit boundary; a host-side
setElementsloop would keep fighting the player.Would maintainers prefer that host scene-view scope, or a deliberately low-level helper export for hosts supplying their own row view? Also, on a current package version, are
domClipChildrenandclipParentMappopulated after the preview loads? That separates a remaining hydration problem from the row-model limitation. I have inspected the source paths; I have not reproduced your hosted editor.jrusso1020 commentedon Oct 24, 2026
Thanks for the detailed write-up and for listing what you already tried. Confirmed: the four helpers are exported from packages/studio/src/player/hooks/timelineSyncHydration.ts (lines 67, 123, 201, 223), but none of the package entry points re-export them, so a host can't reach them. One detail worth knowing: #4132 made the Timeline show top-level rows only, on purpose. So exporting the helpers would give you the hydrated data but would not make the stock Timeline show child rows by itself. Choosing between a low-level helper export and a supported scene-scoped timeline view is a design call for the maintainers, so I'm tagging this as a feature request. — Rames