[go: up one dir, main page]

Native Shell

Build once, native everywhere

Your app sits in the middle. Each arch around it is a platform.

Write only TSX and ship native clients that keep pace with Rust. One codebase runs on 8 platforms, each with the system’s own UI and interaction.

FPS
120
first frame
~1 ms
platforms
8

A client written in TSX, right beside Rust

In NSH only your app’s code is JS. The reactive graph, the renderer, layout and every module are Rust. TSX and Rust apps share the same native execution pipeline.

120 FPS
22 live sparklines, an order book and a tape updating together, frame p95 under 4 ms
~0.1 ms
Engine startup (75–155 µs)
~1 ms
First frame of a counter app
~0.12 ms
One tap, from event to commit
< 1 KB
A hello world’s app code
~1.1 MB
A TSX app’s download on the web (Wasm runtime)
Small apps measured: a few MB above the bare system window
TSXRustBare system window
Memory footprint, macOS~20 MB~19 MB~17 MB
Private dirty memory, Linux (GTK, GL)~48 MB~44 MB~38 MB
Above the system window, macOS / Linux+3 / +9 MB+2 / +5 MB
Private working set, Windows~15 MB~14 MB
Size on each platform: nsh init’s hello world
TSX executableTSX packageRust executableRust package
macOS (Apple silicon)19.8 MB9.5 MB12.4 MB6.0 MB
Linux (x86_64)25.7 MB11.6 MB16.5 MB7.5 MB
Windows (x86_64)28.6 MB12.4 MB15.0 MB6.7 MB
Web—~1.1 MB download——

Release builds. Memory at rest: macOS footprint on an Apple M3 (the counter app and the Rust template at 1200 × 800; the system window holds one label at 480 × 640); Linux private dirty memory on an NVIDIA RTX 3060 Ti (nsh init’s TSX and Rust templates and a GTK window with one label, all at 480 × 640); Windows private working set. Each platform’s measure, with RSS and USS, is in the report. Sizes: nsh package’s release builds, with link-time optimization and stripped; the TSX executable holds the QuickJS runtime, the app’s code is under 1 KB; the package is the zipped .app on macOS, the .tar.gz on Linux and the .zip on Windows; on the web, what the browser downloads, runtime and app. The full performance report

TSX with 0 dependencies

An app is the files below. The nsh command line brings its own bundler and toolchain, and the modules you import are native.

$ nsh init my-app --template ts
$ nsh run my-app --target macos

my-app/
  App.tsx
  nsh.json
  tsconfig.json
  • One nsh executable creates, runs, tests and packages
  • ~120 ms from saving a file to the new version running
  • nsh check type-checks with TypeScript 7’s native tsc
TSXApp.tsx
import { mount, signal, Text, View } from "nsh";

function App() {
  const count = signal(0);
  return (
    <View class="items-center gap-4">
      <Text>Count: {count()}</Text>
      <View
        class="rounded-lg bg-blue-600 px-5 py-3"
         => count.update((n) => n + 1)}
      >
        <Text class="text-white">Tap</Text>
      </View>
    </View>
  );
}
mount(App);

8 platforms, each with the system’s own controls

NSH builds each UI from what its platform already has. Text input, selection, input methods and accessibility come from the system, and what a new OS release brings to them reaches the app with it.

PlatformViewsTextText inputRequires
macOSAppKitCoreTextNSTextViewmacOS 14+
LinuxGTK 4PangoGtkTextViewGTK 4.14+
WindowsWin32, Direct2DDirectWriteNSH’s editor, IMM32Windows 10 1809+
WebHTML, CSSThe browser’s<input>, <textarea>Modern browsers
iOSUIKitCoreTextUITextViewiOS 15+
AndroidAndroid viewsStaticLayoutEditTextAPI 28+
TUIratatuiThe terminal’s fontReadline keys, input methodsmacOS, Windows, Linux
GPUIGPUIGPUI’s text systemNSH’s editorEmbedded in GPUI Kit apps

Maps, web views, video and any native view the host registers are one more subview in the same hierarchy: they lay out, clip, scroll and take touches with the rest of the app. The backend of each platform

One app, in each device’s own form

The same compiled code runs twice here, and only the device differs. With a pointer it is a window with its conversations at the side; on a phone the buttons and the composer float in Liquid Glass with the thread scrolling beneath; in a terminal it sits on the character grid. The app is written once.

DesktopLive: type a message
PhoneLive: type a message
TerminalExplore TUI
+ New chat           │ What makes an NSH app native?
Today                │ A signal holds the count; only the Text that reads it updates   │
What makes an NSH…   │ when you press.                                                 ┃
Plot a week of NSH   │ import { mount, signal, Text, View } from "nsh";                ┃
Signed packages,…    │                                                                 ┃
A sheet with two…    │ const count = signal(0);                                        ┃
Taffy grid vs flex   │ mount(() => (                                                   ┃
                     │   <View  => count.set(count() + 1)}>                 ┃
                     │     <Text>{count()}</Text>                                      ┃
                     │   </View>                                                       ┃
                     │ ));                                                             ┃
                     │                                   What makes an NSH app native? ┃
                     │ Every element is the platform's own view: UIKit on iOS, Android ┃
                     │ views, AppKit on macOS and GTK 4 on Linux. Your TSX describes   ┃
                     │ them; layout, text and the reactive graph run in Rust.          ┃
                     │                                 And the glass around this chat? ┃
                     │ It's Liquid Glass, one glassEffect style. iOS 26 and macOS 26   ┃
                     │ draw the system's material; elsewhere it blurs what scrolls     ┃
                     │ beneath.                                                        ┃
                     │ • The buttons and the composer float over the thread            ┃
                     │ • Messages pass under them as you scroll                        ┃
                     │ • The same code at every idiom                                  ┃
                     │ ╭───────────────────────────────────────────────────────────────╮
                     │ │ › Ask anything                                                │
                     │ ╰───────────────────────────────────────────────────────────────╯

Desktop and phone run live in your browser on Native Shell’s web runtime. The terminal is real output of nsh run --target tui.

The same engine in Rust, when you want it

Rust is a first-class language for whole apps: UI, logic and components compile to native code, ready for demanding business logic. Both use the same elements and signals, and TSX screens import your Rust components.

Building with Rust

Rustsrc/lib.rs
use nsh_core::element::prelude::*;

#[component]
pub fn App() -> impl IntoElement {
    let count = signal(0);
    view()
        .items_center()
        .gap_4()
        .child(text(move || format!("Count: {}", count.get())))
        .child(
            view()
                .rounded_lg().bg(rgb(0x2563eb)).px_5().py_3()
                .on_press(move || count.update(|n| *n += 1))
                .child(text("Tap").text_color(white())),
        )
}

Only the views that changed are touched

Every element is a view the platform keeps. A change runs only the expressions that read the changed signal, in one batch per frame. The main thread never waits for JS and native animations run without it, so scrolling and animation hold 120 FPS and the app sits near idle at rest.

How it stays fast

The two texts that read the signals change and the history gains a row; no other view is touched.

$ nsh test examples/counter/App.tsx --trace
perf commit: 56 ops, 50 mount items, 21 nodes
press(+1)
perf commit: 10 ops, 7 mount items, 24 nodes

After release, your app can still take new UI

Your team can write it, or a model can generate it for each request. It reaches the device as a signed module, runs in a sandbox the host sets, and shows natively.

  1. Write

    Your team or a model writes a TSX module.

    App.tsx
  2. Check

    Type-check it, then run its tests headless.

    nsh check nsh test
  3. Sign

    An Ed25519-signed .nshpkg, its release key certified by an offline root.

    nsh publish
  4. Trust boundary

    Run

    The device verifies the signature, certificate and revocations. Each view gets a VM with memory, call-time and node limits, and only the capabilities the host grants.

    NshAppView NshView AppView

Three ways to ship, one engine

Standalone apps

Write it in Rust, TSX or both, and package an installable iOS, Android, macOS, Windows or Linux app, or a static website.

$ nsh init my-app --template rust
$ nsh run my-app --target macos
$ nsh package my-app --target windows
# --template ts for TSX
# ios · android · macos · windows · linux · gpui · web
Getting started

Dynamic UI and plugins

Embed a view on any screen and load signed TSX modules into it at run time, next to the host’s own native components.

let app = NshAppView()
try app.grantCapabilities(grants)
let info = try await app
  .loadPackage(at: file, trust: trust)
// verified, sandboxed, running
// NshView on Android, AppView elsewhere
Embedding, per platform

AI-generated UI

A model writes a module per request, so the app answers with an interface. Your server checks and signs it.

import { mount, signal, View } from "nsh";

function Answer() {
  const range = signal("1W");
  return <View class="gap-3 p-4">…</View>;
}
mount(Answer);
Signing and sandboxing

Behavior from NSH, the look from you

nsh/base holds unstyled base components that own state, focus, keyboard and accessibility, and the app styles every part. One nsh command line does the rest.

Unstyled base components

Navigation, dialogs, menus, popovers, tabs, selects, data tables, a code editor and a rich text kernel. Style every part with Tailwind classes or typed styles.

nsh/base

One toolchain, for people and agents

One nsh executable creates, builds, type-checks, hot-reloads, tests, packages and signs. Declarations are generated from the Rust sources and the docs are served as llms.txt, so an agent runs the whole loop with the same commands.

nsh run, nsh test, nsh package

Checked on every target

The same tests run headless, and in real windows, an iOS simulator, an Android device or a browser, with the platform’s own input.

nsh test --target ios

Native views and interaction,
your visual design

Install nsh and have your first TSX app running in a minute.