counter.app
3
Real DOM - no runtime
WAID - one codebase, every platform
Write one ordinary Vite + React app and ship it everywhere - a real static website plus native desktop, iOS and Android apps, all GPU-rendered by the Rune runtime. No WebView, no rewrite. The same app, on every screen.
counter.app
3
Real DOM - no runtime
counter.app
3
GPU via Rune (wgpu)
counter.app
3
GPU via Rune (Metal)
counter.app
3
GPU via Rune (Vulkan)
One App.tsx, four artifacts. Tap Count up on any device - they share the exact same build. The web frame is real DOM; the others are GPU-rendered by Rune, no WebView.

One app, every platform
You write one ordinary web app. WAID hands back a real website plus native apps for every screen - no browser bundled inside, no separate codebase to maintain.
Web - live
The same source builds a standard static site. Deploy the dist to any CDN or nginx - no runtime, no shell.
iOS - live
Your dist plus the Rune runtime, drawn with Metal. A signed device build, not a web page in a wrapper.
Desktop - landing
A packaged desktop bundle rendered on the GPU with wgpu. Landing as the runtime publishes.
Android - landing
A generated Android app rendered with Vulkan / GLES. Landing as the runtime publishes.
Rendering
Native targets are drawn on the GPU by Rune, never a browser in a shell - so they look the same across devices.
Your code
You write an ordinary Vite + React + Tailwind app. There is nothing WAID-specific in your source.
One linear build
The build is a straight line, not a fork. Your Vite + React dist goes in, waid build embeds the Rune runtime and rasterizes your icons per platform, and signed native artifacts come out.
Input
Vite + React dist
Your ordinary static build
waid build
Embed + rasterize
Rune runtime in, icons per platform
Output
Signed native artifacts
Desktop, iOS and Android
One codebase
DesktopNo rewrite
Your codeWhere it fits
One codebase, every platform - without adopting React Native, shipping a WebView, or rewriting in Dart for Flutter. Flutter is the closest architectural cousin: its own GPU renderer, no browser. Here is where WAID sits. Each tool has real strengths; the table is about trade-offs, not winners.
Fair notes: Electron and Tauri are mature and battle-tested for desktop; React Native has a vast native-module ecosystem; Flutter is a mature, high-performance framework with its own GPU renderer (Skia / Impeller), a huge ecosystem and one of the strongest single-codebase stories around. The honest difference from Flutter: you write Dart and Flutter widgets, and its web output is a heavy canvas build - with WAID you keep ordinary Vite + React and also get a real static website from the same dist. WAID's bet is one GPU renderer, no WebView and no rewrite - at v0.1.0 maturity.
Under the hood
The story above is the plain version. For engineers, here is the machinery - WAID is a thin native packager over your Vite dist and the Rune runtime. It never parses, transforms or compiles your code.
One command, a native artifact
No transpile, no code rewrite. WAID builds your Vite dist, resolves and embeds the pinned Rune runtime, and hands back a signed native artifact. This run produces a real .ipa; the desktop and Android slots stay hollow until their runtimes publish.
$ waid new my-app && cd my-app scaffolded vite + react + tailwind + shadcn$ waid build --src . --target ios building web dist … 42 files (1.8 MB) resolving runtime … rune 0.1.0 · ios · metal [cache hit] embedding DetirFFI.xcframework … 214 MB generating xcode project … MyApp.xcodeproj archiving … signing device buildbuilt ios artifact -> .waid/build/ios/MyApp.ipa (18.4 MB)$ waid deploy --target ios resolving device … udid 00008130-0a11…3e! install + launch land next (Phase 15). build + sign done.$ .waid/build/
One config, every platform
A single waid.config.json in your source root carries everything the packagers need. It often starts as just a name; capabilities are opt-in, and host functions are desktop-only - declaring them on mobile fails loudly.
{ "name": "peco", // bundle id + app name "icon": "icon.svg", // SVG master, rasterized per platform "iconBackground": "#ffffff", // Android adaptive-icon backdrop "dist": "web/dist", // Vite output (auto-inferred) "media": { // capture + playback "camera_capture": true, // + Apple usage strings "microphone_capture": true }, "host": { // DESKTOP-ONLY - rejected on mobile "fs": true, "shell": true, "vault": true }, "iosBuildProfile": { // iOS device signing "bundleId": "com.splenta.peco", "team": "XXXXXXXXXX" }}GPU-native rendering, no WebView
Native targets do not run your app in a WebView. The Rune runtime draws it on the GPU - wgpu on desktop, Metal on iOS, Vulkan / GLES on Android - and your app reaches real native capabilities too.
Desktop
Author the real menu bar at runtime with waid.menus.set([...]). Accelerators fire before page handlers, exactly like a native app.
iOS today
Flip capture and playback flags in waid.config.json - with Apple usage strings - and the Rune runtime wires them to the OS.
Desktop-only
fs, shell, env, vault, path, and actions are exposed to your app on desktop. Declaring host.* on iOS / Android fails loudly - mobile host is planned.
wgpu / Metal / Vulkan
detir-scene draws every native target: wgpu on desktop, Metal (DetirFFI.xcframework) on iOS, Vulkan / GLES on Android. No WebView anywhere.
if (waid.menus.supported) { waid.menus.set([ { label: "Project", submenu: [ { label: "New Run", accelerator: "CmdOrCtrl+N", onSelect: startRun }, { type: "separator" }, { label: "Archive", enabled: hasSelection, onSelect: archive }, ] }, ])}A runtime it never guesses
GB-scale Rune runtimes are resolved and pinned, never baked into the binary. For every native build WAID walks a fixed four-step order and stops at the first hit - so the runtime it embeds is always the exact, verified one. A checksum mismatch is a hard error, never a stale fallback.
--runtime-dir (or WAID_RUNTIME_DIR) points at a local runtime tree - the offline / dev path.
If a Rune checkout is reachable, WAID builds the runtime and stages it under ~/.detir/runtimes/source/.
A previously fetched runtime at ~/.detir/runtimes/<version>/<platform>/ is reused - offline after first use.
Otherwise WAID downloads the pinned runtime, checks its sha256, and extracts it. A mismatch is a hard error - never a stale fallback.
Private - early access
WAID is private and early-access while the packager hardens across targets. There is no public download yet - the Rune runtime is a pinned private release asset. Tell us what you are building and we will get you in.