the usual way to see a swiftui change on a phone goes through xcode: compile, sign, install, launch. that pipeline is right for shipping software, but it is slow for sketching, and it needs a mac. studio's premise is that the editing and the running happen on the same device, so it needed something lighter.

wren is that layer. it doesn't compile anything. it parses swift source, walks the syntax tree, and builds real swiftui views from what it finds. xcode and the app store pipeline are still there for actual development; wren covers the earlier stretch, where you are still figuring out what to build.

parsing

wren uses swiftsyntax, the same parser the swift toolchain uses, so the source it accepts is ordinary swift. it parses the file, folds operator precedence, collects the declarations, and looks for an entry point: a view struct, a main() function, or plain top-level statements.

let parsed = Parser.parse(source: source)
let folded = try OperatorTable.standardOperators.foldAll(parsed)
let program = try WrenProgram(source: source)

the supported part of the language started small and has grown as studio needed more. it now covers structs, classes with inheritance, protocols, enums with associated values, closures, extensions, and error handling. when code reaches for something the runtime doesn't have, wren tries to fail with a clear error rather than guessing.

runtime values

the interpreter evaluates the program through a single value type. strings, numbers, arrays, closures, views, and wrapped native objects all move through it.

enum RuntimeValue {
    case string(String)
    case number(Double)
    case closure(RuntimeClosure)
    case view(AnyView)
    case native(Any)
}

this is looser than compiled swift on purpose. wren doesn't reconstruct the type system. it carries enough information to evaluate the program and hand swiftui the views it asks for.

the bridge

wren doesn't draw its own ui. when interpreted code constructs a Text or a VStack, the bridge builds the actual swiftui view, so layout, typography, gestures, and animation behave the way they would in a compiled app.

case "VStack":
    let children = try childViews(from: args, interpreter: interpreter)
    return .view(
        AnyView(VStack { childGroup(children) })
    )

the bridge currently covers 66 views and 176 modifiers, with @State, @Binding, @AppStorage, and @GestureState behind them.

state

interpreted @State lives in a store keyed by where the state appears in the code, so it isn't tied to any single render pass. when a value changes, the runtime bumps a generation counter and the host view rebuilds the interpreted tree.

@Published private(set) var generation = 0
private var stateStore: [String: WrenStateBox] = [:]

func stateDidChange() {
    guard !isRendering else { return }
    generation += 1
}

this is what makes a wren program behave like an app rather than a preview. a counter keeps its count while the view around it re-renders, toggles stay toggled, and timers keep firing.

the tradeoff

an interpreter is slower and narrower than compiled swift, and wren doesn't pretend otherwise. the narrowness has turned out to be useful, though. because the runtime supports a specific set of views and modifiers, code written against it targets a known surface. that includes code written by studio's agent, which is a big part of why generated code usually runs on the first try, and why it's easier to fix when it doesn't.

studio has grown a lot around the runtime: multi-file projects, downloadable apps, agents, design systems, export to xcode. all of it goes through the loop described here. wren parses the source, evaluates it, and hands swiftui the views.