nsh

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Native Shell

Native Shell

One app, written in Rust, in TSX, or in both, rendered through each platform's own views on the same Rust engine. Follow the user’s chosen language and delivery model. When neither is specified, prefer Rust for an app shipped in its binary.
一个应用可以使用 Rust、TSX 或两者编写,并通过同一 Rust 引擎由各平台自己的 原生视图渲染。遵循用户选择的语言和交付模式。 如果两者都未指定,对于以 binary 形式交付的应用,优先使用 Rust。

Choose the Mode, Then Read Its Page

选择模式,然后阅读对应页面

1. Rust (preferred)2. Rust with TSX3. TSX
Choose it whenThe app ships in its binary; medium and large apps, demanding logicA Rust core, plus screens that change often or arrive as signed packagesUI delivered at run time, with no Rust of your own
Runs onThe engine and your crate; no JavaScript VMQuickJS (the browser's engine on the web) with your crateQuickJS (the browser's engine on the web)
Start
nsh init my-app --template rust
nsh init my-app --template mixed
nsh init my-app --template ts
How to recognize one
nsh.json
has
"rust"
and
"root"
nsh.json
has
"rust"
, no
"root"
, and a TSX entry
nsh.json
has no
"rust"
EditsRebuild and restartTSX hot, Rust restartsHot
Read firstrust.mdmixed.md, and rust.md for the cratetsx.md
Componentsrust-ui.mdBothtsx.md,
.nsh/types
Testsrust-testing.mdmixed.md "Tests on both sides"tsx.md, testing guide
Whole apprust-recipe.md (
examples/tasks-rs
)
examples/rust-exports
tsx-recipe.md (
examples/tasks
)
In an existing project, stay in its mode unless the user requests a change. For a new app, honor the requested mode; otherwise use the table above; a Rust app can gain TSX later, because it already has a
#[native_module]
. tooling.md covers the commands, debugging, platforms, capabilities, packaging and updates in all three.
The rule of every mode: a component runs once, and only the bindings that read a changed signal run again. A value read in a component body is a snapshot.
1. Rust(首选)2. Rust 与 TSX3. TSX
选择条件应用以 binary 形式交付;适合中大型应用和逻辑密集型场景以 Rust 为核心,界面经常变化或以签名包形式交付在运行时交付 UI,且不使用自己的 Rust
运行于引擎与你的 crate;无 JavaScript VMQuickJS(Web 上浏览器使用的引擎)与你的 crateQuickJS(Web 上浏览器使用的引擎)
初始化
nsh init my-app --template rust
nsh init my-app --template mixed
nsh init my-app --template ts
识别方式
nsh.json
包含
"rust"
和
"root"
nsh.json
包含
"rust"
、不包含
"root"
,且有 TSX 入口
nsh.json
不包含
"rust"
编辑方式重新构建并重启TSX 热更新,Rust 重启热更新
首先阅读rust.mdmixed.md,以及关于 crate 的 rust.mdtsx.md
组件rust-ui.md两者都需要tsx.md、
.nsh/types
测试rust-testing.mdmixed.md 中的“双方测试”tsx.md、测试指南
完整应用rust-recipe.md(
examples/tasks-rs
)
examples/rust-exports
tsx-recipe.md(
examples/tasks
)
在现有项目中,除非用户要求更改,否则保持其模式不变。对于新应用,遵循请求的模式; 否则使用上表;Rust 应用之后可以加入 TSX,因为它已经具有
#[native_module]
。tooling.md 涵盖三种模式下的命令、调试、平台、 capabilities、打包和更新。
所有模式的共同规则:组件只运行一次,只有读取了已变化信号的绑定才会 再次运行。 在组件主体中读取的值是快照。

First App or Existing Project

首个应用或现有项目

Resolve
references/
paths relative to this skill’s directory, not the app’s working directory. Read only the mode and topic pages needed for the task.
  • Existing project: inspect
    nsh.json
    , the entry and, for Rust,
    Cargo.toml
    . Keep its structure, dependency sources and mode. Use the project’s CLI version.
  • First app: run
    nsh --version
    . For Rust or mixed, also check
    cargo --version
    . If a tool is missing, read tooling.md for setup and the prerequisites of the requested target.
  • Read the guides below and the selected mode page. Initialize with the table’s explicit
    --template
    in the user’s chosen location. Work from the generated project, preserving its manifest and entry wiring; the quick-start snippets below illustrate edits, rather than replacing all generated files.
  • Get the generated app through
    nsh check
    and
    nsh test
    before expanding it. Then implement the requested flow and use the mode’s verification loop below. Check the visible result on the requested target when available; report any verification that the environment cannot perform.
相对于此 skill 的目录解析
references/
路径,而不是应用的工作目录。 只读取任务所需的模式和主题页面。
  • 现有项目: 检查
    nsh.json
    、入口,对于 Rust 项目还要检查
    Cargo.toml
    。 保持项目结构、依赖来源和模式不变。使用项目自己的 CLI 版本。
  • 首个应用: 运行
    nsh --version
    。对于 Rust 或 mixed 模式,还要检查
    cargo --version
    。 如果缺少工具,请阅读 tooling.md, 了解设置方式和请求目标平台的先决条件。
  • 阅读下方指南和选定模式的页面。在用户选择的位置使用表格中明确的
    --template
    进行初始化。以生成的项目为基础,保留其 manifest 和入口连接; 以下快速入门代码片段仅演示如何修改,而不是替换所有生成的文件。
  • 先让生成的应用通过
    nsh check
    和
    nsh test
    ,然后再扩展它。 接着实现请求的流程,并使用该模式的验证循环。 目标平台可用时,检查可见结果;报告环境无法执行的任何验证。

Read the Guides First

首先阅读指南

Two guides hold the rules this skill assumes. They are requirements, not inspiration. Read the guide file itself; do not answer from this page, from a similar file in the codebase, or from training data.
GuideRead before
Design Guides, skill
nsh-design-guides
Choosing components, layout, spacing, hierarchy, color, density, interaction states, overlays, motion, interface copy
Coding GuidesApp structure, what runs once, state ownership, keys, theme, events, focus, async, lists, public API, naming, testing
Read the Design Guides first when the change has a visible surface. If the design skill is not installed, fetch
https://nsh.dev/docs/design-guides.md
. The coding guide is a verbatim copy of
https://nsh.dev/docs/coding-guides.md
, so a link in it such as
./reactivity.md
means
https://nsh.dev/docs/reactivity.md
, and
../modules/nsh.md
means
https://nsh.dev/modules/nsh.md
.
以下两份指南包含此 skill 所依据的规则。它们是要求,而不是灵感。 必须阅读指南文件本身;不要根据本页、代码库中的类似文件或训练数据作答。
指南阅读时机
Design Guides、skill
nsh-design-guides
选择组件、布局、间距、层级、颜色、密度、交互状态、浮层、动效、界面文案
Coding Guides应用结构、只运行一次的内容、状态归属、keys、主题、事件、焦点、异步、列表、公共 API、命名、测试
变更涉及可见界面时,先阅读 Design Guides。如果尚未安装 design skill,请获取
https://nsh.dev/docs/design-guides.md
。Coding guide 是
https://nsh.dev/docs/coding-guides.md
的逐字副本,因此其中的链接,例如
./reactivity.md
,表示
https://nsh.dev/docs/reactivity.md
,而
../modules/nsh.md
表示
https://nsh.dev/modules/nsh.md
。

Coding Guides section map

Coding Guides 章节映射

Read the whole guide for a new app, module or feature. For a narrow change, read "Understand what runs once and what runs again" and "Rules for coding agents" first, then the section for the change (
grep -n '^## ' references/coding-guides.md
).
SectionRead when
Architecture at a glanceAlways; layers, an app's files, the store
Bootstrap and root ownership
mount
, the root, what one surface is
Understand what runs once and what runs againAlways; components, bindings, owners, context
Choose the right unitPlain function, component or Rust
#[component]
State ownershipWhere state lives, controlled values, feedback loops
Stable identityKeys, routes, test ids
Rendering and compositionComponent bodies,
<If>
, using
nsh/base
before custom views
Behavior and presentation boundaryWhat
nsh/base
owns, parts and slots
Theme and stylingTokens, the Tailwind scale, state styles, text size
Events, commands, and focusPresses, shortcuts, decisions as props, focus, overlays
Async work and side effects
resource
, effects, stale results
Layout, measurement, and scrollingFlex defaults,
onLayout
, scroll owners, insets
Lists, tables, and large data
List
, why cells keep no state
Public API designAnything other code imports
Platform and capability boundaries
ios={{}}
,
nsh/mobile
,
nsh/desktop
,
capabilities
File and naming conventionsNames of components, props, events, states
Testing strategy
nsh test
, selectors, what to cover
Performance rulesBefore adding a cache;
nsh test --trace
Common failure modesBefore finishing
Rules for coding agentsAlways when an agent writes code
Implementation checklistBefore finishing; run every item against the work
新应用、模块或功能需要阅读整份指南。对于范围较窄的变更,先阅读 “Understand what runs once and what runs again”和“Rules for coding agents”, 然后阅读变更对应的章节(
grep -n '^## ' references/coding-guides.md
)。
章节阅读时机
架构概览(Architecture at a glance)始终;分层、应用文件、store
Bootstrap 与 root 归属
mount
、root、什么构成一个 surface
理解哪些内容只运行一次、哪些会再次运行始终;components、bindings、owners、context
选择合适的单元普通函数、component 或 Rust
#[component]
状态归属状态存放位置、受控值、反馈循环
稳定标识keys、routes、test ids
渲染与组合组件主体、
<If>
、使用
nsh/base
而非自定义视图
行为与呈现边界
nsh/base
负责的内容、parts 和 slots
主题与样式tokens、Tailwind scale、状态样式、字号
事件、命令与焦点按压、快捷键、以 props 传递的 decisions、焦点、overlays
异步工作与副作用
resource
、effects、过期结果
布局、测量与滚动Flex defaults、
onLayout
、scroll owners、insets
列表、表格与大型数据
List
、为何 cells 不保留 state
公共 API 设计其他代码导入的所有内容
平台与 capability 边界
ios={{}}
、
nsh/mobile
、
nsh/desktop
、
capabilities
文件与命名约定components、props、events、states 的名称
测试策略
nsh test
、selectors、应覆盖的内容
性能规则添加 cache 之前;
nsh test --trace
常见失败模式完成之前
Coding Agent 规则Agent 编写代码时始终阅读
实现检查清单完成之前;对工作逐项执行

Non-negotiables

不可妥协项

A floor, not a substitute for the guides.
  • Never invent an API. Look the name up in the crate's source (Rust) or in
    .nsh/types
    (TSX), then its reference page. Do not write by analogy with another framework.
  • Components run once. In Rust, pass a closure or a signal where a value must stay current:
    text(move || format!("{}", count.get()))
    . In TSX, read props and signals in JSX or a closure; do not destructure props.
  • One owner per state. A page's draft is a signal of that page; shared state is a store with private signals. In Rust with TSX, one side owns each piece of state.
  • The look is the app's.
    nsh_base
    /
    nsh/base
    has behavior and no look, and NSH has no styled component library. Draw with theme tokens:
    theme().primary
    in Rust,
    bg-primary
    in TSX. Wrap each base component once in the app's own UI module.
  • Parts draw nothing until styled. A switch with no track and thumb style shows no switch, and a test still passes. Style every part, and look at the result.
  • Use the semantic component. Button, Menu, Popover, Sheet, Dialog and Select bring focus, keyboard, dismissal and accessibility. Do not rebuild them from a view.
  • Long data goes in a virtualized list, with per-item state in data keyed by id, never in a signal created in a row: cells are reused for other rows.
  • A decision the system needs at once is a prop, set beforehand (
    pop_on_back
    ,
    close_on_escape
    ,
    disabled
    ), never a callback returning a boolean.
  • Do not change NSH to fit an app. Solve it in the app first: its own components, a Rust crate, or a native component.
  • TSX has no DOM, no Node and no npm UI. Standard modules are exactly
    buffer
    ,
    console
    ,
    crypto
    ,
    fs/promises
    ,
    net
    ,
    os
    ,
    path
    ,
    process
    ,
    url
    ,
    websocket
    ,
    zlib
    .
这些是最低要求,不能替代指南。
  • 绝不臆造 API。 先在 crate 源码(Rust)或
    .nsh/types
    (TSX)中查找名称, 再查阅对应的 reference page。不要根据其他 framework 类推。
  • 组件只运行一次。 在 Rust 中,如果某个值必须保持最新,请传入 closure 或 signal:
    text(move || format!("{}", count.get()))
    。在 TSX 中,在 JSX 或 closure 中读取 props 和 signals;不要对 props 解构。
  • 每项状态只有一个 owner。 页面的草稿是该页面的 signal;共享状态是使用 私有 signals 的 store。在 Rust 与 TSX 混合模式中,每项状态都由其中一侧拥有。
  • 应用决定外观。
    nsh_base
    /
    nsh/base
    提供行为但不提供外观,而且 NSH 没有 styled component library。使用 theme tokens 绘制界面:在 Rust 中是
    theme().primary
    ,在 TSX 中是
    bg-primary
    。在应用自己的 UI module 中将每个 base component 包装一次。
  • 设置样式前,parts 不会绘制任何内容。 没有 track 和 thumb 样式的 switch 即使测试通过也不会显示。为每个 part 设置样式,并查看实际结果。
  • 使用语义化 component。 Button、Menu、Popover、Sheet、Dialog 和 Select 自带 focus、keyboard、dismissal 和 accessibility。不要基于 view 重新实现它们。
  • 大量数据应放入 virtualized list, 每项状态以 id 为 key 保存在数据中, 绝不能保存在 row 中创建的 signal 中,因为 cells 会被其他 rows 复用。
  • 系统需要立即做出的 decision 应作为 prop, 并预先设置(
    pop_on_back
    、
    close_on_escape
    、
    disabled
    ),绝不能使用返回 boolean 的 callback。
  • 不要为了适应应用而修改 NSH。 首先在应用中解决:使用应用自己的 components、 Rust crate 或 native component。
  • TSX 没有 DOM、Node 或 npm UI。 标准模块严格限定为
    buffer
    、
    console
    、
    crypto
    、
    fs/promises
    、
    net
    、
    os
    、
    path
    、
    process
    、
    url
    、
    websocket
    、
    zlib
    。

The Loop

执行循环

Run your mode's loop before calling work done. Prefer check and headless tests to full builds while working.
sh
undefined
宣布工作完成前,运行所选模式的验证循环。工作期间优先使用 check 和 headless tests, 而不是完整 builds。
sh
undefined

Rust

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

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

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 run

Headless tests prove behavior, not the look: take a screenshot or run the app for
anything visible. In TSX, passing one of `nsh check` and `nsh build` proves little without
the other; [the table in tsx.md](references/tsx.md#who-reports-what) shows who reports what.
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 run

Headless 测试证明行为,而不是外观:对于任何可见内容,请截取屏幕截图或运行应用。
在 TSX 中,仅通过 `nsh check` 和 `nsh build` 中的一个几乎无法证明什么;
[tsx.md 中的表格](references/tsx.md#who-reports-what) 说明了各自负责报告的内容。

Quick Start in Rust

Rust 快速入门

rust
use 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)),
        )
}
json
{ "name": "My App", "rust": ".", "root": "App" }
rust
// 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();
}
The same app in TSX, and the TSX forms, are in tsx.md.
snake_case
Rust names are
camelCase
in TSX (
on_open_change
is
onOpenChange
).
rust
use 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)),
        )
}
json
{ "name": "My App", "rust": ".", "root": "App" }
rust
// 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();
}
同一个应用的 TSX 版本及 TSX 写法见 tsx.md。Rust 的
snake_case
名称在 TSX 中为
camelCase
(
on_open_change
对应
onOpenChange
)。

Documentation

文档

Start with the bundled mode pages and recipes; they work without an NSH checkout. For exact APIs, use the project’s
.nsh/types
in TSX and its resolved dependency source in Rust.
cargo metadata --format-version 1
reports dependency manifests and their source locations; use those paths rather than assuming a Cargo cache path. Run
nsh check
to generate missing TS declarations.
Paths starting with
examples/
,
crates/
or
website/
in the references identify files in the NSH repository; they are optional source pointers, not files in the installed skill or the user’s app. Use them when a checkout is available. Otherwise, use the bundled recipes, the online docs below, or the corresponding file at
https://github.com/longbridge/nsh
at the project’s matching tag or revision. A
website/docs/foo.md
pointer corresponds to
https://nsh.dev/docs/foo.md
;
website/elements/
and
website/modules/
similarly map to site paths.
The site follows the latest release and may describe a newer API than the project uses. Preserve the project’s version and verify examples against its types or source. Fetch individual pages as needed; use
llms-full.txt
only for broad exploration.
  • Find a page:
    https://nsh.dev/llms.txt
    , then load its Markdown;
    https://nsh.dev/llms-full.txt
    for broad reference.
  • Rust:
    https://nsh.dev/docs/rust.md
    ,
    native-components.md
    ,
    networking.md
    .
  • An element or component:
    https://nsh.dev/elements/{name}.md
    .
  • A module:
    https://nsh.dev/modules/{name}.md
    :
    nsh.md
    ,
    nsh-base.md
    ,
    nsh-mobile.md
    ,
    nsh-desktop.md
    ,
    style-reference.md
    ,
    tailwind-reference.md
    ,
    standard-modules.md
    .
  • A guide:
    https://nsh.dev/docs/{name}.md
    :
    reactivity.md
    ,
    layout.md
    ,
    navigation.md
    ,
    lists.md
    ,
    forms-and-keyboard.md
    ,
    platform-specific.md
    ,
    testing-and-trace.md
    .
先从内置的模式页面和 recipes 开始;它们无需 NSH checkout 即可使用。 对于精确 API,TSX 使用项目的
.nsh/types
,Rust 使用已解析的 dependency source。
cargo metadata --format-version 1
会报告 dependency manifests 及其 source locations; 请使用这些路径,不要假定 Cargo cache 路径。 运行
nsh check
生成缺失的 TS declarations。
以
examples/
、
crates/
或
website/
开头的路径表示 NSH repository 中的文件; 它们只是可选的 source pointers,并非已安装 skill 或用户应用中的文件。有 checkout 时可使用它们。 否则,使用内置 recipes、下方在线文档,或 GitHub URL 中与项目匹配 tag 或 revision 对应的文件。
website/docs/foo.md
pointer 对应
https://nsh.dev/docs/foo.md
;
website/elements/
和
website/modules/
也分别映射到相应的 site paths。
站点内容以最新 release 为准,可能描述比项目所用版本更新的 API。 保持项目版本不变,并根据其 types 或 source 验证示例。 按需获取单个页面;仅在广泛探索时使用
llms-full.txt
。
  • 查找页面:
    https://nsh.dev/llms.txt
    ,然后加载其 Markdown;
    https://nsh.dev/llms-full.txt
    用于广泛参考。
  • Rust:
    https://nsh.dev/docs/rust.md
    、
    native-components.md
    、
    networking.md
    。
  • 元素或 component:
    https://nsh.dev/elements/{name}.md
    。
  • 模块:
    https://nsh.dev/modules/{name}.md
    :
    nsh.md
    、
    nsh-base.md
    、
    nsh-mobile.md
    、
    nsh-desktop.md
    、
    style-reference.md
    、
    tailwind-reference.md
    、
    standard-modules.md
    。
  • 指南:
    https://nsh.dev/docs/{name}.md
    :
    reactivity.md
    、
    layout.md
    、
    navigation.md
    、
    lists.md
    、
    forms-and-keyboard.md
    、
    platform-specific.md
    、
    testing-and-trace.md
    。

Working on NSH itself

开发 NSH 本身

This skill is for building apps. In a checkout of
longbridge/nsh
, follow its
AGENTS.md
and
docs/dev/
as well.
此 skill 用于构建应用。在
longbridge/nsh
的 checkout 中,还应遵循其
AGENTS.md
和
docs/dev/
。