| # JS modules | |
| NSH ships two layers of modules. `nsh` and `nsh/<module>` are the primitives every UI is made of, the environment and | |
| the capabilities. `nsh/base` (the crate `nsh-base`) is the unstyled foundation every app's UI builds on: navigation stacks and | |
| pages (and their status bar), dialogs, safe areas, the system's alert dialog and the software keyboard. NSH provides no styled | |
| components: the look of an app is the app's, drawn from the primitives, or from Rust components in the app's own crate, | |
| whose UI set is a module of its own (`app/native`; `docs/dev/components.md`, `tooling.md`). Each export has one home. | |
| A UI set may serve a family of exports whose names only make sense together as a module under a path of its own | |
| specifier, as `gpui-base`'s `plot` is a module of its crate: a submodule of the set (`Registry::submodule`, such as | |
| `nsh/base/<family>` of `nsh/base`), served, described and declared wherever the set is (`Registry::sets`: the hosts' | |
| module tables, the core's components, the toolchain's descriptions, one declaration file per module), so a list of UI | |
| sets names only the set. | |
| Every module is native: `nsh-js` serves it (`modules.rs`: NSH's own modules by `nsh_core::js::NATIVE_MODULES`, then the | |
| registries: the runtime's `nsh_core::runtime::registries()` and the UI sets a host serves, `nsh/base` and the app's), and the bundler keeps | |
| imports of them as imports (`plugin.rs`; how apps are compiled: `native-pipeline.md`). A module can be both: the | |
| hand-written exports of `nsh/android` and `nsh/accessibility` are joined by their registry's. There is no JS file of | |
| NSH's and no internal module apps could reach. | |
| Declarations: each export of a module of NSH's own carries its TypeScript declaration in its table (`modules::Item`: | |
| `signal`'s signatures next to `reactive::signal`), each module its own interfaces (`types`); a registry's components | |
| and modules are declared from their specs (`nsh_core::ui::declarations`, `docs/dev/components.md`), the styles, the | |
| host elements' props and events, the easings and the canvas methods from the core's tables. `nsh-js`'s | |
| `declarations.rs` assembles one file per module (`nsh/list.d.ts`, `nsh/base.d.ts`), `nsh/types.d.ts` (the | |
| element and JSX types `nsh` exports), `nsh/jsx-runtime.d.ts` and `nsh/globals.d.ts`, and tests check every module's | |
| exports against its declarations; the CLI writes them into `.nsh/types` (`tooling.md`). | |
| `nsh` itself only has the reactive API, `mount`, the primitives every UI is made of (`View`, `Text`, `Scroll`, | |
| `Input`, `Image`, `List`, `Canvas` and its classes, `WebView`, `Video`) and the types; there is no catch-all module. | |
| TSX imports mirror the Rust crates' layers: the primitives from `nsh`, as Rust takes them from | |
| `nsh_core::ui::prelude`, the base components from `nsh/base` (`nsh-base`). A released API that moves keeps its old | |
| place as a deprecated alias (`compatibility.md`), so apps built before keep running: `nsh/image`, `nsh/list`, | |
| `nsh/canvas`, `nsh/webview` and `nsh/video`, where primitives were first, export them still, the same values | |
| (`NativeModule::also` / `moved_to`: `nsh` serves those modules' exports, made once per VM whichever is imported | |
| first; their declarations are `@deprecated`, the canvas classes and the modules' types declared there once and | |
| re-exported by `nsh`). Apps import a primitive's tag from `nsh` (`native-pipeline.md`): the table below. `If` and | |
| `Each` are globals. | |
| | Module | Exports | Implemented in (`crates/nsh-js/src`) | | |
| |---|---|---| | |
| | `nsh` | `signal`, `computed`, `effect`, `batch`, `untracked`, `cleanup`, `context`, `split`, `mount`, `inputAction` (a callback over captured JSON arguments), `ErrorBoundary` (`dom.rs`: a memo of the children or the fallback, `native-pipeline.md`), `resource` (`resource.rs`: a callable object over four signals, its loads kept by `nsh_core::resource::Runs`); `View`, `Text`, `Scroll`, `Input` (host elements as components: `dom::host_component`); `Image`, `List`, `Canvas`, `Path2D`, `CanvasRenderingContext2D`, `CanvasGradient`, `WebView`, `Video` (the modules' below, the same values); `YNode`, `Accessor`, `Signal`, `Context`, `Component`, the types of styles, element props, events and `JSX` | `reactive.rs`, `dom.rs`, `modules.rs`, `input_action.rs` | | |
| | `nsh:rt` | what compiled JSX calls (`element`, `insert`, `spread`, `cond`, `each`, `list`, `font`, ...); apps never import it | `dom.rs`, `list.rs` | | |
| | (globals) | `If`, `Each` (control flow; their tags compile to `cond` / `each`) | `dom.rs` | | |
| | `nsh/animated` | `createAnimatedValue`, `AnimatedValue`, `AnimatedNode`, `Animated`, `Easing`, `LayoutAnimation` | `animated.rs` | | |
| | `nsh/device` | `windowSize()`, `colorScheme()`, `fontScale()`, `pixelRatio()`, `locale()` (accessors), `setColorScheme`, `platform` (a constant: `os`, `version`, `idiom`, `catalyst`, `renderer`, `isTerminal`, `select`) | `env.rs` | | |
| | `nsh/accessibility` | `screenReaderEnabled()`, `reduceMotion()` (accessors), `announce(message)`, `setAccessibilityFocus(node)` | `env.rs` + its registry (`nsh_core::runtime`) | | |
| | `nsh/list` (deprecated: `nsh`) | `List` | `list.rs` | | |
| | `nsh/canvas` (deprecated: `nsh`) | `Canvas`, `Path2D`, `CanvasRenderingContext2D`, `CanvasGradient` | `canvas.rs` | | |
| | `nsh/image` (deprecated: `nsh`) | `Image`: the image element's component, with `Image.getSize`, `Image.prefetch` (promises the core resolves from the root's `ImageInfo` event) | `modules.rs` + its registry (`components.rs`) | | |
| | `nsh/gesture` | `PanResponder` | `touch.rs` | | |
| | `nsh/clipboard` | `Clipboard.writeText(text)`, `Clipboard.readText()` / `hasText()`, `writeImage(src)`, `readImage()` / `hasImage()`, `readFiles()` (promises the core resolves from the root's `Clipboard` event; the `has*` reads do not read the content, so iOS shows no paste notice). Images cross as base64 `data:` URIs (an image source and a `fetch` URL, so bytes stay native until JS asks for them), files as `file:` URLs one per line; the backends build both with `nsh_core::platform::clipboard`. UIPasteboard, ClipboardManager (text only), NSPasteboard, GDK, Win32, `navigator.clipboard`; the `clipboard` capability | its registry (`nsh_core::runtime`) | | |
| | `nsh/storage` | `Storage.getItem` / `setItem` / `removeItem` / `keys` / `clear` (async functions of the core; the entries live in memory in `nsh_core::storage`, loaded once from the platform's `StorageBackend` (`EngineConfig::storage`: `nsh_engine::FileStorage::open` in `<EngineConfig::data_dir>/<app id>` (the id travels with the app: `app.json`, the dev server, packages, `Root::app_id`), one per file in the process so a reload reads unflushed writes; a damaged file is set aside as `storage.json.corrupt`, an unreadable one leaves the changes in memory, never replacing it; `localStorage` under `nsh:<app id>:` on the web) and every change handed to it; 5 MB quota; no capability: the app's own data, kept where the host says) | its registry (`nsh_core::runtime`) | | |
| | `nsh/keymap` | `keymap.bind(keystroke, handler, { when, repeat, within })` / `keymap.bind({ keystroke: handler }, options)`: keyboard shortcuts. Keystrokes are parsed and checked in Rust (`nsh_core::keymap`: syntax, `mod`, reserved keystrokes, which ones text inputs keep); the whole table goes to the platform as the root's `Prop::Keymap` → `MountItem::Keymap`, which matches keys on the spot and reports the root's `KeyCommand` event (the binding's id). Host-supported `within: YNode` restricts native matching to focus on that node or a descendant; `keymap.focusedWithin(node)` reads the host's latest focus snapshot nonreactively. Bindings made under an owner go with it; a module's top-level ones are undone when hot module replacement runs it again (`hot::undo_on_rerun`) | `keymap.rs` | | |
| | `nsh/webview`, `nsh/video` (deprecated: `nsh`) | `WebView`, `Video` | `platform_modules.rs` | | |
| | `nsh/native` | `nativeComponent(name, { props, events })`: a component for a native component the embedding app registered; `nativeModule(specifier)`: the exports of a UI set the embedding app gave its engines (`EngineConfig::ui_sets`), as one object made by the loader's code (`modules::host_module`, the same values an import of the set gets) and kept per specifier; only the host's sets, never nsh's own modules or the runtime's registries | `platform_modules.rs`, `modules.rs` | | |
| | `nsh/ios` | `isAvailable`, `InputAccessoryView` | `platform_modules.rs` | | |
| | `nsh/android` | `isAvailable`, `Toast.show(message, "short" \| "long")`, `BackHandler` (`onBackPress` native: it returns the function that removes the listener, which goes with the owner it was added under, as `keymap.bind`'s bindings do; `exitApp` Rust) | `platform_modules.rs` + its registry | | |
| | `nsh/desktop` | `isAvailable`, `titleBar()` (the environment's `title_bar`), `Menu.setApplicationMenu` / `Menu.popup` (decoded natively into `nsh_core::desktop::MenuItem`s, the items' functions kept by id, called on the root's `onMenu`), `Window` (Rust: `setTitle`, `setTitleBar`, `minimize`, `toggleMaximize`, `toggleFullScreen`, `setFrame`, `setLevel`, `orderFront`, `close`); all of it is `Command::Desktop` on the root, which mobile platforms ignore | `desktop.rs` + its registry | | |
| | `nsh/base` | The unstyled foundation of an app's UI. Navigation: `NavStack`, `NavPage`, `createNavStack`, `Dialog` (natives of `nsh-js`, `Registry::native_values`). Safe areas: `SafeAreaView` (Rust, with `Edge`), `safeArea()`, `safeAreaInsets()`, `Insets` (re-exported from the environment). Alerts: `AlertDialog.show({ title, description, actions, presentation })` (a promise of the chosen action's `id`, its `label` without one, `null` when closed without one; `presentation: "actionSheet"` is a list dialog on Android), the platform's own. A press target: `Button` (Rust; no look of its own). A checked / unchecked / indeterminate control: `Checkbox` (Rust; its indicator is a part the app styles and fills with its mark, the `indicator` slot, `checked:` / `indeterminate:` / `disabled:` following the checkbox through `Interactive::project_checked` / `project_indeterminate`). A set of options the user picks one of: `Radio` in a `RadioGroup` (Rust; the group holds no value, each radio its `checked`, the app's indicator its children with `group-checked:` classes). An on / off control: `Switch` (Rust; its track and thumb are parts the app styles, `checked:` / `disabled:` following the switch through `Interactive::project_checked`). The tabs of a tab list: `Tab` in `Tabs` (Rust; the list holds no value, each tab its `selected` and `onPress`, the app shows the selected section's content). A region that expands and collapses: `Collapsible` (Rust; its children always, its `content` slot after them while `open`, kept while collapsed; the trigger and its `expanded` state are the app's). How far a task is: `Progress` (Rust; the progress bar role with the range 0 to 100 and its value, `Accessible::accessibility_value`, none while `indeterminate`; its track and indicator are parts the app styles, the indicator `value` percent of the track's width). Documents: `TextView` (Rust; Markdown, HTML or blocks, `docs/dev/text-view.md`). The keyboard: `KeyboardAvoidingView` (Rust, with `AvoidBehavior`), `Keyboard.dismiss`, `keyboardFrame()` (re-exported from the environment). The status bar: `statusBar` of `NavPage` and `Dialog` (a host prop of screens and modals; `StatusBar`, `StatusBarForeground` declared with the navigation) over `nsh.json`'s `"statusBar"` | `nsh_base::registry()` (`crates/nsh-base`); `navigation.rs`, `env.rs` | | |
| | an app's UI set (`app/native`) | The Rust components, modules and classes of the app's crate (`#[native_module("app/native")]`) | the registry (`components.rs`, `classes.rs`) | | |
| `nsh:hot` (hot module replacement, `hot.rs`) is only imported by development bundles. The same loader also compiles | |
| the chunks of a split bundle (`Screen.js`: a name that is no native module, with code the host gave; `chunks.rs`, | |
| `native-pipeline.md`), which `import()` loads through the global `__nsh_import`. | |
| - **Terminal menus** use `nsh/desktop`: `isAvailable` is true, `Menu` controls terminal menus and `Window.setTitle` | |
| updates the host's title. `platform.idiom === "desktop" && !platform.isTerminal` still describes graphical desktop interaction; terminal apps use | |
| `platform.isTerminal` to choose terminal behavior. Window placement and desktop chrome stay with the host. | |
| - **Platform modules** are importable on both platforms (one bundle runs on both); on the other one their calls do | |
| nothing and their components render nothing. `isAvailable` tells which. | |
| - **Platform-only props** are grouped: `ios={{…}}` / `android={{…}}` / `web={{…}}` / `desktop={{…}}` (macOS, Windows, Linux, and browsers in the desktop idiom) on | |
| elements and components (`IOSProps` / `AndroidProps` / `DesktopProps` in `types.d.ts`, extended per element; the host | |
| props in them are the generated `IOS…HostProps` / `Android…HostProps`). The renderer | |
| (`platform_props` in `dom.rs`) sends only the current platform's object, each key as a prop of its own (keys removed | |
| from the object are cleared); the core knows them by those names. Android: `ripple`, `elevation`, | |
| `accessibilityLiveRegion`, Dialog `statusBarTranslucent`. iOS: `backdropMaterial` (the system material of a | |
| backdrop blur), input `inputAccessoryViewID` / `textContentType` / `keyboardAppearance`, Dialog | |
| `supportedOrientations`, WebView `allowsBackForwardNavigationGestures`. Desktop: | |
| `windowDrag`, WebView `allowsBackForwardNavigationGestures` (macOS' trackpad swipes, Windows' touchscreen swipes; a | |
| prop of several platforms names each: `#[prop(ios = "x", desktop = "x")]`). What two platforms do is a top-level prop, ignored where it does not apply (`presentation`, | |
| `headerLargeTitle`, `headerBackTitle`, AlertDialog's `closeOnEscape` / `closeOnBackdropPress`; `api-naming.md`). | |
| - A module's exports are made when a bundle first imports it or looks it up with `nativeModule`; what must always happen (the root's direction, the | |
| themes) is the renderer's (`dom.rs`, `setup`), not a module's. | |
| ## Host-owned values | |
| An embedding host can add `Registry::value(name, factory)` to a registry it supplies through | |
| `EngineConfig::ui_sets` (or directly through `HostOptions::ui_sets`). The factory is a `Send + Sync` closure; | |
| it runs on the VM's thread on the first import or `nativeModule` lookup of the module. Both access paths | |
| share that VM's value, including when they are used in either order; a new VM or a reload constructs a new one. | |
| Compiling bytecode, generating declarations and enumerating | |
| exports never execute factories. The returned `Ret` value uses the same typed conversions as Rust methods; | |
| the classes it names and `export declare const` are generated with the registry's other declarations. | |
| Factories can capture a scoped native client and return `Instance::new(ClientHandle { ... })`. A class | |
| without a constructor exposes only the operations of that existing client. The embedding host owns the | |
| client's authority, cancellation and external resource cleanup; disposing a JS object or stopping the VM | |
| releases its local handles, while completion of an admitted operation belongs to the external service. | |
| Factory panics and conversion failures reject module evaluation. Conflicting host export names fail | |
| instead of selecting one registry's value. | |
| ## Captured input actions | |
| `inputAction<A>(handler: (args: A) => unknown, args: A): () => void` captures explicit JSON-compatible | |
| arguments and returns a callable that passes only that snapshot to `handler`; incoming event arguments and | |
| the handler's return value are ignored. Use `null` or `[]` for an action with no arguments. Capture reads | |
| getters and Proxy traps, copies the normalized JSON and freezes every captured object and array. The native | |
| JSON bounds apply: 64 levels, 1,048,576 entries and 8 MiB of canonical UTF-8 JSON. | |
| Choose the action and its target from captured arguments. Applications and host services may check current | |
| business state to reject stale work while keeping the captured target and arguments unchanged. Event handlers | |
| follow ordinary native input dispatch against the current UI. Argument capture does not prove which rendered | |
| frame the user saw or grant service authority. | |
| ## Native keymap focus | |
| `KeyBindingOptions.within?: YNode` is an additional native eligibility condition. A supporting backend tries bindings | |
| newest first and skips a scope unless the focused node or one of its mounted ancestors is its `within` node; | |
| older eligible bindings and ordinary native input keep their normal precedence. `within` must be a real node | |
| of that VM. Supplying it without native focus-scope support throws `TypeError`. | |
| `keymap.focusedWithin(node: YNode): boolean` reads the same native focus path from the latest snapshot the host | |
| has supplied. It returns `false` for an empty path or a node outside it, including a removed node. This read | |
| does not subscribe an effect to focus or cause an effect to run. An effect triggered by `windowSize()` can read | |
| the current snapshot during its resize work. Calling the method without host support throws `TypeError`. | |
| `Environment.native_focus_scopes` declares backend support before the first render; the terminal also | |
| retains its existing implicit support. This capability is not a reactive JS environment part. | |
| The host supplies focus before delivering input or environment changes that can enter JS. `AppView` maintains | |
| this ordering; hosts wiring their own `Surface` and engine use the | |
| [focus-path embedding APIs](tui.md#public-embedding-api). | |
| ## Where new things go | |
| Names follow `gpui-base`'s, translated to TSX: before naming anything apps use, read `api-naming.md` (the conventions | |
| and the renames decided so far). | |
| - A primitive, the environment or a capability: a module of NSH's own (`nsh/<module>`), natives in `crates/nsh-js` | |
| listed in `modules.rs` and `nsh_core::js::NATIVE_MODULES`, each export with its declaration (`Ts`) and the | |
| module's interfaces in its `types`. Functions that only ask the platform (a command, a request answered by an event) | |
| are a Rust `#[module]` in `crates/nsh-core/src/runtime/`, registered in `runtime::registries()` under the module's | |
| specifier: their JS face and declarations come from the spec, with nothing written per function. | |
| - An unstyled base component (`nsh-base` is NSH's `gpui-base`, `architecture.md` "Layers"): behavior, state, focus, | |
| keyboard or accessibility a UI needs, or system UI (insets, the keyboard's avoidance, the status bar's setting, a | |
| dialog; controls such as a toggle, a slider or a popover as their behavior is written): a Rust component or | |
| `#[module]` in `crates/nsh-base`, registered in `nsh_base::registry()` (`docs/dev/components.md`). It never styles | |
| (no colors, paddings, radii, fonts or variants of its own), takes the app's styles, exposes its parts and reports | |
| changes of state the app owns. | |
| - A styled widget (a button with a look, a card, a list row): never in NSH, which has no `gpui-component`. Apps | |
| draw their own on `nsh/base` and the primitives, or ship them, as a third party does, as Rust components in a | |
| crate of their own (`docs/dev/components.md`). | |
| - Something only one platform has: `nsh/ios` / `nsh/android`, or a prop in `ios={{…}}` / `android={{…}}` / | |
| `desktop={{…}}`. | |
| - Never a React Native compatibility wrapper. Something released that moves keeps its old name and module as a | |
| deprecated alias (`compatibility.md`); something unreleased simply moves. | |
| ## Standard modules | |
| The standard catalog is eleven bare specifiers, `nsh_core::js::STANDARD_MODULES`, shared by the bundler (which | |
| keeps their imports) and the loader: `buffer`, `console`, `crypto`, `fs/promises`, `net`, `os`, `path`, | |
| `process`, `url`, `websocket` and `zlib`. Only these names import: `node:buffer` and the Node modules outside the | |
| catalog (`fs`, `events`, `assert`, `util`, `string_decoder`, `child_process`, ...) are refused (tests: | |
| `nsh-js` `tests/standard/imports.rs`, which also checks that each module's runtime exports are its declared ones). | |
| The loader asks the module table first (typed registries own their names), then `standard::load`. | |
| | Module | Where | Exports | | |
| |---|---|---| | |
| | `os` | `nsh_core::runtime::os` (`#[module(flat, default)]`) | `platform()`, `arch()`, `homedir()`, `EOL`, default | | |
| | `fs/promises` | `nsh_core::runtime::fs` | `readFile`, `writeFile`, `readdir`, `exists`, `unlink`, `rmdir`, `mkdir`, `stat`, `lstat`, `rename`, `readlink`, default; `Stats` and `Dirent` are `#[class(interface)]` (declared, no value) | | |
| | `process` | `nsh_core::runtime::process`, `nsh-js` `standard/process.rs` | `run`, `spawn`, `exit`, `platform`, `arch`, `nextTick`, `ChildProcess`, default (also `env` / `dataDir` getters); the default object is the global `process` | | |
| | `net` | `nsh_core::runtime::net` | `connect`, `Socket`, default | | |
| | `console` | `nsh-js` `standard/console.rs` | default: the VM's `console` (`log`, `info`, `debug`, `warn`, `error`) | | |
| | `websocket` | `nsh_core::runtime::websocket`, `nsh-js` `standard/websocket.rs` (the global) | `WebSocket` (`connect`), `WebSocketConnection`; the global `WebSocket` constructor | | |
| | `buffer`, `crypto`, `path`, `url`, `zlib` | LLRT, vendored by `quickjs-jit-stdlib` (`standard/mod.rs`) | their LLRT exports, projected by `standard/declarations.rs`; `Buffer` and `crypto` are globals | | |
| `process.platform`, `process.arch`, `os.platform()` and `os.arch()` are Rust's `std::env::consts` values, as | |
| gpui-shell's are: `"macos"`, `"windows"`, `"linux"`, `"ios"`, `"android"`; `"aarch64"`, `"x86_64"` (not Node's | |
| `darwin` / `win32` / `arm64` / `x64`); in WebAssembly they are empty strings, and `platform.os` (`nsh/device`) is | |
| `"web"`. | |
| The typed modules' JS face and `.nsh/types` come from their specs (`#[module]`, `#[class]`; `docs/dev/rust-classes.md`): | |
| grants and limits throw in the call (`Later`, `JsError`: a `TypeError` or `RangeError`), I/O failures reject, and | |
| the work runs as core tasks on the engine's tokio runtime (`fs/promises` on its blocking threads). A call's | |
| `System` carries the VM's `Access`, data directory, `TrustedPaths`, `Quotas` and per-VM state (`Locals`: | |
| `process`'s children, killed at once and joined together when the VM goes). `process.spawn` returns its | |
| `ChildProcess` in the call; a core task posts the child's output to the `Callback`s `on(..)` keeps; `nsh-js` | |
| adds what needs the VM: the global `process` (the module's default object, one per VM) and `nextTick`, a JS job. | |
| `websocket`'s URL, options, grant (`Access::allows_websocket`) and limits are checked in the call, the handshake | |
| uses the configured `WebSocketTransport` (browser WebSocket on web, the native transport elsewhere). On native hosts a task spawned per connection drives the socket (answering pings, keeping up to eight unread | |
| messages); connections count under the VM's `Quotas` kind `runtime::WEBSOCKETS`, which the global `WebSocket` | |
| (`nsh-js` `standard/websocket.rs`, made on first use) shares. `os.homedir()` and `process.dataDir` are metadata | |
| and grant no file access. On WebAssembly, `os.homedir()` returns a typed error and `process.env` an empty object; | |
| both avoid `std::env`, whose accessors panic on `wasm32-unknown-unknown`. | |
| QuickJS and the LLRT modules come through `quickjs-jit` and `quickjs-jit-stdlib` on native and WebAssembly targets. The WebAssembly build uses the interpreter and pregenerated bindings; the portable standard-library implementation supplies `buffer`, `crypto`, `path`, `url`, `zlib` and `zlib-zstd`, including synchronous hashing and compression. Browser entropy reaches Rust through the player's `crypto.getRandomValues` import (`standard/random.rs`); wasm builds select getrandom's custom backend. All eleven catalog modules load on both targets, with `Buffer` and `crypto` globals. On WebAssembly, `path` uses POSIX separators and a virtual `/` working directory. The crates are | |
| published together at one version and pinned exactly (`=0.14.1`), with no git dependency or patch. `standard/declarations.rs` projects their actual exports from checksummed upstream assets in | |
| `crates/nsh-js/third_party/llrt-types`. `zlib` bounds each decompression (64 MiB, or the heap limit when smaller). NSH's WHATWG constructors (URL, Blob, streams, events) | |
| keep their identity. `console` declares the global `console`'s type next to its implementation. The | |
| declared interfaces define the API: Node-shaped subsets compatible with gpui-shell's, not Node.js itself; where | |
| they differ (a symlink `exists`, listeners get the payload only, `Stats`' methods are on its prototype, the child | |
| environment and `cwd` rules), `website/modules/standard-modules.md` says so. | |
| On web, `System` receives a `BrowserFileSystem` and `BrowserWebSocket` from the player. `fs/promises` checks capabilities and quotas in Rust, then sends app-relative operations to the browser's persistent OPFS namespace. Browser WebSocket I/O crosses the transport bridge while Rust retains grants, size/count bounds and deadlines; custom handshake headers are rejected explicitly. `process.run` / `spawn` and raw TCP remain callable exports that report typed platform errors in a browser. `process.env` stays empty there. |