How the Native SDK Dev Workflow Enables Hot Reload While Preserving State

The native dev command watches .native markup sources via the watch_path field in canvas.ui_markup.MarkupOptions and re-evaluates the UI tree on every change while preserving widget provenance and leaving the underlying application model untouched.

The vercel-labs/native repository delivers a fast, iterative development loop for building cross-platform apps. The Native SDK dev workflow enables hot reload while preserving state by strictly separating declarative markup from imperative application logic, so developers can edit .native files and see instant visual updates without losing counters, selections, or network sessions.

How native dev Bootstraps the Watch Loop

When you run native dev inside a project directory, the CLI compiles the Zig or TypeScript core in debug mode and initializes a file watcher. In both src/tooling/templates.zig and src/app_runner/ts_core_main.zig, the generated entry point configures canvas.ui_markup.MarkupOptions with a watch_path pointing to the markup source:

.markup = .{ .source = app_markup, .watch_path = "src/app.native", .io = init.io },

This single option tells the runtime which file to monitor for changes while the app is running.

Live Reload Inside the Runtime

The core reload logic lives in src/runtime/ui_app.zig. When the file specified by watch_path changes on disk, the runtime performs a targeted update rather than a full application restart.

Detecting File Changes via watch_path

The runtime receives the optional watch_path at startup. On every detected change, it invokes readMarkupFile to re-read the markup from disk, resolves any imports, and prepares a fresh UI tree for reconciliation.

Rebuilding the UI Tree

After the markup is parsed, the runtime reconstructs the view hierarchy. Because the application model lives entirely in Zig or TypeScript code—outside the markup layer—fields such as virtual variables, session stores, and network handles remain untouched while the visual surface is rebuilt.

Preserving Widget IDs Through Provenance

State survival depends on the provenance system. Inside ui_app.zig, the self.provenance.watching logic matches logical widgets across reloads by keeping their identities stable. Instead of destroying and recreating every widget, the runtime reconciles the old and new trees based on provenance IDs. This preserves internal widget state, focus, and scroll position.

The mechanism is verified by the UI-App test suite in src/runtime/ui_app_tests.zig, which writes temporary markup files, mutates them on disk, and asserts that widget IDs remain stable while the UI reflects the latest markup changes.

Automatic Model-Binding Refresh

Because the markup is re-evaluated on the fly, data bindings to the model are refreshed automatically. The compile-time model contract—enforced by native check—and the runtime binder both re-execute during reload, guaranteeing type-safe updates. You can add a new bound attribute or rename a markup property and see the result instantly without restarting the app or resetting its state.

Simultaneous Web Frontend Hot-Reload

The CLI is not limited to native surfaces. When configured, native dev also starts a frontend dev server such as Next.js, Vite, React, Svelte, or Vue. This lets mixed native-WebView applications hot-reload both their .native markup and their web UI in parallel.

Programmatic Hot-Reload Configuration

You can enable the same behavior outside the CLI by configuring MarkupOptions directly.

Zig Usage

const ui = canvas.ui_markup.MarkupOptions{
    .source = @embedFile("src/app.native"),
    .watch_path = "src/app.native", // <-- enables hot reload
    .io = std.io.getStdOut(),
};
var app = try UiApp.init(allocator, ui);
defer app.deinit();
try app.run(); // while running, edits to src/app.native are reloaded

TypeScript Core Usage

import { native } from "native-sdk";

await native.dev({
  markup: { source: await readFile("src/app.native"), watchPath: "src/app.native" },
});

Summary

  • native dev watches .native markup via the watch_path field declared in src/tooling/templates.zig and src/app_runner/ts_core_main.zig.
  • The runtime at src/runtime/ui_app.zig calls readMarkupFile on change and rebuilds only the UI tree.
  • Provenance (self.provenance.watching) preserves logical widget identities across reloads.
  • Application state survives because the model lives in Zig/TypeScript code, not in the markup layer.
  • Model bindings refresh automatically through native check and the runtime binder.
  • Mixed native and web apps can hot-reload both surfaces concurrently.

Frequently Asked Questions

What file triggers hot reload in the Native SDK dev workflow?

The path defined by the watch_path field inside canvas.ui_markup.MarkupOptions, typically pointing to a .native file such as src/app.native. Both src/tooling/templates.zig and src/app_runner/ts_core_main.zig set this option for generated projects.

How does the runtime keep widget state intact during a reload?

The self.provenance.watching logic in src/runtime/ui_app.zig tracks stable widget identities across markup versions. By matching provenance IDs, the runtime reconciles the old and new trees instead of recreating widgets from scratch.

Why does application data survive when markup changes?

Application logic and state reside in the Zig or TypeScript core, completely separate from the declarative markup. Only the UI tree is rebuilt on reload; the underlying model, session fields, and network state remain in memory.

Can I hot reload both native markup and a web frontend at the same time?

Yes. The native dev CLI can concurrently launch a frontend dev server—such as Next.js, Vite, React, Svelte, or Vue—so mixed native-WebView projects receive live updates on both surfaces simultaneously.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →