App Model

A zero-native app provides a name, optional WebView content, and optional lifecycle callbacks. The runtime owns the event loop, windows, native views, and native services; the platform owns the web engine and OS host.

The App struct

FieldTypeDescription
context*anyopaquePointer to your app state (required)
name[]const u8App name used in traces and automation snapshots (required)
sourceWebViewSourceInitial WebView content for compatibility startup windows; defaults to empty HTML
source_fn?fn(*anyopaque) !WebViewSourceDynamic source resolver (overrides source when set)
scene_fn?fn(*anyopaque) !ShellConfigDeclarative native window and view tree; when set, startup uses the scene instead of the compatibility window list
start_fn?fn(*anyopaque, *Runtime) !voidCalled after the runtime starts, before startup scene or source loading
event_fn?fn(*anyopaque, *Runtime, Event) !voidCalled on every runtime event (lifecycle + commands)
stop_fn?fn(*anyopaque, *Runtime) !voidCalled before the runtime shuts down

All callback fields are optional. A minimal app only needs context and name; provide source or source_fn when the app has WebView content, and scene_fn when startup should use a declarative native shell.

Startup shape

Without scene_fn, zero-native uses the compatibility path: it loads source or source_fn into the configured startup window list.

With scene_fn, zero-native materializes the returned ShellConfig as native shell windows and views. The first scene window adopts the startup native window; additional scene windows are created through the window service. App.source or source_fn still provides the main WebView content for those windows.

Scene windows and views are kept as resize layout bindings, so return slices backed by static or app-owned storage that lives as long as the window.

const shell_views = [_]zero_native.ShellView{
    .{ .label = "toolbar", .kind = .toolbar, .edge = .top, .height = 52 },
    .{ .label = "refresh", .kind = .button, .parent = "toolbar", .text = "Refresh", .command = "app.refresh" },
    .{ .label = "main", .kind = .webview, .url = "zero://inline", .fill = true },
    .{ .label = "status", .kind = .statusbar, .edge = .bottom, .height = 28, .text = "Ready" },
};
const shell_windows = [_]zero_native.ShellWindow{.{
    .label = "main",
    .title = "Acme",
    .width = 1100,
    .height = 760,
    .views = &shell_views,
}};
const shell_scene: zero_native.ShellConfig = .{ .windows = &shell_windows };

fn scene(context: *anyopaque) anyerror!zero_native.ShellConfig {
    _ = context;
    return shell_scene;
}

fn app(self: *AppState) zero_native.App {
    return .{
        .context = self,
        .name = "acme",
        .source = zero_native.WebViewSource.html("<main>Content</main>"),
        .scene_fn = scene,
        .event_fn = event,
    };
}

WebViewSource

Three constructors for specifying what the WebView loads:

  • .html(content) -- inline HTML string, served as zero://inline
  • .url(address) -- load a remote or local URL
  • .assets(options) -- serve a local file tree through a custom origin

The assets constructor takes a WebViewAssetSource:

.source = zero_native.WebViewSource.assets(.{
    .root_path = "dist",
    .entry = "index.html",      // default
    .origin = "zero://app",     // default
    .spa_fallback = true,       // default
}),
FieldDefaultDescription
root_pathrequiredPath to the directory containing frontend assets
entry"index.html"HTML entry point within the root path
origin"zero://app"Origin used for asset URLs
spa_fallbacktrueServe entry for unknown routes (SPA mode)

Lifecycle events

The runtime dispatches LifecycleEvent values through your event_fn:

  • start -- the app runtime has started and startup scene or source loading is about to run
  • activate -- the app became the active foreground app
  • deactivate -- the app resigned active foreground status
  • frame -- a frame has been requested (for animations or state updates)
  • stop -- the app is shutting down

Native hosts also emit app:activate and app:deactivate to each open window.zero instance:

window.zero.on("app:activate", () => {
  refreshForegroundState();
});

Native file drops dispatch Event.files_dropped to event_fn and emit drop:files to trusted window.zero instances:

window.zero.on("drop:files", (event) => {
  console.log(event.paths);
});

The runner pattern

The generated src/runner.zig wires the runtime with platform services:

  1. Selects the platform (macOS, Linux, or null for headless tests)
  2. Sets up trace sinks (stdout + file) via FanoutTraceSink
  3. Installs panic capture so crashes write last-panic.txt
  4. Initializes window state persistence from windows.zon
  5. Creates the Runtime with all options and calls runtime.run(app)
var runtime = zero_native.Runtime.init(.{
    .platform = my_platform,
    .trace_sink = fanout.sink(),
    .bridge = my_app.bridge(),
    .builtin_bridge = .{ .enabled = true, .commands = &builtin_policies },
    .security = .{
        .permissions = &app_permissions,
        .navigation = .{ .allowed_origins = &.{ "zero://app" } },
    },
    .js_window_api = true,
    .window_state_store = state_store,
    .automation = if (build_options.automation) automation_server else null,
});
try runtime.run(my_app.app());

RuntimeOptions

FieldTypeDefaultDescription
platformPlatformrequiredPlatform abstraction (macOS, Linux, or NullPlatform)
trace_sink?trace.SinknullDestination for structured trace records
log_path?[]const u8nullPath for persistent log file
extensions?ModuleRegistrynullExtension modules with lifecycle hooks
bridge?BridgeDispatchernullApp-defined bridge commands and handlers
builtin_bridgeBridgePolicy.Policy for built-in commands (dialogs, windows)
securitySecurityPolicy.Navigation allowlist, external links, permissions
automation?automation.ServernullFile-based automation server for testing
window_state_store?window_state.StorenullPersistent window geometry and state
js_window_apiboolfalseExpose built-in window.zero helper namespaces for trusted app chrome. Command helpers require origin and command checks, view helpers require origin and view checks, and window, WebView, and platform helpers require origin and window checks. Dialog, OS, clipboard, and credential helpers still require explicit builtin_bridge policy.

Runtime methods

MethodDescription
init(options) RuntimeCreate a runtime
run(app) !voidEnter the platform event loop
createWindow(options) !WindowInfoOpen a new window
listWindows() []WindowInfoList open windows
focusWindow(id) !voidBring a window to front
closeWindow(id) !voidClose a window
invalidate()Request a redraw
invalidateFor(reason, dirty_region)Request a redraw with reason and optional dirty region
frameDiagnostics() FrameDiagnosticsReturn stats from the last frame
dispatchEvent(event)Inject a synthetic event
dispatchPlatformEvent(app, event)Forward a platform event
automationSnapshot()Write state to automation directory