NatUI
Internals

Architecture

Understand the NatUI reconciler, bridge, native hosts, controlled state, and runtime paths.

┌────────────────────────── JavaScript process ──────────────────────┐
│ React application                                                  │
│       ↓                                                            │
│ react-reconciler 0.33 with a JavaScript shadow tree                │
│       ↓ atomic NDJSON commit                 ↑ native events       │
│ transport and Bridge                                               │
└───────┼──────────────────────────────────────┼──────────────────────┘
        ↓                                      │
┌────────────────────────── native host ───────┴─────────────────────┐
│ macOS: Swift and SwiftUI        │ Windows: C# and WinUI 3          │
│ observable node store           │ node registry and control map    │
│ recursive native view mapping   │ native controls and Grid layout  │
└─────────────────────────────────┴──────────────────────────────────┘

Native layout owns geometry

NatUI maps the React tree to platform declarative primitives. VStack becomes a SwiftUI VStack on macOS and a vertical native grid stack on Windows. The native toolkit owns layout proposals, focus, accessibility, theming, and control behavior.

There is no Yoga or browser layout layer. The tradeoff is intentional: applications get platform-native behavior rather than pixel-identical cross-platform rendering or CSS-style layout.

React reconciler

packages/natui uses react-reconciler mutation mode with a JavaScript shadow tree. Each instance records its id, kind, props, handlers, children, and materialization state.

Render-phase methods only construct or rearrange the shadow tree. Commit work emits protocol operations after a node attaches to a materialized parent.

The bridge buffers all operations from one React commit and sends them as one commit message. A host applies the complete batch on its UI thread before one render pass, so it never displays an intermediate commit state.

React may discard render-phase instances. Deferring protocol creation until attachment prevents abandoned nodes from leaking into the native tree.

Props and handlers

Event-handler functions remain in a JavaScript registry. The host emits an event with the node id and event name, then the bridge locates and invokes the current handler.

NatUI validates other props and deep-copies them into documented JSON values before serialization. It reports invalid nested values with their component kind and path. A commit is all-or-nothing, so serialization failure cannot partially mutate the native tree.

Controlled inputs

Native controls apply edits optimistically for immediate feedback. Each edit carries a per-node sequence number. JavaScript acknowledges the highest processed sequence on its next update.

An acknowledgement older than the host's current sequence cannot overwrite the latest local value. When the acknowledgement catches up, React becomes authoritative again, including for transformations and rejected edits.

Events run at React's discrete priority and flush synchronously. After each controlled change, the bridge checks whether React adopted the value. If not, it sends a corrective update carrying the event acknowledgement.

See controlled state and the wire protocol.

Native hosts

macOS

The Swift host stores each node in an observable model. A recursive SwiftUI view switches on the node kind without type erasure, and children use stable ids so React moves remain native moves.

The development host is a Swift Package Manager executable with native GUI application identity. It supports the Node standard-stream mode and an embedded JavaScriptCore mode. The reference packager wraps the release host, application JavaScript, compatibility manifest, and native metadata in a standard .app.

Windows

The Windows development host is an unpackaged, self-contained WinUI 3 executable. It uses a node registry and native control map. Grid-based stacks provide the flexible tracks needed by Spacer and infinite frames.

WinUI also raises events for programmatic changes, so prop application uses guards and structural equality checks to prevent feedback loops.

The base component set has real-window Node-mode verification. The host also embeds V8 for single-process browser bundles through @natui/core/inproc.

The reference packager publishes one portable, architecture-specific, self-contained EXE containing the application JavaScript and manifest as managed resources. The .NET single-file runtime extracts its runtime dependencies to a per-user temporary cache at launch.

Debug surface

The protocol includes four verification messages:

  • dump returns the host's actual native node tree.
  • emit synthesizes an interaction event.
  • edit performs a real optimistic controlled edit.
  • screenshot renders the native window to a PNG and always receives success or error.

These messages allow end-to-end assertions without general-purpose UI automation. Popup layers and some focus or accessibility behavior still need dedicated native testing.

Runtime and packaging paths

NatUI currently has three launch paths:

  • Node development: React runs in Node, spawns the native host, and exchanges NDJSON over standard streams. The development server rebuilds the application's local source graph and applies React Fast Refresh to the existing root, preserving compatible component state and native control identity without restarting Node or the native window.
  • Raw embedded development: Both hosts evaluate an esbuild browser bundle in-process, using system JavaScriptCore on macOS and V8 on Windows. The bundle and host exchange protocol messages through __natui_send and __natui_recv. Standard input remains available as a verification debug channel.
  • Packaged embedded application: pnpm package:demo builds the same embedded runtime into a macOS .app or one Windows self-contained EXE. A generated manifest records identity, platform, architecture, protocol, minimum host API, and the SHA-256 digest of main.js.

The native package loader validates the manifest before JavaScript evaluation. The embedded ready handshake repeats protocol and host API validation at the runtime boundary.

runEmbedded owns the JavaScript lifecycle. Its controller can update the root and exposes an idempotent quit() method. Shutdown synchronously unmounts React, runs effect cleanup, sends one host quit request, rejects pending bridge work, and removes the receive hook. Native window close uses the same path.

Current alpha boundaries

The repository includes 37 host components, including menus, toolbars, navigation, data views, and presentation.

Features still outside the current alpha scope include:

  • A public animation or gesture API
  • Multi-window application APIs
  • Code signing, macOS notarization, installers, and automatic updates
  • Single-instance application orchestration

Accessibility labels, hints, identifiers, and the native controls' default accessibility behavior are in scope.

On this page