# How Palmier Pro's Timeline Ripple Edit Engine Handles Concurrent Clip Mutations

> Discover how Palmier Pro's timeline ripple edit engine manages concurrent clip mutations using atomic transactions and pure-functional utilities for precise editing.

- Repository: [Palmier/palmier-pro](https://github.com/palmier-io/palmier-pro)
- Tags: internals
- Published: 2026-07-27

---

**Palmier Pro's timeline ripple edit engine manages concurrent clip mutations through atomic transactions that validate multicam groups, sync-locked tracks, and linked A/V pairs before computing precise shift vectors via the pure-functional `RippleEngine` utility.**

The palmier-pro video editor implements non-destructive ripple editing as a coordinated system of clip mutations that preserves timeline integrity across complex media relationships. In `Sources/PalmierPro/Editor/ViewModel/EditorViewModel+Ripple.swift`, the engine orchestrates multi-clip operations by discovering dependencies, enforcing constraints, and applying mathematical shift calculations. This architecture ensures that concurrent mutations across linked media, multicam angles, and sync-locked tracks remain consistent, conflict-free, and fully undoable.

## Atomic Mutation via `withTimelineSwap`

All public ripple operations—including `rippleTrimClip`, `rippleDeleteSelectedClips`, `rippleInsertClips`, and range-based deletions—execute inside **`withTimelineSwap(actionName:refreshVisuals:)`**. This wrapper guarantees atomicity by grouping the entire mutation into a single undo transaction and preventing intermediate state exposure.

```swift
withTimelineSwap(actionName: "Ripple Trim", refreshVisuals: true) {
    // Apply resizes, shifts, and track sorting as one unit
    applyShifts(computedShifts)
    updateClipMetadata(clipId, trimStart: newStart, duration: newDuration)
    sortClipsOnTrack(trackIndex)
}

```

The method registers a timeline undo step via `registerTimelineUndo`, ensuring users can revert the entire ripple operation with a single undo command.

## Target Discovery: Building the Ripple Dependency Graph

Before applying any transformation, the engine discovers every clip that must participate in the ripple to maintain synchronization.

### Linked Partners and Multicam Cohorts

The `rippleTrimTargets` method iteratively expands the target set to include:

- **Linked partners**: Audio/video clips sharing a `linkGroupId` when `propagateToLinked` is true
- **Multicam cohorts**: Clips belonging to the same multicam group gathered via `multicamRippleCohort`

The discovery routine runs until closure is reached, ensuring no related clip is left behind during the mutation.

### Sync-Locked Track Handling

Tracks flagged as `syncLocked` automatically shift as a block with the lead track. The engine identifies these tracks through the timeline model and computes collective room checks to prevent downstream collisions.

## Safety Validation Before Mutation

The engine validates three critical constraints to prevent timeline corruption.

### Multicam Atomicity Enforcement

The **`multicamAtomicityViolation`** check ensures ripple operations do not split a multicam group across moving and stationary tracks. If a violation is detected, the operation halts immediately via **`refuseRipple`**, which emits a UI toast and logs the conflict.

```swift
if multicamAtomicityViolation(cohorts: affectedCohorts) {
    refuseRipple(reason: "Multicam group must remain on single track")
    return .refused("Multicam atomicity violated")
}

```

### Sync-Locked Room Checks

When shrinking clips via `rippleTrimClip`, the engine calculates the minimum "shrink room" across all targets and examines **`syncLockedLeftRoom`** to verify that sync-locked tracks possess adequate space to accommodate the shift without crossing frame zero.

### Shift Validation Dry-Runs

Before committing changes, **`validateShifts`** performs a dry-run on the computed `ClipShift` array. Any shift that would push a clip before the timeline start or cause overlaps on sync-locked tracks results in immediate rejection.

## Computing the Ripple with `RippleEngine`

The heavy lifting is delegated to **`RippleEngine`** (referenced from [`Sources/PalmierPro/Utilities/RippleEngine.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Utilities/RippleEngine.swift)), which provides pure functions for shift calculation:

- **`computeRipplePush(clips:insertFrame:pushAmount:excludeIds:)`**: Calculates forward pushes for right-edge edits (inserts or right-side trims) while optionally excluding specific clip IDs
- **`computeRippleShifts(clips:removedIds:)`**: Determines downstream shifts for deletions on the same track
- **`computeRippleShiftsForRanges(clips:removedRanges:)`**: Handles arbitrary frame range deletions using **`mergeRanges(_:)`** to consolidate overlapping regions before calculation

These functions return an array of **`ClipShift`** structures (`clipId` paired with `newStartFrame`), which the main ripple code applies via `applyShifts(_:)`.

```swift
// Compute how much to push clips when inserting at frame 120
let shifts = RippleEngine.computeRipplePush(
    clips: trackClips,
    insertFrame: 120,
    pushAmount: newClipDuration,
    excludeIds: insertedClipIds
)

// Apply the computed shifts atomically
applyShifts(shifts)

```

## Result Reporting and Outcome Types

Deletion and insertion operations return a **`RippleRangesOutcome`** enumeration that communicates success or failure:

- **`.ok(RippleRangesReport)`**: Contains `removedFramesCount`, `clearedTracks`, `shiftedClipCount`, and `resultingFragments` for UI synchronization
- **`.refused(String)`**: Provides a human-readable reason (`multicamAtomicityViolation`, `insufficientSyncLockedRoom`, etc.)

This strongly-typed result ensures the UI layer can display precise feedback when constraints prevent a ripple edit.

## Concurrency Model and Thread Safety

All ripple edits execute as **single-threaded atomic units** from the UI perspective. Because `withTimelineSwap` captures the entire state transformation, the UI thread only observes the final consistent state. The `RippleEngine` computation functions are pure and side-effect-free, making them safe for background execution if future implementations move calculation off the main actor.

## Key Invariants Preserved

The engine maintains four critical timeline invariants during every ripple mutation:

- **Linked A/V synchronization**: Linked partners mutate together with pixel-perfect alignment
- **Multicam atomicity**: Entire multicam groups shift as monolithic units across tracks
- **Sync-locked integrity**: Sync-locked tracks preserve relative offsets and never overlap
- **Temporal validity**: No clip may start before frame zero or violate track-specific constraints

## Summary

- Palmier Pro's ripple edit engine uses **`withTimelineSwap`** to wrap mutations in atomic, undoable transactions
- Target discovery expands selections to include linked clips, multicam cohorts, and sync-locked tracks
- Validation layers enforce **multicam atomicity**, **sync-locked room availability**, and **shift validity** before mutation
- **`RippleEngine`** computes precise shift vectors via pure functions like `computeRipplePush` and `computeRippleShifts`
- Operations return **`RippleRangesOutcome`** to communicate detailed results or refusal reasons
- All mutations preserve **four key invariants**: linked sync, multicam unity, sync-lock integrity, and temporal bounds

## Frequently Asked Questions

### What prevents a ripple edit from breaking multicam synchronization?

The engine calls **`multicamAtomicityViolation`** before executing any mutation. This function checks whether the proposed shift would distribute a multicam group's clips across both moving and stationary tracks. If the group would split, **`refuseRipple`** aborts the operation and alerts the user, ensuring multicam clips remain temporally aligned.

### How does the engine handle clips linked across audio and video tracks?

When `propagateToLinked` is true, the **`rippleTrimTargets`** method discovers all clips sharing a `linkGroupId` with the primary target. These linked partners are included in the mutation set, and the engine validates that sufficient room exists on all linked tracks before applying shifts, preserving exact A/V sync.

### Can ripple edits run on background threads?

While current call sites execute on the main actor, the architecture supports background computation. The **`RippleEngine`** functions are pure and stateless, receiving immutable clip arrays and returning `ClipShift` calculations. The actual mutation occurs inside **`withTimelineSwap`**, which serializes access to the timeline model, making the system thread-safe for concurrent editing scenarios.

### What happens if a ripple edit would push a clip before the timeline start?

The **`validateShifts`** function performs a dry-run calculation on the proposed `ClipShift` array. Any shift producing a `newStartFrame` less than zero triggers immediate rejection. The operation returns **`.refused("Shift would violate timeline bounds")`**, preventing invalid states from reaching the timeline model.