Case studyPersonal · AI product01 / 06 in Lab
Keyline
Every edit, yours or Claude's, is one undo step.
Motion editor where your own Claude animates a live scene and you point at any frame to fix it.
- Role
- Solo · product, design, front end, back end, AI
- Timeline
- 2026 · 1 week
- Stack
- TypeScript · Next.js 16 · React 19 · Cloudflare Workers · Durable Objects · D1 + Drizzle · R2 · MCP SDK · Anthropic API · Workers AI (Clef)
- Status
- Live · free to use

Problem
AI can already generate an animation from a prompt. The hard part is the last 10%: "make this card a bit faster", "the bounce feels cheap". Words are a bad interface for timing and easing, and a chat can't be pointed at one element at 2.3 seconds, so people tune motion somewhere else and paste numbers back.
I wanted that loop to be the product: Claude builds, I watch, point at the exact frame and fix the feel by hand.
What I built
- 01
Your Claude, live
A 31-tool remote MCP server with OAuth 2.1: claude.ai or Claude Code edits the scene while you watch it change.
- 02
Point at a frame
Pause, select elements and pin a comment to that second; Claude claims it, fixes exactly that and resolves the thread.
- 03
Tune feel without words
Timing bars and an easing editor with spring sliders and a live loop; copy any move as CSS, Motion or SwiftUI.
- 04
References in, checked out
Drop images, a brand URL or a video; the browser breaks clips into moves, and a vision model checks the result against your checklist.









Architecture
System map · one request · guarantees
Humans and Claude share one write path.
A drag in the editor and a tool call from Claude become the same thing: a batch of ops, validated whole and committed by the project's Durable Object. Claude sees frames from the same renderer the editor and the export use.
- Code
- LLM
- Check
- Output
System map. Clients: Your Claude, Web editor, Video export. Cloudflare edge · deterministic: MCP server, Project's Durable Object, Chat runner, D1 · R2 · KV, Op validator, Frame renderer. AI: . LLM zone · edits only through ops: Clef-flash, Claude, Clef vision. Connections: Your Claude to MCP server (tool calls); Web editor to Project's Durable Object (ops · live); Web editor to Chat runner (chat); Web editor to Video export (every frame); MCP server to Project's Durable Object (ops); Chat runner to Project's Durable Object (ops); Project's Durable Object to Op validator (validate); Project's Durable Object to D1 · R2 · KV (snapshots); Chat runner to Clef-flash (message kind); Chat runner to Claude (turn + tools); Chat runner to Frame renderer (render_frame); Frame renderer to Claude (PNG frames); Frame renderer to Clef vision (frames).
One question, end to end
“Make the second card bounce in like this reference video.”
01Code
The browser turns the clip into moves, an easing guess and contact sheets
02LLM
Clef-flash reads the message as a revision: medium effort, Opus
03LLM
Claude reads the project overview, the reference sheets and the selected card
04Check
apply_ops is validated whole; a bad op comes back with its index and a valid example
05Code
The Durable Object commits one undo step and broadcasts it; the card moves live
06Check
A rendered strip, the design lint and the Clef checklist confirm the result
Every Claude edit is one undo step.
Atomic batches with exact inverses
One renderer for editor
The same pure renderToSvg
One write path for humans and AI.
Same validator
55 / 57 messages routed right.
Clef-flash eval · never under-picked effort
Decisions & trade-offs
Chose JSON ops on a scene graph over letting the model write animation code
because Ops can be validated, synced and undone exactly, and they render the same on server and client. Nothing the model writes needs a sandbox.
Chose the user's own Claude over MCP over hosting a model and selling credits
because Generating motion is a commodity and people already pay for Claude. Keyline's AI bill stays near zero, with no markup to explain.
Chose one SVG renderer everywhere over canvas, WebGL or a headless browser
because One pure function gives the same picture in the editor, the browser export and the Worker frames Claude checks. No render servers.
Chose a Durable Object per project over a CRDT
because One authority per scene keeps validation and undo simple. Undo runs on the server, so it works across tabs and reloads.
AI specifics
- Models
- claude-opus-5-5, claude-sonnet-5-5, Cloudflare Clef and Clef-flash, or your own Claude over MCP
- Routing
- Clef-flash sorts each chat message into question, tweak, revise or build
- Tool use
- 31 MCP tools, reused one to one as the in-app chat's function calls
- Self-checks
- Worker-rendered frames, a design lint and a Clef vision checklist against references
- Cost controls
- Per-turn requests, 1h prompt cache, capped tool results, monthly budgets
- Evals
- Clef routing 55 / 57, frame detail checks 38 / 40 on real Workers AI
What I'd do differently
I'd design the op format for the model first. My strict schemas made Claude fail whole batches, and errors pointed at expanded ops it never wrote. Now a lenient layer accepts names, aliases and defaults, then maps every error back to Claude's own op index with a valid example.
Results
Live and free with Google or GitHub sign-in. I built it solo in a week, with Claude Code as my coding partner. Clef routing picked the right model and effort for 55 of 57 eval messages. Every chat call is logged with its cost, which is how I found a $3.90 turn and cut it.
Next
Agent-made knobs: named sliders that drive many keyframes at once. Brand import for JavaScript-rendered sites. Lottie and GIF export, then open-sourcing the scene engine.