Loading...
Loading...
Build, run, test and package Native Shell (NSH) apps in Rust, TSX or both. Use for NSH development requests and projects with nsh.json, nsh-* crate dependencies or imports from nsh modules. Covers NSH reactivity, components, styling and tooling, with Coding Guides and working app recipes.
npx skill4agent add longbridge/nsh nsh| 1. Rust (preferred) | 2. Rust with TSX | 3. TSX | |
|---|---|---|---|
| Choose it when | The app ships in its binary; medium and large apps, demanding logic | A Rust core, plus screens that change often or arrive as signed packages | UI delivered at run time, with no Rust of your own |
| Runs on | The engine and your crate; no JavaScript VM | QuickJS (the browser's engine on the web) with your crate | QuickJS (the browser's engine on the web) |
| Start | | | |
| How to recognize one | | | |
| Edits | Rebuild and restart | TSX hot, Rust restarts | Hot |
| Read first | rust.md | mixed.md, and rust.md for the crate | tsx.md |
| Components | rust-ui.md | Both | tsx.md, |
| Tests | rust-testing.md | mixed.md "Tests on both sides" | tsx.md, testing guide |
| Whole app | rust-recipe.md ( | | tsx-recipe.md ( |
#[native_module]references/nsh.jsonCargo.tomlnsh --versioncargo --version--templatensh checknsh test| Guide | Read before |
|---|---|
Design Guides, skill | Choosing components, layout, spacing, hierarchy, color, density, interaction states, overlays, motion, interface copy |
| Coding Guides | App structure, what runs once, state ownership, keys, theme, events, focus, async, lists, public API, naming, testing |
https://nsh.dev/docs/design-guides.mdhttps://nsh.dev/docs/coding-guides.md./reactivity.mdhttps://nsh.dev/docs/reactivity.md../modules/nsh.mdhttps://nsh.dev/modules/nsh.mdgrep -n '^## ' references/coding-guides.md| Section | Read when |
|---|---|
| Architecture at a glance | Always; layers, an app's files, the store |
| Bootstrap and root ownership | |
| Understand what runs once and what runs again | Always; components, bindings, owners, context |
| Choose the right unit | Plain function, component or Rust |
| State ownership | Where state lives, controlled values, feedback loops |
| Stable identity | Keys, routes, test ids |
| Rendering and composition | Component bodies, |
| Behavior and presentation boundary | What |
| Theme and styling | Tokens, the Tailwind scale, state styles, text size |
| Events, commands, and focus | Presses, shortcuts, decisions as props, focus, overlays |
| Async work and side effects | |
| Layout, measurement, and scrolling | Flex defaults, |
| Lists, tables, and large data | |
| Public API design | Anything other code imports |
| Platform and capability boundaries | |
| File and naming conventions | Names of components, props, events, states |
| Testing strategy | |
| Performance rules | Before adding a cache; |
| Common failure modes | Before finishing |
| Rules for coding agents | Always when an agent writes code |
| Implementation checklist | Before finishing; run every item against the work |
.nsh/typestext(move || format!("{}", count.get()))nsh_basensh/basetheme().primarybg-primarypop_on_backclose_on_escapedisabledbufferconsolecryptofs/promisesnetospathprocessurlwebsocketzlib# Rust
cargo check --all-targets # or nsh check, which also checks TS tests
cargo test # the real UI, headless, with nsh_testing::Headless
nsh run # look at it; --target ios, android, web, macos, linux, windows
# Rust with TSX
nsh check # cargo check, then tsc against the types generated from Rust
nsh build # every warn is a finding: undocumented exports, unknown classes
nsh test # cargo test, then *.test.tsx in a runner linked with the crate
nsh run
# TSX
nsh check # the only type check
nsh build # the bundler's errors: nsh check does not see text in a View
nsh test # *.test.tsx, headless
nsh runnsh checknsh builduse nsh_base::{button, theme};
use nsh_core::prelude::*;
#[native_module("app/native")]
fn native(registry: &mut Registry) {
registry.register::<App>();
}
#[component]
pub fn App() -> impl IntoElement {
let count = signal(0_u32);
view()
.flex_1()
.items_center()
.justify_center()
.gap_4()
.bg(move || theme().background)
// A closure is a binding: it runs again when `count` changes.
.child(text(move || format!("Count: {}", count.get())).text_color(move || theme().foreground))
.child(
button()
.px(px(20.0))
.py(px(12.0))
.rounded(move || theme().radius)
.bg(move || theme().primary)
.active(|s| s.bg(move || theme().primary.opacity(0.9)))
.on_press(move || count.update(|c| *c += 1))
.child(text("Tap").text_color(move || theme().primary_foreground)),
)
}{ "name": "My App", "rust": ".", "root": "App" }// tests/app.rs (add matching dev-dependencies from references/rust-testing.md)
use nsh_engine::{EngineConfig, Root};
use nsh_testing::Headless;
#[test]
fn a_tap_counts() {
let mut app = Headless::start(EngineConfig::new(Root::of::<nsh_app_my_app::App>())).unwrap();
app.wait_for("Count: 0").unwrap();
app.press("Tap").unwrap();
app.wait_for("Count: 1").unwrap();
}snake_casecamelCaseon_open_changeonOpenChange.nsh/typescargo metadata --format-version 1nsh checkexamples/crates/website/https://github.com/longbridge/nshwebsite/docs/foo.mdhttps://nsh.dev/docs/foo.mdwebsite/elements/website/modules/llms-full.txthttps://nsh.dev/llms.txthttps://nsh.dev/llms-full.txthttps://nsh.dev/docs/rust.mdnative-components.mdnetworking.mdhttps://nsh.dev/elements/{name}.mdhttps://nsh.dev/modules/{name}.mdnsh.mdnsh-base.mdnsh-mobile.mdnsh-desktop.mdstyle-reference.mdtailwind-reference.mdstandard-modules.mdhttps://nsh.dev/docs/{name}.mdreactivity.mdlayout.mdnavigation.mdlists.mdforms-and-keyboard.mdplatform-specific.mdtesting-and-trace.mdlongbridge/nshAGENTS.mddocs/dev/