WAID - one codebase, every platform

Write Vite, get 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 Detir runtime. No WebView, no rewrite. The same app, on every screen.

  • app.example.com

    counter.app

    3

    Real DOM - no runtime

  • app

    counter.app

    3

    GPU via Detir (wgpu)

  • counter.app

    3

    GPU via Detir (Metal)

  • 9:41

    counter.app

    3

    GPU via Detir (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 Detir, no WebView.

One app, every platform

One Vite app becomes web, desktop, iOS and Android.

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

A real static website

The same source builds a standard static site. Deploy the dist to any CDN or nginx - no runtime, no shell.

iOS - live

A native iOS app

Your dist plus the Detir runtime, drawn with Metal. A signed device build, not a web page in a wrapper.

Desktop - landing

A native desktop app

A packaged desktop bundle rendered on the GPU with wgpu. Landing as the runtime publishes.

Android - landing

A native Android app

A generated Android app rendered with Vulkan / GLES. Landing as the runtime publishes.

Rendering

GPU-native, no WebView

Native targets are drawn on the GPU by Detir, never a browser in a shell - so they look the same across devices.

Your code

No rewrite to learn

You write an ordinary Vite + React + Tailwind app. There is nothing WAID-specific in your source.

One linear build

One dist in, native artifacts out.

The build is a straight line, not a fork. Your Vite + React dist goes in, waid build embeds the Detir 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

Detir runtime in, icons per platform

Output

Signed native artifacts

Desktop, iOS and Android

One codebase

One codebase, every platform.

You keep a single ordinary Vite + React + Tailwind app. The same source becomes a website and native apps for desktop, iOS and Android - so there is one thing to build, test and ship, not four. Every target renders the same app, so what you see on one screen is what ships on the rest.
  • One source of truth - no per-platform fork to keep in sync
  • The web build is a real static site, deployable anywhere today
  • Native targets are GPU-rendered by Detir, not a browser in a shell
  • app.example.com

    counter.app

    3

    Real DOM - no runtime

  • app

    counter.app

    3

    GPU via Detir (wgpu)

  • counter.app

    3

    GPU via Detir (Metal)

  • 9:41

    counter.app

    3

    GPU via Detir (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 Detir, no WebView.

No rewrite

The dist is the app. No rewrite.

WAID does not parse, transform or compile your code. There are no WAID-specific directives, no per-platform branches and no special components to adopt. Search your source for “waid” and the only hit is waid.config.json. Anything that builds to a static dist works - and the same dist is exactly what ships to every platform.
  • No new framework, language or API to learn
  • Your existing Vite + React app builds unchanged
  • The built dist is the material every artifact embeds
~/my-app · waid
$ 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 … detir 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/

  • .waid/build/web/static site
  • .waid/build/ios/MyApp.ipaios artifact
  • .waid/build/desktop/MyApp.appdesktop bundle
  • .waid/build/android/MyApp.apkandroid artifact

Where it fits

Where WAID 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.

WAIDElectronTauriReact NativeFlutter
RenderingGPU-native (Detir)Chromium / DOMOS WebViewOS-native widgetsGPU-native (Skia / Impeller)
Bundles a browser?NoYes - ships ChromiumNo - uses the OS WebViewNoNo
Your codebaseVite + React, unchangedWeb app + Node mainWeb app + Rust coreRN components - a rewriteDart + Flutter widgets - a rewrite
Web targetReal static site, no runtimeDesktop onlyDesktop / mobile shellVia react-native-webCanvas / Wasm build
Native artifactsDesktop + iOS + AndroidDesktop onlyDesktop + mobile (beta)iOS + AndroidDesktop + iOS + Android
Fidelity across platformsOne renderer - pixel-identicalChromium everywhere (heavy)Varies by OS WebViewPer-OS native widgetsOne renderer - consistent

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

How it actually holds together.

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 Detir 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 Detir 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.

~/my-app · waid
$ 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 … detir 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/

  • .waid/build/web/static site
  • .waid/build/ios/MyApp.ipaios artifact
  • .waid/build/desktop/MyApp.appdesktop bundle
  • .waid/build/android/MyApp.apkandroid artifact

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.

waid.config.json
{  "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 Detir 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

Native macOS menus from JS

Author the real menu bar at runtime with waid.menus.set([...]). Accelerators fire before page handlers, exactly like a native app.

iOS today

Camera, microphone & media

Flip capture and playback flags in waid.config.json - with Apple usage strings - and the Detir runtime wires them to the OS.

Desktop-only

Desktop host functions

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

One GPU engine, per-platform backend

detir-scene draws every native target: wgpu on desktop, Metal (DetirFFI.xcframework) on iOS, Vulkan / GLES on Android. No WebView anywhere.

native macOS menus, from JS
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 Detir 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.

01

Explicit override

--runtime-dir (or WAID_RUNTIME_DIR) points at a local runtime tree - the offline / dev path.

02

Source-built cache

If a Detir checkout is reachable, WAID builds the runtime and stages it under ~/.detir/runtimes/source/.

03

Published cache

A previously fetched runtime at ~/.detir/runtimes/<version>/<platform>/ is reused - offline after first use.

04

Verified fetch

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

One React app. Native everywhere.

WAID is private and early-access while the packager hardens across targets. There is no public download yet - the Detir runtime is a pinned private release asset. Tell us what you are building and we will get you in.