S01E08 — 13-06-2026.
zo 0.5.0 — Road to Mobile.
0.4.0 stabilized the compiler. 0.5.0 spends that stability: one zo source now runs on the desktop, across Apple’s platforms (iOS, tvOS, visionOS, watchOS), and on the web, with fine-grained reactivity, real components, styling, and forms. The toy grows up.
one source, every platform
Targets collapse onto a single --target axis. The same program, no rewrite, picks where it runs.
zo run hello.zo # desktop, native window
zo run hello.zo --target ios # iOS Simulator
zo run hello.zo --target visionos # visionOS Simulator
zo run hello.zo --target tvos # tvOS Simulator
zo run hello.zo --target watchos # Apple Watch Simulator
zo run hello.zo --target web # the browser
zo build hello.zo --target webview # a native webview .app
The web target is a backend: the compiler transpiles zo to a public/ bundle. The Apple targets render through native UIKit in the Simulator. On device, glass panels paint real Liquid Glass.
fine-grained reactivity
A write refreshes only what it drives. No runtime signals. The compiler builds the binding graph ahead of time, so when a mut changes, only the slots that read it repaint. The cost of an update is the size of the change, not the size of the view.
fun main() {
mut items: []str = [];
imu view: </> ::= <>
<button @click={fn() => items.push("hi")}>add</button>
<ul>{items.map(fn(item) => <li>{item}</li>)}</ul>
</>;
#render view;
}
Lists ride the same path: items.map(...) binds the rows to the view, keyed diffs move only the rows that moved, an appended item no longer rebuilds the whole list, and an in-place write, items[index] = v, patches a single row.
components
A zsx component is a function that returns markup. It takes props as parameters, and <name ... /> calls it.
fun greeting(name: str) -> </> {
return <h1>hello, {name}!</h1>;
}
fun main() {
imu page ::= <div>
<greeting name="world" />
<greeting name="zo" />
</div>;
#render page;
}
Components nest, carry their own per-instance state, splice children through <slot />, take callbacks as Fn props so events flow back out, and cross pack boundaries, with a guard that rejects a component that includes itself.
styling
zo-styler reads a scoped $: { } stylesheet with Tailwind-like shorthand, and the same rules reach every target. Interaction states are real: :hover, :active, :focus, :disabled.
$: {
.btn {
bg: #2563eb;
c: white;
}
.btn:hover {
bg: red;
}
}
fun main() {
imu page ::= <button class="btn">press me</button>;
#render page;
}
Borders, shadows, text wrap, and caps fill in alongside container surfaces, class selectors, background images, and the material: glass opt-in.
forms
HTML-faithful form controls land: text inputs, checkbox, radio, and select. They bind to state through @input and @submit, and the event carries the control’s value.
load core::zsx::*;
fun main() {
mut text: str = "";
imu view: </> ::= <input
value={text}
@input={fn(event) => text = event.value}
/>;
#render view;
}
naming-convention warnings
zo-checker now warns when a name strays from zo’s conventions: a function or variable that isn’t snake_case, a type that isn’t PascalCase. It is a warning, not an error. It nudges, it doesn’t block.
diagnostics and developer experience
Diagnostics gain a third audience. Alongside human and JSON, --format=xml emits a form built for agents. Color is TTY-aware, and you can suppress it. -q / --quiet drops the build banner, --help leads with examples, exit codes map to failure modes, and build status moves to stderr, the way Cargo does it, so a program’s own output stays clean on stdout.
An agent reads the same diagnostic as XML, one well-formed document, isomorphic to the JSON:
<diagnostics schema="1">
<diagnostic id="immutable-variable" code="E0309" severity="error" phase="analyzer">
<message>Cannot mutate immutable variable</message>
<fixes>
<fix kind="insert" file="main.zo" byte_start="37" byte_end="37">
<text>mut </text>
<description>Declare the variable as mutable with `mut`</description>
</fix>
</fixes>
</diagnostic>
</diagnostics>
The document also carries the source
<snippet>and the primary<span>(byte, line, and column), so an agent locates and applies the fix without parsing prose.
language and internals
One arrow: =:> is gone. The arr pack becomes array.