DEV Community

Humza Tareen
Humza Tareen

Posted on Originally published at humzakt.github.io

Repeat a Clip, Pin It Everywhere: Fixing Instance Semantics in a Render Batch Tool

A render-batch builder tool — a web UI for stacking hooks and visuals into export sets before they go to the video pipeline — had a bug so basic it's easy to miss why it mattered: you couldn't put the same clip in a batch twice. Every real NLE, every design tool, treats "the same media-pool item, placed twice" as the most ordinary operation there is. This tool actively fought it, on purpose, with two separate mechanisms that had each been added for reasonable-sounding reasons and combined into something no editor would expect.

A constraint that made sense in isolation can still be the wrong constraint once you ask what the user is actually trying to do.

The bug, in one hookstack

The report was a slot meant to hold five variants, each pairing one hook against a different visual:

H1112W75 + H113V1
H1112W75 + H113V2
H1112W75 + H113V3
H1112W75 + H113V4
H1112W75 + H113V5
Enter fullscreen mode Exit fullscreen mode

Only the first row was actually buildable. Two client-side constraints were responsible, both self-imposed rather than inherited from any real limitation: excludeFileIds greyed out any library row already used elsewhere in the batch, with a tooltip reading "already in this batch," so the same hook simply couldn't be picked a second time. And even where that was worked around by hand, dedupSolos kept at most one un-stacked row per source asset per slot — so a hand-imported second copy would look fine right up until the next ungroup, unstack, or remove, at which point it silently collapsed away, taking whatever edits had been made to it with it.

The render backend had no opinion on any of this. /api/batch/prepare and /api/render both take plain arrays of clip IDs with repeats honored positionally, and the prepared-clip cache is keyed on (clipId, encode-params hash) — meaning one clip reused across five variants was always going to cost one encode, not five. The entire limitation was a client-side misunderstanding of what an "instance" is, with no backend justification at all.

Two features, one underlying model fix

The fix (PR #1) is really one change wearing two names. Repeat lets an editor explicitly add another instance of a clip via a dedicated control on the row, rather than fighting the library picker. Two new fields on the clip record carry the distinction that matters: explicitInstance, set only when an editor deliberately asked for another copy — dedupSolos never touches these — and duplicateOfId, which points back at the root source row so every instance inherits the same underlying fields and can't quietly drift into looking like a different clip with the same name.

Pin to all solves an adjacent but distinct problem: an editor ticks one clip and asks for it to appear in every variant lane at once, rather than being copied into each one by hand. This mirrors how ad platforms already model the same idea — Google Ads pinning is a per-asset flag, not N duplicated rows in a spreadsheet — and it buys the same property here: one clip record still means one ingest and one encode no matter how many variants reference it, and a new variant imported later picks up the pinned clip automatically, which a model built on materialized duplicates could never do without re-copying into the new row by hand.

// laneOfSlotRaw: unchanged row grouping, for the slot card
// laneOfSlot: variant truth — pinned clip stripped from every lane,
// empties dropped, then re-injected at pinnedPosition
function laneOfSlot(raw: Lane[], pin: { clipId: string; position: 'first' | 'last' }): Lane[] {
  const withoutPin = raw
    .map(lane => lane.filter(c => c.id !== pin.clipId))
    .filter(lane => lane.length > 0);
  return withoutPin.map(lane =>
    pin.position === 'first' ? [pinnedClip, ...lane] : [...lane, pinnedClip]
  );
}
Enter fullscreen mode Exit fullscreen mode

The removal step has to span every lane, not just the lane the pinned clip originally lived in — after a "stack all" operation, every clip in a slot can share one stackGroupId, so a narrower removal rule would drop an entire slot's worth of lanes instead of just the one pinned reference. Everything downstream — variant counts, size and time estimates, matched/vary availability, the submit path — reads through this one function, so none of it needed to change to pick the new behavior up.

Four smaller bugs the audit surfaced

Building the fix meant tracing every code path that touched a duplicated or pinned clip, and that tracing turned up bugs the original report never mentioned: duplicating a clip whose background upload was still in flight aborted the whole export, because the new instance had no entry in the promise-tracking maps the upload pipeline used to resolve completion — a hole that, once found, turned out to already exist in an older auto-clone code path too, just never triggered. Rows that happened to share one underlying Drive file were being finalized and ingested once each rather than once total. A filename-collision dedup check sat below two early-return branches that should have run through it first, so those specific modes shipped duplicate React keys. And preview ordering broke specifically while a clip was pinned, because every group's sort key started with the same pinned ID and had nothing left to distinguish them by.

The follow-up that found the first fix was wrong

PR #2 exists because a screenshot of the shipped build showed exactly the bug the fix was supposed to solve — one file appearing twice, another appearing three times, none of them badged as instances. The cause was narrower than a regression: dropping a file that's already in a slot is the most natural way an editor would ask for "the same hook again," and that interaction path had never been taught about the new instance fields at all. It was, in the PR's own words, the original bug, still fully alive, just for a different gesture than the one the first fix covered.

The more interesting finding in the same PR is a retraction. PR #1's own description had claimed that its upload-crash fix also covered the pre-existing auto-clone path from an earlier feature. Checking that claim rather than trusting it: it did not. The auto-clone carried no provenance field for the fix to walk, so the claim shipped wrong. That's worth sitting with for a second — the person who wrote the fix, checking their own prior PR's description against what the code actually did, and correcting the record rather than letting a plausible-sounding claim stand uninspected.

The rest of PR #2 is a genuine tightening rather than a rewrite: a new fingerprinting function flags a freshly-dropped file against existing rows in the same slot by filename:size, because a clip that was just dropped doesn't have a Drive ID yet — the lookup that assigns one runs asynchronously afterward, so anything keyed on Drive ID would miss it entirely. And the deduplication rule that had been silently deleting rows was narrowed to only ever touch implicit clones — an auto-generated copy with no explicit instance flag, not pinned, not part of a real stack — which is exactly and only the shape of row the original ghost-row bug was about. Two rows an editor created independently, by dropping the same file twice on purpose, now have no shared marker at all and are never touched by the cleanup logic again.

The pattern: model what the user means, not what's convenient to store

Nothing about this bug was algorithmically hard. The render backend had already solved the interesting problem — repeats-as-positional-references with a content-addressed encode cache — months before the UI caught up. The entire cost here was a client-side data model that conflated "the same underlying asset" with "the same intent," and treated a second deliberate copy as noise to be deduplicated away rather than a choice to be preserved. The fix that actually held wasn't the first attempt at instance tracking — it was the second pass, after checking a shipped claim against reality and finding a whole interaction path the first pass hadn't touched at all.

Top comments (0)