Skip to content

@remotion/studio: Fix source-only edits on clips trimmed by parents - #12288

Merged
JonnyBurger merged 3 commits into
mainfrom
codex/source-only-trim-outlines
Oct 15, 2026
Merged

JonnyBurger merged 3 commits into
mainfrom
codex/source-only-trim-outlines

Conversation

@JonnyBurger

@JonnyBurger JonnyBurger commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Source-only timeline edits now slide footage within fixed visible clip bounds, including footage hidden by an ancestor's trim. Dragging right first reduces the child's own trimBefore, then shifts its from to reveal earlier source frames. Explicit durations are adjusted when needed to keep the visible end fixed, while parent scenes and captions retain their timing.

Show the available-source outline from pointer-down through dragging, outside parent clipping masks, including for clips hidden at the playhead. Display Source start / Source end when those limits are reached, preserve keyframe and computed-value restrictions, and calculate the delta in composition frames across playback rates.

Validation

  • Verified IMG_0455 with zero and nonzero child trims: footage moves while the timeline bar stays fixed. A 30-frame preview matched the earlier source frame pixel-for-pixel.
  • Verified saved edits and undo with inferred and explicit durations, nested playback rates, and an existing source trim.
  • 262 focused timeline tests passed, including eight new source-only cases covering inherited trim, source bounds, explicit and inferred duration, nested playback rates, and keyframe locks. Disabling the source-only branch makes the regression cases fail.
  • Existing hidden-clip drag-preview Playwright test passed.
  • Corrected the two source-expression drag tests to calculate pixels per frame from the full clip width, without subtracting the trim handles’ inset. Both failures reproduced before the change and both tests pass afterward.
  • bun run build passed (81 tasks).
  • bun run stylecheck passed (266 tasks and skill-link checks).

Show available source footage while a source-only gesture is held, including
for nested clips hidden at the playhead. Render the outline outside the scene
mask while keeping clip content and pointer hit areas clipped to the scene.
@vercel

vercel Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
bugs Ready Ready Preview Oct 15, 2026 5:40am UTC
remotion Ready Ready Preview Oct 15, 2026 5:40am UTC

Request Review

Let source-only edits reveal footage hidden by an ancestor after the
child's own trim reaches zero. Adjust the child start and duration as
needed without moving its visible timeline bounds or parent content.

Keep live overrides and saved edits consistent, preserve source and
keyframe limits, and show source-boundary feedback with deltas measured
in composition frames across nested playback rates.
@JonnyBurger JonnyBurger changed the title @remotion/studio: Show source-only trim outlines @remotion/studio: Fix source-only edits on clips trimmed by parents Oct 14, 2026
@JonnyBurger
JonnyBurger marked this pull request as ready for review October 14, 2026 20:09

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — minor suggestions inline.

Reviewed changes

  • Source-only from shifting — TimelineSequenceRightEdgeDragHandle.tsx gains a new trimBeforeOnly && target.sourceOnlyLimits branch in getTimelineSequenceLeftEdgeDragValuesForTarget plus the sourceOnlyLimits construction (hidden-prefix, source-end, keyframe, adjustDuration/requiresExplicitDuration bounds) and now previews/saves from and durationInFrames for source-only drags.
  • Unclipped outline — new TimelineTrimOutline.tsx splits the row wrapper into an outer pointerEvents: none div and a clipped inner div, and portals the source-only outline into the outer div via TimelineUnclippedTrimOutline; TimelineTracks.tsx uses the new container.
  • Outline visibility — TimelineSequence.tsx drops the two activeEdgeHighlight === 'source-only' early-returns, computes start/end from the full source range for source-only, and enables naturalSequenceDuration for all edge drags.
  • Tooltip — TimelineTrimTooltip.tsx gains showSourceLimits, which keeps Source start/Source end visible for source-only drags; the test file is updated for the new prop.

I traced the new math through getTimelineSequenceTimingLimits, the save path (getTimelineSequenceLeftEdgeDragChanges → saveSequenceProps) and the preview queueStaticDragOverrideIfChanged calls. I could not construct a case where the new branch yields a negative duration, a trimBefore outside the source range, a from past the hidden prefix, or a save/preview mismatch: fromShift ≤ maximumFromShift ≤ hiddenPrefix, and the adjustDuration cap keeps durationInFrames ≥ keyframes.minimumDuration. Existing timeline tests (keyframe-trim-constraints, timeline-selection, timeline-trim-tooltip) pass locally.

ℹ️ minimumTrimBefore is recomputed instead of derived from the timing limits

The new block derives its own keyframe floor (ownTrimBefore + (keyframes.minimumDuration - initialDuration) * sequencePlaybackRate) rather than reusing getTimelineSequenceTimingLimits(...).minimumTrimBefore, which computes the same quantity from a different initialDuration (visibleEnd vs that module's auto-duration-aware value). They agree today modulo the Math.ceil in keyframes.minimumDuration, but the two paths can drift as either side changes.

Technical details
# Duplicated `minimumTrimBefore` derivation

## Affected sites
- `packages/studio/src/components/Timeline/TimelineSequenceRightEdgeDragHandle.tsx:1447-1455` — new inline floor from `ownTrimBefore` + `visibleEnd`.
- `packages/studio/src/components/Timeline/get-timeline-sequence-timing-limits.ts:283-294` — canonical floor from `trimBefore` + its own `initialDuration`, already returned in the `timingLimits` this same target stores.

## Required outcome
- The source-only clamp and the reason strings stay consistent with the shared timing-limit floor, without two independent formulas that can diverge.

## Open questions for the human
- Is the duplicated formula intentional (e.g. because `timingLimits.minimumTrimBefore` is computed against an `initialDuration` that differs from the drag-visible one), or can it be replaced by `timingLimits.minimumTrimBefore` with a comment explaining any required adjustment?

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash | 𝕏

Comment on lines +583 to +600
if (trimBeforeOnly && target.sourceOnlyLimits) {
const limits = target.sourceOnlyLimits;
const sourceOffset = Math.max(
limits.minimumSourceOffset,
Math.min(
limits.maximumSourceOffset,
target.initialTrimBefore -
deltaFrames * target.parentPlaybackRate * target.playbackRate,
),
);
const fromShift = Math.max(0, -sourceOffset / target.playbackRate);
return {
from: target.initialFrom + fromShift,
durationInFrames:
target.initialDuration - (limits.adjustDuration ? fromShift : 0),
trimBefore: Math.max(0, sourceOffset),
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This branch is the only place the new source-only math is exercised, and it has no automated coverage — the only test change in this PR is timeline-trim-tooltip.test.tsx, and existing left-edge tests all pass trimBeforeOnly: false.

Technical details
# Untested source-only trim path

## Affected sites
- `packages/studio/src/components/Timeline/TimelineSequenceRightEdgeDragHandle.tsx:583-600` — source-only clamp/shift logic.
- `packages/studio/src/components/Timeline/TimelineSequenceRightEdgeDragHandle.tsx:1427-1520` — `sourceOnlyLimits` construction it depends on.
- `packages/studio/src/test/keyframe-trim-constraints.test.ts:321-338` — the closest existing harness (`getTimelineSequenceLeftEdgeDragTargets` + `getTimelineSequenceLeftEdgeDragChanges`), currently only used with `trimBeforeOnly: false`.

## Required outcome
- A focused test that builds a media target with a non-zero `trimBefore` / ancestor-hidden prefix and asserts, for `trimBeforeOnly: true`, the returned `from` / `trimBefore` / `durationInFrames` at: the source-start limit (trim reduced to 0), a mid-range drag, the source-end limit, and a keyframe-locked `trimBefore`.

## Suggested approach (optional)
- Reuse the fixture in `keyframe-trim-constraints.test.ts` (it already builds tracks + prop statuses); add cases mirroring the manual matrix in the PR body (zero/non-zero child trim, explicit/inferred duration, nested playback rate).

Exercise source-only target construction and saved changes across source
bounds, own and inherited trim, explicit and inferred durations, nested
playback rates, and keyframe restrictions. Verify visible clip bounds
stay fixed and edits only affect the selected media.

Correct source-expression drag tests to derive pixels per frame from
the full clip width instead of the distance between inset trim handles.
@JonnyBurger
JonnyBurger merged commit 5e1a69c into main Oct 15, 2026
19 checks passed
@JonnyBurger
JonnyBurger deleted the codex/source-only-trim-outlines branch October 15, 2026 05:46

This branch was successfully deployed

2 active deployments
Preview – remotion — a525b734 Deployed Oct 15, 2026 by vercel[bot]
Preview – bugs — a525b734 Deployed Oct 15, 2026 by vercel[bot]
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.

1 participant