DEV Community

Cover image for I built a macOS app where your coding agent makes the video: Remocn Studio 1.0
Remocn
Remocn

Posted on

I built a macOS app where your coding agent makes the video: Remocn Studio 1.0

Back in August I wrote How to create videos using Claude Code. Near the end it described an early desktop app that put Claude Code's chat next to a Remotion player. That app is now Remocn Studio 1.0. It's free, MIT-licensed, and works with four coding agents instead of one. I launched it on October 1, and 1.0.1 has been out since October 4 for both Apple silicon and Intel Macs.

This post covers what it is, how it works inside, and the bug that shipped in 1.0.0.

Remocn Studio: one sentence in, a Remotion video out

Why I built it

I make launch videos for my own projects, and timeline editors were never a good fit for me. The work I'm worst at in After Effects (keyframes, track management, lining up a subtitle by eye) is exactly the work that's easy to describe as code. Remotion already turns a video into a React project, and Remocn gives you the parts: text entrances, transitions, shader backgrounds. Putting the parts together was still my job, and it went in a loop: edit TSX, switch to the Remotion Studio tab, scrub, see that something lands a beat late, switch back, guess a frame number.

Coding agents shorten that loop, but the agent lives in a terminal and the video lives in a browser tab. When the logo in the second scene is too big, I end up describing pixels in words to a process that can't see them. Remocn Studio puts both in one window, so I can point at the logo instead.

What it is

  • Your own agent, your own subscription. The studio drives the CLI of Claude Code, Codex CLI, GitHub Copilot CLI or Grok Build, whichever one is already signed in on your Mac. No API key, no separate token bill.
  • A standard Remotion project in a folder on disk. The agent writes TSX you can read, open in your editor, commit and render without the studio. There's no timeline, no keyframe UI, and no project format of my own to get locked into.
  • Three ways to give a note. Type it in the chat. Click an element in the preview, which pauses playback and opens its type, spacing and easing as controls you can drag; the value is written back into the source. Or drag a Snapshot box over the frame, and the crop goes into the chat as an image.
  • Export to MP4, WebM, GIF or ProRes at up to 4K, rendered by the project's own Remotion.

Clicking an element opens its properties

The agent doesn't get a single "make me a video" prompt. It follows a seven-stage pipeline (analysis, brand, script, motion, build, choreography, review), and each stage leaves a markdown file in the project (script.md, motion.md, review.md…) that you can read or overrule. Before it calls a video done, it runs a design_check tool that measures the rendered frames for readability and timing.

How it works under the hood

There are three layers. A Tauri v2 core in Rust owns the window, the keychain and the updater. The UI is a Next.js static export running in the webview. Everything else runs in a bun sidecar built on Effect: the agents, chat history, the preview host and the renderer. The Rust core supervises the sidecar and talks to it over stdio using newline-delimited JSON frames. The protocol is defined once as Effect Schema in TypeScript and mirrored with serde on the Rust side, and a test reads the Rust file and fails if the two protocol versions disagree. I added that test after four version bumps had landed on only one side.

The preview is the project's own webpack. My first attempt used Vite. It built a real project without errors and produced 3 CSS class selectors where Remotion's bundler produced 742, because Remotion's Tailwind support is a set of webpack loaders that Vite can't run. As it turns out, Remotion's webpack entry is an array, and Remotion Studio's UI is just its last element. The studio resolves @remotion/bundler and webpack from your project, swaps that last element for its own entry, and mounts a <Player> there. So the pane belongs to the studio, and the pixels come from Remotion.

Clicks map back to source in two steps. To find what you clicked, the preview host injects React Grab before the project bundle loads. It hooks React's DevTools hook and resolves a DOM node to its component and file through the sourcemaps, with its telemetry turned off and its Google-hosted font import removed. To edit a value, the studio reads the JSX call site that Remotion records from jsxDEV's source argument in development ({file, line, column}). It then writes the change with the project's own @remotion/studio-codemods, so the edit is an AST change and not a regex. Animated values stay animated: if a property is driven by interpolate(), dragging it moves the keyframe at the current frame instead of replacing the animation with a constant. The agent is told that the edit was made, so it doesn't make it a second time.

Driving four CLIs. Each provider sits behind an AgentAdapter interface and translates its own event stream into one shared set of events. Claude Code runs through the Claude Agent SDK, and Codex runs through @openai/codex-sdk. Copilot and Grok both speak the Agent Client Protocol (JSON-RPC over stdio), so they share one bridge. Once Copilot worked, adding Grok took little more than finding the CLI, the error messages and some config. File edits inside the project folder run without asking. Shell commands, and any path that resolves outside the folder (after expanding symlinks and ..), get a permission card first. Codex is the exception: headless Codex has nobody to answer approvals, so the OS sandbox is its gate. The studio also brings its own skills to every agent and never writes into ~/.claude, ~/.codex, ~/.copilot or ~/.grok.

The bug that shipped in 1.0.0

Within a few days of launch, a crash report arrived in Sentry: the preview pane threw SyntaxError: Unexpected end of script (and sometimes Unexpected token ':'. Expected '}' to end an object literal.) for projects that had compiled fine.

The preview host served bundle.js straight from disk. But webpack rewrites that file in place on every compile: it truncates the file, then writes it in chunks. An agent saves often, so when a save started a new compile while the pane was still fetching the previous one, the pane received either a build cut off partway or the beginning of one build joined to the end of the next. The webview evaluated that and threw.

The fix in PR #5, "fix(preview): serve the canvas bundle whole, never mid-rewrite" reads bundle.js synchronously in webpack's watch callback, which webpack calls after it has finished writing the assets and before it starts the next compile. That's the only moment the file is guaranteed to be complete. The host then serves that in-memory copy through the same code path it uses for files, with range support. A failed compile keeps the previous copy. I considered two alternatives. An atomic write-and-rename would have needed a custom output file system inside your project's webpack, and the server would still have raced between stat and open. Taking the bytes from the compilation object doesn't work either, because webpack 5 frees them once they're written.

The same PR fixed a second bug. The webview rewrote every sourceMappingURL= string anywhere in the bundle, so a library that builds that comment out of string pieces lost its closing quote. Now only the bundle's last line is rewritten. Both fixes shipped in 1.0.1.

About those reports: crash reports go to Sentry only if you turn them on in Settings › Behavior. They're off by default. Before sending, file paths are scrubbed of your home directory, and the hostname and project source lines are left out.

What's next

It's macOS-only today. The one open issue right now (#3) is a list of macOS layout papercuts, and those come next. Beyond that, I'd rather hear what breaks on your projects than guess. The studio opens any Remotion folder, and projects that don't look like mine are exactly where I expect it to break.

Try it

You need one of the four agent CLIs installed and signed in. The studio checks for it and shows you the exact command if it's missing. If you've tried to make a video with an agent before, I'd especially like to know where this loop still falls short for you.

Top comments (0)