Compare commits
392 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bb97110886 | |||
| 7379550a27 | |||
| b25cefe318 | |||
| ef3ba18168 | |||
| 465a163e27 | |||
| a33d579177 | |||
| 393a0ed36e | |||
| e8f9e4ee50 | |||
| 659c893b29 | |||
| 8d0da34e62 | |||
| 1c1fba0c0f | |||
| 86fecf6cee | |||
| 23d0f5908a | |||
| 919d0e6cdf | |||
| 5d7fee8262 | |||
| 5c32accf12 | |||
| d575734635 | |||
| ee63266095 | |||
| e7c161970c | |||
| ddd975e4ea | |||
| 474cb5e364 | |||
| e6ac6ac4fc | |||
| 0ecdc7d2e9 | |||
| e924d7fcac | |||
| 41c4cdc47a | |||
| 31d5b202bc | |||
| 2bc942db46 | |||
| 199b89e06d | |||
| 716eb27c53 | |||
| baef0d96d3 | |||
| f228e861d1 | |||
| 6ec836c491 | |||
| 9ce0370181 | |||
| a7665807f3 | |||
| fe92cff10a | |||
| 7d67158444 | |||
| c71a7b4638 | |||
| d7aeea1ea9 | |||
| 8b2a97ffe7 | |||
| 473cad71ef | |||
| 06b6ccd53c | |||
| ed84e35975 | |||
| 5fa8074f7c | |||
| 8d7946edbd | |||
| 011caa9183 | |||
| b7c493bab0 | |||
| aa7ed9aa52 | |||
| 833e79e44a | |||
| cafbf206e8 | |||
| 0126d20f30 | |||
| 2fd7c4c3dc | |||
| 4c95b04539 | |||
| c7e64b647c | |||
| a404ca166d | |||
| 7f6830a15b | |||
| 7a29661384 | |||
| a727b1db68 | |||
| 7e3a3157d0 | |||
| 283ab804c0 | |||
| 83a7aee721 | |||
| 30c1410c10 | |||
| bfcc5ff8df | |||
| 3a580c9a8d | |||
| 4269233703 | |||
| b230b140b8 | |||
| 04125b7d62 | |||
| 21f6057041 | |||
| 4f0b57f2c2 | |||
| b21849c1bf | |||
| ff6a1c2c32 | |||
| b01851d03c | |||
| 0f990c2007 | |||
| 4ceffdbbd5 | |||
| 030bf8df74 | |||
| d976ab6351 | |||
| d26428e11b | |||
| 31c140e26f | |||
| e84ee28cbe | |||
| 1789c68049 | |||
| a14d225f0f | |||
| 8fc933b9db | |||
| 8600d7e5d5 | |||
| bdfce1ea7d | |||
| 8f1da1831b | |||
| 476173b6b5 | |||
| 19519dd5ea | |||
| 04b97cc2b7 | |||
| 8014e796a4 | |||
| a59015a246 | |||
| ad6fa36f3e | |||
| 7636ec3686 | |||
| bd3aab4b48 | |||
| 6a871356b4 | |||
| c1bad63c5f | |||
| 3636af4b45 | |||
| ef1f8d9cdd | |||
| 868d0116b0 | |||
| d866d922eb | |||
| a7509a7fa6 | |||
| ea98365a2d | |||
| 7b5b226fb2 | |||
| 5f48ec3f67 | |||
| 514ce820da | |||
| db34c23ea5 | |||
| daefe82106 | |||
| 4c3fb0bc30 | |||
| a43855001a | |||
| a0947a227d | |||
| d84536d04a | |||
| beb1d8712a | |||
| 2209f022ee | |||
| cce5359d5b | |||
| dbe15a7472 | |||
| 19f2933e08 | |||
| 3a188de6b7 | |||
| 72ed3644c6 | |||
| f8c14c59c7 | |||
| 501b59e490 | |||
| 7563a4e61a | |||
| dd9307656a | |||
| b57224d0d1 | |||
| ac06f6c25e | |||
| 64ab5f205c | |||
| 87fa3f92d0 | |||
| 2d23d6f449 | |||
| e48af75fcc | |||
| 2eb9424c63 | |||
| e67d3bd9c5 | |||
| 349618a138 | |||
| f4b9d26258 | |||
| 4be334fd40 | |||
| 6f526a9c1e | |||
| c09a12669e | |||
| 4a947ef2d5 | |||
| 4f22071c82 | |||
| e59091060f | |||
| b566850b46 | |||
| 57bf56bc58 | |||
| 4a19caa338 | |||
| 2c028094bb | |||
| 2027ff03b8 | |||
| 1dfb0db4f2 | |||
| 1e6b615674 | |||
| a1fa2d0285 | |||
| eefa3690c9 | |||
| 4d83ce2f0d | |||
| f7aa92af6d | |||
| 4b9d40b871 | |||
| 6915fc440a | |||
| 8e37536acb | |||
| 07b259f4f3 | |||
| e2627ee07f | |||
| 584dbbbaa9 | |||
| 2d623ac4f6 | |||
| 87d859fd4f | |||
| ce3e42dfd5 | |||
| 49aa5e7481 | |||
| 9b4f62d040 | |||
| edbb2045b2 | |||
| d1852d5dc2 | |||
| c5bb87afc2 | |||
| 36d295ac5c | |||
| 908e3deb58 | |||
| e71338f872 | |||
| 20bc1eb6f3 | |||
| 33da7101fa | |||
| 26df3687f4 | |||
| 512298b474 | |||
| c17c64e4c9 | |||
| 750824580b | |||
| bd8fb61e8c | |||
| 79afe58156 | |||
| a9ba74a5d4 | |||
| b47111069c | |||
| c915991175 | |||
| e75e6337a5 | |||
| a9c6008ced | |||
| 3c03182503 | |||
| c24b33859f | |||
| 95827f9c6b | |||
| f92e4d976b | |||
| 54b8c59a46 | |||
| 3270b8a9f4 | |||
| 660b87cd91 | |||
| 69d8f29ccc | |||
| f5d49207cd | |||
| f8aee3a96a | |||
| a32d206884 | |||
| 137193f113 | |||
| 5b272a28e9 | |||
| ebdc79e6ac | |||
| 2591284640 | |||
| 7af0168c9d | |||
| 7ef829affa | |||
| 7edeef2b5f | |||
| 2b1f64b367 | |||
| 9a8eb99c0e | |||
| 3c9e4a2e3d | |||
| 126ba7cccd | |||
| 6eba88a706 | |||
| 30d4c12d82 | |||
| aaa17b3913 | |||
| 10cc3c24af | |||
| ab5a418b85 | |||
| c086cdcaed | |||
| 34cda8754b | |||
| d068763c86 | |||
| b354fd4517 | |||
| e390c806d0 | |||
| 02b1aaebb2 | |||
| 9af28f75d5 | |||
| 230fe3ff00 | |||
| 7eb9699c73 | |||
| 53e8cf38f0 | |||
| ff11c11704 | |||
| 6e9a2432e1 | |||
| 1e1f75b5f7 | |||
| c833a3cd94 | |||
| 3dd3c9c533 | |||
| 79b7e62f40 | |||
| 095b279a02 | |||
| a49f416300 | |||
| f1afdde69f | |||
| e392ccae52 | |||
| b5fb32c4a9 | |||
| 09f6695024 | |||
| 941f16a0a3 | |||
| fd9c8c66c6 | |||
| 5887a59b20 | |||
| d7c67cdec4 | |||
| 48b0235643 | |||
| b70aacce45 | |||
| 5b80f8e3d5 | |||
| a40fd72765 | |||
| 2b563a51aa | |||
| db9154fad1 | |||
| f96059b5bd | |||
| 1474c24e80 | |||
| 478f766f59 | |||
| 0726a86708 | |||
| 5b0efd676f | |||
| 387da281fa | |||
| 774b8aa694 | |||
| c1713f6e04 | |||
| ab8d80acb7 | |||
| bcbbba905d | |||
| 94296a02eb | |||
| 1fdca1f9d6 | |||
| 1738ecb3a8 | |||
| 92f7eb05e5 | |||
| d013e5ac8f | |||
| 91173a2998 | |||
| cb825cd45f | |||
| a39705e273 | |||
| 4c51ce54e4 | |||
| 31c608e955 | |||
| 7c29914053 | |||
| 372749543d | |||
| 0443d0ee25 | |||
| c656746c1b | |||
| 5cd81d41be | |||
| 73979e5b46 | |||
| 7501ea2c34 | |||
| febf954fa6 | |||
| 57e9259036 | |||
| a51707276d | |||
| f5cf1b4245 | |||
| 1f32a9f77d | |||
| 5fdd0e63d4 | |||
| 4d4f30a394 | |||
| cd425220de | |||
| c69e9f21e3 | |||
| 334f9388a3 | |||
| 7c038fa00e | |||
| d400aa6289 | |||
| 3fc3adeaa5 | |||
| 1f7db7a687 | |||
| f3d7844bb9 | |||
| 11f7605cfc | |||
| 648457d767 | |||
| dafd599a12 | |||
| 371cc948b3 | |||
| 6d723a32c8 | |||
| d05e1f3a7d | |||
| 84b88c0b94 | |||
| 465c60f2e8 | |||
| 21e018e1f3 | |||
| d79a222d6f | |||
| 74a8bbd979 | |||
| a54279cce4 | |||
| 54c5117083 | |||
| 5252439220 | |||
| f4f24c1207 | |||
| 86951f4d14 | |||
| 4a37163653 | |||
| c90dbdaa55 | |||
| 8034dd6b53 | |||
| b25e998ff6 | |||
| 7a4d40447e | |||
| c3bdb68d4f | |||
| 541bc5ec2f | |||
| 61d9c68947 | |||
| f561e62f12 | |||
| b592fc69b6 | |||
| 673a12ff0a | |||
| 4499e04729 | |||
| 497660f578 | |||
| d6f03ea23d | |||
| 916c4900b0 | |||
| 4fe0d8af8a | |||
| de771c608c | |||
| d2ac9d000a | |||
| 9146f7073b | |||
| edaa68db29 | |||
| 9f4c80f417 | |||
| e30c3e0a65 | |||
| db28c80de6 | |||
| 6a6f51df40 | |||
| 4d00f8f6bc | |||
| 203b40f916 | |||
| ee882de65a | |||
| 5a5d12f7b4 | |||
| 37b62c3a5d | |||
| 4248159b51 | |||
| 623d1a81c5 | |||
| 6628d89ff8 | |||
| cd271dab5c | |||
| 1b944a6e6a | |||
| bd068ad156 | |||
| 5128bfdd4c | |||
| 7ec674ea26 | |||
| ffdce1e3ec | |||
| 0ff5d15f3e | |||
| 39ab3e7c76 | |||
| 87caccaa76 | |||
| 26c00d73cd | |||
| fed7c7789c | |||
| 01e1684fe8 | |||
| f1edbb098b | |||
| 1aa2267b69 | |||
| 7c8df23ab6 | |||
| 5400f35ade | |||
| 031c548e89 | |||
| 4a2ea98262 | |||
| 43ae9149ec | |||
| a6063f6444 | |||
| 030811fd02 | |||
| 373346eea7 | |||
| 22a9435218 | |||
| f502dd0b26 | |||
| fb98891782 | |||
| 670e8a488d | |||
| b48c171774 | |||
| 1b0330c905 | |||
| 5b86c81088 | |||
| 058a4ade3b | |||
| b57efc963f | |||
| 78b56821c9 | |||
| 5b5482d1d0 | |||
| 25ef7ac611 | |||
| 16b9b77809 | |||
| e05e188099 | |||
| 6e5e37bff2 | |||
| 618c10e7f3 | |||
| 93bee557b6 | |||
| 838fe8e8eb | |||
| 84ac8fdf67 | |||
| f3eb4a6824 | |||
| bfd76e0b59 | |||
| 204c45ec81 | |||
| 361830b421 | |||
| 621aac75c9 | |||
| c5c0638a2a | |||
| 2ea224fbc7 | |||
| 778cb31f1c | |||
| a5fe972e81 | |||
| 6696ba025b | |||
| a2bef555d3 | |||
| caf7f4e7d2 | |||
| f170efe046 | |||
| 0bf993ccf6 | |||
| 750456ed13 | |||
| e8616c246a | |||
| fe5dfa9e77 | |||
| 487d9e8236 | |||
| 085ad88d01 | |||
| 5d9f1aa476 | |||
| 6d5144d5fb | |||
| 6b5054ea6a | |||
| fd7c6d5123 | |||
| f931c5e8b7 | |||
| 983db224fc |
@@ -1,39 +0,0 @@
|
||||
---
|
||||
name: automate-zero-native
|
||||
description: Automate and inspect running zero-native WebView shell apps via the built-in automation server. Use when the user asks to test the app, list windows, take a screenshot, inspect a snapshot, reload the WebView, or verify a running zero-native example.
|
||||
---
|
||||
|
||||
# Automate zero-native apps
|
||||
|
||||
zero-native has a built-in automation system for inspecting running WebView shell apps. It works through file-based IPC in `.zig-cache/zero-native-automation/`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Run an app with automation enabled:
|
||||
|
||||
```bash
|
||||
zig build run-webview -Dplatform=macos -Dautomation=true
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
zig build
|
||||
zig-out/bin/zero-native automate list
|
||||
zig-out/bin/zero-native automate snapshot
|
||||
zig-out/bin/zero-native automate screenshot [path]
|
||||
zig-out/bin/zero-native automate reload
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Start the app with automation enabled.
|
||||
2. Run `zig-out/bin/zero-native automate snapshot` to confirm the window and WebView source.
|
||||
3. Use `zig-out/bin/zero-native automate reload` to request a reload.
|
||||
4. Use `zig-out/bin/zero-native automate screenshot [path]` when a placeholder screenshot artifact is enough.
|
||||
|
||||
## Notes
|
||||
|
||||
- Automation is compile-time gated: apps built without `-Dautomation=true` ignore automation files.
|
||||
- The current screenshot artifact is a placeholder PPM.
|
||||
- WebView DOM interaction is intentionally out of scope for this file-based automation layer.
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""The GLib 2.72 receipt's error-set allowlist.
|
||||
|
||||
Compiling gtk_host.c on stock ubuntu 22.04 (GLib 2.72, GTK 4.6) cannot
|
||||
succeed: the toolkit's GTK floor is 4.10, so GTK-age failures are the
|
||||
expected steady state. What this receipt pins is that NOTHING ELSE
|
||||
fails - a glib/gio symbol needing 2.74+ without a version-checked
|
||||
fallback shows up here as a diagnostic outside the allowlist below.
|
||||
|
||||
The allowlist is by diagnostic SHAPE, not symbol prefix:
|
||||
- undeclared gtk_/GTK_ functions are the GTK-age roots;
|
||||
- undeclared plain (non-glib-namespaced) identifiers are their
|
||||
cascades (locals whose declaring line failed);
|
||||
- int-conversion lines are cascades of undeclared functions returning
|
||||
int, and incidentally name glib types (GListModel), so a prefix
|
||||
denylist would false-positive on them.
|
||||
Everything else fails the step: unknown type name 'G...', undeclared
|
||||
g_/G_ symbols, missing members, any located shape not seen before,
|
||||
and any error line WITHOUT a file:line:col location (driver failures
|
||||
like "error: Unknown Clang option" never classify as diagnostics, so
|
||||
they must reject rather than sail through an empty error set).
|
||||
|
||||
The receipt also demands positive evidence it ran: at least
|
||||
MIN_GTK_ROOTS allowlisted GTK-age root diagnostics. A compile that
|
||||
produced no classifiable error set (wrong file, broken include path,
|
||||
invocation failure) proves nothing and must fail loudly - the clean
|
||||
run produces ~21 roots, so the floor sits far below real variance
|
||||
while catching "nothing actually compiled".
|
||||
|
||||
Why the cascade allowances are sound despite looking broad: this
|
||||
receipt is one lane in a lattice, not the sole guard on gtk_host.c.
|
||||
Every full-GTK lane (linux-webkitgtk, the canvas smokes, macOS)
|
||||
compiles the same file cleanly, so a typo'd local or an independent
|
||||
conversion bug is a red build elsewhere before it ever reaches this
|
||||
filter - the only errors unique to this lane are the old-glib delta.
|
||||
And within that delta, regressions always announce themselves through
|
||||
a REJECTED root before their cascades matter: a missing glib function
|
||||
is "call to undeclared function 'g_...'" (only gtk_/GTK_ roots are
|
||||
allowed), a missing glib type/macro is an unknown-type-name or
|
||||
undeclared-G_-identifier line - all rejected. The cascades allowed
|
||||
below can only follow roots this filter already failed the step for,
|
||||
or GTK-age roots it exists to permit.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
MIN_GTK_ROOTS = 5
|
||||
|
||||
located = re.compile(r"^[^:\n]+:\d+:\d+: error: (.*)")
|
||||
rejected = []
|
||||
gtk_roots = 0
|
||||
for line in sys.stdin:
|
||||
if "error:" not in line:
|
||||
continue
|
||||
m = located.match(line)
|
||||
if not m:
|
||||
rejected.append(line.rstrip() + " [unlocated error shape - driver or invocation failure]")
|
||||
continue
|
||||
msg = m.group(1)
|
||||
if re.match(r"call to undeclared function '(gtk_|GTK_)", msg):
|
||||
gtk_roots += 1
|
||||
continue
|
||||
if re.match(r"use of undeclared identifier '(?!g_|G_|G[A-Z])", msg):
|
||||
continue
|
||||
if "incompatible integer to pointer conversion" in msg and "from 'int'" in msg:
|
||||
# Only the cascade signature: an undeclared function defaults to
|
||||
# returning int, so its assignment lines convert FROM 'int'.
|
||||
# Conversions from any other type are not that cascade - reject.
|
||||
continue
|
||||
rejected.append(line.rstrip())
|
||||
|
||||
if rejected:
|
||||
print("non-GTK-age diagnostics against GLib 2.72 - the pre-2.74 fallback story regressed:")
|
||||
print("\n".join(rejected))
|
||||
sys.exit(1)
|
||||
if gtk_roots < MIN_GTK_ROOTS:
|
||||
print(
|
||||
f"only {gtk_roots} GTK-age root diagnostics (need >= {MIN_GTK_ROOTS}) - "
|
||||
"the compile did not exercise the old-GTK error set, so this receipt proved nothing"
|
||||
)
|
||||
sys.exit(1)
|
||||
print(f"fallback receipt ok: every error is GTK-age by shape ({gtk_roots} roots)")
|
||||
@@ -0,0 +1,278 @@
|
||||
#!/usr/bin/env bash
|
||||
# Linux canvas smoke under Xvfb — run on a machine with NO WebKitGTK dev
|
||||
# package, which makes the build itself the native-only link test.
|
||||
#
|
||||
# Exercises the Linux gpu_surface software path without a display server:
|
||||
# builds examples/ui-inbox (a native-only app, so its GTK host compiles
|
||||
# with the WebKitGTK stub seam and never links webkitgtk-6.0) with
|
||||
# -Dplatform=linux -Dweb-engine=system -Dautomation=true, runs it under
|
||||
# Xvfb, and asserts against the automation snapshot:
|
||||
#
|
||||
# 1. the built ELF carries no WebKitGTK reference (no libwebkitgtk
|
||||
# DT_NEEDED entry, no webkit_/jsc_ dynamic symbol), audited on the
|
||||
# real binary by tools/audit_web_layer.zig
|
||||
# 2. snapshot ready=true (app booted, automation server live)
|
||||
# 3. gpu_backend=software (the software present path is active)
|
||||
# 4. gpu_nonblank=true (real pixels were presented)
|
||||
# 5. widget-click "Add task" -> '4 open' (automation input mutates state)
|
||||
# 6. a real X11 right-click opens a task row's declared context menu and
|
||||
# clicking its item dispatches the Msg ('1 done')
|
||||
# 7. automate screenshot renders a non-empty PNG
|
||||
# 8. ZERO WebKit helper processes for the whole run (a native-only app
|
||||
# has no web layer to boot WebKit with)
|
||||
#
|
||||
# Deliberately NOT `set -e` (same as windows-canvas-smoke.sh): grep exits 1
|
||||
# on zero matches, and under `set -e` an assignment like `x=$(grep ...)` or
|
||||
# a swallowed `$(cli 2>&1)` capture dies with NO output — this job failed
|
||||
# three times with nothing in the log but the exit code. Every assertion
|
||||
# goes through fail(), which dumps the snapshot and the app log.
|
||||
set -u
|
||||
|
||||
# No WebKit sandbox workaround: a native-only app's host is compiled
|
||||
# without the web layer entirely (and even in web builds the main
|
||||
# WebView is lazy), so no WebKit helper processes start and the runner's
|
||||
# user-namespace restrictions never come into play. The zero-WebKit
|
||||
# assertion below keeps it that way.
|
||||
|
||||
# GTK_A11Y=none: under Xvfb there is no session bus providing org.a11y.Bus,
|
||||
# and GTK4's a11y init blocks ~25 s on the GDBus name lookup before warning
|
||||
# and continuing — the app's first runtime event landed after the readiness
|
||||
# window had already expired (reproduced in a local container: without this
|
||||
# the wait times out at startup; with it the full smoke passes).
|
||||
# Accessibility is not what this smoke tests.
|
||||
export GTK_A11Y="${GTK_A11Y:-none}"
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
app_dir="$repo_root/examples/ui-inbox"
|
||||
snap="$app_dir/.zig-cache/native-sdk-automation/snapshot.txt"
|
||||
cli="$repo_root/zig-out/bin/native"
|
||||
app_log="${TMPDIR:-/tmp}/linux-canvas-smoke-app.log"
|
||||
|
||||
# Readiness budget. Even with GTK_A11Y=none, shared ubuntu-24.04 runners
|
||||
# show a consistent ~27 s stall between EGL init and the app's first
|
||||
# runtime event (measured in runs 28690855597 pass / 28691951139 fail —
|
||||
# the SAME stall in both; the old hard 30 s `automate wait` window flipped
|
||||
# green/red on one or two seconds of runner noise). Local containers show
|
||||
# no stall at all. Widen the budget here instead of weakening the CLI
|
||||
# default; every correctness assertion stays strict.
|
||||
ready_timeout_ms=90000
|
||||
|
||||
app_pid=""
|
||||
xvfb_pid=""
|
||||
cleanup() {
|
||||
[ -n "$app_pid" ] && kill "$app_pid" >/dev/null 2>&1
|
||||
# Reap the app and our Xvfb directly so local runs exit clean (CI would
|
||||
# otherwise rely on the runner's orphan sweep).
|
||||
pkill -f "$app_dir/zig-out/bin/ui-inbox" >/dev/null 2>&1
|
||||
[ -n "$xvfb_pid" ] && kill "$xvfb_pid" >/dev/null 2>&1
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
diagnostics() {
|
||||
echo "---- diagnostics ----"
|
||||
echo "-- snapshot ($snap):"
|
||||
if [ -f "$snap" ]; then tr '|' '\n' < "$snap" | sed 's/^/ /'; else echo " (missing)"; fi
|
||||
echo "-- app log head ($app_log):"
|
||||
head -20 "$app_log" 2>/dev/null | sed 's/^/ /'
|
||||
echo "-- app log tail ($app_log):"
|
||||
tail -40 "$app_log" 2>/dev/null | sed 's/^/ /'
|
||||
# The X window list names what is actually on the glass — the context
|
||||
# menu popover is an override-redirect X window that never appears in
|
||||
# the automation snapshot, so this is the only record of whether it
|
||||
# (or anything else) was mapped when a step failed.
|
||||
echo "-- X windows (xwininfo -root -children):"
|
||||
if [ -n "${DISPLAY:-}" ] && command -v xwininfo >/dev/null 2>&1; then
|
||||
xwininfo -root -children 2>/dev/null | sed 's/^/ /'
|
||||
else
|
||||
echo " (no DISPLAY or xwininfo not installed)"
|
||||
fi
|
||||
echo "---------------------"
|
||||
}
|
||||
|
||||
# The hex ids of every child of the root window, popovers included
|
||||
# (override-redirect windows never pass through a window manager — Xvfb
|
||||
# has none anyway — but they are always children of the root).
|
||||
x_window_ids() {
|
||||
xwininfo -root -children 2>/dev/null | grep -oE '0x[0-9a-f]+'
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
diagnostics
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Canvas apps must never spawn WebKit: the window's main WebView is
|
||||
# created lazily and nothing in this app materializes it, so any
|
||||
# WebKitWebProcess/WebKitNetworkProcess during the run means an eager
|
||||
# creation regressed (and with it launch latency, resident helper
|
||||
# processes, and the sandbox trouble this smoke used to work around).
|
||||
assert_no_webkit() {
|
||||
local helpers
|
||||
helpers=$(pgrep -af 'WebKit(Web|Network)Process' 2>/dev/null)
|
||||
if [ -n "$helpers" ]; then
|
||||
echo "-- WebKit helper processes found ($1):"
|
||||
echo "$helpers" | sed 's/^/ /'
|
||||
fail "canvas app spawned WebKit processes ($1)"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---- build ----------------------------------------------------------------
|
||||
(cd "$repo_root" && zig build) || fail "root zig build (CLI) failed"
|
||||
(cd "$app_dir" && zig build -Dplatform=linux -Dweb-engine=system -Dautomation=true) \
|
||||
|| fail "ui-inbox Linux build failed (a native-only app must build without the WebKitGTK dev package)"
|
||||
|
||||
# ---- 1: the native-only ELF carries no WebKitGTK reference -----------------
|
||||
(cd "$repo_root" && zig run tools/audit_web_layer.zig -- "$app_dir/zig-out/bin/ui-inbox" absent) \
|
||||
|| fail "native-only ELF audit failed (the binary references WebKitGTK)"
|
||||
echo "== native-only ELF audit ok"
|
||||
|
||||
# ---- launch ---------------------------------------------------------------
|
||||
# The script owns its Xvfb (instead of wrapping the app in xvfb-run) so
|
||||
# the xdotool step below shares the app's display. -displayfd picks a
|
||||
# free display number, the modern equivalent of xvfb-run -a's probing.
|
||||
display_file="$(mktemp)"
|
||||
Xvfb -displayfd 4 -screen 0 1280x800x24 4>"$display_file" &
|
||||
xvfb_pid=$!
|
||||
for _ in $(seq 1 100); do
|
||||
[ -s "$display_file" ] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[ -s "$display_file" ] || fail "Xvfb never reported a display number"
|
||||
export DISPLAY=":$(cat "$display_file")"
|
||||
echo "== Xvfb on $DISPLAY"
|
||||
|
||||
cd "$app_dir" || fail "missing $app_dir"
|
||||
rm -rf .zig-cache/native-sdk-automation
|
||||
"$app_dir/zig-out/bin/ui-inbox" > "$app_log" 2>&1 &
|
||||
app_pid=$!
|
||||
|
||||
# ---- 2: automation snapshot becomes ready ---------------------------------
|
||||
# `automate assert` self-reports on timeout (missing patterns + snapshot
|
||||
# tail) and prints the measured latency on success, so green logs carry
|
||||
# the readiness margin.
|
||||
"$cli" automate assert --timeout-ms "$ready_timeout_ms" 'ready=true' \
|
||||
|| fail "snapshot never became ready"
|
||||
|
||||
# ---- 3 + 4: software backend presented non-blank pixels --------------------
|
||||
"$cli" automate assert --timeout-ms 30000 'gpu_nonblank=true' \
|
||||
|| fail "gpu_nonblank never became true"
|
||||
grep -q 'gpu_backend=software' "$snap" || fail "gpu_backend is not software"
|
||||
echo "== canvas: $(grep -o 'gpu_backend=[a-z]*' "$snap" | head -1)" \
|
||||
"$(grep -o 'gpu_nonblank=[a-z]*' "$snap" | head -1)"
|
||||
assert_no_webkit "after first presented frame"
|
||||
echo "== zero WebKit processes after first presented frame"
|
||||
|
||||
# ---- 5: automation widget-click mutates the model --------------------------
|
||||
echo "== open before click: $(grep -oE '[0-9]+ open' "$snap" | head -1)"
|
||||
add_id=$(grep -o 'widget @w1/inbox-canvas#[0-9]* role=button name="Add task"' "$snap" \
|
||||
| grep -o '#[0-9]*' | tr -d '#')
|
||||
[ -n "$add_id" ] || fail "Add task button not found in snapshot"
|
||||
"$cli" automate widget-click inbox-canvas "$add_id" || fail "CLI widget-click failed"
|
||||
"$cli" automate assert --timeout-ms 30000 '4 open' \
|
||||
|| fail "widget-click did not reach '4 open'"
|
||||
echo "== open after click: $(grep -oE '[0-9]+ open' "$snap" | head -1)"
|
||||
|
||||
# ---- 6: a real right-click opens and drives a task-row context menu --------
|
||||
# What this proves: an X-level SECONDARY-button press (GDK button 3)
|
||||
# travels the whole GTK path — click gesture -> button mapping -> runtime
|
||||
# secondary check -> declared-menu lookup -> native popover — and
|
||||
# clicking the popover's "Toggle done" item dispatches the row's Msg,
|
||||
# observable as the model change '1 done' in the snapshot (the popover
|
||||
# itself is an OS surface and never appears in the snapshot, so the
|
||||
# dispatched selection is the provable signal).
|
||||
# The menu is driven by POINTER, never keyboard: under Xvfb there is no
|
||||
# window manager, so the popover's X window never receives keyboard
|
||||
# focus and `xdotool key Down/Return` dies on the canvas beneath it —
|
||||
# but pointer events resolve by position (and the popover holds the
|
||||
# pointer grab), so a click on the popover surface reaches the item. The
|
||||
# popover is an override-redirect X window of its own: it is found by
|
||||
# diffing the root's children across the right-click, and because the
|
||||
# row declares exactly one item and the popover draws no arrow, the
|
||||
# center of that new window IS "Toggle done".
|
||||
# Regression coverage: the swapped GDK button mapping this smoke was
|
||||
# blind to made every right-click arrive as MIDDLE and never open the
|
||||
# menu — under that defect the popover window never appears and the
|
||||
# step fails at the popover lookup, before any selection.
|
||||
# Limit: '1 done' proves the toggle Msg dispatched; it cannot attribute
|
||||
# the dispatch to the popover VISUALLY (no snapshot record of the OS
|
||||
# menu), but the click lands on the popover's own X window, so no
|
||||
# canvas-level path can consume it — the canvas never sees the press.
|
||||
echo "== done before right-click: $(grep -oE '[0-9]+ done' "$snap" | head -1)"
|
||||
row_line=$(grep -o 'widget @w1/inbox-canvas#[0-9]*[^|]*context_menu=\["Toggle done"\][^|]*' "$snap" | head -1)
|
||||
[ -n "$row_line" ] || fail "no task row with the declared context menu in snapshot"
|
||||
bounds=$(echo "$row_line" | grep -o 'bounds=([^)]*)')
|
||||
bx=$(echo "$bounds" | sed -n 's/bounds=(\([0-9.-]*\),.*/\1/p')
|
||||
by=$(echo "$bounds" | sed -n 's/bounds=([0-9.-]*,\([0-9.-]*\) .*/\1/p')
|
||||
bw=$(echo "$bounds" | sed -n 's/.* \([0-9.]*\)x[0-9.]*).*/\1/p')
|
||||
bh=$(echo "$bounds" | sed -n 's/.* [0-9.]*x\([0-9.]*\)).*/\1/p')
|
||||
[ -n "$bx" ] && [ -n "$by" ] && [ -n "$bw" ] && [ -n "$bh" ] || fail "could not parse row bounds: $row_line"
|
||||
win=""
|
||||
for w in $(xdotool search --name "Inbox" 2>/dev/null); do win="$w"; done
|
||||
[ -n "$win" ] || fail "app X window not found"
|
||||
eval "$(xdotool getwindowgeometry --shell "$win")"
|
||||
# Xvfb has no compositor, so GTK draws no CSD shadow and the X window is
|
||||
# exactly the client area; correct by the measured height difference the
|
||||
# same way windows-canvas-smoke.sh does, in case a runner image ever
|
||||
# composites.
|
||||
client_h=$(grep -o 'window @w1 "[^"]*" bounds=([^)]*)' "$snap" | head -1 \
|
||||
| sed -n 's/.*x\([0-9]*\)[^x]*$/\1/p')
|
||||
[ -n "$client_h" ] || client_h=$HEIGHT
|
||||
y_off=$((HEIGHT - client_h))
|
||||
[ "$y_off" -ge 0 ] 2>/dev/null || y_off=0
|
||||
cx=$(awk "BEGIN{printf \"%d\", $X + $bx + $bw / 2}")
|
||||
cy=$(awk "BEGIN{printf \"%d\", $Y + $y_off + $by + $bh / 2}")
|
||||
xdotool windowactivate "$win" >/dev/null 2>&1 || xdotool windowfocus "$win" >/dev/null 2>&1
|
||||
command -v xwininfo >/dev/null 2>&1 || fail "xwininfo not installed (x11-utils) — required to locate the popover's X window"
|
||||
# xdotool reports decimal window ids, xwininfo hexadecimal; compare in hex.
|
||||
win_hex=$(printf '0x%x' "$win")
|
||||
pre_windows=" $(x_window_ids | tr '\n' ' ') "
|
||||
echo "== right-clicking task row $bounds at ($cx,$cy)"
|
||||
xdotool mousemove "$cx" "$cy" click 3
|
||||
# Wait for the popover's X window: a viewable, non-trivial root child
|
||||
# that did not exist before the right-click.
|
||||
popover=""
|
||||
popover_geom=""
|
||||
for _ in $(seq 1 50); do
|
||||
# Root children list in stacking order (bottom to top): scan top-down
|
||||
# so the just-mapped popover wins over any other new surface.
|
||||
for w in $(x_window_ids | tac); do
|
||||
case "$pre_windows" in *" $w "*) continue ;; esac
|
||||
[ "$w" = "$win_hex" ] && continue
|
||||
geom=$(xwininfo -id "$w" 2>/dev/null)
|
||||
echo "$geom" | grep -q 'Map State: IsViewable' || continue
|
||||
pw=$(echo "$geom" | sed -n 's/^ *Width: *\([0-9]*\).*/\1/p')
|
||||
ph=$(echo "$geom" | sed -n 's/^ *Height: *\([0-9]*\).*/\1/p')
|
||||
[ -n "$pw" ] && [ -n "$ph" ] && [ "$pw" -gt 10 ] && [ "$ph" -gt 10 ] || continue
|
||||
popover="$w"
|
||||
popover_geom="$geom"
|
||||
break
|
||||
done
|
||||
[ -n "$popover" ] && break
|
||||
sleep 0.2
|
||||
done
|
||||
[ -n "$popover" ] || fail "context-menu popover X window never appeared after the right-click"
|
||||
px=$(echo "$popover_geom" | sed -n 's/^ *Absolute upper-left X: *\(-*[0-9]*\).*/\1/p')
|
||||
py=$(echo "$popover_geom" | sed -n 's/^ *Absolute upper-left Y: *\(-*[0-9]*\).*/\1/p')
|
||||
pw=$(echo "$popover_geom" | sed -n 's/^ *Width: *\([0-9]*\).*/\1/p')
|
||||
ph=$(echo "$popover_geom" | sed -n 's/^ *Height: *\([0-9]*\).*/\1/p')
|
||||
[ -n "$px" ] && [ -n "$py" ] || fail "could not parse popover geometry for $popover"
|
||||
mx=$((px + pw / 2))
|
||||
my=$((py + ph / 2))
|
||||
echo "== popover $popover at ${pw}x${ph}+${px}+${py}; clicking its only item at ($mx,$my)"
|
||||
xdotool mousemove "$mx" "$my" click 1
|
||||
"$cli" automate assert --timeout-ms 30000 '1 done' \
|
||||
|| fail "right-click menu selection did not dispatch the toggle Msg ('1 done')"
|
||||
echo "== done after menu selection: $(grep -oE '[0-9]+ done' "$snap" | head -1)"
|
||||
|
||||
# ---- 7: screenshot renders a non-empty PNG ---------------------------------
|
||||
"$cli" automate screenshot inbox-canvas || fail "CLI screenshot failed"
|
||||
test -s .zig-cache/native-sdk-automation/screenshot-inbox-canvas.png \
|
||||
|| fail "screenshot PNG missing or empty"
|
||||
|
||||
# ---- 8: still zero WebKit processes at the end of the run -------------------
|
||||
assert_no_webkit "at end of run"
|
||||
echo "== zero WebKit processes at end of run"
|
||||
|
||||
echo "PASS: linux canvas smoke"
|
||||
exit 0
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
# Linux DEBUG-scaffold smoke under Xvfb — the `native dev` shape, pinned.
|
||||
#
|
||||
# The linux-canvas-smoke job builds at the graph's release default, and
|
||||
# Release builds always use the LLVM backend — so a Debug-only x86_64
|
||||
# codegen fault can pass every release-shaped CI lane and still crash the
|
||||
# very first thing a new user runs (`native init` + `native dev` builds
|
||||
# Debug). This smoke is the receipt for that gap: it scaffolds the ts-core
|
||||
# template with the CLI (the `native init` default), builds it exactly as
|
||||
# the dev loop does (-Doptimize=Debug), runs it headless, and requires the
|
||||
# startup scene to actually come up — app_start, scene load, the
|
||||
# gpu_surface shell view created through the GTK host's C seam, and real
|
||||
# presented pixels. The original failure this pins: zig 0.16.0's
|
||||
# self-hosted x86_64 backend (the Debug default without use_llvm) shifts
|
||||
# the stack-passed arguments of `native_sdk_gtk_create_view`, so the host
|
||||
# read a garbage `role` pointer and segfaulted before the first frame.
|
||||
#
|
||||
# Deliberately NOT `set -e` (same reasoning as linux-canvas-smoke.sh):
|
||||
# every assertion goes through fail(), which dumps the snapshot and the
|
||||
# app log instead of dying silently on a swallowed capture.
|
||||
set -u
|
||||
|
||||
# GTK_A11Y=none: under Xvfb there is no session bus providing org.a11y.Bus,
|
||||
# and GTK4's a11y init blocks ~25 s before continuing (see
|
||||
# linux-canvas-smoke.sh, which measured it). Accessibility is not what
|
||||
# this smoke tests.
|
||||
export GTK_A11Y="${GTK_A11Y:-none}"
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cli="$repo_root/zig-out/bin/native"
|
||||
work_dir="${TMPDIR:-/tmp}/native-linux-dev-smoke"
|
||||
app_name="dev-smoke"
|
||||
app_dir="$work_dir/$app_name"
|
||||
snap="$app_dir/.zig-cache/native-sdk-automation/snapshot.txt"
|
||||
app_log="${TMPDIR:-/tmp}/linux-dev-smoke-app.log"
|
||||
|
||||
# Same widened cold-start readiness budget as linux-canvas-smoke.sh:
|
||||
# shared runners stall tens of seconds before the first runtime event.
|
||||
ready_timeout_ms=90000
|
||||
|
||||
app_pid=""
|
||||
cleanup() {
|
||||
[ -n "$app_pid" ] && kill "$app_pid" >/dev/null 2>&1
|
||||
pkill -f "$app_dir/zig-out/bin/$app_name" >/dev/null 2>&1
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
diagnostics() {
|
||||
echo "---- diagnostics ----"
|
||||
echo "-- snapshot ($snap):"
|
||||
if [ -f "$snap" ]; then tr '|' '\n' < "$snap" | sed 's/^/ /'; else echo " (missing)"; fi
|
||||
echo "-- app log head ($app_log):"
|
||||
head -20 "$app_log" 2>/dev/null | sed 's/^/ /'
|
||||
echo "-- app log tail ($app_log):"
|
||||
tail -40 "$app_log" 2>/dev/null | sed 's/^/ /'
|
||||
echo "---------------------"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
diagnostics
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ---- build the CLI ---------------------------------------------------------
|
||||
(cd "$repo_root" && zig build) || fail "root zig build (CLI) failed"
|
||||
|
||||
# ---- scaffold the default template (ts-core, native frontend) --------------
|
||||
rm -rf "$work_dir"
|
||||
mkdir -p "$work_dir" || fail "cannot create $work_dir"
|
||||
(cd "$work_dir" && "$cli" init "$app_name" --framework "$repo_root") \
|
||||
|| fail "native init failed"
|
||||
|
||||
# ---- build the app the way the dev loop does (Debug) ------------------------
|
||||
# `native build` forwards -D flags to the generated graph verbatim;
|
||||
# -Doptimize=Debug pins the exact mode `native dev` uses, and
|
||||
# -Dautomation=true arms the snapshot publisher this smoke asserts on.
|
||||
(cd "$app_dir" && "$cli" build -Doptimize=Debug -Dautomation=true) \
|
||||
|| fail "Debug scaffold build failed"
|
||||
|
||||
# ---- launch headless --------------------------------------------------------
|
||||
cd "$app_dir" || fail "missing $app_dir"
|
||||
rm -rf .zig-cache/native-sdk-automation
|
||||
xvfb-run -a "$app_dir/zig-out/bin/$app_name" > "$app_log" 2>&1 &
|
||||
app_pid=$!
|
||||
|
||||
# ---- startup scene reached the host: ready, then presented pixels ----------
|
||||
# ready=true alone proves the crash site passed: the snapshot publishes
|
||||
# only after app_start dispatched and the scene's shell views (the
|
||||
# gpu_surface view with its role/accessibility strings) were created
|
||||
# through native_sdk_gtk_create_view.
|
||||
"$cli" automate assert --timeout-ms "$ready_timeout_ms" 'ready=true' \
|
||||
|| fail "snapshot never became ready (startup scene did not come up)"
|
||||
"$cli" automate assert --timeout-ms 30000 'gpu_nonblank=true' \
|
||||
|| fail "gpu_nonblank never became true"
|
||||
|
||||
# ---- the template's UI actually built over the scene ------------------------
|
||||
"$cli" automate assert --timeout-ms 30000 'role=button name="Reset"' 'total: 0' \
|
||||
|| fail "template widgets missing from the snapshot"
|
||||
|
||||
echo "PASS: linux dev smoke (Debug scaffold booted its startup scene)"
|
||||
exit 0
|
||||
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env bash
|
||||
# Windows canvas smoke under Wine.
|
||||
#
|
||||
# Exercises the Windows gpu_surface Direct2D path (src/platform/windows/
|
||||
# webview2_host.cpp: child HWND, WM_TIMER frame events, retained binary
|
||||
# packets) under Wine: cross-compiles examples/ui-inbox for
|
||||
# x86_64-windows-gnu, runs the .exe under Xvfb + Wine, and asserts against
|
||||
# the automation snapshot:
|
||||
#
|
||||
# 1. snapshot ready=true (app booted, automation server live)
|
||||
# 2. gpu_backend=direct2d (the retained packet path is active)
|
||||
# 3. gpu_nonblank=true (real pixels were presented)
|
||||
# 4. widget-click "Add task" -> '4 open' (automation input mutates state)
|
||||
# 5. real X11 click + typing lands in the draft textbox (XTEST -> Wine ->
|
||||
# WM_LBUTTONDOWN/WM_CHAR -> runtime)
|
||||
#
|
||||
# Step 5 (xdotool) is deliberately included: it is the only coverage of the
|
||||
# Win32 pointer/keyboard input mapping in webview2_host.cpp. It is also the
|
||||
# flakiest step (window lookup, focus without a window manager), so every
|
||||
# failure path dumps the X window list, the snapshot, and the app log.
|
||||
#
|
||||
# Deliberately NOT `set -e`: grep exits 1 on zero matches inside the poll
|
||||
# loops, and we want explicit, diagnosable failures instead of silent early
|
||||
# exits. Every assertion goes through fail(), which dumps diagnostics.
|
||||
set -u
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
app_dir="$repo_root/examples/ui-inbox"
|
||||
snap="$app_dir/.zig-cache/native-sdk-automation/snapshot.txt"
|
||||
cli="$repo_root/zig-out/bin/native"
|
||||
app_log="${TMPDIR:-/tmp}/windows-canvas-smoke-app.log"
|
||||
|
||||
# Wine needs an X display; when none is present (CI), re-exec the whole
|
||||
# script under a private Xvfb server so the app and xdotool share it. The
|
||||
# explicit screen size beats xvfb-run's 640x480x8 default: the app window is
|
||||
# 720x520 and Wine wants a 24-bit visual.
|
||||
if [ -z "${DISPLAY:-}" ]; then
|
||||
exec xvfb-run -a --server-args="-screen 0 1280x800x24" "$0" "$@"
|
||||
fi
|
||||
|
||||
export WINEPREFIX="${WINEPREFIX:-$repo_root/.zig-cache/wineprefix}"
|
||||
export WINEDEBUG="${WINEDEBUG:--all}"
|
||||
|
||||
app_pid=""
|
||||
cleanup() {
|
||||
[ -n "$app_pid" ] && kill "$app_pid" >/dev/null 2>&1
|
||||
wineserver -k >/dev/null 2>&1
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
diagnostics() {
|
||||
echo "---- diagnostics ----"
|
||||
echo "-- X windows:"
|
||||
xdotool search --name "." 2>/dev/null | while read -r w; do
|
||||
echo " $w: $(xdotool getwindowname "$w" 2>/dev/null)"
|
||||
done
|
||||
echo "-- snapshot ($snap):"
|
||||
if [ -f "$snap" ]; then tr '|' '\n' < "$snap" | sed 's/^/ /'; else echo " (missing)"; fi
|
||||
echo "-- app log tail ($app_log):"
|
||||
tail -40 "$app_log" 2>/dev/null | sed 's/^/ /'
|
||||
echo "---------------------"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
diagnostics
|
||||
exit 1
|
||||
}
|
||||
|
||||
# poll <seconds> <pattern>: wait until $snap contains <pattern>.
|
||||
poll() {
|
||||
local deadline=$((SECONDS + $1))
|
||||
while [ "$SECONDS" -lt "$deadline" ]; do
|
||||
[ -f "$snap" ] && grep -q "$2" "$snap" && return 0
|
||||
sleep 0.5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Print the first snapshot match, retrying reads that land between the
|
||||
# runtime's truncate and rewrite on a presented frame.
|
||||
snapshot_match() {
|
||||
local match=""
|
||||
for _ in $(seq 1 20); do
|
||||
match=$(grep -o "$1" "$snap" | head -1)
|
||||
if [ -n "$match" ]; then
|
||||
printf '%s\n' "$match"
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---- build ----------------------------------------------------------------
|
||||
(cd "$repo_root" && zig build) || fail "root zig build (CLI) failed"
|
||||
(cd "$app_dir" && zig build -Dtarget=x86_64-windows-gnu -Dplatform=windows -Dweb-engine=system -Dautomation=true) \
|
||||
|| fail "ui-inbox Windows cross-compile failed"
|
||||
|
||||
# ---- wineprefix -----------------------------------------------------------
|
||||
# First run initializes the prefix (measured ~10-30s on CI-class machines);
|
||||
# subsequent runs are instant, so no cache step is needed.
|
||||
start=$SECONDS
|
||||
wineboot --init >/dev/null 2>&1
|
||||
wineserver --wait >/dev/null 2>&1
|
||||
echo "== wineprefix ready in $((SECONDS - start))s ($WINEPREFIX)"
|
||||
|
||||
# ---- launch ---------------------------------------------------------------
|
||||
cd "$app_dir" || fail "missing $app_dir"
|
||||
rm -rf .zig-cache/native-sdk-automation
|
||||
mkdir -p .zig-cache/native-sdk-automation
|
||||
wine zig-out/bin/ui-inbox.exe > "$app_log" 2>&1 &
|
||||
app_pid=$!
|
||||
|
||||
# ---- 1: automation snapshot becomes ready ---------------------------------
|
||||
poll 180 'ready=true' || fail "snapshot never became ready"
|
||||
echo "== ready: $(head -1 "$snap" | cut -d'|' -f1)"
|
||||
|
||||
# ---- 2 + 3: Direct2D backend presented non-blank pixels --------------------
|
||||
poll 60 'gpu_nonblank=true' || fail "gpu_nonblank never became true"
|
||||
poll 10 'gpu_backend=direct2d' || fail "gpu_backend is not direct2d"
|
||||
echo "== canvas: $(grep -o 'gpu_backend=[a-z]*' "$snap" | head -1)" \
|
||||
"$(grep -o 'gpu_nonblank=[a-z]*' "$snap" | head -1)" \
|
||||
"$(grep -o 'gpu_sample=0x[0-9a-f]*' "$snap" | head -1)" \
|
||||
"$(grep -o 'gpu_present_mode=[a-z]*' "$snap" | head -1)"
|
||||
|
||||
# ---- 4: automation widget-click mutates the model --------------------------
|
||||
echo "== open before click: $(grep -oE '[0-9]+ open' "$snap" | head -1)"
|
||||
add_id=$(snapshot_match 'widget @w1/inbox-canvas#[0-9]* role=button name="Add task"' \
|
||||
| grep -o '#[0-9]*' | tr -d '#')
|
||||
[ -n "$add_id" ] || fail "Add task button not found in snapshot"
|
||||
"$cli" automate widget-click inbox-canvas "$add_id" || fail "CLI widget-click failed"
|
||||
poll 30 '4 open' || fail "widget-click did not reach '4 open'"
|
||||
echo "== open after click: $(grep -oE '[0-9]+ open' "$snap" | head -1)"
|
||||
|
||||
# ---- 5: real X11 input through the Win32 path ------------------------------
|
||||
# Root-coordinate math. Hidden-titlebar windows keep the full overlapped
|
||||
# frame and reclaim the caption band through WM_NCCALCSIZE, so the Win32
|
||||
# client area starts at the very top of the window. Under a WM-less Wine
|
||||
# the X11 driver still places (and reports) the client X window at the
|
||||
# DEFAULT frame offset - one caption band lower - so the X origin sits a
|
||||
# band BELOW where Win32 client coordinates actually map, and the reported
|
||||
# X height is short by exactly that band (measured: X window 718x489 at
|
||||
# y=30 for a 718x519 client whose clicks land at y=0). The snapshot knows
|
||||
# the true client height, so the height shortfall IS the y correction; a
|
||||
# standard-frame window reports matching heights and corrects by zero.
|
||||
win=""
|
||||
for w in $(xdotool search --name "." 2>/dev/null); do
|
||||
case "$(xdotool getwindowname "$w" 2>/dev/null)" in
|
||||
*[Ii]nbox*) win="$w" ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$win" ] || fail "app X window not found"
|
||||
eval "$(xdotool getwindowgeometry --shell "$win")"
|
||||
# The runtime rewrites snapshot.txt on every presented frame. Retry the
|
||||
# extraction instead of treating a read that lands between truncate and
|
||||
# write as a standard-frame window: that fallback erases the 30px Wine
|
||||
# caption correction and turns the real-input receipt into a flaky miss.
|
||||
client_line=$(snapshot_match 'window @w1 "[^"]*" bounds=([^)]*)')
|
||||
[ -n "$client_line" ] || fail "could not read client bounds from snapshot"
|
||||
client_h=$(printf '%s\n' "$client_line" | sed -n 's/.*x\([0-9]*\)[^x]*$/\1/p')
|
||||
[ -n "$client_h" ] || fail "could not read client height from snapshot"
|
||||
y_off=$((client_h - HEIGHT))
|
||||
[ "$y_off" -ge 0 ] 2>/dev/null || y_off=0
|
||||
echo "== x window $win: pos=($X,$Y) size=${WIDTH}x${HEIGHT} client_h=$client_h y_off=$y_off"
|
||||
xdotool windowactivate "$win" >/dev/null 2>&1 || xdotool windowfocus "$win" >/dev/null 2>&1
|
||||
|
||||
draft_line=$(snapshot_match 'widget @w1/inbox-canvas#[0-9]* role=textbox[^|]*')
|
||||
[ -n "$draft_line" ] || fail "draft textbox not found in snapshot"
|
||||
bounds=$(echo "$draft_line" | grep -o 'bounds=([^)]*)')
|
||||
bx=$(echo "$bounds" | sed -n 's/bounds=(\([0-9.]*\),.*/\1/p')
|
||||
by=$(echo "$bounds" | sed -n 's/bounds=([0-9.]*,\([0-9.]*\) .*/\1/p')
|
||||
bw=$(echo "$bounds" | sed -n 's/.* \([0-9.]*\)x[0-9.]*).*/\1/p')
|
||||
bh=$(echo "$bounds" | sed -n 's/.* [0-9.]*x\([0-9.]*\)).*/\1/p')
|
||||
[ -n "$bx" ] && [ -n "$by" ] && [ -n "$bw" ] && [ -n "$bh" ] || fail "could not parse draft bounds: $draft_line"
|
||||
cx=$(awk "BEGIN{printf \"%d\", $X + $bx + $bw / 2}")
|
||||
cy=$(awk "BEGIN{printf \"%d\", $Y - $y_off + $by + $bh / 2}")
|
||||
echo "== clicking draft field $bounds at ($cx,$cy)"
|
||||
xdotool mousemove "$cx" "$cy" click 1
|
||||
# The click must move widget focus into the textbox before any keys are
|
||||
# sent: spaces in the typed string would otherwise activate whatever
|
||||
# widget held focus (a button press adds a task and the real failure -
|
||||
# input landing in the wrong widget - would read as missing text).
|
||||
poll 10 'role=textbox[^|]*focused=true' || fail "draft textbox did not take focus from the click"
|
||||
sleep 1
|
||||
xdotool type --delay 120 "hi from wine"
|
||||
poll 30 'hi from wine' || fail "typed text never appeared in the snapshot"
|
||||
echo "== draft after typing: $(grep -o 'widget @w1/inbox-canvas#[0-9]* role=textbox[^|]*' "$snap" | head -1 | cut -c1-160)"
|
||||
echo "== input latency: $(grep -o 'gpu_input_latency_ns=[0-9]*' "$snap" | head -1)"
|
||||
|
||||
echo "PASS: windows canvas smoke"
|
||||
exit 0
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
# Windows effects smoke under Wine.
|
||||
#
|
||||
# Exercises the effect system's live Windows path (src/runtime/effects.zig
|
||||
# worker threads -> PostMessageW wake in src/platform/windows/
|
||||
# webview2_host.cpp -> loop-thread drain) without Windows hardware:
|
||||
# cross-compiles examples/effects-probe for x86_64-windows-gnu, runs the
|
||||
# .exe under Xvfb + Wine, and asserts against the automation snapshot and
|
||||
# the app's trace log:
|
||||
#
|
||||
# 1. snapshot ready=true (app booted, automation server live)
|
||||
# 2. gpu_backend=direct2d + nonblank (the canvas presented real pixels)
|
||||
# 3. widget-click "Start stream" (fx.spawn launches cmd.exe under
|
||||
# Wine; streamed lines land in the
|
||||
# model and grow the snapshot)
|
||||
# 4. app log shows event=effects_wake (the worker's PostMessageW wake was
|
||||
# marshalled through the message
|
||||
# loop -- the wake path itself, not
|
||||
# just the frame-tick drain)
|
||||
# 5. widget-click "Cancel" (fx.cancel terminates the child;
|
||||
# status shows "cancelled")
|
||||
# 6. the line count freezes (no lines arrive after cancel,
|
||||
# sampled across ~5 more would-be
|
||||
# line intervals)
|
||||
#
|
||||
# Known caveat: the timer present mode also drains effect completions on
|
||||
# every frame tick (ui_app.zig handleFrame), so line delivery alone cannot
|
||||
# isolate the wake; that is why step 4 checks the trace log for the wake
|
||||
# events directly instead of inferring the wake from model updates.
|
||||
#
|
||||
# Deliberately NOT `set -e`: grep exits 1 on zero matches inside the poll
|
||||
# loops, and we want explicit, diagnosable failures instead of silent early
|
||||
# exits. Every assertion goes through fail(), which dumps diagnostics.
|
||||
set -u
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
app_dir="$repo_root/examples/effects-probe"
|
||||
snap="$app_dir/.zig-cache/native-sdk-automation/snapshot.txt"
|
||||
cli="$repo_root/zig-out/bin/native"
|
||||
app_log="${TMPDIR:-/tmp}/windows-effects-smoke-app.log"
|
||||
|
||||
# Wine needs an X display; when none is present (CI), re-exec the whole
|
||||
# script under a private Xvfb server. The explicit screen size beats
|
||||
# xvfb-run's 640x480x8 default: the app window is 560x480 and Wine wants a
|
||||
# 24-bit visual.
|
||||
if [ -z "${DISPLAY:-}" ]; then
|
||||
exec xvfb-run -a --server-args="-screen 0 1280x800x24" "$0" "$@"
|
||||
fi
|
||||
|
||||
export WINEPREFIX="${WINEPREFIX:-$repo_root/.zig-cache/wineprefix}"
|
||||
export WINEDEBUG="${WINEDEBUG:--all}"
|
||||
|
||||
app_pid=""
|
||||
cleanup() {
|
||||
[ -n "$app_pid" ] && kill "$app_pid" >/dev/null 2>&1
|
||||
wineserver -k >/dev/null 2>&1
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
diagnostics() {
|
||||
echo "---- diagnostics ----"
|
||||
echo "-- snapshot ($snap):"
|
||||
if [ -f "$snap" ]; then tr '|' '\n' < "$snap" | sed 's/^/ /'; else echo " (missing)"; fi
|
||||
echo "-- app log tail ($app_log):"
|
||||
tail -40 "$app_log" 2>/dev/null | sed 's/^/ /'
|
||||
echo "---------------------"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
diagnostics
|
||||
exit 1
|
||||
}
|
||||
|
||||
# poll <seconds> <pattern>: wait until $snap contains <pattern>.
|
||||
poll() {
|
||||
local deadline=$((SECONDS + $1))
|
||||
while [ "$SECONDS" -lt "$deadline" ]; do
|
||||
[ -f "$snap" ] && grep -q "$2" "$snap" && return 0
|
||||
sleep 0.5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# The status bar renders "{N} lines total · {M} dropped"; extract N.
|
||||
total_lines() {
|
||||
grep -o '[0-9]* lines total' "$snap" 2>/dev/null | head -1 | grep -o '^[0-9]*'
|
||||
}
|
||||
|
||||
# widget_id <name>: find a widget id in the snapshot by accessible name.
|
||||
widget_id() {
|
||||
grep -o "widget @w1/probe-canvas#[0-9]* role=button name=\"$1\"" "$snap" \
|
||||
| grep -o '#[0-9]*' | tr -d '#'
|
||||
}
|
||||
|
||||
# ---- build ----------------------------------------------------------------
|
||||
(cd "$repo_root" && zig build) || fail "root zig build (CLI) failed"
|
||||
# effects-probe is a zero-config app (app.zon + src, no build.zig): the CLI
|
||||
# synthesizes its build graph. -Doptimize=Debug keeps the smoke binary at
|
||||
# the debug shape (`native build` alone would inject ReleaseFast).
|
||||
"$cli" build "$app_dir" -Dtarget=x86_64-windows-gnu -Dplatform=windows -Dweb-engine=system -Dautomation=true -Doptimize=Debug \
|
||||
|| fail "effects-probe Windows cross-compile failed"
|
||||
|
||||
# ---- wineprefix -----------------------------------------------------------
|
||||
start=$SECONDS
|
||||
wineboot --init >/dev/null 2>&1
|
||||
wineserver --wait >/dev/null 2>&1
|
||||
echo "== wineprefix ready in $((SECONDS - start))s ($WINEPREFIX)"
|
||||
|
||||
# ---- launch ---------------------------------------------------------------
|
||||
cd "$app_dir" || fail "missing $app_dir"
|
||||
rm -rf .zig-cache/native-sdk-automation
|
||||
mkdir -p .zig-cache/native-sdk-automation
|
||||
wine zig-out/bin/effects-probe.exe > "$app_log" 2>&1 &
|
||||
app_pid=$!
|
||||
|
||||
# ---- 1: automation snapshot becomes ready ---------------------------------
|
||||
poll 180 'ready=true' || fail "snapshot never became ready"
|
||||
echo "== ready: $(head -1 "$snap" | cut -d'|' -f1)"
|
||||
|
||||
# ---- 2: Direct2D backend presented non-blank pixels ------------------------
|
||||
poll 60 'gpu_nonblank=true' || fail "gpu_nonblank never became true"
|
||||
grep -q 'gpu_backend=direct2d' "$snap" || fail "gpu_backend is not direct2d"
|
||||
echo "== canvas: $(grep -o 'gpu_backend=[a-z]*' "$snap" | head -1)" \
|
||||
"$(grep -o 'gpu_nonblank=[a-z]*' "$snap" | head -1)"
|
||||
grep -q 'idle' "$snap" || fail "probe did not start idle"
|
||||
|
||||
# ---- 3: Start spawns the stream and lines arrive ---------------------------
|
||||
start_id=$(widget_id "Start stream")
|
||||
[ -n "$start_id" ] || fail "Start stream button not found in snapshot"
|
||||
"$cli" automate widget-click probe-canvas "$start_id" || fail "CLI widget-click Start failed"
|
||||
poll 30 'streaming:' || fail "status never showed streaming (spawn failed under Wine?)"
|
||||
# The Windows stream paces ~1 line/s (cmd for /L + ping); wait for at
|
||||
# least 2 visible lines so cancel provably interrupts an active stream.
|
||||
poll 60 'stream line 2' || fail "stream lines never reached the model"
|
||||
echo "== streaming: $(grep -o 'streaming: [0-9]* lines' "$snap" | head -1), status-bar: $(total_lines) lines total"
|
||||
|
||||
# ---- 4: the PostMessage wake path fired ------------------------------------
|
||||
# handleFrame also drains completions on every frame tick, so lines in the
|
||||
# model alone cannot isolate the wake. The runner's default -Dtrace=events
|
||||
# sink prints every runtime event; effects_wake records prove the worker's
|
||||
# PostMessageW -> kWakeMessage -> kWake -> .effects_wake marshalling ran.
|
||||
grep -q 'event="effects_wake"' "$app_log" || fail "no effects_wake events in the app log (PostMessage wake never fired)"
|
||||
echo "== effects_wake events so far: $(grep -c 'event="effects_wake"' "$app_log")"
|
||||
|
||||
# ---- 5: Cancel terminates the child ----------------------------------------
|
||||
cancel_id=$(widget_id "Cancel")
|
||||
[ -n "$cancel_id" ] || fail "Cancel button not found in snapshot"
|
||||
"$cli" automate widget-click probe-canvas "$cancel_id" || fail "CLI widget-click Cancel failed"
|
||||
poll 30 'cancelled: code' || fail "status never showed cancelled"
|
||||
frozen=$(total_lines)
|
||||
[ -n "$frozen" ] || fail "could not read line count after cancel"
|
||||
echo "== cancelled at $frozen lines: $(grep -o 'cancelled: code [0-9-]* after [0-9]* lines' "$snap" | head -1)"
|
||||
|
||||
# ---- 6: the line count is frozen -------------------------------------------
|
||||
# ~5 more lines would have arrived at the ~1s cadence if the child were
|
||||
# still alive or queued lines were still draining.
|
||||
sleep 6
|
||||
after=$(total_lines)
|
||||
[ "$after" = "$frozen" ] || fail "line count moved after cancel ($frozen -> $after)"
|
||||
grep -q 'streaming:' "$snap" && fail "status went back to streaming after cancel"
|
||||
echo "== count frozen at $after lines across 6s"
|
||||
|
||||
echo "PASS: windows effects smoke"
|
||||
exit 0
|
||||
@@ -34,20 +34,22 @@ jobs:
|
||||
- platform: macosarm64
|
||||
runner: macos-14
|
||||
- platform: macosx64
|
||||
runner: macos-13
|
||||
runner: macos-15
|
||||
- platform: linux64
|
||||
runner: ubuntu-latest
|
||||
- platform: linuxarm64
|
||||
runner: ubuntu-24.04-arm
|
||||
- platform: windows64
|
||||
runner: windows-2022
|
||||
- platform: windowsarm64
|
||||
runner: windows-11-arm
|
||||
# windowsarm64 disabled: Zig 0.16.0 aarch64-windows binary is broken
|
||||
# https://codeberg.org/ziglang/zig/issues/31865
|
||||
# - platform: windowsarm64
|
||||
# runner: windows-11-arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
|
||||
@@ -64,7 +66,13 @@ jobs:
|
||||
curl --fail --location --output "zig-out/cef-download/${archive}" "${base}/${archive}"
|
||||
curl --fail --location --output "zig-out/cef-download/${archive}.sha256" "${base}/${archive}.sha256"
|
||||
expected="$(tr -d '[:space:]' < "zig-out/cef-download/${archive}.sha256")"
|
||||
actual="$(shasum -a 256 "zig-out/cef-download/${archive}" | awk '{print $1}')"
|
||||
if command -v sha256sum &>/dev/null; then
|
||||
actual="$(sha256sum "zig-out/cef-download/${archive}" | awk '{print $1}')"
|
||||
elif command -v shasum &>/dev/null; then
|
||||
actual="$(shasum -a 256 "zig-out/cef-download/${archive}" | awk '{print $1}')"
|
||||
else
|
||||
echo "::error::No SHA-256 hashing utility found"; exit 1
|
||||
fi
|
||||
test "$expected" = "$actual"
|
||||
tar -xjf "zig-out/cef-download/${archive}" -C zig-out/cef-download
|
||||
echo "CEF_ROOT=zig-out/cef-download/${archive%.tar.bz2}" >> "$GITHUB_ENV"
|
||||
@@ -74,7 +82,12 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cmake -S "$CEF_ROOT" -B "$CEF_ROOT/build/libcef_dll_wrapper"
|
||||
cmake_args=(-S "$CEF_ROOT" -B "$CEF_ROOT/build/libcef_dll_wrapper")
|
||||
case "${{ matrix.platform }}" in
|
||||
*arm64) cmake_args+=(-DPROJECT_ARCH=arm64) ;;
|
||||
macosx64) cmake_args+=(-DCMAKE_OSX_ARCHITECTURES=x86_64 -DPROJECT_ARCH=x86_64) ;;
|
||||
esac
|
||||
cmake "${cmake_args[@]}"
|
||||
cmake --build "$CEF_ROOT/build/libcef_dll_wrapper" --target libcef_dll_wrapper --config Release
|
||||
mkdir -p "$CEF_ROOT/libcef_dll_wrapper"
|
||||
case "${{ matrix.platform }}" in
|
||||
@@ -83,13 +96,13 @@ jobs:
|
||||
esac
|
||||
find "$CEF_ROOT/build/libcef_dll_wrapper" -name "$wrapper" -print -quit | xargs -I{} cp "{}" "$CEF_ROOT/libcef_dll_wrapper/$wrapper"
|
||||
|
||||
- name: Prepare zero-native runtime archive
|
||||
- name: Prepare native-sdk runtime archive
|
||||
if: ${{ inputs.source == 'official' }}
|
||||
shell: bash
|
||||
run: |
|
||||
zig build
|
||||
cli="zig-out/bin/zero-native"
|
||||
if [[ "${{ runner.os }}" == "Windows" ]]; then cli="zig-out/bin/zero-native.exe"; fi
|
||||
cli="zig-out/bin/native"
|
||||
if [[ "${{ runner.os }}" == "Windows" ]]; then cli="zig-out/bin/native.exe"; fi
|
||||
"$cli" cef prepare-release --dir "$CEF_ROOT" --output zig-out/cef --version "${{ inputs.cef_version }}"
|
||||
|
||||
- name: Build CEF from source and prepare runtime
|
||||
@@ -103,7 +116,7 @@ jobs:
|
||||
--platform "${{ matrix.platform }}"
|
||||
--version "${{ inputs.cef_version }}"
|
||||
--output zig-out/cef
|
||||
--zero-native-bin zig-out/bin/zero-native
|
||||
--native-sdk-bin zig-out/bin/native
|
||||
)
|
||||
if [ -n "${{ inputs.cef_branch }}" ]; then
|
||||
args+=(--cef-branch "${{ inputs.cef_branch }}")
|
||||
@@ -116,5 +129,5 @@ jobs:
|
||||
tag_name: cef-${{ inputs.cef_version }}
|
||||
name: CEF ${{ inputs.cef_version }}
|
||||
files: |
|
||||
zig-out/cef/zero-native-cef-${{ inputs.cef_version }}-${{ matrix.platform }}.tar.gz
|
||||
zig-out/cef/zero-native-cef-${{ inputs.cef_version }}-${{ matrix.platform }}.tar.gz.sha256
|
||||
zig-out/cef/native-sdk-cef-${{ inputs.cef_version }}-${{ matrix.platform }}.tar.gz
|
||||
zig-out/cef/native-sdk-cef-${{ inputs.cef_version }}-${{ matrix.platform }}.tar.gz.sha256
|
||||
|
||||
@@ -15,53 +15,180 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
# TypeScript cores compile through the external core compiler at
|
||||
# build/test time; the compiler and the frontend's toolchain both
|
||||
# arrive with this one install (without it the ts-core suites skip
|
||||
# silently, so CI must provide it). No SCRIPTC_NO_CACHE and no
|
||||
# cache action on purpose: hosted runners are ephemeral, so any
|
||||
# per-run compiler cache dies with the VM and runs stay hermetic
|
||||
# across commits by machine lifecycle.
|
||||
- run: npm ci --prefix packages/core
|
||||
- name: Service surface tooling and claims
|
||||
run: node --test packages/core/test/surface_tools.test.ts
|
||||
- run: zig build test
|
||||
- run: zig build validate
|
||||
|
||||
core-compiler-fences:
|
||||
name: Core Compiler Fences
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
# The exact-pinned external core compiler and the frontend's
|
||||
# toolchain, one install (packages/core/package.json is the one
|
||||
# place the pin lives).
|
||||
- run: npm ci --prefix packages/core
|
||||
# Per-fixture contract artifacts the fixture driver consumes: the effective sidecar plus its generated entry module and compiler profile, under zig-out/core-contracts.
|
||||
- run: zig build stage-core-contracts
|
||||
# Determinism-fence negative control: the pristine markup fixture compiles and its co-emitted sidecar attests deterministic: true, then one injected ambient read (Date.now() in update) must be refused by the profile's fences — proving the fences fire, not merely that clean cores pass under them. The positive batteries (every fixture's e2e suite over its real archive) ride `zig build test` in the Zig Core job; this job holds the refusal half.
|
||||
- name: Determinism fences fire (negative control)
|
||||
run: tests/compiled-core/fence_check.sh .zig-cache/fence-check
|
||||
|
||||
macos-webview:
|
||||
name: macOS WebView
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
# gpu-components is a TypeScript-core app, so its smoke build needs
|
||||
# the frontend compiler and exact-pinned TypeScript toolchain.
|
||||
- run: npm ci --prefix packages/core
|
||||
# The mobile aggregate runs on Linux for Android. Exercise the other
|
||||
# store-capable cross-target here against the real iPhone simulator SDK.
|
||||
- run: zig build test-example-mobile-canvas-lib-ios-store
|
||||
- run: zig build test-webview-system-link
|
||||
- run: zig build test-webview-smoke
|
||||
# The zero-config TypeScript runner must load app.zon menus before
|
||||
# automation can select their registered command ids.
|
||||
- run: zig build test-menu-bar-smoke
|
||||
# Signed-package seal pin: an ad-hoc signed package must pass
|
||||
# codesign --verify --strict (macOS runners are the only tier with
|
||||
# codesign; the step skips loudly anywhere else).
|
||||
- run: zig build test-package-signing
|
||||
# Shared macos-14 runners are far noisier than a dev box (the second
|
||||
# CI run measured a 576 ms automation-ready against the 500 ms local
|
||||
# ceiling), so widen the smoke budgets here instead of weakening the
|
||||
# local defaults. NATIVE_SDK_SMOKE_BUDGET_MS raises the first-frame latency
|
||||
# budget and the automation-ready ceiling together; every correctness
|
||||
# assertion in the smokes stays strict.
|
||||
- run: zig build test-gpu-dashboard-smoke
|
||||
env:
|
||||
NATIVE_SDK_SMOKE_BUDGET_MS: "1500"
|
||||
- run: zig build test-gpu-components-smoke
|
||||
env:
|
||||
NATIVE_SDK_SMOKE_BUDGET_MS: "1500"
|
||||
|
||||
macos-gpu-perf:
|
||||
name: macOS GPU Perf
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
# Percentile perf check: 5 cold launches asserting p90
|
||||
# first-frame latency, 5 steady-state widget clicks asserting p90 input
|
||||
# latency, then a reset-scoped retained-animation cadence sample.
|
||||
# Its own job so a shared-runner slowdown is visible in isolation and
|
||||
# never blocks the correctness smokes.
|
||||
# Shared macos-14 runners are far noisier than a dev box (first CI run
|
||||
# measured a 581 ms cold-start outlier against the 300 ms default), so
|
||||
# widen the budgets here instead of weakening the local defaults: this
|
||||
# job exists to catch step-function regressions, not runner noise.
|
||||
- run: zig build test-gpu-dashboard-perf
|
||||
env:
|
||||
NATIVE_SDK_PERF_BUDGET_MS: "1500"
|
||||
NATIVE_SDK_PERF_INPUT_BUDGET_MS: "500"
|
||||
# The virtual display is commonly 25-35 Hz even though AppKit
|
||||
# reports the window visible. Keep physical/dev defaults at the
|
||||
# strict 60 Hz-class 45/20/34; this hosted job remains a
|
||||
# step-function regression sentinel rather than a display gate.
|
||||
NATIVE_SDK_PERF_ANIMATION_MIN_FRAMES: "30"
|
||||
NATIVE_SDK_PERF_ANIMATION_P90_MS: "50"
|
||||
NATIVE_SDK_PERF_ANIMATION_MAX_MS: "100"
|
||||
|
||||
linux-webkitgtk:
|
||||
name: Linux WebKitGTK
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- name: Install WebKitGTK dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev
|
||||
- run: zig build test-webview-system-link -Dplatform=linux
|
||||
# Declare-to-use, proven on real Linux executables WITH the
|
||||
# WebKitGTK dev package installed — the seam, not the environment:
|
||||
# the native-only ui-inbox binary must carry no libwebkitgtk
|
||||
# DT_NEEDED entry and no webkit_/jsc_ dynamic symbol even though
|
||||
# the headers were right there, while the webview example must
|
||||
# keep them. (The environment half — building native-only on a
|
||||
# runner with no WebKitGTK dev package at all — is the
|
||||
# linux-canvas-smoke job.)
|
||||
- run: zig build test-linux-web-layer-audit
|
||||
|
||||
windows-webview:
|
||||
name: Windows WebView
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
# Builds the WebView example with the system engine, compiling the
|
||||
# full embedded-WebView host against the vendored WebView2 SDK
|
||||
# header (third_party/webview2). The host refuses to fall back to
|
||||
# the stubbed layer, so a regression — a missing header, a broken
|
||||
# include path, or a conformance error in the embedded layer — is a
|
||||
# compile failure here, not a silent WebViewNotFound at runtime.
|
||||
- run: zig build test-webview-system-link -Dplatform=windows
|
||||
# Effects.spawn is a pipe-backed background transport. Run its
|
||||
# Windows-only PowerShell probe natively so removing CREATE_NO_WINDOW
|
||||
# cannot leave the platform-neutral and Wine lanes green.
|
||||
- run: zig build test-windows-effects-no-console
|
||||
# The registered-font receipt, natively on Windows: runs the
|
||||
# font-registry suite — registration validation, the glyph-budget
|
||||
# gate, present/reference pixel parity, and the Chinese-receipt
|
||||
# test that registers the committed subsetted Noto Sans SC through
|
||||
# the app-fonts seam and proves the rendered string is real glyphs,
|
||||
# not tofu — on real Windows, not under emulation. The font
|
||||
# pipeline is platform-neutral Zig, so this lane pins that neutral
|
||||
# truth on a Windows host (the Linux run lives in the Zig Core
|
||||
# lane's `zig build test`).
|
||||
- run: zig build test-canvas-fonts
|
||||
|
||||
cef-platform-tooling:
|
||||
name: CEF Platform Tooling
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
# The TypeScript core suites compile through the external core
|
||||
# compiler at build/test time; without the install they skip
|
||||
# silently, so CI must provide it.
|
||||
- run: npm ci --prefix packages/core
|
||||
- run: zig build test-tooling
|
||||
|
||||
npm-package:
|
||||
@@ -72,15 +199,246 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
- run: npm --prefix packages/zero-native run version:check
|
||||
- run: npm --prefix packages/zero-native run scripts:check
|
||||
- run: npm --prefix packages/native-sdk run version:check
|
||||
- run: npm --prefix packages/native-sdk run scripts:check
|
||||
|
||||
native-example-shards:
|
||||
name: Native Examples (${{ matrix.name }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: 1/4
|
||||
step: test-examples-native-shard-1
|
||||
- name: 2/4
|
||||
step: test-examples-native-shard-2
|
||||
- name: 3/4
|
||||
step: test-examples-native-shard-3
|
||||
- name: 4/4
|
||||
step: test-examples-native-shard-4
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
# The TypeScript examples compile through the external core
|
||||
# compiler at build time; the compiler and the frontend toolchain
|
||||
# arrive with this install.
|
||||
- run: npm ci --prefix packages/core
|
||||
# Every example test uses the null backend, so this lane needs no
|
||||
# GTK/WebKitGTK packages. The root build owns the round-robin shard
|
||||
# membership, keeping CI and the complete local group in one registry.
|
||||
- run: zig build ${{ matrix.step }}
|
||||
|
||||
windows-web-layer-audit:
|
||||
name: Windows Web Layer Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
# Declare-to-use, proven on real Windows executables: the
|
||||
# canvas-only ui-inbox cross-compiles without the embedded WebView
|
||||
# layer (no WebView2Loader.dll reference, no loader installed) and
|
||||
# the webview example keeps it.
|
||||
- run: zig build test-windows-web-layer-audit
|
||||
|
||||
# Preserve the established `CI / Native Examples` required-check name
|
||||
# while making it an aggregate receipt for every shard and the audit.
|
||||
native-examples:
|
||||
name: Native Examples
|
||||
if: ${{ always() }}
|
||||
needs:
|
||||
- native-example-shards
|
||||
- windows-web-layer-audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Confirm every native example lane passed
|
||||
env:
|
||||
EXAMPLE_SHARDS_RESULT: ${{ needs.native-example-shards.result }}
|
||||
WINDOWS_AUDIT_RESULT: ${{ needs.windows-web-layer-audit.result }}
|
||||
run: |
|
||||
test "$EXAMPLE_SHARDS_RESULT" = success
|
||||
test "$WINDOWS_AUDIT_RESULT" = success
|
||||
|
||||
linux-canvas-smoke:
|
||||
name: Linux Canvas Smoke
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
# Deliberately NO libwebkitgtk-6.0-dev: ui-inbox declares no web
|
||||
# use, so its host compiles with the WebKitGTK stub seam and this
|
||||
# job doubles as the native-only Linux LINK test — the build can
|
||||
# only succeed if nothing in a native-only app needs the WebKitGTK
|
||||
# headers or pkg-config entry. (The seam under webkit-PRESENT
|
||||
# conditions is the linux-webkitgtk job's ELF cross-audit.)
|
||||
# x11-utils provides xwininfo, which the smoke script uses to find
|
||||
# the context-menu popover's override-redirect X window (and to
|
||||
# dump the X window list in failure diagnostics).
|
||||
- name: Install GTK, Xvfb, xdotool, and x11-utils
|
||||
run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev xvfb xdotool x11-utils
|
||||
# The stub is the expected, configured state of every native-only
|
||||
# Linux app, so compiling the GTK host with the stub define must be
|
||||
# diagnostic-FREE, not merely successful: zig renders every clang
|
||||
# diagnostic of a failing translation unit as `error:` (serialized
|
||||
# clang diagnostics carry no severity into its error bundle), so
|
||||
# even an informational #pragma message in this path masquerades as
|
||||
# the build-killing error the moment any unrelated real error
|
||||
# appears in the file. `zig cc` runs clang in passthrough mode,
|
||||
# where warnings actually reach stderr — `zig build` only surfaces
|
||||
# C diagnostics on failure, which is exactly the escalation this
|
||||
# step pins against. The throwaway cache dir keeps the compile
|
||||
# cold: on a cache hit zig replays nothing, stderr included, so a
|
||||
# warm compiler cache would hide the diagnostics
|
||||
# this step exists to catch.
|
||||
- name: WebKitGTK stub compile is diagnostic-free
|
||||
run: |
|
||||
export ZIG_GLOBAL_CACHE_DIR=$(mktemp -d) ZIG_LOCAL_CACHE_DIR=$(mktemp -d)
|
||||
status=0
|
||||
out=$(zig cc -c src/platform/linux/gtk_host.c -DNATIVE_SDK_ALLOW_WEBKITGTK_STUB $(pkg-config --cflags gtk4) -o /tmp/gtk_host_stub.o 2>&1) || status=$?
|
||||
if [ "$status" -ne 0 ] || [ -n "$out" ]; then
|
||||
echo "the WebKitGTK stub compile must succeed with zero diagnostics (exit $status):"
|
||||
echo "$out"
|
||||
exit 1
|
||||
fi
|
||||
# Drives the gpu_surface software path under Xvfb: snapshot ready,
|
||||
# gpu_backend=software, gpu_nonblank=true, automation widget-click,
|
||||
# a real xdotool right-click driving a task row's native context
|
||||
# menu end to end, a rendered screenshot, an ELF audit that the
|
||||
# built binary carries no WebKitGTK reference, and ZERO WebKit
|
||||
# helper processes (canvas apps never boot WebKit). A11y env, the widened cold-start
|
||||
# readiness budget (shared runners stall ~27 s before the first
|
||||
# runtime event), and failure forensics (dump snapshot, app log,
|
||||
# and X window list) all live in the script.
|
||||
- name: Build and drive ui-inbox headless
|
||||
run: .github/scripts/linux-canvas-smoke.sh
|
||||
# Durable receipt for the GLib 2.72 fallback (the pre-2.74
|
||||
# G_APPLICATION_DEFAULT_FLAGS shim in gtk_host.c): no stock image
|
||||
# pairs an old glib with GTK >= 4.10 (only backport distros do),
|
||||
# and ubuntu 22.04 ships GTK 4.6 — so a clean compile is
|
||||
# impossible here by design. This pins the error SET instead:
|
||||
# every diagnostic must match a GTK-age shape (the allowlist
|
||||
# script holds the shapes, why cascades are allowed, and the
|
||||
# positive-evidence floor) — a 2.74+ glib symbol, type, or
|
||||
# member used without a version-checked fallback surfaces as a
|
||||
# rejected diagnostic and fails this step, and so does a compile
|
||||
# that produced no classifiable error set at all. If this compile ever succeeds outright, the premise
|
||||
# changed (newer GTK in the image) and the receipt must be
|
||||
# re-verified rather than trusted.
|
||||
- name: GTK host GLib 2.72 fallback holds (error-set receipt)
|
||||
run: |
|
||||
docker run --rm -v "$PWD:/src" -v "$(dirname "$(which zig)"):/zig" ubuntu:22.04 bash -ec '
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -q >/dev/null && apt-get install -y -q libgtk-4-dev pkg-config >/dev/null
|
||||
export ZIG_GLOBAL_CACHE_DIR=$(mktemp -d) ZIG_LOCAL_CACHE_DIR=$(mktemp -d)
|
||||
status=0
|
||||
out=$(/zig/zig cc -c /src/src/platform/linux/gtk_host.c -DNATIVE_SDK_ALLOW_WEBKITGTK_STUB $(pkg-config --cflags gtk4) -ferror-limit=0 -o /tmp/gtk_host_2272.o 2>&1) || status=$?
|
||||
if [ "$status" -eq 0 ]; then
|
||||
echo "unexpected clean compile on ubuntu 22.04 - this receipt assumes GTK-age errors; re-verify what it proves now"
|
||||
exit 1
|
||||
fi
|
||||
echo "$out" | python3 /src/.github/scripts/glib272_error_allowlist.py
|
||||
'
|
||||
|
||||
linux-dev-smoke:
|
||||
name: Linux Dev Smoke (Debug scaffold)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
# The scaffold default is the TypeScript core; its frontend and
|
||||
# compiler run at build time from this checkout's packages/core.
|
||||
- run: npm ci --prefix packages/core
|
||||
# No WebKitGTK dev package, same as linux-canvas-smoke: the scaffold
|
||||
# declares no web use, so its host compiles with the stub seam.
|
||||
- name: Install GTK and Xvfb
|
||||
run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev xvfb
|
||||
# The Debug half of the Linux runtime story: linux-canvas-smoke runs
|
||||
# at the graph's release default, and Release always uses the LLVM
|
||||
# backend — a Debug-only x86_64 codegen fault (self-hosted backend,
|
||||
# the mode every fresh `native init` + `native dev` builds) stays
|
||||
# invisible to it. This job scaffolds the default template with the
|
||||
# CLI, builds it -Doptimize=Debug with automation, runs it under
|
||||
# Xvfb, and requires the startup scene to come up (ready=true,
|
||||
# gpu_nonblank=true, template widgets in the snapshot).
|
||||
- name: Scaffold and drive the Debug template headless
|
||||
run: .github/scripts/linux-dev-smoke.sh
|
||||
|
||||
windows-canvas-smoke:
|
||||
name: Windows Canvas Smoke (Wine)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- name: Install Wine, Xvfb, and xdotool
|
||||
run: sudo apt-get update && sudo apt-get install -y wine xvfb xdotool
|
||||
# The Windows twin of the linux-canvas-smoke stub receipt: the
|
||||
# WebView2 stub is the expected, configured state of every
|
||||
# native-only Windows app, so cross-compiling the host with the
|
||||
# stub define must be diagnostic-free (see that job's step comment
|
||||
# for why even an informational #pragma message is dangerous, and
|
||||
# why the compile must run against a cold cache).
|
||||
- name: WebView2 stub cross-compile is diagnostic-free
|
||||
run: |
|
||||
export ZIG_GLOBAL_CACHE_DIR=$(mktemp -d) ZIG_LOCAL_CACHE_DIR=$(mktemp -d)
|
||||
status=0
|
||||
out=$(zig c++ -target x86_64-windows-gnu -std=c++17 -DNATIVE_SDK_ALLOW_WEBVIEW2_STUB -c src/platform/windows/webview2_host.cpp -o /tmp/webview2_host_stub.o 2>&1) || status=$?
|
||||
if [ "$status" -ne 0 ] || [ -n "$out" ]; then
|
||||
echo "the WebView2 stub cross-compile must succeed with zero diagnostics (exit $status):"
|
||||
echo "$out"
|
||||
exit 1
|
||||
fi
|
||||
# Cross-compiles ui-inbox for x86_64-windows-gnu and drives the
|
||||
# retained Direct2D packet path (child HWND + WM_TIMER) under Wine:
|
||||
# snapshot ready, gpu_backend=direct2d, gpu_nonblank=true,
|
||||
# automation widget-click, and real XTEST pointer/keyboard input.
|
||||
# Wineprefix init happens inline in the script (measured 21s from
|
||||
# scratch in an ubuntu-24.04 container, so no cache step).
|
||||
- name: Build and drive ui-inbox.exe under Wine
|
||||
run: .github/scripts/windows-canvas-smoke.sh
|
||||
|
||||
windows-effects-smoke:
|
||||
name: Windows Effects Smoke (Wine)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- name: Install Wine and Xvfb
|
||||
run: sudo apt-get update && sudo apt-get install -y wine xvfb
|
||||
# Cross-compiles examples/effects-probe for x86_64-windows-gnu and
|
||||
# proves the effect system's live Windows path under Wine: fx.spawn
|
||||
# launches cmd.exe, streamed lines land in the model, the worker's
|
||||
# PostMessageW wake shows up as effects_wake events in the trace
|
||||
# log (frame ticks also drain, so the log is the wake's evidence),
|
||||
# and fx.cancel terminates the child with the line count frozen.
|
||||
- name: Build and drive effects-probe.exe under Wine
|
||||
run: .github/scripts/windows-effects-smoke.sh
|
||||
|
||||
frontend-examples:
|
||||
name: Frontend Examples
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- run: zig build test-examples-frontends
|
||||
@@ -90,7 +448,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- run: zig build test-examples-mobile
|
||||
@@ -100,20 +458,69 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
# The default scaffold is a TypeScript core: its build runs the
|
||||
# @native-sdk/core frontend and the external core compiler from
|
||||
# this checkout's own install.
|
||||
- run: npm ci --prefix packages/core
|
||||
- run: zig build
|
||||
- name: Scaffold and test the zero-config native app
|
||||
run: |
|
||||
set -euo pipefail
|
||||
app=".zig-cache/scaffold-native-slim"
|
||||
rm -rf "$app"
|
||||
./zig-out/bin/native init "$app"
|
||||
# Slim scaffold: no build files — the CLI's generated graph drives it.
|
||||
test ! -f "$app/build.zig"
|
||||
test ! -f "$app/build.zig.zon"
|
||||
# The editor surface landed: package.json + tsconfig.json + the
|
||||
# materialized @native-sdk/core copy stock tsc resolves.
|
||||
test -f "$app/package.json"
|
||||
test -f "$app/tsconfig.json"
|
||||
test -f "$app/node_modules/@native-sdk/core/sdk/core.ts"
|
||||
# ...and none of it is build truth: every verb works without it.
|
||||
rm -rf "$app/node_modules"
|
||||
./zig-out/bin/native test "$app" -Dplatform=null
|
||||
./zig-out/bin/native check "$app"
|
||||
# check self-healed the editor copy back into place.
|
||||
test -f "$app/node_modules/@native-sdk/core/sdk/core.ts"
|
||||
./zig-out/bin/native eject "$app"
|
||||
(cd "$app" && zig build test -Dplatform=null)
|
||||
- name: Scaffold and test frontend templates
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for frontend in next vite react svelte vue; do
|
||||
for frontend in native next vite react svelte vue; do
|
||||
app=".zig-cache/scaffold-${frontend}"
|
||||
rm -rf "$app"
|
||||
./zig-out/bin/zero-native init "$app" --frontend "$frontend"
|
||||
(cd "$app" && zig build test -Dplatform=null && ../../zig-out/bin/zero-native validate app.zon)
|
||||
./zig-out/bin/native init "$app" --frontend "$frontend" --full
|
||||
(cd "$app" && zig build test -Dplatform=null && ../../zig-out/bin/native validate app.zon)
|
||||
# Every scaffold ships a CI workflow; parse it as real YAML.
|
||||
test -s "$app/.github/workflows/ci.yml"
|
||||
python3 -c 'import sys, yaml; yaml.safe_load(open(sys.argv[1]))' "$app/.github/workflows/ci.yml"
|
||||
done
|
||||
|
||||
evals-typecheck:
|
||||
name: Evals Typecheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.23.0
|
||||
package_json_file: evals/package.json
|
||||
- run: pnpm install --frozen-lockfile
|
||||
working-directory: evals
|
||||
- run: pnpm typecheck
|
||||
working-directory: evals
|
||||
|
||||
docs:
|
||||
name: Docs
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -32,10 +32,10 @@ jobs:
|
||||
- name: Compare package.json version to npm and check GitHub release
|
||||
id: check
|
||||
run: |
|
||||
LOCAL_VERSION=$(node -p "require('./packages/zero-native/package.json').version")
|
||||
LOCAL_VERSION=$(node -p "require('./packages/native-sdk/package.json').version")
|
||||
echo "Local version: $LOCAL_VERSION"
|
||||
|
||||
NPM_VERSION=$(npm view zero-native version 2>/dev/null || echo "0.0.0")
|
||||
NPM_VERSION=$(npm view @native-sdk/cli version 2>/dev/null || echo "0.0.0")
|
||||
echo "npm version: $NPM_VERSION"
|
||||
|
||||
if [ "$LOCAL_VERSION" != "$NPM_VERSION" ]; then
|
||||
@@ -51,14 +51,14 @@ jobs:
|
||||
missing=0
|
||||
for asset in \
|
||||
CHECKSUMS.txt \
|
||||
zero-native-darwin-arm64 \
|
||||
zero-native-darwin-x64 \
|
||||
zero-native-linux-arm64 \
|
||||
zero-native-linux-x64 \
|
||||
zero-native-linux-musl-arm64 \
|
||||
zero-native-linux-musl-x64 \
|
||||
zero-native-win32-arm64.exe \
|
||||
zero-native-win32-x64.exe
|
||||
native-sdk-darwin-arm64 \
|
||||
native-sdk-darwin-x64 \
|
||||
native-sdk-linux-arm64 \
|
||||
native-sdk-linux-x64 \
|
||||
native-sdk-linux-musl-arm64 \
|
||||
native-sdk-linux-musl-x64 \
|
||||
native-sdk-win32-arm64.exe \
|
||||
native-sdk-win32-x64.exe
|
||||
do
|
||||
if ! printf '%s\n' "$EXISTING_ASSETS" | grep -Fx "$asset" >/dev/null; then
|
||||
echo "Missing release asset: $asset"
|
||||
@@ -92,14 +92,14 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Zig
|
||||
uses: mlugg/setup-zig@v2
|
||||
uses: vercel-labs/setup-zig@v1
|
||||
with:
|
||||
version: 0.16.0
|
||||
|
||||
- name: Extract changelog entry
|
||||
run: |
|
||||
VERSION="${{ needs.check-release.outputs.version }}"
|
||||
awk '/<!-- release:start -->/{found=1; next} /<!-- release:end -->/{found=0} found{print}' CHANGELOG.md > /tmp/release-notes.md
|
||||
awk '/<!-- release:start -->/{found=1; next} /<!-- release:end -->/{exit} found{print}' CHANGELOG.md > /tmp/release-notes.md
|
||||
|
||||
LINES=$(wc -l < /tmp/release-notes.md | tr -d ' ')
|
||||
if [ "$LINES" -lt 2 ]; then
|
||||
@@ -108,35 +108,11 @@ jobs:
|
||||
fi
|
||||
echo "Extracted release notes for $VERSION ($LINES lines)"
|
||||
|
||||
- name: Build native release asset
|
||||
- name: Build native release assets (all platforms)
|
||||
run: |
|
||||
mkdir -p /tmp/zero-native-release
|
||||
|
||||
build_asset() {
|
||||
target="$1"
|
||||
name="$2"
|
||||
rm -rf zig-out
|
||||
zig build -Dtarget="$target" -Doptimize=ReleaseSmall
|
||||
|
||||
src="zig-out/bin/zero-native"
|
||||
case "$name" in
|
||||
*.exe) src="${src}.exe" ;;
|
||||
esac
|
||||
|
||||
cp "$src" "/tmp/zero-native-release/$name"
|
||||
chmod 755 "/tmp/zero-native-release/$name"
|
||||
}
|
||||
|
||||
build_asset aarch64-macos zero-native-darwin-arm64
|
||||
build_asset x86_64-macos zero-native-darwin-x64
|
||||
build_asset aarch64-linux-gnu zero-native-linux-arm64
|
||||
build_asset x86_64-linux-gnu zero-native-linux-x64
|
||||
build_asset aarch64-linux-musl zero-native-linux-musl-arm64
|
||||
build_asset x86_64-linux-musl zero-native-linux-musl-x64
|
||||
build_asset aarch64-windows zero-native-win32-arm64.exe
|
||||
build_asset x86_64-windows zero-native-win32-x64.exe
|
||||
|
||||
(cd /tmp/zero-native-release && shasum -a 256 zero-native-* > CHECKSUMS.txt)
|
||||
# Cross-compiles the CLI for all eight platforms and writes the
|
||||
# flat release assets + CHECKSUMS.txt into zig-out/release/.
|
||||
bash packages/native-sdk/scripts/build-binaries.sh
|
||||
|
||||
- name: Create GitHub Release
|
||||
run: |
|
||||
@@ -153,8 +129,8 @@ jobs:
|
||||
fi
|
||||
|
||||
gh release upload "$TAG" \
|
||||
/tmp/zero-native-release/zero-native-* \
|
||||
/tmp/zero-native-release/CHECKSUMS.txt \
|
||||
zig-out/release/native-sdk-* \
|
||||
zig-out/release/CHECKSUMS.txt \
|
||||
--clobber
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -167,7 +143,7 @@ jobs:
|
||||
&& needs.check-release.outputs.should_release == 'true'
|
||||
&& (needs.github-release.result == 'success' || needs.github-release.result == 'skipped')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
environment: Release
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -182,17 +158,98 @@ jobs:
|
||||
node-version: "24"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
# Publishing uses npm trusted publishing (OIDC): the job's id-token
|
||||
# permission lets npm mint short-lived credentials, so no npm token
|
||||
# secret exists anywhere in this repo. All nine packages —
|
||||
# @native-sdk/cli plus the eight @native-sdk/cli-* platform packages
|
||||
# under packages/native-sdk/npm/ — must each be configured on
|
||||
# npmjs.com with a GitHub Actions trusted publisher pointing at
|
||||
# repository vercel-labs/native, workflow release.yml,
|
||||
# environment Release. If a package is missing that configuration,
|
||||
# npm publish fails loudly with an OIDC authentication error before
|
||||
# anything is uploaded for that package. Trusted publishing requires
|
||||
# npm >= 11.5.1, satisfied by the npm bundled with Node 24.
|
||||
# @native-sdk/core (packages/core) joins that publish set the moment
|
||||
# its "private" flag drops (see the publish step below) — its
|
||||
# npmjs.com trusted-publisher configuration must exist BEFORE that
|
||||
# flip lands.
|
||||
|
||||
- name: Check version sync
|
||||
run: npm --prefix packages/zero-native run version:check
|
||||
run: npm --prefix packages/native-sdk run version:check
|
||||
|
||||
- name: Check package scripts
|
||||
run: npm --prefix packages/zero-native run scripts:check
|
||||
run: npm --prefix packages/native-sdk run scripts:check
|
||||
|
||||
- name: Download release binaries
|
||||
run: |
|
||||
VERSION="${{ needs.check-release.outputs.version }}"
|
||||
mkdir -p /tmp/native-sdk-release
|
||||
gh release download "v$VERSION" \
|
||||
--pattern 'native-sdk-*' \
|
||||
--pattern 'CHECKSUMS.txt' \
|
||||
--dir /tmp/native-sdk-release
|
||||
(cd /tmp/native-sdk-release && sha256sum -c CHECKSUMS.txt)
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Stage binaries into the platform packages
|
||||
run: |
|
||||
# Release-asset name -> npm platform package (same table as
|
||||
# packages/native-sdk/scripts/build-binaries.sh).
|
||||
stage() {
|
||||
asset="$1"; key="$2"; ext="$3"
|
||||
mkdir -p "packages/native-sdk/npm/$key/bin"
|
||||
cp "/tmp/native-sdk-release/$asset" "packages/native-sdk/npm/$key/bin/native$ext"
|
||||
chmod 755 "packages/native-sdk/npm/$key/bin/native$ext"
|
||||
}
|
||||
stage native-sdk-darwin-arm64 darwin-arm64 ""
|
||||
stage native-sdk-darwin-x64 darwin-x64 ""
|
||||
stage native-sdk-linux-arm64 linux-arm64-gnu ""
|
||||
stage native-sdk-linux-x64 linux-x64-gnu ""
|
||||
stage native-sdk-linux-musl-arm64 linux-arm64-musl ""
|
||||
stage native-sdk-linux-musl-x64 linux-x64-musl ""
|
||||
stage native-sdk-win32-arm64.exe win32-arm64 ".exe"
|
||||
stage native-sdk-win32-x64.exe win32-x64 ".exe"
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ github.event.repository.visibility }}" = "public" ]; then
|
||||
npm publish --provenance --access public
|
||||
VERSION="${{ needs.check-release.outputs.version }}"
|
||||
|
||||
# Platform packages first, so the main package's
|
||||
# optionalDependencies pins are resolvable the moment it lands.
|
||||
# Re-runs skip anything already on the registry at this version.
|
||||
publish_dir() {
|
||||
dir="$1"
|
||||
name=$(node -p "require('./$dir/package.json').name")
|
||||
if npm view "$name@$VERSION" version >/dev/null 2>&1; then
|
||||
echo "$name@$VERSION already published, skipping"
|
||||
return 0
|
||||
fi
|
||||
(cd "$dir" && npm publish --provenance --access public)
|
||||
}
|
||||
|
||||
for dir in packages/native-sdk/npm/*/; do
|
||||
publish_dir "${dir%/}"
|
||||
done
|
||||
|
||||
# @native-sdk/core (packages/core) rides the same version and
|
||||
# release gate as the CLI (sync-version.js stamps it; the
|
||||
# version check above refuses a half-bumped tree).
|
||||
#
|
||||
# ============================ LOUD FLIP REQUIREMENT ==========
|
||||
# packages/core/package.json keeps "private": true until the
|
||||
# 0.5.0 cut BY DESIGN, and that field IS the publish switch:
|
||||
# while private, the guard below skips the package (npm would
|
||||
# refuse to publish it anyway); the 0.5.0 change that drops
|
||||
# "private" makes this same step start publishing it with no
|
||||
# workflow edit. Before dropping the flag, configure the
|
||||
# @native-sdk/core trusted publisher on npmjs.com (see the note
|
||||
# above) or the first public release fails here.
|
||||
# =============================================================
|
||||
if [ "$(node -p "require('./packages/core/package.json').private === true")" = "true" ]; then
|
||||
echo "@native-sdk/core is private (pre-0.5.0): skipping publish by design"
|
||||
else
|
||||
npm publish --access public
|
||||
publish_dir packages/core
|
||||
fi
|
||||
working-directory: packages/zero-native
|
||||
|
||||
publish_dir packages/native-sdk
|
||||
|
||||
@@ -1,10 +1,32 @@
|
||||
.DS_Store
|
||||
.zig-cache/
|
||||
zig-out/
|
||||
# Project-local package store for examples with third-party Zig
|
||||
# dependencies (zig fetches into it beside the build).
|
||||
zig-pkg/
|
||||
# CLI-generated build graph for zero-config apps (the examples here) — the
|
||||
# same entry `native init` writes into a new app's .gitignore.
|
||||
.native/
|
||||
|
||||
# Native binaries in zero-native npm package (built by CI or locally)
|
||||
packages/zero-native/bin/zero-native-*
|
||||
packages/zero-native/src/
|
||||
# Native binaries in the @native-sdk/cli npm packages (built by CI or locally)
|
||||
packages/native-sdk/bin/native-sdk-*
|
||||
packages/native-sdk/npm/*/bin/
|
||||
# SDK payload mirrored into the npm package at pack time (copy-framework.js)
|
||||
packages/native-sdk/src/
|
||||
packages/native-sdk/assets/
|
||||
packages/native-sdk/skills/
|
||||
packages/native-sdk/skill-data/
|
||||
packages/native-sdk/build/
|
||||
packages/native-sdk/build.zig
|
||||
packages/native-sdk/build.zig.zon
|
||||
packages/native-sdk/app.zon
|
||||
packages/native-sdk/third_party/
|
||||
packages/native-sdk/packages/
|
||||
packages/native-sdk/tools/
|
||||
packages/native-sdk/LICENSE
|
||||
# npm pack output
|
||||
packages/native-sdk/*.tgz
|
||||
packages/native-sdk/npm/*/*.tgz
|
||||
|
||||
# Downloaded/prepared CEF runtimes are large local artifacts.
|
||||
third_party/cef/macos/
|
||||
@@ -16,3 +38,14 @@ third_party/cef/linux/
|
||||
|
||||
# TypeScript build info (generated)
|
||||
docs/tsconfig.tsbuildinfo
|
||||
.claude/
|
||||
|
||||
# Dev-tool dependency trees (installed per package, never committed)
|
||||
packages/core/node_modules/
|
||||
.pnpm-store/
|
||||
|
||||
# The CLI-materialized editor copy of @native-sdk/core inside the TS example
|
||||
# apps (node_modules is editor surface, never source — the same entry the
|
||||
# TS scaffold writes into a new app's .gitignore)
|
||||
examples/soundboard-ts/node_modules/
|
||||
examples/system-monitor-ts/node_modules/
|
||||
|
||||
@@ -1,16 +1,42 @@
|
||||
# Agent Rules
|
||||
# Agent Guide
|
||||
|
||||
## Releasing
|
||||
Guidance for agents (and humans) working on this repository.
|
||||
|
||||
Releases are manual, single-PR affairs. The maintainer controls the changelog voice and format.
|
||||
## App authoring default
|
||||
|
||||
To prepare a release:
|
||||
Native SDK itself is implemented in Zig, but Native SDK **apps are authored in TypeScript + Native markup by default**. Do not infer the app-authoring language from this repository's implementation language or from older Zig-core examples.
|
||||
|
||||
1. Create a branch (e.g. `prepare-v1.2.0`)
|
||||
2. Bump the version in `packages/zero-native/package.json`
|
||||
3. Run `npm --prefix packages/zero-native run version:sync` to update all version references
|
||||
4. Write the changelog entry in `CHANGELOG.md`, wrapped in `<!-- release:start -->` and `<!-- release:end -->` markers
|
||||
5. Remove the `<!-- release:start -->` and `<!-- release:end -->` markers from the previous release entry; only the latest release should have markers
|
||||
6. Open a PR and merge to `main`
|
||||
- For a new app, use `native init <path>` and expect `src/core.ts`, `src/app.native`, and `app.zon`. Ordinary compiled TypeScript work that needs filesystem, process, JSON, regex, classes, or other static-tier APIs belongs under optional `src/services/`, reached from the core with `Cmd.request`; do not import a service from the core. Do not add Zig app code unless the user explicitly chooses `--template zig-core` or the feature requires a toolkit extension.
|
||||
- Before changing an existing app, inspect its tree. A `src/core.ts` app stays TypeScript; a `src/main.zig` app stays Zig unless the task is specifically a migration.
|
||||
- For default app work, read `skill-data/native-ui/SKILL.md` and `skill-data/ts-core/SKILL.md`; also read `skill-data/ts-services/SKILL.md` when the tree has `src/services/` or the task needs ordinary TypeScript beyond the core subset. `skill-data/core/SKILL.md` covers shared/runtime concerns; `skill-data/zig/SKILL.md` is for Zig-core apps and SDK implementation work.
|
||||
- The `-ts` suffix on a few examples only distinguishes ports from older Zig originals. New TypeScript apps need no suffix because TypeScript is the default.
|
||||
|
||||
CI compares the version in `packages/zero-native/package.json` to what's on npm. If it differs, it publishes the CLI package and creates the GitHub release automatically. If npm already has the version but the GitHub release is missing, CI creates the GitHub release from the marked changelog entry.
|
||||
## Build, test, and gate
|
||||
|
||||
```bash
|
||||
zig build test # root engine + runtime suites
|
||||
zig build validate # sample app.zon manifest check
|
||||
zig build test-example-<name> # one example's suite (e.g. test-example-notes)
|
||||
scripts/gate.sh fast [ref] # affected-only local gate for your diff (default base: main)
|
||||
scripts/gate.sh full # everything CI-shaped that runs locally
|
||||
```
|
||||
|
||||
Run `scripts/gate.sh fast` before finishing any change; it maps your diff to the suites that cover it. The docs site checks with `pnpm --dir docs check` (the gate runs it only when `docs/` changed).
|
||||
|
||||
This repository builds with Zig 0.16.0. If a build fails with "no member named" errors on std APIs (`std.fs.cwd`, `ArrayList.init`, `std.io`), you are writing pre-0.16 idioms — `skill-data/zig/SKILL.md` maps each such compile error to the current idiom as this codebase writes it.
|
||||
|
||||
Pinned goldens (pixel signatures, schema fingerprints, command counts) are updated deliberately: review the rendered output or the counted commands first, and keep the pin's comment a self-contained description of what the value represents.
|
||||
|
||||
## Changelog
|
||||
|
||||
Do not edit `CHANGELOG.md` as part of regular feature or fix work. The release agent reviews the git history since the previous release and writes the complete changelog entry during release preparation; see [RELEASING.md](./RELEASING.md).
|
||||
|
||||
## Where things live
|
||||
|
||||
- `src/` — the engine and runtime; `src/primitives/canvas/` holds the widget, markup, and vector core.
|
||||
- `examples/` — the showcase apps, most zero-config (`app.zon` + `src/`).
|
||||
- `docs/` — the documentation site; `docs/AGENTS.md` has its MDX conventions.
|
||||
- `skills/` and `skill-data/` — the agent skills the CLI ships (`native skills list`).
|
||||
- `tools/` and `scripts/` — dev tooling and the local gate.
|
||||
|
||||
Releases are maintainer-run; see [RELEASING.md](./RELEASING.md).
|
||||
|
||||
@@ -1,24 +1,847 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to zero-native will be documented in this file.
|
||||
All notable changes to the Native SDK (formerly zero-native) will be documented in this file.
|
||||
|
||||
## 0.1.9
|
||||
## 0.9.3
|
||||
|
||||
<!-- release:start -->
|
||||
|
||||
### New Features
|
||||
|
||||
- **Model-driven TypeScript theme state**: Zero-config TypeScript apps can now derive the built-in pack, color scheme, and accent from committed model state while preserving manifest fallback, live system accessibility settings, deterministic replay, and the existing `themePack` helper (#378).
|
||||
- **Platform-correct line deletion**: Command+Backspace on macOS now deletes to the start of a field or logical textarea line across every editable canvas control, with matching TypeScript text helpers, controlled-state behavior, undo, and replay (#377).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Precise macOS file-drop routing**: AppKit drops now retain labeled canvas and WebView targets with top-left, view-local coordinates, while unlabeled window regions fall back to content coordinates (#374).
|
||||
- **Manifest menus in generated runners**: Zero-config TypeScript and Zig-core apps now load `app.zon` commands, shortcuts, and menus consistently in live and replay runners, including ejected-runner fallbacks (#376).
|
||||
- **Large TypeScript message unions compile reliably**: Generated shims now derive comptime scan quotas from message shape and identifier size, allowing wide unions to compile across persistence, channels, environment routing, and the full external-core pipeline (#375).
|
||||
- **Correct combobox Enter precedence**: A bound `on-submit` now handles Enter before trigger activation, so query submission no longer opens the picker or dispatches the wrong command (#373).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @MohakBajaj
|
||||
|
||||
<!-- release:end -->
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### New Features
|
||||
|
||||
- **Flash-free accessory startup**: Apps can opt into accessory activation from `app.zon` to launch without a Dock icon or foreground flash, with tray-affordance validation, runtime composition, packaging support, and an updated menu-bar example (#358).
|
||||
- **Logical canvas radio groups**: Nested radios now form accessible single-selection groups with roving focus and consistent keyboard, pointer, handler, and naming semantics (#361).
|
||||
- **Budget-aware photo decoding**: Dynamic encoded images are downsampled across desktop and mobile codecs to fit a configurable registered-pixel budget, with independent source bounds, deterministic replay, and platform-level regression coverage (#366).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Correct anchored surfaces**: Floating and modal surfaces now dismiss without requiring focus, relayout after scroll restoration, resolve against the correct root, and behave consistently across window contexts (#363).
|
||||
- **Reliable autofocus and caret reveal**: Keyboard focus, autofocus, and automation now transactionally reveal offscreen targets while preserving collapsed end-caret selections in text editors (#364).
|
||||
- **Explicit link decoration**: Linked text spans now honor their underline flag while Markdown-generated links retain conventional underlines (#368).
|
||||
- **Stable macOS window geometry**: Fresh windows now distinguish restored, explicit, and default placement, while AppKit and CEF frame events consistently report content geometry without titlebar drift (#369, #370).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Consistent canvas controls and surfaces**: Checkbox and radio labels can contain markup consistently, while actionable states, disabled colors, variant accents, selection geometry, compact layouts, and zero-width strokes now render uniformly across the schema, runtime, accessibility tree, and documentation (#367).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @sepehr-safari
|
||||
|
||||
## 0.9.1
|
||||
|
||||
### New Features
|
||||
|
||||
- **Multi-item macOS menu bars**: Apps can now manage independent, keyed status items with model-driven updates, events, automation, journaling, and regression coverage (#343).
|
||||
- **Complete TypeScript file effects**: Secure, permission-gated effects now support bounded streaming reads, atomic writes, stat, append, and deletion while preserving deterministic record and replay behavior (#339, #350).
|
||||
- **Actionable desktop notifications**: Notification replacement identifiers and actions dispatch through the ordinary command path on macOS, Windows, and Linux (#347).
|
||||
- **Secondary-window lifecycle control**: Window descriptors can declare quit or hide-on-close behavior, preserve hidden-window identity when reopened, and expose the same model-driven window contract to TypeScript apps (#349, #351).
|
||||
- **Mobile TypeScript cores and services**: TypeScript apps with services now compile into iOS and Android library archives, with mobile packaging and device-level runtime coverage (#346).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Safe Linux alert dialogs**: GTK alert dialogs now initialize with a valid empty format string, avoiding a crash from a null constructor argument (#354).
|
||||
- **Correct compiled-core tuple returns**: The SDK now pins the scriptc tuple-normalization fix and verifies bare-model and effect-tuple ABI returns with a compiled-core regression (#356).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Stronger TypeScript core guidance and diagnostics**: Subset rules now distinguish permanent guarantees from deliberately deferred capabilities and point authors to the appropriate service alternative (#345).
|
||||
- **End-to-end services showcase**: The Feed Reader example now demonstrates the full TypeScript service workflow with typed feed parsing, shared data, fixtures, and replay coverage (#352).
|
||||
- **Updated compiler integration**: scriptc advances through 0.0.31 with refreshed generated contracts, compatibility fixtures, and compiler-surface references (#344, #356).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @ElSebas41
|
||||
- @johnlindquist
|
||||
|
||||
## 0.9.0
|
||||
|
||||
### New Features
|
||||
|
||||
- **Ordinary TypeScript services behind a typed boundary**: Apps can place filesystem, process, JSON, regex, class, and exact-vendored npm work under `src/services/`; Native SDK generates the checked client and codecs, compiles a pinned static service executable, and carries keyed requests, typed streaming, cooperative cancellation, deadlines, supervision, and deterministic replay across the isolated boundary (#317, #321).
|
||||
- **Optional in-process TypeScript services**: Services can use the same boundary through a linked, runtime-localized worker pool with per-key FIFO ordering, parallel independent keys, streaming, timeout and trap isolation, and replay that never starts the carrier; the explicit opt-in now follows the compiler's Windows, Linux, macOS, and cross-target matrix while the isolated child remains the automatic default (#334, #337).
|
||||
- **Engine-owned model persistence**: TypeScript cores can persist committed models through capability-gated, atomically replaced snapshots with generated codecs, debounced writes, backup recovery, explicit restore and migration routes, rollback safety, and journal/replay support (#316).
|
||||
- **SQLite record storage**: TypeScript and Zig apps can use a capability-gated record store for deterministic atomic CRUD effects backed by bundled SQLite across desktop and mobile hosts, with devhost parity and a complete Record Store example (#320).
|
||||
- **Checked relational SQLite**: Append-only migrations, build-validated named SQL, transactions, generated typed commands and live-query subscriptions, replay, and the Relational Notes example make relational SQLite a first-class offline data layer across desktop and mobile (#326).
|
||||
- **Model-driven menu-bar apps**: TypeScript apps can derive status-item labels, icons, tooltips, and rich menus from committed model state, while new macOS effects control hidden startup, fullscreen, Dock visibility, and launch-at-login behavior across both native hosts (#311, #314).
|
||||
- **Platform services for TypeScript cores**: Typed effects now open external URLs, reveal filesystem paths, and format local time through validated macOS, Linux, and Windows backends (#315).
|
||||
- **App-scoped credentials**: TypeScript and Zig cores can store, load, and delete credentials through capability- and permission-gated native providers, with redacted journals, deterministic replay placeholders, and hermetic devhost stores across desktop and mobile (#335).
|
||||
- **Cross-compiled TypeScript cores**: The external core compiler now builds Linux and Windows GNU targets from macOS, Linux, or Windows and macOS targets from macOS, with target-independent contracts and cross-platform end-to-end batteries for Windows and Linux musl (#340).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Working documentation root**: `/docs`, `/docs/`, and the matching Markdown route now resolve to the Introduction instead of ending at a 404 (#338).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Faster retained desktop frames**: Animation pumping and Windows wake scheduling now avoid stalled or redundant work, profiling uses monotonic frame-correlated telemetry, and physical macOS and Windows performance gates protect input latency and frame budgets (#313).
|
||||
- **Measured, compiler-truth service tooling**: A dedicated TypeScript Services reference documents the two-tier model and failure semantics; production-carrier benchmarks measure cold start, latency, and throughput; generated compiler-surface references and manifest diffs keep capability claims mechanically honest; and scriptc advances through 0.0.28 with refreshed contracts and calibration (#325, #327, #328, #329, #333, #336).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @carvalab
|
||||
- @Railly
|
||||
- @camilocbarrera
|
||||
|
||||
## 0.8.4
|
||||
|
||||
### New Features
|
||||
|
||||
- **Streaming fetch responses for TypeScript cores**: `Cmd.fetch` can now deliver line-framed HTTP responses through typed message arms with deterministic terminal errors, loud cancellation, duplicate-key rejection, and bounded line sizes; the rebuilt Chatbot example streams Vercel AI Gateway replies with live model selection and a Stop action (#300).
|
||||
- **Desktop audio capture**: TypeScript cores can start bounded, timestamped microphone or system-output PCM streams on macOS and Windows with explicit lifecycle, permission, drop-count, and replay handling; the new Voice Memo example records, saves, and plays WAV files (#303).
|
||||
- **Customizable macOS DMG packaging**: `native package` now creates polished drag-to-Applications disk images with generated or custom Retina backgrounds, configurable Finder geometry, positioned app and Applications entries, and staged files, directories, or links (#304).
|
||||
- **Live TypeScript theme packs**: zero-config TypeScript apps can export `themePack(model)` to switch the built-in theme pack from app state without losing live system scheme, accessibility, accent, or scale inputs (#308).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Smooth macOS dialog blur**: Host backdrop blur now uses an optimized three-pass Gaussian approximation and correct dirty-region invalidation, eliminating flat or stale dialog backgrounds while preserving the established scrim treatment (#299).
|
||||
- **Byte-accurate PTY event keys**: TypeScript PTY event routes now expose echoed session keys as `Uint8Array`, matching the byte-text host, generated facade, and external-core contract (#307).
|
||||
- **Reliable keyboard widget navigation**: Interactive canvas lists, trees, menus, and anchored controls now retain logical focus across clipped rows, scroll keyboard targets into view, and paint active and focus-visible states consistently (#308).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **TypeScript component gallery**: The GPU component showcase is now a TypeScript core and Native markup app with isolated interactive specimens, model-driven Default and Geist switching, clearer navigation, and dedicated smoke coverage (#308).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @marcusschiesser
|
||||
- @NyxTools-M
|
||||
|
||||
## 0.8.3
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Packaged TypeScript app assets**: Runtime asset lookup now finds bundled macOS resources before the process working directory, so Native markup boot images such as the Kanban agent avatars render after launch (#297).
|
||||
- **Unclipped drag landing motion**: Dropped cards now stay in the lifted drag layer through their landing animation while neighboring reflow remains clipped within its swimlane (#297).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Denser Kanban showcase**: The seeded board now includes twice as many Jira-style tickets and removes redundant issue glyphs from card metadata (#297).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### New Features
|
||||
|
||||
- **Native drag and drop for TypeScript apps**: Native markup's new `on-drag` channel delivers live, release, and cancellation geometry to compiled cores while the renderer lifts the source under the pointer, preserves one globally keyed insertion slot, animates neighboring items, and supports Escape cancellation; TypeScript cores can also map native multi-file drops into ordinary deterministic messages through `dropMsg` (#285).
|
||||
- **Desktop notifications from model cores**: TypeScript apps can return fire-and-forget `Cmd.showNotification` effects and Zig apps can call `fx.showNotification`, with bounded validation and suppression during fake execution and session replay (#283).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Explicit zero canvas padding**: Programmatic and compiled or interpreted Native markup views now preserve `padding="0"` instead of replacing it with the widget kind's default padding (#288).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **TypeScript-first app authoring guidance**: Repository instructions, bundled skills, examples, package documentation, and the docs site now consistently lead with TypeScript cores and Native markup for new apps while keeping Zig as the explicit alternative and toolkit-extension tier (#284).
|
||||
- **Agent ticket Kanban showcase**: The TypeScript Kanban example now presents numbered OpenAI- and Claude-assigned tickets, uses an icon-only add action, keeps columns scrollable, and extends its end-to-end coverage for the updated drag geometry (#295).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @johnlindquist
|
||||
- @Railly
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### New Features
|
||||
|
||||
- **Safe presentational HTML in Markdown**: Markdown now lowers common GitHub-style inline and block HTML into native widgets, including links, details, aligned containers, and caller-resolved images, while scripts, styles, forms, embeds, event attributes, and unsupported or malformed markup remain inert literal text (#280).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Reliable resolved Markdown images**: image discovery now follows renderable block starts, canonicalizes entity-encoded URLs consistently between loading and lookup, preserves aspect ratios within declared bounds, honors centered and end alignment, and ignores images inside comments, unsupported markup, code, and preformatted blocks (#281).
|
||||
- **Payload-free HTTP write requests**: `Effects.fetch` now sends an explicit zero-length body for POST, PUT, and PATCH requests without a payload, preventing debug-build crashes and emitting the required `Content-Length: 0` header (#277).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **History-driven release notes**: release preparation now builds the complete changelog entry and contributor list from the commits since the previous release, replacing the per-change fragment workflow (#278).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @Railly
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### New Features
|
||||
|
||||
- **Compiler-truth checks for TypeScript cores**: `native check` now ends with the pinned external core compiler's analyzer over the entry with the shipped SDK declarations mapped, so check and build share one compiler verdict. Type errors the frontend's own line would miss fail with the compiler's diagnostics verbatim; an analyzer that cannot reach a verdict defers to the build instead of wedging check.
|
||||
- **TypeScript cores compile through the external core compiler**: the frontend checks `src/core.ts` and emits its contract sidecar, the exact-pinned compiler builds a native archive, and the app links a generated mirror over it — no JS runtime in the binary, nothing to configure.
|
||||
- **The TS-to-Zig transpiled lane is removed** (a deliberate pre-1.0 break): `core_compiler = "transpiler"` in app.zon (and `-Dcore-compiler=transpiler`) is refused with a teaching, and `native check` runs the checker and contract only — no emitted Zig lands under `.native/check/`.
|
||||
- **The compiler is a package dependency**: it ships exact-pinned with the SDK's `packages/core` (repo checkouts install it with `npm ci` there; an npm-installed CLI carries it automatically).
|
||||
- **The core dev loop is restart-shaped**: markup hot reload and the instant `native dev --core` node loop are unchanged, and a core edit now pays a native compile measured in seconds on rebuild.
|
||||
- **TypeScript cores are desktop-only for now**: a mobile target with `src/core.ts` is taught before lane selection (the external toolchain does not target mobile yet); Zig and markup cores stay fully supported on mobile.
|
||||
- **Shipped type declarations**: `@native-sdk/core` now ships generated `sdk/*.d.ts` declaration files beside its TypeScript sources, so external tooling can resolve the SDK's types without compiling them.
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Leaner TypeScript toolchain installs**: the unused `@typescript/typescript6` compatibility wrapper is no longer a dependency of `@native-sdk/cli` or `@native-sdk/core`. The frontend already imports its compiler directly through the exactly pinned `@typescript/old` alias, while consumer trees carrying their own wrapper remain unaffected.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
|
||||
## 0.7.2
|
||||
|
||||
### New Features
|
||||
|
||||
- **Geist-style code diffs**: `ui.code` and `<code>` can mark added and removed logical lines with theme-aware full-row washes, renderer-owned `+`/`-` markers, optional line numbers, and unchanged syntax-highlighted clipboard source.
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Verified Zig setup**: repository and generated CI workflows now install Zig with `vercel-labs/setup-zig`, including signed archive and checksum verification.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Theme-accurate disabled buttons**: disabled buttons now keep shadcn's knockout label treatment in the default theme and use Geist's gray-100/gray-700 swap, gray-400 edge, and distinct half-opacity tertiary register in the Geist theme.
|
||||
- **Canonical documentation routes**: documentation now lives under `/docs/`, with permanent redirects from every previous URL, explicit canonical metadata, `.md` siblings, and a generated `llms.txt` that stays aligned with each page's canonical MDX source.
|
||||
- **Geist primary tabs match the design system**: tab strips now use the reference 50px row, full-width bottom rail, content-hugging 14px triggers, 24px spacing, and 16px icon treatment without changing default-theme pill tabs; the GPU component gallery now pairs a compact theme picker with a scrollable component tree and focused specimen views.
|
||||
- **Quiet Windows subprocesses**: `Effects.spawn` no longer opens or flashes a console window when a GUI or tray app launches a console-subsystem helper such as `node.exe`; interactive terminal children remain on the separate PTY API.
|
||||
- **Responsive Windows GPU surfaces**: Windows now renders retained binary canvas packets with Direct2D and DirectWrite, applies dirty-region patches (including edge-safe GPU-resident backdrop blur), and limits RGBA-to-BGRA conversion and invalidation to dirty pixels when software fallback is required.
|
||||
- **Exact Windows packet text and chrome**: Packet rendering now refuses when the bundled/custom font path cannot preserve engine-planned metrics, preserves explicitly positioned glyph runs, prevents system glyph substitution, and samples a covered, changed hidden-titlebar pixel for native caption contrast.
|
||||
- **Truthful GPU backend types**: TypeScript creation options now expose only portable backend requests while view and frame state can report the concrete Direct2D renderer; explicit software requests bypass packet encoding and image uploads and stay on the reference renderer and pixel presenter.
|
||||
- **Reliable registered-image replacement**: Unregistering and then re-registering identical pixels now recreates the removed GPU resource instead of retaining a stale cache key and silently omitting the image.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @oshtz
|
||||
|
||||
## 0.7.1
|
||||
|
||||
### New Features
|
||||
|
||||
- **Declarative folder-to-code editor example**: `examples/code-editor` authors its complete view in hot-reloadable `.native` markup, unifies its titlebar, file pane, tab-strip canvas, and editor background, centers the opened folder name beside a trailing ghost Save icon in a custom titlebar, opens or replaces the focused window's folder with Cmd+O, creates independent editor windows with Cmd+N, builds a clean bounded folders-first disclosure tree with outline-free selection-only Up/Down navigation, leaf-to-parent Left movement, Left/Right expansion, in-place disk-backed Enter rename, Cmd+Enter permanent tabs, and folder focus independent from the active editor, and presents editable syntax-highlighted files (including `.mjs` and large practical sources) in a resizable second pane with flat VS Code-style tabs whose active tab has no top accent and breaks the baseline to meet the editor, replaceable italic previews that pin when double-clicked, dirty dots, active/hover close buttons, native Close/Close Others tab menus, wrapping Cmd+Shift+[/] tab cycling, Cmd+W tab-or-empty-window closing, and serialized disk-backed Save/Cmd+S.
|
||||
- **Generated compiled-core facade**: `corewire --facade` now emits the complete compiler entry and matching profile from the contract sidecar, including explicit `--f64-slot` demotions, authored type provenance, and signed or unsigned integer proofs at every host ingress.
|
||||
- **Facade contract hardening**: generated entries preserve subdirectory module paths, reconstruct private reachable types without invalid imports, preserve Model-first resolution for homonymous unbound bindings, decode optional and composite record fields with a running cursor, prove nullable integer helpers, handle signed and unsigned text-selection sentinels consistently, and refuse legacy sidecars that lack the authored facts a facade requires.
|
||||
- **Effective sidecar projection**: `corewire --effective-sidecar` emits the contract after explicit slot demotions, and staged facade/profile/sidecar triples now describe one compiled layout.
|
||||
- **Editable highlighted code**: `ui.code` and `<code>` keep their read-only default, while `editable` plus `on-input` opts into a syntax-colored multiline editor in both retained and direct rendering, with selection, caret-row highlighting, IME, clipboard, undo/redo, indentation-aware Tab input (tabs or inferred 2–8-space widths, defaulting to two spaces), and no textarea chrome.
|
||||
- **Markdown source highlighting**: `markdown`/`md` joins the code lexer names with themed headings, lists, emphasis, links, inline and fenced code, and comments; the code-editor example selects it for Markdown files.
|
||||
- **Stable line-number gutter**: numbered code reserves at least three marker columns, so short files keep a useful gutter while larger line counts still expand it.
|
||||
- **Double-click messages in Native markup**: `on-double-press` exposes the canvas runtime's additive double-click channel to `.native` views, so the first click can select or preview and the second can perform or pin without a timer. Multi-click chains stay scoped to one control and physical pointer, and a third click returns to the ordinary press action instead of repeating the double action.
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Composable code presentation**: `ui.code` and `<code>` now provide bare highlighted content without their own background, border, radius, shadow, or padding; wrap them in a panel or card when surface chrome is wanted. An enabled line-number gutter remains opaque while horizontally scrolling so source glyphs cannot clash with its pinned markers.
|
||||
- **Flat tree keyboard hierarchy**: `treeitem` rows can declare a one-based `tree-level`, letting Left/Right find logical parents and children in loop-rendered flat trees, while `on-change` can keep arrow-key selection distinct from pointer activation.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Live code docs preview**: The Code component page now loads its real WASM-backed engine scene instead of silently remaining on the static screenshot fallback.
|
||||
- **Reliable large-code editing**: editable code now repaints only visible selected glyphs and caches longest-line width measurements, keeping large selections and steady-state no-wrap rendering inside bounded display-list and host-measurement budgets.
|
||||
- **Complete wrapped long lines**: scrolling a single logical line beyond 128 wrapped rows now pages its visible glyphs instead of leaving the remainder blank.
|
||||
- **Stable code-editor reads**: switching tabs no longer cancels a pinned file's load, and reopened secondary windows keep monotonic file-effect keys so late completions cannot populate a newer document.
|
||||
- **Unsaved-edit protection**: opening another folder or closing a secondary editor window now refuses while that window still has dirty documents.
|
||||
- **Steady editor tabs**: active and inactive tabs now share the same background and label alignment, so filenames no longer shift when selection changes.
|
||||
- **Balance explorer rows**: file-tree hover and selection backgrounds now keep even visual gutters beside the sidebar edge and split handle while preserving compact label alignment.
|
||||
- **Complete repository roots**: the explorer now indexes a folder when it expands instead of spending its bounded tree budget in an eager depth-first walk, so large subtrees cannot hide root files or unexplored sibling folders; `.next` and `.pnpm-store` remain visible but are not recursively indexed.
|
||||
- **Familiar file opening**: Command+Down Arrow now opens the selected tree file as a persistent tab; Command+Enter remains available to the focused control.
|
||||
- **Visible active tabs**: inactive tabs retain their bottom divider, and opening, clicking, or keyboard-cycling to a tab now minimally scrolls it into view horizontally without shifting an already visible tab.
|
||||
- **Distinct new windows**: Command+N now opens each editor window slightly down and to the right of the active window so the new window is immediately apparent.
|
||||
- **Clear empty-window title**: editor windows now show “Code Explorer” in the title bar until a folder is opened.
|
||||
- **Stable editable-code repainting**: syntax-highlighted editors now keep unique retained command IDs while edited text and highlighted spans occupy different runtime storage, preventing a selected editor from crashing when the app deactivates.
|
||||
- **Safe large widget text**: views keep their ordinary 64 KiB text pools inline and allocate practical source-file capacity only when a large layout or edit needs it, while edit, presentation, and context-menu workspaces stay off constrained native stacks and large single-line pastes continue stripping line breaks.
|
||||
- **Folder-only macOS open dialogs**: `allow_directories = true` now matches Linux and Windows by selecting directories rather than allowing files alongside them in AppKit and CEF hosts.
|
||||
- **Code-editor presentation polish**: JavaScript and TypeScript object keys and typed bindings now use the same syntax color as variables, while CSS declaration names retain their property color; numbered editors also use their full trailing width so fitting lines do not produce false horizontal scrolling.
|
||||
- **Complete JSX and TSX syntax highlighting**: JSX-family code blocks now combine JavaScript or TypeScript token coloring with JSX tags and attributes instead of treating the whole file as plain HTML outside `{…}` expressions.
|
||||
- **YAML syntax highlighting**: code surfaces, Markdown fences, and the code-editor example now recognize `yaml` and `yml`, coloring mapping keys, scalars, document markers, anchors, tags, and comments.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### New Features
|
||||
|
||||
- **Code component**: `ui.code` and markup `<code>` render highlighted source with the Geist Code Block palette in both built-in themes, wrapping by default, opt-in logical line numbers, unwrapped horizontal scrolling, and vertical scrolling for height-constrained surfaces; Markdown fences share the same component.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Bounded transformed code rendering**: heavily scaled code surfaces now degrade within the shared command and text-byte budgets instead of rejecting the entire display-list refresh.
|
||||
- **Polished Markdown lists and code blocks**: bullet and ordered-list markers now align with the first content line, while fenced code preserves source indentation and applies theme-aware highlighting with richer HTML/JSX tags and attributes.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
|
||||
## 0.6.3
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Native textarea editing shortcuts**: Up/Down now moves or extends the caret across visual lines, Command+Left/Right uses the current line boundary even through unbroken soft wraps, Command+Up/Down reaches the document boundary, and Command+Z / Command+Shift+Z provides bounded per-editor undo and redo from either the keyboard or macOS Edit menu while keeping controlled `TextBuffer` models synchronized.
|
||||
- **Textarea indentation**: spaces typed at the start of an empty line now remain visible and advance the caret under word wrapping.
|
||||
- **Textarea pointer selection**: Shift-click now extends the selection from the existing caret instead of replacing it.
|
||||
- **Textarea line endings**: caret movement, deletion, and controlled selections now treat CRLF line endings as one indivisible boundary.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
|
||||
## 0.6.2
|
||||
|
||||
### New Features
|
||||
|
||||
- **Reliable desktop overlay windows**: window declarations and runtime creation now support transparent, always-on-top, click-through, and passive-show presentation applied before first visibility; canvas windows reveal after their first alpha-correct present without stealing focus, fall back to a late reveal if rendering wedges, and Windows composites multiple canvas layers while rejecting child surfaces its layered presenter cannot display.
|
||||
- **Honest backend constraints**: Linux main WebViews inherit transparent-window alpha, macOS Chromium rejects transparent windows because windowed CEF content cannot supply alpha, and transparent Windows windows require chromeless chrome with no application menu because the layered compositor cannot capture Win32 non-client pixels.
|
||||
- **Resizable transparent Windows windows**: the layered presenter keeps the nearly invisible system resize frame pointer-targetable without filling intentional alpha-zero regions in the client.
|
||||
- **Hybrid overlay lifecycle**: canvas-only overlay windows stay free of implicit main WebViews in mixed WebView scenes and across hot reloads, while passive Linux windows restore from minimization without taking focus.
|
||||
- **Reliable explicit focus on macOS**: focusing a system-WebView window now activates the app before asking AppKit to make the window key, so an inactive app can come forward as requested.
|
||||
- **Imperative canvas overlays**: `runtime.createWindow` and `window.zero.windows.create` keep transparent windows without an explicit source canvas-only across hot reloads, and JavaScript can select the chromeless titlebar Windows requires.
|
||||
- **Idle overlays stay idle**: transparent canvas windows retain their last presented image without entering a display-rate repaint loop, while software presenters still rebuild fully once when their shared pixel buffer changes surfaces.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Container backgrounds render**: explicit backgrounds on `stack`, `row`, and `column` now paint across the laid-out frame with their configured radius.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @jasonkneen
|
||||
- @sepehr-safari
|
||||
|
||||
## 0.6.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Layered macOS cursors**: GPU surfaces now yield their cursor regions to higher-layer embedded webviews, so links, selectable text, and canvas widgets use the correct cursor in mixed canvas/webview windows such as Workbench.
|
||||
- **Pointer-selected text edits**: editable fields now send pointer caret and selection changes through `on-input`, so model-owned text buffers delete or replace the highlighted span instead of editing at a stale caret.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### New Features
|
||||
|
||||
- **The external-source channel — `fx.openChannel`, TEA subscriptions done our way**: apps with long-lived external sources (sockets, file watchers, app-managed worker threads) get a first-class, journaled way to wake the UI loop and produce a Msg — no more timer-polling a shared queue. `fx.openChannel(.{ .key, .on_event, .max_pending? })` returns a THREAD-SAFE `ChannelHandle` whose `post(bytes)` stages into a per-channel non-lossy FIFO, wakes the host, and delivers one `.data` event Msg per accepted post on the next drain (bytes in drain scratch, bounded at `max_effect_channel_bytes`); `fx.closeChannel(key)` flushes the staged backlog and delivers exactly one `.closed` terminal with final drop totals. Channels share the keyed families' one key space — occupied from open until close delivers — and never fail from the caller's view: a duplicate occupied key or a full table answers with one `.rejected` event.
|
||||
- **Back-pressure is part of the contract, and the post's answer names it**: `post` returns a `ChannelHandle.PostResult` — `.accepted`, `.dropped_full` (staging FIFO full: transient, skip and keep producing), `.dropped_oversized` (bytes over the post bound: a programming error no retry fixes), or `.closed` (the occupancy is over: exit the loop) — so a producer never has to guess "retry later" from "stop forever". Both drop answers count into `dropped_pending`/`dropped_total` on the NEXT delivered event — never silent drops, and never a blocked posting thread given a conforming host wake: the platform's `wake_fn` is contractually a bounded, non-blocking, enqueue-only nudge (documented at `PlatformServices.wake_fn`; every first-party host conforms — macOS `dispatch_async`, GTK `g_idle_add`, Win32 `PostMessageW`), and the runtime holds no channel lock across the call, so even a violating embedder wake hangs only its own posting thread, never a drain, close, or teardown. A violator still inside the hook at teardown is abandoned after a bounded wait, and the platform is then deliberately kept alive — destruction skipped, leaked process-lived, with one loud log — so the stale call can never execute into freed host state. Wakes are exactly as many as the loop needs: a refused post never wakes the host (a wake is issued only when a post makes new work drainable), and accepted posts COALESCE behind one latched wake per drain (a burst costs the host queue one entry, cleared at the drain boundary before it snapshots — so a post racing the drain always lands a fresh wake), meaning neither a refusal storm nor a fast producer can grow the loop's queue. Handle lifetime is safe by construction: the handle resolves through a generation-stamped process-lifetime header, so posts after close, after slot reuse, or after runtime teardown answer `.closed` instead of touching freed memory.
|
||||
- **The journal fingerprint moves, a conscious break**: the `.channel` effect-record kind journals every delivered event as executor truth at the drain boundary, post bytes INLINE (channel posts are small-message-shaped — no blob store detour). Replay feeds the recorded events verbatim and never NEEDS the source — the channel open is an ordinary replayed dispatch that PARKS the occupancy (the key registers as live, duplicate opens reject symmetrically, admission rejections regenerate) and returns an inert handle whose every post answers `.closed`. Honesty about what re-runs: the opening update re-executes under replay, so a producer launched unconditionally really starts — socket connects and blocking setup before its first post included — and is stopped only AT that first post; `ChannelHandle.live()` is the producer-launch check (false for parked replay handles, refused opens, and closed occupancies — advisory, the post's own answer stays authoritative), so producers that consult it before launching keep replay fully offline, the `examples/channel-monitor` pattern. Impossible records (bytes over the post bound, byte-carrying terminals) refuse replay as damage. Journals from earlier builds are refused at the preamble with the standard re-record teaching.
|
||||
- **Bridge refusal timing, a conscious break**: TS-tier refusals produced by the bridge itself — duplicate-spawn keys, image validation, channel admission — used to deliver their rejection Msg at the command cycle's own boundary, before anything else could run. They now stage into the engine's seq-stamped pending stream and deliver at the next host drain, so every rejection — engine-refused or bridge-refused — arrives in ONE seq-ordered stream in command order, which is what `Cmd.batch`'s performed-in-order contract requires across layers (a batch mixing the two authorities used to deliver its rejections out of order). The observable difference: a frame may render between the command cycle and the rejection Msg, so an app or test that asserted the rejection landed inside the same cycle now sees the intermediate model rendered once and the rejection one drain later.
|
||||
- **TS tier first-class**: `Cmd.channelOpen(key, { event })` / `Cmd.channelClose(key)` (wire opcodes 0x15/0x16, additive within cmd_format_version 3) with a five-field event arm matched by name (`key`/`state`/`bytes`/`droppedPending`/`droppedTotal`; the three-member `ChannelState` union checked at build time). Posting is deliberately not a TS verb — transpiled cores are single-threaded: the TS tier opens, closes, and receives, and the native side feeds through `Effects.channelHandle(key)`.
|
||||
- **`examples/channel-monitor`**: an app-owned worker thread samples its own process and posts each reading; the UI updates only when events arrive — no `fx.startTimer`, no polling, and Stop winds the detached worker down through the handle's own `.closed` answer, while a transient `.dropped_full` only skips a sample — the drop counters reach the status line.
|
||||
- **Horizontal and two-axis canvas scrolling**: scroll views declare `axis="vertical|horizontal|both"` (builder `axis:`), horizontal offsets ride `value-x` with the same source-wins reconcile as `value`, the engine draws a bottom-edge scrollbar, keyboard scrolling gains Left/Right/Home/End on horizontal-capable regions, and macOS native scroll drivers carry both axes with OS momentum and rubber-band.
|
||||
- **Independent per-axis wheel routing**: each axis of a wheel/trackpad gesture travels to the nearest ancestor scrollable on that axis, so a horizontal timeline holding a vertical list splits a diagonal gesture — `delta_y` scrolls the list, `delta_x` reaches the timeline.
|
||||
- **BREAKING — `ScrollState` is two-axis now**: the one-axis `{offset, velocity, viewport_extent, content_extent}` record (TS: `offset`/`velocity`/`viewportExtent`/`contentExtent`) was replaced by per-axis fields `offset_x`/`offset_y`, `velocity_x`/`velocity_y`, `viewport_extent_x`/`viewport_extent_y`, `content_extent_x`/`content_extent_y` (TS: `offsetX`…`contentExtentY`); migrate a vertical region by reading the `_y` fields where it read the old ones — an `on-scroll` arm still declaring the old shape fails the build with a teaching that names the new fields.
|
||||
- **Hover-driven Msgs — `on-hover-enter` / `on-hover-leave`**: widgets can now bind pointer hover as first-class TEA vocabulary (Elm's `onMouseEnter`/`onMouseLeave`): enter dispatches once when the pointer enters a bound element's hit region, leave once when it exits — discrete containment edges, never per-move — so hover previews, prefetch, and hover cards are ordinary Msgs. Legal on any element in markup and in Zig views (`ElementOptions.on_hover_enter` / `on_hover_leave`), and the TS tier gets the pair for free (payloadless events need no SDK types).
|
||||
- Binding hover makes the element hover-hittable the way a bound press makes it pressable — but never pressable: clicks keep falling through, no accessibility action is announced, and no hover wash appears (a quiet content tile that binds hover stays visually quiet). Nested bound elements track containment independently; enters fire outermost-first, leaves innermost-first.
|
||||
- Every enter is answered by exactly one eventual leave: the leave Msg is captured when the enter dispatches, so it still arrives when the exit is the element unmounting. Exits resolve exactly like the hover wash already does — moving off, the pointer leaving the window, dismissals, and content scrolling or reflowing out from under a stationary pointer all re-hit-test the last pointer position — and overlays occlude hover the way they occlude clicks.
|
||||
- Opt-in and free when unbound: apps that bind no hover handlers keep an empty containment chain, no extra rebuilds, and no journal traffic. Where bound, hover Msgs derive deterministically from already-journaled pointer input, so recorded sessions replay them byte-identically with no journal format change.
|
||||
- Touch honesty: hover comes from mouse and trackpad pointers only — touch input never synthesizes it, so anything reachable only by hover must stay reachable another way. Deliberate break: reserving pointer-id bit 63 as the touch-source stamp changes the meaning of a journaled field, so the session journal's semantic epoch moves and recordings from earlier builds refuse with the standard re-record teaching.
|
||||
- `examples/notes`: hovering a note row now previews its title, age, and word count in the status bar (the browser status-line convention) without committing the selection.
|
||||
- **Named keys grow `delete`, `home`, `end`, `pageup`, `pagedown`, `insert`, and `f1`–`f12`**: every desktop platform now reports them on GPU-surface key events (they previously surfaced on some platforms as private-use strings or not at all), and shortcuts and menu accelerators can bind them. Terminal-style consumers can encode the full navigation and function-key set; none of these require a modifier, matching platform convention (F5 alone is a valid accelerator).
|
||||
- **Native context menus on Windows and Linux**: a right-click on a widget with a declared menu (or the zero-code editable-text and selected-text defaults) now presents the OS menu at the pointer on Windows (`TrackPopupMenu`) and Linux (`GtkPopoverMenu`), with the selection or dismissal riding the same journaled `context_menu_action` event macOS already emits — one authored menu, one replayable outcome, three desktop platforms.
|
||||
- The `.context_menus` platform capability now reports true on both system-engine hosts, so feature-gated code takes the native path everywhere the system web engine runs.
|
||||
- The engine fallback surface (hosts with no native presenter) now anchors the menu at the click point instead of the target widget's edge, matching where the pointer actually is on wide targets.
|
||||
- Selections now resolve from a present-time snapshot of the shown items, so a menu left open across a rebuild (a timer reordering conditional items) dispatches the item the user saw, never the rebuilt tree's occupant of that slot.
|
||||
- Deliberate automation-protocol break: recorded `context_menu_action` tokens are per-request generations instead of widget ids, so the protocol semantic epoch moves. Recordings from earlier builds are refused loudly at the preamble (their context-menu selections would otherwise be silently swallowed by the token gate); re-record with this build.
|
||||
- **Windows pty transport — ConPTY, first-class**: `fx.ptySpawn` and the whole pty family now run on Windows through `CreatePseudoConsole` over an overlapped pipe pair, honoring the exact vocabulary contract the macOS/Linux backends implement — same spawn admission and environment policy (the bound host environment plus `TERM`; env names match case-insensitively, the Windows rule), same all-or-nothing `ptyWrite`, `ptyResize` via `ResizePseudoConsole`, `ptyKill` via `TerminateProcess` plus pseudoconsole teardown (which reaches every descendant still attached to the console), same coalesced output batches and lossless back-pressure, and the same exactly-one exit. The terminal example runs unchanged (its deterministic shell pick adds cmd.exe), and recorded sessions replay offline exactly as on POSIX.
|
||||
- **Encoding honesty**: the pseudoconsole's pipe contract is UTF-8 with VT sequences in both directions, and the backend creates it with flags 0 — no `PSEUDOCONSOLE_INHERIT_CURSOR`, so conhost never opens with a cursor-position handshake the app would have to answer. There are no console-mode calls to make host-side: the VT modes live inside the pseudoconsole's conhost.
|
||||
- **Differences stated plainly** (docs' platform matrix moved from "staged" to supported): Windows has exit codes only, so `signaled` never occurs there — a crash surfaces as `exited` with the NTSTATUS bit-cast to `i32` — and ConPTY output is conhost's VT rendering of the child's screen, not the child's raw byte stream.
|
||||
- **TS tier: the pty command family**: `Cmd.ptySpawn(argv, { cols?, rows?, term?, event })`, `Cmd.ptyWrite(key, bytes)`, `Cmd.ptyResize(key, cols, rows)`, and `Cmd.ptyKill(key)` (wire opcodes 0x19-0x1C) expose the pty vocabulary to transpiled cores, with an event arm matched by field name (`key`/`state`/`bytes`/`code`/`reason`/`signal`/`droppedWrites`), where `key` is the app's own session key so two sessions routing one arm stay distinguishable. The native side owns the transport; the TS tier spawns, writes, resizes, kills, and receives.
|
||||
- **`<terminal>` — the terminal as a markup built-in**: `ui.terminal(.{ .pty = key, .scrollback, .on_terminal })` (markup `<terminal pty={key} scrollback={offset} on-terminal="...">`) promotes the terminal from the example tier to a first-class element. It binds a model-owned pty effect key — the same id `fx.ptySpawn` named, the media-surface `surface` binding shape — and renders the framework-owned emulator session behind it: the grid painted as real text with geometric box drawing, a theme-derived ANSI palette, selection, cursor, and scrollback, all moved into the canvas (`canvas.TerminalGrid`, the `.terminal` widget kind) from the example. Focused, it routes keys, IME text, and wheel scrollback to the session; the live viewport text rides the widget's accessibility label so screen readers read the real screen and session fingerprints cover cell state.
|
||||
- **The terminal state contract**: `on-terminal` delivers a `canvas.TerminalState` (`scrollback`, `history`, `cols`, `rows`) after every runtime-applied view-state change, and `scrollback` echoes it back under the scroll `value` source-wins reconcile rule. Only app-visible view state crosses the boundary — the emulator's cells, modes, and selection pins stay framework-owned and are never model state. Expressible in both authoring tiers, matched structurally for transpiled cores.
|
||||
- **Teachings**: a `<terminal>` without `pty={binding}` is refused as dead markup (the media-surface-without-surface policy); a literal pty key, `pty`/`scrollback`/`on-terminal` on any other element, and children all teach exactly where they belong, in the validator and both markup engines alike.
|
||||
- **Live `<terminal>` sessions, runtime-owned**: binding a pty key with `<terminal pty={key}>` now renders a REAL session — the runtime feeds the key's journaled pty output into a framework-owned emulator, routes the focused element's keys and IME text back out through `ptyWrite`, answers device queries, scrolls history on the wheel, and drives `ptyResize` from the element's laid-out extent through the shared cell-metrics seam. An app's terminal is `fx.ptySpawn` plus the element: no emulator wiring, no key encoding, no grid plumbing. Because the emulator is fed from the journaled byte stream and every outbound byte crosses the journaled write path, a recorded session replays to the same screen with no shell present.
|
||||
- **Opt-in emulator, consumer-safe**: `AppOptions.terminal_sessions = true` (with a lazy `ghostty` pin in the app's own `build.zig.zon`) wires libghostty-vt behind the element; every other build — scaffolded apps, the docs preview, transpiled cores — gets a stub that renders the empty terminal surface and never traverses that dependency graph. `native_sdk.runtime.terminal_sessions_enabled` reports which half a build carries.
|
||||
- **`examples/workbench`**: a live terminal beside a browser in one resizable split — the terminal is the element (no emulator code in the app), the browser is a webview pane snapped to a markup anchor with app-owned navigation history behind back/forward, reload, and an address bar.
|
||||
- **Terminal — the pty effect vocabulary and a recordable terminal embed**: `fx.ptySpawn(.{ .key, .argv, .cols, .rows, .term?, .on_event })` opens a pseudo-terminal, forks the command onto it as its controlling terminal, and streams output back as coalesced `on_event` Msgs; `fx.ptyWrite(key, bytes)` sends stdin all-or-nothing and returns whether the payload was accepted (a caller that must not lose bytes retains a refusal and retries; verdicts are journaled so replay takes the identical path), `fx.ptyResize(key, cols, rows)` pushes a new grid (SIGWINCH), and `fx.ptyKill(key)` terminates the job. A pty is a spawn with a different transport — it rides the same `command` permission, the same environment policy, the same argv budgets, and the same one key space as spawns, fetches, and channels. macOS and Linux ship the real transport (openpty + a controlling terminal); Windows ships ConPTY (its own fragment); the null platform gets a scriptable fake pty so the whole vocabulary tests headless.
|
||||
- **Output is coalesced per frame, never per read, and back-pressure is lossless**: bytes arriving between drains deliver as one batch bounded at 64 KiB, so `cat largefile` journals per-frame batches instead of a record per `read()`. The transport's staging ring never drops a byte — a full ring parks the reader and the kernel slows the child, a terminal's native flow control — and the exit event reports `dropped_writes` for any `ptyWrite` refused over the session's life.
|
||||
- **One exit per spawn, honest classes**: exactly one `.exit` event ends every accepted (and every refused) spawn — `exited` with the child's code, `signaled` with the signal, `cancelled` after `ptyKill`, `rejected` for requests refused before a child existed (bad argv, zero grid, duplicate key, table full, unsupported platform), `spawn_failed` when the pty or exec could not start.
|
||||
- **Recorded sessions replay byte-identical, offline — no shell present**: output bytes are the effect result, written at effect-result time into the content-addressed blob store beside the journal (`blobs/<sha256[..16]>`, identical batches deduplicated), with the journal record carrying the hash and length. Replay never spawns a process: the `ptySpawn` parks the pty (writes/resizes/kills go inert), the journaled batches and exit feed verbatim from the blob store, and the fingerprint checkpoints verify the replayed emulator grid frame by frame. Adding the pty record kind moved the journal format fingerprint — older recordings refuse at the preamble with the standard re-record teaching.
|
||||
- **`examples/terminal`**: a keyboard-first terminal at the showcase bar — libghostty-vt (Ghostty's extracted VT core, pinned as the `ghostty-vt` Zig module) owns cell state, damage, scrollback, wrapping, reflow, and selection; the canvas paints the viewport as real text with theme-mapped ANSI-16, exact 256-color and truecolor, and wide CJK cells. Typing rides the IME-correct committed-text channel and the emulator's key encoder; cmd/ctrl+shift+space arms line/block cell selection, cmd/ctrl+arrows page the scrollback, and cmd/ctrl+C copies.
|
||||
- **`UiApp.Options.on_text`**: the target-less committed-text seam — `on_key`'s typing twin — for apps that consume text without a focused text-entry widget (a terminal grid). Delivered for unclaimed `text_input` after the same widget-precedence routing `on_key` yields to, carrying the committed UTF-8 (IME results included) so consumers stay layout- and input-method-correct. Chrome may also declare a `variable_prefix` prefix whose command count is model-derived, for chrome whose shape changes per frame (a terminal grid, a data plot).
|
||||
- **Video playback**: a new `<video>` element (registry code 68, attributes `controls`/`autoplay`/`loop`/`muted` at codes 82-85 with `src` riding the existing attribute) plays platform-decoded video through the media-surface texture channel — AVFoundation on macOS decodes straight into the compositor while the app core sees only commands and journaled events; Windows (Media Foundation) and Linux (GStreamer) stage the capability honestly: `video_playback` reports false and the load verbs answer with a teaching plus one explicit failed event until their decoders land.
|
||||
- **The video command/event vocabulary**: `fx.loadVideo` mirrors the audio channel end to end — local-then-URL source cascade with the http(s) scheme check, transport verbs (`playVideo`/`pauseVideo`/`stopVideo`/`seekVideo`/`setVideoVolume`/`setVideoMuted`/`setVideoLoop`), key-stamped events (`loaded` with stream dimensions and duration, position ticks with the honest `buffering` flag, one `completed` at a non-looping natural end, explicit `failed`/`rejected`), and replace semantics that release the surface claim; TypeScript cores get `Cmd.videoLoad`/`videoCtl` at wire opcodes 0x17/0x18 with the by-name seven-field event-arm convention.
|
||||
- **The session journal fingerprint moves** (a deliberate break — recordings from earlier builds are refused at the preamble; re-record with this build): the new `.video` effect-result kind (code 13) and platform-event tag journal every delivered event verbatim, so a recorded playback replays byte-identical on a host with no decoder and no texture producer attached, and texture contents stay out of session fingerprints exactly like every media-surface texture.
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Build fingerprints replace version counters for the session journal and automation protocol**: the journal's `format_version` and the CLI/app `protocol` version are gone in favor of comptime layout fingerprints — a Wyhash over a canonical description reflected from the actual record, event, and command types — so any layout change moves the identity automatically, with no counter to remember to bump and no next integer for parallel branches to contend over. Since no journal or dropbox skew is ever migrated, identity beats ordering: "same or different" was the entire question the integers answered.
|
||||
- Deliberate break: journals and automation sessions recorded by any earlier build are refused with the re-record teaching (the journal preamble now carries the u64 format fingerprint; the snapshot header stamps `protocol=0x...`), and skew refusals name fingerprints instead of version numbers.
|
||||
- A small `semantic_epoch` remains for the rare meaning-only change with identical bytes (the context-menu token generations were one); layout changes need no action.
|
||||
- `zig build print-pins` and `native version` print the fingerprints, so a build's wire identities can be quoted exactly.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **No more stale fringes when content reflows**: incremental canvas damage now covers the anti-aliasing bleed — the up-to-one-device-pixel ring rasterizers ink past a command's bounds — so a list-detail selection change that reflows conditional content (badge pills removed, shrunk, moved, or replaced under new keys) no longer leaves leftover edge pixels where the old content extended beyond the new. Every finalized incremental dirty rect (the refined union, each refined cluster on the retained-patch wire, and the summary fallback) inflates by one device pixel before surface clipping; full repaints are unchanged.
|
||||
- **Terminal context menu**: right-clicking a `<terminal>` now presents the standard Copy and Paste actions, copying the emulator selection and sending pasted clipboard text to the bound PTY.
|
||||
- **Natural terminal editing on macOS**: focused terminals now translate Option+Left/Right to word movement, Command+Left/Right to line boundaries, and Command+Delete to clearing back to the line start, instead of leaking unsupported modifier sequences into the shell prompt; Command+V now sends clipboard text through the terminal's bracketed-paste-aware input path.
|
||||
- **Selectable terminal text**: `<terminal>` now supports pointer-drag cell selection, double-click word selection, triple-click line selection, and Cmd/Ctrl+C clipboard copy without forwarding the copy chord to the child.
|
||||
- **Terminal Tab input**: focused live `<terminal>` components now send Tab and Shift+Tab to the PTY for completion, indentation, and TUI navigation, while focus-entry gestures and ended or unbound terminals retain ordinary traversal.
|
||||
- **Video letterboxes instead of stretching**: the video surface now aspect-fits (contain) the decoded frame — centered at the stream's reported proportions, letterboxed or pillarboxed on black, never distorted. Contain is the video surface's one fit mode, stamped on the `<video>` element and on any app-claimed surface while its playback is live; unknown dimensions before the LOADED report keep the full-frame placeholder, and a source replacement re-fits from the new report. Camera and app-producer media surfaces are untouched.
|
||||
- **Clear terminal focus**: terminal cursors now fill while their live session owns keyboard focus and switch to a hollow outline when focus leaves or the session ends.
|
||||
- **Clean workbench terminal chrome**: the full-bleed terminal pane keeps keyboard focus without showing its clipped outer focus ring as a stray horizontal rule beneath the titlebar.
|
||||
- **Workbench pane focus stays truthful**: clicking the embedded page now blurs the address bar and hollows the terminal caret; clicking either canvas pane restores its expected keyboard focus.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @startewho
|
||||
|
||||
## 0.5.4
|
||||
|
||||
### New Features
|
||||
|
||||
- **`Cmd.imageLoad` — dynamic images, the first full media pipeline**: apps load images at runtime from disk or the network by a model-owned ImageId, the effect executor resolves the audio cascade's source order (local path first, then a verified content-addressed cache entry under `<caches>/images/`, then the network with an atomic cache install behind it), decodes through the platform codec into the existing registered-image storage, and exactly ONE result Msg comes back — `loaded` with the decoded width/height, or one honest failure class from the same vocabulary the direct registration API raises (`decode_failed`, `too_large`, `registry_full`, `unsupported`, `alloc_failed` — the host refused the memory the registration needed, resource exhaustion rather than corrupt bytes — the fetch taxonomy, `http_status` with the status carried through).
|
||||
- **TS tier first-class**: `Cmd.imageLoad(id, { path?, url?, cachePath?, expectedBytes? }, { event })` with a five-field result arm matched by name (`id`/`state`/`width`/`height`/`status` — `id` echoes the requested ImageId so concurrent loads sharing one arm stay distinguishable; the fifteen-member `ImageState` union checked at build time), id expressions welcome (ids are model data), `Cmd.imageCancel(id)` ending a live load loudly (the event arm's "cancelled", freeing the id for a same-id retry; an id with no live load no-ops), `Cmd.imageUnregister(id)` releasing a loaded image's registry slot (the gallery eviction move past the 16-slot registry — synchronous registry surgery like registration itself, no result Msg, misses no-op; a load in flight still registers at its terminal, so cancel first to keep the slot free), opcodes 0x12/0x13/0x14 additive within cmd_format_version 3, and `TsUiApp`'s `image_cache_dir` deriving the content-addressed cache path from the URL so update never builds filesystem paths.
|
||||
- **Markup `<image>` — the runtime-image leaf (element code 67)**: `image="{binding}"` binds the model-owned u64 ImageId in avatar's grammar (binding-only, required on the leaf, negative model values fail the build with a teaching, never a trap), wired through the validator, both engines, `native check`'s model contract, LSP hover docs, and the docs vocabulary; the `image` attribute's scope broadened from avatar-only to avatar+image.
|
||||
- **Recorded sessions replay byte-identical, offline**: an image load's ENCODED source bytes are the effect result, journaled at effect-result time into a content-addressed blob store beside the journal (`blobs/<sha256[..16]>` in the session directory — identical bytes twice store one blob), with the journal record carrying hash + length and the dedup probe verifying an existing blob's bytes before trusting its name (a damaged blob repairs in place from the bytes in hand — recording self-heals the store instead of sealing a journal replay must refuse); replay reads the blob, verifies it against its address, re-runs decode + registration, and delivers the recorded result with no file, network, or cache touched, refusing loudly when the blob store is missing or damaged.
|
||||
- **Journal format, stated plainly**: the image records bump the session-journal format to v7 (the `.image` effect-record kind plus the blob-address fields appended to every effect record); v6 and older journals are refused at the preamble with the standard re-record teaching — a v6 reader would have misparsed the longer records as corruption.
|
||||
- **Zig tier**: `fx.loadImage(.{ .id, .path, .url, .cache_path, .expected_bytes, .on_result })` with `Effects.imageMsg(...)` routing, a fake-executor seam (`pendingImageLoad*`, `feedImageBytes` running the REAL decode+register path, `feedImageResult`), and `imageCachePath` deriving the cache convention; the encoded source is bounded at 1.25 MiB from every source alike and over-bound sources fail whole with `too_large` — a cut image never decodes, so there is no truncated delivery.
|
||||
- **The menu-bar app lifecycle**: windows can declare `close_policy = "hide"` in app.zon (the default `"quit"` keeps today's behavior for every existing app) — the red close button hides the window instead of quitting, the app keeps running behind its status item, and the macOS Dock reopen re-shows it.
|
||||
- New window verbs on the effects channel: `fx.showWindow(label)` un-hides and activates a window (the tray "Open" consequence; also restores a minimized window), and `fx.quitApp()` quits gracefully through the same shutdown path a last-window close takes — both mirrored in the TypeScript tier as `Cmd.showWindow(label)` and `Cmd.quitApp()`.
|
||||
- Hidden state is honest, journaled window state: `WindowState.hidden` rides the frame channel, records into session journals, and replays.
|
||||
- Implemented on macOS (windowShouldClose + orderOut, Dock reopen) and Windows (WM_CLOSE hides via SW_HIDE; the tray re-shows); Linux GTK has no status item to bring a hidden window back, so `"hide"` is refused loudly at build/create time with a teaching instead of stranding a window.
|
||||
|
||||
### Improvements
|
||||
|
||||
- **The tofu guard teaches font registration**: the font-coverage teachings — the `native markup check` error, the Debug view-build diagnostic, and the CLI usage text — now name registering a covering face (`UiApp.Options.fonts`) behind a model binding as the first remedy for text beyond bundled coverage, alongside vector icons and plain words.
|
||||
- **A fonts page in the docs**: `/fonts` documents registering faces for scripts beyond bundled coverage — the `Options.fonts` scaffold shape, every registration-time validation error by name, ownership and lifecycle, how text resolves faces through the typography tokens, and per-platform truth including the unverified mobile seam.
|
||||
- **The Chinese receipt runs natively on Windows in CI**: a new `zig build test-canvas-fonts` step runs the font-registry suite on the Windows runner, including the receipt test that registers a committed subsetted Noto Sans SC (OFL, license alongside) through the app-fonts seam and proves the rendered string is real ideograph outlines — compared against both the bundled face's rendering and the same registered face's own uncovered-string fallback, so tofu from any face fails the receipt.
|
||||
- **Registered-image memory is on-demand**: each registered canvas image slot buffer is one lazy 1 MiB allocation from `Runtime.Options.allocator` at the slot's first registration (freed by `Runtime.deinit`; unregister/register churn reuses buffers, so the footprint stays bounded by the high-water slot count), so a runtime that never registers an image no longer carries the former 16 MiB embedded pixel pool.
|
||||
- **New error on register**: `registerCanvasImage` / `registerCanvasImageBytes` (and the `fx.registerImage` / `fx.registerImageBytes` bindings) now surface `error.OutOfMemory` when a slot's pixel buffer cannot be allocated — the refusal happens before any registry mutation, so the registry is unchanged and the same registration can be retried once memory recovers.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Large markup documents compile in the compiled engine**: `CompiledMarkupView` / `CompiledMarkupImports` no longer fail with "evaluation exceeded 1000 backwards branches" on `.native` documents past ~10KB — documents now carry their source size from parse/resolve time, so the comptime canonicalize pass sizes its branch quota in O(1) instead of re-measuring the tree inside the quota argument (which ran under the caller's default budget).
|
||||
- **Linux `native dev` no longer crashes at startup**: Debug builds on x86_64 Linux segfaulted creating the first shell view (a general protection fault in the GTK host's `native_sdk_gtk_create_view`) because Zig 0.16.0's self-hosted x86_64 backend — the Debug default — mis-places stack-passed arguments in the host call's long mixed signature; the app executable now forces the LLVM backend on x86_64 like every other artifact in the build graph, and the ejected template's build does the same.
|
||||
- **GTK host string caps**: the Linux host now refuses view create/update calls whose string lengths exceed the platform caps with a teaching warning instead of copying from a corrupted pointer, so a broken C-ABI boundary fails loudly at the seam.
|
||||
- **Linux Debug scaffold smoke in CI**: a new `linux-dev-smoke` job scaffolds the default template, builds it `-Doptimize=Debug` (the `native dev` mode Release-shaped lanes never exercise), and drives it under Xvfb to the first presented frame.
|
||||
- **Late-registered fonts re-measure open surfaces**: registering a face after views are installed (`runtime.registerCanvasFont` on a live runtime) now rebuilds every installed `UiApp` surface — the main canvas and declared windows — on the next presented frame, so text laid out before the face joined re-measures with the registered face instead of keeping its pre-registration widths under a repaint.
|
||||
- **macOS host font state ends with its runtime**: `Runtime.deinit` now returns each registered id's host-side registration — the CoreText descriptor and its measurement caches, including the measured-width cache the host previously retained until memory pressure — so embedders that cycle runtimes no longer accumulate per-process font state; removal is ownership-token guarded, so an older runtime's teardown never removes a newer runtime's live face under a shared id.
|
||||
- **Breaking**: `PlatformServices.registerGpuSurfaceFont` now returns the host's ownership token for the registration (`u64`; 0 from hosts that retain no per-id state), and the new optional `unregisterGpuSurfaceFont(id, token)` service returns that state at teardown — a deliberate break while the toolkit is pre-1.0, so host font lifetime has an owner. Embedders implementing a custom platform change `register_gpu_surface_font_fn` to return a `u64` token (0 is fine for a stateless accept) and may supply `unregister_gpu_surface_font_fn` to release per-id host state when the registering runtime deinits.
|
||||
- **Ternaries with spread-literal arms compile from TypeScript cores**: `parsed === null ? q : { ...q, state: "ok", price: parsed }` — and the nested, `!==`, both-arms, argument, object-field, and `x === null ? { ...fallback } : x` spellings — no longer emit Zig that reads the null-narrowing capture before it binds (`use of undeclared identifier`) or evaluates both arms unconditionally; arms that build values statement-by-statement now lower into per-branch blocks feeding a typed temp, so exactly the taken arm runs, and pure-arm ternaries keep their tight `if`/`orelse` expression forms.
|
||||
- **Optional switch payloads keep their optional through the capture**: reading a `number | null` payload (directly or via a `const` local) inside its `case` no longer mistypes the value as non-null, which routed `msg.parsed === null ? ... : ...` around the narrowing lowering and emitted `?f64` into `f64` slots.
|
||||
- **Early-exit guards narrow like early returns**: `if (r === null) break;` in a parse loop (and the `continue`, labeled, multi-statement, `throw`-exit, and `if (x !== null) { ... } else { break/return }` spellings) now narrows the optional for the rest of the loop body the way tsc's flow analysis does, instead of emitting Zig field access on the still-optional value; `if (msg.kind !== "num") break;` narrows the union payload the same way.
|
||||
- **Guard narrowing ends with its block**: a guard's captures no longer leak past the loop body or branch they narrow — reads after the construct see the unnarrowed value again (matching tsc, whose exit path may bypass the guard) instead of referencing an out-of-scope Zig capture.
|
||||
- **Early switch-clause breaks stop the build**: an unlabeled `break` that exits a `switch` from inside a clause body now teaches at transpile time — Zig's `break` binds loops, so the old emission jumped past the enclosing loop instead of resuming after the switch.
|
||||
- **A redundant kind guard no longer un-optionals a switch payload**: scoped kind-narrowing now restores the still-optional markers alongside the substitutions it snapshots, so `const marker = msg.kind === "got" ? 1 : 2;` inside `case "got":` no longer leaves a `number | null` payload typed non-null for the rest of the clause (which emitted `if (parsed != null) parsed + marker else 0` — invalid Zig operands on the `?f64`).
|
||||
- **Inferred locals from narrowed ternaries value non-optional**: `const picked = q === null ? { ...fallback, price: 0 } : q;` (either polarity, no `: Quote` annotation) now types the local by the arm the condition narrows — `Quote`, exactly as tsc infers — instead of the raw optional, which declared a `?Quote` temporary that failed Zig compilation at its first non-optional use (`expected type 'Quote', found '?Quote'`).
|
||||
- **A redundant nested `switch` on the same subject hands back the outer capture**: the arm cleanup now repopulates its narrowing maps from the snapshot instead of only deleting the arm's additions, so an inner arm's capture that OVERWROTE the outer arm's entry no longer leaks into the continuation after the inner switch (which emitted the inner capture name after its Zig block had closed — `use of undeclared identifier`).
|
||||
- **Else-if chains keep post-if narrowing**: `if (x === null) return -1; else if (flag) { n = 2; } return x.v + n;` — and the else-if-else, chained else-if-else-if, and `!==`-polarity spellings — now narrow `x` after the statement like the plain-else form does; the else-if emission path returned before applying the post-if narrowing, so the fall-through read landed on the still-optional value (`optional type does not support field access`).
|
||||
- **Reassigned `let` bindings never fuse into a `const`**: `let p = next(i); if (p === null) continue; p = { ...p, v: 10 };` no longer fuses the declaration and guard into `const p = next(i) orelse continue;` (Zig: `cannot assign to constant`); the binding stays a `var`, the guard keeps its plain null test, and later reads unwrap the live variable — which the assignment path keeps narrowed across provably non-null writes.
|
||||
- **A branch that widens a narrowed optional stays widened past the merge**: `if (p === null) return -1; if (flag) { p = null; } if (p === null) return 0;` — the branch-exit restore that keeps narrowing CONTAINED (additions inside a branch die at its exit) no longer also resurrects a narrow the branch killed by assigning null (or a fresh optional-returning call); kills now re-apply after every branch, switch-arm, and kind-guard exit and propagate through nested blocks, so the post-merge re-check tests the live value instead of emitting `p.? == null` (Zig: `comparison of 'f64' with null`). The merge is conservative — a kill on any path that can reach the merge drops the narrow, and the re-check tsc demands anyway always compiles.
|
||||
- **Compound-guard branches keep those kills dead too**: `if (r !== null && r > 0) { p = null; }` — where the branch emits under the chain's `.?` substitutions — no longer resurrects p's killed narrow when that substitution scope restores its snapshot (its restore ran after the branch re-applied the kill); the scope now rides the same kill-frame protocol as branch and switch-arm exits, and so does the chain-condition emitter, so every full-map narrowing restore in the emitter re-deletes killed entries on exit.
|
||||
- **A kill on an always-exiting branch stays off the surviving flow**: `if (p.v < 0) { p = null; return -1; } return p.v;` inside a null guard — tsc keeps `p` narrowed at the second return because the killing branch left the function, and the emitter now agrees: a branch that always returns (or throws uncaught) drops its kills at the merge instead of deleting the narrow the surviving read depends on (which emitted field access straight onto the `?P`). Kills on paths that resume inside the function — fall-through arms, `break`/`continue` guards in loops, throws caught by an enclosing `try` — still merge outward and drive the post-merge re-check.
|
||||
- **A guard in a lifted callback covers the trailing return it precedes**: `xs.map((p) => { if (p === null) throw bad; return p.v; })` lifts the callback as its statement prefix plus the trailing return's expression, and the prefix's narrowing scope closed before that expression emitted — the read landed on the raw `?P` (`optional type does not support field access`); prefix and trailing expression now share one flow scope, and the scope still closes before the callback's siblings in the emitted loop body.
|
||||
- **A do-while body guard covers the trailing test it flows into**: `do { if (p === null) return -1; n += p.v; } while (p.v > 0);` — tsc evaluates the condition after the body, under the body's flow state, but the body's narrowing scope closed before the lowered `if (!(cond)) break;` emitted, so the test read the raw `?P` (`optional type does not support field access`); the body and the trailing test now share one narrowing scope, restored at the loop boundary. A guard read only by the test binds its capture too, `continue`-carried kills still widen the hoisted first-pass test onto the live optional, and `break`-carried kills still land only on the post-loop state.
|
||||
- **Canvas-app hosts compile silently without a WebView SDK**: the informational `#pragma message` in the GTK host's WebKitGTK stub path (and its Windows WebView2 twin) is gone — zig renders every clang diagnostic of a failing C compile as `error:`, so on machines where a real, unrelated compile error occurred (for example a too-old GTK), the note itself surfaced as the first build-killing error and masked the actual cause; the stub is the expected state of every canvas app and now compiles with zero diagnostics, while a genuinely misconfigured web build still fails loudly via `#error`.
|
||||
- **GTK host compiles against GLib 2.72**: the host now spells "no application flags" in a way that compiles on GLib older than 2.74, so distros that backport GTK 4.10 onto a GLib 2.72 base (Ubuntu 22.04-derived) build canvas apps out of the box.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @codehz
|
||||
- @nextpointer
|
||||
- @perminder-klair
|
||||
|
||||
## 0.5.3
|
||||
|
||||
### New Features
|
||||
|
||||
- **`<media-surface>` — the dynamic texture channel**: a new element compositing textures produced outside the widget tree (video decoders, camera pipelines, external renderers like mpv) into the layout like any widget, with a stable Zig-tier producer API (`runtime.acquireMediaSurfaceProducer`) pushing RGBA8 frames from any thread — latest-wins, damage-tracked, paced by the compositor's presented-frame clock.
|
||||
- **Pushes wake an idle compositor**: a push staging new bytes requests one coalesced frame through the platform's thread-safe cross-thread frame request (the automation watcher's wake path), so video keeps playing in an idle demand-driven app and a late-starting producer is adopted promptly; damage-skipped and stale-handle pushes wake nothing, and teardown disarms the binding under the same fence the wake call holds, so an orphaned producer can never wake a dead host.
|
||||
- **Markup, both engines, and tooling**: `surface="{binding}"` binds the model-owned u64 surface id in the runtime-image-id grammar (binding-only, required, media-surface-scoped with teaching errors), wired through the validator, the compiled and interpreter engines, `native check`'s model contract, LSP hover docs, and a docs component page plus a "Media Producers" recipe.
|
||||
- **Deterministic by policy**: texture contents are presentation chrome — goldens, reference screenshots, and session fingerprints see only the surface's id-derived placeholder, so a session recorded with a live producer replays fingerprint-identical with no producer attached; live GPU hosts composite the real texture through the existing image upload pipeline.
|
||||
- **Reserved id namespace**: bit 63 of the ImageId space now belongs to media-surface textures; `registerCanvasImage` rejects ids with it set (`error.InvalidImageId`) so producer textures and registered images can never collide.
|
||||
- **Cover fit stays inside the frame**: a `cover`-fit media surface carries the image widget's rectangular clip around its texture draw, so the fit-expanded texture can never paint over siblings on hosts that only mask corner radii.
|
||||
- **Adopted-texture memory is on-demand**: each channel's texture buffer is one lazy frame-budget allocation from the new `Runtime.Options.allocator` at first adoption (freed by the new `Runtime.deinit`), so a runtime with no media producers carries zero media-texture bytes — an embedded pool would have put 32 MiB in every Runtime (measured on the docs wasm preview host: 169.5 MB → 137.5 MB per component tile). The allocator freezes into the runtime at init: mutating `options.allocator` on a live runtime never retargets ownership, so allocations and their frees always pair on one allocator.
|
||||
- **Trackpad pinch reaches apps**: pinch-to-zoom now flows from the macOS host (`magnifyWithEvent:`) through phase-explicit `pinch_begin`/`pinch_change`/`pinch_end` input events into a view-global app channel — Zig cores declare `Options.on_pinch`, TypeScript cores export `pinchMsg(pinch)` with `PinchPhase`/`PinchEvent` in `@native-sdk/core/events`; each change carries a multiplicative delta (cumulative gesture scale is the product of `1 + scale`, applied memorylessly — `zoom *= 1 + scale`) and the pointer anchor rides view-local `x`/`y` (the pointer location during the gesture — zoom-at-cursor anchoring, not a between-the-fingers midpoint); every event names its source window and view (`window_id`/`label` in Zig, `windowId`/`label` in TypeScript), so multi-window apps tell pinches apart; a terminal Ended/Cancelled event that still measured a nonzero delta arrives as one last change before the end, so the product always matches what the OS reported. On macOS the delta is AppKit's raw per-event `NSEvent.magnification`, forwarded untransformed: raw magnification IS the multiplicative per-event delta — the convention every browser engine ships — so the product of `1 + scale` is the zoom users already experience for the same gesture in Safari and Chrome. The one guard is a per-event floor: a single event's magnification at or below -1 (a zoom inverting through zero scale — physically impossible, only a driver glitch could report it) clamps just above -1, so every emitted factor stays positive. Windows precision-touchpad and GTK gesture sources are staged follow-ups.
|
||||
- **`widget-pinch` automation verb**: `native automate widget-pinch <view-label> <scale> [x y]` drives the real pinch event stream without a trackpad (`<scale>` is the gesture's FINAL multiplicative zoom — one change carrying `scale - 1`, anchor point defaulting to the view center), journaled like every synthesized input so recorded sessions replay the identical zoom. Automation protocol bumps to v7.
|
||||
- **Session journal v5**: gpu-surface input records gain the pinch `scale` field and the pinch kinds; readers refuse v4 journals loudly in both directions, per the format's skew discipline — re-record sessions with this build.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Registered CJK fonts render dense glyphs**: glyph outline budgets are now sized from real CJK faces (Noto Sans JP/SC/TC/KR measured; 1024 points / 128 contours per glyph), so everyday dense kanji like 鬱 ink as real outlines instead of notdef blocks; the per-font registration size bound rose to 24 MiB so full CJK faces register.
|
||||
- **Glyph complexity validates at registration, not render**: a face whose `maxp` declares glyphs denser than the outline budgets — simple maxima and flattened-composite maxima (`maxCompositePoints`/`maxCompositeContours`) alike — is refused at registration with a teaching that names its numbers against the budgets (`error.FontExceedsGlyphBudgets`), instead of silently degrading individual glyphs to blocks at render time.
|
||||
- **Font bytes are heap-allocated on demand**: the registry copies each registered file into an exact-size allocation from the runtime's new `Options.allocator` (freed by the new `Runtime.deinit`), so the 24 MiB bound is validation, not a storage reservation — a runtime with no registered fonts carries zero font bytes, where a reservation-shaped pool would have embedded 192 MiB in every Runtime (measured on the docs wasm preview host: 313.5 MB → 121.5 MB per component tile).
|
||||
- **`EmbeddedApp.deinit` completes the embed lifecycle**: direct embedders end an embedded app with `defer embedded.deinit()` (idempotent), which returns the runtime's heap-owned registrations — without it, a host creating and destroying apps in one process leaked the registered font storage per cycle. The wrapper hosts and the C ABI's `native_sdk_app_destroy` route their teardown through the same deinit, one lifecycle owner.
|
||||
- **Glyph raster budgets are derived from the registration gate**: the vector core's glyph-fill capacities (`GlyphRasterizer`: 18,560 edges/crossings, flattening clamped at 16 segments per curve) are computed from the outline budgets registration admits, so a truthfully-declared budget-maximal glyph — a 1024-point zigzag contour included — rasterizes at any size instead of hitting `VectorPathTooComplex` and degrading to the block fallback the gate promises cannot happen; the clamp binds only above ~128-px ems and keeps the polyline within 0.2% of the em, so existing renders are byte-identical.
|
||||
- **Single-line fields never hold or paint line breaks**: pasting multi-line text into an input, text field, search field, or combobox now strips the line breaks at the edit seam (the HTML value-sanitization rule — lines join with nothing between them), covering clipboard paste from the shortcut and the context menu, typed and automation `text_input`, and IME composition, with the app's `on_input` hearing the same sanitized bytes the editor applied; a paste of only newlines inserts nothing.
|
||||
- **Defensive render containment**: a single-line value that still holds a `\n` (a model-set value, an old journal) now paints as one line — breaks present as spaces — under a forced content-rect clip, so text can never escape the field's rounded border on any renderer.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @IFTC-XLKJ
|
||||
- @WhiteHades
|
||||
- @jhodges10
|
||||
|
||||
## 0.5.2
|
||||
|
||||
### New Features
|
||||
|
||||
- **Anchored tooltips with hover intent**: `<tooltip anchor="above|below">` beside its trigger in a stack floats against the trigger and hands visibility to the runtime — it shows after the pointer has rested on the trigger for the show delay (default 600ms) and hides on leave, so sweeping a toolbar flashes nothing; after a pointer-hovered tooltip hides on leave, a shared 400ms warm window shows the next trigger's tooltip instantly (the other hide causes below — focus moving on, Escape, a press, view blur — deliberately open no warm window). Delay, warm window, and the behaviors below match shadcn/ui's defaults (Base UI). The model never hears hover, both engines lower it identically, and every transition steps on the recorded input/frame clock, so recorded hover-dwell sessions replay their show/hide frames byte-identically.
|
||||
- **`tooltip-delay` attribute**: per-tooltip show delay in milliseconds (`0` shows the instant the trigger is hovered); absent follows the new `tooltip_show_delay_ms`/`tooltip_warm_window_ms` metric tokens. Registry attr code 80; static (non-anchored) tooltips keep their classic paint-when-rendered behavior.
|
||||
- **Keyboard focus reveals immediately**: tabbing onto a tooltip-owning trigger shows its tooltip with no dwell (keyboard navigation is deliberate, and content revealed on hover or focus must not depend on pointer timing); focus moving on or Escape hides it just as immediately, without warming the pointer's skip window.
|
||||
- **Hoverable content**: a shown tooltip's own bounds hold it open (WCAG 1.4.13; Base UI's `hoverable` default) — the pointer can cross the anchor gap into the tooltip along a bounded safe-polygon corridor, and it hides only after leaving both the trigger and the tooltip (with the usual warm window); the corridor resolves on the recorded frame clock, so replays stay byte-identical.
|
||||
- **Scroll steps the machine**: every scroll path (wheel, kinetic steps, native drivers, keyboard scrolling) routes its hover change through the same intent transition a pointer move takes — a trigger scrolled out from under the pointer disarms/hides, and a trigger scrolled under it arms per normal.
|
||||
- **Press dismisses**: ANY pointer-down — primary or secondary, including downs consumed by the context-menu gesture or a window-drag region — or Space/Enter on the focused trigger cancels a pending reveal, dismisses a shown tooltip, and closes the warm window, so an activated control never re-explains itself on the post-click hover. Keyboard activation and Escape also spend the standing focus reveal: a rebuild that replaces or rekeys the tooltip (the activation's own model update, typically) cannot resurrect it — it stays down while focus rests on the trigger, until the keyboard genuinely leaves and returns; the focus ring stays painted, and a later pointer hover re-earns the dwell normally.
|
||||
- **Rebuild hygiene**: a rebuild that removes, rekeys, disables, or re-parents a tooltip's trigger resets that tooltip's armed/shown/warm state and re-stamps it hidden, even when the tooltip node itself survives.
|
||||
- **View blur resets**: a canvas view that loses focus (to a sibling view or with the window) drops its entire tooltip conversation — armed delay, shown tooltip (keyboard- and pointer-owned), warm window — and re-stamps hidden, so no stale tooltip floats in a view the keyboard left. The window key-loss reset fires on the flag's own focused→unfocused edge, so it holds however the host announces the change: one gain event (macOS) or loss-before-gain (Windows, GTK), including a loss with no gain at all (every window inactive).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Search empty state names its scope**: the system monitor's no-match state now says search only sees the top 128 processes by CPU, so a miss on a quiet process reads as scope, not absence (both tiers).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Dark-scheme accent focus rings settle down — and stay visible**: a `theme_accent` (or `canvas.accentOverrides`) now derives its dark-appearance focus ring at half the accent's saturation instead of the raw brand hue, contrast-floored at 3:1 (WCAG non-text) against the lightest dark tone controls commonly sit on (the house muted surface `#262626` — rings draw outside controls, so clearing the lightest adjacent container tone clears the page background and card surface too, in both shipped packs) — desaturation alone can cost a deep accent the bar it cleared (`#008000` fell from 3.9:1 to ~2.6:1 on the background; the floor lifts it back over 3:1 on background and card surface alike) — so the soundboard search field's ring no longer glares neon in dark mode; `canvas.accentFocusRing` exports the derivation so hand-authored token sets (the Zig soundboard's theme) state the identical ring.
|
||||
- **Breaking**: `canvas.accentOverrides` now takes the resolved `ColorScheme` alongside the accent — a deliberate break while the toolkit is pre-1.0, so the one function under the natural name states the scheme it layers over; pass `.light` to reproduce the previous output exactly.
|
||||
- **Escape in a search field now reaches your core**: Escape's clear (and its composition cancel) was a runtime-local editor operation — the field emptied on screen while the model kept the stale query and the list stayed filtered. Every keyboard-driven editor mutation now derives ONE edit that is applied to the retained editor AND stamped onto the dispatched event, so `on-input` hears Escape exactly like typing, paste, and the clear affordance — on both authoring tiers, and byte-identically under record→replay.
|
||||
- **Automation and accessibility composition verbs ride the real input path**: `widget-action set_composition/commit_composition/cancel_composition` now dispatch the same ime input events a live IME session produces (journaled, mirrored to the core), and `set_selection` reaches the core's selection mirror through the stamped-edit channel.
|
||||
- **Accessibility actions replay without double-dispatch**: a journaled assistive action (press, toggle, set_text, drag, ...) no longer also journals the key/text events its verb synthesizes — replaying the action re-derives them, so recorded AX-driven sessions replay each input exactly once instead of twice.
|
||||
- **Direct verb calls journal outer-wins too**: an embed host's `widgetAction` and automation `widget_action` commands now record the same single `widget_accessibility_action` record the platform accessibility path does (the enum gained the composition kinds), so replay re-runs the verb — focus included — instead of delivering its untargeted key/ime children to whichever field the session happened to leave focused.
|
||||
- **Grids keep their declared column slots**: children fewer than a grid's declared `columns` now keep the column-slot width and fill the leading slots, instead of stretching across the freed row — a search that narrows the soundboard album grid below its column count leaves image-forward tiles at their natural size.
|
||||
- **"terminate request delivered" retires itself**: the system monitor's delivery notice now clears on the next applied sample instead of sitting in the footer forever; failure notes keep sticking (both tiers).
|
||||
- **System monitor footer says UTC**: the sample-time stamp renders from the journaled clock in UTC, and the footer now labels it "UTC" instead of passing it off as local time — local rendering would need a journaled timezone channel to stay replay-byte-identical, so the label is the honest fix (both the Zig example and the TS port).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @marcusschiesser
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Improvements
|
||||
|
||||
- **The TS scaffold's status bar earns its empty state**: a fresh scaffold said "stamped: -1ms" until the first Stamp press; the template's markup now branches on `{stampedMs < 0}` and says "press Stamp for a timestamp" instead — teaching the if/else markup shape in the starter while it's at it.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Exact-fit text no longer elides under geometry pixel snapping on Windows**: edge snapping rounds a frame's two edges independently, so a hug-sized text box at a fractional position could come back up to a full device pixel narrower than the label it was measured for — past the elision slack, so the TS scaffold's centered counter painted "…" instead of its digit at 100% scale. The wrap/elision budget (`textWrapMaxWidth`) now hands back the full snap quantum (1/scale, was 0.5/scale), and the epsilon policy is documented at the seam: painted width may exceed the snapped frame by less than `1/scale + text_elision_slack`, always below the smallest real overflow (a glyph).
|
||||
- **Spawn teardown crash window closed**: the effects channel now joins every spawn, fetch, and file worker thread that converges before its teardown returns (previously it gave up after ~5s and abandoned them), so cancelling or quitting while a real child is still streaming can no longer leave a stale worker writing into freed memory.
|
||||
- **Spawn cancel reaches the whole process tree**: each spawned child runs in its own process group and cancel/teardown signals the group (POSIX), so shell-wrapped commands (`sh -c "a; b"`) no longer leave orphaned grandchildren holding the stream open past the cancel.
|
||||
- **Every worker class tears down bounded**: spawn, fetch, and file workers now share one terminal guarantee — teardown returns within a budget and never frees memory a live thread can still touch. A file worker stuck in blocking I/O that nothing can converge (a write to a FIFO with no reader, a stalled network filesystem), or a spawn worker held hostage by a descendant that escaped the kill's process group (`setsid` daemonization, a shell's `set -m` background job) while holding the stdout pipe open, no longer hangs teardown: teardown interrupts the blocked syscall best-effort at half its budget and, past the full 15s, abandons the worker with one warning and a small deliberate leak. Everything an abandoned worker can still reach lives in process-lifetime storage, so the leak stays safe even when the app tears down the allocator behind the channel right after.
|
||||
- **A fetch that cannot start cancellably is rejected, never run inline**: when the executor cannot start the exchange as a cancelable task, the fetch now delivers one honest `.rejected` terminal instead of silently running an exchange that would evade `cancel`, the timeout, and teardown's join.
|
||||
- **The npm-installed CLI carries its TypeScript toolchain**: @typescript/typescript6 — and @typescript/old, the exactly-pinned alias of the real compiler its one-line entrypoint re-exports — are now regular dependencies of `@native-sdk/cli`, installed by npm in the same transaction as the CLI itself. The first `native check|dev|build|test` on a TypeScript app needs no network and no install step, and never runs npm: it works offline right after install, on read-only (system-owned) prefixes, and under `NODE_ENV=production` alike, because the bundled transpiler resolves the toolchain by node's own ancestor `node_modules` walk across every layout npm or pnpm produces. A repo checkout whose `packages/core` install hasn't happened yet is taught the one command (`cd <sdk>/packages/core && npm ci --include=dev`), and a direct `zig build` against an unresolvable toolchain fails with the same clean teaching and a resolved SDK path instead of a panic stack trace. TypeScript apps need Node.js 22.15+ (on the 23 line: 23.5+) on every layout — repo checkouts included, because every .ts module rides the same `module.registerHooks` type stripping: on a node without the hook the runner fails fast with a one-line upgrade teaching instead of node's raw `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`.
|
||||
- **Drag headers lay out clear of the Windows caption buttons**: on hidden-titlebar windows, a `window-drag` header whose app never consumed the chrome channel's trailing inset rendered right-aligned content UNDER the DWM min/max/close cluster (system-monitor-ts's status text was truncated by the caption punch-out). The runtime now detects the collision after layout and re-lays the view once with the cluster reserved (`DesignTokens.window_controls`, stamped like `text_measure`), so drag-header content stops at the cluster's edge on every app — markup and builder, Zig and TS — while headers that already pad through the chrome insets (soundboard's trailing spacer) keep a byte-identical layout. The same mechanism covers the macOS mirror (a leading traffic-light cluster). Anchored floating children of the drag header resolve against the cleared rect too, so an end-aligned floater moves out from under the buttons just like flow content; anchored children of non-drag widgets keep byte-identical placement.
|
||||
- **Standard titlebars follow dark mode on Windows**: windows with standard chrome now set `DWMWA_USE_IMMERSIVE_DARK_MODE` from the OS app color scheme — at creation (before first show, so a dark launch never flashes a light caption) and again on every appearance broadcast — so a dark-themed app no longer sits under a glaring white titlebar. Hidden-titlebar windows keep their higher-fidelity pixel-sampled caption color, and chromeless windows have no caption to tint.
|
||||
- **Windows release exes are GUI-subsystem**: `native build` (and therefore everything `native package --target windows` wraps) no longer produces console-subsystem binaries, so launching a packaged app never flashes a terminal window behind it. Debug exes keep the console — the dev loop's logs live there — and redirected logging (`app.exe > log 2>&1`) still works on GUI exes. Packaging now reads the exe's PE subsystem, warns loudly when a console-subsystem binary is wrapped (stale zig-out or hand-supplied `--binary`), and carries the finding in the package stats, pinned by tests over synthetic PE headers. The web-frontend scaffold's standalone `build.zig` (Next/Vite/React/Svelte/Vue and `native init --full`) emits the same release-only assignment, so scaffolded apps get the posture without the SDK build graph.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### New Features
|
||||
|
||||
- **TypeScript authoring — write app cores in TypeScript**: `native init` now scaffolds a TypeScript app by default — `src/core.ts` (logic), `src/app.native` (view), `app.zon` (manifest), zero Zig to write — and the build transpiles the core to readable arena-backed Zig, so the shipped binary carries no JS engine and no GC and keeps the native dispatch path (~83ns per update). The `@native-sdk/core` package (the SDK cores import, plus the transpiler the CLI runs) publishes to npm with this release at the SDK's version. Zig cores remain first-class (`native init --template zig-core`), mixed TS-core + Zig-helper apps fall out of tree detection naturally, and a TypeScript app can eject to its emitted Zig at any time. The entries below are the pieces of this one feature (#119).
|
||||
- **examples/ai-chat-ts — an AI chat client authored entirely in TypeScript + Native markup**: a conversation UI over an OpenAI-compatible chat-completions endpoint as two subset modules and zero Zig — `Cmd.fetch` with routed `{status, body}` results, the JSON wire format as pure byte math (`src/api.ts` encodes requests and parses `choices[0].message.content` / `error.message`, refusing anything malformed), conversation history in the Model, the composer on the SDK byte-splice text engine, endpoint/model/key through the env channel (no baked endpoint, no key anywhere in the tree — a teaching state until all three arrive), a model-level in-flight guard, and honest failed states that keep the history with a Retry. The e2e battery (`tests/ts-core/ai_chat_e2e_tests.zig`, in `zig build test-ts-core-e2e`) pins the exact request bytes turn by turn and replays a recorded conversation — transport failure and retry included — byte-identically with zero network; the README states the v1 boundaries plainly (buffered responses, compile-time fetch headers) (#119).
|
||||
- **Docs: "Where Packages Go"** (`/typescript/packages`): the four first-class patterns behind "can I use npm?" — HTTP/AI APIs through `Cmd.fetch` (with a complete transpile-checked core sample), npm-heavy UIs as embedded web frontends, Node libraries as `Cmd.spawn` sidecars, and pure utilities vendored under `src/` or imported from the curated `@native-sdk/core/*` channel — with the core's no-npm boundary stated as the thing that buys replay, headless testing, and native dispatch speed (#119).
|
||||
- **Eval wave 2 — six dual-track authoring cases, one language-blind spec each**: the eval harness gains `"frontend": "app-dual"` cases that run the SAME realistic ask on both authoring tracks — `<case>@ts` scaffolds a full TypeScript app (`native init --template ts-core`), `<case>@zig` the Zig app template — graded by shared checks plus per-track behavioral harnesses asserting one spec: fetch-JSON-into-a-sortable-table, debounced notes autosave (starter provided), a pomodoro timer with a completion sound, a seeded stale-cache delete bug to root-cause, a module split with byte-exact CSV export, and a shell-command system-info panel. The ts harnesses decode the Cmd/Sub wire format (`evals/harness-lib/cmdview.zig`); the zig harnesses ride the SDK's fake effects executor inside the workspace's own `native test` graph. `--track ts|zig` selects a lane; each track gets its current skills (`ts-core`+`native-ui` vs `native-ui`+`zig`), and `pnpm metrics` now reports per-track teaching-error encounters alongside first-pass compliance and the violation taxonomy (#119).
|
||||
- **Transpiler: two real-app fixes the wave surfaced**: byte-element stores of computed values (`buf[i] = src[j]`) now emit with JS's exact ToUint8 wrap instead of stopping as invalid Zig, and records referenced from a declared text-input mirror union stay by value even when the core also stores its editor state in the Model — previously that pointer promotion silently broke `on-input` resolution (runtime view build and `native check`'s model contract alike) for any core keeping a `TextEditState` in its model (#119).
|
||||
- **examples/soundboard-ts — the soundboard authored entirely in TypeScript + Native markup**: the launch-bar port of the Zig soundboard as three files of truth and zero Zig — `src/core.ts` (the committed catalog as const tables, REAL audio through the `Cmd.audioPlay` stream with the engine's local-then-URL cascade, the play-next queue, Copy Title on the clipboard, a motion-gated `Sub.timer` playback clock, and search through the full byte-splice text engine), `src/app.native` (the whole view: grid, album detail, songs library, context menus, the now-playing transport), and `app.zon`. An end-to-end suite (`zig build test-ts-core-e2e`) drives the shipping core and markup through playback, auto-advance, the stale-event window, volume, clipboard, search, record→replay, and a dispatch-latency budget; the README carries an honest ledger of where the port diverges from the Zig original (#119).
|
||||
- **Generated TS wiring resolves the theme and the audio cache**: app.zon's `.theme` pack now reaches TypeScript apps (composed with the live system appearance), and the platform caches directory is resolved at launch so a core's URL audio playback caches under the conventional content-addressed path with no `cachePath` in the core (#119).
|
||||
- **Transpiler emit-contract fixes**: the global `undefined` VALUE now emits the optional empty (`null`) — it previously emitted Zig `undefined`, uninitialized memory — and an early-exit null guard whose narrowed value goes unread emits a plain null test instead of an unused (uncompilable) capture (#119).
|
||||
- **examples/system-monitor-ts — the system monitor authored entirely in TypeScript + Native markup**: the spawn-showcase port of the Zig system monitor as three files of truth and zero Zig — `src/core.ts` (the 2 s sampling cadence as a declarative `Sub.timer`, collect-mode `Cmd.spawn` for `ps`/`vm_stat`/`meminfo`, pure byte parsers over the collected stdout, the exact top-128-by-CPU selection, the confirmed SIGTERM action, and a runtime boot probe that discovers the host's sampler conventions where the Zig original switches at comptime — with the honest "no sampler for this OS" state when nothing answers), `src/app.native` (the whole view: chromeMsg-driven hidden-inset header, `<chart>` sparklines over the core's NaN-padded windows, the real table register with per-row context menus and controlled scroll, the modal confirmation), and `app.zon`. An end-to-end suite (`zig build test-ts-core-e2e`) drives the shipping core and markup through the probe cascade, the Zig example's committed real captures, the timer cadence with pause, search/sort, the kill round trip, and record→replay; the README carries an honest ledger of where the port diverges from the Zig original (#119).
|
||||
- **Markup chart series bind f64 iterables**: `<series values="{binding}">` now accepts `[]const f64` model fields, decls, and fns alongside f32 — transpiled TS cores carry every number array as f64, so markup charts were unreachable from a TS model — narrowed per sample into the f32 chart pipeline in both engines, the validator, and `native check`'s model contract (#119).
|
||||
- **The everyday string methods on core bytes — familiar spellings, byte-honest semantics**: TypeScript cores can now write `s.toUpperCase()`, `s.trim().toLowerCase().includes(q)`, `s.repeat(n)`, `s.padStart(w)`, `s.split(sep)`, `s.startsWith(p)`, `s.indexOf(t)`/`s.lastIndexOf(t)`, and `s.at(i)` directly on `Uint8Array` text. Every length/offset is a BYTE measure, search is byte-wise (`includes`/`indexOf`/`lastIndexOf` dispatch by argument type: bytes needle = substring search, number = the TypedArray element search), case mapping is Unicode 17 SIMPLE case mapping (locale-free, generated tables — 3.1 KiB in the binary — with invalid UTF-8 passing through unchanged), trim strips the exact JS whitespace set over UTF-8, and `split` returns a locally-owned `Uint8Array[]`. Native lowers each call onto rt kernel helpers; node runs the same methods from the same generated tables (devhost polyfill), so both runtimes produce identical bytes by construction — pinned by run-fidelity cases across Greek/Cyrillic casing, growth mappings, invalid-UTF-8 passthrough, repeat/pad edges, and split shapes, plus a machine-checked method matrix (#119).
|
||||
- **The stays-out spellings teach their reason**: `charCodeAt`/`charAt`/`codePointAt`/`normalize`/`replace`/`replaceAll` on bytes teach the new NS1060 (byte text speaks the byte-honest method set), the locale family teaches NS1005, and the regex-taking methods teach NS1040 — never a bare "property does not exist". `trimAsciiSpaces` stays for LF-preserving line parsing; `.trim()` is the canonical whitespace trim (#119).
|
||||
- **TS cores: generics, local function values, and the complete-language tail**: user-declared generic functions, interfaces, and type aliases now compile via per-call-site monomorphization from tsc's own resolved type arguments — one readable Zig fn per distinct instantiation (`pick__Task`, `pick__f64`), deduped, covering records/unions/arrays/optionals, recursion inside a generic, generics calling generics, and structural instantiation of generic types (`Box<Task>` → `Box__Task`); unresolvable call sites teach the new NS1053 (#119).
|
||||
- **Const-bound local function values hoist**: `const scale = (x: number): number => x * 3;` (arrow or function expression) becomes an ordinary module-level fn when capture-free and fully annotated, usable by direct call (recursion included) or as an array-method callback (`xs.map(scale)`); captures, reassignment, storing/returning the value, and record-field calls teach the new NS1054 — and capturing a locally-owned array ends its ownership at the capture (NS1051 machinery) (#119).
|
||||
- **The small-fry tail lands with node-byte-identical pins**: `for (const [i, x] of xs.entries())` (the destructured-pair loop form), `++`/`--`/assignments in provably order-exact value positions (`arr[i++]`, `const n = ++count`), `?.[i]` and `?.method()` optional-chain hops on supported receivers, plain `number`/`string` switch scrutinees (if/else chains with JS strict-equality and default-position semantics), and `typeof CONST` type-query aliases. Float-valued template holes stay deferred — there is no JS-exact f64 formatter in the runtime yet, and node/native divergence is never an option (#119).
|
||||
- **TS cores: exceptions and data classes — the complete language, tier 2**: `throw`/`try`/`catch`/`finally` compile as deterministic control flow — a thrown subset value (one error shape per core, the new NS1057) unwinds through a native payload slot to the nearest catch, across helper calls and out of `.map`/`.filter` callbacks; `finally` lowers to a scoped defer running on every exit (control flow inside it teaches the new NS1058, JS's own no-unsafe-finally), the catch binding narrows once (`const err = e as ParseError;`), rethrow and nested try work, and an uncaught throw is a defined panic at the exported boundary — exactly where node's process would crash. Node-parity is pinned in the run-fidelity corpus, throw-mid-mutation of an owned array included (#119).
|
||||
- **Data classes, no inheritance**: `class Task { fields; constructor; methods }` emits as a plain struct plus module-level functions; `new Task(...)` constructs a record-shaped value, `this` reaches fields and methods, and instance mutation (field writes, `this`-writing methods) follows the same local-ownership rule as arrays (NS1001/NS1051 at the boundaries). The class tail teaches by name: `extends`/`super`/`abstract` (new NS1055), accessors/statics/privates/`#`/class expressions/escaping `this` (new NS1056), generic classes (NS1053), `instanceof` (NS1041); class instances stay local values — Model storage teaches toward records (flagged follow-up) (#119).
|
||||
- **Two reconcile potholes**: arrays OF byte buffers (`const parts: Uint8Array[] = []`) now route through array ownership — `parts.push(chunk)` on your own array works and runs node-identically (bytes VALUES keep their own discipline) — and a bare reference to a module-level function or const helper is a callback (`xs.map(encodeTurn)`, `xs.toSorted(byAscending)` — the same lowering as the arrow spelled inline), plus shorthand members in union literals (`{ kind: "range", v }`) (#119).
|
||||
- **TypeScript apps are the `native init` default**: the scaffold is three files of truth and zero Zig — `src/core.ts` (logic), `src/app.native` (view), `app.zon` (manifest) — and the build detects the core from the tree (a `src/core.ts` transpiles at build time through generated wiring; `src/main.zig` stays the Zig core; both at once is a teaching error). The Zig template remains first-class via `native init --template zig-core` (#119).
|
||||
- **`native dev --core`**: the TypeScript core-logic loop under node — dispatch Msgs as JSON lines, watch the model and effect transcript, run Sub timers and Cmd.delay on a virtual clock. Logic only, honestly not a renderer; `native dev` runs the real app (#119).
|
||||
- **`native check` checks TypeScript cores**: a `src/core.ts` runs the @native-sdk/core subset checker first (NS diagnostics verbatim), then markup and app.zon as before — with a fresh model contract the markup pass type-checks bindings against the core's emitted model (#119).
|
||||
- **Markup text input reaches TypeScript cores**: a core declares its own `TextInputEvent` mirror union and `on-input` matches it structurally, translating each runtime event into the core's union at dispatch — markup text field to TS `update` to re-render, end to end (#119).
|
||||
- **Exported model helpers bind from markup**: an exported single-Model-parameter helper also emits as a Model declaration (`doneCount` binds as `{done_count}`), and `export const viewUnbound = [...]` emits the `view_unbound` lint opt-out — NS1031/NS1032 teach the collision and typo cases (#119).
|
||||
- **TS cores: runtime fetch header values and journaled env deliveries** — the two gaps the ai-chat example surfaced, closed. `Cmd.fetch` header VALUES may now be runtime bytes (`{ authorization: bearerToken(model.apiKey) }` — header NAMES stay compile-time ASCII, NS1029/NS1030 bounds still gate what is knowable at build time and the engine err-arm rejects the rest); no wire change was needed — record 0x09 always carried values as length-prefixed byte fields built at dispatch time, only the emitter's literal-only rule and the node SDK's `FetchSpec` type moved. And the `envMsgs` channel now journals each launch delivery (an additive `.env` effect record: value in `payload`, arm name in `stderr_tail`, dispatch index in `key`), so replay feeds the RECORDED values with zero env reads — a session recorded with credentials set replays byte-identically on a machine where they are unset or different; journals without env records (older recordings) re-derive from the launch configuration exactly as before. `examples/ai-chat-ts` now sends a real `Authorization: Bearer <key>` header instead of the access_token query-parameter workaround, and its replay e2e drives both the unset-env and changed-env launches. Plus NS1051: an un-annotated spread local (`const turns = [...model.turns, next]`) now teaches its array-type annotation instead of the generic emit-time stop (#119).
|
||||
- **TypeScript subset: grammar completeness**: the subset now means "TypeScript minus the ecosystem minus the purity violations" — never minus basic syntax. New in the mapping: `do...while` (body-first, `continue` re-tests, exactly node), labeled statements with labeled `break`/`continue` (loops and blocks, lowered to Zig labels), `default` arms on union-`kind` switches (the `else` prong over unnamed arms; dead-code defaults emit nothing), `**`/`**=` (JS pow corners pinned: NaN exponents, `±1 ** ±Infinity`, right-associativity), the shifts `<< >> >>>` and `~` (ToInt32 with the count masked & 31), the full compound-assignment family (`*= /= %= &= |= ^= <<= >>= >>>=` plus guarded `&&= ||= ??=`), unary `+`, const record destructuring (`const { total, done: doneCount } = stats;`), namespace imports over the core's own modules (`import * as util` — values, calls, and qualified types resolve to the flat emitted names), multi-counter for-inits and comma incrementors (`for (let lo = 0, hi = n; lo < hi; lo++, hi--)`), countdown incrementors (`i--`), hole-free template literals, `satisfies`, and the empty statement (#119).
|
||||
- **Every exclusion now teaches**: twelve new rules close every generic-error hole — NS1039 (namespace aliases are dot-syntax, SDK intrinsics import by name), NS1040 (regexes), NS1041 (runtime type/shape tests: `typeof`/`in`/`instanceof`/`Object.*`), NS1042 (generators), NS1043 (comma/`void`/assignment-as-value), NS1044 (BigInt/Symbol), NS1045 (destructuring beyond const record fields), NS1046 (nested/stored function values and `?.()`), NS1047 (default exports, `export =`, value re-exports), NS1048 (loose `==`), NS1049 (`var`), NS1050 (generic declarations) — and NS1019 broadens to the full fixed-arity story (rest params, `arguments`, call spreads). `var` and generic helpers previously emitted broken Zig silently; both now stop with teachings, and same-file type/value homonyms are caught by NS1038 instead of colliding in the emitted module (#119).
|
||||
- **The grammar matrix**: a new machine-checked suite (`packages/core/test/grammar_matrix.test.ts`) enumerates every statement, operator, expression, and declaration form of the language and pins each to its verdict (SUPPORTED emits and compiles under Zig; BANNED names its teaching rule; tsc-rejected stays tsc's), so the grammar can never grow a silent gap again. New run-fidelity corpora pin the node-vs-native behavior of every new mapping (pow corners, shift wrapping, `-0` preservation, do-while ordering, labeled-jump targets, default-arm matching, destructured aliases, `??=` on 0-vs-null) (#119).
|
||||
- **Inference fixes surfaced by the matrix**: the float-demotion fixpoint no longer terminates one pass early (a two-hop chain — field → destructured alias → local — previously stranded a phantom NS1016 conflict), and shorthand `{ x }` properties now wire the VALUE symbol into inference (a shorthand from a host-boundary parameter previously kept a false integer proof and emitted mismatched Zig) (#119).
|
||||
- **Stock-IDE support for TypeScript apps, working before the npm publish**: `native init` scaffolds `package.json` (the app's name plus an exact `@native-sdk/core` pin) and `tsconfig.json` (the checker's own compiler options, so editor errors match `native check` reality), and the CLI materializes `node_modules/@native-sdk/core` itself — a copy of exactly what the published package will contain — so VS Code et al. resolve `@native-sdk/core` and `@native-sdk/core/text` with full IntelliSense today. `native check|dev|build|test` keep the copy fresh (one info line on refresh) and `native doctor` reports version skew; once the package is on npm, a plain `npm install` writes identical content and the CLI recognizes the version and leaves it alone. None of it is build truth: builds transpile against the SDK checkout and work with node_modules deleted, tree detection still keys on `src/core.ts` alone, and the Zig template is untouched (#119).
|
||||
- **@native-sdk/core is publish-shaped**: `files: ["sdk"]`, a typed exports map for `.` and `./text`, no bin and no runtime dependencies (the transpiler dependency moved to devDependencies), with a package test pinning the manifest shape. The soundboard-ts and system-monitor-ts example ports carry the same editor surface, and a ts-core e2e suite proves the whole contract with the real tsc: fresh scaffold and both ports typecheck with zero injected paths, and the transpiler still takes the core clean after `rm -rf node_modules` (#119).
|
||||
- **TS cores: mutating array methods are legal on locally-owned arrays**: an array your function creates (a literal or a `.slice()`/`.map()`/`.filter()`/`.concat()`/`.toSorted()` copy) now takes the full mutating set — `push` (any seed, not just the empty builder), `pop`/`shift` (returning the find-miss optional), `unshift`, `splice` (JS index clamping, the removed array as its value), `reverse`, `fill`, in-place `sort` (the stable toSorted machinery applied in place), and indexed writes — with node-byte-identical semantics pinned by a new run-fidelity corpus (parser stacks, splice corners, shift/unshift order, sort stability, pop-on-empty) and a machine-checked mutation matrix beside the grammar matrix (#119).
|
||||
- **Teaching errors now fire only at the true semantic boundaries**: shared data keeps NS1001/NS1022 (both rewritten around ownership, NS1022 naming the now-legal `const copy = xs.slice(); copy.sort(cmp);` idiom), and the new NS1051 teaches mutation after an escape — returned from a callback, passed to a call, stored, or aliased — with the escape kind and line named; an escape inside a loop gates the whole loop body, and early-exit returns stay legal (#119).
|
||||
- **TypeScript cores reach the full app surface**: markup sliders and split dividers deliver their applied 0..1 fraction to a core's one-number float arm (`on-change="scrubbed"` — scrub-to-seek from markup), controlled scroll round-trips through a core-declared `ScrollState` mirror, and the generated wiring detects five host-event channels from plain exports — `frameMsg` (presented frames), `keyMsg` (the app-level key fallback), `appearanceMsg` and `chromeMsg` (system appearance and hidden-titlebar geometry as Msg arms), and `envMsgs` (launch environment variables as journaled boot Msgs) — plus `app.zon` `.assets.images` registered at launch as the `ImageId`s markup avatars bind (#119).
|
||||
- **Export lists and value re-exports compile in TypeScript cores**: `export { a, b as c }` and `export { x } from "./m.ts"` now bind real names over existing declarations in the flat emitted namespace — un-renamed entries export the declaration itself, renames emit a `pub const c = a;` alias, and re-export chains resolve end to end (node ≡ native, pinned by run-fidelity). Renamed entry-module helpers join the markup binding surface under their exported names; NS1047 narrows to the genuinely unsound tail (`export default`, `export =`, `export * from`, renamed generics/classes, wiring config, SDK re-exports), and NS1014/NS1038 keep entry points and name uniqueness honest across the new forms (#119).
|
||||
- **Heterogeneous throws with a narrowing catch**: a core may now throw several distinct kind-tagged shapes — the checker collects them into an implicit thrown union (or a declared union whose arms match), the payload slot is that union, and `catch (e)` narrows it with plain kind tests (`if (e.kind === "parse")`) with no `as` ceremony; rethrow re-raises the bound value, narrowing works across callback boundaries, and NS1057 narrows to genuinely unsound throws (untagged values in a mix, tag collisions, untyped escapes, `new Error`). All pinned node ≡ native by run-fidelity (#119).
|
||||
- **Class statics and erased privacy**: `static` methods lower to receiver-less module functions under the class's mangled names (`Task.fromRow(...)` -> `Task__fromRow`), `static readonly` fields with initializers become module consts (`Task.LIMIT`), and `private`/`protected` keywords are accepted and erased (tsc enforces them at the type level — their whole meaning). Mutable statics teach NS1010 (module state), `this` inside a static member teaches toward the class name, and `#`-fields stay taught; NS1056 narrows accordingly. Pinned node ≡ native by run-fidelity (#119).
|
||||
- **Three mutation loosenings**: `xs[xs.length] = v` on an owned array is a push (the one growth shape; compound forms stay taught), a reassigned `let` stays owned when every assignment installs a fresh owning construction (each reassignment resets the emitted builder), and passing an owned array into a `readonly T[]` parameter is a BORROW when the callee provably only reads it (coinductive analysis — recursion over borrowed slices stays legal; returns, stores, casts to mutable, and onward passes into mutable positions still end ownership). NS1001/NS1051 copy updated; mutation matrix rows moved and extended; all pinned node ≡ native (#119).
|
||||
- **Accurate teaching for the Array statics (NS1059)**: `Array.from`/`Array.of`/`Array.fromAsync` now teach their own construction rewrites (the literal, the spread copy, the push-builder loop) instead of the generic runtime-shapes copy; `Array.isArray` keeps NS1041 — it really is a runtime type test. Comma expressions in value position stay taught (NS1043): the split-statement lowering is only JS-order-exact in the pinned positions, so they did not fall out of the machinery for free (#119).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **The components reference is markup-first**: every component page leads with its Native markup sample (all fences validated against the live `native markup check`), interactive samples show the core side in both authoring languages behind the TS | Zig toggle (accordion, checkbox, radio, dialog, input, scroll, select, slider, split, chart), and the builder form moves to a consistent "Programmatic construction (Zig)" section at the end of each page — real API docs, framed as the Zig tier's programmatic alternative. The section index tells the one-language story, and four samples that shipped unnamed text controls now carry accessible names (#119).
|
||||
- **The TypeScript authoring package is `@native-sdk/core`**: the transpiler package moved from `packages/ts-app-core` to `packages/core` under its real npm name — cores were already importing `@native-sdk/core`, and the dev-harness resolver now maps that one specifier straight onto the package's own SDK module (#119).
|
||||
- **TS-first docs with a TS | Zig toggle**: code samples on the flagship pages (Quick Start, App Model, Native UI, State & Data Flow) show TypeScript first with the Zig form one tab away — the reader's language choice is remembered site-wide — and the new [TypeScript Cores](https://native-sdk.dev/typescript) page covers the app-core subset, Cmd/Sub effects, text-is-bytes, the node dev loop, capacity knobs, and the eject story. Toolkit-extension pages stay Zig on purpose: that tier is the machinery itself (#119).
|
||||
- **Docs code presentation**: the TypeScript | Zig code toggle now renders as the same segmented pill control as the component previews' Default | Geist theme-pack toggle (one shared primitive, identical in dark mode), and code samples can carry a filename header — a file glyph plus the path (```` ```ts:src/core.ts ````), integrated into the toggle's header bar opposite the segments — applied across the quick-start, TypeScript, app-model, native-ui, and config pages' complete-file samples; copied markdown renders the path as a labeled line above a plain fence (#119).
|
||||
- **Markup binds your model's field names exactly as you wrote them**: TypeScript cores now emit Zig with the TS spellings intact — fields, exported single-model helpers, Msg payload records, and locals alike — so `nextId` binds as `{nextId}` and `doneCount` as `{doneCount}`, ending the dual-naming rule (camelCase in core.ts, snake_case in app.native) that every author had to hold in their head. Zig cores are untouched: their fields were already the names markup binds. The whole pipeline follows from the one change — the model contract, `native check`'s typed pass, both markup engines, hot reload, and the eject story (the emitted module now mirrors your source, and markup keeps binding the same names after you adopt it) (#119).
|
||||
- The TS-track host surfaces that matched emitted names structurally now speak the TS SDK spellings (`timestampMs`/`intervalMs`, `colorScheme`/`reduceMotion`/`highContrast`, `tabsProjected`, the audio arm's `positionMs`/`durationMs`), and the declared scroll-state mirror accepts the canvas spelling or the TS spelling — never a mix (#119).
|
||||
- NS1031 collisions are now exact-name collisions (`doneCount` the helper vs `doneCount` the field); `viewUnbound` entries were already the TS names and stay so. Scaffold templates, both ports' views, the ai-chat view, docs, and the skill teach the one rule (#119).
|
||||
- **zig-core starter parity**: `native init --template zig-core` now scaffolds the same app as the TypeScript template — counter, a ticking switch driving a repeating 1s `fx.startTimer`, a Stamp button reading the journaled clock (`fx.wallMs`), a bindable `total` helper, and the matching markup — with generated full-loop tests covering the timer and clock seams; the quick-start code toggle now shows both starters verbatim (#119).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Packaging fails loudly when signing fails**: `native package --signing adhoc|identity` no longer exits 0 while shipping an unsigned bundle — output paths with spaces (`--output "My App.app"`) now sign correctly (the signing pipeline execs argv arrays instead of shell strings, which also unbreaks spaced paths and spaced identities in the notarization helpers), every signed bundle must pass `codesign --verify --deep --strict` before packaging succeeds, any codesign failure stops the package with codesign's own reason, and the package report proves the outcome with a `signing: adhoc (signed, verified)` line (#118).
|
||||
- **Per-thread memory no longer scales with the canvas scratch**: the render planner's fixed scratch buffers lived in static thread-local storage, so on Windows every thread the process spawned (window host, COM, accessibility, workers) privately committed a full ~6.5 MB copy — most of a small app's working set. The scratch now allocates lazily on the one thread that actually plans frames: a scaffolded counter app's private working set drops ~4x, its `.tls` section shrinks from ~6.5 MB to under 200 bytes, and the executable itself is ~6.5 MB smaller. Linux and macOS binaries shed the same per-thread TLS block (#117).
|
||||
- **Transpiler: a ternary initializer under null-guard fusion parenthesizes**: `const x = c ? f(a) : g(a); if (x === null) <exit>` lowers to Zig's `orelse` fusion, and the conditional's if/else expression now wraps in parens — bare, the `orelse` bound to the ELSE arm alone (a type error at best, the wrong value at worst). The same guard covers `??` over a ternary left side; pinned in the conformance corpus and the node/native run-fidelity corpus (#119).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @SunkenInTime
|
||||
- @sepehr-safari
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### New Features
|
||||
|
||||
- **Native-only host builds (Windows)**: the build graph now infers web use from app.zon — a `.frontend` block, the `"webview"` capability, a `.shell` webview view, or the Chromium engine — and an app that declares none of them compiles its Windows host without the embedded WebView layer: no WebView2 header, no `WebView2Loader.dll` installed, staged, or referenced by the executable. A new `.webview_layer = "auto"|"include"|"exclude"` manifest field (and `-Dweb-layer`) overrides the inference, an exclude that contradicts a web declaration is rejected at validate, configure, and package time, and a native-only build that reaches webview creation at runtime fails fast with a teaching `WebViewLayerNotBuilt` error. `native check` and the package report print the web-layer verdict, and a CI cross-audit asserts the presence/absence of the loader reference in real cross-compiled executables (#107).
|
||||
- **Native-only host builds (Linux)**: the WebKitGTK compile seam mirrors the Windows one — an app whose app.zon declares no web use compiles its GTK host with `NATIVE_SDK_ALLOW_WEBKITGTK_STUB`, so the executable neither links `webkitgtk-6.0` nor references any `webkit_`/`jsc_` symbol, building needs no WebKitGTK development package, and users need no `libwebkitgtk` at runtime. The web-layer auditor (`tools/audit_web_layer.zig`) grew a hand-rolled ELF reader (DT_NEEDED entries + dynamic symbols) that CI runs both ways: the native-only fixture must scan clean even with the dev package installed, and the Linux canvas smoke now builds on a runner without WebKitGTK at all. `native package` refuses to package a WebKitGTK-linking binary under a native-only decision, record→replay and automation-driven sessions are pinned on native-only apps, and the macOS GPU dashboard smoke asserts a native-only app spawns zero WebKit helper processes (#110).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Zig 0.16 guidance**: a new `zig` skill (`native skills get zig`) maps each pre-0.16 std idiom's compile error to the current one — `std.Io` file IO and writers, unmanaged `ArrayList`, `main(std.process.Init)`, spawning, clocks, `{t}`/`{f}` formatting, `build.zig` modules — with the same content for humans as the docs' Zig 0.16 Notes page; the native-ui skill carries the short table, and a failing `native build|test|dev` now points at the catalog when std members come up missing (#105).
|
||||
- **Lazy Linux WebView startup**: GTK windows now create only GTK chrome at window creation and materialize the main `WebKitWebView` on first web use, so canvas-only apps do not start WebKit processes on Linux; child-WebView bridge responses no longer require a main WebView to exist (#106).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @WhiteHades
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Linear-light edge blending**: anti-aliased fringes on opaque rounded rectangles, path fills, and strokes now composite through a linear-light coverage path, removing the dark rims that sRGB blending produced on curved geometry while keeping opaque interiors, glyph coverage, and translucent overlays byte-identical (#89).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Single-line fields handle overflowing values**: text, selection rects, composition underlines, and the caret now clip to the field's content rect, and a horizontal scroll offset keeps the caret visible — typing past the edge scrolls the value, Home scrolls back, and deleting never leaves trailing emptiness. Covers text fields, inputs, search fields, and comboboxes; values that fit render exactly as before (#90).
|
||||
- **Cross-drive apps on Windows**: `native dev|build|test` no longer fails with `expected path relative to build root; found absolute path` when the app and the npm-installed SDK live on different drives — the generated build graph now bridges volumes with a `.native/sdk` directory junction (no admin rights needed) and keeps the zon dependency relative; the junction is retargeted automatically when the SDK moves or upgrades. Where the bridge cannot apply (`native eject`, full-shape `native init`, or a filesystem that refuses junctions), the CLI explains the cross-volume constraint and both ways out instead of writing a build Zig would reject (#92).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @fleeting-zone
|
||||
- @kvnwdev
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Windows rendering is DPI-aware and sharper**: Windows apps now declare Per-Monitor V2 DPI awareness, each window carries its own device scale, and canvases, native child views, hidden-titlebar sizing, and explicit WebView frames re-render/re-round correctly when moved across mixed-DPI monitors (#81).
|
||||
- **Smoother canvas geometry**: rounded-rect fills and strokes now render through continuous coverage while eligible hairline borders snap to crisp device-pixel columns, so arcs stay anti-aliased and 1px borders stay sharp under the default house and Geist packs (#81).
|
||||
- **Canonical package and documentation metadata**: npm package metadata, release automation, docs, templates, and examples now point at the renamed `vercel-labs/native` repository and `native-sdk.dev`; `version:sync` stamps repository/homepage metadata into platform packages and `version:check` rejects drift before publish (#78, #80).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Windows embedded WebView is real from a plain checkout**: the WebView2 SDK header and loader are vendored under `third_party/webview2/` (BSD-licensed), every build graph puts the header on the include path, and the host now refuses to compile with the WebView layer silently stubbed — previously every Windows build shipped the stub and WebView loads reported `WebViewNotFound` at runtime (#86).
|
||||
- **WebView2 host conformance fixes**: a missing lambda capture in the bridge message handler, a mingw-compatible WRL event-handler factory, an `EventToken` shim, and STA COM initialization on the host thread let WebView2 environment creation and bridge messaging run on Windows (#86).
|
||||
- **WebView2Loader.dll ships with the app**: `zig build` installs the architecture's loader next to the executable, `zig build run` resolves it during dev runs, generated frontend/package commands carry `NATIVE_SDK_PATH`, and `native package --target windows` includes the loader in the artifact (the Evergreen WebView2 runtime itself is preinstalled on current Windows) (#86).
|
||||
- **Checkbox marks use the vector core**: checked boxes now draw one stroked polyline with round caps and joins instead of two aliased diagonal lines, and stroke caps ride the GPU packet path so the host and reference renderer agree (#87).
|
||||
- **Path geometry lifetimes are owned by the builder**: chart, spinner, and checkbox path commands no longer borrow threadlocal frame scratch, so separately emitted trees cannot alias each other's path elements (#87).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **npm package assets**: Ship the SDK's root `assets/` directory in `@native-sdk/cli` so installed packages include `assets/native-sdk.manifest`, the default macOS icon, and entitlements needed by generated apps (#72, #75).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
- @lzitser23
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### New Features
|
||||
|
||||
- **zero-native is now the Native SDK**: The toolkit, CLI, and packages are renamed end to end — the CLI binary is `native`, the Zig module and build helper are `native_sdk` (`native_sdk.addApp`, `native_sdk.addMobileLib`), the embed C ABI prefix is `native_sdk_*`, and the npm CLI package is `@native-sdk/cli`.
|
||||
- **Native-rendered apps by default**: `native init` scaffolds a native-rendered app — a declarative `.native` markup view plus Zig logic on the `UiApp` runtime (a `Model`, a `Msg` union, `update`, and a view) — with web frontends still available via `--frontend next|vite|react|svelte|vue`.
|
||||
- Native markup: HTML-inspired views with flex layout, `{bindings}` to model fields and functions, typed `on-*` message dispatch, `for`/`if`/`else` structure tags (multi-child `for` bodies, `<else>` empty states), and keyed identity; a deliberately closed grammar keeps logic in Zig.
|
||||
- Comptime compilation: views compile at build time into direct field access — release binaries carry no parser, and markup or binding mistakes are compile errors with line and column.
|
||||
- Hot reload: dev builds watch every `.native` file — imported components and fragments embedded in Zig views included — and update the running window in place, preserving model state, selection, and widget identity.
|
||||
- Expressions in bindings: arithmetic, comparisons, boolean logic, string concatenation, and a closed 17-function formatting library (`fixed`, `thousands`, `date`/`time`, `pad`, `plural`, ...), evaluated bit-identically by both markup engines; string-producing model functions bind directly through the build arena.
|
||||
- Cross-file components: `<import>` splices template files (transitively, with cycle and duplicate diagnostics), template args take literal defaults, `<slot/>` marks where use-site children land, and `native eject component` transfers a library composite's canonical source into your app exactly once.
|
||||
- `canvas.Ui`, the programmatic builder under the markup: structural widget identity, typed message handlers, flex-first layout, and per-element `opacity`/`transform` render channels for animated composition.
|
||||
- **The model–view contract, checked in both directions**: `native check` verifies every binding path, iterable, key, message tag, payload type, and expression in every `.native` file against the app's reflected `Model`/`Msg` surface in milliseconds — with did-you-mean suggestions and a dead-state lint for model fields and messages no view uses.
|
||||
- **Markup tooling**: `native markup check` (instant validation with positions), a language server (diagnostics, completion, hover), a TextMate grammar with editor setup, `native markup dump` over the canonical serialized document format, and the `native-ui` agent skill — the complete authoring reference, served through the skills CLI.
|
||||
- **Two-way tooling**: `native automate provenance` reports where a live widget was authored (file, byte span, template instantiation chain), and `native automate edit` writes minimal-diff attribute and text edits back into the markup source — validated before anything touches the file, with hot reload closing the loop.
|
||||
- **Full component catalog**: every built-in component is expressible in markup — tabs, tables, dialogs, drawers, sheets, selects, comboboxes, accordions, menus, badges, avatars, tooltips, inputs, and more — implemented in both engines with parity tests, alongside new composites in markup and Zig:
|
||||
- Charts (`<chart>` / `ui.chart`): line, area, bar, and band series drawn through the vector path pipeline with design-token colors, deterministic downsampling past 256 points, axis labels on a nice-step lattice, and pointer hover details.
|
||||
- Markdown (`<markdown>` / `native_sdk.markdown`): a GitHub-flavored subset — headings, inline styles, links, lists, task lists, fenced code, blockquotes, pipe tables, autolinks, and model-driven collapsibles — that degrades malformed input to text and never fails a build.
|
||||
- Disclosure trees with the full ARIA tree keymap, steppers and timeline items, input groups with focus-within rings, chat bubbles with reaction pills and thread-width caps, and a `ui.nav` push/pop page container with stable per-page state.
|
||||
- Resizable split panes with model-owned fractions, keyboard and assistive resize, and optional eased animation on model-driven moves.
|
||||
- Windowed virtual lists: viewport-sized widget budgets at 100,000 items, variable row extents that converge to measured truth without visible jumps, tail anchoring for chat transcripts, and `on_reach_end`/`on_reach_start` for infinite fetch and history loading.
|
||||
- Anchored floating surfaces (dropdowns, selects, popovers) that float above the tree with edge auto-flip; dismissal (Escape, click-outside, assistive dismiss) is a Msg the model owns, and focused selects get the full open/navigate/commit keymap.
|
||||
- Vector icons: an SVG stroke-icon subset parser, 50 curated built-in icons, leading or trailing icon slots on buttons, toggle chips, list and menu rows, badges, and timeline items, app-registered icons comptime-parsed from your own SVGs, model-bound icon names, and a loud missing-icon fallback.
|
||||
- **Text engine**:
|
||||
- Inline styled spans — weight (resolved to real faces), italic, monospace, color tokens, underline, strikethrough, size scale, per-span backgrounds, and hit-testable links — wrap as one paragraph in Zig and markup alike.
|
||||
- Honest single-line text: unwrapped text elides with a trailing ellipsis by default, an `overflow` policy knob keeps the deliberate hard cut available, and word wrap is an explicit opt-in — paint always agrees with measurement.
|
||||
- `heading`/`display` typography rungs on the token ladder, first-class text alignment, and fixed grid column counts.
|
||||
- **Selection and clipboard**: cmd/ctrl+C/X/V in editable fields through the platform clipboard, click-drag selection with copy on static text (surviving rebuilds, exposed to semantics and automation), and clipboard effects for app code.
|
||||
- **Interaction model**:
|
||||
- Presses fall through to the nearest pressable ancestor, so any element with a handler is a real hit target — nested pressables resolve to the deepest one, and text selection still works inside pressable rows.
|
||||
- Press-and-hold, double-click, Enter as a list row's primary action, and an app-level key fallback (`Options.on_key`) with pinned precedence — quiet list rows stay transparent to app-owned selection models.
|
||||
- Source-driven `autofocus`, observable typed scroll events (`on_scroll`), a built-in search-field clear affordance, and a quiet-hover style knob for content tiles.
|
||||
- **Effect system**: the update loop's command half — `update` gains an effects channel of bounded, key-addressed effects that deliver exactly one terminal Msg each and are fully testable against a deterministic fake executor:
|
||||
- `fx.spawn` runs subprocesses with streamed lines or whole-output collect mode (stderr tail included), raisable per-effect line bounds, and cancellation; `fx.fetch` runs HTTP(S) requests with an explicit failure taxonomy, timeouts, and a streaming response mode for line-oriented endpoints.
|
||||
- `fx.readFile`/`fx.writeFile` persistence, `fx.startTimer`/`fx.cancelTimer`, `fx.writeClipboard`/`fx.readClipboard`, `fx.registerImageBytes` for runtime images, `fx.closeWindow`/`fx.minimizeWindow`, and the `init_fx` boot hook so loading states are in the very first paint.
|
||||
- A facade time API (`nowMs`, `monotonicMs`) plus `Clock`/`TestClock` seams for deterministic time-dependent logic.
|
||||
- **Audio, end to end on five platforms**: `fx.playAudio` with full transport (pause, resume, stop, seek, volume), real decoded durations, position ticks, and honest completion and failure reports — AVFoundation on macOS, Media Foundation on Windows, GStreamer on Linux, and the experimental mobile hosts on iOS (AVFoundation) and Android (MediaPlayer).
|
||||
- Streaming with a verified track cache: URL sources resolve local file, then size-verified cache, then progressive stream (filling the cache in parallel for the next play), with honest `buffering` states and explicit failures — never a silent stall.
|
||||
- Real spectrum analysis on macOS, Windows, and Linux: 32 log-spaced bands at ~25 Hz from the app's own playback, journaled at the effect boundary so record/replay repaints identical bars; hosts that cannot analyze report the capability honestly instead of fabricating bands.
|
||||
- **Images**: a platform decode seam (CGImageSource, gdk-pixbuf, WIC) so the toolkit bundles no image decoders; runtime image registration renders through every path — GPU packets, software presentation, and screenshots — with pixels riding an out-of-band upload channel so image-bearing frames stay on the GPU path; avatars take a bound image with initials fallback.
|
||||
- **Windowing and chrome**: model-declared secondary windows (presence is visibility; a user close dispatches a Msg), enforced window minimum sizes, and present-before-show so a canvas window never appears blank.
|
||||
- Titlebar control on all three desktops: `hidden_inset`, a tall unified-toolbar variant, and fully `chromeless` styles; markup `window-drag` regions; and an `on_chrome` hook carrying the real overlay insets and control-cluster frames — with real system window controls preserved on Linux client-side decorations and Windows DWM caption buttons.
|
||||
- Native context menus, declared per widget in Zig or markup (`<context-menu>`): the real OS menu where one exists, an anchored canvas surface elsewhere, editable-text cut/copy/paste defaults, and full automation support for enumerating and invoking items.
|
||||
- A menu-bar status item with model-driven title and menu; canvas and WebView panes composed in one window; adoption of app-owned native views into the layout (`adoptViewSurface`); and native scroll drivers on macOS that give every scroll region OS momentum, rubber-band overscroll, and the system overlay scrollbar with zero app code.
|
||||
- **Experimental iOS and Android host tiers — the toolkit owns the entire mobile app**: complete UIKit and Android hosts ship in the SDK over the embed C ABI, an app project carries zero host code, and embedding a hand-written host stays first-class.
|
||||
- `native dev --target ios|android [--device name]` builds, installs, and launches on a simulator or emulator and streams the app log; `native package --target ios` emits an archive-ready Xcode project and `--target android` a complete generated host project plus a debug-signed APK — no build-system project, no plugin matrix.
|
||||
- Touch, soft keyboard, and IME forwarding; safe-area and keyboard insets on the window-chrome channel plus host-reported form factor; platform text metrics; platform audio and image decoding; and damage-rect rendering so a keystroke repaints and uploads only the changed region instead of the whole screen.
|
||||
- Declared platform chrome: apps project a tab set and primary action as a real system tab bar, and a model-owned page stack drives real push/pop transitions with the system edge-swipe back gesture — navigation state stays in the model and replays deterministically from the Msg journal.
|
||||
- The soundboard ships the proof: one codebase, a desktop composition plus a compact phone shell selected by the host-reported form factor, running on the simulator via `native dev --target ios`.
|
||||
- **Theme packs and design tokens**: named packs — the default register plus `geist`, the design register of the bundled Geist type family — compose with the live system appearance; interaction-state formulas, control metrics, and focus-ring geometry are all token-stated; new `success`/`warning`/`info` semantic color tokens; the stock theme follows the OS light/dark, high-contrast, and reduce-motion settings live; modal scrims blur the content behind them for real; app-registered TrueType fonts resolve everywhere a font id rides.
|
||||
- **Deterministic rendering core**: a bounded, std-only TTF parser inks real anti-aliased glyphs (bundled Geist and Geist Mono) on every headless path — screenshots, mobile embeds, pixel goldens — while layout measures exactly what gets inked; an allocation-free vector rasterizer with bit-identical cross-platform coverage draws paths, icons, and charts.
|
||||
- **Automation and testing**: `native automate` gains `assert` (regex polling against the accessibility snapshot), deterministic PNG screenshots, per-stage frame profiling (`profile on`), and widget verbs for hold, secondary click, context-menu invocation, drag, wheel, and tray actions.
|
||||
- Deterministic session record and replay: journal every platform event and effect result, then re-run headlessly with checkpoint verification (`native automate record` / `replay --verify`).
|
||||
- `native init` scaffolds a CI workflow: null-platform tests for every frontend plus a Linux automation smoke that drives the app's real binary under Xvfb.
|
||||
- **Accessibility as machine checks**: unnamed interactive controls, icon-only controls without labels, and misused roles are validation errors (degradations report as warnings; `--strict` promotes); a deterministic tree-level audit catches labels that resolve empty at runtime, focus-unreachable widgets, and duplicate sibling labels; and assistive actions actuate through the same activation paths keyboard users take instead of reporting success on nothing.
|
||||
- **Showcase examples**: calculator, notes (folders, trash, context menus), soundboard (a real music library with playback and search), deck (a radically re-skinned sibling proving theme packs and chrome passes), system-monitor (live effects-driven sampling), markdown-viewer (split-pane editor and preview), and feed (a 100,000-post virtual list) — each with a deterministic test suite, and a prepared real-music catalog that streams out of the box.
|
||||
- **Docs site**: a full Components section (34 pages) where every preview is rendered offscreen by the engine itself and upgrades on hover to a live engine instance running in-page via a ~306 KB (gzip) wasm build; attribute tables generate from the validator's own vocabulary so docs cannot drift; the whole site restructured native-first with new State & Data Flow, App & Runtime, Theming, and Testing in CI pages.
|
||||
- **Zero-config toolchain and distribution**: `native dev|build|test|check` work in a directory holding only `app.zon` and `src/` (`native eject` writes the build files exactly once when you want to own them); the pinned Zig toolchain downloads on consent with checksum verification; and `@native-sdk/cli` installs from npm with zero scripts — eight platform binaries plus the SDK source, so `native init && native dev` work offline right after install.
|
||||
- **One-image app icons**: drop a single square PNG or SVG in `assets/`, and `native package` generates everything — a masked, grid-correct macOS `.icns`, a multi-size Windows `.ico`, Linux hicolor PNGs, and iOS/Android catalog icons — with exact linear-light downscales, teaching errors for bad sources, and no external tools.
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Performance — frame cost scales with what changed, not view size**:
|
||||
- GPU packets ride a compact binary encoding (~10x smaller than JSON, ~40x effective capacity — text-heavy frames no longer silently fall back to software rendering), steady-state frames ship incremental patches (~20x less wire per interaction), and repaints derive per-change dirty-rect lists so pixels between two far-apart changes stay retained.
|
||||
- Per-command raster caches stop re-rasterizing unchanged content (host draw p50 dropped an order of magnitude on animated views); frame planning and widget reconciliation moved from quadratic scans to indexed lookups (end-to-end interaction p50 improved ~2.3-3.2x on large views); backdrop blur cost no longer scales with radius; a click emits one display list instead of three.
|
||||
- Launch to glass: the first canvas frame presents before the event loop starts, first paint rasterizes across cores, the main WebView is created lazily, and warm launches measured 150→120 ms on the heaviest showcase app; `NATIVE_SDK_WINDOW_TIMING=1` prints a per-phase launch breakdown.
|
||||
- Occluded windows throttle to a ~1 Hz heartbeat instead of spinning the frame clock (spectrum reports pause too); accessibility publishes only when the tree actually changed and defer off the input-to-glass path; frame pacing delivers exactly one event per display interval; input latency is measured to the responding present, honestly.
|
||||
- `zig build bench-render` runs deterministic interaction scenarios against committed per-scenario budgets, and a percentile GPU perf check gates first-frame and input-to-present latency in CI.
|
||||
- **Component fidelity**: the built-in components land a refined default look, verified pixel-for-pixel in CI under both theme packs.
|
||||
- Measured control geometry and state washes, ring-offset focus rings, flat buttons with a quiet destructive treatment, segmented button groups rendered as one bar with collapsed seams, compact badges, and hairline tables.
|
||||
- Reworked accordion, tabs, alert, and card treatments with sensible per-kind layout defaults; skeletons pulse and the caret blinks; select menus read like menus (row highlight, trailing checkmark for the committed option).
|
||||
- Native cursor conventions (the pointing hand is reserved for true links), flat list rows, axis-aware separators, and edge-pinned scrolling with opt-in rubber-band overscroll.
|
||||
- **Capacity and honesty**: per-view widget budgets quadrupled to 1024 nodes (command, glyph, and text budgets raised to match) with headroom telemetry in every snapshot; explicit `width`/`height` are definite bounds; layout overflow is diagnosed, dispatch errors degrade and record instead of exiting the app, and every effect-facing type and constant is exported from the `native_sdk` facade.
|
||||
- **Teaching validation**: handlers on elements that can never receive them, `gap` on stacking containers, `wrap` on non-text elements, and literal glyphs outside the bundled font's coverage are all positioned teaching errors, enforced identically by the validator, both engines, and the language server.
|
||||
- **Desktop parity**: the Linux and Windows hosts reach the macOS seam contract — app timers, appearance events, window options at create, interactive window moves, IME composition on Windows, and hidden-titlebar fidelity with real system controls; CI gains Windows canvas and effects smokes under Wine, a headless Linux canvas smoke, and a containerized Linux live-truth harness driving every showcase app on real GTK.
|
||||
- **Observability**: automation snapshots report the live present path and mode, patch sizes, fallback reasons with byte counts, budget headroom, audio state, tray contents, and per-stage frame percentiles while profiling; `NATIVE_SDK_GPU_DRAW_TRACE=1` attributes every present.
|
||||
- **Docs and skill accuracy**: the code-signing page documents the real ad-hoc Gatekeeper experience, form-control and picker docs match what the engine ships, the keyboard and interaction seams are documented where developers look, and stale commands and API shapes were fixed across the site.
|
||||
- **Example polish**: showcase headers carry only working controls under hidden-inset titlebars, the soundboard adopts desktop list-selection conventions, notes gains Recently Deleted and dialog autofocus, the deck refined its hardware identity across feedback passes, system-monitor lands the standard settings flow, and every showcase app ships the zero-config scaffold shape with a real neutral default app icon.
|
||||
- **Contributor workflow**: changelog fragments (`changelog.d/`) end merge conflicts on this file, and `scripts/gate.sh` runs a tiered local gate that scales with the diff.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Input and focus**: clicked and tabbed-into fields always show a caret (drawn in the field's own ink, readable in every scheme); Escape dismisses surfaces opened from non-focusable triggers; Enter inserts a newline in textareas (the primary chord submits); programmatic focus is quiet on non-editables; composite rows hover, point, and press as one surface; cross-centered overflow distributes evenly.
|
||||
- **Model-driven control state**: sliders, exclusive selections, and toggle-button chips follow the model when the source moves (a live drag is never yanked); disabled selection controls render disabled; idle disabled buttons no longer wear an accent outline.
|
||||
- **Rendering correctness**: pixel snapping no longer wraps exact-fit text or elides exact-fit badges; packet text honors engine line breaks; text bounds cover glyph ink; mono runs read as monospace on every headless path; avatar initials center; the spinner actually spins and sizes to the icon register; offscreen screenshots clear with live tokens; render animations invalidate only the affected commands; one invalid UTF-8 byte can no longer hang the renderer; budget overflows apply atomically instead of tearing the retained tree.
|
||||
- **macOS**: Debug builds no longer abort at launch on an SDK sanitizer trap; `resizable = false` is honored; frames keep pumping during live resize and menu tracking; occluded windows keep presenting and flush instantly on reveal; quitting mid-playback no longer crashes; the Chromium (CEF) host builds and runs again, verified live with child WebViews.
|
||||
- **Windows and Linux**: Windows apps launch on real Windows (common-controls manifest, dynamic task-dialog resolution) and builds link again; embed input timestamps and network error classification fixed on Windows; Linux audio no longer sticks in a buffering state; a saturated frame loop no longer freezes GTK windows; runtimes heap-allocate in every runner, fixing startup crashes under default stack limits; GTK initial allocation and overlay z-order fixed.
|
||||
- **Packaging**: signed bundles keep a valid code signature; packaged apps read their bundled assets and show their display name in the menu bar; archives are labeled with the real optimize mode; unbundled dev runs fall back to the embedded default Dock icon.
|
||||
- **Automation and CLI reliability**: commands queue with delivery acknowledgments instead of overwriting a single slot; a landing command wakes an idle app (~4 ms consumption); CLI and app handshake on a protocol version, and stale publishers or binaries are refused loudly; parseable payloads land on stdout; clicks aim at the rendered control, not its stretched box; `native dev` runs Debug so hot reload is actually compiled in; no CLI verb exits silently, and `--help` exits 0 everywhere.
|
||||
- **Hardening**: the markdown renderer survives hostile input (three quadratic blowups fixed, a fuzz corpus added); large models neither exhaust the comptime branch quota nor ride the stack (`UiApp.create` constructs in place); mobile embed libraries stage per target so cross-target builds cannot poison each other; oversized inline window sources fail loudly instead of leaving a blank window; docs live previews build, lay out with the selected pack's tokens, animate, and route keyboard shortcuts correctly.
|
||||
- **Measured-label controls no longer elide under pixel snapping**: a control sized exactly to its measured label — toggle chips (the system monitor's "PID" sort chip painted "PI…"), buttons, segmented controls and tab triggers, menu and list rows, tooltips, checkbox/radio/switch labels, hug-sized status bars — could lose a fraction of a pixel to render-time geometry snapping and swap real glyphs for an ellipsis. Every measured-label intrinsic width now rounds UP to the snap grid (the badge rule from the previous round), the switch additionally reserves its snapped track extent, and themes without geometry snapping stay bit-identical.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### New Features
|
||||
|
||||
- **Keyboard shortcuts**: Add app-level keyboard shortcuts with manifest and runtime configuration, native delivery to Zig `Event.shortcut`, and typed JavaScript `window.zero` shortcut events (#62).
|
||||
- **Manifest-driven runner shortcuts**: Load `app.zon` shortcuts automatically in generated runners, with a `RunOptions.shortcuts` override for apps that build shortcut lists in Zig (#62).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Shortcut documentation and validation**: Document the `app.zon` shortcut schema, portable key names, modifier behavior, backend support, and validation limits (#62).
|
||||
- **Windows WebView2 child bridges**: Enable bridge-enabled trusted child WebViews on Windows WebView2, bringing that backend closer to the macOS and Linux system WebView behavior (#62).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Shortcut matching and delivery**: Fix shortcut modifier handling, shifted punctuation matching, backend event routing, and edge cases across AppKit, GTK, WebView2, and macOS CEF (#62).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### New Features
|
||||
|
||||
- **Layered WebView runtime**: Model each native window as a stack of named WebViews, including the reserved startup `main` WebView and child WebViews with frame, layer, zoom, transparency, routing, resizing, reload, and close support across the native backends (#28).
|
||||
- **JavaScript WebView API**: Add typed `window.zero.webviews.*` helpers and `zero-native.webview.*` built-in bridge commands for create, list, setFrame, navigate, setZoom, setLayer, and close operations (#28).
|
||||
- **Isolated child WebViews**: Keep child WebViews bridge-isolated by default, allow trusted child chrome with `bridge: true`, enforce navigation policy on child URLs, and scope WebView commands to the calling native window (#28).
|
||||
- **Browser example**: Add a browser-style example that demonstrates layered WebViews, browser controls, isolated page content, frontend asset handling, and the root `zig build run-browser` command (#28).
|
||||
- **zero-native skills**: Ship CLI-served agent skills and reference material for building and automating zero-native apps (#38).
|
||||
|
||||
### Improvements
|
||||
|
||||
- **WebView and bridge documentation**: Document WebView APIs, built-in bridge commands, security boundaries, backend support, packaging, testing, and app model updates (#28, #38).
|
||||
- **WebView smoke coverage**: Extend automation smoke tests to exercise child WebView create, resize, navigate, and close operations for system WebView and macOS CEF builds (#28).
|
||||
- **CEF runtime builds**: Harden the CEF runtime workflows across macOS, Linux, and Windows, including Windows runtime build fixes (#25, #26).
|
||||
- **macOS compatibility**: Set the native app baseline to macOS 11 (#22).
|
||||
- **Contributor guidance**: Clarify signed commit requirements and contribution PR guidance (#10).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Windows WebView builds**: Fix Windows WebView build failures before the layered WebView release.
|
||||
- **React example dependencies**: Include the missing React example type dependencies (#11).
|
||||
- **GitHub release notes**: Avoid duplicate contributor lists when creating GitHub releases (#24).
|
||||
- **macOS package permissions**: Preserve executable permissions for packaged macOS app binaries (#39).
|
||||
|
||||
### Contributors
|
||||
|
||||
- @Anshuman71
|
||||
- @PrathamGhaywat
|
||||
- @ctate
|
||||
|
||||
## 0.1.9
|
||||
|
||||
### New Features
|
||||
|
||||
- **Linux and Windows desktop support**: Add platform-aware CEF tooling, Linux and Windows desktop build paths, Windows native host plumbing, and cross-platform CEF runtime packaging/release coverage.
|
||||
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
<!-- release:end -->
|
||||
|
||||
## 0.1.8
|
||||
|
||||
<!-- release:start -->
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Install completion delay** - Drain redirected GitHub responses during postinstall so npm exits immediately after the native binary is installed.
|
||||
@@ -26,12 +849,9 @@ All notable changes to zero-native will be documented in this file.
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
<!-- release:end -->
|
||||
|
||||
## 0.1.7
|
||||
|
||||
<!-- release:start -->
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Install progress** - Show native binary download progress and checksum status during the npm postinstall step.
|
||||
@@ -39,12 +859,9 @@ All notable changes to zero-native will be documented in this file.
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
<!-- release:end -->
|
||||
|
||||
## 0.1.6
|
||||
|
||||
<!-- release:start -->
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Init next steps** - Print the follow-up commands after scaffolding so users can immediately run their new app.
|
||||
@@ -52,12 +869,9 @@ All notable changes to zero-native will be documented in this file.
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
<!-- release:end -->
|
||||
|
||||
## 0.1.5
|
||||
|
||||
<!-- release:start -->
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **macOS local asset loading** - Prefer current-directory asset roots during local `zig build run` so Vite-based examples render their production bundles instead of blank windows.
|
||||
@@ -65,12 +879,9 @@ All notable changes to zero-native will be documented in this file.
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
<!-- release:end -->
|
||||
|
||||
## 0.1.4
|
||||
|
||||
<!-- release:start -->
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Scaffolded app builds** - Ship the framework source tree in the npm package and make `zero-native init` point generated apps at the installed package root so `zig build run` can resolve `src/root.zig`.
|
||||
@@ -82,12 +893,9 @@ All notable changes to zero-native will be documented in this file.
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
<!-- release:end -->
|
||||
|
||||
## 0.1.3
|
||||
|
||||
<!-- release:start -->
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **CLI package homepage** - Point npm package metadata at `https://zero-native.dev`.
|
||||
@@ -97,7 +905,6 @@ All notable changes to zero-native will be documented in this file.
|
||||
### Contributors
|
||||
|
||||
- @ctate
|
||||
<!-- release:end -->
|
||||
|
||||
## 0.1.2
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Contributing
|
||||
|
||||
Thanks for helping improve zero-native. This guide is for maintainers and contributors working on the framework repository itself.
|
||||
Thanks for helping improve the Native SDK. This guide is for maintainers and contributors working on the toolkit repository itself.
|
||||
|
||||
For app author documentation, start at [zero-native.dev](https://zero-native.dev).
|
||||
For app author documentation, start at [native-sdk.dev](https://native-sdk.dev).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -14,7 +14,7 @@ For app author documentation, start at [zero-native.dev](https://zero-native.dev
|
||||
|
||||
## Local Checks
|
||||
|
||||
Run the framework tests:
|
||||
Run the toolkit tests:
|
||||
|
||||
```bash
|
||||
zig build test
|
||||
@@ -41,8 +41,8 @@ zig build run-webview
|
||||
Check the npm CLI package:
|
||||
|
||||
```bash
|
||||
npm --prefix packages/zero-native run version:check
|
||||
npm --prefix packages/zero-native run scripts:check
|
||||
npm --prefix packages/native-sdk run version:check
|
||||
npm --prefix packages/native-sdk run scripts:check
|
||||
```
|
||||
|
||||
Check the documentation site:
|
||||
@@ -63,7 +63,7 @@ zig build run-webview -Dweb-engine=system
|
||||
For Chromium on macOS, install CEF and run with the Chromium engine:
|
||||
|
||||
```bash
|
||||
zero-native cef install
|
||||
native cef install
|
||||
zig build run-webview -Dweb-engine=chromium
|
||||
```
|
||||
|
||||
@@ -85,11 +85,17 @@ zig build package
|
||||
Package explicitly through the CLI:
|
||||
|
||||
```bash
|
||||
zero-native package --target macos --manifest app.zon --assets assets --binary zig-out/lib/libzero-native.a
|
||||
native package --target macos --manifest app.zon --assets assets --binary zig-out/lib/libnative-sdk.a
|
||||
```
|
||||
|
||||
For Chromium packages, configure `.web_engine = "chromium"` and `.cef` in `app.zon`, or use temporary `--web-engine` and `--cef-dir` overrides while testing.
|
||||
|
||||
Verify an ad-hoc signed package's code signature survives packaging intact (macOS; skips loudly on hosts without `codesign`):
|
||||
|
||||
```bash
|
||||
zig build test-package-signing
|
||||
```
|
||||
|
||||
## Automation Development
|
||||
|
||||
Enable automation in a build:
|
||||
@@ -101,19 +107,22 @@ zig build run-webview -Dautomation=true
|
||||
Interact with the running app:
|
||||
|
||||
```bash
|
||||
zero-native automate wait
|
||||
zero-native automate list
|
||||
zero-native automate bridge '{"id":"ping","command":"native.ping","payload":null}'
|
||||
native automate wait
|
||||
native automate list
|
||||
native automate bridge '{"id":"ping","command":"native.ping","payload":null}'
|
||||
```
|
||||
|
||||
Automation writes artifacts under `.zig-cache/zero-native-automation`.
|
||||
Automation writes artifacts under `.zig-cache/native-sdk-automation`.
|
||||
|
||||
|
||||
## Making a Pull Request
|
||||
Thank you for your contribution! Please follow these steps to ensure a smooth review process:
|
||||
1. Fork the repository and create a new branch for your feature or bug fix.
|
||||
2. Make your changes and commit them with clear, descriptive messages.
|
||||
3. Push your branch to your forked repository.
|
||||
4. Open a pull request against the main repository's `main` branch.
|
||||
|
||||
Please cryptographically sign your commits so they show as **Verified** on GitHub. This requires a GPG or SSH signing key added to your GitHub account — see [GitHub's guide](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification). Note: the `Signed-off-by` trailer (`git commit -s`) is a DCO attestation and does **not** produce the Verified badge; you need `git commit -S` (uppercase) or `commit.gpgsign = true` in your git config.
|
||||
Branch from `main` (fork first if you don't have push access), keep the change focused, and run the tiered local gate before opening the PR:
|
||||
|
||||
```bash
|
||||
scripts/gate.sh fast # root suites + the example suites your diff touches
|
||||
```
|
||||
|
||||
Do not edit `CHANGELOG.md` as part of a feature or fix PR; the release agent writes the complete entry from the release-range history. Open the PR against `main` describing what changed and why; for larger changes, open an issue first so the design can be discussed.
|
||||
|
||||
Commits must be cryptographically signed (`git commit -S`, or set `commit.gpgsign = true`) so they show as **Verified** — the `Signed-off-by` trailer from `git commit -s` is a DCO attestation, not a signature.
|
||||
|
||||
@@ -1,120 +1,138 @@
|
||||
# zero-native
|
||||
# Native SDK
|
||||
|
||||
Build native desktop apps with web UI. Tiny binaries. Minimal memory. Instant rebuilds.
|
||||
**Native SDK is the complete toolkit for building native desktop applications.**
|
||||
|
||||
zero-native is a Zig desktop app shell for modern web frontends. Use the platform WebView when you want the smallest possible app, or bundle Chromium through CEF when rendering consistency matters.
|
||||
Native SDK exists because expressive UI and native performance should not be competing goals. Developers often choose web-based runtimes because they offer freedom, speed and control over the product experience. But that freedom often comes with a heavy runtime. Native SDK keeps the expressive authoring model and replaces the runtime with native rendering.
|
||||
|
||||
## Quick Start
|
||||
Views are declarative markup in `.native` files, logic is plain TypeScript compiled to native code at build time — or Zig, first-class by choice — and Native SDK's own engine draws every pixel into real OS windows. No browser, no WebView, no JS runtime in the binary: Zig is how everything works, TypeScript and Native markup are how apps are authored.
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/assets/soundboard-dark.webp">
|
||||
<img src=".github/assets/soundboard-light.webp" alt="The Soundboard example app rendered by the Native SDK engine: a music library with album cover art, search, and a playback bar" width="100%">
|
||||
</picture>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="70%">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/assets/notes-dark.webp">
|
||||
<img src=".github/assets/notes-light.webp" alt="The Notes example app rendered by the Native SDK engine: a three-pane notes manager with folders, a note list, and an open note" width="640">
|
||||
</picture>
|
||||
</td>
|
||||
<td width="30%">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/assets/calculator-dark.webp">
|
||||
<img src=".github/assets/calculator-light.webp" alt="The Calculator example app rendered by the Native SDK engine: a finished calculation above a full keypad" width="270">
|
||||
</picture>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<sub>Soundboard, Notes, and Calculator from <a href="./examples">examples/</a> — every pixel drawn by the Native SDK engine, captured through its deterministic reference renderer. The images follow your color scheme.</sub>
|
||||
|
||||
## Quick start
|
||||
|
||||
Install the CLI:
|
||||
|
||||
```bash
|
||||
npm install -g zero-native
|
||||
npm install -g @native-sdk/cli
|
||||
```
|
||||
|
||||
Create and run an app:
|
||||
|
||||
```bash
|
||||
zero-native init my_app --frontend next
|
||||
native init my_app
|
||||
cd my_app
|
||||
zig build run
|
||||
native dev
|
||||
```
|
||||
|
||||
The first run installs frontend dependencies, builds the generated native shell, and opens a desktop window rendering your web UI.
|
||||
A native window opens with a working counter. The whole app is three files of truth — view, logic, manifest — and no build config. The view is `src/app.native`, a markup file that binds values and dispatches messages (the counter row at its heart):
|
||||
|
||||
Read the full guide at [zero-native.dev/quick-start](https://zero-native.dev/quick-start).
|
||||
```html
|
||||
<row gap="8" main="center" cross="center" grow="1">
|
||||
<button variant="secondary" on-press="decrement">-</button>
|
||||
<text>{count}</text>
|
||||
<button variant="primary" on-press="increment">+</button>
|
||||
</row>
|
||||
```
|
||||
|
||||
## Why zero-native
|
||||
All logic lives in `src/core.ts`: a `Model` interface, a `Msg` union, and one pure `update` function — the only place state changes, plain TypeScript compiled to native code at build time:
|
||||
|
||||
### Tiny and fast
|
||||
|
||||
System WebView apps do not bundle a browser runtime, so the native shell stays small and starts quickly. Your app uses WKWebView on macOS and WebKitGTK on Linux.
|
||||
|
||||
### Choose your web engine
|
||||
|
||||
Pick the engine that fits the product. System WebView gives you a lightweight native footprint. Chromium through CEF gives you predictable rendering and a pinned web platform on supported targets.
|
||||
|
||||
### Fast native rebuilds
|
||||
|
||||
The native layer is Zig, so app logic, bridge commands, and platform integrations rebuild quickly. Your frontend can still use the web tooling you already know.
|
||||
|
||||
### Native power without heavy glue
|
||||
|
||||
Zig calls C directly, which keeps platform SDKs, native libraries, codecs, and local system integrations within reach when the WebView layer needs to do real native work.
|
||||
|
||||
### Explicit security model
|
||||
|
||||
The WebView is treated as untrusted by default. Native commands, permissions, navigation, external links, and window APIs are opt-in and policy controlled.
|
||||
|
||||
## Status
|
||||
|
||||
zero-native is pre-release. Desktop support now covers macOS 11+, Linux, and Windows build paths, with Chromium/CEF distributed as platform-specific runtimes.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
`App` is the small Zig object that describes your application: name, WebView source, lifecycle hooks, and optional native services.
|
||||
|
||||
`Runtime` owns the event loop, windows, bridge dispatch, automation hooks, tracing, and platform services.
|
||||
|
||||
`WebViewSource` tells the runtime what to load: inline HTML, a URL, or packaged frontend assets served from a local app origin.
|
||||
|
||||
`app.zon` is the app manifest. It declares app metadata, icons, windows, frontend assets, web engine selection, security policy, bridge permissions, and packaging inputs.
|
||||
|
||||
`window.zero.invoke()` is the JavaScript-to-Zig bridge. Calls are size-limited, origin checked, permission checked, and routed only to registered handlers.
|
||||
|
||||
## Configuration
|
||||
|
||||
Most project-level behavior lives in `app.zon`:
|
||||
|
||||
```zig
|
||||
.{
|
||||
.id = "com.example.my-app",
|
||||
.name = "my-app",
|
||||
.display_name = "My App",
|
||||
.version = "0.1.0",
|
||||
.web_engine = "system",
|
||||
.permissions = .{ "window" },
|
||||
.capabilities = .{ "webview", "js_bridge" },
|
||||
.security = .{
|
||||
.navigation = .{
|
||||
.allowed_origins = .{ "zero://app", "http://127.0.0.1:5173" },
|
||||
},
|
||||
},
|
||||
.windows = .{
|
||||
.{ .label = "main", .title = "My App", .width = 960, .height = 640 },
|
||||
},
|
||||
```ts
|
||||
export function update(model: Model, msg: Msg): Model {
|
||||
switch (msg.kind) {
|
||||
case "increment":
|
||||
return { ...model, count: model.count + 1 };
|
||||
case "decrement":
|
||||
return { ...model, count: model.count - 1 };
|
||||
case "reset":
|
||||
return { ...model, count: 0 };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `.web_engine = "system"` for the platform WebView. On supported macOS builds, use `.web_engine = "chromium"` with a `.cef` config when you want to bundle Chromium.
|
||||
Prefer Zig for the core? `native init my_app --template zig-core` scaffolds the same app with `src/main.zig` — same loop, same runtime, first-class by choice.
|
||||
|
||||
## Documentation
|
||||
Edit `src/app.native` while `native dev` runs and the window updates in place, keeping your state. `native dev --core` runs the TypeScript core under node for instant logic checks, `native check` validates the core and every view in milliseconds without building, and `native build` produces an optimized release binary.
|
||||
|
||||
The full documentation is at [zero-native.dev](https://zero-native.dev).
|
||||
Read the full guide at [native-sdk.dev/quick-start](https://native-sdk.dev/quick-start).
|
||||
|
||||
- [Quick Start](https://zero-native.dev/quick-start)
|
||||
- [Web Engines](https://zero-native.dev/web-engines)
|
||||
- [App Model](https://zero-native.dev/app-model)
|
||||
- [Bridge](https://zero-native.dev/bridge)
|
||||
- [Security](https://zero-native.dev/security)
|
||||
- [Packaging](https://zero-native.dev/packaging)
|
||||
## What you get
|
||||
|
||||
**Beautiful by default** — Great software should not start from a blank slate. The built-in component catalog — buttons, tabs, text fields, dialogs, charts, virtual lists, and more — ships with considered typography, spacing, and color, so the app `native init` scaffolds already looks intentional the first time its window opens.
|
||||
|
||||
**Customizable by design** — Your app should have its own identity, not ours. Styling is design tokens end to end: color, radius, and typography resolve by name, re-resolve live when the theme changes, and can be replaced wholesale — `examples/soundboard` and `examples/deck` are the same music player separated only by tokens and a chrome pass.
|
||||
|
||||
**Native from the start** — Every interface is rendered without a browser or WebView. The engine draws into real OS windows while scroll physics, menus, dialogs, the tray, and text input stay with the operating system, and markup compiles into the executable at build time, so a release build carries no parser or interpreter — the scaffolded counter app builds to a single binary a few megabytes small.
|
||||
|
||||
**Predictable state** — State changes should be explicit, inspectable and easy to reason about. Events produce messages, messages update state, and state renders the interface; markup can bind and dispatch but never mutate. The loop is so deterministic that `native automate record` journals a session and `replay` reproduces it headlessly, verified frame by frame against state fingerprints.
|
||||
|
||||
**Simple authoring** — Interfaces should be easy to read, easy to write and easy to generate. Views are elements, flex layout, `{bindings}`, and expressions like `selected="{f == filter}"`, and `native check` validates every view against your app's actual `Model` and `Msg` — bindings, iterables, message tags — in milliseconds, with `file:line:column` errors that teach.
|
||||
|
||||
**AI is part of the workflow** — Native SDK is designed for a world where humans and AI agents build software together. Every app embeds an automation server, so any agent can read accessibility snapshots, drive widgets, assert on live state, and take deterministic screenshots of the running window; accessibility findings are machine-checked in `native check`; and the CLI ships the agent skills that teach all of it (`native skills list`).
|
||||
|
||||
## Examples
|
||||
|
||||
Framework-specific starter examples live in `examples/`:
|
||||
The apps pictured above live in [examples/](./examples), most as zero-config projects — `app.zon` plus `src/`, no build files — run straight from their directory with `native dev`. Start with the TypeScript examples when learning the primary authoring path. The `-ts` suffix on `soundboard-ts` and `system-monitor-ts` is historical because those apps are ports kept beside older Zig originals. Chatbot is TypeScript-only and follows the unsuffixed naming used by new apps created with `native init`.
|
||||
|
||||
- `examples/next`
|
||||
- `examples/react`
|
||||
- `examples/svelte`
|
||||
- `examples/vue`
|
||||
| Example | What it shows |
|
||||
| --- | --- |
|
||||
| [`chatbot`](./examples/chatbot) | TypeScript + Native markup end to end: modules, a text editor, streaming fetch effects, and replay-safe configuration. |
|
||||
| [`soundboard-ts`](./examples/soundboard-ts) | The full music-player showcase in TypeScript + Native markup: audio, search, assets, timers, and context menus. |
|
||||
| [`system-monitor-ts`](./examples/system-monitor-ts) | A live process monitor in TypeScript + Native markup: subprocess effects, tables, charts, and timers. |
|
||||
| [`calculator`](./examples/calculator) | A complete small app: markup keypad, keyboard input, chrome shortcuts, theming. |
|
||||
| [`notes`](./examples/notes) | Persistence through the effects channel: debounced writes, restore on boot, dialogs, search. |
|
||||
| [`soundboard`](./examples/soundboard) | Album grid with decoded cover art, context menus, timers, and a custom theme. |
|
||||
| [`deck`](./examples/deck) | The soundboard player rebuilt as a dense hardware chassis: two windows, same widgets, different tokens. |
|
||||
| [`feed`](./examples/feed) | A 100,000-row list, virtualized with runtime-owned scrolling. |
|
||||
|
||||
Each example is a complete zero-native app with `app.zon`, a Zig shell, and a minimal frontend project. Run one with `zig build run` from its directory.
|
||||
The unsuffixed showcase apps above predate the TypeScript default and retain their Zig cores as first-class alternative implementations. The full catalog in [examples/README.md](./examples/README.md) also covers guarded OS capabilities, GPU surfaces, WebView composition, web-frontend shells, and the iOS/Android embed hosts.
|
||||
|
||||
Mobile embedding examples are available too:
|
||||
## Platforms
|
||||
|
||||
- `examples/ios`
|
||||
- `examples/android`
|
||||
macOS is the primary development platform and carries the deepest support: Metal presentation, OS scroll physics, native context menus, app menus, tray, and dialogs. Linux runs the full showcase through the deterministic software renderer in real windows, with pointer, keyboard, scroll, native context menus, IME composition, and HiDPI; Windows runs on a Win32 host with native context menus and IME composition and is exercised in CI, including real input injection. Mobile support is experimental: iOS is simulator-proven through the embed library and Android cross-compiles with the full embed ABI, but APIs and tooling on both are still evolving — desktop is the mature surface. WebView surfaces coexist on every desktop platform. The [platform support matrix](https://native-sdk.dev/platform-support) documents exactly what each host supports today.
|
||||
|
||||
These show how an iOS or Android host app links the zero-native C ABI from `libzero-native.a`.
|
||||
## Documentation
|
||||
|
||||
For local framework development, see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
The full documentation is at [native-sdk.dev](https://native-sdk.dev).
|
||||
|
||||
- [Quick Start](https://native-sdk.dev/quick-start) — install to a running, tested app
|
||||
- [Philosophy](https://native-sdk.dev/philosophy) — the six principles behind the toolkit
|
||||
- [App Model](https://native-sdk.dev/app-model) — the model/message/update loop, wiring, and hot reload
|
||||
- [TypeScript Cores](https://native-sdk.dev/typescript) — the app-core subset, effects, subscriptions, and the node dev loop
|
||||
- [Native UI](https://native-sdk.dev/native-ui) — every element, attribute, and pattern in the markup
|
||||
- [Components](https://native-sdk.dev/components) — the component catalog
|
||||
- [State & Data Flow](https://native-sdk.dev/state) — derive-don't-store, bindings, and text editing
|
||||
- [Testing](https://native-sdk.dev/testing) — full-loop UI tests, headless on any machine
|
||||
- [Automation](https://native-sdk.dev/automation) — snapshots, widget driving, record/replay, screenshots
|
||||
- [Capabilities](https://native-sdk.dev/capabilities) — guarded OS services: notifications, clipboard, dialogs, credentials
|
||||
- [Packaging](https://native-sdk.dev/packaging) — from binary to distributable app
|
||||
- [Platform Support](https://native-sdk.dev/platform-support) — what each host supports today
|
||||
|
||||
## Contributing
|
||||
|
||||
Native SDK is pre-1.0: APIs still move, and the toolkit is evolving quickly. Bug reports and focused pull requests are welcome — for larger changes, open an issue first so the design can be discussed. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup and local checks.
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](./LICENSE)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Releasing
|
||||
|
||||
Releases are manual, single-PR affairs. The maintainer controls the changelog voice and format.
|
||||
|
||||
To prepare a release:
|
||||
|
||||
1. Create a branch (e.g. `prepare-v1.2.0`)
|
||||
2. Bump the version in `packages/native-sdk/package.json`
|
||||
3. Run `npm --prefix packages/native-sdk run version:sync` to update all version references
|
||||
4. Review the git history since the previous release and write the complete changelog entry at the top of `CHANGELOG.md`, under a new `## <version>` heading wrapped in `<!-- release:start -->` and `<!-- release:end -->` markers
|
||||
5. Populate the entry's `### Contributors` from commit authors and `Co-authored-by` trailers in the release range, using GitHub handles when available; this marked block is also the GitHub release body
|
||||
6. Remove the `<!-- release:start -->` and `<!-- release:end -->` markers from the previous release entry; only the latest release should have markers
|
||||
7. Open a PR and merge to `main`
|
||||
|
||||
## Writing the changelog
|
||||
|
||||
Follow the existing format and voice. Group changes under descriptive headings such as `### New Features`, `### Bug Fixes`, and `### Improvements`. Give each bullet a bold lead-in followed by a concise description, and include PR numbers when available. Do not prefix entries with commit hashes.
|
||||
|
||||
The release entry should cover the complete git range since the previous release, including changes whose individual PRs did not touch `CHANGELOG.md`.
|
||||
|
||||
CI compares the version in `packages/native-sdk/package.json` to what's on npm. If it differs, it cross-builds the CLI for every platform, creates the GitHub release with the binaries, publishes the per-platform binary packages (`packages/native-sdk/npm/*`), and publishes `@native-sdk/cli` last — so the main package only lands once every binary package it pins is live. If npm already has the version but the GitHub release is missing assets, CI recreates the GitHub release from the marked changelog entry.
|
||||
|
||||
Publishing uses npm trusted publishing (OIDC) — there is no npm token secret. One-time setup: on npmjs.com, each of the nine packages (`@native-sdk/cli` plus the eight `@native-sdk/cli-*` platform packages under `packages/native-sdk/npm/*`) must have a GitHub Actions trusted publisher configured with repository `vercel-labs/native`, workflow `release.yml`, and environment `Release`. Every publish runs with `--provenance`. If a package is missing its trusted-publisher configuration, `npm publish` fails loudly with an OIDC authentication error for that package.
|
||||
@@ -1,7 +1,7 @@
|
||||
.{
|
||||
.id = "dev.zero_native",
|
||||
.name = "zero-native",
|
||||
.display_name = "zero-native",
|
||||
.id = "dev.native_sdk",
|
||||
.name = "native-sdk",
|
||||
.display_name = "Native SDK",
|
||||
.version = "0.1.0",
|
||||
.icons = .{ "assets/icon.icns", "assets/icon.ico" },
|
||||
.platforms = .{ "macos" },
|
||||
@@ -10,10 +10,10 @@
|
||||
.bridge = .{
|
||||
.commands = .{
|
||||
.{ .name = "native.ping", .origins = .{ "zero://inline", "zero://app" } },
|
||||
.{ .name = "zero-native.window.list", .permissions = .{ "window" }, .origins = .{ "zero://inline", "zero://app" } },
|
||||
.{ .name = "zero-native.window.create", .permissions = .{ "window" }, .origins = .{ "zero://inline", "zero://app" } },
|
||||
.{ .name = "zero-native.window.focus", .permissions = .{ "window" }, .origins = .{ "zero://inline", "zero://app" } },
|
||||
.{ .name = "zero-native.window.close", .permissions = .{ "window" }, .origins = .{ "zero://inline", "zero://app" } },
|
||||
.{ .name = "native-sdk.window.list", .permissions = .{ "window" }, .origins = .{ "zero://inline", "zero://app" } },
|
||||
.{ .name = "native-sdk.window.create", .permissions = .{ "window" }, .origins = .{ "zero://inline", "zero://app" } },
|
||||
.{ .name = "native-sdk.window.focus", .permissions = .{ "window" }, .origins = .{ "zero://inline", "zero://app" } },
|
||||
.{ .name = "native-sdk.window.close", .permissions = .{ "window" }, .origins = .{ "zero://inline", "zero://app" } },
|
||||
},
|
||||
},
|
||||
.security = .{
|
||||
@@ -25,6 +25,6 @@
|
||||
.web_engine = "system",
|
||||
.cef = .{ .dir = "third_party/cef/macos", .auto_install = false },
|
||||
.windows = .{
|
||||
.{ .label = "main", .title = "zero-native", .width = 720, .height = 480, .restore_state = true },
|
||||
.{ .label = "main", .title = "native-sdk", .width = 720, .height = 480, .restore_state = true },
|
||||
},
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,15 @@
|
||||
<!-- Generated by `zig build generate-icon` (tools/generate_app_icon.zig). Edit the tool, not this file. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
||||
<defs>
|
||||
<linearGradient id="plate" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#262626"/>
|
||||
<stop offset="1" stop-color="#171717"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="12" stdDeviation="16" flood-color="#000000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect x="100" y="100" width="824" height="824" rx="185.4" fill="url(#plate)" filter="url(#shadow)"/>
|
||||
<rect x="372" y="272" width="380" height="380" rx="84" fill="#ffffff" fill-opacity="0.52"/>
|
||||
<rect x="272" y="372" width="380" height="380" rx="84" fill="#ffffff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 853 B |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!-- Application manifest embedded into Windows app executables. Declaring
|
||||
the common-controls v6 side-by-side dependency activates the modern
|
||||
control styling and the v6-only exports (TaskDialogIndirect); without
|
||||
it the loader binds the system-default v5 assembly. Declaring
|
||||
per-monitor-v2 DPI awareness makes GetDpiForWindow report the real
|
||||
monitor DPI (instead of a virtualized 96) so the canvas rasterizes
|
||||
at device scale and Windows never bitmap-stretches the window.
|
||||
dpiAwareness carries an ordered fallback list: readers take the first
|
||||
value they recognize, so PerMonitorV2 degrades to PerMonitor where v2
|
||||
is unknown. The dpiAwareness element itself is ignored before Windows
|
||||
10 1607; there the legacy dpiAware element beside it picks up, and its
|
||||
true/pm value requests per-monitor awareness where supported with
|
||||
system-DPI awareness as the floor. Readers that understand dpiAwareness
|
||||
prefer it over dpiAware, so the two elements coexist safely. -->
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -1,8 +1,15 @@
|
||||
.{
|
||||
.name = .zero_native,
|
||||
.fingerprint = 0x338d08a1e3dd81aa,
|
||||
.name = .native_sdk,
|
||||
.fingerprint = 0xc309966142f33087,
|
||||
.version = "0.1.0",
|
||||
.minimum_zig_version = "0.16.0",
|
||||
// No dependencies. The terminal emulator (libghostty-vt) is
|
||||
// deliberately NOT pinned here: a pin in this file is materialized
|
||||
// into every consumer's package directory even when lazy and
|
||||
// unused. Apps that want live `<terminal>` sessions pin it in their
|
||||
// own build.zig.zon and ask for it with
|
||||
// `addAppArtifacts(.{ .terminal_sessions = true })` — see
|
||||
// examples/workbench, which also owns that path's test coverage.
|
||||
.dependencies = .{},
|
||||
.paths = .{
|
||||
"README.md",
|
||||
@@ -11,13 +18,16 @@
|
||||
"SECURITY.md",
|
||||
"app.zon",
|
||||
"assets",
|
||||
"build",
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"docs",
|
||||
"examples",
|
||||
"packages",
|
||||
"src",
|
||||
"templates",
|
||||
"tests",
|
||||
"third_party/sqlite",
|
||||
"tools",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Run a TypeScript module of the @native-sdk/core transpiler tier under
|
||||
// node from ANY SDK layout:
|
||||
//
|
||||
// node ts_run.mjs <module.ts> [args...]
|
||||
//
|
||||
// ONE code path for every layout: a load hook strips EVERY .ts module with
|
||||
// the transpiler's own pinned TypeScript compiler (shipped as a dependency
|
||||
// of @native-sdk/cli, or a repo checkout's `npm ci` install inside
|
||||
// packages/core — either way resolved from the target module's location by
|
||||
// node's ancestor node_modules walk). Node's own type stripping is never
|
||||
// relied on: it refuses node_modules-resident .ts by design
|
||||
// (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), and outside node_modules
|
||||
// it only became DEFAULT in node 22.18 — so a checkout target on
|
||||
// 22.15-22.17 would die with ERR_UNKNOWN_FILE_EXTENSION if the hook let it
|
||||
// "fall through". Hooking everything makes the 22.15 floor true for both
|
||||
// layouts. Then the runner imports the requested module with argv
|
||||
// respliced so the target sees its usual shape (its own path at argv[1],
|
||||
// its arguments from argv[2]).
|
||||
//
|
||||
// On node builds without module.registerHooks (pre-22.15, or 23.0-23.4 —
|
||||
// the hook landed in 22.15 and 23.5, so ">=22.15" alone is not the
|
||||
// capability line) NO .ts target can run — node_modules-resident
|
||||
// stripping is refused by design and default stripping outside
|
||||
// node_modules only landed in 22.18, which is above this tier anyway —
|
||||
// so the runner fails fast with one teaching line (upgrade to Node.js
|
||||
// 22.15+, on the 23 line 23.5+) before importing it, instead of
|
||||
// surfacing node's raw extension/stripping error.
|
||||
|
||||
import module, { createRequire } from 'node:module';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { pathToFileURL, fileURLToPath } from 'node:url';
|
||||
|
||||
const target = process.argv[2];
|
||||
if (!target) {
|
||||
console.error('usage: node ts_run.mjs <module.ts> [args...]');
|
||||
process.exit(2);
|
||||
}
|
||||
const targetPath = resolve(target);
|
||||
// Drop the runner from argv so the target module parses its own argv
|
||||
// exactly as when node runs it directly.
|
||||
process.argv.splice(1, 1);
|
||||
|
||||
if (typeof module.registerHooks !== 'function') {
|
||||
// No load hooks on this node (pre-22.15, or a 23.0-23.4 build). Every
|
||||
// .ts target needs the hook — node_modules-resident stripping is
|
||||
// refused by design, and native default stripping outside node_modules
|
||||
// is 22.18+ — so any .ts target would fail deep inside node with a raw
|
||||
// extension/stripping error. Teach the fix instead.
|
||||
if (targetPath.endsWith('.ts')) {
|
||||
console.error(
|
||||
`TypeScript apps need Node.js 22.15+ (on the 23 line: 23.5+); you're running ${process.version} - upgrade node and re-run.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
let ts = null;
|
||||
module.registerHooks({
|
||||
load(url, context, nextLoad) {
|
||||
if (url.startsWith('file:') && url.endsWith('.ts')) {
|
||||
const filePath = fileURLToPath(url);
|
||||
// The frontend's own pinned compiler, resolved from the target
|
||||
// module's location (packages/core/node_modules after the taught
|
||||
// `npm ci`, or the dependency npm installed beside the CLI):
|
||||
// resolving from the target finds our own nested/hoisted exact
|
||||
// pin first, so a consumer tree's conflicting hoisted typescript
|
||||
// never wins nearest-wins over it (same reasoning as
|
||||
// typed_ast.ts).
|
||||
if (ts === null) {
|
||||
try {
|
||||
ts = createRequire(targetPath)('@typescript/old');
|
||||
} catch {
|
||||
// A missing toolchain reaches here only on a direct
|
||||
// `node ts_run.mjs` run: every CLI verb gates resolution with
|
||||
// a fuller per-layout teaching before this runner is spawned.
|
||||
// Keep the direct-run error sane and name the checkout fix.
|
||||
console.error(
|
||||
`ts_run.mjs: the transpiler's TypeScript toolchain (@typescript/old) does not resolve from ${targetPath} - on a repo checkout, run \`npm ci --include=dev\` in packages/core.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
const { outputText } = ts.transpileModule(readFileSync(filePath, 'utf8'), {
|
||||
fileName: filePath,
|
||||
compilerOptions: {
|
||||
target: ts.ScriptTarget.ESNext,
|
||||
module: ts.ModuleKind.ESNext,
|
||||
verbatimModuleSyntax: true,
|
||||
},
|
||||
});
|
||||
return { format: 'module', source: outputText, shortCircuit: true };
|
||||
}
|
||||
return nextLoad(url, context);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await import(pathToFileURL(targetPath).href);
|
||||
@@ -1,3 +1,7 @@
|
||||
node_modules/
|
||||
.next/
|
||||
next-env.d.ts
|
||||
.next-gate/
|
||||
.next-agent/
|
||||
.next-check/
|
||||
.next-final/
|
||||
|
||||
@@ -1,10 +1,73 @@
|
||||
import createMDX from "@next/mdx";
|
||||
import { createRequire } from "node:module";
|
||||
import { readdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const withMDX = createMDX();
|
||||
// Resolve the plugin to an absolute path (still a string, so the config
|
||||
// stays serializable for Turbopack). A bare "remark-gfm" is require()d
|
||||
// from the MDX loader's own package context, which under pnpm's strict
|
||||
// module isolation cannot see this app's dependencies — production
|
||||
// builds resolved it, the Turbopack dev server did not.
|
||||
const require = createRequire(import.meta.url);
|
||||
const docsContentDir = fileURLToPath(new URL("./src/app/docs", import.meta.url));
|
||||
|
||||
function docsSlugs(dir = docsContentDir, segments = []) {
|
||||
const slugs = [];
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) {
|
||||
slugs.push(...docsSlugs(path.join(dir, entry.name), [...segments, entry.name]));
|
||||
} else if (entry.name === "page.mdx" && segments.length > 0) {
|
||||
slugs.push(segments.join("/"));
|
||||
}
|
||||
}
|
||||
return slugs;
|
||||
}
|
||||
|
||||
const withMDX = createMDX({
|
||||
options: {
|
||||
// GFM is what gives .mdx pages pipe tables (plus autolinks and
|
||||
// strikethrough) — without it, table markdown renders as a plain
|
||||
// paragraph of pipes.
|
||||
remarkPlugins: [[require.resolve("remark-gfm")]],
|
||||
},
|
||||
});
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
pageExtensions: ["ts", "tsx", "md", "mdx"],
|
||||
// CI-style builds set NEXT_DIST_DIR so `pnpm check` never shares .next
|
||||
// with a running dev server (a shared dist dir corrupts the dev cache).
|
||||
distDir: process.env.NEXT_DIST_DIR || ".next",
|
||||
// The gate builds into .next-gate INSIDE this dir; without an ignore,
|
||||
// the dev watcher sees every one of those build files land and
|
||||
// recompiles continuously whenever a gate runs.
|
||||
watchOptions: {
|
||||
ignored: ["**/.next-gate/**", "**/.next-check/**"],
|
||||
},
|
||||
async redirects() {
|
||||
// Config redirects preserve the request query string. Keeping these out
|
||||
// of the prerendered catch-all route avoids baking a query-less Location
|
||||
// header into every legacy URL's static response.
|
||||
const legacyDocsRedirects = docsSlugs().flatMap((slug) => [
|
||||
{ source: `/${slug}`, destination: `/docs/${slug}`, permanent: true },
|
||||
{ source: `/${slug}.md`, destination: `/docs/${slug}.md`, permanent: true },
|
||||
{ source: `/md/${slug}`, destination: `/docs/${slug}.md`, permanent: true },
|
||||
]);
|
||||
|
||||
return [
|
||||
// The Philosophy page became the Introduction, the opening page of the docs.
|
||||
{ source: "/philosophy", destination: "/docs/introduction", permanent: true },
|
||||
// docsSlugs() only yields nested slugs, so the /docs segment itself has
|
||||
// neither a route nor a generated redirect and 404s. It is the parent of
|
||||
// every documentation link on the site and the likeliest hand-typed entry
|
||||
// point, so open it on the Introduction instead. The .md sibling keeps the
|
||||
// Markdown surface whole for agents that reach for it.
|
||||
{ source: "/docs", destination: "/docs/introduction", permanent: true },
|
||||
{ source: "/docs.md", destination: "/docs/introduction.md", permanent: true },
|
||||
...legacyDocsRedirects,
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default withMDX(nextConfig);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@zero-native/docs",
|
||||
"name": "@native-sdk/docs",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -9,7 +9,7 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"check": "pnpm typecheck && pnpm build"
|
||||
"check": "pnpm typecheck && pnpm build && node scripts/check-doc-routes.mjs && node scripts/check-code-toggle.mjs && node scripts/check-wasm-preview.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdx-js/loader": "^3",
|
||||
@@ -17,11 +17,12 @@
|
||||
"@next/mdx": "^16.1.6",
|
||||
"clsx": "^2.1.1",
|
||||
"geist": "^1.7.0",
|
||||
"next": "^16.1.6",
|
||||
"next": "^16.2.9",
|
||||
"next-themes": "^0.4.6",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"shiki": "^4.0.2",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
@@ -34,5 +35,10 @@
|
||||
"@types/react-dom": "^19",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"postcss@<8.5.10": ">=8.5.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
postcss@<8.5.10: '>=8.5.10'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -22,10 +25,10 @@ importers:
|
||||
version: 2.1.1
|
||||
geist:
|
||||
specifier: ^1.7.0
|
||||
version: 1.7.0(next@16.2.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6))
|
||||
version: 1.7.0(next@16.2.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6))
|
||||
next:
|
||||
specifier: ^16.1.6
|
||||
version: 16.2.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
specifier: ^16.2.9
|
||||
version: 16.2.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
@@ -38,6 +41,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^19
|
||||
version: 19.2.6(react@19.2.6)
|
||||
remark-gfm:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
shiki:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
@@ -124,89 +130,105 @@ packages:
|
||||
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.4':
|
||||
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-riscv64@1.2.4':
|
||||
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.4':
|
||||
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.34.5':
|
||||
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-ppc64@0.34.5':
|
||||
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-riscv64@0.34.5':
|
||||
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.5':
|
||||
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.34.5':
|
||||
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
|
||||
@@ -264,8 +286,8 @@ packages:
|
||||
'@types/react': '>=16'
|
||||
react: '>=16'
|
||||
|
||||
'@next/env@16.2.5':
|
||||
resolution: {integrity: sha512-Lb9ElHD2klcyeVD25vW+siPFqz9QMzDUSgvFZNO+dZEKoMHex4viJhVuzBhrXKqb+UKnih7mVYbt50/7KLsSCA==}
|
||||
'@next/env@16.2.9':
|
||||
resolution: {integrity: sha512-ki5VxxXfzD/9TDe13wyeTKIjQTAwBVpnr8KhRDUr8ltMUq1/NBpWNT5tiPoxiGl+PHM4X2ahSOiPk6iAimIzPg==}
|
||||
|
||||
'@next/mdx@16.2.5':
|
||||
resolution: {integrity: sha512-U1r0I3Ga5/PYKH+loar1OfWCjkZXwG6qFovDzyAFPI2Nxi9gLOWZQ3dLNC5znSGLPToJauRbqgi3kfkKEFqNig==}
|
||||
@@ -278,50 +300,54 @@ packages:
|
||||
'@mdx-js/react':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-arm64@16.2.5':
|
||||
resolution: {integrity: sha512-BW+8PGVmsruomXHsitD8JG6gny9lEdobctjBwvtPF8AKtxGDR7nR35FOl/oK9UAPXBOBm+vx0k8qtpeHOXQMGQ==}
|
||||
'@next/swc-darwin-arm64@16.2.9':
|
||||
resolution: {integrity: sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@16.2.5':
|
||||
resolution: {integrity: sha512-ZoCGnCl9LlQJWmqXrZAUlNxvuNmclvE+7zUif+nDydkkehl9FKxHJ+wxSQMj+C37BYFerKiEdX9s9o02ir975Q==}
|
||||
'@next/swc-darwin-x64@16.2.9':
|
||||
resolution: {integrity: sha512-7IAtK4MeybpqRV9GRABWEhJ62mOS+rzWOzOTFie4cSEtm12xsoOMJRcECoZx3FHPzFAqN/IJtHqWAFOLfl152w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.2.5':
|
||||
resolution: {integrity: sha512-AwcZzMChaWkOTZt3vu+2ZMIj8g4dYQY+B8VUVhlFSQ2JtvyZpefyYHTe00D6b6L7BysYw7vl3zsvs9jix8tl5Q==}
|
||||
'@next/swc-linux-arm64-gnu@16.2.9':
|
||||
resolution: {integrity: sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.2.5':
|
||||
resolution: {integrity: sha512-QqMgqWbCBFsfiQ7BF3dUlW8HJy1LWhpcqbTpoHMWA9IV+TnWwDKozQJA5NdIAHjQ00yX2Q7AUkLr/XK4n77q8A==}
|
||||
'@next/swc-linux-arm64-musl@16.2.9':
|
||||
resolution: {integrity: sha512-qZTI3pf9SGc/obr8NkQAekBxmp1QK+kVm+VAf3BALLfFAj+1kUhkTxmrWpVos9R/UYIA8AWX2p6cGI5WdwzVUA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.2.5':
|
||||
resolution: {integrity: sha512-3hzeiFGZtyATVx9pCeuzTshXmh50vHZitqaeZiyJZaUmjQyrfjsVUgS8apOj1vEJCIpKJM/55F45yPAV2kpjsA==}
|
||||
'@next/swc-linux-x64-gnu@16.2.9':
|
||||
resolution: {integrity: sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@16.2.5':
|
||||
resolution: {integrity: sha512-0mzZV/mAt7Qj2tYNdTB6AqrS8dwng/AQLSYC5Z1YLpZdi2wxqKDPK7RY2RvjB1fXyJfOfdA3l/yTF5yLi+WfuQ==}
|
||||
'@next/swc-linux-x64-musl@16.2.9':
|
||||
resolution: {integrity: sha512-QumimHkGEG6vM3PfEDWKyKen03NcqLOkeKB1EfcPe7VxzmEiCa4jNnMyBn/US5zcd/VE1CI+O8Ovb3lfjVHfGw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.2.5':
|
||||
resolution: {integrity: sha512-f/H4nZ2zJBvA8/+HpsB9mNonF9zfQoAU6D0WxJrfzhJDvJLfngVN85oqxUyrDVK99DIFfFYhLpGa5K+c5uotSw==}
|
||||
'@next/swc-win32-arm64-msvc@16.2.9':
|
||||
resolution: {integrity: sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.2.5':
|
||||
resolution: {integrity: sha512-nuP7DHs4koAojsIxVPkihNgKiRUKtCU65j5X6DAbSy8VBrfT/o90bCLLHPf51JEdOZwZMFzM6e0NiGWfIWjVAg==}
|
||||
'@next/swc-win32-x64-msvc@16.2.9':
|
||||
resolution: {integrity: sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -1088,24 +1114,28 @@ packages:
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.2.4':
|
||||
resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.2.4':
|
||||
resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.2.4':
|
||||
resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.2.4':
|
||||
resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==}
|
||||
@@ -1275,6 +1305,10 @@ packages:
|
||||
esast-util-from-js@2.0.1:
|
||||
resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==}
|
||||
|
||||
escape-string-regexp@5.0.0:
|
||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
estree-util-attach-comments@3.0.0:
|
||||
resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==}
|
||||
|
||||
@@ -1384,24 +1418,28 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||
@@ -1429,9 +1467,33 @@ packages:
|
||||
resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
markdown-table@3.0.4:
|
||||
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
|
||||
|
||||
mdast-util-find-and-replace@3.0.2:
|
||||
resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
|
||||
|
||||
mdast-util-from-markdown@2.0.3:
|
||||
resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==}
|
||||
|
||||
mdast-util-gfm-autolink-literal@2.0.1:
|
||||
resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
|
||||
|
||||
mdast-util-gfm-footnote@2.1.0:
|
||||
resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==}
|
||||
|
||||
mdast-util-gfm-strikethrough@2.0.0:
|
||||
resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
|
||||
|
||||
mdast-util-gfm-table@2.0.0:
|
||||
resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
|
||||
|
||||
mdast-util-gfm-task-list-item@2.0.0:
|
||||
resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
|
||||
|
||||
mdast-util-gfm@3.1.0:
|
||||
resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
|
||||
|
||||
mdast-util-mdx-expression@2.0.1:
|
||||
resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
|
||||
|
||||
@@ -1459,6 +1521,27 @@ packages:
|
||||
micromark-core-commonmark@2.0.3:
|
||||
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
|
||||
|
||||
micromark-extension-gfm-autolink-literal@2.1.0:
|
||||
resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
|
||||
|
||||
micromark-extension-gfm-footnote@2.1.0:
|
||||
resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==}
|
||||
|
||||
micromark-extension-gfm-strikethrough@2.1.0:
|
||||
resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==}
|
||||
|
||||
micromark-extension-gfm-table@2.1.1:
|
||||
resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==}
|
||||
|
||||
micromark-extension-gfm-tagfilter@2.0.0:
|
||||
resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
|
||||
|
||||
micromark-extension-gfm-task-list-item@2.1.0:
|
||||
resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==}
|
||||
|
||||
micromark-extension-gfm@3.0.0:
|
||||
resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
|
||||
|
||||
micromark-extension-mdx-expression@3.0.1:
|
||||
resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==}
|
||||
|
||||
@@ -1548,14 +1631,19 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
nanoid@3.3.15:
|
||||
resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
next-themes@0.4.6:
|
||||
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
||||
|
||||
next@16.2.5:
|
||||
resolution: {integrity: sha512-TkVTm9F2WEulkgGljm4wPwNgvCCWCVw6StUHsZb8WZpHFRjepoUWg3d7L4IMg7IyjcJ4Co9eVhpro8e8O+KarQ==}
|
||||
next@16.2.9:
|
||||
resolution: {integrity: sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -1587,14 +1675,14 @@ packages:
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
postcss@8.4.31:
|
||||
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postcss@8.5.14:
|
||||
resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postcss@8.5.16:
|
||||
resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
property-information@7.1.0:
|
||||
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
||||
|
||||
@@ -1676,6 +1764,9 @@ packages:
|
||||
rehype-recma@1.0.0:
|
||||
resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==}
|
||||
|
||||
remark-gfm@4.0.1:
|
||||
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
|
||||
|
||||
remark-mdx@3.1.1:
|
||||
resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==}
|
||||
|
||||
@@ -1685,6 +1776,9 @@ packages:
|
||||
remark-rehype@11.1.2:
|
||||
resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
|
||||
|
||||
remark-stringify@11.0.0:
|
||||
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
||||
|
||||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
@@ -2006,7 +2100,7 @@ snapshots:
|
||||
'@types/react': 19.2.14
|
||||
react: 19.2.6
|
||||
|
||||
'@next/env@16.2.5': {}
|
||||
'@next/env@16.2.9': {}
|
||||
|
||||
'@next/mdx@16.2.5(@mdx-js/loader@3.1.1)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.6))':
|
||||
dependencies:
|
||||
@@ -2015,28 +2109,28 @@ snapshots:
|
||||
'@mdx-js/loader': 3.1.1
|
||||
'@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.6)
|
||||
|
||||
'@next/swc-darwin-arm64@16.2.5':
|
||||
'@next/swc-darwin-arm64@16.2.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@16.2.5':
|
||||
'@next/swc-darwin-x64@16.2.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.2.5':
|
||||
'@next/swc-linux-arm64-gnu@16.2.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.2.5':
|
||||
'@next/swc-linux-arm64-musl@16.2.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.2.5':
|
||||
'@next/swc-linux-x64-gnu@16.2.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@16.2.5':
|
||||
'@next/swc-linux-x64-musl@16.2.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.2.5':
|
||||
'@next/swc-win32-arm64-msvc@16.2.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.2.5':
|
||||
'@next/swc-win32-x64-msvc@16.2.9':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/number@1.1.1': {}
|
||||
@@ -3014,6 +3108,8 @@ snapshots:
|
||||
esast-util-from-estree: 2.0.0
|
||||
vfile-message: 4.0.3
|
||||
|
||||
escape-string-regexp@5.0.0: {}
|
||||
|
||||
estree-util-attach-comments@3.0.0:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.9
|
||||
@@ -3049,9 +3145,9 @@ snapshots:
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
geist@1.7.0(next@16.2.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)):
|
||||
geist@1.7.0(next@16.2.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)):
|
||||
dependencies:
|
||||
next: 16.2.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
next: 16.2.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
|
||||
get-nonce@1.0.1: {}
|
||||
|
||||
@@ -3192,6 +3288,15 @@ snapshots:
|
||||
|
||||
markdown-extensions@2.0.0: {}
|
||||
|
||||
markdown-table@3.0.4: {}
|
||||
|
||||
mdast-util-find-and-replace@3.0.2:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
escape-string-regexp: 5.0.0
|
||||
unist-util-is: 6.0.1
|
||||
unist-util-visit-parents: 6.0.2
|
||||
|
||||
mdast-util-from-markdown@2.0.3:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
@@ -3209,6 +3314,63 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
mdast-util-gfm-autolink-literal@2.0.1:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
ccount: 2.0.1
|
||||
devlop: 1.1.0
|
||||
mdast-util-find-and-replace: 3.0.2
|
||||
micromark-util-character: 2.1.1
|
||||
|
||||
mdast-util-gfm-footnote@2.1.0:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
devlop: 1.1.0
|
||||
mdast-util-from-markdown: 2.0.3
|
||||
mdast-util-to-markdown: 2.1.2
|
||||
micromark-util-normalize-identifier: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
mdast-util-gfm-strikethrough@2.0.0:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
mdast-util-from-markdown: 2.0.3
|
||||
mdast-util-to-markdown: 2.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
mdast-util-gfm-table@2.0.0:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
devlop: 1.1.0
|
||||
markdown-table: 3.0.4
|
||||
mdast-util-from-markdown: 2.0.3
|
||||
mdast-util-to-markdown: 2.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
mdast-util-gfm-task-list-item@2.0.0:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
devlop: 1.1.0
|
||||
mdast-util-from-markdown: 2.0.3
|
||||
mdast-util-to-markdown: 2.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
mdast-util-gfm@3.1.0:
|
||||
dependencies:
|
||||
mdast-util-from-markdown: 2.0.3
|
||||
mdast-util-gfm-autolink-literal: 2.0.1
|
||||
mdast-util-gfm-footnote: 2.1.0
|
||||
mdast-util-gfm-strikethrough: 2.0.0
|
||||
mdast-util-gfm-table: 2.0.0
|
||||
mdast-util-gfm-task-list-item: 2.0.0
|
||||
mdast-util-to-markdown: 2.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
mdast-util-mdx-expression@2.0.1:
|
||||
dependencies:
|
||||
'@types/estree-jsx': 1.0.5
|
||||
@@ -3310,6 +3472,64 @@ snapshots:
|
||||
micromark-util-symbol: 2.0.1
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-gfm-autolink-literal@2.1.0:
|
||||
dependencies:
|
||||
micromark-util-character: 2.1.1
|
||||
micromark-util-sanitize-uri: 2.0.1
|
||||
micromark-util-symbol: 2.0.1
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-gfm-footnote@2.1.0:
|
||||
dependencies:
|
||||
devlop: 1.1.0
|
||||
micromark-core-commonmark: 2.0.3
|
||||
micromark-factory-space: 2.0.1
|
||||
micromark-util-character: 2.1.1
|
||||
micromark-util-normalize-identifier: 2.0.1
|
||||
micromark-util-sanitize-uri: 2.0.1
|
||||
micromark-util-symbol: 2.0.1
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-gfm-strikethrough@2.1.0:
|
||||
dependencies:
|
||||
devlop: 1.1.0
|
||||
micromark-util-chunked: 2.0.1
|
||||
micromark-util-classify-character: 2.0.1
|
||||
micromark-util-resolve-all: 2.0.1
|
||||
micromark-util-symbol: 2.0.1
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-gfm-table@2.1.1:
|
||||
dependencies:
|
||||
devlop: 1.1.0
|
||||
micromark-factory-space: 2.0.1
|
||||
micromark-util-character: 2.1.1
|
||||
micromark-util-symbol: 2.0.1
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-gfm-tagfilter@2.0.0:
|
||||
dependencies:
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-gfm-task-list-item@2.1.0:
|
||||
dependencies:
|
||||
devlop: 1.1.0
|
||||
micromark-factory-space: 2.0.1
|
||||
micromark-util-character: 2.1.1
|
||||
micromark-util-symbol: 2.0.1
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-gfm@3.0.0:
|
||||
dependencies:
|
||||
micromark-extension-gfm-autolink-literal: 2.1.0
|
||||
micromark-extension-gfm-footnote: 2.1.0
|
||||
micromark-extension-gfm-strikethrough: 2.1.0
|
||||
micromark-extension-gfm-table: 2.1.1
|
||||
micromark-extension-gfm-tagfilter: 2.0.0
|
||||
micromark-extension-gfm-task-list-item: 2.1.0
|
||||
micromark-util-combine-extensions: 2.0.1
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-mdx-expression@3.0.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.9
|
||||
@@ -3501,30 +3721,32 @@ snapshots:
|
||||
|
||||
nanoid@3.3.12: {}
|
||||
|
||||
nanoid@3.3.15: {}
|
||||
|
||||
next-themes@0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
|
||||
dependencies:
|
||||
react: 19.2.6
|
||||
react-dom: 19.2.6(react@19.2.6)
|
||||
|
||||
next@16.2.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
|
||||
next@16.2.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
|
||||
dependencies:
|
||||
'@next/env': 16.2.5
|
||||
'@next/env': 16.2.9
|
||||
'@swc/helpers': 0.5.15
|
||||
baseline-browser-mapping: 2.10.27
|
||||
caniuse-lite: 1.0.30001792
|
||||
postcss: 8.4.31
|
||||
postcss: 8.5.16
|
||||
react: 19.2.6
|
||||
react-dom: 19.2.6(react@19.2.6)
|
||||
styled-jsx: 5.1.6(react@19.2.6)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 16.2.5
|
||||
'@next/swc-darwin-x64': 16.2.5
|
||||
'@next/swc-linux-arm64-gnu': 16.2.5
|
||||
'@next/swc-linux-arm64-musl': 16.2.5
|
||||
'@next/swc-linux-x64-gnu': 16.2.5
|
||||
'@next/swc-linux-x64-musl': 16.2.5
|
||||
'@next/swc-win32-arm64-msvc': 16.2.5
|
||||
'@next/swc-win32-x64-msvc': 16.2.5
|
||||
'@next/swc-darwin-arm64': 16.2.9
|
||||
'@next/swc-darwin-x64': 16.2.9
|
||||
'@next/swc-linux-arm64-gnu': 16.2.9
|
||||
'@next/swc-linux-arm64-musl': 16.2.9
|
||||
'@next/swc-linux-x64-gnu': 16.2.9
|
||||
'@next/swc-linux-x64-musl': 16.2.9
|
||||
'@next/swc-win32-arm64-msvc': 16.2.9
|
||||
'@next/swc-win32-x64-msvc': 16.2.9
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -3550,15 +3772,15 @@ snapshots:
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
postcss@8.4.31:
|
||||
postcss@8.5.14:
|
||||
dependencies:
|
||||
nanoid: 3.3.12
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
postcss@8.5.14:
|
||||
postcss@8.5.16:
|
||||
dependencies:
|
||||
nanoid: 3.3.12
|
||||
nanoid: 3.3.15
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@@ -3708,6 +3930,17 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
remark-gfm@4.0.1:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
mdast-util-gfm: 3.1.0
|
||||
micromark-extension-gfm: 3.0.0
|
||||
remark-parse: 11.0.0
|
||||
remark-stringify: 11.0.0
|
||||
unified: 11.0.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
remark-mdx@3.1.1:
|
||||
dependencies:
|
||||
mdast-util-mdx: 3.0.0
|
||||
@@ -3732,6 +3965,12 @@ snapshots:
|
||||
unified: 11.0.5
|
||||
vfile: 6.0.3
|
||||
|
||||
remark-stringify@11.0.0:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
mdast-util-to-markdown: 2.1.2
|
||||
unified: 11.0.5
|
||||
|
||||
scheduler@0.27.0: {}
|
||||
|
||||
semver@7.7.4:
|
||||
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 614 B |
|
After Width: | Height: | Size: 634 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 1010 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 822 B |
|
After Width: | Height: | Size: 832 B |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |