The goal
wildloop ships an iOS app today. It is a WKWebView showing the mobile breakpoint of the web app, and it behaves like one: tapping a bottom tab loads, then switches. A phone app switches immediately and fills the screen it already showed.
We want stx to compile to native views — the React Native equivalent — so that wildloop is a native app written in stx, not a responsive website wrapped in Swift.
Where this actually stands
packages/stx-native exists: 8,012 lines across a parser, an IR, a headwind→native-style compiler, renderers for both platforms (ios.swift 765 lines, android.kt 1,305), both bridges, a runtime, hot reload and a CLI.
What I verified by running it:
- The headwind compiler works.
flex-1 flex-col justify-center items-center p-4 bg-blue-500produces correct native flex, padding and colour. This is usually where these projects die, and it is done. - The CLI has never been executed — a committed syntax error stops it before it starts (#1985).
- There are zero tests in the entire package (#1986).
- The parser cannot compile our own component library —
<Button>and<Card>throw,<TabBar>silently collapses to a singleTextnode (#1983). - The renderers have never rendered anything. No IR has reached a device.
So: a working front half, an unverified back half, and a middle that needs rethinking.
The two decisions that shape everything
1. Do not write a second parser (#1983). stx's own pipeline already handles server scripts, directives, component expansion and slots, and emits output with client bindings intact — :show, x-text, @click survive precisely so the runtime can bind them. Feed that to the native translator and all 102 components in @stacksjs/components transfer without a rewrite. Keep the IR, the headwind compiler and the component mapping; drop the bespoke lexer.
2. The runtime has to be able to drive something other than the DOM (#1984). This is the make-or-break item. signals.ts is 8,692 lines with 268 direct DOM calls. Signals themselves are pure — only the bind* layer is coupled — so the seam exists conceptually and has never been cut. React Native and Vue both solved exactly this with a host interface; that is the shape to copy.
Nothing else is worth funding until #1984 has an answer, and it is answerable in about a week.
Order of work
- #1985 — fix the CLI, so the package can be run at all.
- #1986 — golden tests over the compiler, so the one working part stops being unprotected.
- #1984 — the spike. Host interface behind
bind*, two implementations, portbindTextonly. Go / no-go. - #1983 — replace the parser with a translator over stx's rendered output.
- #1987 — vertical slice: one wildloop screen as real
UIViews on the simulator, one tap, one signal update. - Then, and only then: lists with recycling, a native navigation stack, text and images, Android.
What Craft has to provide
Craft is the right host — Zig binary, lifecycle, packaging, and a deep device bridge (speech, haptics, camera, motion, geolocation). Two gaps, filed there:
- craft-native/craft#327 — no native view tree; a framework targeting Craft can only draw into a WebView.
- craft-native/craft#328 — rendering native views would still require a
WKWebViewpurely to execute JavaScript.
Not part of this
#1988 fixes wildloop's tab bar doing a full page load on every tap. That is a defect against our own documented rule, it costs wildloop today, and it must not wait behind a months-long native effort.
Honest scope
This is months, and two renderers to keep at parity is a permanent tax rather than a one-time cost. What makes it tractable: the expensive host work is already done in Craft, the layout compiler already works, the component library transfers if we take the translator route, and steps 3 and 5 answer the viability question cheaply. The target is also smaller than React Native's — the primitives wildloop needs, not every primitive.