Docs

Introduction

Native Shell (NSH for short) runs native UI written in Rust or TSX, on one engine written in Rust, and shows it with each platform’s own UI. Write a whole app in Rust, compiled to native code with no JavaScript VM; write TSX modules that a native app loads at run time, in a sandbox it controls; or combine both, with TSX screens on top of your Rust components. NSH is designed for AI-generated, dynamically delivered native UI: a model writes a small TSX module, and the app shows it natively. The same mechanism makes native apps extensible in general — plugins, features, whole screens delivered after release.

NSH runs on phones and desktops alike: iOS (UIKit), Android (Android views), macOS (AppKit), Windows (Win32 windows drawn with Direct2D) and Linux (GTK 4), in GPUI apps and on the web. Each platform has a native backend of its own, and the same app runs on all of them (Platforms).

#Why it exists

First: UI generated by AI. An assistant inside your app can answer with an interface rather than with text. A model writes a small TSX module (a form, a chart, a booking card), your server checks, builds and signs it, and the app downloads it and shows it in an NshAppView: native UI, sandboxed, limited to what you granted, and with no app update. NSH is built around this use, so its design is AI friendly throughout:

  • A format models write well. A module is one TSX file using signals, a handful of host elements and Tailwind classes — the vocabulary models already know from the web, with no build configuration and no NPM.
  • A precise, generated contract. Every element, prop, style key and module has TypeScript declarations generated from the Rust sources (.nsh/types), so generated code can be type-checked, and a model can be given the exact API instead of guessing. These docs are also served as plain text for models (llms.txt, llms-full.txt).
  • Checkable before it ships. nsh test runs a module headless, without a device, takes steps on it and reports errors and the views it produced; build errors come with a code frame. A generation loop can compile, run, read the result and retry. The compiler also runs in-process as a library (nsh_bundler::compile_module) and in the browser (Playground).
  • Safe to run when it is not trusted. Generated code is untrusted code. Each module runs in its own QuickJS VM with memory, call-time and node limits, reaches only the capabilities the host grants, and arrives as a signed package the app verifies first.
  • Native without native code. The output is the platform’s own UI — UIKit, Android views, AppKit, GTK 4 or Win32 with Direct2D — with the system’s text, input and accessibility, yet the model never writes Swift, Kotlin, Java or Rust, and the app never needs a new release.
From a prompt to native UI: a model writes a TSX module; your server builds, checks and signs it; across the trust boundary, the app verifies the package, runs it in a sandbox with the limits and capabilities it set, and shows it with the platform's own UI. From a prompt to native UI: a model writes a TSX module; your server builds, checks and signs it; across the trust boundary, the app verifies the package, runs it in a sandbox with the limits and capabilities it set, and shows it with the platform's own UI.

Second: extending native apps, and delivering UI dynamically. The same mechanism serves any module, not only generated ones. The app embeds an NshAppView (iOS), NshView (Android) or AppView (macOS, Windows, Linux) wherever a module should appear, and NSH runs the module inside it:

  • Delivered after release. A module is a signed package (.nshpkg) the app downloads at run time. The app checks its signature against the keys it trusts and against a revocation list before running it (Signed packages). A change needs no new binary and no store release.
  • The host stays in control. Each module runs in its own sandboxed VM, with memory, time and node limits, and reaches only what the host grants: network hosts, web pages, the status bar (Sandbox and capabilities). Several modules can run side by side; there are no global singletons.
  • It looks like the rest of the app. Modules render with the platform’s own UI, so text, input, accessibility and scrolling are the system’s. The host can register its own native views — a map, a player, a design-system component — for modules to use (Native components).

Third: whole apps, in Rust or TSX. nsh package turns an app into an installable iOS, Android, macOS, Windows, Linux or web app. An app written in Rust gets Rust’s type system, the crates.io ecosystem and native performance for its own code, with no JavaScript VM on any platform (Building with Rust); a TSX app runs on the prebuilt runtime. The getting started guide creates one of each.

#Why Native Shell, next to GPUI Shell

Longbridge is building NSH as the plugin layer of its mobile client, and its desktop client needs the same layer. GPUI Shell makes GPUI desktop apps extensible in JavaScript, and the first idea was to use it on phones too. It does not fit there, because its modules are drawn by GPUI itself, on the GPU, not with the platform’s views:

  • System behavior would have to be rebuilt. A self-drawn surface inside a native screen has none of the platform’s text selection, nor the selection menu that comes with it (Copy, Share, Translate, Look Up, Select All), nor the system’s input methods, accessibility and new visual materials each iOS release brings. Recreating them in a GPU renderer is a large, never-finished job, and the copies drift from the real thing.
  • Mobile teams would have to adopt a new UI stack. Running a self-drawn layer at scale in an existing mobile client means its iOS and Android teams take on an unfamiliar renderer, its compatibility issues across OS versions and devices, and its bugs.

NSH therefore renders modules with each platform’s own UI: UIKit on iOS, Android views on Android, AppKit on macOS, Win32 windows drawn with Direct2D on Windows and GTK 4 on Linux. One module format and one runtime run in all these hosts.

The two are positioned differently. GPUI Shell is for extending GPUI desktop applications; Native Shell is for native apps on every platform, phones and desktops alike.

#An app

The same counter, in Rust and in TSX:

use nsh_core::ui::prelude::*;

#[component]
pub fn Counter() -> impl IntoElement {
    let count = signal(0);
    view()
        .flex_1()
        .items_center()
        .justify_center()
        .gap_3()
        .child(text(move || count.get().to_string()).text_5xl().font_weight(FontWeight::BOLD))
        .child(
            view()
                .rounded_lg()
                .bg(rgb(0x2563eb))
                .px_5()
                .py_3()
                .active(|s| s.opacity(0.6))
                .on_press(move || count.update(|n| *n += 1))
                .child(text("Tap").text_lg().text_color(white())),
        )
}

A view (view(), <View>) becomes a UIView on iOS, a ViewGroup on Android, an NSView on macOS and a GTK widget on Linux, a text a native text view, and the button’s press feedback (.active(..), activeStyle) runs on the main thread. When you tap, count.update changes one signal, and only the text that reads it is updated: the component function does not run again. The Rust app is native code from start to end; in the TSX app, only the module’s own code is JavaScript.

A TSX module is also what a native app loads at run time. The host app shows it with a few lines of native code (iOS, Android, macOS, Windows, Linux). On iOS:

let app = NshAppView()                         // a UIView: place it anywhere
try app.grantCapabilities(#"{ "network": { "hosts": ["api.example.com"] } }"#)
let info = try await app.loadPackage(at: downloadedFile, trust: trust)   // verified, then run

Everything the module calls into — the reactive graph, the renderer, layout, text and every module it imports — is native, written in Rust.

#How it runs

The toolchain is a single nsh binary. An app written in Rust is a crate: nsh builds the runner of each target with your crate linked in, and its root component runs on the engine directly. A TSX app (a module) is a set of .tsx / .ts files with an entry, usually App.tsx:

  1. Build. nsh bundles the module’s files (with rolldown and Oxc) and compiles the JSX with its own compiler. Imports of nsh and nsh/* stay imports: the runtime serves them natively, so the bundle holds only your code. The bundle is compiled to QuickJS bytecode.
  2. Run. On the device, the engine thread runs the bytecode in QuickJS. Compiled JSX calls the native renderer directly; changes are collected during one JavaScript turn, laid out with Taffy (flexbox and CSS grid), and handed to the main thread as one batch of view operations per frame.
  3. Render. The main thread applies the batch to native views. Text is measured and laid out once on the engine thread (CoreText on Apple platforms, StaticLayout on Android, DirectWrite on Windows, Pango on Linux) and only drawn on the main thread.

A Rust app takes the same path from step 2 on, with its components running natively in place of QuickJS. In a TSX app, JavaScript only describes the UI and handles events, so the work done in the interpreter is small. Parsing styles, resolving Tailwind classes, diffing, layout and text all happen in Rust.

#What you get

  • Rust or TSX. Apps written in Rust, with typed styles and compile-time checks and no JavaScript VM; TSX for screens and modules delivered at run time; or both, with TSX importing your Rust components, modules and classes (Building with Rust).
  • Embedding. NshAppView (Swift), NshView (Java/Kotlin) and the desktops’ AppView (AppKit, GTK, Win32) put a module in a native app, as many as you like (iOS, Android, macOS, Linux, Windows).
  • Signed, revocable delivery. Ed25519-signed packages, release keys certified by an offline root key, revocation lists (Signed packages).
  • A sandbox the host configures. VM limits and capabilities granted per view, in the iOS and Android SDKs (Sandbox and capabilities).
  • The host’s own views in modules. Register native components from Swift, Java or Rust (Native components).
  • Native views. Embedding a native map, web view or video player has no layering problems, accessibility uses the platform’s own tree, and text input is the system’s.
  • Fine-grained reactivity. Signals like Solid’s, implemented natively. Components run once; only the expressions that read a changed signal run again.
  • Styles and Tailwind, resolved natively: flexbox, CSS grid, shadows, gradients, filters, transitions (Styling, Tailwind).
  • Primitives and base components; the look is the app’s. Apps draw their UI from the primitives (View, Text, Input, List, …) with styles and Tailwind classes, and build on nsh/base, unstyled base components that own behavior, state and accessibility: navigation stacks and modals, safe areas, system alerts, the keyboard and the status bar. The styled component library on top is the app’s own or the community’s (Three layers). Desktop layouts are built from the primitives with desktop={{…}} props and platform.select (Platform-specific code).
  • One binary toolchain. A single nsh executable builds modules, writes their type declarations, serves them with hot module replacement, packages and signs them.

#Where it runs

PlatformStatusBackendNotes
iOSSupported (iOS 15.0+)UIKitDevices and the simulator; embedded with NativeShell.xcframework (iOS)
AndroidSupported (API 28, Android 9+)Android viewsarm64-v8a and x86_64; embedded with nsh.aar (Android)
macOSSupported (macOS 14+)AppKitStandalone apps; embedded with AppView (macOS)
WindowsSupported (Windows 10 1809+)Win32, Direct2DStandalone apps; embedded with AppView (Windows)
LinuxSupported (GTK 4.14+)GTK 4Standalone apps; embedded with AppView (Linux)
GPUI (macOS, Windows, Linux)SupportedGPUIEmbedded with AppView in GPUI and GPUI Kit apps (GPUI)
WebSupported (modern browsers)HTMLStatic sites; embedded in a page with createPlayer or an iframe (Web)
Mac CatalystFor developmentUIKitThe iPhone-sized window nsh run opens on a Mac, to see a module as on a phone
One module, many hosts: the same bundle and Rust runtime on iOS, Android, Linux, macOS with AppKit and Windows with Win32 / Direct2D, GPUI on all three desktops, and web pages drawn with HTML. One module, many hosts: the same bundle and Rust runtime on iOS, Android, Linux, macOS with AppKit and Windows with Win32 / Direct2D, GPUI on all three desktops, and web pages drawn with HTML.

#How it compares

The usual ways to put UI into an app after release are web views, server-driven UI described in JSON, and mini-program containers; for whole apps, cross-platform frameworks such as React Native, Flutter, Electron and Tauri. An NSH module renders native UI where a web view renders a page, carries its own state and logic where a server-driven schema can only recombine built-in components, and runs in a sandbox the host configures in code. Comparing approaches goes through each.

#Next steps