Euan Cowie
§

Work

Code as data, not text

TypeScript, React Native and Convex behind an AI-native canvas product, and an AST-based virtual file system so generated projects can be edited as structured data instead of patched as strings.

OrganisationBloom Labs
PeriodMarch — April 2026
StackTypeScript / AST / React Native / Convex / CRDTs / AI tooling

The problem

Bloom Labs brought me in on a fixed-term contract to de-risk an unreleased frontend/backend canvas product: a visual environment where you build an application by manipulating it directly, with an AI in the loop, and real source code underneath. TypeScript throughout, React and React Native on the client, Convex for backend state and projections, and agents operating over a model of the user's project.

That product has a hard question at its centre. Four things have to stay in agreement — user intent, the runtime preview, backend state, and the source code — and each of them can change first. Get the mediation between them wrong and the product is a demo that falls apart on the second edit.

De-risking means answering whether the architecture can hold before the company commits to it. My contract was scoped to that.

Code as data

The piece I would point at first is the AST-based virtual file system.

Every tool in this category eventually faces the same choice: treat the user's project as text, or as structured data. Text is easy and wrong. String-patching generated code works until an import needs adding to a file that already has a conflicting one, or a rename has to reach three call sites, or a generated edit has to survive the user having hand-edited the file in between.

So I built a virtual file system that models a project as round-trippable structured data — parse to a tree, operate on the tree, emit back to source, and get something a human would have written. That enables safe imports, dependency-aware edits, deterministic code generation, and refactors that land in the correct files rather than the nearest one.

It is the same instinct as validating a deployment against a published schema: make the invalid state unrepresentable, rather than detecting it afterwards. The hard part is round-trip fidelity, and it is the part most tools in this space hand-wave.

The bridge between editor and runtime

The canvas has to run the app you are building inside the app you are building it with, and the shell has to be able to reach into that runtime and control it.

On web, that was an iframe bridge between editor and runtime. On mobile, I built an app-in-app shell: an outer Expo application hosting and driving an inner Expo application over a custom native bridge written for the purpose, demonstrated working. Most React Native engineers consume the framework; very few write a bridge underneath it.

I also built the canvas UI itself in React Native and Expo with Reanimated, with hand-written gesture and animation logic, because an interface for manipulating an app under construction only works if it feels immediate rather than mediated by a form.

Alongside that: Convex-backed projections plus import and style projections for the inspector, CRDT collaboration primitives, and custom agents operating over the project graph.

What it meant

This is the third time I have built the same shape of thing — work out the structure of an interface nobody documented, then put a usable, controllable surface over it. At Omecu it was a React portal over a gRPC control plane. At Zelim it was a control plane over hardware in places you cannot reach. Here it was an editor over a live runtime, twice, on two platforms.

The contract ended when Bloom changed direction as a company. That sits correctly with what de-risking is for: the work exists to tell you whether to keep going, and this time the answer came back no. The reasoning survives regardless, and the code-as-data piece is the part I would build the same way again.

Because it was contract work, the code is not mine to publish — but the architecture is describable, and I am happy to walk through it.