Skip to content

feat(studio): let an agent author motion - #3520

Draft
miguel-heygen wants to merge 1 commit into
feat/studio-webmcp-transformfrom
feat/studio-webmcp-animation
Draft

feat(studio): let an agent author motion#3520
miguel-heygen wants to merge 1 commit into
feat/studio-webmcp-transformfrom
feat/studio-webmcp-animation

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

Four tools that author motion: studio_add_animation, studio_update_animation, studio_add_keyframe, studio_delete_animation.

Stacked on #3519. This completes the tool set; only the docs page and the browser proof remain.

Why

Motion is the one thing an agent cannot judge or author from source. Reading GSAP code tells you the tween exists; it does not tell you the title lands a beat late or the ease feels mushy. Paired with studio_seek and studio_frame from earlier in the stack, these close that loop.

How, and why these are less confident than the rest

The handlers underneath mostly cannot report back:

Handler Signal
handleGsapAddAnimation(method) none. Position comes from the live playhead, and the call is void ...catch()
handleGsapAddKeyframeBatch returns a promise but catches its own failure
handleGsapDeleteAnimation discards its promise entirely
handleGsapUpdateMeta returns a boolean. The one honest one

#3519 solved the same problem by reading the result back. That does not work here: the animation list comes from React state that only refreshes on a render, and no render happens inside one tool call.

So rather than fake a verification with a frame-timer, these report what was dispatched, and every description tells the agent to call studio_inspect to see the result. Saying "I asked for this" is honest; saying "this happened" would not be.

Three consequences worth stating:

studio_add_animation takes no position. The handler reads the playhead itself, so accepting one would report a number that had no effect. It reports where the playhead actually was and tells the agent to studio_seek first.

studio_update_animation rules out the no-selection case before dispatch. The handler answers false for both "nothing selected" and "the write failed". Eliminating one beforehand is what makes the other legible, and it is why a false from this tool is reported as a real failure with a stale-id hint.

Keyframe percent and properties are validated in the tool, because nothing in the platform checks input against the declared inputSchema.

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)

15 tests in animationTools.test.ts:

  • Add reports the playhead's actual value, proven with a playhead at 7.25 while the caller supplies nothing.
  • Add is marked dispatched, not claimed as landed.
  • Update confirms via the boolean, and a false becomes a real failure with a stale-id hint.
  • The no-selection case is asserted to be caught before dispatch, so the ambiguous false can never be reached that way.
  • Keyframe properties go through in one call, so one undo entry.
  • Percent is rejected at -1, 101, NaN and "50"; properties rejected for {}, {y: null}, [] and a string; 0 and 100 accepted as the ends.
  • Blocked writes and unknown methods never reach a handler.

Full package suite 4602 passing across 414 files. bunx tsc --noEmit, bunx oxlint and bunx fallow audit --fail-on-issues all clean.

Still owed

Nothing in this stack has been exercised in a real browser. That is the last unit, and it needs chrome://flags/#enable-webmcp-testing.

Four tools: add an animation, change its duration/ease/position, add a
keyframe, delete it. This is the capability that makes the tool set worth
having, because motion is the one thing an agent cannot judge or author from
source.

These are deliberately less confident than the rest of the set, and the
reason is the handlers underneath them:

`handleGsapAddAnimation(method)` takes only a method. Its insert position
comes from the live playhead, not the caller, and the call is `void ...catch()`
so it returns nothing.

`handleGsapAddKeyframeBatch` returns a promise but catches its own failure, so
awaiting proves the call finished, not that it landed.

`handleGsapDeleteAnimation` discards its promise entirely.

`handleGsapUpdateMeta` is the one honest signal. It returns a boolean.

U8 handled the same problem by reading the result back. That does not work
here: the animation list comes from React state that only refreshes on a
render, and no render happens inside one tool call. Rather than fake a
verification with a frame-timer, these report what was DISPATCHED and the
descriptions tell the agent to call studio_inspect to see the result. Saying
"I asked for this" is honest; saying "this happened" would not be.

Three consequences worth stating:

`studio_add_animation` takes no position. The handler reads the playhead, so
accepting one would report a number that had no effect. It reports where the
playhead actually was and tells the agent to seek first.

`studio_update_animation` rules out the no-selection case BEFORE dispatch. The
handler answers `false` for both "nothing selected" and "the write failed", so
eliminating one is what makes the other legible.

Keyframe percent and properties are validated in the tool, because nothing in
the platform checks input against the declared schema.
@miguel-heygen
miguel-heygen force-pushed the feat/studio-webmcp-transform branch from 96cebf2 to b4123be Compare August 27, 2026 04:48
@miguel-heygen
miguel-heygen force-pushed the feat/studio-webmcp-animation branch from 721c271 to 90cef4f Compare August 27, 2026 04:49
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