chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:29:49 +08:00
commit 505bac6b53
1470 changed files with 457757 additions and 0 deletions
+252
View File
@@ -0,0 +1,252 @@
---
name: core
description: Core Native SDK guide for AI agents. Read this before explaining the Native SDK or changing a Native SDK app. Covers the mental model (native-rendered apps by default, WebView shells as a coexisting architecture), project structure, app.zon, App and Runtime patterns, frontend integration, web engines, JavaScript bridge commands, permissions, windows, WebViews, dialogs, packaging, debugging, testing, and when to load deeper references. For authoring native-rendered UI (Native markup .native views, Model/Msg/update, UiApp), load the native-ui skill. Use when the user asks what the Native SDK is, how to build or modify an app, how to package or debug it, or how to add native capabilities.
---
# Build Native SDK apps
The Native SDK is a cross-platform native app toolkit inspired by the web. The default app (`native init`) is native-rendered: a declarative Native markup view (`.native`) plus Zig logic on the `UiApp` loop, drawn by Native SDK's own engine — **load the `native-ui` skill for everything about authoring those** (markup grammar, bindings, messages, testing, hot reload). This skill covers the shared foundation and the WebView-shell architecture: apps that render web frontends in a WebView (`native init --frontend next|vite|react|svelte|vue`). Both architectures share windows, policies, lifecycle, commands, and platform services, and one app can mix native-rendered surfaces and WebViews. WebView engines: platform WebView (WKWebView on macOS, WebKitGTK on Linux) or Chromium through CEF where supported.
Agents should assume they do not know the Native SDK from general model knowledge. Read this skill first. For implementation work, run `native skills get core --full` so the referenced files are included in the CLI output.
## Mental model
- `App` describes the product: app state, name, WebView source, lifecycle callbacks, and optional bridge dispatcher.
- `Runtime` owns the event loop, windows, bridge dispatch, security checks, automation, tracing, platform services, and window state.
- `WebViewSource` tells the runtime what to load: inline HTML, a URL, or packaged assets from a local app origin.
- `app.zon` is the app manifest: identity, icons, windows, frontend assets, web engine, permissions, bridge policy, security policy, and packaging inputs.
- `src/runner.zig` is the generated runtime wiring. Edit it when adding runtime services, security policy, builtin bridge policy, tracing, automation, or platform setup.
- `src/main.zig` is app behavior. Edit it when changing app state, source selection, lifecycle callbacks, or app-defined bridge handlers.
- `frontend/` is normal web code. It talks to native Zig through `window.zero.invoke()` or builtin helpers when those are enabled.
## Task router
These references are included by `native skills get core --full`. Use them when the task touches the topic:
- Project creation, generated files, build steps: `references/project-anatomy.md`
- `App`, `Runtime`, callbacks, embedding, tests: `references/app-model-runtime.md`
- React/Vue/Svelte/Next/Vite, dev server, bundled assets: `references/frontend-assets.md`
- App-defined bridge commands, builtin commands, permissions, windows, WebViews, dialogs: `references/bridge-security-native-capabilities.md`
- Web engine choice, CEF, packaging, signing, doctor, logs, debugging: `references/web-engines-packaging-debugging.md`
- Running-app inspection and smoke tests: `native skills get automation`
- `zig build` fails on std APIs ("no member named 'cwd'", ArrayList `init`): `native skills get zig` — the Zig 0.16 idioms, indexed by compile error
## Quick start
Use the CLI for new apps:
```bash
npm install -g @native-sdk/cli
native init my_app --frontend next
cd my_app
zig build run
```
Frontend choices are `next`, `vite`, `react`, `svelte`, and `vue`. The first `zig build run` installs frontend dependencies, builds the native shell, and opens a desktop window.
## Workflow for existing apps
Before editing an existing Native SDK app:
1. Read `app.zon`, `src/main.zig`, `src/runner.zig`, and `build.zig`.
2. Identify whether the change is app metadata/policy, runtime wiring, app-native behavior, frontend behavior, packaging, or automation.
3. Follow the generated code and examples in the repository instead of inventing a new app layout.
4. Prefer exact security policy changes over broad allowances.
5. Validate with the narrowest useful command.
Common file ownership:
- `app.zon`: app identity, version, icons, windows, permissions, capabilities, bridge command policy, allowed origins, frontend dist/dev config, web engine, CEF config.
- `src/main.zig`: `App` state, source selection, lifecycle callbacks, custom bridge handlers.
- `src/runner.zig`: `Runtime.init`, platform selection, security policy, builtin bridge policy, `js_window_api`, automation server, trace sinks, panic capture, window state store.
- `build.zig`: build options, frontend build/dev/package steps, platform link setup, test steps.
- `frontend/`: web app implementation, `window.zero` calls, dev/build config.
## Core app model
The minimal Zig app returns `native_sdk.App` with `context`, `name`, and a WebView source:
```zig
const App = struct {
fn app(self: *@This()) native_sdk.App {
return .{
.context = self,
.name = "my-app",
.source = native_sdk.WebViewSource.html("<h1>Hello from Native SDK</h1>"),
};
}
};
```
Use these source constructors:
- `native_sdk.WebViewSource.html(content)` for small inline demos.
- `native_sdk.WebViewSource.url(address)` for an explicit URL.
- `native_sdk.WebViewSource.assets(.{ .root_path = "frontend/dist", .entry = "index.html" })` for packaged frontend assets.
For framework apps, prefer a dynamic source so development loads the local dev server and production loads bundled assets:
```zig
fn source(context: *anyopaque) anyerror!native_sdk.WebViewSource {
const self: *App = @ptrCast(@alignCast(context));
return native_sdk.frontend.sourceFromEnv(self.env_map, .{
.dist = "frontend/dist",
.entry = "index.html",
});
}
```
`sourceFromEnv` reads `NATIVE_SDK_FRONTEND_URL`; otherwise it serves the configured asset directory. Use it for most framework apps.
## app.zon essentials
Keep `app.zon` as the source of truth for app-level behavior:
```zig
.{
.id = "com.example.my-app",
.name = "my-app",
.display_name = "My App",
.description = "One line about the app, shown in the About panel.",
.version = "0.1.0",
.icons = .{"assets/icon.png"},
.platforms = .{ "macos", "linux" },
.permissions = .{},
.capabilities = .{ "webview" },
.frontend = .{
.dist = "frontend/dist",
.entry = "index.html",
.spa_fallback = true,
.dev = .{
.url = "http://127.0.0.1:5173/",
.command = .{ "npm", "--prefix", "frontend", "run", "dev", "--", "--host", "127.0.0.1" },
.ready_path = "/",
.timeout_ms = 30000,
},
},
.security = .{
.navigation = .{
.allowed_origins = .{ "zero://app", "http://127.0.0.1:5173" },
.external_links = .{ .action = "deny" },
},
},
.web_engine = "system",
.windows = .{
.{ .label = "main", .title = "My App", .width = 960, .height = 640, .restore_state = true },
},
}
```
Use exact local origins for dev servers. Add `zero://inline` only for inline HTML sources.
## Common implementation recipes
### Add a new framework app
Use `native init <path> --frontend <next|vite|react|svelte|vue>`. Then inspect the generated `app.zon`, `src/main.zig`, and `build.zig` before customizing. For framework behavior, keep frontend work in `frontend/` and use `sourceFromEnv` so development and packaged builds share one app shell.
### Add a native bridge command
1. Add state and a handler in `src/main.zig`.
2. Register the handler in `bridge()`.
3. Allow the command in `app.zon` and in the runtime bridge policy if the runner reads manifest policy into runtime.
4. Call it from JavaScript with `window.zero.invoke("namespace.command", payload)`.
5. Return valid JSON from Zig. Use `native_sdk.bridge.writeJsonStringValue()` for user-controlled strings.
Bridge calls are size-limited, origin-checked, permission-checked, and routed only to registered handlers.
### Add windows, child WebViews, or dialogs
Use builtin bridge commands only after enabling a policy for the exact commands and origins. Window and child WebView commands need the `window` permission when permissions are configured. Dialog commands are always default-deny and require explicit `builtin_bridge` policy. See `references/bridge-security-native-capabilities.md`.
### Choose a web engine
Default to `.web_engine = "system"` for small apps and native footprint. Use `.web_engine = "chromium"` plus `.cef` when the app needs a pinned Chromium platform or rendering consistency. Chromium apps must install/package the matching CEF layout.
### Package an app
Zero-config apps package WITHOUT ejecting — `native package` works directly on the zero-config build (`native eject` is only for owning the build files, never a packaging prerequisite):
```bash
native build
native package --target macos
```
Apps that own their build (ejected or scaffolded `--full`) wire the same step into the build graph: keep package metadata in `app.zon`, build the frontend assets, build the native binary, then package:
```bash
zig build package
native doctor --manifest app.zon --strict
```
Use signing and CEF options only when the product requires them.
## Development commands
For iterative frontend work, use the managed dev server flow:
```bash
zig build dev
```
Or run the CLI directly after building the binary:
```bash
native dev --manifest app.zon --binary zig-out/bin/MyApp
```
Vite usually uses `http://127.0.0.1:5173/`; Next.js usually uses `http://127.0.0.1:3000/`. The app WebView loads the dev URL directly, so framework HMR remains owned by Vite, Next.js, or the selected dev server.
## Security defaults
Treat WebView content as untrusted:
- List only needed `permissions` and `capabilities`.
- Prefer exact bridge command origins over `"*"`.
- Keep main-frame navigation allowlisted in `security.navigation.allowed_origins`.
- Keep external links denied unless the product explicitly needs them.
- Use a strict CSP for packaged frontend assets.
- Built-in dialogs are always default-deny and require explicit `builtin_bridge` policy.
- Child WebViews receive `window.zero` only when explicitly created with `bridge: true`.
Common bridge failure codes are `invalid_request`, `unknown_command`, `permission_denied`, `handler_failed`, `payload_too_large`, and `internal_error`.
## Validate changes
Useful commands:
```bash
zig build run
zig build dev
zig build test
zig build test-tooling
native validate app.zon
native doctor --manifest app.zon --strict
zig build package
```
Run BOTH `zig build` and `zig build test` before calling a change done: Zig's lazy analysis means code only tests reference (or only `main()` reference) can sit broken for weeks under the other command alone — tests never analyze `main`, so an API removed from std can keep "passing" until the app build finally touches it.
The toolkit requires Zig 0.16.0. When a build fails with "no member named" errors on std APIs (`std.fs.cwd`, `ArrayList.init`, `std.io`, `GeneralPurposeAllocator`), the code was written against older Zig idioms — `native skills get zig` maps each such compile error to the 0.16 idiom as this SDK writes it.
For GUI smoke tests, build with automation enabled and use the `automation` skill:
```bash
zig build run -Dplatform=macos -Dautomation=true
zig-out/bin/native automate snapshot
```
When changing app behavior, add focused Zig tests when the code can run headlessly. Use automation-based tests only for WebView/runtime integration that requires a GUI-capable session.
## Examples to inspect
- `examples/hello`: smallest inline HTML app.
- `examples/webview`: bridge and WebView runtime example.
- `examples/browser`: layered WebView/browser-style example.
- `examples/next`: Next.js with production assets.
- `examples/react`, `examples/svelte`, `examples/vue`: Vite frontend apps.
- `examples/ios`, `examples/android`: mobile host embedding examples.
## When answering users
Explain the Native SDK in concrete terms: Zig owns native app lifecycle and security; web UI renders in a WebView; the bridge is opt-in and policy controlled; `app.zon` is the manifest; framework frontend development uses a dev server in development and bundled assets in production. If asked to implement, read the app files first and make the smallest change in the correct layer.
@@ -0,0 +1,141 @@
# App Model and Runtime
Use this when editing `src/main.zig`, `src/runner.zig`, lifecycle behavior, runtime setup, or tests.
## `App`
A Native SDK app returns a `native_sdk.App` value:
```zig
const App = struct {
fn app(self: *@This()) native_sdk.App {
return .{
.context = self,
.name = "my-app",
.source = native_sdk.WebViewSource.html("<h1>Hello</h1>"),
.source_fn = source,
.start_fn = start,
.event_fn = event,
.stop_fn = stop,
};
}
};
```
Required fields:
- `context`: pointer to app state.
- `name`: app name for traces and automation snapshots.
- `source`: initial WebView source. Overridden by `source_fn` when present.
Optional callbacks:
- `source_fn`: dynamic source resolver.
- `start_fn`: called after runtime start and initial load.
- `event_fn`: receives lifecycle and runtime events.
- `stop_fn`: called before shutdown.
## `WebViewSource`
Choose one:
```zig
native_sdk.WebViewSource.html("<p>Inline</p>")
native_sdk.WebViewSource.url("http://127.0.0.1:5173/")
native_sdk.WebViewSource.assets(.{
.root_path = "frontend/dist",
.entry = "index.html",
.origin = "zero://app",
.spa_fallback = true,
})
```
Use inline HTML only for small examples and smoke tests. Use URL sources for explicit local/remote loading. Use assets for packaged apps.
## Runtime setup
Generated runners create a `Runtime` with platform services:
```zig
var runtime = native_sdk.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` fields agents commonly touch:
- `platform`: macOS, Linux, Windows, or `NullPlatform`.
- `trace_sink`: stdout/file/fanout trace destination.
- `bridge`: app-defined bridge dispatcher.
- `builtin_bridge`: policy for built-in windows, WebViews, and dialogs.
- `security`: permissions, navigation allowlist, external links.
- `automation`: file-based automation server.
- `window_state_store`: persisted window geometry.
- `js_window_api`: exposes `window.zero.windows` and `window.zero.webviews`.
## Windows from Zig
Use runtime methods for native window management:
```zig
const info = try runtime.createWindow(.{
.label = "tools",
.title = "Tools",
.default_frame = native_sdk.geometry.RectF.init(80, 80, 420, 320),
});
try runtime.focusWindow(info.id);
```
Window limits:
- Max windows: 16.
- Max label bytes: 64.
- Max title bytes: 128.
Persisted window state is keyed primarily by `label`, so labels should be stable.
## EmbeddedApp
Use `EmbeddedApp` when another host owns the main loop:
```zig
var embedded = native_sdk.embed.EmbeddedApp.init(my_app.app(), my_platform);
try embedded.start();
try embedded.frame();
try embedded.resize(new_surface);
try embedded.stop();
```
This is useful for mobile hosts, game engines, custom render loops, and headless tests. The repository includes iOS and Android examples that link `libnative-sdk.a` through Swift/Kotlin host apps.
## Headless tests
Use `NullPlatform` or `TestHarness` when GUI behavior is not required:
```zig
var null_platform = native_sdk.NullPlatform.init(.{});
var runtime = native_sdk.Runtime.init(.{
.platform = null_platform.platform(),
});
```
Good headless test targets:
- source selection
- bridge handler logic
- bridge policy enforcement
- lifecycle callbacks
- manifest/tooling behavior
Use automation smoke tests for real WebView/window integration.
@@ -0,0 +1,230 @@
# Bridge, Security, and Native Capabilities
Use this when adding JavaScript-to-Zig calls, builtin commands, permissions, windows, child WebViews, dialogs, navigation policies, or external links.
## Bridge architecture
JavaScript calls native Zig through:
```javascript
const result = await window.zero.invoke("native.ping", { source: "webview" });
```
The runtime:
1. Parses the JSON request.
2. Enforces message size limits.
3. Checks origin and permissions.
4. Looks up a registered handler.
5. Runs the handler and returns a JSON response.
Bridge commands are default-deny. A command must be registered in Zig and allowed by policy.
## Handler pattern
```zig
fn ping(context: *anyopaque, invocation: native_sdk.bridge.Invocation, output: []u8) anyerror![]const u8 {
_ = invocation;
const self: *App = @ptrCast(@alignCast(context));
self.ping_count += 1;
return std.fmt.bufPrint(output, "{{\"message\":\"pong\",\"count\":{d}}}", .{self.ping_count});
}
```
Dispatcher pattern:
```zig
fn bridge(self: *App) native_sdk.BridgeDispatcher {
self.handlers = .{.{ .name = "native.ping", .context = self, .invoke_fn = ping }};
return .{
.policy = .{ .enabled = true, .commands = &policies },
.registry = .{ .handlers = &self.handlers },
};
}
```
When returning user-controlled strings, escape them:
```zig
return native_sdk.bridge.writeJsonStringValue(output, user_name);
```
## Size limits
- Request message: 16 KiB.
- Response: 16 KiB.
- Handler result: 12 KiB.
- Request ID: 64 bytes.
- Command name: 128 bytes.
For large data, do not force everything through one bridge response. Use native files/resources or chunking patterns.
## Security policy
Core defaults:
- No permissions granted unless listed.
- Bridge commands denied unless policy allows them.
- Navigation blocked unless origin is allowlisted.
- External links denied unless configured.
- Dialog builtin commands always denied unless explicitly listed in `builtin_bridge`.
Manifest examples:
```zig
.permissions = .{ "window" },
.capabilities = .{ "webview", "js_bridge" },
.bridge = .{
.commands = .{
.{ .name = "native.ping", .origins = .{ "zero://app" } },
},
},
.security = .{
.navigation = .{
.allowed_origins = .{ "zero://app", "http://127.0.0.1:5173" },
.external_links = .{ .action = "deny" },
},
},
```
Prefer exact origins over `"*"`. Use `"*"` only for commands that expose no native state and only when the project already accepts that risk.
## Builtin commands
The Native SDK includes builtin bridge commands for windows, layered WebViews, and dialogs. These are controlled separately from app-defined commands via `builtin_bridge`.
Window commands:
- `native-sdk.window.list`
- `native-sdk.window.create`
- `native-sdk.window.focus`
- `native-sdk.window.close`
Layered WebView commands:
- `native-sdk.webview.create`
- `native-sdk.webview.list`
- `native-sdk.webview.setFrame`
- `native-sdk.webview.navigate`
- `native-sdk.webview.setZoom`
- `native-sdk.webview.setLayer`
- `native-sdk.webview.close`
Dialog commands:
- `native-sdk.dialog.openFile`
- `native-sdk.dialog.saveFile`
- `native-sdk.dialog.showMessage`
Enable explicitly:
```zig
const app_permissions = [_][]const u8{native_sdk.security.permission_window};
.security = .{
.permissions = &app_permissions,
.navigation = .{ .allowed_origins = &.{ "zero://app" } },
},
.builtin_bridge = .{
.enabled = true,
.commands = &.{
.{ .name = "native-sdk.window.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "native-sdk.webview.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "native-sdk.dialog.openFile", .origins = .{ "zero://app" } },
},
},
```
`js_window_api = true` exposes `window.zero.windows.*` and `window.zero.webviews.*`, but it does not bypass origin or permission checks.
## Windows from JavaScript
```javascript
const win = await window.zero.windows.create({
label: "tools",
title: "Tools",
width: 420,
height: 320,
});
const all = await window.zero.windows.list();
await window.zero.windows.focus(win.id);
await window.zero.windows.close(win.id);
```
Window state persistence uses stable labels. Use meaningful labels like `main`, `settings`, `tools`, or `preview`.
## Layered WebViews
Child WebViews are native WebViews layered inside a native window:
```javascript
const preview = await window.zero.webviews.create({
label: "preview",
url: "https://example.com",
frame: { x: 24, y: 24, width: 480, height: 320 },
layer: 10,
bridge: false,
});
await preview.setZoom(1.25);
await preview.setLayer(20);
await preview.close();
```
Rules:
- WebView URLs must pass navigation policy.
- Commands target only the calling native window.
- `main` is reserved for the startup WebView.
- Child WebViews receive `window.zero` only with `bridge: true`.
- Backend gaps should reject with `invalid_request`.
## Dialogs
Dialogs require explicit `builtin_bridge` policy.
```javascript
const files = await window.zero.invoke("native-sdk.dialog.openFile", {
title: "Select a file",
defaultPath: "/home",
allowMultiple: true,
allowDirectories: false,
});
const path = await window.zero.invoke("native-sdk.dialog.saveFile", {
title: "Save as",
defaultName: "untitled.txt",
});
const result = await window.zero.invoke("native-sdk.dialog.showMessage", {
style: "warning",
title: "Confirm",
message: "Delete this item?",
primaryButton: "Delete",
secondaryButton: "Cancel",
});
```
Use native dialogs for trusted app UI. Do not expose arbitrary filesystem access to remote or untrusted origins.
## Error handling
JavaScript bridge calls reject with `error.code`:
- `invalid_request`: malformed input, unsupported operation, denied navigation URL, missing target, duplicate/reserved label.
- `unknown_command`: no registered handler.
- `permission_denied`: origin or permission failed.
- `handler_failed`: handler returned an error.
- `payload_too_large`: request too large.
- `internal_error`: unexpected runtime failure.
Always handle errors in frontend code:
```javascript
try {
await window.zero.invoke("native.save", payload);
} catch (error) {
console.error(error.code, error.message);
}
```
@@ -0,0 +1,124 @@
# Frontend and Assets
Use this when working with React, Vue, Svelte, Next.js, Vite, dev servers, HMR, static assets, or packaged frontend output.
## Recommended source pattern
Framework apps should use a dynamic source:
```zig
fn source(context: *anyopaque) anyerror!native_sdk.WebViewSource {
const self: *App = @ptrCast(@alignCast(context));
return native_sdk.frontend.sourceFromEnv(self.env_map, .{
.dist = "frontend/dist",
.entry = "index.html",
});
}
```
`sourceFromEnv` checks `NATIVE_SDK_FRONTEND_URL`. If set, the WebView loads the dev server URL. If not set, it serves packaged assets from `dist`.
## Manifest frontend config
```zig
.frontend = .{
.dist = "frontend/dist",
.entry = "index.html",
.spa_fallback = true,
.dev = .{
.url = "http://127.0.0.1:5173/",
.command = .{ "npm", "--prefix", "frontend", "run", "dev", "--", "--host", "127.0.0.1" },
.ready_path = "/",
.timeout_ms = 30000,
},
},
```
Fields:
- `dist`: built frontend output path.
- `entry`: HTML entry file within `dist`.
- `spa_fallback`: serve `entry` for unknown routes.
- `dev.url`: local dev server URL.
- `dev.command`: command the Native SDK can spawn.
- `dev.ready_path`: path to poll for readiness.
- `dev.timeout_ms`: readiness timeout.
## Dev server workflow
Use:
```bash
zig build dev
```
Or directly:
```bash
native dev --manifest app.zon --binary zig-out/bin/MyApp
```
The dev command starts the frontend process, waits for readiness, sets `NATIVE_SDK_FRONTEND_URL`, launches the native shell, and terminates the frontend when the shell exits.
Framework defaults:
- Vite, React, Vue, Svelte: `http://127.0.0.1:5173/`, command `npm run dev -- --host 127.0.0.1`.
- Next.js: `http://127.0.0.1:3000/`, command `npm run dev -- --hostname 127.0.0.1`.
## Production assets
For packaged builds, serve local assets from `zero://app`:
```zig
return native_sdk.frontend.productionSource(.{
.dist = "frontend/dist",
.entry = "index.html",
});
```
The package/build flow should build the frontend before packaging. `zig build bundle-assets` and `native bundle-assets` copy the configured dist directory into build/package output.
## Security and frontend origins
Add exact origins to `security.navigation.allowed_origins`:
```zig
.security = .{
.navigation = .{
.allowed_origins = .{
"zero://app",
"http://127.0.0.1:5173",
},
},
},
```
Use `zero://inline` only for inline examples. Do not allow `"*"` for production navigation.
For packaged HTML, start with a strict CSP:
```html
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'">
```
For dev servers, keep development CSP separate and add only the local dev/WebSocket endpoints required by the framework.
## Frontend to native calls
Use:
```javascript
const result = await window.zero.invoke("native.ping", { source: "webview" });
```
For builtin windows/WebViews helpers, enable `js_window_api` and policy in the runner. For custom commands, register handlers in Zig and allow the command in policy.
## Example projects
- `examples/next`: Next.js app with production assets under `frontend/out`.
- `examples/react`: React with Vite.
- `examples/svelte`: Svelte with Vite.
- `examples/vue`: Vue with Vite.
When unsure about frontend build commands or output directories, inspect the matching example before editing.
@@ -0,0 +1,108 @@
# Project Anatomy
Use this when creating, orienting in, or restructuring a Native SDK app.
## Generated project files
A zero-config app ships no build files at all — just `app.zon` + `src/` (+ `assets/`); the `native dev|test|build` verbs synthesize the build graph into `.native/build/` (gitignored). `build.zig`/`build.zig.zon` appear only in apps that own their build (`native eject`, the `--full` scaffold, or an expanded example):
- `build.zig`: Zig build graph. Expanded scaffolds expose platform selection, trace mode, debug overlay, automation, JS bridge, web engine overrides, frontend install/build/dev steps, tests, and package steps.
- `build.zig.zon`: Zig package manifest and dependency declaration.
- `app.zon`: app manifest read by CLI/build/package/doctor tooling.
- `src/main.zig`: app state, `app()` method, source resolver, optional bridge dispatcher, lifecycle callbacks.
- `src/runner.zig`: platform and runtime setup: native backend, trace sinks, panic capture, log paths, state store, security policy, builtin bridge policy, automation.
- `assets/`: icons and other package resources.
- `frontend/`: framework app when using Next, Vite, React, Svelte, or Vue.
## app.zon responsibilities
Keep product-level metadata and policies in `app.zon`:
```zig
.{
.id = "com.example.my-app",
.name = "my-app",
.display_name = "My App",
.version = "0.1.0",
.icons = .{"assets/icon.png"},
.platforms = .{ "macos", "linux" },
.permissions = .{ "window" },
.capabilities = .{ "webview", "js_bridge" },
.bridge = .{
.commands = .{
.{ .name = "native.ping", .origins = .{ "zero://app" } },
},
},
.security = .{
.navigation = .{
.allowed_origins = .{ "zero://app", "http://127.0.0.1:5173" },
.external_links = .{ .action = "deny" },
},
},
.frontend = .{
.dist = "frontend/dist",
.entry = "index.html",
.spa_fallback = true,
.dev = .{
.url = "http://127.0.0.1:5173/",
.command = .{ "npm", "--prefix", "frontend", "run", "dev", "--", "--host", "127.0.0.1" },
.ready_path = "/",
.timeout_ms = 30000,
},
},
.web_engine = "system",
.windows = .{
.{ .label = "main", .title = "My App", .width = 960, .height = 640, .restore_state = true },
},
}
```
Important manifest fields:
- `id`: reverse-DNS bundle identifier. Used for bundle metadata and log/state paths.
- `name`: short machine name.
- `display_name`: human app name — shown by the application menu, Dock, app switcher, and About panel in dev runs and packaged bundles alike.
- `description`: optional one-line About-panel description (max 256 bytes, single line).
- `version`: package and bundle version — also shown in the About panel.
- `icons`: package resources.
- `platforms`: package targets.
- `permissions`: runtime grants checked by bridge and builtin commands.
- `capabilities`: broad feature declarations.
- `bridge.commands`: app-defined command allowlist.
- `security.navigation.allowed_origins`: main-frame navigation allowlist.
- `security.navigation.external_links`: external link policy.
- `frontend`: production asset and dev server config.
- `web_engine`: `system` or `chromium`.
- `cef`: CEF layout config for Chromium.
- `windows`: initial window definitions.
## Build steps to know
Common generated steps:
```bash
zig build run
zig build dev
zig build test
zig build package
zig build frontend-install
zig build frontend-build
```
Repository-level useful steps:
```bash
zig build test
zig build test-tooling
zig build test-webview-smoke -Dplatform=macos
zig build test-package-cef-layout -Dplatform=macos
```
## Layering rule
- If changing app identity, packaging inputs, permissions, origins, windows, frontend dist/dev paths, or web engine, update `app.zon`.
- If changing runtime services, platform setup, automation, logging, security wiring, or builtin bridge policy, update `src/runner.zig`.
- If changing native business behavior, lifecycle callbacks, bridge handlers, or source selection, update `src/main.zig`.
- If changing UI, routes, CSS, or web calls, update `frontend/`.
Do not put package metadata in Zig app state unless the generated project already does that. Do not bypass `app.zon` policy for convenience.
@@ -0,0 +1,199 @@
# Web Engines, Packaging, and Debugging
Use this when choosing system WebView vs Chromium, installing CEF, packaging, signing, running doctor, finding logs, or debugging runtime behavior.
## Web engine choice
Default:
```zig
.web_engine = "system",
```
System mode:
- Uses the OS web engine.
- macOS: WKWebView.
- Linux: WebKitGTK.
- Smallest app footprint.
- Fastest startup.
- Rendering depends on the user's OS.
Chromium mode:
```zig
.web_engine = "chromium",
.cef = .{ .dir = "third_party/cef/macos", .auto_install = false },
```
Chromium mode:
- Bundles CEF.
- Gives predictable Chromium behavior.
- Increases package size and startup cost.
- Requires matching CEF layout at build and package time.
Use Chromium when the product needs a pinned web platform, complex frontend rendering consistency, or Chromium-only behavior. Otherwise prefer `system`.
## CEF setup
Install the prepared runtime:
```bash
native cef install
native doctor --manifest app.zon
```
Pin CEF in app setup or CI when reproducibility matters:
```bash
native cef install --version <version>
```
Useful overrides:
```bash
zig build run -Dweb-engine=chromium -Dcef-dir=third_party/cef/macos
zig build run -Dweb-engine=chromium -Dcef-auto-install=true
native package --web-engine chromium --cef-dir third_party/cef/macos
```
Normal product configuration should live in `app.zon`; CLI/build flags are for temporary overrides.
## Packaging
Simple path:
```bash
zig build package
```
CLI path:
```bash
native package --target macos --manifest app.zon --binary zig-out/bin/MyApp
```
Important package manifest fields:
- `id`: bundle ID, desktop ID, log/state prefix.
- `display_name`: app/menu/window title fallback.
- `version`: package metadata.
- `icons`: copied into package resources.
- `platforms`: intended package targets.
- `frontend`: asset directory and entry file.
- `web_engine` and `cef`: engine and Chromium runtime config.
For frontend apps, package the built frontend assets. The build step usually wires this automatically. If using CLI directly, pass `--assets frontend/dist`.
## macOS packages
`zig build package` creates a `.app` bundle with:
- `Contents/MacOS/<binary>`
- `Contents/Resources/AppIcon.icns` (generated from the square `assets/icon.png`/`.svg` source in `.icons`; a prebuilt `.icns` ships untouched under its own name)
- `Contents/Info.plist`
- `Contents/Resources/dist/` when frontend assets are configured
- `Contents/Frameworks/Chromium Embedded Framework.framework` for Chromium apps
macOS minimum system version is 11.0.
Signing modes:
```bash
native package --target macos --signing none
native package --target macos --signing adhoc
native package --target macos --signing identity --identity "Developer ID Application: Your Name"
```
For Chromium apps, verify the CEF framework and resources are included and signed before notarization.
## Linux and Windows packages
Linux creates an install tree with:
- `bin/<name>`
- `share/applications/<name>.desktop`
- icons under `share/icons/hicolor/...`
Windows packaging is early support and creates a directory-based distributable layout.
Shortcut commands:
```bash
native package-linux --binary zig-out/bin/MyApp
native package-windows --binary zig-out/bin/MyApp.exe
```
## Doctor and validation
Validate manifest schema:
```bash
native validate app.zon
```
Check environment and package readiness:
```bash
native doctor
native doctor --manifest app.zon --strict
native doctor --manifest app.zon --web-engine chromium --cef-dir third_party/cef/macos
```
Doctor checks:
- host platform
- WebView availability
- manifest validity
- log directory writability
- CEF layout when Chromium is selected
- signing tools
Use `--strict` in CI or before release so warnings fail the command.
## Debugging
Trace modes:
- `off`
- `events`
- `runtime`
- `all`
Build/run flags commonly include:
```bash
zig build run -Dtrace=all
zig build run-webview -Ddebug-overlay=true
```
Log defaults:
- macOS: `~/Library/Logs/<bundle-id>/native-sdk.jsonl`
- Linux: `~/.local/state/<bundle-id>/logs/native-sdk.jsonl`
- Windows: `%LOCALAPPDATA%\<bundle-id>\Logs\native-sdk.jsonl`
Environment variables:
```bash
NATIVE_SDK_LOG_DIR=/tmp/my-logs zig build run
NATIVE_SDK_LOG_FORMAT=text zig build run
```
Panic capture:
```zig
pub const panic = std.debug.FullPanic(native_sdk.debug.capturePanic);
```
Generated runners usually install panic capture so crashes write `last-panic.txt` and append a fatal trace record.
## Common failures
- App window opens blank: check `WebViewSource`, `frontend.dist`, `frontend.entry`, and allowed origins.
- Dev server never loads: check `app.zon frontend.dev.url`, command, readiness path, and `NATIVE_SDK_FRONTEND_URL`.
- Bridge call rejects with `permission_denied`: check command origin and permissions in policy.
- Bridge call rejects with `unknown_command`: handler was not registered or command name differs.
- Chromium app fails at launch: check CEF layout, version mismatch, bundle Frameworks layout, and signing.
- Package misses frontend: check `frontend.dist`, frontend build step, and `--assets`.