Repository navigation
Native Shell v0.2.0
Native Shell 0.2.0 adds nsh/base, unstyled base components for navigation, dialogs, controls, popups and documents, and a capability-controlled set of standard modules (fs/promises, process, net, websocket, crypto and more). nsh test runs an app's tests headless or on every platform, and the theme is now shadcn/ui's tokens. It also replaces the React Native–shaped API with Native Shell's own names: see Breaking Changes, and rebuild apps packaged for 0.1.
Components
- Base components:
nsh/baseholds unstyled components that own behavior, state, focus and accessibility and leave every visual choice to the app:NavStack/NavPage,DialogandAlertDialog. Rust apps present dialogs and navigation fromnsh_basetoo. (#167, #236) - Controls:
Switch,Checkbox(checked, unchecked or indeterminate),Radio/RadioGroupandTab/Tabs. Style their states with Tailwind'schecked:orcheckedStyle. (#256, #259, #276, #296, #192) - Popups: the
Overlayprimitive places content against an anchor;Popover,Menu/ContextMenu,TooltipandSheetare built on it. Any view can open its own menu on a secondary click. (#253, #143, #202) - TextView: render Markdown and HTML documents, with selection across texts. (#227)
- Theme: the theme is shadcn/ui's tokens (
neutralby default), with the app's own tokens andtoken(); Tailwind classes name the tokens (bg-primary,text-muted-foreground). (#304, #207, #142)
Runtime and modules
- Standard modules: eleven bare imports,
buffer,console,crypto,fs/promises,net,os,path,process,url,websocketandzlib, with grants, quotas and cancellation checked in Rust. All of them load on the web, where files live in app-private OPFS. (#145) - Storage:
nsh/storagekeeps an app's data on the device, per appid. (#110) - Clipboard: read and write images and copied files. (#151)
- Async data:
resourceloads data asynchronously, in TSX and in Rust. (#179) - Host functions: apps call functions the embedding app registers at run time. (#148)
- Effects run the function they return as their cleanup; refs work on signals and fields; unhandled promise rejections are logged as browsers report them. (#183, #175, #163)
Rust components and apps with Rust crates
- Rust classes: TypeScript uses Rust types as classes with
newand calls Rust without JSON; onlypubfunctions are exported, with clearer TypeScript. (#87, #155) - App crates: an app lists one or several Rust crates, each imported on its own, built from the nsh its crates depend on and without a command line of their own; Rust components nest on one host, modules group functions under a function, and components get state styles. (#86, #88, #154, #136, #203, #237)
- Web: apps with Rust components package and run on the web, and their crates can use wasm-bindgen. (#78, #228)
- Templates:
nsh init --template ts|rust|mixed, each with a test of its own. (#146, #267, #310)
Testing and tooling
nsh test: runs an app's*.test.tsxheadless, on a manual clock when asked, and replacesnsh trace. (#187, #188, #189, #205)- On real platforms:
nsh test --targetruns the same tests with native input in GPUI windows, on Linux and Windows hosts, in an iOS simulator and on Android devices. (#223, #232, #234, #242) - Bundler:
nsh.jsonchooses import conditions and aliases; apps bundle without readingtsconfig.json; named imports a native module does not export, text outside aTextand components that take their props apart are reported. (#138, #164, #172, #176, #177, #186) - NSH's generated files live in
.nsh, andnsh packagewrites to.nsh/out.nsh runconfigures development windows and runs Git-backed apps; source installs show their Git revision. (#219, #115, #213, #220)
Elements, layout and styles
- Imports: every primitive is exported from
nsh(View,Text,Scroll,Input,Overlay,Image,List,Canvas,WebView,Video). (#240) - CSS layout: grids and gradients lay out as CSS does;
overflow: "hidden"clips in layout; views inside text followverticalAlign. (#181, #258, #295) - Images: SVG images on every platform, and the @1x, @2x or @3x variant that fits the screen. (#230, #229)
- Input: input method compositions are reported and kept going; mouse movement,
onHoverMoveand file drops on desktop views. (#222, #113, #141, #93) - Focus and accessibility:
focusablekeeps a pressable view out of the keyboard focus; disabled views are announced as disabled. (#139, #195) - Lists: sticky headers stick a distance from the top, and stay pinned with their recycled cells in place. (#285, #280, #281)
Platforms and embedding
- GPUI is the backend for embedding in GPUI / GPUI Kit apps; standalone apps run on each OS's own backend (AppKit, Win32, GTK 4). Embedding apps set VM limits and connect to a dev server. (#217, #99, #173)
- iOS: the embed SDK ships as dynamic frameworks. (#244)
- macOS: native desktop title bars, graceful shutdown, keyboard focus across pressable views and closing modals, transparent web views and
import()chunks served from a directory. (#92, #91, #94, #95, #140, #116, #211) - Desktop: scroll indicators appear near their thumbs on hover;
Window.orderFrontandsetFrame'sanchorTop; one copy of the desktop backends' shared logic lives in the core. (#224, #215, #114, #218) - Web: published pages fetch each file once; scroll snapping reaches the final interval. (#252, #157)
- Embedding: hosts set their apps' locale and text size; only embedded apps are limited, and
nsh run --limitsacts as a host. (#150, #169) - Packages:
nsh publishsets a package's minimum runtime; packages from newer signers run on older runtimes; hosts accept a package once it proved healthy; errors carry their kind, stack and package, with positions mapped offline. (#101, #103, #137, #104, #106)
Performance
- A screen's and a modal's content is built once, one changing child updates without remaking its siblings, and accessibility writes only the attributes that changed. (#119, #174, #118)
Breaking Changes
Base components move to nsh/base (#167)
-import { Stack, Screen, Modal, createNavigator, Alert } from "nsh/ui";
+import { NavStack, NavPage, Dialog, createNavStack, AlertDialog } from "nsh/base";
-<Stack navigator={nav}>{(r) => <Screen title="Home" preventBack={dirty()} onBackRequest={ask}>…</Screen>}</Stack>
+<NavStack state={nav}>{(r) => <NavPage title="Home" header={<MyHeader />} popOnBack={!dirty()} onBackPress={ask}>…</NavPage>}</NavStack>
-<Modal visible={open()} onRequestClose={() => open.set(false)} transparent>…</Modal>
+<Dialog open={open()} onOpenChange={open.set} class="m-auto w-80 rounded-xl bg-white" backdropClass="bg-black/50">…</Dialog>-<View activeOpacity={0.6} accessibilityRole="button" onPress={go} />
+<View activeStyle={{ opacity: 0.6 }} role="button" onPress={go} />
-<Input onChangeText={set} onSubmitEditing={send} secureTextEntry />
+<Input onChange={set} onSubmit={send} masked />
-<List ListHeaderComponent={<Title />} contentContainerStyle={{ padding: 16 }} />
+<List header={<Title />} contentStyle={{ padding: 16 }} />NSH draws no look of its own: desktop and web apps that relied on NSH's page header give their pages a header.
Native Shell's names for props, events and modules (#284, #283, #274)
-<Scroll scrollEnabled={false} showsIndicator={false} onMomentumScrollEnd={done} />
+<Scroll scrollable={false} scrollbar={false} onMomentumEnd={done} />
-<List onEndReached={more} onEndReachedThreshold={0.5} />
+<List onLoadMore={more} loadMoreThreshold={0.5} />
-<View onLayout={(e) => width.set(e.width)} />
+<View onLayout={(bounds) => width.set(bounds.size.width)} />
-<Image src={{ uri: url }} defaultSource={logo} />
+<Image src={url} placeholder={logo} />
-<WebView source="https://example.com" />
+<WebView src="https://example.com" />
-Platform.OS; PixelRatio.get(); Clipboard.setString(t);
+platform.os; pixelRatio(); await Clipboard.writeText(t);Event payloads use typed geometry (e.offset.y, e.position.x, e.size.width), and window.ReactNativeWebView.postMessage is window.nsh.postMessage. The pull requests list every rename.
shadcn/ui theme tokens (#304)
-{ "themes": [{ "mode": "dark", "radius": 12, "colors": { "primary.background": "[#7](https://github.com/longbridge/native-shell/pull/7)c3aed" } }] }
+{ "cssVars": { "theme": { "radius": "0.75rem" }, "dark": { "primary": "[#7](https://github.com/longbridge/native-shell/pull/7)c3aed" } } }
-class="bg-danger text-link bg-list-head"
+class="bg-destructive text-primary bg-muted"nsh.json needs an id to package and publish (#110)
{
"name": "Orders",
+ "id": "com.yourcompany.orders"
}Grids stretch their auto tracks as CSS does (#181)
A grid with a definite size and no alignContent / justifyContent shares its size among its auto tracks; alignContent: "flex-start" keeps the old layout.
Command line (#189, #232, #217)
-nsh trace App.tsx --tap <SELECTOR>
-nsh run --script <STEPS>
+echo '<steps>' | nsh test App.tsx --trace
+echo '<steps>' | nsh test App.tsx --target macos
-nsh package my-app --target gpui
+nsh package my-app --target macos # or windows, linuxRust API (#207, #304, #169, #236)
-nsh_core::ui::ambient::theme().primary
+nsh_base::theme().primary
-Limits::standalone_desktop() / Limits::standalone_mobile()
+Limits::standalone()
-stack().child(screen().title("Home"))
+nav_stack().child(nav_page().title("Home"))New Contributors
- @madcodelife made their first contribution in #99
- @Heyana made their first contribution in #107
- @ohdarling made their first contribution in #244
- @dXnSix made their first contribution in #230
- @xianguoGou made their first contribution in #252
- @LeeDashan made their first contribution in #282
- @mengyinghu-sys made their first contribution in #295
Full Changelog: v0.1.1...v0.2.0