Skip to content

fix(cli): timeline lists clips nested in a timed element and lint flags ones that never show - #5732

Merged
miguel-heygen merged 7 commits into
mainfrom
fix/timeline-and-lint-see-nested-clips
Oct 17, 2026
Merged

miguel-heygen merged 7 commits into
mainfrom
fix/timeline-and-lint-see-nested-clips

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

What

hyperframes timeline now shows clips nested inside another timed element, and lint reports a nested clip that can never be visible.

Before, a project with timed "shot" wrappers holding timed <video> clips showed only the wrappers, each with children: []. Every clip inside was missing from the timeline, so an agent reading it saw empty shots and re-added clips that were already there.

There was a second problem behind it. A nested clip's data-start counts from the composition's start, not from its wrapper, and the clip only shows while the wrapper shows. Clips written with wrapper-relative times therefore never appear: in a 9-shot fixture, every clip after the first shot rendered black, and nothing said why.

How

  • Timeline (describeProject): a timed element that is not a sub-composition host now lists the timed elements inside it as rows, recursively, at their real playback time (the same start rule as the runtime).
    • The new rows appear in the wrapper's children, and each one points back to its wrapper through a new parentRow field.
    • nested, host and hostRow keep their sub-composition meaning. The edit commands use them to bound a sub-composition clip by its host's length, which would be wrong for a wrapper child, whose times are composition times.
  • Never visible: a nested row whose window does not overlap its parent's gets a warning, for example never visible: plays 0-2s, but #shot2 only shows 5-10s; a nested data-start counts from the composition, not from #shot2. The text view now prints a row's warnings; before, it dropped them.
  • Lint: new nested_clip_never_visible in the structure rules. It is a warning in the CLI and an error in Studio, like its neighbours, and it runs in sub-composition files too. The existing video_nested_in_timed_element and nested_structure_needs_subcomposition warnings say the pattern is discouraged; this one names the clips that will never be seen.
  • Documented in the lint reference, with one line in the core skill.

Verification

  • Tests that fail on main:

    • describeProject.test.ts "lists clips nested in a timed wrapper at their playback time and says when one never shows".
    • structure.test.ts "flags a nested clip whose window misses its parent's" and "checks sub-composition files too". "leaves a nested clip that plays inside its parent's window" guards against false hits.
  • The CLI timeline suites (108 tests) and the lint package (958 tests, including the catalog check that structure rules stay silent on every shipped registry item and skill example) pass 3 runs in a row.

  • CLI and lint tsc are clean, as are oxlint, oxfmt and the comment ratchet.

  • Built CLI on a fixture of 9 timed shot wrappers holding 40 timed videos, with wrapper-relative starts:

    main this branch
    timeline --json video rows 0 40
    Shot 1 children 0 5
    Rows warned never visible 0 35 (every clip in shots 2-9)
    lint nested_clip_never_visible 0 35

Release notes: timeline now lists timed elements nested in other timed elements, so kind/index refs such as video/3 can point at a different clip than before. Use #id refs in scripts.

@mintlify

mintlify Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Oct 17, 2026, 12:08 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@mintlify

mintlify Bot commented Oct 17, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟡 Building – Oct 17, 2026, 12:02 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 154 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes at ef13e87b. The timing and the lint rule are right, but the new nested rows now also feed the timeline edit commands. As a result, the very edit the new warning asks for is refused.

Blocking

  1. Edits on a nested clip, or on its wrapper, collide with each other (timeline/a2Shared.ts overlap and the duplicate branch of mutationConflict). Both checks compare against every row from allRows in the same file on the same trackIndex, and wrapper children are now in that list. A child without data-track-index falls to track 0, the same lane as a wrapper on track 0. With this PR's own fixture (#shot2 at 5–10s on track 0, holding #lost at 0–2s and #seen at 6–8s):

    • timeline move #lost 6, which is the fix the new warning suggests, is refused as "would overlap #shot2": the child collides with its own parent.
    • timeline move #shot2 4 is refused as "would overlap #seen". This worked on main, where children weren't rows. The suggested fix, "move the named neighbour", then points at the wrapper's own child.
    • timeline duplicate #seen inserts at 8, inside #shot2, so it's refused. The duplicate branch has no --overwrite bypass.

    Skipping ancestor and descendant rows (through parentRow) in both checks would fix it, along with a move test on a nested clip.

  2. A sub-composition host inside a timed wrapper produces children: [null] (describeProject.ts flatten). add() recurses only into clip.inside, and a top-level clip's children are added only in the outer loop. A nested host's sub-composition clips are therefore never added, so at() returns undefined for them, and --json lists null children pointing at rows that don't exist. Example: <div id="shot" data-start="0" data-duration="5"><div data-composition-src="scene.html" data-start="0" data-duration="5"></div></div>. Fix: have add() also walk clip.children with host = clip, or don't read sub-compositions for nested hosts.

Non-blocking

  • Nested <audio> gets the error, but it still plays. The runtime hides a nested element when its parent isn't showing (clipChainVisibleAt), but media playback uses only the clip's own window. A nested <audio> outside its parent's window is still heard. Lint says it "never appears", and Studio treats that as an error. Following the suggested fix would change when the audio plays. Excluding audio clips, or wording this for visual clips only, would avoid that.
  • Lint ignores data-hf-media-start-basis="global". In a sub-composition, a legacy root-time media data-start is compared against a local parent window. The timeline gets this right through resolveMediaStartSeconds.
  • In-point cuts stop at sub-compositions. cutRow cuts the sub-composition row but passes inside through unchanged. Wrapper children inside a host with an in-point keep their uncut times.
  • The text view hides nesting. Wrapper children have nested: false, so the header counts them as top-level. The "no stable name" warning now prints on every unnamed nested row. kind/index refs also shift, which is worth a line in the release notes.

Checked and fine

  • The body's timing claim holds. In startResolver.ts computeStart, a nested numeric data-start counts from the composition, not from the wrapper. Visibility also needs every timed ancestor visible (clipChainVisibleAt), so "never visible when the windows don't overlap" is the right rule for what shows on screen.
  • resolveRowTiming describes inner rows with the same scope and origin. The overlap test matches the runtime's half-open clip windows.
  • Both checks skip windows they can't resolve. A nested media clip with no data-start defaults to 0, which matches the compiler.
  • isSubCompositionHost keeps both checks from walking into hosts. The new lint rule is its own entry in structureRules. nested, host and hostRow keep their meaning, and parentRow is purely additive.

CI is the only run. Not armed or merged.

— Rames

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Thanks, all addressed at fcc274f3.

Blocking

  1. overlap and the duplicate branch of mutationConflict now skip rows that are an ancestor or descendant of the edited row (walking parentRow). New e2e cases on this PR's fixture: move #lost 8, move #shot2 4 and duplicate #seen all succeed. They fail without the change.
  2. flatten now walks a nested host's children with that host as host, so a sub-composition inside a timed wrapper lists its clips. New test: #shot holding a compositions/title.html host lists scene, and scene lists t1, t2, with no null children.

Non-blocking

  • Nested <audio>: excluded from the timeline warning and from the lint rule, since it plays on its own window.
  • data-hf-media-start-basis="global": lint skips those media clips (it still checks clips nested inside them).
  • In-point cuts: cutRow now cuts inside too. New test: a clip in a wrapper under a host with in-point 5 shows 5–8 s and media start 6, not its uncut 4–8 s.
  • Text view: the header counts wrapper children as nested, and "no stable name" no longer prints per nested row (the details only print the never-visible warning for nested rows). The kind/index shift is in the PR body as a release-notes line.

Devbox: each new test fails on the parent commit; the CLI timeline suites (113) and lint (959) pass 3 runs in a row; tsc, the comment ratchet and format:check are clean.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 071d68a8. Last time I requested changes at ef13e87b. Both blockers are fixed. The two pushes since fcc274f3 are behaviour-preserving: followsParentVisibility and isTimed replace the two inline guards with the same conditions, in the same order, and one test helper is inlined; 071d68a8 only undoes a stray reformat.

  1. Nested edits no longer collide with their own parent. overlap and the duplicate branch of mutationConflict now skip rows in the same parent chain (sameLineage, walking parentRow). The new e2e cases run on the PR's own fixture. move #lost 8 succeeds, which is the move the warning suggests. move #shot2 4 and duplicate #seen also succeed. Sub-composition rows are in another file, so the file check still keeps them apart.
  2. A sub-composition host inside a wrapper lists its clips. flatten's add() now walks clip.children with host = clip at every depth, so a nested host's clips become rows. The new test sees shot → [scene] and scene → [t1, t2], with no null children.

My non-blocking notes are mostly covered too:

  • <audio> is now excluded, in both the lint rule and the CLI warning.
  • Lint skips data-hf-media-start-basis="global".
  • cutRow now carries the cut into inside, and there's a test for a clip nested in a wrapper.
  • The header now counts wrapper children as nested.
  • The text view prints only the never-visible warning, which also matches main, where it printed no warnings at all.

kind/index refs still shift for projects with wrapped clips. That's worth a line in the release notes, but it doesn't block.

CI is the only run. Not armed or merged.

— Rames

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit eab6ca1 Oct 17, 2026
70 checks passed
@miguel-heygen
miguel-heygen deleted the fix/timeline-and-lint-see-nested-clips branch October 17, 2026 02:42
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.

2 participants