Cross-origin isolation before code
The first commit after seven quiet months is @beemmeup/ffmpeg-catalog, a package that shells out to a real ffmpeg binary in CI and writes down what it says. BeemMeUp came back with a catalog, not a feature.
Studio was the point of it: an in-browser FFmpeg node-graph editor, running ffmpeg.wasm against whatever file the user drops. The multi-threaded core is faster, but it needs SharedArrayBuffer, and SharedArrayBuffer needs crossOriginIsolated to be true, and that needs two headers on the document: Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp.
require-corp is the part that should worry you before you turn it on. Under it, every subresource the page loads (images, video, fetches) has to opt in explicitly: same-origin, CORS-enabled, or carrying a Cross-Origin-Resource-Policy header. Anything else gets blocked, silently in some cases. Turn that on globally and you can break parts of the app that have nothing to do with Studio.
So the brief I wrote for the work asked for a list, not a change: "Don't fix anything yet. I want the inventory first. If Supabase Storage turns out to be unfixable, that changes the design."
What the inventory found
The inventory ran against the live app with headless Chromium, checking crossOriginIsolated and every network request for a requestfailed event. /ffmpeg-studio came back crossOriginIsolated === true, SharedArrayBuffer constructing normally. Every other route, /, /pricing, the API routes, stayed false or threw a ReferenceError, and zero requests failed anywhere. Route scoping held.
There was one real break, and it wasn't hypothetical: a bare <img src> or <video src> pointed at a cross-origin host with no CORP header gets blocked under require-corp. Neither storage.googleapis.com nor Supabase Storage sends one. Every CORS-mode request passed fine, including the entire hls.js pipeline pulling a manifest and segments straight off GCS, because hls.js fetches with CORS rather than a bare tag.
That distinction became a Studio build rule. Every image and video element in the tool routes through one <StudioMedia> wrapper that forces crossorigin="anonymous", because a thumbnail component that renders everywhere else in the app would otherwise render nothing inside Studio, with a console message that points at COEP rather than at the component.
Scoping the headers
The fix landed as a route match in next.config.ts, not a global header:
{
source: '/ffmpeg-studio/:path*',
headers: [
{ key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
{ key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' },
],
},
A second block marks /ffmpeg/:path*, the wasm worker and the core it loads, with Cross-Origin-Resource-Policy: same-origin. That one exists because a same-origin Worker script fetch isn't exempt from COEP the way a same-origin fetch() is: it loads fine from a route with no COOP/COEP, and fails silently (an opaque error event, never a message) when constructed from a route that has them. That difference only showed up by testing it.
The core that OOMs sooner
The design spec has a table worth keeping around, because the obvious choice is wrong for large files:
| Core | Threading | Memory |
|---|---|---|
core-mt | multi-threaded | fixed 1 GB heap, growth disabled |
| single-threaded | single-threaded | grows toward wasm32's ~2 GB ceiling |
The multi-threaded core is faster and OOMs sooner. The decision recorded is to ship it as the default and fall back to single-threaded automatically whenever crossOriginIsolated is false, which covers both the deliberate scoping and any browser where isolation didn't take.
Isolation being fixed at document load, not something a client-side route change can flip, turned into its own problem later: the whole app shell ended up doing full page reloads on every tab switch, not just the two tool pairs that actually cross the isolation boundary. That's a separate story, and the fix for it is dated two weeks after this one.