Hello world — desktop
Verve desktop apps are native executables that open an OS webview window (WKWebView on macOS, WebView2 on Windows, WebKitGTK on Linux) and serve your UI over a custom verve://app/ scheme. The frontend talks to Zig through a typed IPC bridge — no HTTP server, no ports.
verve-cli new hello --desktop --template minimal
cd hello
zig build
./zig-out/bin/appType a name and click Greet — the button calls a Zig handler that formats the reply and sends it back over the bridge.
The entry point
src/main.zig opens the window, embeds the frontend assets, and wires the message handler:
//! Minimal desktop scaffold entry point.
//!
//! Opens a window, wires the IPC handler, runs the platform event loop.
//! No tray, no multi-window, no smoke harness — see the full scaffold
//! (`verve-cli new <dir> --desktop`) for those.
const std = @import("std");
const desktop = @import("desktop");
const public_assets = @import("public_assets");
const handlers = @import("handlers.zig");
pub fn main(init: std.process.Init) !void {
_ = init;
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// The build embeds `frontend/` into `public_assets.entries` with
// the same shape the desktop asset router consumes. Cast directly —
// the field layout is intentionally compatible.
const asset_entries: []const desktop.AssetEntry = @ptrCast(public_assets.entries);
var window = try desktop.Window.init(allocator, .{
.title = "Verve Desktop — minimal",
.width = 480,
.height = 320,
.devtools = true,
.assets = asset_entries,
.initial_path = "index.html",
});
defer window.deinit();
const ctx_ptr = handlers.attach(&window);
window.setMessageHandler(handlers.onMessage, ctx_ptr);
window.run();
}
Typed IPC routes
src/handlers.zig declares routes as a comptime struct table. Each public decl is one route: Args (decoded from the JS request), Reply (encoded back), and a handle function:
//! Minimal IPC route table.
//!
//! Each route in `Routes` declares an `Args` type, a `Reply` type, and
//! a `handle(ctx, alloc, args)` fn. The router parses incoming JSON
//! against `Args`, calls the handler, JSON-encodes `Reply`, and ships
//! it back so `await window.verve.request(...)` resolves.
const std = @import("std");
const desktop = @import("desktop");
const RouterCtx = struct {
window: *desktop.Window,
};
var ctx: RouterCtx = undefined;
const Router = desktop.Router(RouterCtx, Routes);
pub const onMessage = Router.dispatch;
pub fn attach(window: *desktop.Window) *RouterCtx {
ctx = .{ .window = window };
return &ctx;
}
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(_: *RouterCtx, alloc: std.mem.Allocator, args: Args) !Reply {
const trimmed = std.mem.trim(u8, args.name, &std.ascii.whitespace);
const who = if (trimmed.len == 0) "world" else trimmed;
const message = try std.fmt.allocPrint(alloc, "Hello, {s}!", .{who});
return .{ .message = message };
}
};
};
Calling from the frontend
The bridge injects window.verve at document-start. The frontend is plain HTML/JS (the full desktop template adds SSR + WASM hydration):
const reply = await window.verve.request({ type: "greet", name: "Ada" });
// reply.message === "Hello, Ada!"The type field selects the route; remaining fields decode into the route's Args struct. Type mismatches are rejected at the bridge, and unknown routes return an error reply.
Two desktop templates
--template minimal | --desktop (full) | |
|---|---|---|
| Window + IPC | yes | yes |
| Frontend | static HTML/CSS | SSR at build time + WASM hydration |
| Platform features | — | tray, notifications, deep links, cookies, print, multi-window |
| Dev loop | zig build | zig build dev (watch + respawn), zig build smoke golden tests |
Platform notes
- macOS — links Cocoa + WebKit;
zig build bundleproduces a.app. - Windows — WebView2 header and loader DLL are vendored; needs the Evergreen runtime at run time (preinstalled on Windows 11).
- Linux — GTK3 default (
libgtk-3-dev libwebkit2gtk-4.1-dev), GTK4 via-Dgtk4=true(tray and window snapshot unsupported on GTK4).
The desktop guides cover the full platform API — windows, tray, notifications, hotkeys, deep links, file watching, and more. Start at Desktop architecture.