Skip to content

fix(studio): playhead keyframe edits keep their ease, undo names its edit and never waits on videos - #4889

Merged
miguel-heygen merged 82 commits into
mainfrom
fix/studio-playhead-writer-carrier
Oct 3, 2026
Merged

miguel-heygen merged 82 commits into
mainfrom
fix/studio-playhead-writer-carrier

Conversation

@miguel-heygen

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

Copy link
Copy Markdown
Collaborator

What

Edits on GSAP-animated layers now write their value at the playhead into the tween the file already has. Several undo, history, host and connection fixes ride along. The edit accuracy bank comes last.

Behaviour change

Studio's own API requests now carry no cookies, so they no longer queue behind the preview's paused videos. A host whose mounted Studio API authenticates with same-origin cookies sets VITE_STUDIO_API_SAME_ORIGIN_CREDENTIALS=true.

This applies when Studio is served from a loopback host (localhost, 127.0.0.1, [::1]). Served from any other host, Studio's requests keep same-origin credentials as before.

An edit inside a tween keeps every other tween's time. Extending a tween changes its span, so tweens placed relative to it move with it: after an edit past its end, a following call with no position, >, += or a chained .to(); after an edit before its start (which writes its own position as a number), a following < call. These shifts are smaller than main's, which moved the edited tween to the end of the script.

Item 1: one writer for an edit at the playhead

A move, nudge, resize (size or scale) or rotate on a property a GSAP tween animates now follows one rule: on a keyframe, change it; between two keyframes, add one at the playhead; outside the tween, extend it to the playhead and keep its authored ends. Other keyframes keep their values and times. Values come from the file's tween, or from GSAP's own parse of it where the tween leaves an end implicit (a from() end, a to() start, a CSS translate GSAP folds into x), never from the dragged element. GSAP parses a to() tween only once the playhead has passed its start, and every soft reload resets that; such a tween's timeline is played from the tween's start through its end and back, with the tween's channels cleared, before it is read. Starting at the tween's start lets an earlier tween on the same layer set its start value, as playback does. The gesture's live value is never read as its start, and afterwards the timeline's time and every layer's live value are put back. The edit is refused with "Edit this animation in the Code tab" only if that parse still leaves the end unknown.

Replaced writers: the size keyframe tween a resize used to add beside the existing one, the convert-then-add-keyframe and outside-range rewrites in the move, resize and rotate paths.

Root fixes on the way:

  • An edit at the playhead rewrites its tween where it stands: the call keeps its place in the script and its position argument. The server used to remove the tween and append a new one at the end of the script, so any later tween placed relative to the sequence (no position, a chained .to(), <, >, +=, a label) moved.
  • Studio's write-side parse now reads addLabel, so a tween placed at a label gets its real start there too. It read 0, which also misplaced the hold set kept before a first position keyframe.
  • A rotate on a tween that turns another axis (rotationY, rotationX) holds the new angle across the whole tween, instead of turning from 0 at the tween's start. The writer now refuses a value that no keyframe carries and nothing holds, so no caller can leave a channel un-held.
  • A group drag or nudge plans every member before the first write, so one member's refusal moves none.
  • A move could write x/y into a tween that only animates size: the position finder fell back to any tween. It now returns only tweens that write position.
  • Where two tweens meet at the playhead, the edit goes to the one whose keyframe the file states there (the later one if both do), not the one listed first.
  • A drag no longer merges two real position tweens that run one after another; only holds can conflict.
  • A rotate edits the rotation tween at the playhead, not the first one in the file.
  • A tween shared by several elements is refused instead of moving all of them.
  • A flat edit matches only a tween that carries its own channels; a keyframed tween at the same element and start no longer passes its ease or duration to it.
  • A tween given a position when its call has none gets it after its last argument, so a call written with a trailing comma stays valid.
  • A tween with a delay is matched at its real start, and the edit is written so GSAP shows the new value at the playhead, not delay later.
  • A tween with no authored ease keeps GSAP's default ease, power1.out, by name. GSAP holds a resolved ease as a function, which used to read as unknown and refuse the edit. A built-in ease is named the same way; a custom ease function is still refused.
  • A tween with repeat, yoyo, repeatDelay, snap, overwrite or immediateRender is edited as on main: the keyframes are written and those settings are kept.
  • Every keyframe tween Studio re-writes carries the source's ease as easeEach. A tween-level ease does not ease percentage keyframes, so a linear tween re-written that way eased each segment power1.inOut and drifted off its path mid-segment. This covers the extend-to-playhead path, and the auto-keyframe-off whole-path and whole-property shifts when the tween authors its ease.
  • An array of keyframe steps keeps GSAP's exact step times (the parse rounds them to 0.1%).
  • Behaviour change for review: resolveGroupTween no longer splits a tween that mixes several property groups into one tween per group. It returns the mixed tween and the edit goes into it in place (the writer leaves its other channels alone). Why: inside one gesture the writes are buffered, so the split's re-read saw the pre-split file, and later writes targeted ids the split had already removed and were appended beside the old tweens as duplicates. A resize whose tween also animates position now writes the size and the anchor move in that one write.

Item 2: undo pressed while an edit saves undoes that edit

Built on the pending-edit registry: an undo pressed while an edit is still saving waits for that edit and undoes it, not an edit started after the key.

  • An undo of a move whose save failed stays undone and does not step back an older edit.
  • The host API revertNewestStudioPendingEdit keeps returning the function that shows the edit again.
  • A style set by its JavaScript name (backgroundColor, WebkitX) saves under its CSS name.
  • A second undo pressed while the same edit saves steps back to the edit before it, instead of reading "Can't undo".

Item 3: an edit stays saved when the history reply after it cannot be read

A history reply that is a 200 but not JSON used to reject inside the history refresh. The edit is on disk and in history either way; the refresh now logs "The history's reply was unreadable." and keeps the last history it read.

Item 4: style fields for hosts

ColorField and GradientField are exported, and a session style commit takes a map of properties.

Item 5: a host can move layers by exact film px

handleDomGroupMoveBy(moves) moves each selection by its delta in composition px, as one undo step, through the group drag's own path (for align and distribute in a host app). A member that cannot move refuses the call before anything is written. A group move or drag whose animated layers live in different files is refused too, since the group's save writes one file. Which file a layer writes to is decided by the same rule the save uses, so a layer that names the open file and one that leaves it implied count as one file. Each member is planned at its real drop before the first write.

Item 6: an outside change during a drag waits for the drop

Item 7: connections

  • One live-updates subscription per mount: an edit no longer reopens the events stream, and a thumbnail request that is cancelled frees its slot at once, rather than when its load settles.
  • studioApiFetch owns Studio's API requests (every call in the package moves to it, including the freeze-frame, loudness and peak-map requests that landed on main since), with a lint rule against a bare fetch. Requests carry no cookies, so Chrome pools them apart from the preview's media; paused videos could hold all six sockets a host gets and leave an undo waiting. The two telemetry calls go through it too: they post to PostHog's own host with the API key in the body, and a cross-origin request carries no cookies under the default either, so omitting credentials changes nothing for them.
  • An opt-out for hosts on same-origin cookies, documented in the studio-server docs.
  • CI runs the paused-video undo check as its own step beside the inline-text caret check, with ffmpeg installed for its fixture; its result file is uploaded with the caret screenshots.

Since the last review (707e3d6)

  • An unplayed keyframe tween clears the channels of every keyframe before it is read, so a channel that first appears in a later keyframe keeps its authored start instead of the drag's live value (5518787).
  • Reading a tween the playhead has not reached now puts back each element's whole state afterwards: every attribute (style, an SVG layer's transform, an attr tween's value) and GSAP's cache, including the depth origin. Targets that are not styled elements (the runtime's own {} duration filler, an XML-namespace element) are skipped; before, the filler made the read throw. The filter is one shared helper, elementTargets, in utils/elementGsap.ts.
  • Merged main again; the only conflict was the timeline delete, which takes main's one-request delete and keeps studioApiFetch.

Earlier rounds (358a94b)

  • Merged main. Main's feat(studio): track completed preview editing gestures #4916 moved the group drag into its own hook (useGsapAwareGroupMove) and added keyframe usage tracking. This PR's group changes (no refusal toast for host calls, planning each member at its real drop, the one-file rule) now live in that hook. An edit at the playhead reports a keyframe add only when it adds one, as main's replaced writers did.
  • Review fixes: reading a tween the playhead has not reached takes its start from the earlier tween on the same layer, as playback does, and puts back every layer's live drag afterwards; the one-file rule compares the files the save actually writes; the motion-path drag reports its keyframe add again, and an inserted start hold counts as an add.
  • The fixes for the review round on 358a94b (delay, default ease, looping tweens, trailing comma, second undo, flat/keyframed match, cross-file group, preflight offset) are listed in the sections above.

Test plan

  • Writer: 16 tests in gsapValueAtPlayhead.test.ts; the 10 that go through the move, resize and rotate entry points fail on the base code (from() edit, to() edit before its first keyframe with a folded translate, refusal on an unparsed tween, parsed end beats the dragged value, array steps, no x/y into a size tween, two tweens meeting at the playhead, rotation at a boundary, size and scale resize).
  • easeEach: four tests fail on the base code (whole-property shift of a linear flat tween and of a keyframed tween, auto-keyframe-off drag, extend-to-playhead).
  • Item 3: the new history test fails on the base code.
  • Duplicate tweens: a resize with buffered writes on a tween mixing x and width makes one write on the file's own id.
  • Paused videos: 8 paused videos, undo socket wait limit 250 ms: before 7/7 runs fail (waits up to 10.5 s), after 8/8 pass (0 ms).
  • Lint and typecheck pass on this head; the unit suites run in CI.
  • In-place rewrite: 9 route tests (no position, < with +=, >, a label, a from() call, a chained .to().to(), and the edited tween's own <, label and number position). Each compares every other call's source text and resolved start before and after. All 9 fail on the base route code.
  • Rotation hold: fails with either half of the fix reverted.
  • Group plan: a 3-member group whose second member refuses writes nothing.
  • Review fixes, each failing on the code before its fix: a to() tween the playhead has not reached, in real GSAP with a dragged value set (reads its authored start, keeps the live value and the playhead); a flat edit next to a keyframed tween at the same start; a trailing-comma call given a position (the old output does not parse); two undo presses started together during one save (they undo both edits: each press picks its edit at the press, and in turn skips it if another press already undid it; a failed undo changes nothing, so the next press targets that edit again; and a press whose edit was already undone, with a newer edit made since the press, says "Can't undo" as main does instead of undoing that newer edit). The abort slot release and the shared-tween refusal each fail when their line is removed.
  • Second review round, real GSAP playing the written file: a delayed tween with no ease and one with ease: "none" show the dragged value at the playhead (the old code refused, then showed 150 for 200); a tween with no ease writes easeEach: "power1.out"; expo.in is named and a custom function refused; a repeat: 1, yoyo: true tween is written and keeps both. A group with members in two files writes nothing, and the group preflight plans at the member's real drop. Each fails with its fix removed.
  • Since 707e3d6, real GSAP: a channel first appearing in a later keyframe saves its authored start (the written file replays 77 at the edit, 0 at the tween's start, 300 at its end; the first-keyframe-only read fails two tests); a sibling's live attr value survives a read beside the runtime's {} filler (fails on 13d7b8d with a TypeError, and with a style-only restore as 50 for 77); the shared target filter drops a plain object and an XML-namespace element. The undo and GSAP test files pass three runs in a row (413 tests).
  • Earlier round, real GSAP: reading a tween the playhead has not reached leaves a sibling's live value and the timeline's time as they were (an earlier version moved the sibling from 77 to 50), and a later tween on the same layer reads its start from the earlier one (25, not 0) and plays the same afterwards. The motion-path drag and an inserted start hold each report a keyframe add. A group whose members name the open file or leave it implied is written (comparing raw names refused it). Main's keyframe usage tests pass against the new writer.

Known follow-ups

  • Editing a from()/fromTo() entrance at the playhead rewrites it as a keyframed to(), so the layer shows its end state before the entrance starts (pre-existing on main).
  • An undo pressed while two edits are saving should target the painted-back edit's own claim id; it takes the newest claim after the key, as on main.
  • The first undo after the first edit on a media-heavy project can restore nothing, because the project's history opens lazily. A fix is ready for the next PR.
  • A keyframes array with different channels per step is rewritten as percentage keyframes without holding each channel, so a channel that first appears in a later step animates from 0%. A step's delay is copied into a keyframe's properties. Older than this PR (add-keyframe's array conversion does the same).
  • Two keyframed tweens on the same element and start can still take each other's step times: the match checks only that both are keyframed. Rare.
  • A whole-path or whole-property shift on a tween with no authored ease writes no easeEach, so its segments ease power1.inOut rather than GSAP's default power1.out. No worse than main.
  • Both GSAP parsers advance the sequence without a tween's delay, so a call with no position after a delayed tween is placed delay too early. Next PR.
  • Reading a tween the playhead has not reached sets it up early, as main's drag did (gsapDragPositionCommit.ts:197-198 on main: clear the dragged channels, seek to the tween's start). Pre-existing on main, not introduced here. Two cases still differ from plain playback, both as on main: a later tween with overwrite: "auto" takes its property from the earlier tween before playback reaches it, and a starting value set outside the timeline (a gsap.set in the script) is read as 0 because the tween's channels are cleared first. The fix is to read the start without setting the tween up, and to clear only a live gesture's channels. Next PR, each case with a test that fails on main first.
  • An unplayed-tween read restores the attributes of styled elements only, so an attr value drafted by hand on an XML-namespace element (not something Studio writes) comes back at its timeline value. Fixed in the next head, which merges main again for the core seek fix.
  • A plain object tweened in the timeline gets its timeline value back after an unplayed-tween read; a value assigned to it outside the timeline is not kept. Studio writes none.
  • A tween whose earlier tween on the same layer has already ended: its start should be read after seeking to 0 and then to its start. And an edit to a repeat: -1 loop stretches every loop. Both noted in review; next PR.
  • Undo presses that overlap while saves are running still have two rare orderings, both refused rather than mis-applied: a press waiting on one edit's files and a plain step back on other files can run in either order, and the second gets "Can't undo" (main undoes both there); and a press held in a queue through more than 100 other undos can forget its edit was already undone and get "Can't undo". A change made outside the app while presses are queued can still be the one a step back undoes. The root fix is for the history server to pick the edit to undo, with each press carrying the point it was pressed at. Next PR after the reader fixes.
  • Several tests are slow or time-sensitive under a loaded runner and should be fixed rather than rerun: two studio-server tests whose first case pays a cold module load near the 5 s limit, a fixed half-second wait between rounds in the paused-video undo walk, a clipboard test that failed once in a full run, and the timeline virtualization and property panel tests on a heavily loaded machine. Next PR.
  • About 50 call sites repeat "a layer's file, else the open file, else index.html". The GSAP writers now share one helper; the rest should move to it.
  • If a project's history exceeds its 2 GB budget, pruning could drop a media file copied in the background before it is recorded.

Before

Main at b5e3ffa, Studio on generated fixture projects, headless Chrome.

  1. Editing a GSAP tween at the playhead: a box on a linear x tween (5 s to 10 s, ease: "none") is Alt-dragged 200 px at 8 s, which shifts its whole path. The saved keyframes have no easeEach, so each segment eases power1.inOut. At 6.17 s the box is 174 px behind where the linear path puts it (dashed outline). The dots, sampled every 0.25 s, bunch up at the ends.

Before: GSAP path after an Alt-drag

  1. Undo while an older edit is still saving: every file save is held 5 s in the browser (the same on both sides) so the steps happen at human speed. Move A, press Ctrl+Z once A's save is on the wire, then move B. The undo steps back B, the newer edit, and the screen puts A back at its start while the file keeps A's move (translate: 300px 0px).
before-undo-while-saving.mp4
  1. Undo with 8 paused videos in the preview: the undo request waited 3067 ms for a connection before it was sent, and the next edit did not save within 30 s.
before-undo-paused-videos.mp4

After

This PR at cf082c4, with the same fixtures and steps.

  1. The same Alt-drag saves easeEach: "none". At 6.17 s the box sits on the linear path (0 px off) and the dots are evenly spaced.

After: GSAP path after the same Alt-drag

  1. The same sequence shows "Can't undo: index.html changed since that edit." and puts the screen back, and both moves stay in the file.
after-undo-while-saving.mp4
  1. The same undo waited 0 ms for a connection and was answered in 161 ms. Over three rounds the waits were 0, 0 and 1 ms.
after-undo-paused-videos.mp4

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Edit accuracy: accurate 1301 (base branch 1217), smooth 1141 of those

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

Newly passing (84)

  • nudge-fromto-px-r0-nested-z100-mid
  • nudge-fromto-px-r30-nested-z100-mid
  • move-fromto-px-r0-nested-z200-mid
  • move-fromto-px-r0-nested-z50-mid
  • move-fromto-px-r30-nested-z200-mid
  • move-fromto-px-r30-nested-z50-mid
  • move-keys-px-r0-nested-z200-mid
  • move-keys-px-r0-nested-z50-mid
  • move-keys-px-r30-nested-z200-mid
  • move-keys-px-r30-nested-z50-mid
  • resize-keys-px-r0-nested-z200-on
  • resize-keys-px-r0-nested-z50-on
  • resize-keys-px-r30-nested-z200-on
  • resize-keys-px-r30-nested-z50-on
  • resize-scale-px-r0-nested-z200-on
  • resize-scale-px-r0-nested-z50-on
  • resize-scale-px-r30-nested-z200-on
  • resize-scale-px-r30-nested-z50-on
  • resize-size-px-r0-nested-z200-on
  • resize-size-px-r0-nested-z50-on
  • resize-size-px-r30-nested-z200-on
  • resize-size-px-r30-nested-z50-on
  • resize-fromto-px-r0-nested-z200-mid
  • resize-fromto-px-r0-nested-z50-mid
  • resize-fromto-px-r30-nested-z200-mid
  • resize-fromto-px-r30-nested-z50-mid
  • resize-keys-px-r0-nested-z200-mid
  • resize-keys-px-r0-nested-z50-mid
  • resize-keys-px-r30-nested-z200-mid
  • resize-keys-px-r30-nested-z50-mid
  • ...

Quarantined, measured but not gated (0)

@miguel-heygen
miguel-heygen force-pushed the fix/studio-playhead-writer-carrier branch from ba48107 to ac7e288 Compare October 2, 2026 06:16
@mintlify

mintlify Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

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

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Oct 3, 2026, 6:36 PM

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

@miguel-heygen miguel-heygen changed the title fix(studio): edits on GSAP-animated layers write their value at the playhead fix(studio): playhead keyframe edits keep their ease, undo names its edit and never waits on videos Oct 2, 2026
@miguel-heygen
miguel-heygen force-pushed the fix/studio-playhead-writer-carrier branch from ac7e288 to cf082c4 Compare October 2, 2026 07:51
miguel-heygen added a commit that referenced this pull request Oct 2, 2026
miguel-heygen added a commit that referenced this pull request Oct 2, 2026
…keyframes (#4866)

* test(studio): score edits on GSAP-animated size, scale, rotation, keyframes and from/fromTo

* test(studio): settle the edit bench only once no shadow preview is waiting to be swapped in

* test(studio): end a drag at its recorded pointer-up, even when no frame painted while held

* ci(studio): run the keyframed edit accuracy cases in CI across 20 shards

* refactor(studio): split the keyframe render and stray-css checks into small helpers

* test(studio): bank the edit bench's smooth verdict as smooth, judged on the worst frame's work

* style(studio): name the edit gate's smooth count helper

* test(studio): count smooth edit cases only among the accurate ones

* test(studio): only keyframed cases wait for the preview swap, so others keep main's undo timing

* refactor(studio): keep the edit bench's keyframe check apart from the hidden preview scan

* test(studio): check the keyframed cases are in the full grid without pinning its size

* test(studio): quarantine the nudge sequence whose redo waits behind preview videos (#4889)

* test(studio): bank the 255 keyframed edit cases that pass
@miguel-heygen
miguel-heygen force-pushed the fix/studio-playhead-writer-carrier branch from cf082c4 to 5b5a027 Compare October 2, 2026 10:30
handleDomGroupMoveBy(moves) on useDomEditSession and DomEditValue moves each
selection by its delta in composition px, for Desktop's align and distribute.

It reuses the group drag's own path: a drag member per element (press-time
route, measured offset map), the delta carried to screen px through the
preview frames, then handleGsapAwareGroupPathOffsetCommit, so one shared
coalesce key gives one undo entry and GSAP members take the GSAP write.
A member that can't move refuses the call before anything is written; the
refusal rejects with its reason and shows no Studio toast (the group commit
takes refusalToast: false; telemetry stays). Zero deltas are skipped.

Tests: a pre-offset element and one under a half-scale parent move by exactly
{10,0} and {0,-5} under one group key; a half-scale parent with a 30 deg
rotated ancestor lands within the 0.001 px a translate is written with; a
GSAP member goes through the GSAP write; three refusal kinds write nothing,
leave the boxes where they were, show no toast and leave no gesture mark,
while a canvas group drag still toasts; the session hands the call to the
group commit with refusalToast: false. Each mutation (no frame mapping, no
zero skip, no restore, no teardown, no capability check, toast on refusal)
fails at least one of them.
While a gesture mark is live in the preview, refreshPlayer takes the shadow load instead of
swapping scenes in place. A swap replaced the node under the pointer mid-drag; the shadow load
is then held by the existing gesture hold and, if the drop saved, reloaded fresh by the stale
rule, so this adds no second "released" signal.

Test: an outside change during a live drag on a preview that can swap scenes leaves the dragged
node in place, shows nothing under the pointer, and after the drop loads fresh with the dropped
position. It fails on the parent without the condition ("swapped under the pointer").

Cost case, measured in a real headless preview on the edit-accuracy bench's per-frame teleport
check. Every GSAP instant patch was forced to miss and fall back to reloadPreview during its own
commit; move and rotate of a held element (gsap.set and tl.set holds), px and xPercent
placement, r0 and r30, root and nested, at 100%:
- The gesture mark was live when the fallback asked for the reload, but had always ended by the
  time refreshPlayer ran (refreshKey is consumed in an effect after the commit settles). So the
  new condition never fired there: root previews offer no scene swap and load shadow on both
  builds, and nested previews swap on both builds.
- Worst post-release frame offset, 32 cases on this build and 32 on the parent: 0.00 px over
  76-106 frames each (release to settle). No frame was off by more than 0.5 px.
- Sensitivity: a 3 px nudge injected 50 ms after the fallback reads back as 3.00 px.
@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Re: tai's review at 375d1ca (resize-fromto-px-r0-root-z50-on, ~90 px after pointer-up).

Agreed on the cause, and your forward/backward replay of the saved HTML matches mine: main's backward rerender (#4911) at the from() -> keyframed to() junction at t = 2. The fix is in the runtime, where every composition's seek goes through: #4942 replaces the backward step with one forced render at t, which keeps #4911's same-time set order (its step tests stay green) and renders a tween that starts exactly at t.

Dry run of this PR's next head (this branch + current main + #4942), on the bench:

When #4942 merges I merge main here, push that test with the regression pinned through the built save and reload path for drop and reload, run it three times, and ask you to re-review with the numbers from this branch's own CI run.

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Re: tai's review at 375d1ca, with the numbers from this head.

At 93a9d43 (main merged, including #4942's core seek fix), from this PR's own CI edit-accuracy run (37144840600):

  • resize-fromto-px-r0-root-z50-on: drop 0, reload 0.064 px, teleport 0.24 px (was 90.06 / 90.02 / 90.08).
  • all 36 (move|nudge|resize)-fromto-*-on cases: drop, reload and teleport pass; max drop 0, max reload 0.09 px. They still fail keys, as on main.
  • the gate: 1301 accurate (base 1217), 84 newly passing, 0 regressed.

Regression through the built runtime, in this head: gsapValueAtPlayhead.browser.test.ts saves a drag on the fromTo start with the real planner and writer, loads the written file in Chrome with the built runtime and seeks through __player.seek(2): x = 90; with #4942's two core files rolled back it reads 0. I also have its save-and-reload form ready (the same drag checked after Studio's soft reload in the live page, then after a fresh load): green three runs in a row, and red without #4942 (0 for 90). It goes up as a follow-up commit so this head's green run stands.

@miguel-heygen
miguel-heygen merged commit 7dea1e5 into main Oct 3, 2026
83 checks passed
@miguel-heygen
miguel-heygen deleted the fix/studio-playhead-writer-carrier branch October 3, 2026 20:11
@github-actions github-actions Bot mentioned this pull request Oct 3, 2026

This branch was successfully deployed

1 active deployment
staging - docs — 93a9d437 Deployed Oct 3, 2026 by mintlify[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.

4 participants