Remocn gives you 234 copy-paste Remotion components. That solves the parts of a video — the text entrance, the whip pan, the shader background. It does not solve the assembly: what plays when, how long the beat holds, which transition hides which cut. That work is still yours, and it happens in a loop that looks like this: edit TSX, alt-tab to remotion studio, scrub, notice the subtitle lands a beat late, alt-tab back, guess a frame number, repeat.
Claude Code already shortens that loop — point it at a Remotion project, install the remocn skill, and it writes scenes for you. But the agent is in a terminal and the video is in a browser tab, and neither knows about the other. When something looks wrong, you describe it in words: "the logo in the second scene, the one that fades in around frame 90, it's too big." You are being a translation layer between two windows.
Remocn studio removes the translation layer. It is a local macOS desktop app that runs the Claude Agent SDK against a folder on your disk, compiles that folder with its own Remotion bundler, and puts the chat and the player side by side in one window. When the logo is too big, you click it.
┌──────────┬─────────────────────┬──────────────┐
│ projects │ chat │ preview │
│ │ │ │
│ sessions │ streamed answer │ <Player> │
│ (SQLite) │ + activity lines │ + export │
└──────────┴─────────────────────┴──────────────┘
Tauri v2 (Rust core) ⇄ bun sidecar
├─ Claude Agent SDK
├─ preview host (the project's webpack)
└─ export (the project's renderer)
It is closer to a purpose-built Claude Code GUI than to a video editor. That distinction drives everything else, so it is worth being explicit about the three decisions underneath it.
Claude produces code, not a spec. The agent writes real Remotion TSX into your folder — the same files you would have written. There is no project JSON, no timeline document, no proprietary format to export from. Close the app and you still have a normal Remotion project that opens in any editor. The accepted cost is that the preview cannot cheaply re-render props; it has to compile source the app has never seen.
Open any folder. The app owns neither your entry point, nor your Remotion version, nor your build config. It adapts to the project instead of the project adapting to it.
It uses your Claude subscription. Auth is picked up from the Claude Code CLI you are already logged into. No API key, no separate OAuth, no per-token billing surprise. The cost of that is honest: the app is inert without Claude Code installed and logged in.
The loop, end to end
Opening a folder
Point it at an existing Remotion project, or use New project… — a vendored template expands offline (a package.json, a tsconfig.json, and a src/Root.tsx with exactly one <Composition id="Main">), then bun install streams its output into the pane while the chat is already usable. Both steps are idempotent, so a failed install offers a Retry that is safe to press after Claude has already edited the scene.
Then the app checks the things it depends on but does not own: whether Claude Code is logged in, which bun is running the sidecar, whether the folder is a Remotion project, whether its dependencies are installed and agree with the lockfile, whether an entry point is registered, and — once the preview compiles — whether any composition exists and whether one of them is called Main.
Everything that passes is silent. The checklist renders nothing at all when there is nothing to act on. Missing dependencies can be installed from the card. Only being logged out locks the composer, because that is the one failure that would otherwise happen on send — a folder that is not yet a Remotion project does not lock anything, since asking Claude to set one up is a perfectly reasonable next move.
Talking to it
The composer picks a mode, spelled the way the Agent SDK spells it: Auto hands routine calls to Claude Code's own classifier, Accept edits lets writes inside the project through and still stops at every command, and Plan makes the turn read-only. Plan mode ends in a card holding the plan itself: approve it and the same turn carries on building, or send it back to be revised without losing the turn. The mode belongs to the session and survives a restart.
Underneath the mode sits a permission gate that decides every call reaching it. Read, Glob, Grep, Write and Edit run silently as long as every path resolves inside the opened folder. Bash always asks. Anything resolving outside the folder always asks — and "resolves" means symlinks and .. are expanded first, so ../../.ssh/config is outside the folder whatever the literal argument said.
Four choices on the card: approve once, always allow this session, decline (the agent gets a message it can carry on from, not an error that ends the turn), or cancel the turn. The card sits above the composer rather than in the transcript, because an approval is a thing to answer, not a thing that happened.
The composer also carries a model picker, a reasoning-effort setting from low to max, a context-window ring that fills as the session grows, and image attachments — Cmd+V pastes a screenshot straight in.
Reading what it did
Assistant answers render as markdown — headings, lists, syntax-highlighted code — streamed with a per-word reveal, so an 85-character delta arriving every 470 ms reads as typing rather than stepping.
Every tool call collapses to one compact activity line: Edit src/Scene.tsx, Bash bun run build, with an icon for the kind of work and its state in the icon's colour. Click it and it expands into a real line diff computed from the tool's own old_string/new_string — not parsed out of result text — or the command next to its output. A run of consecutive calls folds into one row showing the newest of them and a +N, which makes that row a live ticker of what the agent is doing right now. Only a failure breaks a run and keeps its own row, so an error is never hidden behind a chevron.
Turns keep running when you look away. Each session has its own status — running, waiting on a permission, failed — and a turn that finishes while you are elsewhere leaves an unread dot. The projects pane orders itself around that: sessions needing you float to the top of their group, a collapsed project carries a rollup for the worst state inside it, and the "Show N more" cap counts only quiet rows, so it can never hide the one thing that was asking.
Watching it play
The preview pane runs the project's own Remotion bundler, with our entry mounted instead of the Studio UI.
That turned out to be a much smaller trick than it sounds. Remotion's webpack entry is an array whose last element is a parameter, and @remotion/studio/previewEntry is only Internals.waitForRoot((Root) => render(<Studio …/>)). Studio is a UI on top of the bundler, not part of it. Taking that slot and mounting <Player> gives you a pane that is ours and pixels that are Remotion's — staticFile(), Tailwind, path aliases, webpackOverride and every remotion.config.ts setting behave exactly as they do under remotion studio, because they are the same webpack.
A Vite host was tried first and quietly produced 3 CSS class selectors where Remotion's bundler produced 742: enableTailwind is a splice of webpack loaders, and Vite falls through to the project's own PostCSS config instead.
Which composition plays is folder → Main → first, and the folder wins because it is the only thing you actually pointed at — opening src/demos/launch-film in a project with forty compositions should not play transition-lab. The pane always says which of the three rules fired.
Pointing at what's wrong
Two gestures, deliberately separate because they answer different questions.
Inspect answers change this. Turn it on, hover the frame, click the element you mean, and it lands in the composer as an [Element #N] token you can point at from your sentence. What Claude receives with that token is the absolute path, line and column of the JSX that rendered the node, the component that owns it, a project-only component stack, the composition, the frame you were looking at, the frame within the enclosing scene with its timing, and the node's own markup. "Make this bigger" becomes an instruction, not a riddle.
Source resolution is React Grab driven headlessly — telemetry off, its overlay taken down entirely, the one web-font @import in its stylesheet stripped when the file is served, and a test that fails if a version bump reintroduces one. The hit-testing is ours, because a picker for video needs rules a general one does not: it takes the first element that actually paints at the point rather than the topmost transparent wrapper, and it climbs out of inline wrappers to the first block-level element — so clicking a word in a line animated word-by-word selects the line, not the word. An SVG is always a drawing: clicking one path selects the whole icon. Holding Alt turns every rule off and takes the literal node.
Snapshot answers look at this. Turn it on and the player pauses; click the frame to attach the whole thing, or drag a rectangle to attach just the part that is wrong. It arrives in the composer as an ordinary [Image #N] attachment.
The pixels come from renderStill in the project's own @remotion/renderer — the path an export will take — so what Claude sees is what will be in the file, not what a webview happened to paint. Measured against a real project, the still is byte-identical to what npx remotion still produces for the same frame in the same session.
A capture takes 83–122 ms, because the render page stays open across captures. It started at 8.4 s. The navigation was the whole cost: that page pulled 19.9 MB over 71 requests, 64 of them fonts, and selectComposition and renderStill each did their own. Caching the composition measurement, warming the page while you are still aiming, and holding it open past the four slow steps of renderStill is what turned it into a gesture that answers at the speed of a click.
Getting the file out
Export renders the playing composition to out/<Composition>.mp4 through the project's own @remotion/renderer, with progress that reads Rendering — 64/300 frames, then Encoding, then Combining the audio and the video, and a reveal in Finder at the end.
There is no second bundle, and that is the feature: the preview host has already compiled this project and is already serving the render page it compiled, so the export renders from the same URL a snapshot does. A second bundle() would cost another ~7 s and 1.67 GB of peak memory for a byte-identical result — and it could differ from what is on screen, which is the one thing an export must never do.
Before rendering, the app reads remotion, @remotion/renderer and @remotion/bundler out of your node_modules and refuses when they disagree, naming every package that drifted. The render writes a dotfile and renames it on success, so a cancel or a failure cannot leave a half-written Main.mp4 that looks finished, and cannot destroy the export you made ten minutes ago. Cancelling waits for Remotion to actually stop before deleting the partial.
Remembering all of it
Every conversation lives in the app's own SQLite, written by the sidecar as the events arrive. Closing the window does not throw the transcript away; reopening a session reloads its blocks, rebinds the folder and resumes the same SDK session. Only Claude Code's session_id is borrowed — its transcript files are not a public contract and would break the pane on any CLI update.
A folder is a first-class row, so the same project opened twice (through a symlink, say) is one history rather than two. A project whose folder has moved keeps its transcripts and collects under a Moved or deleted heading, with a Locate… that reconnects it instead of forking a second project. Deleting a session is undoable for a few seconds, and quitting inside that window drops the delete rather than rushing it.
What the agent already knows
What makes this remocn studio and not a generic Claude Code GUI: the app bundle carries a Claude Code plugin with four skills — the vendored remocn, remotion-best-practices and remotion-interactivity, plus one of our own, video-lessons.
video-lessons is 44 KB of production rules where every entry exists because the opposite was tried and had to be re-rendered: why text entrances travel on X and never on Y (browsers snap glyph baselines to whole device pixels, so the identical curve that ticks vertically is smooth horizontally), why a transform on a span silently forces inline-block and breaks two-tone paragraphs, which TransitionSeries shapes render an empty frame at a boundary. The turn's system prompt tells the agent to work from it before writing any video code, so those corrections no longer have to be pasted into a prompt by hand.
Nothing is installed into your machine or your project for this. A project that ships its own copy of a bundled skill gets the plugin dropped wholesale rather than shadowed.
What it costs to run
- macOS. Nothing in the code is macOS-specific, but that is all that is tested.
- Claude Code, installed and logged in, on a Pro or Max subscription.
- bun, resolved from your machine — the app does not bundle a runtime.
- A Remotion project with its dependencies installed.
The current release is v0.0.1 and the app updates itself from GitHub releases: it checks once per launch, offers what it finds in the sidebar footer, and installs and restarts on request. The bundle is not Apple code-signed, so first launch needs the usual Gatekeeper override. Updates are signed with the updater's own minisign key, which is a different thing entirely.
What's next
The prototype's definition of done is met — open a real project, ask for a scene, watch the edits land, approve the one Bash call it needs, see it in the player, export an mp4, no terminal. What is honestly still missing:
Two open bugs, both in the preview. The pane sizes its stage from a hardcoded 16:9, so a 9:16 composition plays as a narrow strip with black on both sides — the pixels are right, the frame around them is not. And a compile that fails while the agent is mid-write can leave the pane stuck at Compiling — 100%: the fix is a Reload button that is always present, plus announcing ready again after a clean compile that follows a failed one, so the pane heals itself.
Export is deliberately narrow. It renders h264 and every other knob comes from your remotion.config.ts. Format and quality settings, a render queue and Lambda are all out of scope for now.
Some UI is present but inert. ⌘K search, the project sort control and a Settings row are laid out and disabled rather than silently doing nothing — the shape is finished, the behaviour is not.
Element references do not go stale. A rebuild clears the markers and disarms Inspect, but marking individual references stale per changed file is deferred.
Windows and Linux are untested, not excluded. The port should be cheap: there are no macOS-specific paths or process spawning, only macOS-specific testing.
And the wider direction: this is the local half of the idea, where the agent writes real code you own. The hosted half — studio.remocn.dev, a spec-driven browser editor over one generic composition — is a separate product with a separate trade-off, and neither is trying to become the other.
If you want the components without the app, remocn.dev is a plain shadcn registry — npx shadcn@latest add @remocn/soft-blur-in and it is your file.