Piazzi takes Tauri’s architecture, the system webview with capability-scoped native access and your frontend toolchain left alone, and moves every guarantee it can from runtime to compile time.
The native side is zig build and nothing else. No Rust toolchain, no Node in the native build, and no JS runtime inside the app you ship. The framework is sized so one person can read all of it in an afternoon.
Declare your API once, in Zig.
Write your API as a Zig struct. At compile time Piazzi reads it and derives the JS client, the TypeScript declarations, and the dispatch table. There is no second definition to keep in sync, so nothing can drift.
A type JavaScript can’t hold exactly is a compile error at the declaration, and the message tells you the fix. Try it on updated_at.
const Note = struct {
id: u32,
title: []const u8,
body: []const u8,
updated_at: i53,
};
const Api = struct {
pub const Events = union(enum) {
notes_changed: struct { id: u32 },
};
pub fn listNotes(ctx: *piazzi.Ctx) ![]const Note {
return store.all(ctx.arena);
}
pub fn saveNote(ctx: *piazzi.Ctx, note: Note) !void {
try store.put(note);
ctx.emit(.{ .notes_changed = .{ .id = note.id } });
}
};
// Generated by zig build. Do not edit.
export interface Note {
id: number;
title: string;
body: string;
updated_at: number;
}
export function listNotes(): Promise<Note[]>;
export function saveNote(note: Note): Promise<void>;
export type Events = {
notes_changed: { id: number };
};
$ zig build
src/main.zig:5:17: error: i64 can’t be represented exactly in JS
updated_at: i64,
^~~
note: use i53, or piazzi.Int64 to send it as a bigint
Capabilities you don’t grant aren’t in the binary.
Piazzi borrows Tauri’s capability model and moves enforcement to compile time. A native capability your app doesn’t grant isn’t denied at runtime. Its code is never compiled in, and nm can prove it.
Grants live next to your API, not in a separate config file. Switch a few on and off and watch the audit.
const App = piazzi.App(.{
.api = Api,
.capabilities = .{
// .shell is never granted, so none of its code exists
},
});
$ zig build audit
compiled in fs, dialog, opener
not compiled in clipboard, notification, http, shell
$ nm zig-out/bin/notes | grep -cE 'piazzi\.(clipboard|notification|http|shell)'
0
Know what every webview can render before you ship.
Your UI runs in three engines: WebView2 on Windows, WKWebView on macOS, and WebKitGTK on Linux. CSS that works in one can break in another. zig build check reads your built frontend and flags what the oldest engine you target can’t render.
It works out that oldest engine from the minimum OS versions you declare. It warns by default and fails the build in strict mode. It ships first, as a standalone tool called piazzi-check that runs on any dist/ folder, including a Tauri app’s.
$ zig build check
web/dist/assets/main-4f2a.css:812
text-wrap: balance unsupported in WebKitGTK 2.44 (linux), 3 occurrences
web/dist/assets/index-9c1b.js:4471
Object.groupBy unsupported in the WebKit shipped with macOS 13.0
web/dist/assets/index-9c1b.js:5102
heuristic: .at() seen on an unknown receiver
2 definite, 1 heuristic. Build continues (pass -Dstrict-check to fail).
- Windows
- WebView2, which tracks current Chromium
- macOS
- WKWebView, as shipped with your minimum macOS
- Linux
- WebKitGTK, at the version you declare
Every desktop binary from one machine.
Zig is the compiler, the build system, the linker, and the cross-compiler, with a libc for every target. Once each platform’s SDK pieces are fetched, one host builds all three binaries, one command each.
Signing and notarizing for macOS still needs a Mac, and most installers build on the system they target. So there’s no CI matrix for compiling, and a small one for packaging and signing.
| Target | Zig brings | You fetch once |
|---|---|---|
aarch64-macos | Darwin libc and the linker | Headers and link stubs from an Apple SDK you supply |
x86_64-windows | mingw-w64 headers and libc | WebView2 SDK headers, fetched by a build step |
x86_64-linux-gnu.2.35 | glibc at the version you pick | A GTK and WebKitGTK sysroot, assembled by a script |
No hidden caveats.
Some problems come with the system webview. Piazzi inherits them like every framework in this category, and lists them here so you don’t have to find them yourself.
Shared, and documented rather than fixed
- WebKitGTK has graphics trouble on Linux: blank windows, resize flicker, and renderer failures on some NVIDIA and Wayland setups.
- Linux machines need WebKitGTK. The .deb declares it, and the AppImage bundles it and grows to match.
- Stale or locked-down Windows installs can lack the WebView2 Runtime, so the installer fetches it.
- Unsigned Windows apps trip Defender. The guide covers signing and building reputation.
Not in version 1
- Mobile. No iOS, no Android.
- A bundled engine. The engine is an interface from the first commit, so one engine everywhere can come in version 2 without a rewrite.
- A plugin ABI. Capability modules live in the tree for now.
- An auto-updater. The hook ships in 1.0 and the updater follows in 1.1.
- Remote content in any view that can call your API.
What ships, in order.
Every milestone ends with something you can run. There are no dates here on purpose: this is the order things arrive in, and a milestone counts once it has shipped.
- Now
The design
Written down in full. Nothing below has shipped yet.
- M0
Two go/no-go spikes
A macOS window with a WKWebView through
objc_msgSend, and a Windows window with WebView2 through COM. If either fails, the plan changes. - M1
piazzi-check, on its ownThe compatibility linter ships before the framework, with its own README, tested against real apps built with other frameworks.
- M2
Scheme, assets, one origin
Apps load from
piazzi://appin dev and release alike, and the secure-context checks become tests. - M3
IPC by hand
Calls in both directions, promises in JS, errors carried across, and one API thread with a per-request arena.
- M4
Comptime bindings
The generated client and types replace the hand-written dispatch. The first build-time and binary-size measurements are taken here.
- M5
Windows
The WebView2 backend, and the engine and platform interfaces redesigned against a second real platform.
- M6
Security model
Compile-time capability gating, runtime path scoping, CSP injection, the origin lock, and
zig build audit. - M7
Linux and packaging
The WebKitGTK backend,
zig build bundle, and cross-compilation verified in CI. - M8
Version 1.0
Docs, the Notes example app, a compatibility table, a signing guide, and a comparison where every number was measured.
- M9
A bundled engine
One engine on every platform. Not yet scoped, and planned for version 2.
Why it’s called Piazzi
1 January 1801
On the first night of 1801, Giuseppe Piazzi, at his observatory in Palermo, found Ceres, the first asteroid ever discovered. He also noticed 61 Cygni drifting against the fixed stars, and called it the Flying Star.
He made his name by noticing what had moved. A framework built to catch drift before it ships should carry it.