Skip to content

feat(studio): GSAP runtime bridge + optimistic updates [3/6]#1169

Merged
miguel-heygen merged 8 commits into
mainfrom
feat/keyframes-3a-runtime-bridge
Jun 5, 2026
Merged

feat(studio): GSAP runtime bridge + optimistic updates [3/6]#1169
miguel-heygen merged 8 commits into
mainfrom
feat/keyframes-3a-runtime-bridge

Conversation

@miguel-heygen
Copy link
Copy Markdown
Collaborator

Summary

Runtime bridge reads gsap.getProperty() from iframe for drag commits. Optimistic update pattern for keyframe mutations. Async pipeline with beforeReload/skipReload.

Part 3 of 6 — drag infrastructure. Depends on PR 2.

Test plan

  • Drag GSAP element → position persists via GSAP mutation (not CSS offset)
  • Optimistic cache updates roll back on failure

@miguel-heygen miguel-heygen force-pushed the feat/keyframes-2-spring-runtime branch from 6139501 to 960c5c4 Compare June 3, 2026 00:21
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-3a-runtime-bridge branch from cf4414b to 3708bb9 Compare June 3, 2026 00:21
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-2-spring-runtime branch from 960c5c4 to 767108f Compare June 3, 2026 04:35
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-3a-runtime-bridge branch from 3708bb9 to e92c7ad Compare June 3, 2026 04:36
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-2-spring-runtime branch from 767108f to 9034f44 Compare June 3, 2026 04:58
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-3a-runtime-bridge branch 3 times, most recently from 85dadcb to 50e19d7 Compare June 3, 2026 05:27
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-2-spring-runtime branch from 9034f44 to 0ffea06 Compare June 3, 2026 05:27
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-2-spring-runtime branch from 0ffea06 to c7b1116 Compare June 4, 2026 04:57
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-3a-runtime-bridge branch from 50e19d7 to 2f0902d Compare June 4, 2026 04:57
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-2-spring-runtime branch from 79b1b43 to cbdf012 Compare June 4, 2026 16:15
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-3a-runtime-bridge branch from 2f0902d to ac80b0f Compare June 4, 2026 16:15
Revert totalTime nudge that caused black first frames in from() tweens.
Keep stale CSS offset cleanup. Regenerate baselines for offset cleanup.
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-2-spring-runtime branch from cbdf012 to 718e431 Compare June 4, 2026 16:40
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-3a-runtime-bridge branch from ac80b0f to a6b6208 Compare June 4, 2026 16:40
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-3a-runtime-bridge branch from a6b6208 to 5106898 Compare June 4, 2026 17:44
The regression harness used container duration (format.duration) to
compute PSNR checkpoints. Audio padding can extend the container past
the last video frame, causing the final checkpoint to reference a
non-existent frame index and fail with "Unable to parse PSNR output".

Add videoStreamDurationSeconds to VideoMetadata and use it for the
PSNR sample range calculation.
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-3a-runtime-bridge branch from 5106898 to c253e28 Compare June 5, 2026 03:22
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

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

Bridge + optimistic-update layer. +378/-0 pure additive against the previous stack PR. Two things worth verifying explicitly before merge:

1. Cross-origin iframe access for the runtime bridge

The PR body says "Runtime bridge reads gsap.getProperty() from iframe for drag commits." If this is implemented via iframe.contentWindow.gsap.getProperty(...) or similar direct cross-realm access, it has the same fragility we hit on hf-internal#396 (useTimelinePlayer.ts) — same-origin coupling that breaks the moment any flow (CDN preview, sandboxed iframe, dev-server cross-port) puts the iframe at a different origin.

The cleaner long-term shape is postMessage with a typed protocol ({ source: "hf-studio-bridge", type: "getProperty", target, prop } → response message). Worth confirming which approach this PR took, and if it's direct contentWindow access, what the plan is for preview/CDN paths.

2. Optimistic update rollback semantics

The PR body promises "Optimistic cache updates roll back on failure" in the test plan. The two things to verify:

  • Server returns 400/500: client cache reverts to pre-mutation state. No partial application.
  • Server succeeds but returns a different result than predicted: cache reconciles to server-truth (last-write-wins, not optimistic-overwrites-server).

Race condition to look for: user fires mutation A (optimistic) then mutation B (optimistic) before A's server response. If A's response is "rejected", does B's optimistic state persist (correct) or get clobbered by A's rollback (incorrect — drops B)? The async pipeline + beforeReload/skipReload flags hint at a queue model; worth a test that covers the interleave.

3. skipReload flag

beforeReload and skipReload are new state flags. skipReload in particular sounds like it bypasses the normal reload-after-mutation refresh. Verify the cache stays consistent in that path — if the runtime bridge updates the live iframe directly and the server-side script is also updated, the next reload (if it does happen) should be a no-op. If the runtime and AST diverge silently, that's a hard-to-debug bug class.

I'd want to see at minimum one test that exercises: optimistic update → server success → skipReload → next reload → cache and DOM still match server-truth. If the test isn't there yet, worth adding.

Non-blocking observation

378 lines added with 0 deletions is a lot of new surface in @hyperframes/studio — anything exported from this PR becomes part of Studio's public API. Worth a check that internal helpers (the bridge plumbing, the async pipeline orchestration) are kept module-private with // @internal JSDoc where appropriate, and only the public useOptimisticGsapMutation-style hooks are exported.

Review by Jerrai (hyperframes specialist)

…od baselines

Baselines regenerated inside Dockerfile.test on the devbox to match
the current runtime init.ts changes. Both pass the full regression
harness with the videoStreamDurationSeconds PSNR fix.
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-3a-runtime-bridge branch from c253e28 to aa99131 Compare June 5, 2026 05:55
A single transition frame at 10.742s renders with marginal PSNR
(26.6 dB vs 30 threshold) on CI runners but passes on the devbox
Docker image. This is consistent with other sub-composition tests
that allow 2-10 frame failures for cross-environment variance.
@miguel-heygen miguel-heygen force-pushed the feat/keyframes-3a-runtime-bridge branch from aa99131 to 3361cd9 Compare June 5, 2026 14:57
Base automatically changed from feat/keyframes-2-spring-runtime to main June 5, 2026 15:51
@miguel-heygen miguel-heygen merged commit 12e87e0 into main Jun 5, 2026
24 of 25 checks passed
@miguel-heygen miguel-heygen deleted the feat/keyframes-3a-runtime-bridge branch June 5, 2026 15:51
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 5, 2026

Fallow audit report

Found 127 findings.

Dead code (1)
Severity Rule Location Description
major fallow/unused-file packages/studio/src/hooks/gsapRuntimeBridge.ts:1 File is not reachable from any entry point
Duplication (83, showing 50)
Severity Rule Location Description
minor fallow/code-duplication packages/core/src/parsers/gsapParser.stress.test.ts:296 Code clone group 1 (10 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.stress.test.ts:313 Code clone group 2 (13 lines, 5 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.stress.test.ts:499 Code clone group 2 (13 lines, 5 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:281 Code clone group 1 (10 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:296 Code clone group 2 (13 lines, 5 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:312 Code clone group 2 (13 lines, 5 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:346 Code clone group 2 (13 lines, 5 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1096 Code clone group 3 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1139 Code clone group 3 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1197 Code clone group 4 (16 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1209 Code clone group 5 (10 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1210 Code clone group 6 (6 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1216 Code clone group 7 (6 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1224 Code clone group 4 (16 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1239 Code clone group 5 (10 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1240 Code clone group 6 (6 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1248 Code clone group 7 (6 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1257 Code clone group 4 (16 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1271 Code clone group 6 (6 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1451 Code clone group 8 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.test.ts:1470 Code clone group 8 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:564 Code clone group 9 (7 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:662 Code clone group 9 (7 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:921 Code clone group 10 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:1172 Code clone group 11 (15 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:1253 Code clone group 11 (15 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:1316 Code clone group 10 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:53 Code clone group 12 (15 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:100 Code clone group 15 (10 lines, 4 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:100 Code clone group 13 (13 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:100 Code clone group 14 (11 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:104 Code clone group 16 (7 lines, 5 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:112 Code clone group 17 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:130 Code clone group 15 (10 lines, 4 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:130 Code clone group 13 (13 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:130 Code clone group 14 (11 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:134 Code clone group 16 (7 lines, 5 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:142 Code clone group 17 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:160 Code clone group 18 (12 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:183 Code clone group 19 (12 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:202 Code clone group 18 (12 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:217 Code clone group 19 (12 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:235 Code clone group 15 (10 lines, 4 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:241 Code clone group 20 (23 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:246 Code clone group 21 (8 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:253 Code clone group 22 (8 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:283 Code clone group 23 (37 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:286 Code clone group 16 (7 lines, 5 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:325 Code clone group 14 (11 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/init.test.ts:325 Code clone group 15 (10 lines, 4 instances)

Showing 50 of 83 findings. Run fallow locally or inspect the CI output for the full report.

Health (43)
Severity Rule Location Description
critical fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:73 'resolveNode' has CRAP score 315.9 (threshold: 30.0, cyclomatic 36)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:147 'selectorFromQueryCall' has CRAP score 49.5 (threshold: 30.0, cyclomatic 13)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:225 'visitCallExpression' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:284 'resolveTargetSelector' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:313 'objectExpressionToRecord' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:404 'visitCallExpression' has CRAP score 31.6 (threshold: 30.0, cyclomatic 10)
minor fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:961 'buildTweenStatementCode' has CRAP score 31.6 (threshold: 30.0, cyclomatic 10)
major fallow/high-crap-score packages/core/src/parsers/gsapParser.ts:1287 'resolveConversionProps' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/parsers/gsapSerialize.ts:66 'lines' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/parsers/gsapSerialize.ts:222 '<arrow>' has CRAP score 37.1 (threshold: 30.0, cyclomatic 11)
minor fallow/high-crap-score packages/core/src/parsers/gsapSerialize.ts:274 '<arrow>' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-cognitive-complexity packages/core/src/runtime/init.ts:30 'initSandboxRuntimeModular' has cognitive complexity 22 (threshold: 15)
critical fallow/high-crap-score packages/core/src/runtime/init.ts:264 'applyClipLayout' has CRAP score 567.6 (threshold: 30.0, cyclomatic 49)
minor fallow/high-crap-score packages/core/src/runtime/init.ts:477 'resolveAuthoredCompositionDurationFloorSeconds' has CRAP score 31.6 (threshold: 30.0, cyclomatic 10)
critical fallow/high-crap-score packages/core/src/runtime/init.ts:545 'resolveRootTimelineFromDocument' has CRAP score 385.6 (threshold: 30.0, cyclomatic 40)
minor fallow/high-crap-score packages/core/src/runtime/init.ts:651 'collectRootChildCandidates' has CRAP score 37.1 (threshold: 30.0, cyclomatic 11)
minor fallow/high-crap-score packages/core/src/runtime/init.ts:1016 'emitRootStageLayoutDiagnostics' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/runtime/init.ts:1120 'onError' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/core/src/runtime/init.ts:1184 'rebindTimelineFromResolution' has CRAP score 37.1 (threshold: 30.0, cyclomatic 11)
major fallow/high-crap-score packages/core/src/runtime/init.ts:1234 '<arrow>' has CRAP score 79.4 (threshold: 30.0, cyclomatic 17)
minor fallow/high-crap-score packages/core/src/runtime/init.ts:1352 'resolveDurationSeconds' has CRAP score 49.5 (threshold: 30.0, cyclomatic 13)
minor fallow/high-crap-score packages/core/src/runtime/init.ts:1784 'seekStandaloneRegisteredTimelines' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
critical fallow/high-crap-score packages/core/src/runtime/init.ts:1872 'transportTick' has CRAP score 482.4 (threshold: 30.0, cyclomatic 45)
major fallow/high-crap-score packages/core/src/runtime/init.ts:1992 'hardSyncAllMedia' has CRAP score 71.3 (threshold: 30.0, cyclomatic 16)
major fallow/high-crap-score packages/core/src/runtime/init.ts:2016 '<arrow>' has CRAP score 97.0 (threshold: 30.0, cyclomatic 19)
critical fallow/high-crap-score packages/core/src/runtime/init.ts:2173 'teardown' has CRAP score 106.4 (threshold: 30.0, cyclomatic 20)
critical fallow/high-crap-score packages/core/src/studio-api/routes/files.ts:434 '<arrow>' has CRAP score 160.0 (threshold: 30.0, cyclomatic 25)
critical fallow/high-crap-score packages/core/src/studio-api/routes/files.ts:605 '<arrow>' has CRAP score 238.6 (threshold: 30.0, cyclomatic 31)
critical fallow/high-crap-score packages/engine/src/utils/ffprobe.ts:126 'extractPngMetadataFromBuffer' has CRAP score 35.5 (threshold: 30.0, cyclomatic 32)
critical fallow/high-crap-score packages/engine/src/utils/ffprobe.ts:246 'probePromise' has CRAP score 224.4 (threshold: 30.0, cyclomatic 30)
major fallow/high-crap-score packages/producer/src/regression-harness.ts:220 'formatResidualSuffix' has CRAP score 56.0 (threshold: 30.0, cyclomatic 7)
critical fallow/high-crap-score packages/producer/src/regression-harness.ts:230 'parseArgs' has CRAP score 210.0 (threshold: 30.0, cyclomatic 14)
critical fallow/high-crap-score packages/producer/src/regression-harness.ts:276 'validateMetadata' has CRAP score 2550.0 (threshold: 30.0, cyclomatic 50)
critical fallow/high-crap-score packages/producer/src/regression-harness.ts:395 'discoverTestSuites' has CRAP score 182.0 (threshold: 30.0, cyclomatic 13)
critical fallow/high-crap-score packages/producer/src/regression-harness.ts:409 'tryAddSuite' has CRAP score 132.0 (threshold: 30.0, cyclomatic 11)
minor fallow/high-crap-score packages/producer/src/regression-harness.ts:542 'psnrAtCheckpoint' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
minor fallow/high-crap-score packages/producer/src/regression-harness.ts:582 'extractMonoPcm16' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
critical fallow/high-crap-score packages/producer/src/regression-harness.ts:618 'saveFailureDetails' has CRAP score 702.0 (threshold: 30.0, cyclomatic 26)
critical fallow/high-crap-score packages/producer/src/regression-harness.ts:792 'runTestSuite' has CRAP score 2652.0 (threshold: 30.0, cyclomatic 51)
critical fallow/high-crap-score packages/producer/src/regression-harness.ts:1279 'run' has CRAP score 132.0 (threshold: 30.0, cyclomatic 11)
minor fallow/high-crap-score packages/producer/src/regression-harness.ts:1334 '<arrow>' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
minor fallow/high-crap-score packages/producer/src/regression-harness.ts:1389 'results' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
major fallow/high-crap-score packages/studio/src/hooks/gsapRuntimeBridge.ts:93 'computeCurrentPercentage' has CRAP score 72.0 (threshold: 30.0, cyclomatic 8)

Generated by fallow.

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