Skip to content

Native Shell v0.2.0

Choose a tag to compare

@github-actions github-actions released this 22 Oct 03:29
· 333 commits to main since this release
669b62b

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/base holds unstyled components that own behavior, state, focus and accessibility and leave every visual choice to the app: NavStack / NavPage, Dialog and AlertDialog. Rust apps present dialogs and navigation from nsh_base too. (#167, #236)
  • Controls: Switch, Checkbox (checked, unchecked or indeterminate), Radio / RadioGroup and Tab / Tabs. Style their states with Tailwind's checked: or checkedStyle. (#256, #259, #276, #296, #192)
  • Popups: the Overlay primitive places content against an anchor; Popover, Menu / ContextMenu, Tooltip and Sheet are 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 (neutral by default), with the app's own tokens and token(); 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, websocket and zlib, 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/storage keeps an app's data on the device, per app id. (#110)
  • Clipboard: read and write images and copied files. (#151)
  • Async data: resource loads 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 new and calls Rust without JSON; only pub functions 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.tsx headless, on a manual clock when asked, and replaces nsh trace. (#187, #188, #189, #205)
  • On real platforms: nsh test --target runs 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.json chooses import conditions and aliases; apps bundle without reading tsconfig.json; named imports a native module does not export, text outside a Text and components that take their props apart are reported. (#138, #164, #172, #176, #177, #186)
  • NSH's generated files live in .nsh, and nsh package writes to .nsh/out. nsh run configures 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 follow verticalAlign. (#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, onHoverMove and file drops on desktop views. (#222, #113, #141, #93)
  • Focus and accessibility: focusable keeps 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.orderFront and setFrame's anchorTop; 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 --limits acts as a host. (#150, #169)
  • Packages: nsh publish sets 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, linux

Rust 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

Full Changelog: v0.1.1...v0.2.0