Desktop architecture

Verve desktop apps are native executables embedding the OS webview — WKWebView (macOS), WebView2 (Windows), WebKitGTK (Linux). Assets are embedded in the binary and served over a custom verve://app/ scheme; the frontend talks to Zig through a typed IPC bridge. No HTTP server, no open ports.

Start with the desktop hello world for the scaffold walkthrough.

The window

var window = try desktop.Window.init(allocator, .{
    .title = "My app",
    .width = 900,
    .height = 600,
    .devtools = true,
    .assets = asset_entries,        // build-embedded frontend files
    .initial_path = "index.html",
});
defer window.deinit();
window.setMessageHandler(handlers.onMessage, ctx_ptr);
window.run();                        // blocks on the native event loop

WindowOptions also carries event callbacks: on_url_open (deep links), on_drag_drop (file paths), on_resize, on_focus, on_close (return true to allow), and dev_assets (serve from disk in dev, embedded in prod).

Window methods worth knowing: setTitle, loadUrl, loadHtml, evalJs, openChildWindow (multi-window), print / printWithOptions, snapshot (window capture), cookies(), clipboard().

Typed IPC

The bridge injects window.verve at document-start:

const reply = await window.verve.request({ type: "greet", name: "Ada" });
window.verve.send(json);          // one-way
window.verve.onMessage(fn);       // Zig → JS pushes

Zig side: a comptime route table. Each public decl is a route with Args, Reply, and handle:

const Routes = struct {
    pub const greet = struct {
        pub const Args = struct { name: []const u8 = "world" };
        pub const Reply = struct { message: []const u8 };
        pub fn handle(ctx: *AppCtx, alloc: std.mem.Allocator, args: Args) !Reply {
            return .{ .message = try std.fmt.allocPrint(alloc, "Hello, {s}!", .{args.name}) };
        }
    };
};
// dispatch is generated: Router(AppCtx, Routes)

The type field selects the route; remaining JSON fields decode into Args by name; the return encodes back. Errors become error replies — no unwrapped panics across the bridge.

Assets

build.zig embeds frontend/ + public/ as an AssetEntry table (path, bytes, content-type). asset_router.resolve serves them under verve://app/, rejecting path traversal. With dev_assets configured, disk wins over embedded — edit-reload without rebuilding.

SSR + hydration (full template)

The --desktop (full) template adds the web stack inside the window: tools/render_index.zig runs the SSR at build time to produce index.html, and the WASM client hydrates islands exactly as on the web. zig build dev watches and respawns; zig build smoke runs golden-diff regression tests; zig build bundle produces a macOS .app.

Next: Desktop platform APIs.