Describe the bug
When dragging a clip horizontally in the Studio's timeline editor, the Studio rewrites index.html on disk and:
-
Injects inline style="z-index: N" on every clip in the file — not only on the clip that was dragged. The injected values are derived from data-track-index in inverse order (track 0 receives the highest z-index, the last track receives the lowest). Inline-style specificity overrides any CSS z-index the author defined.
-
Produces malformed self-closing tags when adding the inline attribute to void elements. Example output:
<img
id="gif-img"
...
alt="rotating earth gif"
/ style="z-index: 7">
The / of the self-closing img is no longer adjacent to >. HTML5 parsers tolerate this, so rendering still works, but it's an authoring artifact that surprises diff readers and any downstream tooling that parses the file with a strict parser.
The first issue contradicts the official skill documentation, which explicitly states:
data-track-index does not affect visual layering — use CSS z-index.
Steps to reproduce
npx hyperframes init repro --example blank --non-interactive
- Edit
index.html to add a background video + an overlay with explicit CSS z-index whose visual order does not match data-track-index order:
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: #000; }
#bg-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; }
#title { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
font: 700 120px sans-serif; color: #fff; z-index: 20; }
</style>
<div id="root" data-composition-id="main" data-start="0" data-duration="10"
data-width="1920" data-height="1080">
<video id="bg-video" data-start="0" data-track-index="0" src="some-bg.mp4" muted playsinline></video>
<div id="title" class="clip" data-start="0" data-duration="10" data-track-index="1">TITLE</div>
</div>
npx hyperframes preview
- In the Studio, drag the video clip's block horizontally on the timeline (any small distance that commits — typically more than ~2 seconds of timeline).
- Re-read
index.html from disk.
Expected behavior
- Only
data-start (and possibly data-duration) of the dragged clip is modified.
- The author's CSS
z-index remains the source of truth for visual layering, as documented.
- Other clips are left untouched.
- Void elements stay well-formed.
Actual behavior
Every clip in the file is rewritten with an inline style="z-index: N" attribute. Example diff:
- <video id="bg-video" data-start="0" data-track-index="0" src="some-bg.mp4" muted playsinline></video>
+ <video id="bg-video" data-start="0" data-track-index="0" src="some-bg.mp4" muted playsinline style="z-index: 9"></video>
- <div id="title" class="clip" data-start="0" data-duration="10" data-track-index="1">TITLE</div>
+ <div id="title" class="clip" data-start="0" data-duration="10" data-track-index="1" style="z-index: 8">TITLE</div>
Result: inline z-index: 9 on the video (CSS z-index was 0) now sits on top of the title's inline z-index: 8 (CSS z-index was 20). The intended layering is silently inverted. In a composition with a full-bleed background video, all overlays become permanently hidden behind it even though the original CSS placed them on top.
When the dragged element is an <img> (a void element), the inline attribute is injected with a misplaced self-closing slash:
<img
id="gif-img"
class="clip"
data-start="1"
data-duration="13"
data-track-index="2"
src="assets/earth.gif"
alt="rotating earth gif"
/ style="z-index: 7">
Empirical observations during the drag gesture
Verified via Playwright with MutationObserver installed inside the composition iframe before the drag:
| Phase |
Mutations on iframe .clip elements |
Mutations on parent Studio UI |
| During the drag (~2.4s, 30 mouse-move events) |
0 |
38 (~15 mut/s on the drag-preview ghost element) |
On mouseup (commit) |
iframe reloads with new file contents (observer loses references) |
— |
So the bug isn't a per-frame mutation issue during the drag itself — it's a serialization issue when the Studio commits the edit to disk and reinjects layering metadata that wasn't there before.
Why this matters
- Composition author's intent (CSS
z-index) is silently overridden by Studio's metadata derivation.
- Documented contract (
data-track-index does not affect layering) is broken by the Studio's own behavior.
- The corruption is persistent in the file — surviving page reload, restart, and shared via VCS.
- Any composition using CSS
z-index for non-default stacking will be broken the first time someone drags a clip in the Studio.
Environment
✓ Version 0.6.25 (latest)
Node.js v22+
OS Windows 11 (10.0.26200)
Shell PowerShell
Suggested fix direction
- Preserve the author's CSS
z-index and stop auto-injecting inline z-index when committing a timeline edit. If the Studio needs an internal ordering for the layers inspector, compute it without mutating the source HTML.
- If automatic z-index injection is intentional, at minimum:
- Use a consistent and documented ordering (matching DOM order, or the existing CSS z-index if defined).
- Honor existing inline/CSS z-index instead of overwriting it.
- Add a project-level opt-out flag.
- Fix the HTML serializer to handle void elements correctly when injecting attributes: produce
<img ... style="..." /> (or <img ... style="...">), not <img ... / style="...">.
Describe the bug
When dragging a clip horizontally in the Studio's timeline editor, the Studio rewrites
index.htmlon disk and:Injects inline
style="z-index: N"on every clip in the file — not only on the clip that was dragged. The injected values are derived fromdata-track-indexin inverse order (track 0 receives the highest z-index, the last track receives the lowest). Inline-style specificity overrides any CSSz-indexthe author defined.Produces malformed self-closing tags when adding the inline attribute to void elements. Example output:
The
/of the self-closing img is no longer adjacent to>. HTML5 parsers tolerate this, so rendering still works, but it's an authoring artifact that surprises diff readers and any downstream tooling that parses the file with a strict parser.The first issue contradicts the official skill documentation, which explicitly states:
Steps to reproduce
npx hyperframes init repro --example blank --non-interactiveindex.htmlto add a background video + an overlay with explicit CSSz-indexwhose visual order does not matchdata-track-indexorder:npx hyperframes previewindex.htmlfrom disk.Expected behavior
data-start(and possiblydata-duration) of the dragged clip is modified.z-indexremains the source of truth for visual layering, as documented.Actual behavior
Every clip in the file is rewritten with an inline
style="z-index: N"attribute. Example diff:Result: inline
z-index: 9on the video (CSS z-index was 0) now sits on top of the title's inlinez-index: 8(CSS z-index was 20). The intended layering is silently inverted. In a composition with a full-bleed background video, all overlays become permanently hidden behind it even though the original CSS placed them on top.When the dragged element is an
<img>(a void element), the inline attribute is injected with a misplaced self-closing slash:Empirical observations during the drag gesture
Verified via Playwright with
MutationObserverinstalled inside the composition iframe before the drag:.clipelementsmouseup(commit)So the bug isn't a per-frame mutation issue during the drag itself — it's a serialization issue when the Studio commits the edit to disk and reinjects layering metadata that wasn't there before.
Why this matters
z-index) is silently overridden by Studio's metadata derivation.data-track-indexdoes not affect layering) is broken by the Studio's own behavior.z-indexfor non-default stacking will be broken the first time someone drags a clip in the Studio.Environment
Suggested fix direction
z-indexand stop auto-injecting inline z-index when committing a timeline edit. If the Studio needs an internal ordering for the layers inspector, compute it without mutating the source HTML.<img ... style="..." />(or<img ... style="...">), not<img ... / style="...">.