commit 505bac6b53c51447107d66a7c7ee1363ebdb1eba Author: wehub-resource-sync Date: Mon Jul 13 12:29:49 2026 +0800 chore: import upstream snapshot with attribution diff --git a/.github/assets/calculator-dark.webp b/.github/assets/calculator-dark.webp new file mode 100644 index 0000000..d83aeaa Binary files /dev/null and b/.github/assets/calculator-dark.webp differ diff --git a/.github/assets/calculator-light.webp b/.github/assets/calculator-light.webp new file mode 100644 index 0000000..52093a2 Binary files /dev/null and b/.github/assets/calculator-light.webp differ diff --git a/.github/assets/notes-dark.webp b/.github/assets/notes-dark.webp new file mode 100644 index 0000000..3b18710 Binary files /dev/null and b/.github/assets/notes-dark.webp differ diff --git a/.github/assets/notes-light.webp b/.github/assets/notes-light.webp new file mode 100644 index 0000000..0deef69 Binary files /dev/null and b/.github/assets/notes-light.webp differ diff --git a/.github/assets/soundboard-dark.webp b/.github/assets/soundboard-dark.webp new file mode 100644 index 0000000..517b4ee Binary files /dev/null and b/.github/assets/soundboard-dark.webp differ diff --git a/.github/assets/soundboard-light.webp b/.github/assets/soundboard-light.webp new file mode 100644 index 0000000..1e7ac51 Binary files /dev/null and b/.github/assets/soundboard-light.webp differ diff --git a/.github/scripts/linux-canvas-smoke.sh b/.github/scripts/linux-canvas-smoke.sh new file mode 100755 index 0000000..3541882 --- /dev/null +++ b/.github/scripts/linux-canvas-smoke.sh @@ -0,0 +1,152 @@ +#!/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. automate screenshot renders a non-empty PNG +# 7. 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="" +cleanup() { + [ -n "$app_pid" ] && kill "$app_pid" >/dev/null 2>&1 + # xvfb-run does not forward signals to an already-detached app; reap the + # app and its 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 +} +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 +} + +# 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 --------------------------------------------------------------- +cd "$app_dir" || fail "missing $app_dir" +rm -rf .zig-cache/native-sdk-automation +xvfb-run -a "$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: 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" + +# ---- 7: 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 diff --git a/.github/scripts/windows-canvas-smoke.sh b/.github/scripts/windows-canvas-smoke.sh new file mode 100755 index 0000000..8e3e332 --- /dev/null +++ b/.github/scripts/windows-canvas-smoke.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +# Windows canvas smoke under Wine. +# +# Exercises the Windows gpu_surface software path (src/platform/windows/ +# webview2_host.cpp: child HWND, WM_TIMER frame events, SetDIBitsToDevice +# blits) without Windows hardware: 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=software (the SetDIBitsToDevice 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 : wait until $snap contains . +poll() { + local deadline=$((SECONDS + $1)) + while [ "$SECONDS" -lt "$deadline" ]; do + [ -f "$snap" ] && grep -q "$2" "$snap" && return 0 + sleep 0.5 + 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: software backend presented non-blank pixels -------------------- +poll 60 '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)" \ + "$(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=$(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" +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")" +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=$((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=$(grep -o 'widget @w1/inbox-canvas#[0-9]* role=textbox[^|]*' "$snap" | head -1) +[ -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 diff --git a/.github/scripts/windows-effects-smoke.sh b/.github/scripts/windows-effects-smoke.sh new file mode 100755 index 0000000..94b62c8 --- /dev/null +++ b/.github/scripts/windows-effects-smoke.sh @@ -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=software + 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 : wait until $snap contains . +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 : 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: software backend presented non-blank pixels ------------------------ +poll 60 '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)" +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 diff --git a/.github/workflows/cef-runtime.yml b/.github/workflows/cef-runtime.yml new file mode 100644 index 0000000..e1a4e44 --- /dev/null +++ b/.github/workflows/cef-runtime.yml @@ -0,0 +1,133 @@ +name: CEF Runtime + +on: + workflow_dispatch: + inputs: + cef_version: + description: "CEF version to package" + required: true + default: "144.0.6+g5f7e671+chromium-144.0.7559.59" + source: + description: "Build from official archive or CEF source" + required: true + type: choice + default: "official" + options: + - official + - source + cef_branch: + description: "Optional CEF branch for source builds" + required: false + default: "" + +permissions: + contents: write + +jobs: + build: + name: Build ${{ matrix.platform }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - platform: macosarm64 + runner: macos-14 + - platform: macosx64 + runner: macos-15 + - platform: linux64 + runner: ubuntu-latest + - platform: linuxarm64 + runner: ubuntu-24.04-arm + - platform: windows64 + runner: windows-2022 + # 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 + with: + version: 0.16.0 + + - name: Download official CEF + if: ${{ inputs.source == 'official' }} + shell: bash + run: | + set -euo pipefail + version="${{ inputs.cef_version }}" + platform="${{ matrix.platform }}" + archive="cef_binary_${version}_${platform}.tar.bz2" + base="https://cef-builds.spotifycdn.com" + mkdir -p zig-out/cef-download + 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")" + 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" + + - name: Build CEF wrapper + if: ${{ inputs.source == 'official' }} + shell: bash + run: | + set -euo pipefail + 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 + windows*) wrapper="libcef_dll_wrapper.lib" ;; + *) wrapper="libcef_dll_wrapper.a" ;; + esac + find "$CEF_ROOT/build/libcef_dll_wrapper" -name "$wrapper" -print -quit | xargs -I{} cp "{}" "$CEF_ROOT/libcef_dll_wrapper/$wrapper" + + - name: Prepare native-sdk runtime archive + if: ${{ inputs.source == 'official' }} + shell: bash + run: | + zig build + 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 + if: ${{ inputs.source == 'source' }} + shell: bash + run: | + zig build + chmod +x tools/cef/build-from-source.sh + args=( + tools/cef/build-from-source.sh + --platform "${{ matrix.platform }}" + --version "${{ inputs.cef_version }}" + --output zig-out/cef + --native-sdk-bin zig-out/bin/native + ) + if [ -n "${{ inputs.cef_branch }}" ]; then + args+=(--cef-branch "${{ inputs.cef_branch }}") + fi + "${args[@]}" + + - name: Publish release asset + uses: softprops/action-gh-release@v2 + with: + tag_name: cef-${{ inputs.cef_version }} + name: CEF ${{ inputs.cef_version }} + files: | + 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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8245730 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,335 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + zig: + name: Zig Core + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + - uses: actions/setup-node@v4 + with: + node-version: 22 + # The TypeScript core suites transpile at build/test time; without + # node they skip silently, so CI must provide it. + - run: npm ci --prefix packages/core + - run: zig build test + - run: zig build validate + + macos-webview: + name: macOS WebView + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + - run: zig build test-webview-system-link + - run: zig build test-webview-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: mlugg/setup-zig@v2 + with: + version: 0.16.0 + # Percentile perf check: 5 cold launches asserting p90 + # first-frame latency under NATIVE_SDK_PERF_BUDGET_MS, then 5 steady-state + # widget clicks asserting p90 input latency under NATIVE_SDK_PERF_INPUT_BUDGET_MS. + # 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" + + linux-webkitgtk: + name: Linux WebKitGTK + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + 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 + 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 + + cef-platform-tooling: + name: CEF Platform Tooling + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + - uses: actions/setup-node@v4 + with: + node-version: 22 + # The TypeScript core suites transpile at build/test time; without + # node they skip silently, so CI must provide it. + - run: npm ci --prefix packages/core + - run: zig build test-tooling + + npm-package: + name: npm Package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + - run: npm --prefix packages/native-sdk run version:check + - run: npm --prefix packages/native-sdk run scripts:check + + native-examples: + name: Native Examples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + - uses: actions/setup-node@v4 + with: + node-version: 22 + # The TypeScript examples transpile at build time; the transpiler + # needs its installed dependency. + - run: npm ci --prefix packages/core + - name: Install GTK dependencies + run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev + - run: zig build test-examples-native + # 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 + + linux-canvas-smoke: + name: Linux Canvas Smoke + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + 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.) + - name: Install GTK and Xvfb + run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev xvfb + # Drives the gpu_surface software path under Xvfb: snapshot ready, + # gpu_backend=software, gpu_nonblank=true, automation widget-click, + # 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) + # all live in the script. + - name: Build and drive ui-inbox headless + run: .github/scripts/linux-canvas-smoke.sh + + windows-canvas-smoke: + name: Windows Canvas Smoke (Wine) + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + - name: Install Wine, Xvfb, and xdotool + run: sudo apt-get update && sudo apt-get install -y wine xvfb xdotool + # Cross-compiles ui-inbox for x86_64-windows-gnu and drives the + # gpu_surface software path (child HWND + WM_TIMER + SetDIBitsToDevice) + # under Wine: snapshot ready, gpu_backend=software, 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: mlugg/setup-zig@v2 + 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 + with: + version: 0.16.0 + - run: zig build test-examples-frontends + + mobile-examples: + name: Mobile Examples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + - run: zig build test-examples-mobile + + scaffold: + name: Generated App Scaffolds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + 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 transpiler 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 native next vite react svelte vue; do + app=".zig-cache/scaffold-${frontend}" + rm -rf "$app" + ./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 + 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: docs/package.json + - run: pnpm install --frozen-lockfile + working-directory: docs + - run: pnpm check + working-directory: docs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0842dc9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,255 @@ +name: Release + +on: + push: + branches: + - main + workflow_dispatch: + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + check-release: + name: Check for new version + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + outputs: + should_release: ${{ steps.check.outputs.should_release }} + needs_github_release: ${{ steps.check.outputs.needs_github_release }} + version: ${{ steps.check.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + + - name: Compare package.json version to npm and check GitHub release + id: check + run: | + LOCAL_VERSION=$(node -p "require('./packages/native-sdk/package.json').version") + echo "Local version: $LOCAL_VERSION" + + 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 + echo "Version changed: $NPM_VERSION -> $LOCAL_VERSION" + echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "needs_github_release=true" >> "$GITHUB_OUTPUT" + else + echo "Version unchanged on npm, skipping publish" + echo "should_release=false" >> "$GITHUB_OUTPUT" + + TAG="v$LOCAL_VERSION" + EXISTING_ASSETS=$(gh release view "$TAG" --json assets --jq '.assets[].name' 2>/dev/null || true) + missing=0 + for asset in \ + CHECKSUMS.txt \ + 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" + missing=1 + fi + done + + if [ "$missing" -eq 0 ]; then + echo "GitHub release $TAG exists with native assets" + echo "needs_github_release=false" >> "$GITHUB_OUTPUT" + else + echo "GitHub release $TAG is missing native assets, will create/update it" + echo "needs_github_release=true" >> "$GITHUB_OUTPUT" + fi + fi + + echo "version=$LOCAL_VERSION" >> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + github-release: + name: Create GitHub Release + needs: check-release + if: needs.check-release.outputs.needs_github_release == 'true' + runs-on: macos-14 + timeout-minutes: 10 + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + + - name: Extract changelog entry + run: | + VERSION="${{ needs.check-release.outputs.version }}" + awk '//{found=1; next} //{exit} found{print}' CHANGELOG.md > /tmp/release-notes.md + + LINES=$(wc -l < /tmp/release-notes.md | tr -d ' ') + if [ "$LINES" -lt 2 ]; then + echo "Error: No release notes found between and markers in CHANGELOG.md" + exit 1 + fi + echo "Extracted release notes for $VERSION ($LINES lines)" + + - name: Build native release assets (all platforms) + run: | + # 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: | + VERSION="${{ needs.check-release.outputs.version }}" + TAG="v$VERSION" + + if gh release view "$TAG" &>/dev/null; then + echo "Release $TAG already exists, updating assets" + else + echo "Creating release $TAG..." + gh release create "$TAG" \ + --title "$TAG" \ + --notes-file /tmp/release-notes.md + fi + + gh release upload "$TAG" \ + zig-out/release/native-sdk-* \ + zig-out/release/CHECKSUMS.txt \ + --clobber + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish: + name: Publish CLI to npm + needs: [check-release, github-release] + if: >- + always() + && needs.check-release.outputs.should_release == 'true' + && (needs.github-release.result == 'success' || needs.github-release.result == 'skipped') + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: Release + permissions: + contents: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + 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/native-sdk run version:check + + - name: Check package scripts + 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: | + 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 + publish_dir packages/core + fi + + publish_dir packages/native-sdk diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0cc77d --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +.DS_Store +.zig-cache/ +zig-out/ +# 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 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/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/ +third_party/cef/windows/ +third_party/cef/linux/ + +# Compiled static libraries +*.a + +# TypeScript build info (generated) +docs/tsconfig.tsbuildinfo +.claude/ + +# Dev-tool dependency trees (installed per package, never committed) +packages/core/node_modules/ + +# 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/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e19fb0c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,33 @@ +# Agent Guide + +Guidance for agents (and humans) working on this repository. + +## Build, test, and gate + +```bash +zig build test # root engine + runtime suites +zig build validate # sample app.zon manifest check +zig build test-example- # 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` directly. Each user-visible change ships a fragment in `changelog.d/` — see `changelog.d/README.md` for the format and voice. Internal-only polish needs no fragment. + +## 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). diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f406288 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,395 @@ +# Changelog + +All notable changes to the Native SDK (formerly zero-native) will be documented in this file. + +## 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 — `@ts` scaffolds a full TypeScript app (`native init --template ts-core`), `@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, `` 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**: `` 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` → `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 ` 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) ` 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, `` 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: `` splices template files (transitively, with cycle and duplicate diagnostics), template args take literal defaults, `` 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 (`` / `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 (`` / `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 (``): 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 + +## 0.1.8 + +### Bug Fixes + +- **Install completion delay** - Drain redirected GitHub responses during postinstall so npm exits immediately after the native binary is installed. + +### Contributors + +- @ctate + +## 0.1.7 + +### Improvements + +- **Install progress** - Show native binary download progress and checksum status during the npm postinstall step. + +### Contributors + +- @ctate + +## 0.1.6 + +### Improvements + +- **Init next steps** - Print the follow-up commands after scaffolding so users can immediately run their new app. + +### Contributors + +- @ctate + +## 0.1.5 + +### 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. + +### Contributors + +- @ctate + +## 0.1.4 + +### 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`. +- **Long scaffold names** - Keep generated Zig package names within Zig's 32-character manifest limit. +- **Next scaffold builds** - Include the Node.js type package that Next expects for TypeScript projects. +- **Frontend dependency versions** - Generate projects with current Next, React, Vite, Vue, Svelte, and plugin versions. +- **Svelte scaffold builds** - Use the matching Svelte Vite plugin in generated Svelte projects. + +### Contributors + +- @ctate + +## 0.1.3 + +### Bug Fixes + +- **CLI package homepage** - Point npm package metadata at `https://zero-native.dev`. +- **Current-directory init** - Support `zero-native init --frontend ` as shorthand for scaffolding into the current directory. +- **CLI usage errors** - Exit cleanly for invalid CLI arguments instead of printing Zig stack traces for expected user input mistakes. + +### Contributors + +- @ctate + +## 0.1.2 + +### Bug Fixes + +- **npm install fallback** - Do not fail package installation or point global shims at missing binaries when a native release asset is unavailable. +- **Release asset ordering** - Upload the macOS arm64 native binary and `CHECKSUMS.txt` before publishing the npm package so postinstall downloads succeed immediately. + +### Contributors + +- @ctate + +## 0.1.1 + +### Bug Fixes + +- **npm package homepage** - Add the zero-native repository homepage to the CLI package metadata. +- **Chromium example launches** - Stage the CEF framework correctly for the `hello` and `webview` examples when running with `-Dweb-engine=chromium`. +- **Linux WebKitGTK build** - Update navigation policy and external URI handling for current WebKitGTK and GTK4 headers. +- **macOS WebView smoke test** - Use the emitted CLI binary and queue automation early enough for stable CI smoke tests. + +### Release Process + +- **GitHub releases** - Create missing GitHub releases from marked changelog entries when npm already has the version. +- **CEF runtime release** - Publish the prepared macOS arm64 CEF runtime used by `zero-native cef install`. + +### Contributors + +- @ctate + +## 0.1.0 + +### Initial Release + +- Initial pre-release development version. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3173fb9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,128 @@ +# Contributing + +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 [native-sdk.dev](https://native-sdk.dev). + +## Prerequisites + +- [Zig 0.16.0+](https://ziglang.org/download/) +- Node.js with npm for the CLI package and generated frontend projects +- pnpm for the documentation site +- macOS for WKWebView and Chromium/CEF development +- Linux with GTK4 and WebKitGTK 6 for Linux system WebView development + +## Local Checks + +Run the toolkit tests: + +```bash +zig build test +``` + +Validate the sample app manifest: + +```bash +zig build validate +``` + +Build the WebView example against the system engine: + +```bash +zig build test-webview-system-link +``` + +Run the WebView example: + +```bash +zig build run-webview +``` + +Check the npm CLI package: + +```bash +npm --prefix packages/native-sdk run version:check +npm --prefix packages/native-sdk run scripts:check +``` + +Check the documentation site: + +```bash +pnpm --dir docs install --frozen-lockfile +pnpm --dir docs check +``` + +## Web Engine Development + +The system WebView path is the default development loop: + +```bash +zig build run-webview -Dweb-engine=system +``` + +For Chromium on macOS, install CEF and run with the Chromium engine: + +```bash +native cef install +zig build run-webview -Dweb-engine=chromium +``` + +Useful Chromium smoke checks: + +```bash +zig build test-webview-cef-smoke -Dplatform=macos -Dweb-engine=chromium +zig build test-package-cef-layout -Dplatform=macos +``` + +## Packaging Development + +Create a local package artifact: + +```bash +zig build package +``` + +Package explicitly through the CLI: + +```bash +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: + +```bash +zig build run-webview -Dautomation=true +``` + +Interact with the running app: + +```bash +native automate wait +native automate list +native automate bridge '{"id":"ping","command":"native.ping","payload":null}' +``` + +Automation writes artifacts under `.zig-cache/native-sdk-automation`. + + +## Making a Pull Request + +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 +``` + +If the change is user-visible, add a changelog fragment in `changelog.d/` (see [changelog.d/README.md](./changelog.d/README.md)) instead of editing `CHANGELOG.md`. 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. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4f6f639 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc7e23c --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# Native SDK + +**Native SDK is the complete toolkit for building native desktop applications.** + +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. + +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. + + + + The Soundboard example app rendered by the Native SDK engine: a music library with album cover art, search, and a playback bar + + + + + + + +
+ + + The Notes example app rendered by the Native SDK engine: a three-pane notes manager with folders, a note list, and an open note + + + + + The Calculator example app rendered by the Native SDK engine: a finished calculation above a full keypad + +
+ +Soundboard, Notes, and Calculator from examples/ — every pixel drawn by the Native SDK engine, captured through its deterministic reference renderer. The images follow your color scheme. + +## Quick start + +Install the CLI: + +```bash +npm install -g @native-sdk/cli +``` + +Create and run an app: + +```bash +native init my_app +cd my_app +native dev +``` + +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): + +```html + + + {count} + + +``` + +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: + +```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 }; + } +} +``` + +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. + +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. + +Read the full guide at [native-sdk.dev/quick-start](https://native-sdk.dev/quick-start). + +## 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 + +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`. + +| Example | What it shows | +| --- | --- | +| [`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. | + +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. + +## Platforms + +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, IME composition, and HiDPI; Windows runs on a Win32 host with 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. + +## Documentation + +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) diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..083d31f --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`vercel-labs/zero-native` +- 原始仓库:https://github.com/vercel-labs/zero-native +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..8a7058c --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,18 @@ +# 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. Run `scripts/changelog-merge.sh` to fold any pending `changelog.d/` fragments into the `## Unreleased` section +5. Write the changelog entry in `CHANGELOG.md`, wrapped in `` and `` markers +6. 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 +7. Remove the `` and `` markers from the previous release entry; only the latest release should have markers +8. Open a PR and merge to `main` + +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. diff --git a/app.zon b/app.zon new file mode 100644 index 0000000..954b334 --- /dev/null +++ b/app.zon @@ -0,0 +1,30 @@ +.{ + .id = "dev.native_sdk", + .name = "native-sdk", + .display_name = "Native SDK", + .version = "0.1.0", + .icons = .{ "assets/icon.icns", "assets/icon.ico" }, + .platforms = .{ "macos" }, + .permissions = .{ "window" }, + .capabilities = .{ "webview", "js_bridge", "native_module" }, + .bridge = .{ + .commands = .{ + .{ .name = "native.ping", .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 = .{ + .navigation = .{ + .allowed_origins = .{ "zero://app", "zero://inline", "http://127.0.0.1:5173" }, + .external_links = .{ .action = "deny" }, + }, + }, + .web_engine = "system", + .cef = .{ .dir = "third_party/cef/macos", .auto_install = false }, + .windows = .{ + .{ .label = "main", .title = "native-sdk", .width = 720, .height = 480, .restore_state = true }, + }, +} diff --git a/assets/icon.icns b/assets/icon.icns new file mode 100644 index 0000000..98d4303 Binary files /dev/null and b/assets/icon.icns differ diff --git a/assets/icon.ico b/assets/icon.ico new file mode 100644 index 0000000..e2804e0 Binary files /dev/null and b/assets/icon.ico differ diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..6e8503e Binary files /dev/null and b/assets/icon.png differ diff --git a/assets/icon.svg b/assets/icon.svg new file mode 100644 index 0000000..f298ddb --- /dev/null +++ b/assets/icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/native-sdk.entitlements b/assets/native-sdk.entitlements new file mode 100644 index 0000000..64b458c --- /dev/null +++ b/assets/native-sdk.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.network.client + + + diff --git a/assets/native-sdk.manifest b/assets/native-sdk.manifest new file mode 100644 index 0000000..c7b63f9 --- /dev/null +++ b/assets/native-sdk.manifest @@ -0,0 +1,28 @@ + + + + + + + + + + + true/pm + PerMonitorV2, PerMonitor + + + diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..053b77e --- /dev/null +++ b/build.zig @@ -0,0 +1,2968 @@ +const std = @import("std"); +const web_engine_tool = @import("src/tooling/web_engine.zig"); + +const PlatformOption = enum { + auto, + null, + macos, + linux, + windows, +}; + +const TraceOption = enum { + off, + events, + runtime, + all, +}; + +const WebEngineOption = enum { + system, + chromium, +}; + +const PackageTarget = enum { + macos, + windows, + linux, + ios, + android, +}; + +const SigningMode = enum { + none, + adhoc, + identity, +}; + +pub const AppOptions = @import("build/app.zig").AppOptions; +pub const addApp = @import("build/app.zig").addApp; +pub const AppArtifacts = @import("build/app.zig").AppArtifacts; +pub const addAppArtifacts = @import("build/app.zig").addAppArtifacts; +pub const MobileLibOptions = @import("build/app.zig").MobileLibOptions; +pub const addMobileLib = @import("build/app.zig").addMobileLib; +const mobile_export_symbol_names = @import("build/app.zig").mobile_export_symbol_names; + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const host_target = b.graph.host; + const optimize = b.standardOptimizeOption(.{}); + const platform_option = b.option(PlatformOption, "platform", "Desktop backend: auto, null, macos, linux, windows") orelse .auto; + const trace_option = b.option(TraceOption, "trace", "Trace output: off, events, runtime, all") orelse .events; + _ = b.option(bool, "debug-overlay", "Enable debug overlay output") orelse false; + _ = b.option(bool, "automation", "Enable Native SDK automation artifacts") orelse false; + _ = b.option(bool, "webview", "Deprecated compatibility flag; native surfaces are always enabled") orelse true; + const web_engine_override = b.option(WebEngineOption, "web-engine", "Override app.zon web engine: system, chromium"); + const cef_dir_override = b.option([]const u8, "cef-dir", "Override CEF root directory for Chromium builds"); + const cef_auto_install_override = b.option(bool, "cef-auto-install", "Override app.zon CEF auto-install setting"); + _ = b.option(bool, "js-bridge", "Enable optional JavaScript bridge stubs") orelse false; + const package_target = b.option(PackageTarget, "package-target", "Package target: macos, windows, linux, ios, android") orelse .macos; + const signing_mode = b.option(SigningMode, "signing", "Signing mode: none, adhoc, identity") orelse .none; + const package_version = packageVersion(b); + const optimize_name = @tagName(optimize); + // Resolve against THIS build's root: as a dependency of a user app the + // build runner's cwd is the app project, and a cwd-relative "app.zon" + // would read (and panic on) the user's manifest instead of ours. + const app_web_engine = web_engine_tool.readManifestConfig(b.allocator, b.graph.io, b.pathFromRoot("app.zon")) catch |err| { + std.debug.panic("failed to read the framework's own app.zon web engine config: {s}", .{@errorName(err)}); + }; + const resolved_web_engine = web_engine_tool.resolve(app_web_engine, .{ + .web_engine = if (web_engine_override) |value| webEngineFromBuildOption(value) else null, + .cef_dir = cef_dir_override, + .cef_auto_install = cef_auto_install_override, + }) catch |err| { + std.debug.panic("invalid app.zon web engine config: {s}", .{@errorName(err)}); + }; + const web_engine = buildWebEngineFromResolved(resolved_web_engine.engine); + const browser_web_engine: WebEngineOption = web_engine_override orelse .system; + const cef_auto_install = resolved_web_engine.cef_auto_install; + const selected_platform: PlatformOption = switch (platform_option) { + .auto => if (target.result.os.tag == .macos) .macos else if (target.result.os.tag == .linux) .linux else if (target.result.os.tag == .windows) .windows else .null, + else => platform_option, + }; + const cef_dir = cef_dir_override orelse defaultCefDir(selected_platform, resolved_web_engine.cef_dir); + if (selected_platform == .macos and target.result.os.tag != .macos) { + @panic("-Dplatform=macos requires a macOS target"); + } + if (selected_platform == .linux and target.result.os.tag != .linux) { + @panic("-Dplatform=linux requires a Linux target"); + } + if (selected_platform == .windows and target.result.os.tag != .windows) { + @panic("-Dplatform=windows requires a Windows target"); + } + if (web_engine == .chromium and selected_platform != .macos) { + @panic("-Dweb-engine=chromium currently requires -Dplatform=macos"); + } + + const geometry_mod = module(b, target, optimize, "src/primitives/geometry/root.zig"); + const assets_mod = module(b, target, optimize, "src/primitives/assets/root.zig"); + const app_dirs_mod = module(b, target, optimize, "src/primitives/app_dirs/root.zig"); + const trace_mod = module(b, target, optimize, "src/primitives/trace/root.zig"); + const app_manifest_mod = module(b, target, optimize, "src/primitives/app_manifest/root.zig"); + const diagnostics_mod = module(b, target, optimize, "src/primitives/diagnostics/root.zig"); + const platform_info_mod = module(b, target, optimize, "src/primitives/platform_info/root.zig"); + const json_mod = module(b, target, optimize, "src/primitives/json/root.zig"); + const canvas_mod = module(b, target, optimize, "src/primitives/canvas/root.zig"); + canvas_mod.addImport("geometry", geometry_mod); + canvas_mod.addImport("json", json_mod); + if (target.result.os.tag == .macos) { + // The estimator-vs-CoreText agreement test (text_metrics_tests.zig) + // shapes the bundled face through CoreText; apps already link these + // transitively via AppKit. + canvas_mod.linkFramework("CoreFoundation", .{}); + canvas_mod.linkFramework("CoreGraphics", .{}); + canvas_mod.linkFramework("CoreText", .{}); + canvas_mod.linkSystemLibrary("c", .{}); + } + const debug_mod = module(b, target, optimize, "src/debug/root.zig"); + debug_mod.addImport("app_dirs", app_dirs_mod); + debug_mod.addImport("trace", trace_mod); + + const geometry_tests = testArtifact(b, geometry_mod); + const assets_tests = testArtifact(b, assets_mod); + const app_dirs_tests = testArtifact(b, app_dirs_mod); + const trace_tests = testArtifact(b, trace_mod); + const app_manifest_tests = testArtifact(b, app_manifest_mod); + const diagnostics_tests = testArtifact(b, diagnostics_mod); + const platform_info_tests = testArtifact(b, platform_info_mod); + const json_tests = testArtifact(b, json_mod); + const canvas_tests = testArtifact(b, canvas_mod); + + const desktop_mod = module(b, target, optimize, "src/root.zig"); + desktop_mod.addImport("geometry", geometry_mod); + desktop_mod.addImport("app_dirs", app_dirs_mod); + desktop_mod.addImport("assets", assets_mod); + desktop_mod.addImport("trace", trace_mod); + desktop_mod.addImport("app_manifest", app_manifest_mod); + desktop_mod.addImport("diagnostics", diagnostics_mod); + desktop_mod.addImport("platform_info", platform_info_mod); + desktop_mod.addImport("json", json_mod); + desktop_mod.addImport("canvas", canvas_mod); + const desktop_tests = testArtifact(b, desktop_mod); + const desktop_test_shards = desktopTestShardArtifacts(b, desktop_mod); + + // The embeddable static library's root module carries only the C ABI + // exports (fixed WebView shell host); user-app canvas libraries are + // produced by `addMobileLib` from src/embed/app_exports.zig instead. + const embed_exports_mod = module(b, target, optimize, "src/embed/c_exports.zig"); + embed_exports_mod.addImport("native_sdk", desktop_mod); + embed_exports_mod.export_symbol_names = &mobile_export_symbol_names; + const embed_lib = b.addLibrary(.{ + .linkage = .static, + .name = "native-sdk", + .root_module = embed_exports_mod, + // The embed C ABI (`native_sdk_app_viewport`) is exactly the + // f32-heavy SysV signature Zig 0.16.0's self-hosted x86_64 backend + // miscompiles (see useLlvmWorkaround in build/app.zig): without + // this, Debug x86_64 libs (Android emulators, Intel simulators) + // hand clang hosts corrupted inset/keyboard floats. addMobileLib + // already forces LLVM there; the fixed-shell lib must match. + .use_llvm = @import("build/app.zig").useLlvmWorkaround(target), + }); + b.installArtifact(embed_lib); + + const automation_protocol_mod = module(b, target, optimize, "src/automation/protocol.zig"); + const automation_protocol_tests = testArtifact(b, automation_protocol_mod); + // The app-icon pipeline as a standalone module: tooling needs only + // the vector core + PNG codec slice of canvas, not the full canvas + // module (which links platform frameworks on macOS and would weigh + // down the cross-compiled CLI). + const app_icon_mod = module(b, target, optimize, "src/primitives/canvas/app_icon.zig"); + app_icon_mod.addImport("geometry", geometry_mod); + // The iOS and Android host sources as embedded bytes: the tooling + // module writes and compiles them for `native dev|package --target + // ios|android`. + const ios_host_mod = module(b, target, optimize, "src/platform/ios/files.zig"); + const android_host_mod = module(b, target, optimize, "src/platform/android/files.zig"); + const tooling_mod = module(b, target, optimize, "src/tooling/root.zig"); + tooling_mod.addImport("assets", assets_mod); + tooling_mod.addImport("app_dirs", app_dirs_mod); + tooling_mod.addImport("app_manifest", app_manifest_mod); + tooling_mod.addImport("diagnostics", diagnostics_mod); + tooling_mod.addImport("debug", debug_mod); + tooling_mod.addImport("platform_info", platform_info_mod); + tooling_mod.addImport("trace", trace_mod); + tooling_mod.addImport("app_icon", app_icon_mod); + tooling_mod.addImport("ios_host", ios_host_mod); + tooling_mod.addImport("android_host", android_host_mod); + const tooling_tests = testArtifact(b, tooling_mod); + + // Ejected-component identity proofs: a separate test module because + // the canonical component sources under src/tooling/components/ + // import `native_sdk` exactly as they will inside an app after + // `native eject component ` copies them there. + const eject_components_mod = module(b, target, optimize, "src/tooling/components/identity_tests.zig"); + eject_components_mod.addImport("native_sdk", desktop_mod); + const eject_components_tests = testArtifact(b, eject_components_mod); + + // Transpiled-core end-to-end suite: tests/ts-core/fixture.ts is + // emitted by the repo's own transpiler AT BUILD TIME (never a + // committed Zig snapshot) and driven through the real runtime via + // `TsCoreHost`. Gated on node plus the transpiler package's + // installed dependency: absent either, the suite is skipped (the + // bridge itself stays covered by src/runtime/ts_core_host_tests.zig + // against a hand-written emitted-ABI core). + const ts_core_e2e_tests = tsCoreE2eArtifact(b, target, optimize, desktop_mod, tooling_mod); + + const ui_markup_mod = module(b, target, optimize, "src/primitives/canvas/ui_markup.zig"); + const markup_lsp_mod = module(b, target, optimize, "tools/native-sdk/markup_lsp.zig"); + markup_lsp_mod.addImport("ui_markup", ui_markup_mod); + const markup_lsp_tests = testArtifact(b, markup_lsp_mod); + + const automation_cli_mod = module(b, target, optimize, "tools/native-sdk/automation.zig"); + automation_cli_mod.addImport("automation_protocol", automation_protocol_mod); + automation_cli_mod.addImport("ui_markup", ui_markup_mod); + const automation_cli_tests = testArtifact(b, automation_cli_mod); + + const markup_cli_mod = module(b, target, optimize, "tools/native-sdk/markup.zig"); + markup_cli_mod.addImport("ui_markup", ui_markup_mod); + markup_cli_mod.addImport("markup_lsp", markup_lsp_mod); + const markup_cli_tests = testArtifact(b, markup_cli_mod); + + // `native version` names the commit the binary was built from, so + // binary/framework skew ("your native binary may be stale") is a + // one-command check. Falls back to "unknown" outside a git checkout. + const cli_build_info = b.addOptions(); + cli_build_info.addOption([]const u8, "build_commit", cliBuildCommit(b)); + + const cli_mod = module(b, target, optimize, "tools/native-sdk/main.zig"); + cli_mod.addImport("tooling", tooling_mod); + cli_mod.addImport("automation_protocol", automation_protocol_mod); + cli_mod.addImport("ui_markup", ui_markup_mod); + cli_mod.addImport("markup_lsp", markup_lsp_mod); + cli_mod.addOptions("cli_build_info", cli_build_info); + const cli_exe = b.addExecutable(.{ + .name = "native", + .root_module = cli_mod, + }); + b.installArtifact(cli_exe); + + // `zig build cli` builds and installs ONLY the CLI executable. The + // release pipeline cross-compiles it for every supported platform + // (packages/native-sdk/scripts/build-binaries.sh), and skipping the + // framework libraries, examples, and docs artifacts keeps that + // eight-target loop fast. + const cli_step = b.step("cli", "Build only the native CLI executable (cross-compile friendly)"); + cli_step.dependOn(&b.addInstallArtifact(cli_exe, .{}).step); + + const host_assets_mod = module(b, host_target, optimize, "src/primitives/assets/root.zig"); + const host_app_dirs_mod = module(b, host_target, optimize, "src/primitives/app_dirs/root.zig"); + const host_app_manifest_mod = module(b, host_target, optimize, "src/primitives/app_manifest/root.zig"); + const host_diagnostics_mod = module(b, host_target, optimize, "src/primitives/diagnostics/root.zig"); + const host_platform_info_mod = module(b, host_target, optimize, "src/primitives/platform_info/root.zig"); + const host_trace_mod = module(b, host_target, optimize, "src/primitives/trace/root.zig"); + const host_debug_mod = module(b, host_target, optimize, "src/debug/root.zig"); + host_debug_mod.addImport("app_dirs", host_app_dirs_mod); + host_debug_mod.addImport("trace", host_trace_mod); + const host_automation_protocol_mod = module(b, host_target, optimize, "src/automation/protocol.zig"); + const host_geometry_mod = module(b, host_target, optimize, "src/primitives/geometry/root.zig"); + const host_app_icon_mod = module(b, host_target, optimize, "src/primitives/canvas/app_icon.zig"); + host_app_icon_mod.addImport("geometry", host_geometry_mod); + const host_ios_host_mod = module(b, host_target, optimize, "src/platform/ios/files.zig"); + const host_android_host_mod = module(b, host_target, optimize, "src/platform/android/files.zig"); + const host_tooling_mod = module(b, host_target, optimize, "src/tooling/root.zig"); + host_tooling_mod.addImport("assets", host_assets_mod); + host_tooling_mod.addImport("app_dirs", host_app_dirs_mod); + host_tooling_mod.addImport("app_manifest", host_app_manifest_mod); + host_tooling_mod.addImport("diagnostics", host_diagnostics_mod); + host_tooling_mod.addImport("debug", host_debug_mod); + host_tooling_mod.addImport("platform_info", host_platform_info_mod); + host_tooling_mod.addImport("trace", host_trace_mod); + host_tooling_mod.addImport("app_icon", host_app_icon_mod); + host_tooling_mod.addImport("ios_host", host_ios_host_mod); + host_tooling_mod.addImport("android_host", host_android_host_mod); + const host_ui_markup_mod = module(b, host_target, optimize, "src/primitives/canvas/ui_markup.zig"); + const host_markup_lsp_mod = module(b, host_target, optimize, "tools/native-sdk/markup_lsp.zig"); + host_markup_lsp_mod.addImport("ui_markup", host_ui_markup_mod); + const host_cli_mod = module(b, host_target, optimize, "tools/native-sdk/main.zig"); + host_cli_mod.addImport("tooling", host_tooling_mod); + host_cli_mod.addImport("automation_protocol", host_automation_protocol_mod); + host_cli_mod.addImport("ui_markup", host_ui_markup_mod); + host_cli_mod.addImport("markup_lsp", host_markup_lsp_mod); + host_cli_mod.addOptions("cli_build_info", cli_build_info); + const host_cli_exe = b.addExecutable(.{ + .name = "native", + .root_module = host_cli_mod, + }); + // Docs component-preview generator: renders the built-in component + // catalog offscreen through the deterministic reference renderer and + // writes theme-aware webp pairs plus the markup vocabulary JSON into + // docs/. Regenerate with `zig build docs-component-previews`. + const docs_previews_mod = module(b, target, optimize, "tools/docs_component_previews.zig"); + docs_previews_mod.addImport("native_sdk", desktop_mod); + // The eject registry as its own lean module (its imports stay inside + // src/tooling/), so the vocab JSON's `ejectable` table is written from + // the same rows `native eject component` dispatches on — the docs' + // resolves from that JSON and can never drift. + docs_previews_mod.addImport("eject_components", module(b, target, optimize, "src/tooling/eject_components.zig")); + const docs_previews_exe = b.addExecutable(.{ + .name = "docs-component-previews", + .root_module = docs_previews_mod, + }); + const run_docs_previews = b.addRunArtifact(docs_previews_exe); + run_docs_previews.addArg(b.pathFromRoot("docs/public/components")); + run_docs_previews.addArg(b.pathFromRoot("docs/src/lib/component-vocab.json")); + run_docs_previews.has_side_effects = true; + const docs_previews_step = b.step("docs-component-previews", "Render built-in component previews and vocab JSON into docs/"); + docs_previews_step.dependOn(&run_docs_previews.step); + + // Render macro-benchmark: deterministic scenarios through the REAL + // engine pipeline (UiApp + Runtime + null-platform binary packet + // presents), reporting end-to-end and per-stage p50/p90 per + // interaction. Baselines should come from + // `zig build bench-render -Doptimize=ReleaseFast`. + const bench_render_mod = module(b, target, optimize, "tools/bench_render.zig"); + bench_render_mod.addImport("native_sdk", desktop_mod); + const bench_render_exe = b.addExecutable(.{ + .name = "bench-render", + .root_module = bench_render_mod, + }); + const run_bench_render = b.addRunArtifact(bench_render_exe); + run_bench_render.has_side_effects = true; + // Repo root as cwd so the relative budgets path in the docs/gate + // invocation resolves regardless of where `zig build` ran from. + run_bench_render.setCwd(b.path(".")); + // Ratchet mode: `zig build bench-render -Doptimize=ReleaseFast -- + // --check tools/bench-render-budgets.txt` compares the median e2e + // p50 of three suite passes against the committed budgets (the + // benchmark refuses --check outside ReleaseFast). + if (b.args) |bench_args| run_bench_render.addArgs(bench_args); + const bench_render_step = b.step("bench-render", "Run the render macro-benchmark (deterministic scenarios; pass -Doptimize=ReleaseFast for baselines, `-- --check tools/bench-render-budgets.txt` for the budget ratchet)"); + bench_render_step.dependOn(&run_bench_render.step); + + // Live docs previews: the same scene catalog compiled to + // wasm32-freestanding (tools/docs_wasm_preview.zig) so the docs + // upgrade the static webp tiles to interactive engine instances. + // ReleaseSmall + strip keep the module small enough to lazy-load. + const wasm_target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }); + const wasm_optimize: std.builtin.OptimizeMode = .ReleaseSmall; + const wasm_geometry_mod = module(b, wasm_target, wasm_optimize, "src/primitives/geometry/root.zig"); + const wasm_json_mod = module(b, wasm_target, wasm_optimize, "src/primitives/json/root.zig"); + const wasm_canvas_mod = module(b, wasm_target, wasm_optimize, "src/primitives/canvas/root.zig"); + wasm_canvas_mod.addImport("geometry", wasm_geometry_mod); + wasm_canvas_mod.addImport("json", wasm_json_mod); + const wasm_native_mod = module(b, wasm_target, wasm_optimize, "src/root.zig"); + wasm_native_mod.addImport("geometry", wasm_geometry_mod); + wasm_native_mod.addImport("json", wasm_json_mod); + wasm_native_mod.addImport("canvas", wasm_canvas_mod); + wasm_native_mod.addImport("app_dirs", module(b, wasm_target, wasm_optimize, "src/primitives/app_dirs/root.zig")); + wasm_native_mod.addImport("assets", module(b, wasm_target, wasm_optimize, "src/primitives/assets/root.zig")); + wasm_native_mod.addImport("trace", module(b, wasm_target, wasm_optimize, "src/primitives/trace/root.zig")); + wasm_native_mod.addImport("app_manifest", module(b, wasm_target, wasm_optimize, "src/primitives/app_manifest/root.zig")); + wasm_native_mod.addImport("diagnostics", module(b, wasm_target, wasm_optimize, "src/primitives/diagnostics/root.zig")); + wasm_native_mod.addImport("platform_info", module(b, wasm_target, wasm_optimize, "src/primitives/platform_info/root.zig")); + const docs_wasm_preview_mod = module(b, wasm_target, wasm_optimize, "tools/docs_wasm_preview.zig"); + docs_wasm_preview_mod.addImport("native_sdk", wasm_native_mod); + docs_wasm_preview_mod.strip = true; + const docs_wasm_preview_exe = b.addExecutable(.{ + .name = "component-preview", + .root_module = docs_wasm_preview_mod, + }); + docs_wasm_preview_exe.entry = .disabled; + docs_wasm_preview_exe.rdynamic = true; + // The engine trades heap for fixed capacity but still builds some + // sizable stack temporaries (NullPlatform alone is ~800 KB); the + // 1 MB wasm default overflows into linear memory silently. + docs_wasm_preview_exe.stack_size = 16 * 1024 * 1024; + const copy_docs_wasm_preview = b.addUpdateSourceFiles(); + copy_docs_wasm_preview.addCopyFileToSource(docs_wasm_preview_exe.getEmittedBin(), "docs/public/wasm/component-preview.wasm"); + const docs_wasm_preview_step = b.step("docs-wasm-preview", "Compile the live component-preview wasm module into docs/public/wasm/"); + docs_wasm_preview_step.dependOn(©_docs_wasm_preview.step); + + const file_contains_checker_mod = module(b, host_target, optimize, "tools/check_file_contains.zig"); + const file_contains_checker = b.addExecutable(.{ + .name = "check-file-contains", + .root_module = file_contains_checker_mod, + }); + + // Registry pin printer: emits the ui_schema table counts and + // fingerprints in the exact form ui_schema_tests.zig pins, plus the + // next free code per table for reserving codes ahead of parallel + // work. `zig build print-pins` after registry additions. + const print_pins_mod = module(b, host_target, optimize, "tools/print_pins.zig"); + print_pins_mod.addImport("ui_schema", module(b, host_target, optimize, "src/primitives/canvas/ui_schema.zig")); + const print_pins_exe = b.addExecutable(.{ + .name = "print-pins", + .root_module = print_pins_mod, + }); + const run_print_pins = b.addRunArtifact(print_pins_exe); + run_print_pins.has_side_effects = true; + const print_pins_step = b.step("print-pins", "Print registry counts and fingerprints in pin-ready form"); + print_pins_step.dependOn(&run_print_pins.step); + + const platform_arg = switch (selected_platform) { + .auto => unreachable, + .null => "null", + .macos => "macos", + .linux => "linux", + .windows => "windows", + }; + + const test_step = b.step("test", "Run package and framework tests"); + test_step.dependOn(&b.addRunArtifact(geometry_tests).step); + test_step.dependOn(&b.addRunArtifact(assets_tests).step); + test_step.dependOn(&b.addRunArtifact(app_dirs_tests).step); + test_step.dependOn(&b.addRunArtifact(trace_tests).step); + test_step.dependOn(&b.addRunArtifact(app_manifest_tests).step); + test_step.dependOn(&b.addRunArtifact(diagnostics_tests).step); + test_step.dependOn(&b.addRunArtifact(platform_info_tests).step); + test_step.dependOn(&b.addRunArtifact(json_tests).step); + test_step.dependOn(&b.addRunArtifact(canvas_tests).step); + for (desktop_test_shards) |shard_tests| { + test_step.dependOn(&b.addRunArtifact(shard_tests).step); + } + test_step.dependOn(&b.addRunArtifact(automation_protocol_tests).step); + test_step.dependOn(&b.addRunArtifact(tooling_tests).step); + test_step.dependOn(&b.addRunArtifact(eject_components_tests).step); + if (ts_core_e2e_tests) |ts_core_artifacts| { + const ts_core_e2e_step = b.step("test-ts-core-e2e", "Run the transpiled-core end-to-end suites (requires node)"); + const host_e2e_run = b.addRunArtifact(ts_core_artifacts.host); + const soundboard_e2e_run = b.addRunArtifact(ts_core_artifacts.soundboard); + const monitor_e2e_run = b.addRunArtifact(ts_core_artifacts.system_monitor); + const scaffold_ide_e2e_run = b.addRunArtifact(ts_core_artifacts.scaffold_ide); + // The suite scaffolds and typechecks real trees under .zig-cache; + // no build inputs/outputs to hash, so always run it. + scaffold_ide_e2e_run.has_side_effects = true; + const ai_chat_e2e_run = b.addRunArtifact(ts_core_artifacts.ai_chat); + ts_core_e2e_step.dependOn(&host_e2e_run.step); + ts_core_e2e_step.dependOn(&soundboard_e2e_run.step); + ts_core_e2e_step.dependOn(&monitor_e2e_run.step); + ts_core_e2e_step.dependOn(&scaffold_ide_e2e_run.step); + ts_core_e2e_step.dependOn(&ai_chat_e2e_run.step); + test_step.dependOn(&host_e2e_run.step); + test_step.dependOn(&soundboard_e2e_run.step); + test_step.dependOn(&monitor_e2e_run.step); + test_step.dependOn(&scaffold_ide_e2e_run.step); + test_step.dependOn(&ai_chat_e2e_run.step); + } + test_step.dependOn(&b.addRunArtifact(markup_lsp_tests).step); + test_step.dependOn(&b.addRunArtifact(automation_cli_tests).step); + test_step.dependOn(&b.addRunArtifact(markup_cli_tests).step); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-package-types", "Verify package TypeScript platform feature names", &.{ + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "NativeSdkCommandInfo" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "list(): Promise" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "NativeSdkCreateWebViewViewOptions" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "Stable runtime view id" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "update(label: string" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "focus(options: string | NativeSdkViewSelector)" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "close(options: string | NativeSdkViewSelector)" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "kind: \"webview\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "url: string" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "NativeSdkPlatformFeatureSelector" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "supports(value: NativeSdkPlatformFeature | NativeSdkPlatformFeatureSelector)" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "\"native_control_commands\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "\"nativeControlCommands\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "\"recent_documents\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "\"recentDocuments\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "\"file_drops\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "\"fileDrops\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "\"app_activation_events\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "\"appActivationEvents\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "\"gpu_surfaces\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "\"gpuSurfaces\"" }, + .{ .path = "packages/native-sdk/native-sdk.d.ts", .pattern = "gpuFirstFrameLatencyNs: number" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-app-test-entry-analysis", "Verify the managed app test step force-analyzes the entry point (UiApp.create's Model-defaults rule must teach at `native test`, not ambush at `native build`)", &.{ + .{ .path = "build/app.zig", .pattern = "app_analysis.zig" }, + .{ .path = "build/app.zig", .pattern = "if (@hasDecl(app, \"main\")) _ = &app.main;" }, + .{ .path = "build/app.zig", .pattern = "test_step.dependOn(&analysis_obj.step);" }, + .{ .path = "src/runtime/ui_app.zig", .pattern = "has no default value - give every Model field a default" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-bridge-view-selector-helpers", "Verify injected view helpers accept string selectors", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "viewSelectorPayload(options)" }, + .{ .path = "src/platform/macos/cef_host.mm", .pattern = "viewSelectorPayload(options)" }, + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "viewSelectorPayload(options)" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "viewSelectorPayload(options)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "focus:function(options){return invoke('native-sdk.view.focus',viewSelectorPayload(options))" }, + .{ .path = "src/platform/macos/cef_host.mm", .pattern = "focus:function(options){return invoke('native-sdk.view.focus',viewSelectorPayload(options))" }, + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "focus:function(options){return invoke('native-sdk.view.focus',viewSelectorPayload(options))" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "focus:function(options){return invoke('native-sdk.view.focus',viewSelectorPayload(options))" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "close:function(options){return invoke('native-sdk.view.close',viewSelectorPayload(options))" }, + .{ .path = "src/platform/macos/cef_host.mm", .pattern = "close:function(options){return invoke('native-sdk.view.close',viewSelectorPayload(options))" }, + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "close:function(options){return invoke('native-sdk.view.close',viewSelectorPayload(options))" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "close:function(options){return invoke('native-sdk.view.close',viewSelectorPayload(options))" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-docs-command-contracts", "Verify command docs match native view update contracts", &.{ + .{ .path = "docs/src/app/commands/page.mdx", .pattern = ".text = \"Refreshed\"" }, + .{ .path = "docs/src/app/commands/page.mdx", .pattern = "const commands = await window.zero.commands.list();" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-docs-native-view-contracts", "Verify native surface docs describe view identity", &.{ + .{ .path = "docs/src/app/native-surfaces/page.mdx", .pattern = "ViewInfo.id" }, + .{ .path = "docs/src/app/native-surfaces/page.mdx", .pattern = "window.zero.views.update(\"status\"" }, + .{ .path = "docs/src/app/native-surfaces/page.mdx", .pattern = "first-frame latency budget" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-docs-shell-manifest-contracts", "Verify app.zon docs describe shell compatibility window labels", &.{ + .{ .path = "docs/src/app/app-zon/page.mdx", .pattern = "labels must stay unique across both lists" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-js-view-helper-contracts", "Verify injected view helpers support label-first updates", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "update:function(options,patch)" }, + .{ .path = "src/platform/macos/cef_host.mm", .pattern = "update:function(options,patch)" }, + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "update:function(options,patch)" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "update:function(options,patch)" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-linux-gpu-frame-emission-below-paint", "Verify the Linux GPU frame emission is scheduled below layout and paint priorities", &.{ + // A saturated demand-driven frame loop (cycle cost > frame + // interval => every present re-arms at delay 0) scheduled at + // G_PRIORITY_DEFAULT would be ready on every main-loop + // iteration and starve GTK's layout/paint sources: presents + // land and queue_draw keeps inviting a repaint, but the glass + // freezes on stale pixels for as long as the loop stays armed. + // The emission must stay below GDK_PRIORITY_REDRAW so every + // presented frame paints before the next frame event. + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "view->gpu_emit_source = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE," }, + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "paint (GDK_PRIORITY_REDRAW," }, + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "static void native_sdk_gpu_surface_schedule_frame_emission" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-linux-audio-buffering-clears-on-noop-resume", "Verify the Linux audio buffering flag drops when the 100% resume completes synchronously", &.{ + // The buffering flag normally drops at the PLAYING + // state-changed message. When the refill's earlier PAUSED + // request never completed, the 100% resume is a no-op that + // posts no message; a non-ASYNC set_state result is the only + // signal, and the flag must drop on it or it rides every + // position tick for the rest of the track. + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "const int change = gst->element_set_state(audio->playbin, NATIVE_SDK_GST_STATE_PLAYING);" }, + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "change != NATIVE_SDK_GST_STATE_CHANGE_ASYNC" }, + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "#define NATIVE_SDK_GST_STATE_CHANGE_ASYNC 2" }, + }); + // The embedded-WebView layer must stay real AND declare-to-use: the + // standard build graph puts the vendored WebView2 SDK header on the + // include path exactly when app.zon declares web use (`if + // (web_layer)`), and only the native-only branch passes the stub + // define. In the guard the stub define is tested FIRST, before + // header visibility: on a machine where the WebView2 SDK headers are + // reachable through the system include paths, an + // include-path-decides guard would compile the full layer into a + // native-only build and reintroduce the WebView2Loader.dll reference + // its executable must not carry. Without the define the header is + // required — a web build that cannot see it fails at compile time + // instead of quietly shipping the stubbed host (whose WebView loads + // report WebViewNotFound at runtime). + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-windows-webview2-vendor", "Verify the vendored WebView2 SDK stays wired into every web-declaring Windows build graph", &.{ + .{ .path = "third_party/webview2/include/WebView2.h", .pattern = "CreateCoreWebView2EnvironmentWithOptions" }, + .{ .path = "third_party/webview2/include/EventToken.h", .pattern = "EventRegistrationToken" }, + .{ .path = "third_party/webview2/LICENSE.txt", .pattern = "Redistribution and use in source and binary forms" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "#if defined(NATIVE_SDK_ALLOW_WEBVIEW2_STUB)" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "#elif __has_include() && __has_include()" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "#error \"WebView2.h not found" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "LoadLibraryW(L\"WebView2Loader.dll\")" }, + .{ .path = "build/app.zig", .pattern = ".system => if (web_layer) {" }, + .{ .path = "build/app.zig", .pattern = "app_mod.addIncludePath(dep.path(\"third_party/webview2/include\"));" }, + .{ .path = "build/app.zig", .pattern = "third_party/webview2/x64/WebView2Loader.dll" }, + .{ .path = "build/app.zig", .pattern = "\"-DNATIVE_SDK_ALLOW_WEBVIEW2_STUB\"" }, + .{ .path = "src/tooling/templates.zig", .pattern = "third_party/webview2/include" }, + .{ .path = "src/tooling/templates.zig", .pattern = "third_party/webview2/x64/WebView2Loader.dll" }, + }); + // The Linux mirror of the Windows seam: the stub define wins over + // header visibility (a native-only build never reintroduces the + // libwebkitgtk link even where the dev package is installed), the + // header is required for web builds, and both build graphs compile + // gtk_host.c with the stub and drop the webkitgtk-6.0 link when the + // web layer is excluded. + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-linux-webkitgtk-seam", "Verify the WebKitGTK compile seam stays wired through the GTK host and both Linux build graphs", &.{ + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "#if defined(NATIVE_SDK_ALLOW_WEBKITGTK_STUB)" }, + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "#elif __has_include()" }, + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "#error \"webkit/webkit.h not found" }, + .{ .path = "build/app.zig", .pattern = "\"-DNATIVE_SDK_ALLOW_WEBKITGTK_STUB\"" }, + .{ .path = "src/tooling/templates.zig", .pattern = "\"-DNATIVE_SDK_ALLOW_WEBKITGTK_STUB\"" }, + }); + addLayoutCheckStep(b, test_step, "test-windows-webview2-loader-layout", "Verify the vendored WebView2 loader binaries are present", &.{ + "third_party/webview2/x64/WebView2Loader.dll", + "third_party/webview2/arm64/WebView2Loader.dll", + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-windows-packaged-assets-webview2", "Verify Windows packaged assets are served through WebView2 request interception", &.{ + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "constexpr const char *kAssetVirtualOrigin = \"https://native-sdk-app.localhost\";" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "return virtualAssetEntryUrl(webview.asset_entry);" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "AddWebResourceRequestedFilter(L\"https://native-sdk-app.localhost/*\"" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "assetWebResourceResponse(environment_ref.Get(), found->second, uri)" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "bridgeOriginForWebViewUrl(source_webview->second, source_url)" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "webview.spa_fallback = spa_fallback != 0;" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-macos-cef-packaged-assets-webviews", "Verify macOS CEF child WebViews resolve packaged asset URLs before loading", &.{ + .{ .path = "src/platform/macos/cef_host.mm", .pattern = "self.assetRoots = [[NSMutableDictionary alloc] init];" }, + .{ .path = "src/platform/macos/cef_host.mm", .pattern = "resolvedWebViewURLString:(NSString *)url windowId:(uint64_t)windowId" }, + .{ .path = "src/platform/macos/cef_host.mm", .pattern = "CefBrowserHost::CreateBrowser(windowInfo, client.get(), std::string(resolvedURL.UTF8String)" }, + .{ .path = "src/platform/macos/cef_host.mm", .pattern = "self.webviewPendingURLs[[self webViewKeyForWindow:windowId label:label]] = resolvedURL;" }, + .{ .path = "src/platform/macos/cef_host.mm", .pattern = "bridgeOriginForWindowId:window_id_ webViewLabel:labelString sourceURL:sourceURLString" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-native-accessibility-roles", "Verify AppKit native views publish accessibility roles", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkAccessibilityRoleForNativeViewKind" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NSAccessibilityToolbarRole" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NSAccessibilityProgressIndicatorRole" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "view.accessibilityRole = NativeSdkAccessibilityRoleForNativeViewKind(kind)" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-input-repaints-retained-canvas", "Verify GPU input wakes retained canvas frames", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "- (void)requestRetainedCanvasFrame" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "[self requestRetainedCanvasFrame];" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-input-paces-retained-canvas", "Verify GPU input frame requests are paced to the display interval", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkRetainedFrameIntervalNanoseconds" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "retainedFrameLastEmitNs" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "queuePointerMotionInputEvent:(NSEvent *)event" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "pendingPointerMotionKind = kind" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "emitQueuedPointerMotionInputEvent" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "queueScrollInputEvent:(NSEvent *)event" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "pendingScrollDeltaY += deltaY" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "emitQueuedScrollInputEvent" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "dispatch_after(dispatch_time(DISPATCH_TIME_NOW" }, + // The single per-surface frame-event scheduler: every producer + // (requests, completions, occluded completions) coalesces into + // one paced emission per display interval. + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "- (void)scheduleFrameEventEmission" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "- (void)emitScheduledFrameEvent" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-gpu-occluded-frame-heartbeat", "Verify occluded windows throttle frame completions to a heartbeat and restore full cadence on reveal", &.{ + // macOS: occluded surfaces pace logical completions on the ~1 Hz + // heartbeat (never stopping — on_frame-driven models stay gently + // current), de-occlusion supersedes the parked emission for an + // immediate return to the display grid, and heartbeat completions + // are flagged so the runtime never stamps input latency from them. + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkOccludedFrameHeartbeatNs = 1000000000ull" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "- (BOOL)occludedFramePacingActive" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "heartbeatPaced ? NativeSdkOccludedFrameHeartbeatNs" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "self.frameEventEmissionGeneration += 1" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "occluded:[self occludedFramePacingActive]" }, + // Exempt producers (a real present's completion, an input's + // responding frame) fire at grid promptness — neither can + // sustain a spin. + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "scheduleFrameEventEmissionForPresentCompletion:YES" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "- (void)noteGpuSurfaceInputActivity" }, + // Windows: the same throttle keyed on the one reliable Win32 + // occlusion signal (minimize); restore re-arms the pending + // one-shot timer at the frame-grid delay, and the input / + // first-present exemptions ride one prompt-frame flag. + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "kGpuOccludedHeartbeatNs = 1000000000ull" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "gpuSurfaceOccludedPacingActive" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "IsIconic(root)" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "gpu_prompt_frame_pending" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "native_sdk_windows_note_gpu_surface_input" }, + // The runtime side of the measurement honesty: occluded logical + // completions resolve pending inputs without a latency stamp, + // and every dispatched input notes the host for a prompt + // responding frame. + .{ .path = "src/runtime/gpu_surface_events.zig", .pattern = "resolveGpuSurfaceInputForOccludedFrame" }, + .{ .path = "src/runtime/gpu_surface_events.zig", .pattern = "noteGpuSurfaceInput" }, + // GTK: no reliable cross-backend occlusion signal — the decision + // to leave full cadence is documented at the scheduler, not + // implicit. + .{ .path = "src/platform/linux/gtk_host.c", .pattern = "No occluded/minimized throttle here, DELIBERATELY" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-drawable-integral-pixels", "Verify AppKit GPU surfaces use integral drawable pixels", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "ceil(size.width * scale)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "ceil(size.height * scale)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "self.metalLayer.drawableSize = drawableSize" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-resize-repaints-retained-canvas", "Verify AppKit GPU resize requests a correctly sized retained frame", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "_metalLayer.contentsGravity = kCAGravityTopLeft" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "if (changed) {" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "[self requestRetainedCanvasFrame];" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "canvasTextureMatchesDrawable" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-packet-transforms", "Verify AppKit GPU packet presenter applies command transforms", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkPacketApplyTransform(command[@\"transform\"])" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "[affine setTransformStruct:transform]" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "[affine concat]" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-packet-paths", "Verify AppKit GPU packet presenter draws path commands", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "[kind isEqualToString:@\"path\"]" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "[verb isEqualToString:@\"quad_to\"]" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "[kind isEqualToString:@\"fill_path\"]" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "[kind isEqualToString:@\"stroke_path\"]" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-packet-corner-radii", "Verify AppKit GPU packet presenter honors per-corner radii", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkPacketRoundedRectPath" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "CGFloat topRight = NativeSdkPacketRadiusAt(radiusValue, 1, maxRadius)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "CGFloat bottomLeft = NativeSdkPacketRadiusAt(radiusValue, 3, maxRadius)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "return NativeSdkPacketRoundedRectPath(rect, shape[@\"radius\"])" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-packet-load-frames", "Verify AppKit GPU packet presenter handles retained load frames", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "canvasPacketPixels" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "[loadAction isEqualToString:@\"load\"]" }, + // Retained-backing discipline: dirty updates require the validity + // flag, and every present that mutates the backing clears it until + // the draw succeeds — a failed draw can never leak stale pixels + // around a later scissor. + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "!self.canvasPacketPixelsValid" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "self.canvasPacketPixelsValid = YES" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "hasDirtyRect:uploadDirtyRect" }, + }); + // Version prose is machine-checked: the wire-format v2/v3 drift (spec + // comments and changelog naming a version the constant had moved past) + // shipped once, so the constant and every prose site that names the + // version are pinned together. Bumping `binary_packet_version` fails + // this step until the encoder comment, the host decoder comment, and + // the patterns below move with it. + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-wire-format-version-prose", "Verify wire-format version prose matches the packet version constant", &.{ + .{ .path = "src/primitives/canvas/serialization.zig", .pattern = "pub const binary_packet_version: u8 = 4;" }, + .{ .path = "src/primitives/canvas/serialization.zig", .pattern = "Compact binary gpu-surface packet encoding (wire format v4)." }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "Compact binary gpu-surface packet decoding (wire format v4)." }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-packet-blur-effects", "Verify AppKit GPU packet presenter applies blur effects", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkPacketApplyBlur" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "CGBitmapContextGetData(context)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NSIntersectionRect(rect, clipRect)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkPacketTransformRect(transformValue, NativeSdkPacketRect(effect[@\"rect\"]))" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "return NativeSdkPacketApplyBlur(effect, opacity, context, scale, transformValue, hasClip, clipRect)" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-packet-text-layout", "Verify AppKit GPU packet presenter honors text layout metadata", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NSMutableParagraphStyle" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkPacketTextLineBreakMode" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkPacketTextAlignment" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkPacketNumber(layout[@\"maxWidth\"], 0)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "[value drawWithRect:NSMakeRect(origin.x, origin.y - size, textWidth, textHeight)" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-packet-font-assets", "Verify AppKit GPU packet text registers bundled font assets", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "#import " }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "CTFontManagerRegisterFontsForURL" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "@[ @\"fonts\", @\"Fonts\", @\"assets/fonts\" ]" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkRegisterBundledFonts();" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkPacketPreferredFont(text, size)" }, + .{ .path = "src/tooling/templates.zig", .pattern = "app_mod.linkFramework(\"CoreText\", .{});" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-packet-span-fonts", "Verify AppKit packet text resolves reserved span font ids to real weighted and italic faces", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "static NSFont *NativeSdkItalicSansFont(NSFont *font)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "static NSFont *NativeSdkWeightedSansFont(" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkWeightedSansFont(@[ @\"Geist-Medium\", @\"Geist Medium\" ], base, NSFontWeightMedium, NO, size)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkWeightedSansFont(@[ @\"Geist-Bold\", @\"Geist Bold\" ], base, NSFontWeightBold, YES, size)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkItalicSansFont(base)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NativeSdkItalicSansFont(NativeSdkWeightedSansFont(@[ @\"Geist-Bold\", @\"Geist Bold\" ], base, NSFontWeightBold, YES, size))" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-widget-cursor-bridge", "Verify AppKit GPU widgets apply retained cursor intent", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "native_sdk_appkit_set_view_cursor" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "resetCursorRects" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NSTrackingMouseMoved" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NATIVE_SDK_APPKIT_GPU_INPUT_POINTER_CANCEL" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "_surfaceCursor = cursor ?: [NSCursor arrowCursor]" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NSCursor pointingHandCursor" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-widget-accessibility-actions", "Verify AppKit GPU widget accessibility actions route to the runtime", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "accessibilityPerformPress" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "emitWidgetAccessibilityActionWithId" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NATIVE_SDK_APPKIT_EVENT_WIDGET_ACCESSIBILITY_ACTION" }, + // The per-element action gate: without it AppKit derives the + // advertised action list from the CLASS's selectors, so every + // widget element (a static label included) offers + // press/increment/decrement/cancel and performing an + // unsupported one reports success while actuating nothing. + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "isAccessibilitySelectorAllowed" }, + // An assistive client's AXFocused WRITE must move the app's + // real focus, not just flip a flag on the snapshot element. + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "setAccessibilityFocused" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-widget-accessibility-text-ranges", "Verify AppKit GPU widget accessibility publishes text selection ranges", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "accessibilitySelectedTextRange" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "accessibilitySelectedTextRanges" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "accessibilityVisibleCharacterRange" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-widget-accessibility-grid-metrics", "Verify AppKit GPU widget accessibility publishes grid and scroll metrics", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "accessibilityRowIndexRange" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "accessibilityColumnIndexRange" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "accessibilityRowCount" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "accessibilityMaxValue" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-widget-ime-bridge", "Verify AppKit GPU widgets route native text input and IME composition", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NSTextInputClient" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "insertText:(id)string replacementRange" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "setMarkedText:(id)string selectedRange" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NATIVE_SDK_APPKIT_GPU_INPUT_IME_SET_COMPOSITION" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-windows-gpu-widget-ime-bridge", "Verify the Windows GPU host routes WM_IME composition onto the shared IME event kinds", &.{ + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "case WM_IME_STARTCOMPOSITION:" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "case WM_IME_COMPOSITION:" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "case WM_IME_ENDCOMPOSITION:" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "kGpuInputImeSetComposition = 8" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "kGpuInputImeCommitComposition = 9" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "kGpuInputImeCancelComposition = 10" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "gpuImeCommitAction(pending, result)" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "ISC_SHOWUICOMPOSITIONWINDOW" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-windows-view-frame-rounding", "Verify Windows native view frames accumulate logical coordinates before rounding and declare the full DPI awareness fallback chain", &.{ + // The frame policy: every physical edge is the once-rounded + // product of an ACCUMULATED logical coordinate and the window + // scale, with width/height as edge differences. Per-level + // rounding drifts from the round of the sum (the static_asserts + // beside nativeViewCoord carry the numeric proof, compiled on + // every Windows host build). + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "static void nativeViewLogicalOrigin(Host *host, const NativeView &view, double *logical_x, double *logical_y)" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "frame.right = nativeViewCoord((logical_x + view.width) * scale);" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "static_assert(nativeViewCoord(10.4 * 1.5) + nativeViewCoord(10.4 * 1.5) == 32 && nativeViewCoord((10.4 + 10.4) * 1.5) == 31," }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "MoveWindow(view.hwnd, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, TRUE);" }, + // The embedded manifest's DPI declarations: the ordered + // dpiAwareness list degrades PerMonitorV2 to PerMonitor, and the + // legacy dpiAware element covers systems that ignore dpiAwareness + // entirely. + .{ .path = "assets/native-sdk.manifest", .pattern = "true/pm" }, + .{ .path = "assets/native-sdk.manifest", .pattern = "PerMonitorV2, PerMonitor" }, + // The host's runtime DPI resolution mirrors that manifest chain: + // GetDpiForWindow, then shcore's GetDpiForMonitor (per-monitor v1), + // then the system DPI. Without the per-monitor-v1 and system rungs + // an aware process on a pre-1607 system would render at real + // physical pixels while the host still scaled everything at 1x. + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "GetProcAddress(shcore, \"GetDpiForMonitor\")" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST)" }, + .{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "return systemDpi();" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-gpu-widget-text-command-bridge", "Verify AppKit GPU text widgets route native text commands", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "- (void)selectAll:(id)sender" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "@selector(selectAll:)" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "emitSyntheticKeyDownWithKey:@\"a\" modifiers:(NativeSdkShortcutModifierPrimary | NativeSdkShortcutModifierCommand)" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-appkit-appearance-bridge", "Verify AppKit reports system light and dark appearance changes", &.{ + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "effectiveAppearance" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "accessibilityDisplayShouldReduceMotion" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "accessibilityDisplayShouldIncreaseContrast" }, + .{ .path = "src/platform/macos/appkit_host.m", .pattern = "NATIVE_SDK_APPKIT_EVENT_APPEARANCE_CHANGED" }, + .{ .path = "src/platform/macos/root.zig", .pattern = ".reduce_motion = event.reduce_motion != 0" }, + .{ .path = "src/platform/macos/root.zig", .pattern = ".high_contrast = event.high_contrast != 0" }, + .{ .path = "src/platform/macos/root.zig", .pattern = ".appearance_changed => state.emit" }, + }); + addFileContainsCheckStep(b, file_contains_checker, test_step, "test-docs-builtin-bridge-policy", "Verify bridge policy docs include guarded dialog commands", &.{ + .{ .path = "docs/src/app/security/page.mdx", .pattern = ".{ .name = \"native-sdk.dialog.saveFile\"" }, + .{ .path = "docs/src/app/bridge/builtin-commands/page.mdx", .pattern = ".{ .name = \"native-sdk.dialog.saveFile\"" }, + }); + + addTestStep(b, "test-geometry", "Run geometry module tests", geometry_tests); + addTestStep(b, "test-assets", "Run assets module tests", assets_tests); + addTestStep(b, "test-app-dirs", "Run app directory module tests", app_dirs_tests); + addTestStep(b, "test-trace", "Run trace module tests", trace_tests); + addTestStep(b, "test-app-manifest", "Run app manifest module tests", app_manifest_tests); + addTestStep(b, "test-diagnostics", "Run diagnostics module tests", diagnostics_tests); + addTestStep(b, "test-platform-info", "Run platform info module tests", platform_info_tests); + addTestStep(b, "test-json", "Run JSON primitive tests", json_tests); + addTestStep(b, "test-canvas", "Run canvas display list tests", canvas_tests); + addTestStep(b, "test-desktop", "Run Native SDK framework tests", desktop_tests); + for (desktop_test_shard_specs, desktop_test_shards) |spec, shard_tests| { + addTestStep(b, b.fmt("test-desktop-{s}", .{spec.name}), spec.description, shard_tests); + } + addTestStep(b, "test-automation-protocol", "Run automation protocol tests", automation_protocol_tests); + addTestStep(b, "test-automation-cli", "Run native automate CLI tests", automation_cli_tests); + addTestStep(b, "test-markup-cli", "Run native markup CLI tests", markup_cli_tests); + addTestStep(b, "test-tooling", "Run Native SDK tooling tests", tooling_tests); + addTestStep(b, "test-eject-components", "Run ejected-component widget-identity tests", eject_components_tests); + + const run_hello = b.addSystemCommand(&.{ "zig", "build", "run", b.fmt("-Dplatform={s}", .{platform_arg}), b.fmt("-Dtrace={s}", .{@tagName(trace_option)}) }); + run_hello.setCwd(b.path("examples/hello")); + const run_hello_step = b.step("run-hello", "Run the native-sdk hello WebView example"); + run_hello_step.dependOn(&run_hello.step); + + const run_webview = b.addSystemCommand(&.{ "zig", "build", "run", b.fmt("-Dplatform={s}", .{platform_arg}), b.fmt("-Dtrace={s}", .{@tagName(trace_option)}), b.fmt("-Dweb-engine={s}", .{@tagName(web_engine)}), b.fmt("-Dcef-dir={s}", .{exampleCefDir(b, cef_dir)}) }); + run_webview.setCwd(b.path("examples/webview")); + const run_webview_step = b.step("run-webview", "Run the native-sdk WebView example"); + run_webview_step.dependOn(&run_webview.step); + + const browser_cef_dir = cef_dir_override orelse defaultCefDir(selected_platform, "third_party/cef/macos"); + const run_browser = b.addSystemCommand(&.{ "zig", "build", "run", b.fmt("-Dplatform={s}", .{platform_arg}), b.fmt("-Dtrace={s}", .{@tagName(trace_option)}), b.fmt("-Dweb-engine={s}", .{@tagName(browser_web_engine)}), b.fmt("-Dcef-dir={s}", .{exampleCefDir(b, browser_cef_dir)}) }); + run_browser.setCwd(b.path("examples/browser")); + const run_browser_step = b.step("run-browser", "Run the native-sdk browser example"); + run_browser_step.dependOn(&run_browser.step); + + const build_webview_system = b.addSystemCommand(&.{ "zig", "build", b.fmt("-Dplatform={s}", .{platform_arg}), "-Dweb-engine=system" }); + build_webview_system.setCwd(b.path("examples/webview")); + const webview_system_link_step = b.step("test-webview-system-link", "Build the WebView example with the system engine"); + webview_system_link_step.dependOn(&build_webview_system.step); + + const build_browser_system = b.addSystemCommand(&.{ "zig", "build", b.fmt("-Dplatform={s}", .{platform_arg}), "-Dweb-engine=system" }); + build_browser_system.setCwd(b.path("examples/browser")); + const browser_system_link_step = b.step("test-browser-system-link", "Build the browser example with the system engine"); + browser_system_link_step.dependOn(&build_browser_system.step); + + // Windows web-layer PE cross-audit: the declare-to-use inference, + // proven on real executables from any host via cross-compile. + // ui-inbox's manifest declares no web use, so its exe must carry no + // WebView2Loader.dll reference (the whole embedded layer compiles + // out) and no loader may land beside it; the webview example declares + // the "webview" capability, so its exe must keep the reference. The + // loader string lives as UTF-16 in the host, so the audit is a + // dedicated scanner (tools/audit_web_layer.zig), not a text grep. + const web_layer_auditor = b.addExecutable(.{ + .name = "audit-web-layer", + .root_module = module(b, host_target, optimize, "tools/audit_web_layer.zig"), + }); + // A loader installed by a build predating the inference would fail + // the absence check below without being this build's fault; clear it + // so the assertion tests what THIS build installs. + const clean_native_only_loader = b.addSystemCommand(&.{ "sh", "-c", "rm -f examples/ui-inbox/zig-out/bin/WebView2Loader.dll" }); + const build_native_only_windows = b.addSystemCommand(&.{ "zig", "build", "-Dtarget=x86_64-windows-gnu", "-Dplatform=windows" }); + build_native_only_windows.setCwd(b.path("examples/ui-inbox")); + build_native_only_windows.step.dependOn(&clean_native_only_loader.step); + const build_webview_windows = b.addSystemCommand(&.{ "zig", "build", "-Dtarget=x86_64-windows-gnu", "-Dplatform=windows", "-Dweb-engine=system" }); + build_webview_windows.setCwd(b.path("examples/webview")); + const audit_native_only_exe = b.addRunArtifact(web_layer_auditor); + audit_native_only_exe.addArgs(&.{ "examples/ui-inbox/zig-out/bin/ui-inbox.exe", "absent" }); + audit_native_only_exe.has_side_effects = true; + audit_native_only_exe.step.dependOn(&build_native_only_windows.step); + const audit_webview_exe = b.addRunArtifact(web_layer_auditor); + audit_webview_exe.addArgs(&.{ "examples/webview/zig-out/bin/webview.exe", "present" }); + audit_webview_exe.has_side_effects = true; + audit_webview_exe.step.dependOn(&build_webview_windows.step); + const audit_native_only_loader = b.addSystemCommand(&.{ + "sh", "-c", + \\test ! -f examples/ui-inbox/zig-out/bin/WebView2Loader.dll || { + \\ echo "web-layer audit FAILED: the native-only build installed WebView2Loader.dll" >&2 + \\ exit 1 + \\} + }); + audit_native_only_loader.step.dependOn(&build_native_only_windows.step); + const web_layer_audit_step = b.step("test-windows-web-layer-audit", "Cross-compile a native-only and a web example for Windows and audit the web layer in each exe"); + web_layer_audit_step.dependOn(&audit_native_only_exe.step); + web_layer_audit_step.dependOn(&audit_webview_exe.step); + web_layer_audit_step.dependOn(&audit_native_only_loader.step); + + // Linux web-layer ELF cross-audit: the same declare-to-use proof on + // real Linux executables. Unlike the Windows step this cannot + // cross-compile from any host — the GTK host needs the system GTK4 + // (and, for the web example, WebKitGTK) development headers — so the + // step runs on Linux with both packages installed and proves the + // SEAM, not the environment: the native-only exe must carry no + // libwebkitgtk DT_NEEDED entry and no webkit_/jsc_ dynamic symbol + // even though the dev package was right there, while the web example + // must keep them. The environment half (a native-only app building + // on a runner with no WebKitGTK dev package at all) is proven by the + // linux-canvas-smoke CI job. + const build_native_only_linux = b.addSystemCommand(&.{ "zig", "build", "-Dplatform=linux" }); + build_native_only_linux.setCwd(b.path("examples/ui-inbox")); + const build_webview_linux = b.addSystemCommand(&.{ "zig", "build", "-Dplatform=linux", "-Dweb-engine=system" }); + build_webview_linux.setCwd(b.path("examples/webview")); + const audit_native_only_elf = b.addRunArtifact(web_layer_auditor); + audit_native_only_elf.addArgs(&.{ "examples/ui-inbox/zig-out/bin/ui-inbox", "absent" }); + audit_native_only_elf.has_side_effects = true; + audit_native_only_elf.step.dependOn(&build_native_only_linux.step); + const audit_webview_elf = b.addRunArtifact(web_layer_auditor); + audit_webview_elf.addArgs(&.{ "examples/webview/zig-out/bin/webview", "present" }); + audit_webview_elf.has_side_effects = true; + audit_webview_elf.step.dependOn(&build_webview_linux.step); + const linux_web_layer_audit_step = b.step("test-linux-web-layer-audit", "Build a native-only and a web example for Linux (Linux host with GTK4 + WebKitGTK dev packages) and audit the web layer in each ELF"); + linux_web_layer_audit_step.dependOn(&audit_native_only_elf.step); + linux_web_layer_audit_step.dependOn(&audit_webview_elf.step); + + const frontend_examples_step = b.step("test-examples-frontends", "Run frontend example tests"); + addExampleTestStep(b, host_cli_exe, frontend_examples_step, "test-example-next", "Run Next example tests", "examples/next", .owned); + addExampleTestStep(b, host_cli_exe, frontend_examples_step, "test-example-react", "Run React example tests", "examples/react", .owned); + addExampleTestStep(b, host_cli_exe, frontend_examples_step, "test-example-svelte", "Run Svelte example tests", "examples/svelte", .owned); + addExampleTestStep(b, host_cli_exe, frontend_examples_step, "test-example-vue", "Run Vue example tests", "examples/vue", .owned); + addFileContainsCheckStep(b, file_contains_checker, frontend_examples_step, "test-example-frontend-positioning", "Verify frontend example native shell positioning", &.{ + .{ .path = "examples/next/README.md", .pattern = "opens the native app shell with WebView content." }, + .{ .path = "examples/react/README.md", .pattern = "opens the native app shell with WebView content." }, + .{ .path = "examples/svelte/README.md", .pattern = "opens the native app shell with WebView content." }, + .{ .path = "examples/vue/README.md", .pattern = "opens the native app shell with WebView content." }, + }); + + const native_examples_step = b.step("test-examples-native", "Run native-first example tests"); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-command-app", "Run command app example tests", "examples/command-app", .owned); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-native-shell", "Run native shell example tests", "examples/native-shell", .owned); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-native-panels", "Run native panels example tests", "examples/native-panels", .owned); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-gpu-surface", "Run GPU surface example tests", "examples/gpu-surface", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-gpu-dashboard", "Run GPU dashboard example tests", "examples/gpu-dashboard", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-gpu-components", "Run GPU components example tests", "examples/gpu-components", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-ui-inbox", "Run ui builder inbox example tests", "examples/ui-inbox", .owned); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-kanban", "Run ui builder kanban example tests", "examples/kanban", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-habits", "Run markup habits example tests", "examples/habits", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-soundboard", "Run soundboard example tests", "examples/soundboard", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-soundboard-ts", "Run soundboard-ts example tests", "examples/soundboard-ts", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-deck", "Run deck example tests", "examples/deck", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-markdown-viewer", "Run markdown viewer example tests", "examples/markdown-viewer", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-calculator", "Run calculator example tests", "examples/calculator", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-notes", "Run notes example tests", "examples/notes", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-split-collapse", "Run split collapse example tests", "examples/split-collapse", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-system-monitor", "Run system monitor example tests", "examples/system-monitor", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-system-monitor-ts", "Run system-monitor-ts example tests", "examples/system-monitor-ts", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-effects-probe", "Run effects probe example tests", "examples/effects-probe", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-feed", "Run feed example tests", "examples/feed", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-canvas-preview", "Run canvas preview example tests", "examples/canvas-preview", .managed); + addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-capabilities", "Run capabilities example tests", "examples/capabilities", .owned); + addFileContainsCheckStep(b, file_contains_checker, native_examples_step, "test-example-capabilities-events", "Verify capabilities example event bridge names", &.{ + .{ .path = "examples/capabilities/src/main.zig", .pattern = "native-sdk:drop:files" }, + }); + + const mobile_examples_step = b.step("test-examples-mobile", "Verify mobile example project layouts"); + addLayoutCheckStep(b, mobile_examples_step, "test-example-ios-layout", "Verify iOS example layout", &.{ + "examples/ios/README.md", + "examples/ios/app.zon", + "examples/ios/NativeSdkIOSExample.xcodeproj/project.pbxproj", + "examples/ios/NativeSdkIOSExample/AppDelegate.swift", + "examples/ios/NativeSdkIOSExample/SceneDelegate.swift", + "examples/ios/NativeSdkIOSExample/NativeSdkHostViewController.swift", + "examples/ios/NativeSdkIOSExample/native_sdk.h", + "examples/ios/NativeSdkIOSExample/NativeSdkDyldShim.c", + }); + addLayoutCheckStep(b, mobile_examples_step, "test-example-android-layout", "Verify Android example layout", &.{ + "examples/android/README.md", + "examples/android/app.zon", + "examples/android/settings.gradle", + "examples/android/build.gradle", + "examples/android/app/build.gradle", + "examples/android/app/src/main/AndroidManifest.xml", + "examples/android/app/src/main/java/dev/native_sdk/examples/android/MainActivity.kt", + "examples/android/app/src/main/cpp/CMakeLists.txt", + "examples/android/app/src/main/cpp/native_sdk_jni.c", + "examples/android/app/src/main/cpp/native_sdk.h", + }); + addLayoutCheckStep(b, mobile_examples_step, "test-example-mobile-shell-layout", "Verify shared mobile-shell metadata", &.{ + "examples/mobile-shell/README.md", + "examples/mobile-shell/app.zon", + }); + + // Host-tier packaged project pin: `native package --target ios` must + // keep emitting the complete, deterministic Xcode project (toolkit + // host sources, Info.plist, asset catalog, scheme) — drift is caught + // here. --binary stubs the embed library with the fixed-shell lib so + // the check needs no iOS cross-compile. + const package_ios_layout_run = b.addRunArtifact(host_cli_exe); + package_ios_layout_run.setCwd(b.path("examples/calculator")); + package_ios_layout_run.setEnvironmentVariable("NATIVE_SDK_PATH", b.pathFromRoot(".")); + package_ios_layout_run.addArgs(&.{ "package", "--target", "ios", "--output", "zig-out/package/test-ios-layout", "--binary" }); + package_ios_layout_run.addFileArg(embed_lib.getEmittedBin()); + package_ios_layout_run.has_side_effects = true; + const package_ios_layout_step = b.step("test-package-ios-layout", "Verify the generated iOS host project layout"); + const package_ios_layout_paths = [_][]const u8{ + "examples/calculator/zig-out/package/test-ios-layout/calculator.xcodeproj/project.pbxproj", + "examples/calculator/zig-out/package/test-ios-layout/calculator.xcodeproj/xcshareddata/xcschemes/calculator.xcscheme", + "examples/calculator/zig-out/package/test-ios-layout/Host/uikit_host.m", + "examples/calculator/zig-out/package/test-ios-layout/Host/native_sdk_app.h", + "examples/calculator/zig-out/package/test-ios-layout/Host/Info.plist", + "examples/calculator/zig-out/package/test-ios-layout/Assets.xcassets/AppIcon.appiconset/AppIcon.png", + "examples/calculator/zig-out/package/test-ios-layout/Assets.xcassets/AppIcon.appiconset/Contents.json", + "examples/calculator/zig-out/package/test-ios-layout/Libraries/libnative-sdk.a", + "examples/calculator/zig-out/package/test-ios-layout/README.md", + "examples/calculator/zig-out/package/test-ios-layout/package-manifest.zon", + }; + for (package_ios_layout_paths) |path| { + const check = b.addSystemCommand(&.{ "test", "-f", path }); + check.step.dependOn(&package_ios_layout_run.step); + package_ios_layout_step.dependOn(&check.step); + mobile_examples_step.dependOn(&check.step); + } + const package_ios_layout_contents = [_]FileContainsCheck{ + .{ .path = "examples/calculator/zig-out/package/test-ios-layout/calculator.xcodeproj/project.pbxproj", .pattern = "PRODUCT_BUNDLE_IDENTIFIER = \"dev.native-sdk.calculator\";" }, + .{ .path = "examples/calculator/zig-out/package/test-ios-layout/Host/Info.plist", .pattern = "UILaunchScreen" }, + .{ .path = "examples/calculator/zig-out/package/test-ios-layout/Host/uikit_host.m", .pattern = "native_sdk_app_render_pixels" }, + }; + for (package_ios_layout_contents) |check_value| { + const check = b.addRunArtifact(file_contains_checker); + check.setCwd(b.path(".")); + check.addArg(check_value.path); + check.addArg(check_value.pattern); + check.step.dependOn(&package_ios_layout_run.step); + package_ios_layout_step.dependOn(&check.step); + mobile_examples_step.dependOn(&check.step); + } + // Host-tier packaged project pin, Android: `native package --target + // android` must keep emitting the complete generated host project + // (toolkit host sources, app.zon-derived manifest, launcher icons) — + // drift is caught here. --binary stubs the embed library with the + // fixed-shell lib so the check needs no Android cross-compile, and + // ANDROID_HOME points at a nonexistent SDK so the deterministic + // project-only path runs everywhere (the APK assembly is exercised + // by the live loops, not CI). + const package_android_layout_run = b.addRunArtifact(host_cli_exe); + package_android_layout_run.setCwd(b.path("examples/calculator")); + package_android_layout_run.setEnvironmentVariable("NATIVE_SDK_PATH", b.pathFromRoot(".")); + package_android_layout_run.setEnvironmentVariable("ANDROID_HOME", b.pathFromRoot("zig-out/no-android-sdk")); + package_android_layout_run.addArgs(&.{ "package", "--target", "android", "--output", "zig-out/package/test-android-layout", "--binary" }); + package_android_layout_run.addFileArg(embed_lib.getEmittedBin()); + package_android_layout_run.has_side_effects = true; + const package_android_layout_step = b.step("test-package-android-layout", "Verify the generated Android host project layout"); + const package_android_layout_paths = [_][]const u8{ + "examples/calculator/zig-out/package/test-android-layout/AndroidManifest.xml", + "examples/calculator/zig-out/package/test-android-layout/Host/NativeSdkActivity.java", + "examples/calculator/zig-out/package/test-android-layout/Host/android_host.c", + "examples/calculator/zig-out/package/test-android-layout/Host/native_sdk_app.h", + "examples/calculator/zig-out/package/test-android-layout/res/mipmap-mdpi/ic_launcher.png", + "examples/calculator/zig-out/package/test-android-layout/res/mipmap-xxxhdpi/ic_launcher.png", + "examples/calculator/zig-out/package/test-android-layout/Libraries/libnative-sdk.a", + "examples/calculator/zig-out/package/test-android-layout/README.md", + "examples/calculator/zig-out/package/test-android-layout/package-manifest.zon", + }; + for (package_android_layout_paths) |path| { + const check = b.addSystemCommand(&.{ "test", "-f", path }); + check.step.dependOn(&package_android_layout_run.step); + package_android_layout_step.dependOn(&check.step); + mobile_examples_step.dependOn(&check.step); + } + const package_android_layout_contents = [_]FileContainsCheck{ + .{ .path = "examples/calculator/zig-out/package/test-android-layout/AndroidManifest.xml", .pattern = "package=\"dev.native_sdk.calculator\"" }, + .{ .path = "examples/calculator/zig-out/package/test-android-layout/AndroidManifest.xml", .pattern = "dev.native_sdk.host.NativeSdkActivity" }, + .{ .path = "examples/calculator/zig-out/package/test-android-layout/Host/NativeSdkActivity.java", .pattern = "InputMethodManager" }, + .{ .path = "examples/calculator/zig-out/package/test-android-layout/Host/android_host.c", .pattern = "native_sdk_app_render_pixels" }, + }; + for (package_android_layout_contents) |check_value| { + const check = b.addRunArtifact(file_contains_checker); + check.setCwd(b.path(".")); + check.addArg(check_value.path); + check.addArg(check_value.pattern); + check.step.dependOn(&package_android_layout_run.step); + package_android_layout_step.dependOn(&check.step); + mobile_examples_step.dependOn(&check.step); + } + addFileContainsCheckStep(b, file_contains_checker, mobile_examples_step, "test-example-mobile-shell-metadata", "Verify shared mobile-shell metadata values", &.{ + .{ .path = "examples/mobile-shell/app.zon", .pattern = ".platforms = .{ \"ios\", \"android\" }" }, + .{ .path = "examples/mobile-shell/app.zon", .pattern = ".capabilities = .{ \"webview\", \"native_views\", \"native_module\" }" }, + .{ .path = "examples/mobile-shell/app.zon", .pattern = ".id = \"mobile.back\"" }, + .{ .path = "examples/mobile-shell/app.zon", .pattern = ".id = \"mobile.refresh\"" }, + .{ .path = "examples/mobile-shell/app.zon", .pattern = ".label = \"mobile-header\"" }, + .{ .path = "examples/mobile-shell/app.zon", .pattern = ".label = \"workspace\", .kind = \"webview\"" }, + }); + addFileContainsCheckStep(b, file_contains_checker, mobile_examples_step, "test-example-mobile-host-commands", "Verify mobile host command metadata values", &.{ + .{ .path = "examples/ios/app.zon", .pattern = ".id = \"mobile.back\"" }, + .{ .path = "examples/ios/app.zon", .pattern = ".id = \"mobile.refresh\"" }, + .{ .path = "examples/ios/app.zon", .pattern = ".label = \"mobile-header\"" }, + .{ .path = "examples/android/app.zon", .pattern = ".id = \"mobile.back\"" }, + .{ .path = "examples/android/app.zon", .pattern = ".id = \"mobile.refresh\"" }, + .{ .path = "examples/android/app.zon", .pattern = ".label = \"mobile-header\"" }, + }); + addFileContainsCheckStep(b, file_contains_checker, mobile_examples_step, "test-example-android-widget-ime", "Verify Android retained widget IME and action bridge", &.{ + .{ .path = "examples/android/app/src/main/java/dev/native_sdk/examples/android/MainActivity.kt", .pattern = "override fun onCreateInputConnection" }, + .{ .path = "examples/android/app/src/main/java/dev/native_sdk/examples/android/MainActivity.kt", .pattern = "nativeIme(nativeApp, kind, text, cursor)" }, + .{ .path = "examples/android/app/src/main/java/dev/native_sdk/examples/android/MainActivity.kt", .pattern = "WIDGET_ACTION_KIND_SET_COMPOSITION = 7" }, + .{ .path = "examples/android/app/src/main/java/dev/native_sdk/examples/android/MainActivity.kt", .pattern = "WIDGET_ACTION_DRAG = 1 shl 8" }, + .{ .path = "examples/android/app/src/main/java/dev/native_sdk/examples/android/MainActivity.kt", .pattern = "WIDGET_ACTION_DROP_FILES = 1 shl 9" }, + }); + addFileContainsCheckStep(b, file_contains_checker, mobile_examples_step, "test-example-mobile-widget-abi", "Verify mobile examples use stable widget ABI lookups", &.{ + .{ .path = "examples/ios/NativeSdkIOSExample/native_sdk.h", .pattern = "native_sdk_viewport_state_t" }, + .{ .path = "examples/ios/NativeSdkIOSExample/native_sdk.h", .pattern = "native_sdk_app_scroll" }, + .{ .path = "examples/ios/NativeSdkIOSExample/native_sdk.h", .pattern = "native_sdk_app_set_text_measure" }, + .{ .path = "examples/android/app/src/main/cpp/native_sdk.h", .pattern = "native_sdk_app_set_text_measure" }, + .{ .path = "examples/mobile-canvas/ios/native_sdk_app.h", .pattern = "native_sdk_app_set_text_measure" }, + .{ .path = "examples/ios/NativeSdkIOSExample/NativeSdkHostViewController.swift", .pattern = "native_sdk_app_widget_semantics_by_id" }, + .{ .path = "examples/android/app/src/main/cpp/native_sdk.h", .pattern = "native_sdk_app_widget_semantics_by_id" }, + .{ .path = "examples/android/app/src/main/java/dev/native_sdk/examples/android/MainActivity.kt", .pattern = "nativeScroll(nativeApp" }, + .{ .path = "examples/android/app/src/main/java/dev/native_sdk/examples/android/MainActivity.kt", .pattern = "nativeWidgetSemanticsByIdFields" }, + .{ .path = "examples/android/app/src/main/cpp/native_sdk_jni.c", .pattern = "native_sdk_app_widget_semantics_by_id" }, + .{ .path = "examples/android/app/src/main/cpp/native_sdk_jni.c", .pattern = "native_sdk_app_scroll" }, + }); + addFileContainsCheckStep(b, file_contains_checker, mobile_examples_step, "test-example-mobile-canvas-span-fonts", "Verify the iOS embed shim measures reserved span font ids with the macOS face mapping", &.{ + .{ .path = "examples/mobile-canvas/ios/main.m", .pattern = "static UIFont *NativeSdkItalicSansFont(UIFont *font)" }, + .{ .path = "examples/mobile-canvas/ios/main.m", .pattern = "static UIFont *NativeSdkWeightedSansFont(" }, + .{ .path = "examples/mobile-canvas/ios/main.m", .pattern = "NativeSdkWeightedSansFont(@[ @\"Geist-Medium\", @\"Geist Medium\" ], UIFontWeightMedium, size)" }, + .{ .path = "examples/mobile-canvas/ios/main.m", .pattern = "NativeSdkWeightedSansFont(@[ @\"Geist-Bold\", @\"Geist Bold\" ], UIFontWeightBold, size)" }, + .{ .path = "examples/mobile-canvas/ios/main.m", .pattern = "NativeSdkItalicSansFont(NativeSdkWeightedSansFont(@[ @\"Geist-Bold\", @\"Geist Bold\" ], UIFontWeightBold, size))" }, + }); + + const build_mobile_canvas_lib = b.addSystemCommand(&.{ "zig", "build", "lib" }); + build_mobile_canvas_lib.setCwd(b.path("examples/mobile-canvas")); + const mobile_canvas_lib_step = b.step("test-example-mobile-canvas-lib", "Build the mobile-canvas embed static library through addMobileLib"); + mobile_canvas_lib_step.dependOn(&build_mobile_canvas_lib.step); + mobile_examples_step.dependOn(&build_mobile_canvas_lib.step); + + // Android cross-compile proof: pure Zig (no NDK sysroot — the static + // lib links no libc), PIC so the objects can land in the shim's .so. + const build_mobile_canvas_lib_android = b.addSystemCommand(&.{ "zig", "build", "lib", "-Dtarget=aarch64-linux-android" }); + build_mobile_canvas_lib_android.setCwd(b.path("examples/mobile-canvas")); + const mobile_canvas_lib_android_step = b.step("test-example-mobile-canvas-lib-android", "Cross-compile the mobile-canvas embed static library for aarch64-linux-android"); + mobile_canvas_lib_android_step.dependOn(&build_mobile_canvas_lib_android.step); + mobile_examples_step.dependOn(&build_mobile_canvas_lib_android.step); + + const examples_step = b.step("test-examples", "Run all example tests and layout checks"); + examples_step.dependOn(frontend_examples_step); + examples_step.dependOn(native_examples_step); + examples_step.dependOn(mobile_examples_step); + + const build_webview_cef = b.addSystemCommand(&.{ "zig", "build", "-Dplatform=macos", "-Dweb-engine=chromium", b.fmt("-Dcef-dir={s}", .{exampleCefDir(b, cef_dir)}) }); + build_webview_cef.setCwd(b.path("examples/webview")); + const webview_cef_link_step = b.step("test-webview-cef-link", "Build the WebView example with Chromium/CEF"); + webview_cef_link_step.dependOn(&build_webview_cef.step); + + const webview_smoke_step = b.step("test-webview-smoke", "Run macOS WebView automation smoke test"); + const webview_smoke_build = b.addSystemCommand(&.{ "zig", "build", "-Dplatform=macos", "-Dweb-engine=system", "-Dautomation=true", "-Djs-bridge=true" }); + webview_smoke_build.setCwd(b.path("examples/webview")); + const webview_smoke_run = b.addSystemCommand(&.{ + "sh", "-c", + \\set -eu + \\cd examples/webview + \\app="zig-out/bin/webview" + \\cli="$1" + \\case "$cli" in /*) ;; *) cli="../../$cli" ;; esac + \\request='{"id":"smoke","command":"native.ping","payload":{"source":"smoke"}}' + \\response_file=".zig-cache/native-sdk-automation/bridge-response.txt" + \\mkdir -p .zig-cache/native-sdk-automation + \\rm -f .zig-cache/native-sdk-automation/snapshot.txt .zig-cache/native-sdk-automation/windows.txt .zig-cache/native-sdk-automation/command*.txt "$response_file" + \\printf 'bridge %s\n' "$request" > .zig-cache/native-sdk-automation/command-1.txt + \\"$app" > .zig-cache/native-sdk-webview-smoke.log 2>&1 & + \\pid=$! + \\trap 'status=$?; kill "$pid" >/dev/null 2>&1 || true; wait "$pid" >/dev/null 2>&1 || true; if [ "$status" -ne 0 ]; then echo "---- app log (.zig-cache/native-sdk-webview-smoke.log) ----" >&2; cat .zig-cache/native-sdk-webview-smoke.log >&2 2>/dev/null || true; fi' EXIT + \\snapshot="$("$cli" automate wait 2>&1)" + \\case "$snapshot" in *"ready=true"*) ;; *) echo "automation snapshot was not ready" >&2; exit 1 ;; esac + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && ! grep -q 'name="webview.load"' .zig-cache/native-sdk-webview-smoke.log; do attempts=$((attempts + 1)); sleep 0.1; done + \\grep -q 'name="webview.load"' .zig-cache/native-sdk-webview-smoke.log || { echo "main window never loaded its webview source (blank window)" >&2; exit 1; } + \\if grep -q 'name="dispatch.error"' .zig-cache/native-sdk-webview-smoke.log; then echo "runtime recorded a dispatch error during startup" >&2; exit 1; fi + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*) ;; *) echo "native.ping did not succeed: $response" >&2; exit 1 ;; esac + \\case "$response" in *'pong from Zig'*) ;; *) echo "native.ping response was unexpected: $response" >&2; exit 1 ;; esac + \\rm -f "$response_file" + \\printf 'bridge %s\n' '{"id":"webview-create","command":"native-sdk.webview.create","payload":{"label":"smoke","url":"https://example.com","frame":{"x":24,"y":24,"width":320,"height":220}}}' > .zig-cache/native-sdk-automation/command-1.txt + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*) ;; *) echo "webview create did not succeed: $response" >&2; exit 1 ;; esac + \\rm -f "$response_file" + \\printf 'bridge %s\n' '{"id":"webview-resize","command":"native-sdk.webview.setFrame","payload":{"label":"smoke","frame":{"x":36,"y":36,"width":420,"height":260}}}' > .zig-cache/native-sdk-automation/command-1.txt + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*) ;; *) echo "webview resize did not succeed: $response" >&2; exit 1 ;; esac + \\rm -f "$response_file" + \\printf 'bridge %s\n' '{"id":"webview-navigate","command":"native-sdk.webview.navigate","payload":{"label":"smoke","url":"https://example.com/?smoke=1"}}' > .zig-cache/native-sdk-automation/command-1.txt + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*) ;; *) echo "webview navigate did not succeed: $response" >&2; exit 1 ;; esac + \\rm -f "$response_file" + \\printf 'bridge %s\n' '{"id":"webview-close","command":"native-sdk.webview.close","payload":{"label":"smoke"}}' > .zig-cache/native-sdk-automation/command-1.txt + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*) ;; *) echo "webview close did not succeed: $response" >&2; exit 1 ;; esac + \\echo "webview smoke ok" + , + "sh", + }); + webview_smoke_run.addFileArg(cli_exe.getEmittedBin()); + webview_smoke_run.step.dependOn(&webview_smoke_build.step); + webview_smoke_run.step.dependOn(&cli_exe.step); + webview_smoke_step.dependOn(&webview_smoke_run.step); + + const native_shell_smoke_step = b.step("test-native-shell-smoke", "Run macOS native-shell automation smoke test"); + const native_shell_smoke_build = b.addSystemCommand(&.{ "zig", "build", "-Dplatform=macos", "-Dweb-engine=system", "-Dautomation=true", "-Djs-bridge=true" }); + native_shell_smoke_build.setCwd(b.path("examples/native-shell")); + const native_shell_smoke_run = b.addSystemCommand(&.{ + "sh", "-c", + \\set -eu + \\cd examples/native-shell + \\app="zig-out/bin/native-shell" + \\cli="$1" + \\case "$cli" in /*) ;; *) cli="../../$cli" ;; esac + \\automation_dir=".zig-cache/native-sdk-automation" + \\response_file="$automation_dir/bridge-response.txt" + \\mkdir -p "$automation_dir" + \\rm -f "$automation_dir/snapshot.txt" "$automation_dir/accessibility.txt" "$automation_dir/windows.txt" "$automation_dir"/command*.txt "$response_file" + \\"$app" > .zig-cache/native-sdk-native-shell-smoke.log 2>&1 & + \\pid=$! + \\trap 'status=$?; kill "$pid" >/dev/null 2>&1 || true; wait "$pid" >/dev/null 2>&1 || true; if [ "$status" -ne 0 ]; then echo "---- app log (.zig-cache/native-sdk-native-shell-smoke.log) ----" >&2; cat .zig-cache/native-sdk-native-shell-smoke.log >&2 2>/dev/null || true; fi' EXIT + \\ready="$("$cli" automate wait 2>&1)" + \\case "$ready" in *"ready=true"*) ;; *) echo "native-shell automation snapshot was not ready" >&2; exit 1 ;; esac + \\snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\case "$snapshot" in *'window @w1 "Native SDK Native Shell"'*) ;; *) echo "native-shell window was missing from snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/toolbar kind=toolbar'*) ;; *) echo "toolbar view was missing from snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/sidebar kind=sidebar'*) ;; *) echo "sidebar view was missing from snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/main kind=webview'*) ;; *) echo "main WebView was missing from snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/statusbar kind=statusbar'*) ;; *) echo "statusbar view was missing from snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/refresh-icon kind=icon_button'*'accessibility_label="Refresh workspace"'*) ;; *) echo "refresh icon accessibility metadata was missing from snapshot" >&2; exit 1 ;; esac + \\"$cli" automate focus refresh-button >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ focus_line="$(printf '%s\n' "$snapshot" | grep -F 'view @w1/refresh-button kind=button' || true)" + \\ case "$focus_line" in *'focused=true'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$focus_line" in *'focused=true'*) ;; *) echo "native-shell refresh button did not receive focus" >&2; exit 1 ;; esac + \\"$cli" automate focus-next >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ focus_line="$(printf '%s\n' "$snapshot" | grep -F 'view @w1/palette-button kind=button' || true)" + \\ case "$focus_line" in *'focused=true'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$focus_line" in *'focused=true'*) ;; *) echo "native-shell focus-next did not move focus to palette button" >&2; exit 1 ;; esac + \\"$cli" automate focus-previous >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ focus_line="$(printf '%s\n' "$snapshot" | grep -F 'view @w1/refresh-button kind=button' || true)" + \\ case "$focus_line" in *'focused=true'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$focus_line" in *'focused=true'*) ;; *) echo "native-shell focus-previous did not return focus to refresh button" >&2; exit 1 ;; esac + \\"$cli" automate resize 900 640 >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'window @w1 "Native SDK Native Shell" bounds=('*' 900x640)'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'window @w1 "Native SDK Native Shell" bounds=('*' 900x640)'*) ;; *) echo "native-shell window resize was not reflected in snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/toolbar kind=toolbar'*'bounds=(0,0 900x52)'*) ;; *) echo "native-shell toolbar did not relayout after resize" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/main kind=webview'*'bounds=(240,52 660x548)'*) ;; *) echo "native-shell main WebView did not relayout after resize" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/statusbar kind=statusbar'*'bounds=(240,600 660x40)'*) ;; *) echo "native-shell statusbar did not relayout after resize" >&2; exit 1 ;; esac + \\rm -f "$response_file" + \\printf 'bridge %s\n' '{"id":"native-shell-refresh","command":"native-sdk.command.invoke","payload":{"name":"app.refresh"}}' > "$automation_dir/command-1.txt" + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*'"name":"app.refresh"'*'"source":"bridge"'*) ;; *) echo "native-shell command bridge did not succeed: $response" >&2; exit 1 ;; esac + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'Refreshed from bridge. Count 1.'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'view @w1/status-label kind=label'*'Refreshed from bridge. Count 1.'*) ;; *) echo "native-shell status view did not reflect bridge refresh" >&2; exit 1 ;; esac + \\"$cli" automate menu-command app.refresh >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'Refreshed from menu. Count 2.'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'view @w1/status-label kind=label'*'Refreshed from menu. Count 2.'*) ;; *) echo "native-shell menu command did not update status" >&2; exit 1 ;; esac + \\"$cli" automate native-command app.refresh refresh-button >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'Refreshed from toolbar. Count 3.'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'view @w1/status-label kind=label'*'Refreshed from toolbar. Count 3.'*) ;; *) echo "native-shell toolbar command did not update status" >&2; exit 1 ;; esac + \\"$cli" automate shortcut app.refresh >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'Refreshed from shortcut. Count 4.'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'view @w1/status-label kind=label'*'Refreshed from shortcut. Count 4.'*) ;; *) echo "native-shell shortcut command did not update status" >&2; exit 1 ;; esac + \\"$cli" automate menu-command app.preview.open >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'view @w1/preview kind=webview'*'bounds=(520,96 320x220)'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'view @w1/preview kind=webview'*'bounds=(520,96 320x220)'*) ;; *) echo "native-shell preview WebView was not created" >&2; exit 1 ;; esac + \\"$cli" automate menu-command app.preview.close >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'view @w1/preview kind=webview'*) ;; *) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'view @w1/preview kind=webview'*) echo "native-shell preview WebView was not closed" >&2; exit 1 ;; *) ;; esac + \\echo "native-shell smoke ok" + , + "sh", + }); + native_shell_smoke_run.addFileArg(cli_exe.getEmittedBin()); + native_shell_smoke_run.step.dependOn(&native_shell_smoke_build.step); + native_shell_smoke_run.step.dependOn(&cli_exe.step); + native_shell_smoke_step.dependOn(&native_shell_smoke_run.step); + + const gpu_surface_smoke_step = b.step("test-gpu-surface-smoke", "Run macOS GPU surface automation smoke test"); + // The GPU smoke apps are managed examples (no build.zig of their own), + // so their binaries come from the CLI verb. -Doptimize=Debug keeps the + // smoke binary at the debug shape the in-dir builds used (`native + // build` injects ReleaseFast when no optimize flag is passed). + const gpu_surface_smoke_build = managedExampleRun(b, cli_exe, &.{ "build", "-Dplatform=macos", "-Dweb-engine=system", "-Dautomation=true", "-Doptimize=Debug" }); + gpu_surface_smoke_build.setCwd(b.path("examples/gpu-surface")); + const gpu_surface_smoke_run = b.addSystemCommand(&.{ + "sh", "-c", + \\set -eu + \\cd examples/gpu-surface + \\app="zig-out/bin/gpu-surface" + \\cli="$1" + \\case "$cli" in /*) ;; *) cli="../../$cli" ;; esac + \\automation_dir=".zig-cache/native-sdk-automation" + \\mkdir -p "$automation_dir" + \\# Startup latencies are load-sensitive on shared CI/agent machines. + \\# NATIVE_SDK_SMOKE_BUDGET_MS raises the first-frame latency budget (default + \\# stays 150 ms) and the automation-ready ceiling (default stays + \\# 500 ms; the ceiling never drops below it) without weakening the + \\# local defaults; every correctness assertion stays strict. + \\smoke_budget_ms="${NATIVE_SDK_SMOKE_BUDGET_MS:-150}" + \\case "$smoke_budget_ms" in ''|*[!0-9]*) echo "NATIVE_SDK_SMOKE_BUDGET_MS must be a positive integer of milliseconds: $smoke_budget_ms" >&2; exit 1 ;; esac + \\if [ "$smoke_budget_ms" -le 0 ]; then echo "NATIVE_SDK_SMOKE_BUDGET_MS must be a positive integer of milliseconds: $smoke_budget_ms" >&2; exit 1; fi + \\smoke_budget_ns=$((smoke_budget_ms * 1000000)) + \\ready_budget_ms="$smoke_budget_ms" + \\if [ "$ready_budget_ms" -lt 500 ]; then ready_budget_ms=500; fi + \\ready_budget_ns=$((ready_budget_ms * 1000000)) + \\rm -f "$automation_dir/snapshot.txt" "$automation_dir/accessibility.txt" "$automation_dir/windows.txt" "$automation_dir"/command*.txt + \\"$app" > .zig-cache/native-sdk-gpu-surface-smoke.log 2>&1 & + \\pid=$! + \\trap 'status=$?; kill "$pid" >/dev/null 2>&1 || true; wait "$pid" >/dev/null 2>&1 || true; if [ "$status" -ne 0 ]; then echo "---- app log (.zig-cache/native-sdk-gpu-surface-smoke.log) ----" >&2; cat .zig-cache/native-sdk-gpu-surface-smoke.log >&2 2>/dev/null || true; fi' EXIT + \\ready="$("$cli" automate wait 2>&1)" + \\case "$ready" in *"ready=true"*) ;; *) echo "gpu-surface automation snapshot was not ready" >&2; exit 1 ;; esac + \\ready_uptime="$(printf '%s\n' "$ready" | sed -n 's/.*runtime_uptime_ns=\([0-9][0-9]*\).*/\1/p')" + \\case "$ready_uptime" in ''|*[!0-9]*) echo "gpu-surface automation ready uptime was missing" >&2; exit 1 ;; esac + \\if [ "$ready_uptime" -le 0 ] || [ "$ready_uptime" -gt "$ready_budget_ns" ]; then echo "gpu-surface automation ready exceeded $ready_budget_ms ms: $ready_uptime ns" >&2; exit 1; fi + \\snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\case "$snapshot" in *'window @w1 "Native SDK GPU Surface"'*) ;; *) echo "gpu-surface window was missing from snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/canvas kind=gpu_surface'*'accessibility_label="Animated GPU surface"'*) ;; *) echo "gpu_surface view was missing from snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/inspector kind=webview'*) ;; *) echo "inspector WebView was missing from snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/toolbar kind=toolbar'*) ;; *) echo "toolbar view was missing from snapshot" >&2; exit 1 ;; esac + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'GPU frame 1 from canvas.'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'view @w1/status-label kind=label'*'GPU frame 1 from canvas.'*) ;; *) echo "gpu-surface frame event did not reach the runtime" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/canvas kind=gpu_surface'*'gpu_nonblank=true'*) ;; *) echo "gpu-surface frame was not verified as nonblank" >&2; exit 1 ;; esac + \\first_frame_latency="$(printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/canvas kind=gpu_surface.* gpu_first_frame_latency_ns=\([0-9][0-9]*\).*/\1/p')" + \\case "$first_frame_latency" in ''|*[!0-9]*) echo "gpu-surface first frame latency was missing" >&2; exit 1 ;; esac + \\if [ "$first_frame_latency" -le 0 ] || [ "$first_frame_latency" -gt "$smoke_budget_ns" ]; then echo "gpu-surface first frame exceeded $smoke_budget_ms ms: $first_frame_latency ns" >&2; exit 1; fi + \\# The runtime publishes its own fixed 150 ms budget verdict. Within that + \\# budget the verdict must agree exactly; beyond it (reachable only when + \\# NATIVE_SDK_SMOKE_BUDGET_MS > 150) the runtime must report the overrun honestly. + \\if [ "$first_frame_latency" -le 150000000 ]; then + \\ case "$snapshot" in *'view @w1/canvas kind=gpu_surface'*'gpu_first_frame_latency_budget_ns=150000000'*'gpu_first_frame_latency_budget_exceeded=0'*'gpu_first_frame_latency_budget_ok=true'*) ;; *) echo "gpu-surface first frame exceeded the latency budget" >&2; exit 1 ;; esac + \\else + \\ case "$snapshot" in *'view @w1/canvas kind=gpu_surface'*'gpu_first_frame_latency_budget_ns=150000000'*'gpu_first_frame_latency_budget_ok=false'*) ;; *) echo "gpu-surface runtime did not report the first-frame budget overrun" >&2; exit 1 ;; esac + \\fi + \\"$cli" automate native-command gpu.refresh refresh >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'GPU surface refreshed from toolbar. Count 1.'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'view @w1/status-label kind=label'*'GPU surface refreshed from toolbar. Count 1.'*) ;; *) echo "gpu-surface refresh command did not update status" >&2; exit 1 ;; esac + \\"$cli" automate resize 960 620 >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'window @w1 "Native SDK GPU Surface" bounds=('*' 960x620)'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'window @w1 "Native SDK GPU Surface" bounds=('*' 960x620)'*) ;; *) echo "gpu-surface window resize was not reflected in snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/canvas kind=gpu_surface'*'bounds=(0,0 680x534)'*) ;; *) echo "gpu_surface view did not relayout after resize" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/inspector kind=webview'*'bounds=(680,52 280x534)'*) ;; *) echo "inspector WebView did not relayout after resize" >&2; exit 1 ;; esac + \\echo "gpu-surface smoke ok" + , + "sh", + }); + gpu_surface_smoke_run.addFileArg(cli_exe.getEmittedBin()); + gpu_surface_smoke_run.step.dependOn(&gpu_surface_smoke_build.step); + gpu_surface_smoke_run.step.dependOn(&cli_exe.step); + gpu_surface_smoke_step.dependOn(&gpu_surface_smoke_run.step); + + const writeback_smoke_step = b.step("test-writeback-smoke", "Run macOS provenance + write-back automation smoke test"); + // Debug on purpose: the write-back loop lives on the markup + // interpreter + hot-reload watch, which apps enable in Debug + // (kanban's dev_markup_reload) - the release engine is compiled and + // watchless by design. + const writeback_smoke_build = managedExampleRun(b, cli_exe, &.{ "build", "-Dplatform=macos", "-Dweb-engine=system", "-Dautomation=true", "-Doptimize=Debug" }); + writeback_smoke_build.setCwd(b.path("examples/kanban")); + const writeback_smoke_run = b.addSystemCommand(&.{ + "sh", "-c", + \\set -eu + \\cd examples/kanban + \\app="zig-out/bin/kanban" + \\cli="$1" + \\case "$cli" in /*) ;; *) cli="../../$cli" ;; esac + \\automation_dir=".zig-cache/native-sdk-automation" + \\mkdir -p "$automation_dir" + \\rm -f "$automation_dir/snapshot.txt" "$automation_dir"/command*.txt "$automation_dir/provenance.txt" + \\# The smoke edits src/board.native through the write-back verb and restores + \\# it through the same verb; the trap restores from the backup on ANY + \\# failure so an aborted run never leaves the example dirty. + \\cp src/board.native .zig-cache/board.native.smoke-backup + \\"$app" > .zig-cache/native-sdk-writeback-smoke.log 2>&1 & + \\pid=$! + \\trap 'status=$?; kill "$pid" >/dev/null 2>&1 || true; wait "$pid" >/dev/null 2>&1 || true; cp .zig-cache/board.native.smoke-backup src/board.native; if [ "$status" -ne 0 ]; then echo "---- app log (.zig-cache/native-sdk-writeback-smoke.log) ----" >&2; cat .zig-cache/native-sdk-writeback-smoke.log >&2 2>/dev/null || true; fi' EXIT + \\"$cli" automate assert --timeout-ms 30000 'ready=true' >/dev/null + \\snapshot="$(cat "$automation_dir/snapshot.txt")" + \\button_id="$(printf '%s\n' "$snapshot" | sed -n 's/.*widget @w1\/kanban-canvas#\([0-9][0-9]*\) role=button name="Add card".*/\1/p' | head -n 1)" + \\case "$button_id" in ''|*[!0-9]*) echo "writeback smoke: Add card button id was missing from the snapshot" >&2; exit 1 ;; esac + \\# 1. Provenance: the button reports its authored span in the root file. + \\provenance="$("$cli" automate provenance kanban-canvas "$button_id" 2>/dev/null)" + \\case "$provenance" in *"authored=markup"*"root=src/board.native"*) ;; *) echo "writeback smoke: button provenance was not markup-authored: $provenance" >&2; exit 1 ;; esac + \\case "$provenance" in *"node file=src/board.native"*) ;; *) echo "writeback smoke: button provenance named the wrong file: $provenance" >&2; exit 1 ;; esac + \\# 2. Template + import chain: a card title reports its definition site in + \\# the component file plus the site in the root file, with the + \\# for-loop iteration key. + \\card_id="$(printf '%s\n' "$snapshot" | sed -n 's/.*widget @w1\/kanban-canvas#\([0-9][0-9]*\) role=text name="Sketch the board layout".*/\1/p' | head -n 1)" + \\case "$card_id" in ''|*[!0-9]*) echo "writeback smoke: card text id was missing from the snapshot" >&2; exit 1 ;; esac + \\card_provenance="$("$cli" automate provenance kanban-canvas "$card_id" 2>/dev/null)" + \\case "$card_provenance" in *"node file=src/components/board-column.native"*) ;; *) echo "writeback smoke: card provenance missed the component file: $card_provenance" >&2; exit 1 ;; esac + \\case "$card_provenance" in *"use file=src/board.native"*) ;; *) echo "writeback smoke: card provenance missed the use-site chain: $card_provenance" >&2; exit 1 ;; esac + \\case "$card_provenance" in *"keys="*) ;; *) echo "writeback smoke: card provenance missed the iteration key: $card_provenance" >&2; exit 1 ;; esac + \\# 3. Write-back: flip the button label through the verb; the app's own + \\# hot-reload watch picks the file change up and repaints. + \\"$cli" automate edit kanban-canvas "$button_id" set-text "Add task" >/dev/null 2>&1 + \\"$cli" automate assert --timeout-ms 15000 'role=button name="Add task"' >/dev/null + \\# The file diff is byte-exact: exactly the label bytes changed. + \\sed 's/>Add cardAdd task .zig-cache/board.native.smoke-expected + \\cmp -s .zig-cache/board.native.smoke-expected src/board.native || { echo "writeback smoke: the edit was not minimal-diff" >&2; exit 1; } + \\# 4. Flip it back through the same verb: the structural id survived the + \\# reload (text is not identity), and the file restores byte-identical. + \\"$cli" automate edit kanban-canvas "$button_id" set-text "Add card" >/dev/null 2>&1 + \\"$cli" automate assert --timeout-ms 15000 'role=button name="Add card"' >/dev/null + \\cmp -s .zig-cache/board.native.smoke-backup src/board.native || { echo "writeback smoke: the flip-back did not restore the file byte-identically" >&2; exit 1; } + \\# 5. Refusal: an edit that fails validation leaves the file untouched. + \\if "$cli" automate edit kanban-canvas "$button_id" set-attr bogus 1 >/dev/null 2>&1; then echo "writeback smoke: an invalid edit was not refused" >&2; exit 1; fi + \\cmp -s .zig-cache/board.native.smoke-backup src/board.native || { echo "writeback smoke: a refused edit touched the file" >&2; exit 1; } + \\echo "writeback smoke ok" + , + "sh", + }); + writeback_smoke_run.addFileArg(cli_exe.getEmittedBin()); + writeback_smoke_run.step.dependOn(&writeback_smoke_build.step); + writeback_smoke_run.step.dependOn(&cli_exe.step); + writeback_smoke_step.dependOn(&writeback_smoke_run.step); + + const gpu_dashboard_smoke_step = b.step("test-gpu-dashboard-smoke", "Run macOS GPU dashboard automation smoke test"); + const gpu_dashboard_smoke_build = managedExampleRun(b, cli_exe, &.{ "build", "-Dplatform=macos", "-Dweb-engine=system", "-Dautomation=true", "-Doptimize=Debug" }); + gpu_dashboard_smoke_build.setCwd(b.path("examples/gpu-dashboard")); + const gpu_dashboard_smoke_run = b.addSystemCommand(&.{ + "sh", "-c", + \\set -eu + \\cd examples/gpu-dashboard + \\app="zig-out/bin/gpu-dashboard" + \\cli="$1" + \\case "$cli" in /*) ;; *) cli="../../$cli" ;; esac + \\automation_dir=".zig-cache/native-sdk-automation" + \\mkdir -p "$automation_dir" + \\# First-frame latency is load-sensitive: a cold file cache or CI/agent + \\# machine contention can blow the 150 ms budget while the frame itself + \\# is presented and correct. Load tolerance without weakening the proof: + \\# (a) NATIVE_SDK_SMOKE_BUDGET_MS raises the smoke's latency budget (default + \\# stays 150 ms) and the automation-ready ceiling (default stays + \\# 500 ms; the ceiling never drops below it), and + \\# (b) a budget-only overrun relaunches the app once and re-measures. + \\# Every correctness assertion (frame presented, packet-representable, + \\# retained content, widget semantics) stays strict on whichever launch + \\# survives, and the runtime's own fixed 150 ms budget verdict is still + \\# asserted verbatim whenever the measured latency is within 150 ms. + \\smoke_budget_ms="${NATIVE_SDK_SMOKE_BUDGET_MS:-150}" + \\case "$smoke_budget_ms" in ''|*[!0-9]*) echo "NATIVE_SDK_SMOKE_BUDGET_MS must be a positive integer of milliseconds: $smoke_budget_ms" >&2; exit 1 ;; esac + \\if [ "$smoke_budget_ms" -le 0 ]; then echo "NATIVE_SDK_SMOKE_BUDGET_MS must be a positive integer of milliseconds: $smoke_budget_ms" >&2; exit 1; fi + \\smoke_budget_ns=$((smoke_budget_ms * 1000000)) + \\ready_budget_ms="$smoke_budget_ms" + \\if [ "$ready_budget_ms" -lt 500 ]; then ready_budget_ms=500; fi + \\ready_budget_ns=$((ready_budget_ms * 1000000)) + \\# gpu-dashboard is a native-only app (nothing in app.zon declares web + \\# use), so its macOS host must never boot the WebKit stack: WKWebView + \\# instantiation spawns com.apple.WebKit.* XPC helpers. They are + \\# launchd-parented (not app children), so the honest cheap probe is + \\# the machine-wide helper set before launch vs at the end of the + \\# session — a NEW helper during this headless smoke window means the + \\# lazy main-WebView path regressed (the failure lists the processes + \\# so a coincidental web app launched mid-smoke is tellable apart). + \\webkit_helpers() { pgrep -f 'com\.apple\.WebKit\.(WebContent|Networking|GPU)' 2>/dev/null | tr '\n' ' '; } + \\webkit_before="$(webkit_helpers)" + \\pid="" + \\trap 'status=$?; kill "$pid" >/dev/null 2>&1 || true; wait "$pid" >/dev/null 2>&1 || true; if [ "$status" -ne 0 ]; then echo "---- app log (.zig-cache/native-sdk-gpu-dashboard-smoke.log) ----" >&2; cat .zig-cache/native-sdk-gpu-dashboard-smoke.log >&2 2>/dev/null || true; fi' EXIT + \\stop_app() { + \\ kill "$pid" >/dev/null 2>&1 || true + \\ wait "$pid" >/dev/null 2>&1 || true + \\ pid="" + \\} + \\launch_and_measure_first_frame() { + \\ rm -f "$automation_dir/snapshot.txt" "$automation_dir/accessibility.txt" "$automation_dir/windows.txt" "$automation_dir"/command*.txt + \\ "$app" > .zig-cache/native-sdk-gpu-dashboard-smoke.log 2>&1 & + \\ pid=$! + \\ ready="$("$cli" automate wait 2>&1)" + \\ case "$ready" in *"ready=true"*) ;; *) echo "gpu-dashboard automation snapshot was not ready" >&2; exit 1 ;; esac + \\ ready_uptime="$(printf '%s\n' "$ready" | sed -n 's/.*runtime_uptime_ns=\([0-9][0-9]*\).*/\1/p')" + \\ case "$ready_uptime" in ''|*[!0-9]*) echo "gpu-dashboard automation ready uptime was missing" >&2; exit 1 ;; esac + \\ if [ "$ready_uptime" -le 0 ] || [ "$ready_uptime" -gt "$ready_budget_ns" ]; then echo "gpu-dashboard automation ready exceeded $ready_budget_ms ms: $ready_uptime ns" >&2; exit 1; fi + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'window @w1 "Native SDK GPU Dashboard"'*) ;; *) echo "gpu-dashboard window was missing from snapshot" >&2; exit 1 ;; esac + \\ case "$snapshot" in *'view @w1/main kind=webview'*) echo "dashboard should not create an implicit main WebView" >&2; exit 1 ;; *) ;; esac + \\ case "$snapshot" in *'source kind=html bytes=0'*) echo "dashboard should not publish an empty default WebView source" >&2; exit 1 ;; *) ;; esac + \\ case "$snapshot" in *'view @w1/dashboard-canvas kind=gpu_surface'*'accessibility_label="Native-rendered product dashboard canvas"'*) ;; *) echo "dashboard GPU canvas was missing from snapshot" >&2; exit 1 ;; esac + \\ attempts=0 + \\ while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'view @w1/dashboard-canvas kind=gpu_surface'*'gpu_nonblank=true'*'canvas_commands=68'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\ done + \\ case "$snapshot" in *'view @w1/dashboard-canvas kind=gpu_surface'*'gpu_nonblank=true'*'canvas_commands=68'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "dashboard GPU canvas did not present the retained display list as a packet" >&2; exit 1 ;; esac + \\ case "$snapshot" in *'view @w1/dashboard-canvas kind=gpu_surface'*'canvas_commands=68'*'widget_semantics=48'*) ;; *) echo "dashboard GPU canvas was missing retained commands or widget semantics" >&2; exit 1 ;; esac + \\ first_frame_latency="$(printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/dashboard-canvas kind=gpu_surface.* gpu_first_frame_latency_ns=\([0-9][0-9]*\).*/\1/p')" + \\ case "$first_frame_latency" in ''|*[!0-9]*) echo "dashboard GPU first frame latency was missing" >&2; exit 1 ;; esac + \\ if [ "$first_frame_latency" -le 0 ]; then echo "dashboard GPU first frame latency was not recorded" >&2; exit 1; fi + \\} + \\launch_and_measure_first_frame + \\if [ "$first_frame_latency" -gt "$smoke_budget_ns" ]; then + \\ echo "dashboard GPU first frame exceeded $smoke_budget_ms ms ($first_frame_latency ns); relaunching once to rule out machine load" >&2 + \\ stop_app + \\ launch_and_measure_first_frame + \\fi + \\if [ "$first_frame_latency" -gt "$smoke_budget_ns" ]; then echo "dashboard GPU first frame exceeded $smoke_budget_ms ms on both launches: $first_frame_latency ns" >&2; exit 1; fi + \\# The runtime publishes its own fixed 150 ms budget verdict. Within that + \\# budget the verdict must agree exactly; beyond it (reachable only when + \\# NATIVE_SDK_SMOKE_BUDGET_MS > 150) the runtime must report the overrun honestly. + \\if [ "$first_frame_latency" -le 150000000 ]; then + \\ case "$snapshot" in *'view @w1/dashboard-canvas kind=gpu_surface'*'gpu_first_frame_latency_budget_ns=150000000'*'gpu_first_frame_latency_budget_exceeded=0'*'gpu_first_frame_latency_budget_ok=true'*) ;; *) echo "dashboard GPU first frame exceeded the latency budget" >&2; exit 1 ;; esac + \\else + \\ case "$snapshot" in *'view @w1/dashboard-canvas kind=gpu_surface'*'gpu_first_frame_latency_budget_ns=150000000'*'gpu_first_frame_latency_budget_ok=false'*) ;; *) echo "dashboard runtime did not report the first-frame budget overrun" >&2; exit 1 ;; esac + \\fi + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'role=text name="Canvas frame:'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\widget_line() { + \\ printf '%s\n' "$snapshot" | grep -F "$1" | head -1 + \\} + \\line="$(widget_line 'role=tab name="Dashboard mode"')" + \\case "$line" in *'actions=[focus,press,select]'*) ;; *) echo "dashboard toolbar mode semantics were missing" >&2; exit 1 ;; esac + \\line="$(widget_line 'role=button name="Refresh dashboard"')" + \\case "$line" in *'actions=[focus,press]'*) ;; *) echo "dashboard toolbar refresh semantics were missing" >&2; exit 1 ;; esac + \\# CoreText-backed layout metrics: the refresh button must be sized by the + \\# platform text measure provider, not the deterministic estimator + \\# (estimateTextWidthForFont sizes "Refresh" at 14px to 51.842 -> 75.841995 wide). + \\case "$line" in *'75.841995x34'*) echo "dashboard refresh button was sized by the estimator; platform text measurement is inactive" >&2; exit 1 ;; esac + \\refresh_width="$(printf '%s\n' "$line" | sed -n 's/.*bounds=([0-9.,-]* \([0-9.]*\)x[0-9.]*).*/\1/p')" + \\case "$refresh_width" in ''|*[!0-9.]*) echo "dashboard refresh button width was missing" >&2; exit 1 ;; esac + \\if [ "$(printf '%s\n' "$refresh_width < 50 || $refresh_width > 110" | bc)" -eq 1 ]; then echo "dashboard refresh button width was implausible: $refresh_width" >&2; exit 1; fi + \\line="$(widget_line 'role=button name="Live render status"')" + \\case "$line" in *'actions=[focus,press]'*) ;; *) echo "dashboard live render button semantics were missing" >&2; exit 1 ;; esac + \\line="$(widget_line 'role=textbox name="Forecast amount"')" + \\case "$line" in *'text="$13.4M"'*) ;; *) echo "dashboard forecast textbox semantics were missing" >&2; exit 1 ;; esac + \\line="$(widget_line 'role=dialog name="Revenue filter popover"')" + \\case "$line" in '') echo "dashboard popover semantics were missing" >&2; exit 1 ;; esac + \\line="$(widget_line 'role=text name="Canvas frame:')" + \\case "$line" in *'packet ok'*) ;; *) echo "dashboard canvas status semantics were missing" >&2; exit 1 ;; esac + \\gpu_frame_from_snapshot() { + \\ printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/dashboard-canvas kind=gpu_surface.* gpu_frame=\([0-9][0-9]*\).*/\1/p' + \\} + \\canvas_revision_from_snapshot() { + \\ printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/dashboard-canvas kind=gpu_surface.* canvas_revision=\([0-9][0-9]*\).*/\1/p' + \\} + \\snapshot_contains() { + \\ case "$snapshot" in *"$1"*) return 0 ;; *) return 1 ;; esac + \\} + \\canvas_revision_before_resize="$(canvas_revision_from_snapshot)" + \\case "$canvas_revision_before_resize" in ''|*[!0-9]*) echo "dashboard canvas revision was missing before resize" >&2; exit 1 ;; esac + \\"$cli" automate resize 1120 700 >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'window @w1 "Native SDK GPU Dashboard" bounds=('*' 1120x700)'*'view @w1/dashboard-canvas kind=gpu_surface'*'bounds=(0,0 1120x700)'*'gpu_nonblank=true'*'canvas_commands=68'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'window @w1 "Native SDK GPU Dashboard" bounds=('*' 1120x700)'*) ;; *) echo "dashboard window resize was not reflected in snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/dashboard-canvas kind=gpu_surface'*'bounds=(0,0 1120x700)'*) ;; *) echo "dashboard GPU canvas did not relayout after resize" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/dashboard-canvas kind=gpu_surface'*'gpu_nonblank=true'*'canvas_commands=68'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "dashboard GPU canvas did not remain packet-renderable after resize" >&2; exit 1 ;; esac + \\canvas_revision_after_resize="$(canvas_revision_from_snapshot)" + \\case "$canvas_revision_after_resize" in ''|*[!0-9]*) echo "dashboard canvas revision was missing after resize" >&2; exit 1 ;; esac + \\if [ "$canvas_revision_after_resize" -lt "$canvas_revision_before_resize" ]; then echo "dashboard canvas revision went backwards after resize: $canvas_revision_before_resize -> $canvas_revision_after_resize" >&2; exit 1; fi + \\gpu_frame_before="$(gpu_frame_from_snapshot)" + \\case "$gpu_frame_before" in ''|*[!0-9]*) gpu_frame_before=0 ;; esac + \\gpu_frame_after="$gpu_frame_before" + \\switch_id="$(printf '%s\n' "$snapshot" | sed -n 's/.*widget @w1\/dashboard-canvas#\([0-9][0-9]*\) role=switch name="Auto refresh".*/\1/p' | head -1)" + \\case "$switch_id" in ''|*[!0-9]*) echo "dashboard auto refresh switch id was missing from snapshot" >&2; exit 1 ;; esac + \\"$cli" automate widget-click dashboard-canvas "$switch_id" >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ gpu_frame_after="$(gpu_frame_from_snapshot)" + \\ case "$gpu_frame_after" in ''|*[!0-9]*) gpu_frame_after=0 ;; esac + \\ if [ "$gpu_frame_after" -gt "$gpu_frame_before" ]; then + \\ if snapshot_contains 'Auto refresh off.' && snapshot_contains 'view @w1/dashboard-canvas kind=gpu_surface' && snapshot_contains 'canvas_frame_full_repaint=false' && snapshot_contains 'canvas_frame_pipeline_uploads=0' && snapshot_contains 'canvas_frame_glyph_uploads=0' && snapshot_contains 'canvas_frame_text_uploads=0' && snapshot_contains 'canvas_frame_gpu_packet_unsupported=0' && snapshot_contains 'canvas_frame_gpu_packet_representable=true'; then + \\ switch_line="$(printf '%s\n' "$snapshot" | grep -F 'role=switch name="Auto refresh"' | head -1)" + \\ case "$switch_line" in *'value=0'*) break ;; esac + \\ fi + \\ fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if [ "$gpu_frame_after" -le "$gpu_frame_before" ]; then echo "dashboard switch click did not request a GPU frame" >&2; exit 1; fi + \\case "$snapshot" in *'Auto refresh off.'*) ;; *) echo "dashboard switch click did not update status" >&2; exit 1 ;; esac + \\switch_line="$(printf '%s\n' "$snapshot" | grep -F 'role=switch name="Auto refresh"' | head -1)" + \\case "$switch_line" in *'value=0'*) ;; *) echo "dashboard switch click did not route through pointer input" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/dashboard-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "dashboard switch click did not present an incremental GPU packet without interaction-time uploads" >&2; exit 1 ;; esac + \\input_timestamp="$(printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/dashboard-canvas kind=gpu_surface.* gpu_input_timestamp_ns=\([0-9][0-9]*\).*/\1/p')" + \\case "$input_timestamp" in ''|*[!0-9]*) echo "dashboard GPU input timestamp was missing after widget interaction" >&2; exit 1 ;; esac + \\if [ "$input_timestamp" -le 0 ]; then echo "dashboard GPU input timestamp was not recorded" >&2; exit 1; fi + \\input_latency="$(printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/dashboard-canvas kind=gpu_surface.* gpu_input_latency_ns=\([0-9][0-9]*\).*/\1/p')" + \\case "$input_latency" in ''|*[!0-9]*) echo "dashboard GPU input latency was missing after widget interaction" >&2; exit 1 ;; esac + \\# --- Incremental pixel equivalence: relaunch under the host's verify + \\# mode, where every scissored dirty update is byte-compared against a + \\# from-scratch full redraw of the retained command list. Clicks, the + \\# live-pulse animation, and a mid-animation resize drive scissored + \\# patches; the verify counters must show checks with zero mismatches. + \\stop_app + \\rm -f "$automation_dir/snapshot.txt" "$automation_dir"/command*.txt + \\verify_log=".zig-cache/native-sdk-gpu-dashboard-verify.log" + \\NATIVE_SDK_GPU_VERIFY_INCREMENTAL=1 "$app" > "$verify_log" 2>&1 & + \\pid=$! + \\ready="$("$cli" automate wait 2>&1)" + \\case "$ready" in *"ready=true"*) ;; *) echo "gpu-dashboard verify relaunch was not ready" >&2; exit 1 ;; esac + \\snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\switch_id="$(printf '%s\n' "$snapshot" | sed -n 's/.*widget @w1\/dashboard-canvas#\([0-9][0-9]*\) role=switch name="Auto refresh".*/\1/p' | head -1)" + \\live_id="$(printf '%s\n' "$snapshot" | sed -n 's/.*widget @w1\/dashboard-canvas#\([0-9][0-9]*\) role=button name="Live render status".*/\1/p' | head -1)" + \\case "$switch_id" in ''|*[!0-9]*) echo "dashboard verify switch id was missing" >&2; exit 1 ;; esac + \\case "$live_id" in ''|*[!0-9]*) echo "dashboard verify live button id was missing" >&2; exit 1 ;; esac + \\"$cli" automate widget-click dashboard-canvas "$switch_id" >/dev/null 2>&1 + \\sleep 0.4 + \\"$cli" automate widget-click dashboard-canvas "$live_id" >/dev/null 2>&1 + \\sleep 0.3 + \\"$cli" automate resize 1120 700 >/dev/null 2>&1 + \\sleep 0.5 + \\"$cli" automate widget-click dashboard-canvas "$live_id" >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ if grep -q "gpu incremental verify view=dashboard-canvas checks=" "$verify_log" 2>/dev/null; then break; fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if ! grep -q "gpu incremental verify view=dashboard-canvas checks=" "$verify_log" 2>/dev/null; then echo "dashboard verify mode recorded no incremental checks" >&2; exit 1; fi + \\if grep -q "verify MISMATCH" "$verify_log" 2>/dev/null; then echo "dashboard incremental present diverged from a full redraw:" >&2; grep "verify MISMATCH" "$verify_log" >&2; exit 1; fi + \\if grep "gpu incremental verify view=dashboard-canvas checks=" "$verify_log" | grep -qv "mismatches=0"; then echo "dashboard incremental verify reported mismatches" >&2; exit 1; fi + \\# The whole session ran (two launches, clicks, resizes): a native-only + \\# macOS app must have spawned ZERO new WebKit helper processes. + \\webkit_after="$(webkit_helpers)" + \\new_webkit="" + \\for helper_pid in $webkit_after; do + \\ case " $webkit_before " in *" $helper_pid "*) ;; *) new_webkit="$new_webkit $helper_pid" ;; esac + \\done + \\if [ -n "$new_webkit" ]; then + \\ echo "native-only gpu-dashboard session spawned WebKit helper processes:$new_webkit" >&2 + \\ for helper_pid in $new_webkit; do ps -p "$helper_pid" -o pid,ppid,command >&2 || true; done + \\ exit 1 + \\fi + \\echo "zero new WebKit helper processes during the native-only session" + \\echo "gpu-dashboard smoke ok" + , + "sh", + }); + gpu_dashboard_smoke_run.addFileArg(cli_exe.getEmittedBin()); + gpu_dashboard_smoke_run.step.dependOn(&gpu_dashboard_smoke_build.step); + gpu_dashboard_smoke_run.step.dependOn(&cli_exe.step); + gpu_dashboard_smoke_step.dependOn(&gpu_dashboard_smoke_run.step); + + // Percentile performance check — a single-sample gate was noise-dominated, + // so this asserts p90 over N launches. Deliberately NOT part of + // `zig build test` or the fast gate tier: N launches are slow and the + // numbers only mean something on a controlled machine. Runs via + // `scripts/gate.sh full --perf` locally and a dedicated macos-14 CI job. + // Knobs: NATIVE_SDK_PERF_LAUNCHES, NATIVE_SDK_PERF_INTERACTIONS, NATIVE_SDK_PERF_BUDGET_MS, + // NATIVE_SDK_PERF_INPUT_BUDGET_MS — see scripts/perf-gpu-dashboard.sh. + const gpu_dashboard_perf_step = b.step("test-gpu-dashboard-perf", "Run macOS GPU dashboard percentile performance check (N launches; slow)"); + const gpu_dashboard_perf_build = managedExampleRun(b, cli_exe, &.{ "build", "-Dplatform=macos", "-Dweb-engine=system", "-Dautomation=true", "-Doptimize=Debug" }); + gpu_dashboard_perf_build.setCwd(b.path("examples/gpu-dashboard")); + const gpu_dashboard_perf_run = b.addSystemCommand(&.{ "sh", "scripts/perf-gpu-dashboard.sh" }); + gpu_dashboard_perf_run.addFileArg(cli_exe.getEmittedBin()); + gpu_dashboard_perf_run.step.dependOn(&gpu_dashboard_perf_build.step); + gpu_dashboard_perf_run.step.dependOn(&cli_exe.step); + gpu_dashboard_perf_step.dependOn(&gpu_dashboard_perf_run.step); + + const canvas_preview_smoke_step = b.step("test-canvas-preview-smoke", "Run macOS canvas + webview (both-in-one-window) automation smoke test"); + const canvas_preview_smoke_build = managedExampleRun(b, cli_exe, &.{ "build", "-Dplatform=macos", "-Dweb-engine=system", "-Dautomation=true", "-Doptimize=Debug" }); + canvas_preview_smoke_build.setCwd(b.path("examples/canvas-preview")); + const canvas_preview_smoke_run = b.addSystemCommand(&.{ + "sh", "-c", + \\set -eu + \\cd examples/canvas-preview + \\app="zig-out/bin/canvas-preview" + \\cli="$1" + \\case "$cli" in /*) ;; *) cli="../../$cli" ;; esac + \\automation_dir=".zig-cache/native-sdk-automation" + \\mkdir -p "$automation_dir" + \\rm -f "$automation_dir/snapshot.txt" "$automation_dir/accessibility.txt" "$automation_dir/windows.txt" "$automation_dir"/command*.txt "$automation_dir/screenshot-preview-canvas.png" + \\"$app" > .zig-cache/native-sdk-canvas-preview-smoke.log 2>&1 & + \\pid=$! + \\trap 'status=$?; kill "$pid" >/dev/null 2>&1 || true; wait "$pid" >/dev/null 2>&1 || true; if [ "$status" -ne 0 ]; then echo "---- app log (.zig-cache/native-sdk-canvas-preview-smoke.log) ----" >&2; cat .zig-cache/native-sdk-canvas-preview-smoke.log >&2 2>/dev/null || true; fi' EXIT + \\ready="$("$cli" automate wait 2>&1)" + \\case "$ready" in *"ready=true"*) ;; *) echo "canvas-preview automation snapshot was not ready" >&2; exit 1 ;; esac + \\# Both architectures live in window 1: a presenting Metal canvas and + \\# a live WKWebView on a real https URL — with no implicit main webview. + \\"$cli" automate assert 'view @w1/preview-canvas kind=gpu_surface.*gpu_nonblank=true' 'view @w1/preview kind=webview.*url="https://example.com/"' + \\"$cli" automate assert --absent 'view @w1/main kind=webview' 'source kind=html bytes=0' + \\# The webview pane is snapped to the canvas anchor widget: right of + \\# the 224pt sidebar, below the 56pt toolbar. + \\preview_line="$(grep 'view @w1/preview kind=webview' "$automation_dir/snapshot.txt" | head -1)" + \\preview_x="$(printf '%s\n' "$preview_line" | sed -n 's/.*bounds=(\([0-9.]*\),.*/\1/p')" + \\case "$preview_x" in ''|*[!0-9.]*) echo "canvas-preview webview x was missing" >&2; exit 1 ;; esac + \\if [ "$(printf '%s\n' "$preview_x < 224" | bc)" -eq 1 ]; then echo "canvas-preview webview was not snapped right of the sidebar: x=$preview_x" >&2; exit 1; fi + \\# Navigation from a Msg: the app command switches the model URL and + \\# the runtime navigates the platform webview. + \\"$cli" automate native-command app.docs >/dev/null 2>&1 + \\"$cli" automate assert 'view @w1/preview kind=webview.*url="https://native-sdk.dev/"' 'name="URL: https://native-sdk.dev/"' + \\# Resize keeps the pane snapped to the anchor widget's new frame: + \\# the panel right of the 224pt sidebar and below the 56pt toolbar. + \\"$cli" automate resize 1200 800 >/dev/null 2>&1 + \\"$cli" automate assert 'window @w1 "Native SDK Canvas Preview" bounds=.*1200x800' 'view @w1/preview kind=webview.*bounds=.224,56 976x744' + \\# Canvas screenshot evidence (reference-rendered PNG of the chrome). + \\"$cli" automate screenshot preview-canvas >/dev/null 2>&1 + \\test -s "$automation_dir/screenshot-preview-canvas.png" || { echo "canvas-preview screenshot was empty" >&2; exit 1; } + \\echo "canvas-preview smoke ok" + , + "sh", + }); + canvas_preview_smoke_run.addFileArg(cli_exe.getEmittedBin()); + canvas_preview_smoke_run.step.dependOn(&canvas_preview_smoke_build.step); + canvas_preview_smoke_run.step.dependOn(&cli_exe.step); + canvas_preview_smoke_step.dependOn(&canvas_preview_smoke_run.step); + + const gpu_components_smoke_step = b.step("test-gpu-components-smoke", "Run macOS GPU components automation smoke test"); + const gpu_components_smoke_build = managedExampleRun(b, cli_exe, &.{ "build", "-Dplatform=macos", "-Dweb-engine=system", "-Dautomation=true", "-Doptimize=Debug" }); + gpu_components_smoke_build.setCwd(b.path("examples/gpu-components")); + const gpu_components_smoke_run = b.addSystemCommand(&.{ + "sh", "-c", + \\set -eu + \\cd examples/gpu-components + \\app="zig-out/bin/gpu-components" + \\cli="$1" + \\case "$cli" in /*) ;; *) cli="../../$cli" ;; esac + \\automation_dir=".zig-cache/native-sdk-automation" + \\mkdir -p "$automation_dir" + \\# Startup latencies are load-sensitive on shared CI/agent machines. + \\# NATIVE_SDK_SMOKE_BUDGET_MS raises the first-frame latency budget (default + \\# stays 150 ms) and the automation-ready ceiling (default stays + \\# 500 ms; the ceiling never drops below it) without weakening the + \\# local defaults; every correctness assertion stays strict. + \\smoke_budget_ms="${NATIVE_SDK_SMOKE_BUDGET_MS:-150}" + \\case "$smoke_budget_ms" in ''|*[!0-9]*) echo "NATIVE_SDK_SMOKE_BUDGET_MS must be a positive integer of milliseconds: $smoke_budget_ms" >&2; exit 1 ;; esac + \\if [ "$smoke_budget_ms" -le 0 ]; then echo "NATIVE_SDK_SMOKE_BUDGET_MS must be a positive integer of milliseconds: $smoke_budget_ms" >&2; exit 1; fi + \\smoke_budget_ns=$((smoke_budget_ms * 1000000)) + \\ready_budget_ms="$smoke_budget_ms" + \\if [ "$ready_budget_ms" -lt 500 ]; then ready_budget_ms=500; fi + \\ready_budget_ns=$((ready_budget_ms * 1000000)) + \\rm -f "$automation_dir/snapshot.txt" "$automation_dir/accessibility.txt" "$automation_dir/windows.txt" "$automation_dir"/command*.txt + \\"$app" > .zig-cache/native-sdk-gpu-components-smoke.log 2>&1 & + \\pid=$! + \\trap 'status=$?; kill "$pid" >/dev/null 2>&1 || true; wait "$pid" >/dev/null 2>&1 || true; if [ "$status" -ne 0 ]; then echo "---- app log (.zig-cache/native-sdk-gpu-components-smoke.log) ----" >&2; cat .zig-cache/native-sdk-gpu-components-smoke.log >&2 2>/dev/null || true; fi' EXIT + \\ready="$("$cli" automate wait 2>&1)" + \\case "$ready" in *"ready=true"*) ;; *) echo "gpu-components automation snapshot was not ready" >&2; exit 1 ;; esac + \\ready_uptime="$(printf '%s\n' "$ready" | sed -n 's/.*runtime_uptime_ns=\([0-9][0-9]*\).*/\1/p')" + \\case "$ready_uptime" in ''|*[!0-9]*) echo "gpu-components automation ready uptime was missing" >&2; exit 1 ;; esac + \\if [ "$ready_uptime" -le 0 ] || [ "$ready_uptime" -gt "$ready_budget_ns" ]; then echo "gpu-components automation ready exceeded $ready_budget_ms ms: $ready_uptime ns" >&2; exit 1; fi + \\snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\case "$snapshot" in *'window @w1 "Native SDK GPU Components"'*) ;; *) echo "gpu-components window was missing from snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/main kind=webview'*) echo "components should not create an implicit main WebView" >&2; exit 1 ;; *) ;; esac + \\case "$snapshot" in *'source kind=html bytes=0'*) echo "components should not publish an empty default WebView source" >&2; exit 1 ;; *) ;; esac + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'gpu_nonblank=true'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'gpu_nonblank=true'*) ;; *) echo "components GPU surface was not ready and nonblank" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "components GPU frame was not packet-representable" >&2; exit 1 ;; esac + \\first_frame_latency="$(printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/components-canvas kind=gpu_surface.* gpu_first_frame_latency_ns=\([0-9][0-9]*\).*/\1/p')" + \\case "$first_frame_latency" in ''|*[!0-9]*) echo "components GPU first frame latency was missing" >&2; exit 1 ;; esac + \\if [ "$first_frame_latency" -le 0 ] || [ "$first_frame_latency" -gt "$smoke_budget_ns" ]; then echo "components GPU first frame exceeded $smoke_budget_ms ms: $first_frame_latency ns" >&2; exit 1; fi + \\# The runtime publishes its own fixed 150 ms budget verdict. Within that + \\# budget the verdict must agree exactly; beyond it (reachable only when + \\# NATIVE_SDK_SMOKE_BUDGET_MS > 150) the runtime must report the overrun honestly. + \\if [ "$first_frame_latency" -le 150000000 ]; then + \\ case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'gpu_first_frame_latency_budget_ns=150000000'*'gpu_first_frame_latency_budget_exceeded=0'*'gpu_first_frame_latency_budget_ok=true'*) ;; *) echo "components GPU first frame exceeded the latency budget" >&2; exit 1 ;; esac + \\else + \\ case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'gpu_first_frame_latency_budget_ns=150000000'*'gpu_first_frame_latency_budget_ok=false'*) ;; *) echo "components runtime did not report the first-frame budget overrun" >&2; exit 1 ;; esac + \\fi + \\gpu_frame_from_snapshot() { + \\ printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/components-canvas kind=gpu_surface.* gpu_frame=\([0-9][0-9]*\).*/\1/p' + \\} + \\canvas_revision_from_snapshot() { + \\ printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/components-canvas kind=gpu_surface.* canvas_revision=\([0-9][0-9]*\).*/\1/p' + \\} + \\snapshot_contains() { + \\ case "$snapshot" in *"$1"*) return 0 ;; *) return 1 ;; esac + \\} + \\case "$snapshot" in *'widget @w1/components-canvas#113 role=checkbox'*'value=1'*'actions=[focus,toggle]'*) ;; *) echo "checkbox widget was not initially selected" >&2; exit 1 ;; esac + \\case "$snapshot" in *'widget @w1/components-canvas#114 role=switch'*'value=1'*'actions=[focus,toggle]'*) ;; *) echo "switch widget was not initially selected" >&2; exit 1 ;; esac + \\case "$snapshot" in *'widget @w1/components-canvas#116 role=progressbar'*'value=1'*) ;; *) echo "progress widget was missing from the initial snapshot" >&2; exit 1 ;; esac + \\case "$snapshot" in *'widget @w1/components-canvas#117 role=tab'*'value=1'*'actions=[focus,select]'*) ;; *) echo "small segmented control was not initially selected" >&2; exit 1 ;; esac + \\case "$snapshot" in *'widget @w1/components-canvas#119 role=tab'*'value=0'*'actions=[focus,select]'*) ;; *) echo "large segmented control was not initially available" >&2; exit 1 ;; esac + \\case "$snapshot" in *'widget @w1/components-canvas#142 role=menuitem'*'actions=[focus,press,select]'*) ;; *) echo "menu item widget was not initially actionable" >&2; exit 1 ;; esac + \\case "$snapshot" in *'widget @w1/components-canvas#86 role=tab'*'actions=[focus,press,select]'*) ;; *) echo "theme toolbar trigger was not initially actionable" >&2; exit 1 ;; esac + \\case "$snapshot" in *'widget @w1/components-canvas#83 role=button'*'actions=[focus,press]'*) ;; *) echo "refresh toolbar widget was not initially actionable" >&2; exit 1 ;; esac + \\gpu_frame_before="$(gpu_frame_from_snapshot)" + \\case "$gpu_frame_before" in ''|*[!0-9]*) gpu_frame_before=0 ;; esac + \\gpu_frame_after="$gpu_frame_before" + \\canvas_revision_before="$(canvas_revision_from_snapshot)" + \\case "$canvas_revision_before" in ''|*[!0-9]*) canvas_revision_before=0 ;; esac + \\canvas_revision_after="$canvas_revision_before" + \\"$cli" automate widget-click components-canvas 86 >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ gpu_frame_after="$(gpu_frame_from_snapshot)" + \\ case "$gpu_frame_after" in ''|*[!0-9]*) gpu_frame_after=0 ;; esac + \\ canvas_revision_after="$(canvas_revision_from_snapshot)" + \\ case "$canvas_revision_after" in ''|*[!0-9]*) canvas_revision_after=0 ;; esac + \\ if [ "$gpu_frame_after" -gt "$gpu_frame_before" ] || [ "$canvas_revision_after" -gt "$canvas_revision_before" ]; then + \\ if snapshot_contains 'GPU component theme: ' && snapshot_contains ' from native_view. Count 1.' && snapshot_contains 'view @w1/components-canvas kind=gpu_surface' && snapshot_contains 'canvas_frame_gpu_packet_unsupported=0' && snapshot_contains 'canvas_frame_gpu_packet_representable=true'; then break; fi + \\ fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if [ "$gpu_frame_after" -le "$gpu_frame_before" ] && [ "$canvas_revision_after" -le "$canvas_revision_before" ]; then echo "theme automation command did not update the retained GPU canvas" >&2; exit 1; fi + \\case "$snapshot" in *'GPU component theme: '*' from native_view. Count 1.'*) ;; *) echo "theme automation command did not update status" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "theme automation command did not present a packet-renderable GPU frame" >&2; exit 1 ;; esac + \\"$cli" automate widget-action components-canvas 111 focus >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'widget @w1/components-canvas#111 role=textbox'*'focused=true'*'text="native-sdk"'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'widget @w1/components-canvas#111 role=textbox'*'focused=true'*'text="native-sdk"'*) ;; *) echo "widget focus automation did not focus retained text" >&2; exit 1 ;; esac + \\"$cli" automate widget-key components-canvas z z >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'widget @w1/components-canvas#111 role=textbox'*'focused=true'*'text="native-sdkz"'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'widget @w1/components-canvas#111 role=textbox'*'focused=true'*'text="native-sdkz"'*) ;; *) echo "widget keyboard automation did not update retained text" >&2; exit 1 ;; esac + \\"$cli" automate widget-key components-canvas tab >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'widget @w1/components-canvas#112 role=textbox'*'focused=true'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'widget @w1/components-canvas#112 role=textbox'*'focused=true'*) ;; *) echo "widget keyboard automation did not move focus" >&2; exit 1 ;; esac + \\"$cli" automate widget-action components-canvas 105 press >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ if snapshot_contains 'Keyed icon_button #105.' && snapshot_contains 'widget @w1/components-canvas#105 role=button' && snapshot_contains 'actions=[focus,press]'; then break; fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if ! snapshot_contains 'Keyed icon_button #105.' || ! snapshot_contains 'widget @w1/components-canvas#105 role=button' || ! snapshot_contains 'actions=[focus,press]'; then echo "icon button automation press did not update status" >&2; exit 1; fi + \\"$cli" automate widget-action components-canvas 111 set-text native-engine >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'widget @w1/components-canvas#111 role=textbox'*'text="native-engine"'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'widget @w1/components-canvas#111 role=textbox'*'text="native-engine"'*) ;; *) echo "text field automation did not update retained text" >&2; exit 1 ;; esac + \\"$cli" automate widget-action components-canvas 115 increment >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$snapshot" in *'Keyed slider #115: value '*) + \\ case "$snapshot" in *'widget @w1/components-canvas#115 role=slider'*'value=0.62'*) ;; *'widget @w1/components-canvas#115 role=slider'*) break ;; esac + \\ ;; + \\ esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\case "$snapshot" in *'Keyed slider #115: value '*) ;; *) echo "slider automation increment did not update status" >&2; exit 1 ;; esac + \\case "$snapshot" in *'widget @w1/components-canvas#115 role=slider'*'value=0.62'*) echo "slider automation increment did not change value" >&2; exit 1 ;; *'widget @w1/components-canvas#115 role=slider'*) ;; *) echo "slider widget was missing after increment" >&2; exit 1 ;; esac + \\gpu_frame_before="$(gpu_frame_from_snapshot)" + \\case "$gpu_frame_before" in ''|*[!0-9]*) gpu_frame_before=0 ;; esac + \\gpu_frame_after="$gpu_frame_before" + \\"$cli" automate widget-drag components-canvas 115 0.25 0.82 >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ gpu_frame_after="$(gpu_frame_from_snapshot)" + \\ case "$gpu_frame_after" in ''|*[!0-9]*) gpu_frame_after=0 ;; esac + \\ if [ "$gpu_frame_after" -gt "$gpu_frame_before" ]; then + \\ if snapshot_contains 'Clicked slider #115: value 0.82.' && snapshot_contains 'widget @w1/components-canvas#115 role=slider' && { snapshot_contains 'value=0.82' || snapshot_contains 'value=0.819'; }; then + \\ case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) break ;; esac + \\ fi + \\ fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if [ "$gpu_frame_after" -le "$gpu_frame_before" ]; then echo "slider automation drag did not request a GPU frame" >&2; exit 1; fi + \\if ! snapshot_contains 'Clicked slider #115: value 0.82.' || ! snapshot_contains 'widget @w1/components-canvas#115 role=slider' || { ! snapshot_contains 'value=0.82' && ! snapshot_contains 'value=0.819'; }; then echo "slider automation drag did not update retained slider state" >&2; exit 1; fi + \\case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "slider automation drag did not present an incremental GPU packet without interaction-time uploads" >&2; exit 1 ;; esac + \\"$cli" automate widget-action components-canvas 156 press >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ if snapshot_contains 'Keyed data_cell #156: selected.' && snapshot_contains 'widget @w1/components-canvas#156 role=gridcell' && snapshot_contains 'focused=true' && snapshot_contains 'value=1'; then break; fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if ! snapshot_contains 'Keyed data_cell #156: selected.' || ! snapshot_contains 'widget @w1/components-canvas#156 role=gridcell' || ! snapshot_contains 'focused=true' || ! snapshot_contains 'value=1'; then echo "grid cell automation press did not focus and report status" >&2; exit 1; fi + \\gpu_frame_before="$(gpu_frame_from_snapshot)" + \\case "$gpu_frame_before" in ''|*[!0-9]*) gpu_frame_before=0 ;; esac + \\gpu_frame_after="$gpu_frame_before" + \\"$cli" automate widget-click components-canvas 119 >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ gpu_frame_after="$(gpu_frame_from_snapshot)" + \\ case "$gpu_frame_after" in ''|*[!0-9]*) gpu_frame_after=0 ;; esac + \\ if [ "$gpu_frame_after" -gt "$gpu_frame_before" ]; then + \\ if snapshot_contains 'Clicked segmented_control #119: selected.' && snapshot_contains 'widget @w1/components-canvas#117 role=tab' && snapshot_contains 'value=0' && snapshot_contains 'widget @w1/components-canvas#119 role=tab' && snapshot_contains 'focused=true' && snapshot_contains 'value=1'; then + \\ case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) break ;; esac + \\ fi + \\ fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if [ "$gpu_frame_after" -le "$gpu_frame_before" ]; then echo "segmented control automation click did not request a GPU frame" >&2; exit 1; fi + \\if ! snapshot_contains 'Clicked segmented_control #119: selected.' || ! snapshot_contains 'widget @w1/components-canvas#117 role=tab' || ! snapshot_contains 'value=0' || ! snapshot_contains 'widget @w1/components-canvas#119 role=tab' || ! snapshot_contains 'focused=true' || ! snapshot_contains 'value=1'; then echo "segmented control automation click did not update retained selection state" >&2; exit 1; fi + \\case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "segmented control automation click did not present an incremental GPU packet without interaction-time uploads" >&2; exit 1 ;; esac + \\# The catalog's project menu is an ACTIONS menu (no sibling declares a + \\# committed row), so automation select mints NO selection: value + \\# stays 0. Automation focus follows the pointer contract (rows take + \\# focus QUIETLY, no visible affordance), so nothing repaints and no + \\# frame is requested — the republished snapshot alone carries the + \\# focus move. A picker menu would mint value=1 and repaint; the + \\# select specimen's committed-row tests cover that register. + \\"$cli" automate widget-action components-canvas 142 select >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ case "$(printf '%s\\n' "$snapshot" | grep -F 'components-canvas#142 role=menuitem' | head -1)" in *'focused=true'*'value=0'*) break ;; esac + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\menu_item_line="$(printf '%s\\n' "$snapshot" | grep -F 'components-canvas#142 role=menuitem' | head -1)" + \\case "$menu_item_line" in *'focused=true'*'value=0'*) ;; *) echo "menu item automation select did not move quiet focus without minting a selection" >&2; exit 1 ;; esac + \\"$cli" automate widget-action components-canvas 113 toggle >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ if snapshot_contains 'Keyed checkbox #113: off.' && snapshot_contains 'widget @w1/components-canvas#113 role=checkbox' && snapshot_contains 'value=0'; then break; fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if ! snapshot_contains 'Keyed checkbox #113: off.' || ! snapshot_contains 'widget @w1/components-canvas#113 role=checkbox' || ! snapshot_contains 'value=0'; then echo "checkbox automation toggle did not update the retained widget snapshot" >&2; exit 1; fi + \\gpu_frame_before="$(gpu_frame_from_snapshot)" + \\case "$gpu_frame_before" in ''|*[!0-9]*) gpu_frame_before=0 ;; esac + \\gpu_frame_after="$gpu_frame_before" + \\"$cli" automate widget-click components-canvas 113 >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ gpu_frame_after="$(gpu_frame_from_snapshot)" + \\ case "$gpu_frame_after" in ''|*[!0-9]*) gpu_frame_after=0 ;; esac + \\ if [ "$gpu_frame_after" -gt "$gpu_frame_before" ]; then + \\ if snapshot_contains 'Clicked checkbox #113: on.' && snapshot_contains 'widget @w1/components-canvas#113 role=checkbox' && snapshot_contains 'value=1'; then + \\ case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) break ;; esac + \\ fi + \\ fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if [ "$gpu_frame_after" -le "$gpu_frame_before" ]; then echo "checkbox automation click did not request a GPU frame" >&2; exit 1; fi + \\if ! snapshot_contains 'Clicked checkbox #113: on.' || ! snapshot_contains 'widget @w1/components-canvas#113 role=checkbox' || ! snapshot_contains 'value=1'; then echo "checkbox automation click did not route through pointer input" >&2; exit 1; fi + \\case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "checkbox automation click did not present an incremental GPU packet without interaction-time uploads" >&2; exit 1 ;; esac + \\"$cli" automate widget-action components-canvas 114 toggle >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ if snapshot_contains 'Keyed switch_control #114: off.' && snapshot_contains 'widget @w1/components-canvas#114 role=switch' && snapshot_contains 'value=0'; then break; fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if ! snapshot_contains 'Keyed switch_control #114: off.' || ! snapshot_contains 'widget @w1/components-canvas#114 role=switch' || ! snapshot_contains 'value=0'; then echo "switch automation toggle did not wake the idle app" >&2; exit 1; fi + \\gpu_frame_before="$(gpu_frame_from_snapshot)" + \\case "$gpu_frame_before" in ''|*[!0-9]*) gpu_frame_before=0 ;; esac + \\gpu_frame_after="$gpu_frame_before" + \\"$cli" automate widget-click components-canvas 114 >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ gpu_frame_after="$(gpu_frame_from_snapshot)" + \\ case "$gpu_frame_after" in ''|*[!0-9]*) gpu_frame_after=0 ;; esac + \\ if [ "$gpu_frame_after" -gt "$gpu_frame_before" ]; then + \\ if snapshot_contains 'Clicked switch_control #114: on.' && snapshot_contains 'widget @w1/components-canvas#114 role=switch' && snapshot_contains 'value=1'; then + \\ case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) break ;; esac + \\ fi + \\ fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if [ "$gpu_frame_after" -le "$gpu_frame_before" ]; then echo "switch automation click did not request a GPU frame" >&2; exit 1; fi + \\if ! snapshot_contains 'Clicked switch_control #114: on.' || ! snapshot_contains 'widget @w1/components-canvas#114 role=switch' || ! snapshot_contains 'value=1'; then echo "switch automation click did not route through pointer input" >&2; exit 1; fi + \\case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "switch automation click did not present an incremental GPU packet without interaction-time uploads" >&2; exit 1 ;; esac + \\gpu_frame_before="$(gpu_frame_from_snapshot)" + \\case "$gpu_frame_before" in ''|*[!0-9]*) gpu_frame_before=0 ;; esac + \\gpu_frame_after="$gpu_frame_before" + \\canvas_revision_before="$(canvas_revision_from_snapshot)" + \\case "$canvas_revision_before" in ''|*[!0-9]*) canvas_revision_before=0 ;; esac + \\canvas_revision_after="$canvas_revision_before" + \\"$cli" automate widget-action components-canvas 120 increment >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ gpu_frame_after="$(gpu_frame_from_snapshot)" + \\ case "$gpu_frame_after" in ''|*[!0-9]*) gpu_frame_after=0 ;; esac + \\ canvas_revision_after="$(canvas_revision_from_snapshot)" + \\ case "$canvas_revision_after" in ''|*[!0-9]*) canvas_revision_after=0 ;; esac + \\ if [ "$gpu_frame_after" -gt "$gpu_frame_before" ] || [ "$canvas_revision_after" -gt "$canvas_revision_before" ]; then + \\ if snapshot_contains 'Keyed list #120: offset 56.' && snapshot_contains 'widget @w1/components-canvas#120 role=list' && snapshot_contains 'scroll=[offset=56,viewport=56,content=168]'; then + \\ case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) break ;; esac + \\ fi + \\ fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if [ "$gpu_frame_after" -le "$gpu_frame_before" ] && [ "$canvas_revision_after" -le "$canvas_revision_before" ]; then echo "list automation increment did not update the retained GPU canvas" >&2; exit 1; fi + \\if ! snapshot_contains 'Keyed list #120: offset 56.' || ! snapshot_contains 'widget @w1/components-canvas#120 role=list' || ! snapshot_contains 'scroll=[offset=56,viewport=56,content=168]'; then echo "list automation increment did not update retained scroll semantics" >&2; exit 1; fi + \\case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "list automation increment did not present an incremental GPU packet without interaction-time uploads" >&2; exit 1 ;; esac + \\gpu_frame_before="$(gpu_frame_from_snapshot)" + \\case "$gpu_frame_before" in ''|*[!0-9]*) gpu_frame_before=0 ;; esac + \\gpu_frame_after="$gpu_frame_before" + \\canvas_revision_before="$(canvas_revision_from_snapshot)" + \\case "$canvas_revision_before" in ''|*[!0-9]*) canvas_revision_before=0 ;; esac + \\canvas_revision_after="$canvas_revision_before" + \\"$cli" automate widget-action components-canvas 150 increment >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ gpu_frame_after="$(gpu_frame_from_snapshot)" + \\ case "$gpu_frame_after" in ''|*[!0-9]*) gpu_frame_after=0 ;; esac + \\ canvas_revision_after="$(canvas_revision_from_snapshot)" + \\ case "$canvas_revision_after" in ''|*[!0-9]*) canvas_revision_after=0 ;; esac + \\ if [ "$gpu_frame_after" -gt "$gpu_frame_before" ] || [ "$canvas_revision_after" -gt "$canvas_revision_before" ]; then + \\ if snapshot_contains 'Keyed table #150: offset 56.' && snapshot_contains 'widget @w1/components-canvas#150 role=grid' && snapshot_contains 'scroll=[offset=56,viewport=28,content=140]'; then + \\ case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) break ;; esac + \\ fi + \\ fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if [ "$gpu_frame_after" -le "$gpu_frame_before" ] && [ "$canvas_revision_after" -le "$canvas_revision_before" ]; then echo "data grid automation increment did not update the retained GPU canvas" >&2; exit 1; fi + \\if ! snapshot_contains 'Keyed table #150: offset 56.' || ! snapshot_contains 'widget @w1/components-canvas#150 role=grid' || ! snapshot_contains 'scroll=[offset=56,viewport=28,content=140]'; then echo "data grid automation increment did not update retained scroll semantics" >&2; exit 1; fi + \\case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "data grid automation increment did not present an incremental GPU packet without interaction-time uploads" >&2; exit 1 ;; esac + \\gpu_frame_before="$(gpu_frame_from_snapshot)" + \\case "$gpu_frame_before" in ''|*[!0-9]*) gpu_frame_before=0 ;; esac + \\gpu_frame_after="$gpu_frame_before" + \\canvas_revision_before="$(canvas_revision_from_snapshot)" + \\case "$canvas_revision_before" in ''|*[!0-9]*) canvas_revision_before=0 ;; esac + \\canvas_revision_after="$canvas_revision_before" + \\"$cli" automate widget-wheel components-canvas 130 20 >/dev/null 2>&1 + \\attempts=0 + \\while [ "$attempts" -lt 50 ]; do + \\ snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)" + \\ gpu_frame_after="$(gpu_frame_from_snapshot)" + \\ case "$gpu_frame_after" in ''|*[!0-9]*) gpu_frame_after=0 ;; esac + \\ canvas_revision_after="$(canvas_revision_from_snapshot)" + \\ case "$canvas_revision_after" in ''|*[!0-9]*) canvas_revision_after=0 ;; esac + \\ if [ "$gpu_frame_after" -gt "$gpu_frame_before" ] || [ "$canvas_revision_after" -gt "$canvas_revision_before" ]; then + \\ case "$snapshot" in *'widget @w1/components-canvas#130 role=group'*'scroll=[offset=84,viewport=56,content=140]'*) + \\ case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) break ;; esac + \\ ;; + \\ esac + \\ fi + \\ attempts=$((attempts + 1)) + \\ sleep 0.1 + \\done + \\if [ "$gpu_frame_after" -le "$gpu_frame_before" ] && [ "$canvas_revision_after" -le "$canvas_revision_before" ]; then echo "scroll automation wheel did not update the retained GPU canvas" >&2; exit 1; fi + \\case "$snapshot" in *'widget @w1/components-canvas#130 role=group'*'scroll=[offset=84,viewport=56,content=140]'*) ;; *) echo "scroll automation wheel did not update retained scroll semantics" >&2; exit 1 ;; esac + \\case "$snapshot" in *'view @w1/components-canvas kind=gpu_surface'*'canvas_frame_full_repaint=false'*'canvas_frame_pipeline_uploads=0'*'canvas_frame_image_uploads=0'*'canvas_frame_glyph_uploads=0'*'canvas_frame_text_uploads=0'*'canvas_frame_gpu_packet_unsupported=0'*'canvas_frame_gpu_packet_representable=true'*) ;; *) echo "scroll automation wheel did not present an incremental GPU packet without interaction-time uploads" >&2; exit 1 ;; esac + \\input_timestamp="$(printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/components-canvas kind=gpu_surface.* gpu_input_timestamp_ns=\([0-9][0-9]*\).*/\1/p')" + \\case "$input_timestamp" in ''|*[!0-9]*) echo "components GPU input timestamp was missing after widget interaction" >&2; exit 1 ;; esac + \\if [ "$input_timestamp" -le 0 ]; then echo "components GPU input timestamp was not recorded" >&2; exit 1; fi + \\input_latency="$(printf '%s\n' "$snapshot" | sed -n 's/.*view @w1\/components-canvas kind=gpu_surface.* gpu_input_latency_ns=\([0-9][0-9]*\).*/\1/p')" + \\case "$input_latency" in ''|*[!0-9]*) echo "components GPU input latency was missing after widget interaction" >&2; exit 1 ;; esac + \\# gpu_input_latency now stamps at the RESPONDING present's completion + \\# (input to glass), which legitimately spans the paced render wait — + \\# up to a couple of display intervals while animations hold the paced + \\# loop. Assert an explicit input-to-glass bound (the perf harness + \\# budgets the same channel at 100 ms) instead of the one-interval + \\# budget flag the old completion-channel stamp happened to satisfy. + \\if [ "$input_latency" -le 0 ] || [ "$input_latency" -gt 100000000 ]; then echo "components GPU input-to-glass latency was implausible: $input_latency ns" >&2; exit 1; fi + \\echo "gpu-components smoke ok" + , + "sh", + }); + gpu_components_smoke_run.addFileArg(cli_exe.getEmittedBin()); + gpu_components_smoke_run.step.dependOn(&gpu_components_smoke_build.step); + gpu_components_smoke_run.step.dependOn(&cli_exe.step); + gpu_components_smoke_step.dependOn(&gpu_components_smoke_run.step); + + const webview_cef_smoke_step = b.step("test-webview-cef-smoke", "Run macOS Chromium WebView automation smoke test"); + const webview_cef_smoke_build = b.addSystemCommand(&.{ "zig", "build", "-Dplatform=macos", "-Dweb-engine=chromium", b.fmt("-Dcef-dir={s}", .{exampleCefDir(b, cef_dir)}), "-Dautomation=true", "-Djs-bridge=true" }); + webview_cef_smoke_build.setCwd(b.path("examples/webview")); + const webview_cef_smoke_run = b.addSystemCommand(&.{ + "sh", "-c", + \\set -eu + \\cd examples/webview + \\app="zig-out/bin/webview" + \\cli="$1" + \\case "$cli" in /*) ;; *) cli="../../$cli" ;; esac + \\request='{"id":"ping","command":"native.ping","payload":{"source":"cef-smoke"}}' + \\response_file=".zig-cache/native-sdk-automation/bridge-response.txt" + \\mkdir -p .zig-cache/native-sdk-automation + \\rm -f .zig-cache/native-sdk-automation/snapshot.txt .zig-cache/native-sdk-automation/windows.txt .zig-cache/native-sdk-automation/command*.txt "$response_file" + \\printf 'bridge %s\n' "$request" > .zig-cache/native-sdk-automation/command-1.txt + \\"$app" > .zig-cache/native-sdk-webview-cef-smoke.log 2>&1 & + \\pid=$! + \\trap 'status=$?; kill "$pid" >/dev/null 2>&1 || true; wait "$pid" >/dev/null 2>&1 || true; if [ "$status" -ne 0 ]; then echo "---- app log (.zig-cache/native-sdk-webview-cef-smoke.log) ----" >&2; cat .zig-cache/native-sdk-webview-cef-smoke.log >&2 2>/dev/null || true; fi' EXIT + \\snapshot="$("$cli" automate wait 2>&1)" + \\case "$snapshot" in *"ready=true"*) ;; *) echo "automation snapshot was not ready" >&2; exit 1 ;; esac + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && ! grep -q 'name="webview.load"' .zig-cache/native-sdk-webview-cef-smoke.log; do attempts=$((attempts + 1)); sleep 0.1; done + \\grep -q 'name="webview.load"' .zig-cache/native-sdk-webview-cef-smoke.log || { echo "main window never loaded its webview source (blank window)" >&2; exit 1; } + \\if grep -q 'name="dispatch.error"' .zig-cache/native-sdk-webview-cef-smoke.log; then echo "runtime recorded a dispatch error during startup" >&2; exit 1; fi + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*'pong from Zig'*) ;; *) echo "native.ping response was unexpected: $response" >&2; exit 1 ;; esac + \\rm -f "$response_file" + \\printf 'bridge %s\n' '{"id":"webview-create","command":"native-sdk.webview.create","payload":{"label":"smoke","url":"https://example.com","frame":{"x":24,"y":24,"width":320,"height":220}}}' > .zig-cache/native-sdk-automation/command-1.txt + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*) ;; *) echo "cef webview create did not succeed: $response" >&2; exit 1 ;; esac + \\rm -f "$response_file" + \\printf 'bridge %s\n' '{"id":"webview-resize","command":"native-sdk.webview.setFrame","payload":{"label":"smoke","frame":{"x":36,"y":36,"width":420,"height":260}}}' > .zig-cache/native-sdk-automation/command-1.txt + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*) ;; *) echo "cef webview resize did not succeed: $response" >&2; exit 1 ;; esac + \\rm -f "$response_file" + \\printf 'bridge %s\n' '{"id":"webview-navigate","command":"native-sdk.webview.navigate","payload":{"label":"smoke","url":"https://example.com/?smoke=1"}}' > .zig-cache/native-sdk-automation/command-1.txt + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*) ;; *) echo "cef webview navigate did not succeed: $response" >&2; exit 1 ;; esac + \\rm -f "$response_file" + \\printf 'bridge %s\n' '{"id":"webview-close","command":"native-sdk.webview.close","payload":{"label":"smoke"}}' > .zig-cache/native-sdk-automation/command-1.txt + \\attempts=0 + \\while [ "$attempts" -lt 50 ] && [ ! -s "$response_file" ]; do attempts=$((attempts + 1)); sleep 0.1; done + \\response="$(cat "$response_file" 2>/dev/null || true)" + \\case "$response" in *'"ok":true'*) ;; *) echo "cef webview close did not succeed: $response" >&2; exit 1 ;; esac + \\echo "cef webview smoke ok" + , + "sh", + }); + webview_cef_smoke_run.addFileArg(cli_exe.getEmittedBin()); + webview_cef_smoke_run.step.dependOn(&webview_cef_smoke_build.step); + webview_cef_smoke_run.step.dependOn(&cli_exe.step); + webview_cef_smoke_step.dependOn(&webview_cef_smoke_run.step); + + const dev_run = b.addSystemCommand(&.{ "zig", "build", "run", b.fmt("-Dplatform={s}", .{platform_arg}) }); + dev_run.setCwd(b.path("examples/webview")); + const dev_step = b.step("dev", "Run managed frontend dev server and native shell"); + dev_step.dependOn(&dev_run.step); + + const lib_step = b.step("lib", "Build native-sdk embeddable static library"); + lib_step.dependOn(&b.addInstallArtifact(embed_lib, .{}).step); + + const doctor_run = b.addRunArtifact(host_cli_exe); + doctor_run.addArg("doctor"); + const doctor_step = b.step("doctor", "Print native-sdk platform diagnostics"); + doctor_step.dependOn(&doctor_run.step); + + const validate_run = b.addRunArtifact(host_cli_exe); + validate_run.addArgs(&.{ "validate", "app.zon" }); + const validate_step = b.step("validate", "Validate app.zon"); + validate_step.dependOn(&validate_run.step); + + const bundle_run = b.addRunArtifact(host_cli_exe); + bundle_run.addArgs(&.{ "bundle-assets", "app.zon", "assets", "zig-out/assets" }); + const bundle_step = b.step("bundle-assets", "Bundle app assets"); + bundle_step.dependOn(&bundle_run.step); + + const package_run = b.addRunArtifact(host_cli_exe); + package_run.addArgs(&.{ + "package", + "--target", + @tagName(package_target), + "--output", + b.fmt("zig-out/package/native-sdk-{s}-{s}-{s}{s}", .{ package_version, @tagName(package_target), optimize_name, packageSuffix(package_target) }), + "--binary", + }); + package_run.addFileArg(embed_lib.getEmittedBin()); + package_run.addArgs(&.{ "--manifest", "app.zon", "--assets", "assets", "--optimize", optimize_name, "--signing", @tagName(signing_mode), "--web-engine", @tagName(web_engine), "--cef-dir", cef_dir }); + if (cef_auto_install) package_run.addArg("--cef-auto-install"); + package_run.step.dependOn(&embed_lib.step); + package_run.step.dependOn(&bundle_run.step); + const package_step = b.step("package", "Create local package artifact"); + package_step.dependOn(&package_run.step); + + const package_cef_run = b.addRunArtifact(host_cli_exe); + package_cef_run.addArgs(&.{ + "package", + "--target", + "macos", + "--output", + b.fmt("zig-out/package/native-sdk-cef-smoke-{s}.app", .{optimize_name}), + "--binary", + }); + package_cef_run.addFileArg(embed_lib.getEmittedBin()); + package_cef_run.addArgs(&.{ "--manifest", "app.zon", "--assets", "assets", "--optimize", optimize_name, "--web-engine", "chromium", "--cef-dir", cef_dir }); + if (cef_auto_install) package_cef_run.addArg("--cef-auto-install"); + package_cef_run.step.dependOn(&embed_lib.step); + package_cef_run.step.dependOn(&bundle_run.step); + + const package_cef_check = b.addSystemCommand(&.{ + "sh", "-c", + b.fmt( + \\set -e + \\app="zig-out/package/native-sdk-cef-smoke-{s}.app" + \\test -d "$app/Contents/Frameworks/Chromium Embedded Framework.framework" + \\test -f "$app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/icudtl.dat" + \\test -f "$app/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libGLESv2.dylib" + \\test -f "$app/Contents/Resources/package-manifest.zon" + \\echo "cef package layout ok" + , .{optimize_name}), + }); + package_cef_check.step.dependOn(&package_cef_run.step); + const package_cef_smoke_step = b.step("test-package-cef-layout", "Verify macOS Chromium package layout"); + package_cef_smoke_step.dependOn(&package_cef_check.step); + + // Signed-package seal pin: package an ad-hoc signed bundle and prove + // the signature survives packaging intact with codesign's own strict + // verifier. This is the regression gate for the ordering bug where a + // file written into Contents/Resources AFTER signing invalidated the + // resource seal ("file added"), turning every quarantined install + // into Gatekeeper's "damaged — move to Trash" dialog. The bundle + // carries the CLI as its executable (packaging only needs a real + // Mach-O to sign); the check skips loudly on hosts without codesign + // (any non-macOS machine) instead of pretending to have verified. + // Signing that claims to sign now fails the package on hosts that + // cannot run codesign (that IS the fix this step gates), so only a + // macOS host requests adhoc here; elsewhere the package stays + // unsigned and the check script below skips loudly as before. + const package_signing_mode: []const u8 = if (b.graph.host.result.os.tag == .macos) "adhoc" else "none"; + const package_signing_run = b.addRunArtifact(host_cli_exe); + package_signing_run.addArgs(&.{ "package", "--target", "macos", "--output", "zig-out/package/native-sdk-signing-verify.app", "--binary" }); + package_signing_run.addFileArg(host_cli_exe.getEmittedBin()); + package_signing_run.addArgs(&.{ "--manifest", "app.zon", "--assets", "assets", "--optimize", optimize_name, "--signing", package_signing_mode }); + package_signing_run.has_side_effects = true; + const package_signing_check = b.addSystemCommand(&.{ + "sh", "-c", + \\set -e + \\app="zig-out/package/native-sdk-signing-verify.app" + \\if ! command -v codesign >/dev/null 2>&1; then + \\ echo "codesign unavailable on this host; skipping signed-package verification" + \\ exit 0 + \\fi + \\codesign --verify --strict --deep "$app" + \\grep -q "ad-hoc signed" "$app/Contents/Resources/signing-plan.txt" + \\echo "signed package verify ok" + , + }); + package_signing_check.step.dependOn(&package_signing_run.step); + const package_signing_step = b.step("test-package-signing", "Verify an ad-hoc signed macOS package passes codesign --verify --strict"); + package_signing_step.dependOn(&package_signing_check.step); + + const package_windows_run = b.addRunArtifact(host_cli_exe); + package_windows_run.addArgs(&.{ "package-windows", "--output", b.fmt("zig-out/package/native-sdk-{s}-windows-Debug", .{package_version}), "--manifest", "app.zon", "--assets", "assets" }); + const package_windows_step = b.step("package-windows", "Create local Windows artifact directory"); + package_windows_step.dependOn(&package_windows_run.step); + + const package_linux_run = b.addRunArtifact(host_cli_exe); + package_linux_run.addArgs(&.{ "package-linux", "--output", b.fmt("zig-out/package/native-sdk-{s}-linux-Debug", .{package_version}), "--manifest", "app.zon", "--assets", "assets" }); + const package_linux_step = b.step("package-linux", "Create local Linux artifact directory"); + package_linux_step.dependOn(&package_linux_run.step); + + const package_ios_run = b.addRunArtifact(host_cli_exe); + package_ios_run.addArgs(&.{ "package-ios", "--output", b.fmt("zig-out/mobile/native-sdk-{s}-ios-Debug", .{package_version}), "--manifest", "app.zon", "--assets", "assets" }); + const package_ios_step = b.step("package-ios", "Create local iOS host skeleton"); + package_ios_step.dependOn(&package_ios_run.step); + + const package_android_run = b.addRunArtifact(host_cli_exe); + package_android_run.addArgs(&.{ "package-android", "--output", b.fmt("zig-out/mobile/native-sdk-{s}-android-Debug", .{package_version}), "--manifest", "app.zon", "--assets", "assets" }); + const package_android_step = b.step("package-android", "Create local Android host project"); + package_android_step.dependOn(&package_android_run.step); + + // Default app icon: rendered from vector geometry (tools/ + // generate_app_icon.zig) through the SDK's own path rasterizer, so + // the checked-in .icns/.ico/.png/.svg all regenerate from source. + // The built-in app-icon pipeline assembles and round-trip-validates + // the containers itself (no external tools, any host OS), and the + // CLI's embedded scaffold copies are kept in sync. + const generate_icon_step = b.step("generate-icon", "Regenerate the default app icon (.icns/.ico/.png/.svg) from vector source"); + const generate_icon_mod = module(b, target, optimize, "tools/generate_app_icon.zig"); + generate_icon_mod.addImport("native_sdk", desktop_mod); + const generate_icon_exe = b.addExecutable(.{ + .name = "generate-app-icon", + .root_module = generate_icon_mod, + }); + const generate_icon_run = b.addRunArtifact(generate_icon_exe); + generate_icon_run.addArgs(&.{ + "assets/icon.icns", + "assets/icon.png", + "assets/icon.ico", + "assets/icon.svg", + "src/tooling/default_icon.icns", + "src/tooling/default_icon.png", + "zig-out/icon-full-bleed.png", + // The one committed full-bleed copy: the notes example's raw + // one-image icon source (it demos the packaging mask + inset), + // regenerated here so it can never drift from the default. + "examples/notes/assets/icon.png", + }); + generate_icon_run.has_side_effects = true; + generate_icon_step.dependOn(&generate_icon_run.step); + + const notarize_run = b.addRunArtifact(host_cli_exe); + notarize_run.addArgs(&.{ + "package", + "--target", + "macos", + "--output", + b.fmt("zig-out/package/native-sdk-{s}-macos-{s}.app", .{ package_version, optimize_name }), + "--binary", + }); + notarize_run.addFileArg(embed_lib.getEmittedBin()); + notarize_run.addArgs(&.{ "--manifest", "app.zon", "--assets", "assets", "--optimize", optimize_name, "--signing", "identity", "--web-engine", @tagName(web_engine), "--cef-dir", cef_dir }); + if (cef_auto_install) notarize_run.addArg("--cef-auto-install"); + notarize_run.step.dependOn(&embed_lib.step); + notarize_run.step.dependOn(&bundle_run.step); + const notarize_step = b.step("notarize", "Package, sign with identity, and notarize for macOS distribution"); + notarize_step.dependOn(¬arize_run.step); + + const dmg_script = b.addSystemCommand(&.{ + "sh", "-c", + b.fmt( + \\APP="zig-out/package/native-sdk-{s}-macos-{s}.app" + \\DMG="zig-out/package/native-sdk-{s}-macos-{s}.dmg" + \\test -d "$APP" || {{ echo "run 'zig build package' first" >&2; exit 1; }} + \\hdiutil create -volname "native-sdk" -srcfolder "$APP" -ov -format UDZO "$DMG" + \\echo "created $DMG" + , .{ package_version, optimize_name, package_version, optimize_name }), + }); + dmg_script.step.dependOn(&package_run.step); + const dmg_step = b.step("dmg", "Create macOS .dmg disk image from the packaged .app"); + dmg_step.dependOn(&dmg_script.step); + + const cef_bundle_script = b.addSystemCommand(&.{ + "sh", "-c", + b.fmt( + \\set -e + \\rm -rf "zig-out/Frameworks/Chromium Embedded Framework.framework" "zig-out/bin/Frameworks/Chromium Embedded Framework.framework" ".zig-cache/o/Frameworks/Chromium Embedded Framework.framework" + \\mkdir -p "zig-out/Frameworks" "zig-out/bin/Frameworks" ".zig-cache/o/Frameworks" + \\cp -R "{s}/Release/Chromium Embedded Framework.framework" "zig-out/Frameworks/" + \\cp -R "{s}/Release/Chromium Embedded Framework.framework" "zig-out/bin/Frameworks/" + \\cp -R "{s}/Release/Chromium Embedded Framework.framework" ".zig-cache/o/Frameworks/" + \\if [ -d "{s}/Resources" ]; then + \\ mkdir -p "zig-out/bin/Resources/cef" + \\ cp -R "{s}/Resources/"* "zig-out/bin/Resources/cef/" + \\fi + \\echo "CEF framework copied for local dev runs" + , .{ cef_dir, cef_dir, cef_dir, cef_dir, cef_dir }), + }); + const cef_bundle_step = b.step("cef-bundle", "Copy CEF framework and resources into zig-out/bin/ for local dev runs"); + if (cef_auto_install) { + const cef_bundle_auto = b.addRunArtifact(host_cli_exe); + cef_bundle_auto.addArgs(&.{ "cef", "install", "--dir", cef_dir }); + cef_bundle_script.step.dependOn(&cef_bundle_auto.step); + } + cef_bundle_step.dependOn(&cef_bundle_script.step); +} + +fn module(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, path: []const u8) *std.Build.Module { + return b.createModule(.{ + .root_source_file = b.path(path), + .target = target, + .optimize = optimize, + }); +} + +/// The short commit hash of the framework checkout the CLI is built +/// from, for `native version` staleness checks. "unknown" when +/// git is unavailable (e.g. building from a package tarball). +fn cliBuildCommit(b: *std.Build) []const u8 { + var code: u8 = undefined; + const output = b.runAllowFail(&.{ "git", "rev-parse", "--short", "HEAD" }, &code, .ignore) catch return "unknown"; + const trimmed = std.mem.trim(u8, output, " \n\r\t"); + if (trimmed.len == 0) return "unknown"; + return trimmed; +} + +fn testArtifact(b: *std.Build, mod: *std.Build.Module) *std.Build.Step.Compile { + return filteredTestArtifact(b, mod, "test", &.{}); +} + +/// The two transpiled-core end-to-end test binaries: the host/markup +/// fixture suite (tests/ts-core) and the soundboard-ts example suite — +/// the launch-gate port driven as a REAL app (its committed core, its +/// shipping markup). Each runs the @native-sdk/core transpiler (node) +/// over the TS core at build time, pairs the emitted core with its rt +/// kernel in one generated module, and compiles the Zig test root +/// against it plus the framework. Returns null — the suites are +/// skipped, not failed — when node or the transpiler package's +/// installed dependency (`npm ci` in packages/core) is missing. +const TsCoreE2eArtifacts = struct { + host: *std.Build.Step.Compile, + soundboard: *std.Build.Step.Compile, + system_monitor: *std.Build.Step.Compile, + /// The stock-IDE contract: a fresh scaffold (and the committed TS + /// example ports) typecheck under the REAL tsc with zero injected + /// paths, and builds keep working with node_modules deleted. + scaffold_ide: *std.Build.Step.Compile, + ai_chat: *std.Build.Step.Compile, +}; + +fn tsCoreE2eArtifact( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + desktop_mod: *std.Build.Module, + tooling_mod: *std.Build.Module, +) ?TsCoreE2eArtifacts { + const node = b.findProgram(&.{"node"}, &.{}) catch return null; + b.build_root.handle.access( + b.graph.io, + "packages/core/node_modules/@typescript/typescript6", + .{}, + ) catch return null; + + // Each fixture stages its own copy of rt.zig, so each emitted core + // owns a distinct rt kernel instance — the process contract the + // coexistence e2e test pins (two live cores, no shared arenas). + const fixture_mod = tsCoreFixtureModule(b, target, optimize, node, "tests/ts-core/fixture.ts"); + const markup_fixture_mod = tsCoreFixtureModule(b, target, optimize, node, "tests/ts-core/markup_fixture.ts"); + + const e2e_mod = module(b, target, optimize, "tests/ts-core/host_e2e_tests.zig"); + e2e_mod.addImport("native_sdk", desktop_mod); + e2e_mod.addImport("ts_core_fixture", fixture_mod); + e2e_mod.addImport("ts_markup_fixture", markup_fixture_mod); + + // The soundboard-ts example's core and markup, tested as one app: + // the test root stages beside a copy of the example's app.native so + // the compiled markup engine builds the SHIPPING view over the + // emitted model. + const soundboard_core_mod = tsCoreFixtureModule(b, target, optimize, node, "examples/soundboard-ts/src/core.ts"); + const soundboard_stage = b.addWriteFiles(); + const soundboard_root = soundboard_stage.addCopyFile(b.path("tests/ts-core/soundboard_e2e_tests.zig"), "soundboard_e2e_tests.zig"); + _ = soundboard_stage.addCopyFile(b.path("examples/soundboard-ts/src/app.native"), "app.native"); + const soundboard_mod = b.createModule(.{ + .root_source_file = soundboard_root, + .target = target, + .optimize = optimize, + }); + soundboard_mod.addImport("native_sdk", desktop_mod); + soundboard_mod.addImport("ts_soundboard_core", soundboard_core_mod); + + // The system-monitor-ts example's core and markup, tested the same + // way — plus the ORIGINAL Zig example's committed sampler captures, + // staged as fixtures so both ports parse the same recorded truth. + const monitor_core_mod = tsCoreFixtureModule(b, target, optimize, node, "examples/system-monitor-ts/src/core.ts"); + const monitor_stage = b.addWriteFiles(); + const monitor_root = monitor_stage.addCopyFile(b.path("tests/ts-core/system_monitor_e2e_tests.zig"), "system_monitor_e2e_tests.zig"); + _ = monitor_stage.addCopyFile(b.path("examples/system-monitor-ts/src/app.native"), "app.native"); + _ = monitor_stage.addCopyFile(b.path("examples/system-monitor/src/fixtures/sysctl.txt"), "fixtures/sysctl.txt"); + _ = monitor_stage.addCopyFile(b.path("examples/system-monitor/src/fixtures/ps.txt"), "fixtures/ps.txt"); + _ = monitor_stage.addCopyFile(b.path("examples/system-monitor/src/fixtures/vm_stat.txt"), "fixtures/vm_stat.txt"); + _ = monitor_stage.addCopyFile(b.path("examples/system-monitor/src/fixtures/ps-edge.txt"), "fixtures/ps-edge.txt"); + const monitor_mod = b.createModule(.{ + .root_source_file = monitor_root, + .target = target, + .optimize = optimize, + }); + monitor_mod.addImport("native_sdk", desktop_mod); + monitor_mod.addImport("ts_system_monitor_core", monitor_core_mod); + + // The stock-IDE suite runs the CLI's scaffold + editor-package plumbing + // (the tooling module) and shells out to node for the real tsc. + const scaffold_ide_mod = module(b, target, optimize, "tests/ts-core/scaffold_ide_e2e_tests.zig"); + scaffold_ide_mod.addImport("tooling", tooling_mod); + + // The ai-chat-ts example's core and markup, tested the same way: + // the chat client for an OpenAI-compatible endpoint, driven through + // the fake fetch feed (no network) with its shipping markup. + const ai_chat_core_mod = tsCoreFixtureModule(b, target, optimize, node, "examples/ai-chat-ts/src/core.ts"); + const ai_chat_stage = b.addWriteFiles(); + const ai_chat_root = ai_chat_stage.addCopyFile(b.path("tests/ts-core/ai_chat_e2e_tests.zig"), "ai_chat_e2e_tests.zig"); + _ = ai_chat_stage.addCopyFile(b.path("examples/ai-chat-ts/src/app.native"), "app.native"); + const ai_chat_mod = b.createModule(.{ + .root_source_file = ai_chat_root, + .target = target, + .optimize = optimize, + }); + ai_chat_mod.addImport("native_sdk", desktop_mod); + ai_chat_mod.addImport("ts_ai_chat_core", ai_chat_core_mod); + + return .{ + .host = filteredTestArtifact(b, e2e_mod, "ts-core-e2e-tests", &.{}), + .soundboard = filteredTestArtifact(b, soundboard_mod, "ts-soundboard-e2e-tests", &.{}), + .system_monitor = filteredTestArtifact(b, monitor_mod, "ts-system-monitor-e2e-tests", &.{}), + .scaffold_ide = filteredTestArtifact(b, scaffold_ide_mod, "ts-scaffold-ide-e2e-tests", &.{}), + .ai_chat = filteredTestArtifact(b, ai_chat_mod, "ts-ai-chat-e2e-tests", &.{}), + }; +} + +/// Transpile one TS fixture core at build time and pair the emitted +/// Zig with its rt kernel in one generated module. +fn tsCoreFixtureModule( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + node: []const u8, + fixture_path: []const u8, +) *std.Build.Module { + const transpile = b.addSystemCommand(&.{node}); + transpile.addFileArg(b.path("packages/core/src/cli.ts")); + transpile.addFileArg(b.path(fixture_path)); + transpile.addArg("-o"); + const emitted_core = transpile.addOutputFileArg("core.zig"); + // The transpiler reads its own sources, the SDK modules, and the + // core's WHOLE import graph at run time; declare them all so edits + // re-emit the fixture. The graph is declared as every sibling .ts of + // the entry (a superset of the reachable imports: over-approximation + // only re-runs the transpile, never misses a stale input). + tsCoreAddDirInputs(b, transpile, "packages/core/sdk"); + tsCoreAddDirInputs(b, transpile, std.fs.path.dirname(fixture_path) orelse "."); + const transpiler_sources = [_][]const u8{ + "checker.ts", "cli.ts", "diagnostics.ts", "emitter.ts", "infer.ts", "modules.ts", "transpile.ts", "typed_ast.ts", "types.ts", + }; + for (transpiler_sources) |source| { + transpile.addFileInput(b.path(b.fmt("packages/core/src/{s}", .{source}))); + } + + // The emitted core imports "rt.zig" relatively: stage both files + // into one generated directory to root the fixture module there. + const staged = b.addWriteFiles(); + const core_root = staged.addCopyFile(emitted_core, "core.zig"); + _ = staged.addCopyFile(b.path("packages/core/rt/rt.zig"), "rt.zig"); + return b.createModule(.{ + .root_source_file = core_root, + .target = target, + .optimize = optimize, + }); +} + +/// Declare every .ts file in `dir_path` (relative to the build root) as a +/// file input of the transpile step — the multi-file staleness set. +fn tsCoreAddDirInputs(b: *std.Build, transpile: *std.Build.Step.Run, dir_path: []const u8) void { + var dir = b.build_root.handle.openDir(b.graph.io, dir_path, .{ .iterate = true }) catch return; + defer dir.close(b.graph.io); + var it = dir.iterate(); + while (it.next(b.graph.io) catch null) |entry| { + if (entry.kind != .file) continue; + if (!std.mem.endsWith(u8, entry.name, ".ts")) continue; + transpile.addFileInput(b.path(b.fmt("{s}/{s}", .{ dir_path, entry.name }))); + } +} + +fn filteredTestArtifact(b: *std.Build, mod: *std.Build.Module, name: []const u8, filters: []const []const u8) *std.Build.Step.Compile { + // use_llvm: Zig 0.16.0's self-hosted x86_64 backend miscompiles the + // SysV C ABI for f32-heavy signatures (native_sdk_app_viewport); see + // useLlvmWorkaround in build/app.zig for the full story and repro. + const use_llvm = if (mod.resolved_target) |target| @import("build/app.zig").useLlvmWorkaround(target) else null; + return b.addTest(.{ .name = name, .root_module = mod, .filters = filters, .use_llvm = use_llvm }); +} + +/// One slice of the framework test suite, selected by test-name filters. +/// The framework module compiles into a single test binary whose ~580 +/// tests run serially in one process, which made that binary the longest +/// step in `zig build test` by a wide margin — the rest of the suite is +/// dozens of binaries that each finish in well under a second. The +/// aggregate `test` step therefore runs the framework module as one +/// filtered binary per family below, so the build runner executes the +/// families concurrently and the suite finishes with the slowest family +/// instead of the sum of all of them. `zig build test-desktop` still runs +/// the unfiltered binary when a single process is easier to debug, and +/// each family has its own `test-desktop-` step. +const DesktopTestShard = struct { + /// Suffix for the artifact name and the `test-desktop-` step. + name: []const u8, + description: []const u8, + /// A test file's namespace is routed to the first shard in + /// `desktop_test_shard_specs` with a matching prefix, so earlier + /// entries carve their family out of the later, broader ones and + /// the final empty prefix collects every namespace left over. + prefixes: []const []const u8, +}; + +const desktop_test_shard_specs = [_]DesktopTestShard{ + .{ + .name = "canvas-widget", + .description = "Run framework canvas widget tests", + .prefixes = &.{"runtime.canvas_widget"}, + }, + .{ + .name = "canvas-frame", + .description = "Run framework canvas frame, budget, image, and font tests", + .prefixes = &.{"runtime.canvas"}, + }, + .{ + .name = "ui-shell", + .description = "Run framework UI app and shell layout tests", + .prefixes = &.{ "runtime.ui_app", "runtime.shell" }, + }, + .{ + .name = "runtime-core", + .description = "Run framework runtime core, effects, session, and bridge tests", + .prefixes = &.{"runtime."}, + }, + .{ + .name = "platform", + .description = "Run framework platform, automation, embed, and remaining tests", + .prefixes = &.{""}, + }, +}; + +/// A framework source file that declares top-level tests, described by +/// the dotted namespace the test runner uses in fully-qualified test +/// names: tests in src/runtime/effects_tests.zig are named +/// "runtime.effects_tests.test.". +const DesktopTestFile = struct { + namespace: []const u8, + /// Names of the file's `test ""` declarations, in source order. + /// Unnamed `test { ... }` blocks are not listed: the test runner + /// exempts them from filtering, so they run in every shard. That is + /// deliberate — those blocks are the aggregators whose references + /// make the compiler discover the named tests in the first place, + /// and their bodies are empty at run time. + named_tests: []const []const u8, +}; + +/// Test binaries for the framework module, one per shard spec. Filters +/// are derived from the source tree at configure time: every framework +/// file with a top-level `test` declaration contributes its namespace as +/// a filter ("runtime.effects_tests.test") to exactly one shard, so a +/// new test file is picked up — and routed to a shard by prefix — the +/// moment it exists, and a file can never silently fall out of +/// `zig build test` because a hand-kept list went stale. +fn desktopTestShardArtifacts(b: *std.Build, mod: *std.Build.Module) [desktop_test_shard_specs.len]*std.Build.Step.Compile { + const files = desktopTestFiles(b); + var filters: [desktop_test_shard_specs.len]std.ArrayList([]const u8) = @splat(.empty); + for (files) |file| { + const shard = desktopTestShardIndex(file.namespace); + if (std.mem.eql(u8, file.namespace, "root")) { + // Filters match anywhere in a fully-qualified name, and the + // module root's namespace ("root") is a dotted suffix of + // every other root.zig namespace ("runtime.root", + // "platform.root", ...), so a bare "root.test" filter would + // pull those files' tests into this shard as well. Filter + // the module root by exact test name instead. + for (file.named_tests) |test_name| { + filters[shard].append(b.allocator, b.fmt("root.test.{s}", .{test_name})) catch @panic("OOM"); + } + continue; + } + filters[shard].append(b.allocator, b.fmt("{s}.test", .{file.namespace})) catch @panic("OOM"); + // The same suffix hazard across two shards would run the longer + // file's tests twice per `zig build test`. Refuse the layout up + // front so the collision is resolved when the file is added, not + // when a duplicated test starts flaking. + for (files) |other| { + if (desktopTestShardIndex(other.namespace) == shard) continue; + if (std.mem.endsWith(u8, other.namespace, b.fmt(".{s}", .{file.namespace}))) { + std.debug.panic( + "framework test shards: namespace {s} is a dotted suffix of {s} in another shard; their name filters would overlap. Adjust desktop_test_shard_specs so both land in one shard.", + .{ file.namespace, other.namespace }, + ); + } + } + } + var artifacts: [desktop_test_shard_specs.len]*std.Build.Step.Compile = undefined; + for (&artifacts, desktop_test_shard_specs, filters) |*artifact, spec, shard_filters| { + artifact.* = filteredTestArtifact(b, mod, b.fmt("desktop-{s}-tests", .{spec.name}), shard_filters.items); + } + return artifacts; +} + +fn desktopTestShardIndex(namespace: []const u8) usize { + for (desktop_test_shard_specs, 0..) |spec, index| { + for (spec.prefixes) |prefix| { + if (std.mem.startsWith(u8, namespace, prefix)) return index; + } + } + unreachable; // the final shard's empty prefix matches every namespace +} + +/// Scans the framework source tree for files declaring top-level tests, +/// sorted by namespace so the generated filters — and therefore the +/// shard binaries' cache manifests — do not churn with directory +/// iteration order. src/primitives and src/tooling are separate modules +/// with their own test binaries; everything else under src/ belongs to +/// the framework module rooted at src/root.zig. +fn desktopTestFiles(b: *std.Build) []const DesktopTestFile { + const gpa = b.allocator; + const io = b.graph.io; + var files: std.ArrayList(DesktopTestFile) = .empty; + var src_dir = b.build_root.handle.openDir(io, "src", .{ .iterate = true }) catch |err| + std.debug.panic("framework test shards: unable to open src/: {s}", .{@errorName(err)}); + defer src_dir.close(io); + var walker = src_dir.walk(gpa) catch @panic("OOM"); + defer walker.deinit(); + while (walker.next(io) catch |err| + std.debug.panic("framework test shards: unable to walk src/: {s}", .{@errorName(err)})) |entry| + { + if (entry.kind == .directory) { + if (entry.depth() == 1 and + (std.mem.eql(u8, entry.basename, "primitives") or std.mem.eql(u8, entry.basename, "tooling"))) + { + walker.leave(io); + } + continue; + } + if (entry.kind != .file or !std.mem.endsWith(u8, entry.basename, ".zig")) continue; + const source = entry.dir.readFileAlloc(io, entry.basename, gpa, .limited(16 * 1024 * 1024)) catch |err| + std.debug.panic("framework test shards: unable to read src/{s}: {s}", .{ entry.path, @errorName(err) }); + var named_tests: std.ArrayList([]const u8) = .empty; + var has_tests = false; + var lines = std.mem.splitScalar(u8, source, '\n'); + while (lines.next()) |line| { + // Only column-zero declarations: a test nested inside a + // container would carry that container's namespace, which + // these filters would not match. + if (!std.mem.startsWith(u8, line, "test ")) continue; + has_tests = true; + const rest = line["test ".len..]; + if (rest.len > 0 and rest[0] == '"') { + if (std.mem.indexOfScalar(u8, rest[1..], '"')) |end| { + named_tests.append(gpa, rest[1 .. 1 + end]) catch @panic("OOM"); + } + } + } + if (!has_tests) continue; + const namespace = gpa.dupe(u8, entry.path[0 .. entry.path.len - ".zig".len]) catch @panic("OOM"); + std.mem.replaceScalar(u8, namespace, std.fs.path.sep, '.'); + files.append(gpa, .{ .namespace = namespace, .named_tests = named_tests.items }) catch @panic("OOM"); + } + const sorted = files.items; + std.mem.sort(DesktopTestFile, sorted, {}, struct { + fn lessThan(_: void, lhs: DesktopTestFile, rhs: DesktopTestFile) bool { + return std.mem.lessThan(u8, lhs.namespace, rhs.namespace); + } + }.lessThan); + return sorted; +} + +fn addTestStep(b: *std.Build, name: []const u8, description: []const u8, artifact: *std.Build.Step.Compile) void { + const step = b.step(name, description); + step.dependOn(&b.addRunArtifact(artifact).step); +} + +/// How an example's build is driven. `managed` examples carry only +/// app.zon + src (+ assets): the `native` CLI synthesizes their build +/// graph, so their suites run through the CLI verbs — exactly what a user +/// gets from `native init`. `owned` examples keep a build.zig of their own +/// (extra steps or flags the generated graph does not provide) and are +/// driven through plain in-dir `zig build`. +const ExampleBuildShape = enum { managed, owned }; + +fn addExampleTestStep(b: *std.Build, cli_exe: *std.Build.Step.Compile, group: *std.Build.Step, name: []const u8, description: []const u8, example_path: []const u8, shape: ExampleBuildShape) void { + const run = switch (shape) { + .owned => b.addSystemCommand(&.{ "zig", "build", "test", "-Dplatform=null" }), + .managed => managedExampleRun(b, cli_exe, &.{ "test", "-Dplatform=null" }), + }; + run.setCwd(b.path(example_path)); + // Every example suite must actually run every time: the child build owns + // its own caching, and this outer step's argv never changes when example + // or framework sources do, so letting the step cache would skip real + // tests. But a side-effect run that also inherits stdio holds the build + // runner's global stderr lock for the child's entire lifetime, which + // executes the example suites strictly one at a time. Capturing both + // streams keeps the always-run semantics while releasing that lock, so + // independent examples run concurrently (bounded by the runner's job + // pool, one worker per CPU — cold child builds peak well under 1 GB + // each, so a machine-wide pool of them fits in memory). On failure the + // captured stderr is reported under this step's name, so a failing + // example still names itself. + run.has_side_effects = true; + _ = run.captureStdOut(.{}); + _ = run.captureStdErr(.{}); + // Concurrent children all spawn the same binary, so carry the step name + // into the run: a failure line then reads "test-example- failure" + // instead of nineteen indistinguishable "run exe native" entries. + run.setName(name); + const step = b.step(name, description); + step.dependOn(&run.step); + group.dependOn(&run.step); +} + +/// Run a `native` CLI verb (argv tail) against a managed example. The CLI +/// artifact runs from the build cache, where its executable location does +/// not reveal the framework checkout, so NATIVE_SDK_PATH pins the generated +/// graph's framework dependency to this repository. +fn managedExampleRun(b: *std.Build, cli_exe: *std.Build.Step.Compile, argv_tail: []const []const u8) *std.Build.Step.Run { + const run = b.addRunArtifact(cli_exe); + run.addArgs(argv_tail); + run.setEnvironmentVariable("NATIVE_SDK_PATH", b.pathFromRoot(".")); + return run; +} + +fn addLayoutCheckStep(b: *std.Build, group: *std.Build.Step, name: []const u8, description: []const u8, paths: []const []const u8) void { + const step = b.step(name, description); + for (paths) |path| { + const check = b.addSystemCommand(&.{ "test", "-f", path }); + step.dependOn(&check.step); + group.dependOn(&check.step); + } +} + +const FileContainsCheck = struct { + path: []const u8, + pattern: []const u8, +}; + +fn addFileContainsCheckStep(b: *std.Build, checker: *std.Build.Step.Compile, group: *std.Build.Step, name: []const u8, description: []const u8, checks: []const FileContainsCheck) void { + const step = b.step(name, description); + for (checks) |check_value| { + const check = b.addRunArtifact(checker); + // The checked paths are relative to this build script. Run steps + // otherwise inherit the invoking process's cwd, and `zig build test` + // is also invoked from zero-config app directories that resolve up + // to this build.zig (scaffolded workspaces, examples) — from there + // the relative paths would point into the app, not the repo. + check.setCwd(b.path(".")); + check.addArg(check_value.path); + check.addArg(check_value.pattern); + step.dependOn(&check.step); + group.dependOn(&check.step); + } +} + +fn packageSuffix(target: PackageTarget) []const u8 { + return switch (target) { + .macos => ".app", + .windows, .linux, .ios, .android => "", + }; +} + +fn packageVersion(b: *std.Build) []const u8 { + var file = std.Io.Dir.cwd().openFile(b.graph.io, "build.zig.zon", .{}) catch return "0.1.0"; + defer file.close(b.graph.io); + var buffer: [4096]u8 = undefined; + const len = file.readPositionalAll(b.graph.io, &buffer, 0) catch return "0.1.0"; + const bytes = buffer[0..len]; + const marker = ".version = \""; + const start = std.mem.indexOf(u8, bytes, marker) orelse return "0.1.0"; + const value_start = start + marker.len; + const value_end = std.mem.indexOfScalarPos(u8, bytes, value_start, '"') orelse return "0.1.0"; + return b.allocator.dupe(u8, bytes[value_start..value_end]) catch return "0.1.0"; +} + +fn defaultCefDir(platform: PlatformOption, configured: []const u8) []const u8 { + if (!std.mem.eql(u8, configured, web_engine_tool.default_cef_dir)) return configured; + return switch (platform) { + .linux => "third_party/cef/linux", + .windows => "third_party/cef/windows", + else => configured, + }; +} + +/// CEF dir for a sub-build whose cwd is an example directory (two levels +/// below the repo root). Relative paths are resolved by the example's +/// build against its own root, so the repo-root-relative default must be +/// rebased; absolute overrides pass through, but the example build.zigs +/// reject them (b.path panics), so callers should prefer relative paths. +fn exampleCefDir(b: *std.Build, cef_dir: []const u8) []const u8 { + if (std.fs.path.isAbsolute(cef_dir)) return cef_dir; + return b.fmt("../../{s}", .{cef_dir}); +} + +fn webEngineFromBuildOption(option: WebEngineOption) web_engine_tool.Engine { + return switch (option) { + .system => .system, + .chromium => .chromium, + }; +} + +fn buildWebEngineFromResolved(engine: web_engine_tool.Engine) WebEngineOption { + return switch (engine) { + .system => .system, + .chromium => .chromium, + }; +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..98167eb --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,25 @@ +.{ + .name = .native_sdk, + .fingerprint = 0xc309966142f33087, + .version = "0.1.0", + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + .paths = .{ + "README.md", + "CHANGELOG.md", + "LICENSE", + "SECURITY.md", + "app.zon", + "assets", + "build", + "build.zig", + "build.zig.zon", + "docs", + "examples", + "packages", + "src", + "templates", + "tests", + "tools", + }, +} diff --git a/build/app.zig b/build/app.zig new file mode 100644 index 0000000..09ef245 --- /dev/null +++ b/build/app.zig @@ -0,0 +1,991 @@ +//! Framework build helper: `addApp` gives a markup/builder app a complete +//! build (exe, run, test) from a ~5-line build.zig. The app supplies +//! src/main.zig, app.zon, and assets; the runner and all framework modules +//! come from the native-sdk dependency. + +const std = @import("std"); + +/// The shared web-layer inference contract: this build graph is one thin +/// adapter over it (the CLI's manifest tooling and the app runner are the +/// others), feeding it the inputs only this boundary sees — the +/// `-Dweb-engine` and `-Dweb-layer` flags resolved against app.zon. +const web_layer_contract = @import("../src/primitives/app_manifest/web_layer.zig"); + +const PlatformOption = enum { + auto, + null, + macos, + linux, + windows, +}; + +const TraceOption = enum { + off, + events, + runtime, + all, +}; + +const WebEngineOption = web_layer_contract.WebEngine; + +const WebLayerOption = web_layer_contract.WebViewLayer; + +pub const AppOptions = struct { + name: []const u8, + /// App entry point; defaults to src/main.zig (relative to `app_root`). + main: []const u8 = "src/main.zig", + /// Root of the app source tree, relative to the build root. "." for a + /// build.zig that lives in the app directory (every ejected app). The + /// CLI's generated build graph under `/.native/build/` passes + /// "../.." so `src/`, `app.zon`, and `assets/` keep resolving in the + /// app directory rather than the cache directory. + app_root: []const u8 = ".", +}; + +/// Which core the app tree carries. No flag and no config anywhere: the +/// tree IS the truth — `src/core.ts` is a TypeScript core (transpiled at +/// build time, run through generated wiring), `src/main.zig` a Zig one, +/// and both at once is a teaching error naming the two files. +const CoreTree = enum { zig, ts, both, neither }; + +fn detectCoreTree(b: *std.Build, app_root: []const u8) CoreTree { + const has_ts = appFileExists(b, app_root, "src/core.ts"); + const has_zig = appFileExists(b, app_root, "src/main.zig"); + if (has_ts and has_zig) return .both; + if (has_ts) return .ts; + if (has_zig) return .zig; + return .neither; +} + +fn appFileExists(b: *std.Build, app_root: []const u8, sub_path: []const u8) bool { + b.build_root.handle.access(b.graph.io, appPath(b, app_root, sub_path), .{}) catch return false; + return true; +} + +/// The staged TypeScript-core wiring: one generated directory holding the +/// transpiled core (core.zig), its rt kernel, the app's markup, and the +/// SDK's generated-wiring entry (ts_core_main.zig as main.zig). Built once +/// per app build and shared by the exe and test modules. +const TsCoreStage = struct { + main_root: std.Build.LazyPath, +}; + +fn tsCoreStage(b: *std.Build, dep: *std.Build.Dependency, app_root: []const u8) TsCoreStage { + if (!appFileExists(b, app_root, "src/app.native")) { + @panic("\nthis app has a TypeScript core (src/core.ts) but no view: TS apps render markup," ++ + " so add src/app.native (the whole view tier binds the core's emitted model)\n"); + } + const node = b.findProgram(&.{"node"}, &.{}) catch { + @panic("\nbuilding a TypeScript app core needs node on PATH (the @native-sdk/core transpiler runs at" ++ + " build time; the binary it emits ships no JS runtime).\nInstall Node.js 22+ — https://nodejs.org" ++ + " or `brew install node` — and re-run.\n"); + }; + dep.builder.build_root.handle.access( + dep.builder.graph.io, + "packages/core/node_modules/@typescript/typescript6", + .{}, + ) catch { + // Name the SDK dependency's real location (a checkout or the + // npm-installed @native-sdk/cli package — both carry + // packages/core with its lockfile, so `npm ci` works in place). + std.debug.panic("\nthe SDK's @native-sdk/core transpiler is missing its installed dependency.\nFix with:" ++ + " cd {s}/packages/core && npm ci\n", .{dep.builder.build_root.path orelse ""}); + }; + + // The transpiler runs through build/ts_run.mjs, not as `node cli.ts`: + // on the npm-installed layout the transpiler's .ts sources live inside + // node_modules, where node refuses its builtin type stripping — the + // runner strips those modules with the transpiler's own installed + // TypeScript and is a pass-through on a repo checkout. + const transpile = b.addSystemCommand(&.{node}); + transpile.addFileArg(dep.path("build/ts_run.mjs")); + transpile.addFileArg(dep.path("packages/core/src/cli.ts")); + transpile.addFileArg(b.path(appPath(b, app_root, "src/core.ts"))); + transpile.addArg("-o"); + const emitted_core = transpile.addOutputFileArg("core.zig"); + // The transpiler reads its own sources, the SDK modules, and the core's + // WHOLE import graph at run time; declare them so a transpiler upgrade + // or an edit to ANY module of a multi-file core re-emits it. The graph + // is declared as every .ts under the app's src/ (a superset of the + // reachable imports: over-approximation only re-runs the transpile, + // never misses a stale input). Failure mode: the checker's NS + // diagnostics stream to stderr verbatim — they are the teaching layer, + // nothing wraps them. + addTsDirInputs(b, dep.builder, transpile, "packages/core/sdk"); + addAppTsDirInputs(b, transpile, appPath(b, app_root, "src")); + const transpiler_sources = [_][]const u8{ + "checker.ts", "cli.ts", "diagnostics.ts", "emitter.ts", "infer.ts", "modules.ts", "transpile.ts", "typed_ast.ts", "types.ts", + }; + for (transpiler_sources) |source| { + transpile.addFileInput(dep.path(b.fmt("packages/core/src/{s}", .{source}))); + } + + // The wiring imports core.zig and app.native relatively; the emitted + // core imports rt.zig relatively: stage all four into one directory. + const staged = b.addWriteFiles(); + _ = staged.addCopyFile(emitted_core, "core.zig"); + _ = staged.addCopyFile(dep.path("packages/core/rt/rt.zig"), "rt.zig"); + _ = staged.addCopyFile(b.path(appPath(b, app_root, "src/app.native")), "app.native"); + const main_root = staged.addCopyFile(dep.path("src/app_runner/ts_core_main.zig"), "main.zig"); + return .{ .main_root = main_root }; +} + +/// Declare every .ts file in an SDK-relative directory as a file input of +/// the transpile step (the SDK library modules an app may import). +fn addTsDirInputs(b: *std.Build, sdk_builder: *std.Build, transpile: *std.Build.Step.Run, dir_path: []const u8) void { + var dir = sdk_builder.build_root.handle.openDir(b.graph.io, dir_path, .{ .iterate = true }) catch return; + defer dir.close(b.graph.io); + var it = dir.iterate(); + while (it.next(b.graph.io) catch null) |entry| { + if (entry.kind != .file) continue; + if (!std.mem.endsWith(u8, entry.name, ".ts")) continue; + transpile.addFileInput(sdk_builder.path(b.fmt("{s}/{s}", .{ dir_path, entry.name }))); + } +} + +/// Declare every .ts file under the app's src/ (recursively — a core may +/// split into subdirectories) as a file input of the transpile step. +fn addAppTsDirInputs(b: *std.Build, transpile: *std.Build.Step.Run, src_path: []const u8) void { + var dir = b.build_root.handle.openDir(b.graph.io, src_path, .{ .iterate = true }) catch return; + defer dir.close(b.graph.io); + var walker = dir.walk(b.allocator) catch return; + defer walker.deinit(); + while (walker.next(b.graph.io) catch null) |entry| { + if (entry.kind != .file) continue; + if (!std.mem.endsWith(u8, entry.basename, ".ts")) continue; + transpile.addFileInput(b.path(b.fmt("{s}/{s}", .{ src_path, entry.path }))); + } +} + +/// The `native_sdk_app_*` C ABI every embed static library exports. +pub const mobile_export_symbol_names = [_][]const u8{ + "native_sdk_app_create", + "native_sdk_app_destroy", + "native_sdk_app_start", + "native_sdk_app_activate", + "native_sdk_app_deactivate", + "native_sdk_app_stop", + "native_sdk_app_resize", + "native_sdk_app_viewport", + "native_sdk_app_viewport_state", + "native_sdk_app_gpu_frame_state", + "native_sdk_app_text_input_state", + "native_sdk_app_set_text_measure", + "native_sdk_app_set_audio_service", + "native_sdk_app_audio_event", + "native_sdk_app_set_image_service", + "native_sdk_app_set_automation_dir", + "native_sdk_app_touch", + "native_sdk_app_scroll", + "native_sdk_app_key", + "native_sdk_app_text", + "native_sdk_app_ime", + "native_sdk_app_command", + "native_sdk_app_frame", + "native_sdk_app_chrome_tab_count", + "native_sdk_app_chrome_tab_at", + "native_sdk_app_chrome_primary_action", + "native_sdk_app_chrome_selected_tab", + "native_sdk_app_chrome_navigation_depth", + "native_sdk_app_chrome_navigation_back_command", + "native_sdk_app_chrome_icon_pixels", + "native_sdk_app_set_form_factor", + "native_sdk_app_set_chrome_tabs_projected", + "native_sdk_app_set_asset_root", + "native_sdk_app_set_asset_entry", + "native_sdk_app_last_command_count", + "native_sdk_app_last_command_name", + "native_sdk_app_last_error_name", + "native_sdk_app_widget_semantics_count", + "native_sdk_app_widget_semantics_at", + "native_sdk_app_widget_semantics_by_id", + "native_sdk_app_widget_text_geometry", + "native_sdk_app_widget_action", + "native_sdk_app_render_pixel_size", + "native_sdk_app_render_pixels", + "native_sdk_app_render_pixels_damage", +}; + +pub const MobileSceneOption = enum { + /// The user app's UiApp on a gpu_surface view (window 1, + /// "mobile-surface"), pumped by the host's frame callback. + canvas, + /// The fixed WebView shell the ios/android/mobile-shell examples embed + /// today; the app module is not compiled in. + webview, +}; + +pub const MobileLibOptions = struct { + name: []const u8, + /// Mobile app entry (the `"app"` module the embed host drives); must + /// declare `Model`, `Msg`, `initModel`, and `mobileOptions` — see + /// `src/embed/ui_host.zig`. Ignored for `.scene = .webview`. + main: []const u8 = "src/main.zig", + scene: MobileSceneOption = .canvas, +}; + +/// Mobile counterpart of `addApp`: produce the embed static library +/// (`native_sdk_app_*` C ABI) compiled with the user's UiApp. Call it from +/// a standalone build.zig (it registers the standard `target`/`optimize` +/// options itself). +pub fn addMobileLib(b: *std.Build, dep: *std.Build.Dependency, options: MobileLibOptions) void { + const target = nativeSdkTarget(b); + const optimize_request = b.option(std.builtin.OptimizeMode, "optimize", "Prioritize performance, safety, or binary size"); + const optimize = exampleOptimizeMode(b, optimize_request, .Debug); + addMobileLibWithTarget(b, dep, target, optimize, options); +} + +/// The mobile-lib wiring behind `addMobileLib`, for builds that already +/// resolved `target`/`optimize` (`addAppArtifacts` registers the `lib` +/// step through this for iOS/Android targets, so every standard app — +/// generated graph or ejected `addApp` — can produce the embed library +/// with nothing but `-Dtarget`). +fn addMobileLibWithTarget(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, options: MobileLibOptions) void { + const native_sdk_mod = nativeSdkModule(b, dep, target, optimize); + // Android hosts load the embed lib inside a shared object + // (System.loadLibrary / NativeActivity), so every object must be PIC — + // without it Zig emits local-exec TLS relocations (R_AARCH64_TLSLE_*) + // that the NDK linker rejects when producing the shim .so. Imported + // modules leave `pic` null and inherit this from the root module. + const pic: ?bool = if (target.result.abi.isAndroid()) true else null; + const exports_mod = b.createModule(.{ + .root_source_file = dep.path(switch (options.scene) { + .canvas => "src/embed/app_exports.zig", + .webview => "src/embed/c_exports.zig", + }), + .target = target, + .optimize = optimize, + .pic = pic, + }); + exports_mod.addImport("native_sdk", native_sdk_mod); + if (options.scene == .canvas) { + const app_mod = localModule(b, target, optimize, options.main); + app_mod.addImport("native_sdk", native_sdk_mod); + exports_mod.addImport("app", app_mod); + } + exports_mod.export_symbol_names = &mobile_export_symbol_names; + + const lib = b.addLibrary(.{ + .linkage = .static, + .name = options.name, + .root_module = exports_mod, + // The embed C ABI (`native_sdk_app_viewport`) is exactly the + // f32-heavy SysV signature Zig 0.16.0's self-hosted x86_64 backend + // miscompiles (see useLlvmWorkaround in the framework build.zig): + // clang-compiled hosts calling a self-hosted Debug lib receive + // corrupted inset/keyboard floats on x86_64 (Android emulators, + // Intel simulators). Force LLVM there; Release already uses it. + .use_llvm = useLlvmWorkaround(target), + }); + b.installArtifact(lib); + + const lib_step = b.step("lib", "Build the mobile embed static library"); + lib_step.dependOn(&b.addInstallArtifact(lib, .{}).step); +} + +/// The pieces `addApp` wires, for callers that extend the standard app +/// build (extra native sources, frameworks, post-build steps such as +/// entitlement signing). `install` is the artifact-install step behind the +/// default `zig build`; append dependencies to it and to `run` to order +/// work between the emitted binary and its consumers. +pub const AppArtifacts = struct { + exe: *std.Build.Step.Compile, + tests: *std.Build.Step.Compile, + install: *std.Build.Step.InstallArtifact, + run: *std.Build.Step.Run, +}; + +pub fn addApp(b: *std.Build, dep: *std.Build.Dependency, app_options: AppOptions) void { + _ = addAppArtifacts(b, dep, app_options); +} + +pub fn addAppArtifacts(b: *std.Build, dep: *std.Build.Dependency, app_options: AppOptions) AppArtifacts { + const target = nativeSdkTarget(b); + const optimize_request = b.option(std.builtin.OptimizeMode, "optimize", "Prioritize performance, safety, or binary size"); + const optimize = exampleOptimizeMode(b, optimize_request, .Debug); + const app_optimize = exampleOptimizeMode(b, optimize_request, .ReleaseFast); + + // The core role is detected from the tree (never a flag or config): + // builds with a custom `main` entry declared their core explicitly and + // skip detection. + const core_tree: CoreTree = if (std.mem.eql(u8, app_options.main, "src/main.zig")) + detectCoreTree(b, app_options.app_root) + else + .zig; + if (core_tree == .both) { + @panic("\nthis app declares two cores: src/core.ts (TypeScript) and src/main.zig (Zig)." ++ + "\nAn app has exactly one core - the tree is the truth. Keep src/core.ts and delete" ++ + " src/main.zig,\nor keep src/main.zig and delete src/core.ts. (Other Zig files under" ++ + " src/ are fine either way.)\n"); + } + const ts_stage: ?TsCoreStage = if (core_tree == .ts) tsCoreStage(b, dep, app_options.app_root) else null; + + // Mobile targets get the embed static library as a `lib` step: the + // artifact the toolkit-owned iOS host (and any hand-written shim) + // links, so `native dev|package --target ios` works against every + // standard app build — generated graph or ejected — with nothing but + // `-Dtarget`. Desktop targets keep the step absent. + if (ts_stage != null and (target.result.os.tag == .ios or target.result.abi.isAndroid())) { + @panic("\nTypeScript app cores build desktop apps today; the mobile embed library for TS" ++ + " cores lands with the mobile host tier.\nBuild for a desktop target, or port the core" ++ + " to a Zig `mobileOptions` app for mobile.\n"); + } + if (target.result.os.tag == .ios or target.result.abi.isAndroid()) { + addMobileLibWithTarget(b, dep, target, optimize, .{ + .name = app_options.name, + .main = appPath(b, app_options.app_root, app_options.main), + }); + } + const platform_option = b.option(PlatformOption, "platform", "Desktop backend: auto, null, macos, linux, windows") orelse .auto; + const trace_option = b.option(TraceOption, "trace", "Trace output: off, events, runtime, all") orelse .events; + const debug_overlay = b.option(bool, "debug-overlay", "Enable debug overlay output") orelse false; + const automation_enabled = b.option(bool, "automation", "Enable Native SDK automation artifacts") orelse false; + const js_bridge_enabled = b.option(bool, "js-bridge", "Enable optional JavaScript bridge stubs") orelse false; + const web_engine_override = b.option(WebEngineOption, "web-engine", "Override app.zon web engine: system, chromium"); + const web_layer_override = b.option(WebLayerOption, "web-layer", "Override app.zon webview_layer: auto, include, exclude"); + const cef_dir_override = b.option([]const u8, "cef-dir", "Override CEF root directory for Chromium builds"); + const cef_auto_install_override = b.option(bool, "cef-auto-install", "Override app.zon CEF auto-install setting"); + const selected_platform: PlatformOption = switch (platform_option) { + .auto => if (target.result.os.tag == .macos) .macos else if (target.result.os.tag == .linux) .linux else if (target.result.os.tag == .windows) .windows else .null, + else => platform_option, + }; + if (selected_platform == .macos and target.result.os.tag != .macos) { + @panic("-Dplatform=macos requires a macOS target"); + } + if (selected_platform == .linux and target.result.os.tag != .linux) { + @panic("-Dplatform=linux requires a Linux target"); + } + if (selected_platform == .windows and target.result.os.tag != .windows) { + @panic("-Dplatform=windows requires a Windows target"); + } + const app_config = appManifestBuildConfig(b, app_options.app_root); + const web_engine = web_engine_override orelse app_config.web_engine; + const cef_dir = cef_dir_override orelse defaultCefDir(selected_platform, app_config.cef_dir); + const cef_auto_install = cef_auto_install_override orelse app_config.cef_auto_install; + if (web_engine == .chromium and selected_platform != .macos) { + @panic("-Dweb-engine=chromium currently requires -Dplatform=macos"); + } + const web_layer = resolveWebLayer(app_config, web_engine, web_layer_override); + + const options = b.addOptions(); + options.addOption([]const u8, "platform", switch (selected_platform) { + .auto => unreachable, + .null => "null", + .macos => "macos", + .linux => "linux", + .windows => "windows", + }); + options.addOption([]const u8, "trace", @tagName(trace_option)); + options.addOption([]const u8, "web_engine", @tagName(web_engine)); + options.addOption(bool, "debug_overlay", debug_overlay); + options.addOption(bool, "automation", automation_enabled); + options.addOption(bool, "js_bridge", js_bridge_enabled); + options.addOption(bool, "web_layer", web_layer); + const options_mod = options.createModule(); + + const app_mod = appModule(b, dep, target, app_optimize, app_options, options_mod, ts_stage); + const exe = b.addExecutable(.{ + .name = app_options.name, + .root_module = app_mod, + }); + linkPlatform(b, dep, target, app_mod, exe, selected_platform, web_engine, web_layer, cef_dir, cef_auto_install); + const install = b.addInstallArtifact(exe, .{}); + b.getInstallStep().dependOn(&install.step); + + const run = b.addRunArtifact(exe); + addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir); + addWebView2RuntimeRunFiles(dep, target, run, web_engine, web_layer); + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run.step); + + const test_app_mod = if (app_optimize == optimize) app_mod else appModule(b, dep, target, optimize, app_options, options_mod, ts_stage); + const tests = b.addTest(.{ .root_module = test_app_mod, .use_llvm = useLlvmWorkaround(target) }); + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&b.addRunArtifact(tests).step); + + // `native test` must surface the app's compile-time teaching errors, + // not just its test failures. Test builds never analyze `main` (the + // test runner replaces the entry point), so rules that fire inside it + // — UiApp.create's Model-defaults rule above all — used to ambush at + // `native build`, the LAST step in the loop. Compiling this object + // forces full semantic analysis of the app module, entry point + // included; nothing links or runs. + const analysis_root = b.addWriteFiles().add("app_analysis.zig", + \\//! Generated by the app build: force semantic analysis of the + \\//! app's entry point at test time. Exactly `main`, transitively — + \\//! the same surface `native build` analyzes — so test-time can + \\//! never be stricter than the build it fronts. + \\comptime { + \\ const app = @import("app"); + \\ if (@hasDecl(app, "main")) _ = &app.main; + \\} + \\ + ); + const analysis_mod = b.createModule(.{ + .root_source_file = analysis_root, + .target = target, + .optimize = optimize, + }); + analysis_mod.addImport("app", test_app_mod); + const analysis_obj = b.addObject(.{ + .name = b.fmt("{s}-analysis", .{app_options.name}), + .root_module = analysis_mod, + .use_llvm = useLlvmWorkaround(target), + }); + test_step.dependOn(&analysis_obj.step); + + // `zig build model-contract`: reflect the app's Model/Msg into + // zig-out/model-contract.zon so `native check` can verify markup + // bindings against the app's real surface without compiling the app. + // The artifact carries a hash over the app's Zig sources; the checker + // degrades to structural checking when it goes stale. Apps without a + // pub Model/Msg pair make this a silent no-op. The test step refreshes + // the artifact too, so CI-checked apps always hold a fresh one. + const contract_root = b.addWriteFiles().add("model_contract_emit.zig", + \\//! Generated by the app build: emits the model contract artifact + \\//! (see the toolkit's ui_markup_contract.zig). + \\const std = @import("std"); + \\const native_sdk = @import("native_sdk"); + \\const app = @import("app"); + \\ + \\pub fn main(init: std.process.Init) !void { + \\ try native_sdk.canvas.emitModelContractMain(app, init); + \\} + \\ + ); + // The emit root must share the app module's native_sdk instance so + // the Msg payload types it classifies are the same types the app + // declares its variants with. + const contract_mod = b.createModule(.{ + .root_source_file = contract_root, + .target = target, + .optimize = optimize, + }); + contract_mod.addImport("app", test_app_mod); + if (test_app_mod.import_table.get("native_sdk")) |sdk_mod| { + contract_mod.addImport("native_sdk", sdk_mod); + } + const contract_exe = b.addExecutable(.{ + .name = b.fmt("{s}-model-contract", .{app_options.name}), + .root_module = contract_mod, + .use_llvm = useLlvmWorkaround(target), + }); + const contract_run = b.addRunArtifact(contract_exe); + contract_run.setCwd(b.path(app_options.app_root)); + contract_run.addArgs(&.{ "--src", "src", "--out", "zig-out/model-contract.zon" }); + contract_run.has_side_effects = true; + const contract_step = b.step("model-contract", "Emit zig-out/model-contract.zon for `native check`"); + contract_step.dependOn(&contract_run.step); + test_step.dependOn(&contract_run.step); + + // `zig build package`: bundle the built binary through the `native` + // CLI (built from the native_sdk dependency), so a scaffolded app can + // package itself without locating the CLI by hand. + const host_os = b.graph.host.result.os.tag; + const package_target: ?[]const u8 = switch (host_os) { + .macos => "macos", + .linux => "linux", + .windows => "windows", + else => null, + }; + if (package_target) |package_target_name| { + const package_run = b.addRunArtifact(dep.artifact("native")); + // The CLI resolves SDK-owned package inputs (the vendored + // WebView2 loader) from the framework root; the cached artifact's + // own location cannot derive it, so hand it over explicitly. + package_run.setEnvironmentVariable("NATIVE_SDK_PATH", dep.builder.pathFromRoot(".")); + package_run.addArgs(&.{ "package", "--target", package_target_name, "--manifest", "app.zon", "--output" }); + package_run.addArg(if (host_os == .macos) + b.fmt("zig-out/package/{s}.app", .{app_options.name}) + else + b.fmt("zig-out/package/{s}", .{package_target_name})); + package_run.addArg("--binary"); + package_run.addFileArg(exe.getEmittedBin()); + // The archive and report names carry an optimize label; this + // build graph knows the packaged binary's REAL mode, so forward + // it instead of letting the CLI assume one. + package_run.addArgs(&.{ "--optimize", @tagName(app_optimize) }); + // Forward the RESOLVED web-layer decision, never the raw inputs: + // this graph already decided web vs native-only for the exe it is + // packaging (app.zon declarations plus -Dweb-layer/-Dweb-engine), + // and the CLI re-inferring from app.zon alone would miss a + // flag-driven override — a WebView2-referencing exe packaged + // without its loader. Handing over the decision itself makes + // exe/package agreement structural. + package_run.addArgs(&.{ "--web-layer", if (web_layer) "include" else "exclude" }); + // Same reasoning for the web engine: the CLI defaults to system, so + // a Chromium exe packaged without these flags would ship no CEF + // runtime (the generated build graph already forwards them). + package_run.addArgs(&.{ "--web-engine", @tagName(web_engine), "--cef-dir", cef_dir }); + if (cef_auto_install) package_run.addArg("--cef-auto-install"); + package_run.has_side_effects = true; + const package_step = b.step("package", "Create a distributable package via the native CLI"); + package_step.dependOn(&package_run.step); + } + + return .{ .exe = exe, .tests = tests, .install = install, .run = run }; +} + +/// Zig 0.16.0's self-hosted x86_64 backend miscompiles the SysV C calling +/// convention for f32-heavy signatures with interleaved pointer arguments +/// (`native_sdk_app_viewport`: 11 f32s + 2 pointers): both the caller and +/// the callee place/read the wrong registers and stack slots, so safe-area +/// insets arrive as garbage on x86_64 Debug builds while every LLVM-backed +/// build is correct. Minimal repro (fails under `zig test`, passes with +/// `-fllvm` on x86_64-linux): +/// +/// fn take(a: ?*anyopaque, w: f32, h: f32, s: f32, p: ?*anyopaque, +/// t: f32, r: f32, bo: f32, l: f32, kt: f32, kr: f32, kb: f32, +/// kl: f32) callconv(.c) void { ... } +/// +/// Force the LLVM backend on x86_64 until the upstream backend is fixed; +/// Release modes already default to LLVM, so this only changes Debug. +pub fn useLlvmWorkaround(target: std.Build.ResolvedTarget) ?bool { + return if (target.result.cpu.arch == .x86_64) true else null; +} + +fn exampleOptimizeMode(b: *std.Build, requested: ?std.builtin.OptimizeMode, default_mode: std.builtin.OptimizeMode) std.builtin.OptimizeMode { + if (requested) |mode| return mode; + return switch (b.release_mode) { + .off => default_mode, + .any, .fast => .ReleaseFast, + .safe => .ReleaseSafe, + .small => .ReleaseSmall, + }; +} + +fn appModule(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, app_options: AppOptions, options_mod: *std.Build.Module, ts_stage: ?TsCoreStage) *std.Build.Module { + const native_sdk_mod = nativeSdkModule(b, dep, target, optimize); + const runner_mod = b.createModule(.{ + .root_source_file = dep.path("src/app_runner/root.zig"), + .target = target, + .optimize = optimize, + }); + const manifest_mod = b.createModule(.{ .root_source_file = b.path(appPath(b, app_options.app_root, "app.zon")) }); + runner_mod.addImport("native_sdk", native_sdk_mod); + runner_mod.addImport("build_options", options_mod); + runner_mod.addImport("app_manifest_zon", manifest_mod); + + const app_mod = if (ts_stage) |stage| + // TypeScript core: the app module roots at the staged generated + // wiring (ts_core_main.zig beside the transpiled core.zig, its rt + // kernel, and the app's markup). + b.createModule(.{ + .root_source_file = stage.main_root, + .target = target, + .optimize = optimize, + }) + else + localModule(b, target, optimize, appPath(b, app_options.app_root, app_options.main)); + app_mod.addImport("native_sdk", native_sdk_mod); + app_mod.addImport("runner", runner_mod); + if (ts_stage != null) { + // The wiring derives scene/identity/security from app.zon itself. + app_mod.addImport("app_manifest_zon", manifest_mod); + } + return app_mod; +} + +fn nativeSdkTarget(b: *std.Build) std.Build.ResolvedTarget { + const target = b.standardTargetOptions(.{}); + if (target.result.os.tag != .macos) return target; + + if (b.sysroot == null) { + b.sysroot = macosSdkPath(b) orelse b.sysroot; + } + + var query = target.query; + query.os_tag = .macos; + query.os_version_min = .{ .semver = .{ .major = 11, .minor = 0, .patch = 0 } }; + return b.resolveTargetQuery(query); +} + +fn macosSdkPath(b: *std.Build) ?[]const u8 { + if (b.graph.environ_map.get("SDKROOT")) |sdkroot| { + if (sdkroot.len > 0) return sdkroot; + } + + const result = std.process.run(b.allocator, b.graph.io, .{ + .argv = &.{ "xcrun", "--sdk", "macosx", "--show-sdk-path" }, + .stdout_limit = .limited(4096), + .stderr_limit = .limited(4096), + }) catch return null; + defer b.allocator.free(result.stderr); + if (result.term != .exited or result.term.exited != 0) { + b.allocator.free(result.stdout); + return null; + } + return std.mem.trimEnd(u8, result.stdout, "\r\n"); +} + +fn localModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, path: []const u8) *std.Build.Module { + return b.createModule(.{ + .root_source_file = b.path(path), + .target = target, + .optimize = optimize, + }); +} + +fn nativeSdkModule(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { + const geometry_mod = externalModule(b, dep, target, optimize, "src/primitives/geometry/root.zig"); + const assets_mod = externalModule(b, dep, target, optimize, "src/primitives/assets/root.zig"); + const app_dirs_mod = externalModule(b, dep, target, optimize, "src/primitives/app_dirs/root.zig"); + const trace_mod = externalModule(b, dep, target, optimize, "src/primitives/trace/root.zig"); + const app_manifest_mod = externalModule(b, dep, target, optimize, "src/primitives/app_manifest/root.zig"); + const diagnostics_mod = externalModule(b, dep, target, optimize, "src/primitives/diagnostics/root.zig"); + const platform_info_mod = externalModule(b, dep, target, optimize, "src/primitives/platform_info/root.zig"); + const json_mod = externalModule(b, dep, target, optimize, "src/primitives/json/root.zig"); + const canvas_mod = externalModule(b, dep, target, optimize, "src/primitives/canvas/root.zig"); + canvas_mod.addImport("geometry", geometry_mod); + canvas_mod.addImport("json", json_mod); + const debug_mod = externalModule(b, dep, target, optimize, "src/debug/root.zig"); + debug_mod.addImport("app_dirs", app_dirs_mod); + debug_mod.addImport("trace", trace_mod); + + const native_sdk_mod = externalModule(b, dep, target, optimize, "src/root.zig"); + native_sdk_mod.addImport("geometry", geometry_mod); + native_sdk_mod.addImport("assets", assets_mod); + native_sdk_mod.addImport("app_dirs", app_dirs_mod); + native_sdk_mod.addImport("trace", trace_mod); + native_sdk_mod.addImport("app_manifest", app_manifest_mod); + native_sdk_mod.addImport("diagnostics", diagnostics_mod); + native_sdk_mod.addImport("platform_info", platform_info_mod); + native_sdk_mod.addImport("json", json_mod); + native_sdk_mod.addImport("canvas", canvas_mod); + return native_sdk_mod; +} + +fn externalModule(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, path: []const u8) *std.Build.Module { + return b.createModule(.{ + .root_source_file = dep.path(path), + .target = target, + .optimize = optimize, + }); +} + +// -fno-sanitize=builtin on every ObjC compile: Zig 0.16.0's Debug UBSan +// aborts any process whose first dispatch_once runs — the macOS SDK's +// inline `_dispatch_once` ends in `__builtin_assume(*predicate == ~0l)` +// (dispatch/once.h), Zig's bundled clang instruments that builtin, and the +// check fires spuriously at startup; zig's ubsan_rt then cannot even decode +// the report ("invalid enum value" / "passing zero to clz()" panics). +// Reproduced with a 10-line `zig cc` program against both the 14.5 and +// 26.0 SDKs. Release builds never hit it (no UBSan), which is why only +// Debug-built examples (standardOptimizeOption default) crashed. +fn linkPlatform(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.ResolvedTarget, app_mod: *std.Build.Module, exe: *std.Build.Step.Compile, platform: PlatformOption, web_engine: WebEngineOption, web_layer: bool, cef_dir: []const u8, cef_auto_install: bool) void { + if (platform == .macos) { + switch (web_engine) { + .system => { + const sdk_include = if (b.sysroot) |sysroot| b.fmt("-I{s}/usr/include", .{sysroot}) else ""; + const flags: []const []const u8 = if (b.sysroot) |sysroot| &.{ "-fobjc-arc", "-fno-sanitize=builtin", "-ObjC", "-mmacosx-version-min=11.0", "-isysroot", sysroot, sdk_include } else &.{ "-fobjc-arc", "-fno-sanitize=builtin", "-ObjC", "-mmacosx-version-min=11.0" }; + app_mod.addCSourceFile(.{ .file = dep.path("src/platform/macos/appkit_host.m"), .flags = flags }); + app_mod.linkFramework("WebKit", .{}); + }, + .chromium => { + const cef_check = addCefCheck(b, target, cef_dir); + if (cef_auto_install) { + const cef_auto = b.addSystemCommand(&.{ "native", "cef", "install", "--dir", cef_dir }); + cef_check.step.dependOn(&cef_auto.step); + } + exe.step.dependOn(&cef_check.step); + const include_arg = b.fmt("-I{s}", .{cef_dir}); + const define_arg = b.fmt("-DNATIVE_SDK_CEF_DIR=\"{s}\"", .{cef_dir}); + // The SDK's usr/include must stay a system include dir (searched after zig's + // bundled libc++/libc headers). A plain -I shadows libc++'s / + // wrappers in ObjC++ and surfaces SDK nullability gaps as a diagnostic flood. + const sdk_include = if (b.sysroot) |sysroot| b.fmt("-isystem{s}/usr/include", .{sysroot}) else ""; + const flags: []const []const u8 = if (b.sysroot) |sysroot| &.{ "-fobjc-arc", "-fno-sanitize=builtin", "-ObjC++", "-std=c++17", "-stdlib=libc++", "-mmacosx-version-min=11.0", "-isysroot", sysroot, sdk_include, include_arg, define_arg } else &.{ "-fobjc-arc", "-fno-sanitize=builtin", "-ObjC++", "-std=c++17", "-stdlib=libc++", "-mmacosx-version-min=11.0", include_arg, define_arg }; + app_mod.addCSourceFile(.{ .file = dep.path("src/platform/macos/cef_host.mm"), .flags = flags }); + app_mod.addObjectFile(b.path(b.fmt("{s}/libcef_dll_wrapper/libcef_dll_wrapper.a", .{cef_dir}))); + app_mod.addFrameworkPath(b.path(b.fmt("{s}/Release", .{cef_dir}))); + app_mod.linkFramework("Chromium Embedded Framework", .{}); + app_mod.addRPath(.{ .cwd_relative = "@executable_path/Frameworks" }); + }, + } + if (b.sysroot) |sysroot| { + app_mod.addFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sysroot, "System/Library/Frameworks" }) }); + } + app_mod.linkFramework("AppKit", .{}); + // The audio playback service (the AppKit host's single AVPlayer). + app_mod.linkFramework("AVFoundation", .{}); + // Spectrum analysis of the app's own playback: the MediaToolbox + // audio tap hands the player's PCM to the host, and Accelerate + // (vDSP) turns it into band magnitudes. + app_mod.linkFramework("MediaToolbox", .{}); + app_mod.linkFramework("Accelerate", .{}); + app_mod.linkFramework("Foundation", .{}); + app_mod.linkFramework("CoreText", .{}); + app_mod.linkFramework("UniformTypeIdentifiers", .{}); + app_mod.linkFramework("Security", .{}); + app_mod.linkFramework("Metal", .{}); + app_mod.linkFramework("QuartzCore", .{}); + app_mod.linkSystemLibrary("c", .{}); + if (web_engine == .chromium) app_mod.linkSystemLibrary("c++", .{}); + } else if (platform == .linux) { + switch (web_engine) { + .system => if (web_layer) { + app_mod.addCSourceFile(.{ .file = dep.path("src/platform/linux/gtk_host.c"), .flags = &.{} }); + app_mod.linkSystemLibrary("gtk4", .{}); + app_mod.linkSystemLibrary("webkitgtk-6.0", .{}); + app_mod.linkSystemLibrary("dl", .{}); + } else { + // Native-only app (nothing in app.zon declares web use): + // compile the GTK host without the embedded web layer. + // The stub define excludes the layer outright — the host + // honors it before probing for the WebKitGTK header, so + // the layer stays out even on machines where the + // development package is installed — libwebkitgtk is + // neither linked nor required at runtime, and the + // executable carries no WebKit reference at all. + app_mod.addCSourceFile(.{ .file = dep.path("src/platform/linux/gtk_host.c"), .flags = &.{"-DNATIVE_SDK_ALLOW_WEBKITGTK_STUB"} }); + app_mod.linkSystemLibrary("gtk4", .{}); + app_mod.linkSystemLibrary("dl", .{}); + }, + .chromium => { + const cef_check = addCefCheck(b, target, cef_dir); + if (cef_auto_install) { + const cef_auto = b.addSystemCommand(&.{ "native", "cef", "install", "--dir", cef_dir }); + cef_check.step.dependOn(&cef_auto.step); + } + exe.step.dependOn(&cef_check.step); + const include_arg = b.fmt("-I{s}", .{cef_dir}); + const define_arg = b.fmt("-DNATIVE_SDK_CEF_DIR=\"{s}\"", .{cef_dir}); + app_mod.addCSourceFile(.{ .file = dep.path("src/platform/linux/cef_host.cpp"), .flags = &.{ "-std=c++17", include_arg, define_arg } }); + app_mod.addObjectFile(b.path(b.fmt("{s}/libcef_dll_wrapper/libcef_dll_wrapper.a", .{cef_dir}))); + app_mod.addLibraryPath(b.path(b.fmt("{s}/Release", .{cef_dir}))); + app_mod.linkSystemLibrary("cef", .{}); + app_mod.addRPath(.{ .cwd_relative = "$ORIGIN" }); + }, + } + app_mod.linkSystemLibrary("c", .{}); + if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{}); + } else if (platform == .windows) { + // Common-controls v6 side-by-side dependency: without this + // manifest the loader binds the system-default v5 assembly, which + // renders classic-styled controls and lacks the v6-only exports. + // The manifest also declares per-monitor-v2 DPI awareness so the + // canvas rasterizes at real device scale instead of Windows + // bitmap-stretching a 96-DPI surface on scaled displays. + exe.win32_manifest = dep.path("assets/native-sdk.manifest"); + switch (web_engine) { + .system => if (web_layer) { + // The vendored WebView2 SDK header (third_party/webview2) + // turns on the host's embedded-WebView layer; the host + // fails the compile by design if it cannot be found. + app_mod.addIncludePath(dep.path("third_party/webview2/include")); + app_mod.addCSourceFile(.{ .file = dep.path("src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }); + // WebView2Loader.dll rides next to the installed app + // executable: the host loads it at runtime to discover + // the machine's WebView2 runtime. Canvas apps never + // touch it. + const loader = b.addInstallBinFile(dep.path(webView2LoaderSubPath(target)), "WebView2Loader.dll"); + b.getInstallStep().dependOn(&loader.step); + } else { + // Native-only app (nothing in app.zon declares web use): + // compile the host without the embedded-WebView layer. + // The stub define excludes the layer outright — the host + // honors it before probing for the WebView2 header, so + // the layer stays out even on machines where the SDK + // headers are reachable through the system include paths + // — no WebView2Loader.dll is installed or path-wired, + // and the executable carries no reference to it at all. + app_mod.addCSourceFile(.{ .file = dep.path("src/platform/windows/webview2_host.cpp"), .flags = &.{ "-std=c++17", "-DNATIVE_SDK_ALLOW_WEBVIEW2_STUB" } }); + }, + .chromium => { + const cef_check = addCefCheck(b, target, cef_dir); + if (cef_auto_install) { + const cef_auto = b.addSystemCommand(&.{ "native", "cef", "install", "--dir", cef_dir }); + cef_check.step.dependOn(&cef_auto.step); + } + exe.step.dependOn(&cef_check.step); + const include_arg = b.fmt("-I{s}", .{cef_dir}); + const define_arg = b.fmt("-DNATIVE_SDK_CEF_DIR=\"{s}\"", .{cef_dir}); + app_mod.addCSourceFile(.{ .file = dep.path("src/platform/windows/cef_host.cpp"), .flags = &.{ "-std=c++17", include_arg, define_arg } }); + app_mod.addObjectFile(b.path(b.fmt("{s}/libcef_dll_wrapper/libcef_dll_wrapper.lib", .{cef_dir}))); + app_mod.addLibraryPath(b.path(b.fmt("{s}/Release", .{cef_dir}))); + }, + } + app_mod.linkSystemLibrary("c", .{}); + app_mod.linkSystemLibrary("c++", .{}); + app_mod.linkSystemLibrary("user32", .{}); + app_mod.linkSystemLibrary("gdi32", .{}); + app_mod.linkSystemLibrary("imm32", .{}); + app_mod.linkSystemLibrary("comctl32", .{}); + app_mod.linkSystemLibrary("ole32", .{}); + app_mod.linkSystemLibrary("oleacc", .{}); + app_mod.linkSystemLibrary("shell32", .{}); + // The audio backend: Media Foundation (session + source resolver + // + streaming audio renderer) and WinHTTP (the cache fill). + app_mod.linkSystemLibrary("mf", .{}); + app_mod.linkSystemLibrary("mfplat", .{}); + app_mod.linkSystemLibrary("winhttp", .{}); + if (web_engine == .chromium) app_mod.linkSystemLibrary("libcef", .{}); + } +} + +/// The vendored WebView2Loader.dll for the target architecture, relative +/// to the framework root. +fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 { + return if (target.result.cpu.arch == .aarch64) + "third_party/webview2/arm64/WebView2Loader.dll" + else + "third_party/webview2/x64/WebView2Loader.dll"; +} + +/// `zig build run` executes the cached artifact, which has no installed +/// WebView2Loader.dll beside it; the vendored loader's directory goes on +/// the run step's PATH so the host's LoadLibrary resolves it in dev runs. +fn addWebView2RuntimeRunFiles(dep: *std.Build.Dependency, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, web_layer: bool) void { + if (web_engine != .system) return; + if (!web_layer) return; + if (target.result.os.tag != .windows) return; + const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?; + run.addPathDir(dep.builder.pathFromRoot(loader_dir)); +} + +fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void { + if (web_engine != .chromium) return; + if (target.result.os.tag != .macos) return; + const copy = b.addSystemCommand(&.{ + "sh", "-c", + b.fmt( + \\set -e + \\exe="$0" + \\exe_dir="$(dirname "$exe")" + \\rm -rf "zig-out/Frameworks/Chromium Embedded Framework.framework" "zig-out/bin/Frameworks/Chromium Embedded Framework.framework" ".zig-cache/o/Frameworks/Chromium Embedded Framework.framework" && + \\mkdir -p "zig-out/Frameworks" "zig-out/bin/Frameworks" ".zig-cache/o/Frameworks" "$exe_dir" && + \\cp -R "{s}/Release/Chromium Embedded Framework.framework" "zig-out/Frameworks/" && + \\cp -R "{s}/Release/Chromium Embedded Framework.framework" "zig-out/bin/Frameworks/" && + \\cp -R "{s}/Release/Chromium Embedded Framework.framework" ".zig-cache/o/Frameworks/" && + \\cp "{s}/Release/Chromium Embedded Framework.framework/Libraries/libEGL.dylib" "$exe_dir/" && + \\cp "{s}/Release/Chromium Embedded Framework.framework/Libraries/libGLESv2.dylib" "$exe_dir/" && + \\cp "{s}/Release/Chromium Embedded Framework.framework/Libraries/libvk_swiftshader.dylib" "$exe_dir/" && + \\cp "{s}/Release/Chromium Embedded Framework.framework/Libraries/vk_swiftshader_icd.json" "$exe_dir/" + , .{ cef_dir, cef_dir, cef_dir, cef_dir, cef_dir, cef_dir, cef_dir }), + }); + copy.addFileArg(exe.getEmittedBin()); + run.step.dependOn(©.step); +} + +fn addCefCheck(b: *std.Build, target: std.Build.ResolvedTarget, cef_dir: []const u8) *std.Build.Step.Run { + const script = switch (target.result.os.tag) { + .macos => b.fmt( + \\test -f "{s}/include/cef_app.h" && + \\test -d "{s}/Release/Chromium Embedded Framework.framework" && + \\test -f "{s}/libcef_dll_wrapper/libcef_dll_wrapper.a" || {{ + \\ echo "missing CEF dependency for -Dweb-engine=chromium" >&2 + \\ echo "Fix with: native cef install --dir {s}" >&2 + \\ exit 1 + \\}} + , .{ cef_dir, cef_dir, cef_dir, cef_dir }), + .linux => b.fmt( + \\test -f "{s}/include/cef_app.h" && + \\test -f "{s}/Release/libcef.so" && + \\test -f "{s}/libcef_dll_wrapper/libcef_dll_wrapper.a" || {{ + \\ echo "missing CEF dependency for -Dweb-engine=chromium" >&2 + \\ echo "Fix with: native cef install --dir {s}" >&2 + \\ exit 1 + \\}} + , .{ cef_dir, cef_dir, cef_dir, cef_dir }), + .windows => b.fmt( + \\test -f "{s}/include/cef_app.h" && + \\test -f "{s}/Release/libcef.dll" && + \\test -f "{s}/libcef_dll_wrapper/libcef_dll_wrapper.lib" || {{ + \\ echo "missing CEF dependency for -Dweb-engine=chromium" >&2 + \\ echo "Fix with: native cef install --dir {s}" >&2 + \\ exit 1 + \\}} + , .{ cef_dir, cef_dir, cef_dir, cef_dir }), + else => "echo unsupported CEF target >&2; exit 1", + }; + return b.addSystemCommand(&.{ "sh", "-c", script }); +} + +/// What the build graph reads out of app.zon: the web-engine/CEF knobs +/// and the web-layer inference inputs. An unreadable or unparsable +/// manifest falls back to the system engine WITH the web layer kept — +/// over-inclusion is a size cost, wrong exclusion is a broken app. +const AppManifestBuildConfig = struct { + web_engine: WebEngineOption = .system, + cef_dir: []const u8 = "third_party/cef/macos", + cef_auto_install: bool = false, + webview_layer: WebLayerOption = .auto, + /// The first web declaration found (for teaching messages), or null + /// when app.zon declares no web use. `web_engine = "system"` alone is + /// NOT web intent — it is the default in many canvas manifests. + web_declaration: ?web_layer_contract.Declaration = null, +}; + +/// The lenient app.zon shape the build graph parses for inference: only +/// the fields that decide the web layer and the web engine; everything +/// else is ignored. Full schema validation stays with `native validate` +/// and the runner's comptime import. +const InferenceManifest = struct { + capabilities: []const []const u8 = &.{}, + web_engine: []const u8 = "system", + webview_layer: []const u8 = "auto", + cef: struct { + dir: []const u8 = "third_party/cef/macos", + auto_install: bool = false, + } = .{}, + frontend: ?struct {} = null, + shell: struct { + windows: []const struct { + views: []const struct { + kind: []const u8 = "", + } = &.{}, + } = &.{}, + } = .{}, +}; + +fn defaultCefDir(platform: PlatformOption, configured: []const u8) []const u8 { + if (!std.mem.eql(u8, configured, "third_party/cef/macos")) return configured; + return switch (platform) { + .linux => "third_party/cef/linux", + .windows => "third_party/cef/windows", + else => configured, + }; +} + +/// Resolve an app-relative path against `app_root` (see AppOptions). Kept +/// lexical: `b.path` rejects absolute paths and the generated build graph +/// hands us "../..", which openat/b.path both resolve fine. +fn appPath(b: *std.Build, app_root: []const u8, sub_path: []const u8) []const u8 { + if (app_root.len == 0 or std.mem.eql(u8, app_root, ".")) return sub_path; + return b.pathJoin(&.{ app_root, sub_path }); +} + +fn appManifestBuildConfig(b: *std.Build, app_root: []const u8) AppManifestBuildConfig { + // The fallback for a manifest this lenient parse cannot read keeps + // the web layer (see AppManifestBuildConfig): a shape mismatch here + // is not proof the app declares no web use. + const fallback: AppManifestBuildConfig = .{ .web_declaration = .unreadable_manifest }; + const source = b.build_root.handle.readFileAlloc(b.graph.io, appPath(b, app_root, "app.zon"), b.allocator, .limited(1024 * 1024)) catch return fallback; + const source_z = b.allocator.dupeZ(u8, source) catch return fallback; + @setEvalBranchQuota(2000); + const raw = std.zon.parse.fromSliceAlloc(InferenceManifest, b.allocator, source_z, null, .{ .ignore_unknown_fields = true }) catch return fallback; + return .{ + .web_engine = web_layer_contract.parseWebEngine(raw.web_engine) orelse .system, + .cef_dir = raw.cef.dir, + .cef_auto_install = raw.cef.auto_install, + .webview_layer = web_layer_contract.parseWebViewLayer(raw.webview_layer) orelse @panic("app.zon .webview_layer must be \"auto\", \"include\", or \"exclude\""), + .web_declaration = web_layer_contract.manifestDeclaration(raw), + }; +} + +/// The web-layer decision for this build: the shared contract fed this +/// boundary's inputs — the manifest declarations from the lenient parse +/// and the engine RESOLVED from `-Dweb-engine` orelse app.zon. WEB means +/// the embedded-WebView layer compiles in; NATIVE-ONLY compiles the host +/// without it. `.webview_layer` (and `-Dweb-layer`) override the +/// inference — but an exclude that contradicts a web declaration is a +/// hard configure error, never a silently broken app. +fn resolveWebLayer(config: AppManifestBuildConfig, web_engine: WebEngineOption, override: ?WebLayerOption) bool { + const setting = override orelse config.webview_layer; + const declaration = web_layer_contract.foldEngine(config.web_declaration, web_engine); + const decision = web_layer_contract.decide(setting, declaration) catch std.debug.panic( + "the web layer is excluded ({s}) but the app declares web use ({s}); remove the exclude or drop the web declaration", + .{ if (override != null) "-Dweb-layer=exclude" else "app.zon .webview_layer = \"exclude\"", declaration.?.text() }, + ); + return decision.enabled; +} diff --git a/build/ts_run.mjs b/build/ts_run.mjs new file mode 100644 index 0000000..1bac623 --- /dev/null +++ b/build/ts_run.mjs @@ -0,0 +1,63 @@ +#!/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 [args...] +// +// From a repo checkout `node packages/core/src/cli.ts` works directly — +// node strips types natively. From the npm-installed @native-sdk/cli the +// SAME file sits inside node_modules, where node refuses its builtin type +// stripping by design (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING). This +// runner closes that gap without forking the layouts: it registers a load +// hook that strips ONLY node_modules-resident .ts modules, using the +// transpiler's own installed TypeScript (the `npm ci` dependency inside +// packages/core, resolved from the target module's location), and leaves +// every other module to node's native handling. Then it 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 (< 22.15) the hook is +// skipped: repo checkouts keep working natively and the npm layout fails +// with node's own teaching error naming the unsupported stripping. + +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 [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') { + let ts = null; + module.registerHooks({ + load(url, context, nextLoad) { + if (url.startsWith('file:') && url.endsWith('.ts') && url.includes('node_modules')) { + const filePath = fileURLToPath(url); + // The transpiler's own dependency, resolved from the transpiler's + // location (packages/core/node_modules after the taught `npm ci`). + ts ??= createRequire(targetPath)('@typescript/typescript6'); + 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); diff --git a/changelog.d/README.md b/changelog.d/README.md new file mode 100644 index 0000000..a48adaf --- /dev/null +++ b/changelog.d/README.md @@ -0,0 +1,29 @@ +# Changelog fragments + +Agents and feature branches do not edit `CHANGELOG.md` directly — concurrent work would conflict on every merge. Instead, each change lands with a small fragment in this directory, and `scripts/changelog-merge.sh` folds all fragments into the `## Unreleased` section of `CHANGELOG.md` (typically during release prep, see RELEASING.md). + +## Writing a fragment + +Add `changelog.d/.md`, where `` names your change (e.g. `gpu-dashboard-smoke-budget.md`). The file holds a bullet or two for one changelog section: + +- The first line starts with a section tag: `feature:`, `improvement:`, or `fix:`, followed by the first bullet's text. +- Any further lines are additional bullets (start them with `- `; bare lines get `- ` prefixed for you). +- One tag per fragment. A change that touches multiple sections ships multiple fragments. +- Match the CHANGELOG voice: bold lead-in, then the story. One line per bullet — never hard-wrap. + +Example (`changelog.d/faster-frobnication.md`): + +``` +improvement: **Faster frobnication**: the frobnicator now memoizes per-frame, cutting rebuild time ~40% on the kanban example. +- **Frobnication telemetry**: automation snapshots report `frob_cache_hits=`. +``` + +Tags map to sections: `feature:` → `### New Features`, `improvement:` → `### Improvements`, `fix:` → `### Bug Fixes`. + +## Merging + +```sh +scripts/changelog-merge.sh +``` + +appends every fragment's bullets to the end of its section under `## Unreleased` (creating the section — or the whole `## Unreleased` block — when missing), then deletes the merged fragments. This `README.md` is never merged or deleted. The script refuses unknown tags loudly instead of guessing. diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..a6ca52a --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.next/ +next-env.d.ts +.next-gate/ +.next-agent/ +.next-check/ diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 0000000..5790313 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,22 @@ +# Docs Site Conventions + +## MDX Tables + +Always use HTML `` syntax in MDX pages, never markdown pipe tables. This ensures consistent styling and avoids MDX parsing edge cases. + +```html +
+ + + + + + + + + + + + +
ColumnDescription
fieldWhat it does
+``` diff --git a/docs/next.config.mjs b/docs/next.config.mjs new file mode 100644 index 0000000..c5b5110 --- /dev/null +++ b/docs/next.config.mjs @@ -0,0 +1,40 @@ +import createMDX from "@next/mdx"; +import { createRequire } from "node:module"; + +// 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 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() { + return [ + // The Philosophy page became the Introduction, the opening page of the docs. + { source: "/philosophy", destination: "/introduction", permanent: true }, + ]; + }, +}; + +export default withMDX(nextConfig); diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..491ae29 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,44 @@ +{ + "name": "@native-sdk/docs", + "version": "0.0.0", + "private": true, + "type": "module", + "packageManager": "pnpm@10.23.0", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "typecheck": "tsc --noEmit", + "check": "pnpm typecheck && pnpm build && node scripts/check-code-toggle.mjs" + }, + "dependencies": { + "@mdx-js/loader": "^3", + "@mdx-js/react": "^3", + "@next/mdx": "^16.1.6", + "clsx": "^2.1.1", + "geist": "^1.7.0", + "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" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/mdx": "^2", + "@types/node": "^22", + "@types/react": "^19", + "@types/react-dom": "^19", + "tailwindcss": "^4", + "typescript": "^5" + }, + "pnpm": { + "overrides": { + "postcss@<8.5.10": ">=8.5.10" + } + } +} \ No newline at end of file diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml new file mode 100644 index 0000000..37fb0e5 --- /dev/null +++ b/docs/pnpm-lock.yaml @@ -0,0 +1,4132 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + postcss@<8.5.10: '>=8.5.10' + +importers: + + .: + dependencies: + '@mdx-js/loader': + specifier: ^3 + version: 3.1.1 + '@mdx-js/react': + specifier: ^3 + version: 3.1.1(@types/react@19.2.14)(react@19.2.6) + '@next/mdx': + specifier: ^16.1.6 + version: 16.2.5(@mdx-js/loader@3.1.1)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.6)) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + geist: + specifier: ^1.7.0 + version: 1.7.0(next@16.2.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + next: + 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) + radix-ui: + specifier: ^1.4.3 + version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: + specifier: ^19 + version: 19.2.6 + 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 + tailwind-merge: + specifier: ^3.5.0 + version: 3.5.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@4.2.4) + devDependencies: + '@tailwindcss/postcss': + specifier: ^4 + version: 4.2.4 + '@types/mdx': + specifier: ^2 + version: 2.0.13 + '@types/node': + specifier: ^22 + version: 22.19.17 + '@types/react': + specifier: ^19 + version: 19.2.14 + '@types/react-dom': + specifier: ^19 + version: 19.2.3(@types/react@19.2.14) + tailwindcss: + specifier: ^4 + version: 4.2.4 + typescript: + specifier: ^5 + version: 5.9.3 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + 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==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mdx-js/loader@3.1.1': + resolution: {integrity: sha512-0TTacJyZ9mDmY+VefuthVshaNIyCGZHJG2fMnGaDttCt8HmjUF7SizlHJpaCDoGnN635nK1wpzfpx/Xx5S4WnQ==} + peerDependencies: + webpack: '>=5' + peerDependenciesMeta: + webpack: + optional: true + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@mdx-js/react@3.1.1': + resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + + '@next/env@16.2.9': + resolution: {integrity: sha512-ki5VxxXfzD/9TDe13wyeTKIjQTAwBVpnr8KhRDUr8ltMUq1/NBpWNT5tiPoxiGl+PHM4X2ahSOiPk6iAimIzPg==} + + '@next/mdx@16.2.5': + resolution: {integrity: sha512-U1r0I3Ga5/PYKH+loar1OfWCjkZXwG6qFovDzyAFPI2Nxi9gLOWZQ3dLNC5znSGLPToJauRbqgi3kfkKEFqNig==} + peerDependencies: + '@mdx-js/loader': '>=0.15.0' + '@mdx-js/react': '>=0.15.0' + peerDependenciesMeta: + '@mdx-js/loader': + optional: true + '@mdx-js/react': + optional: true + + '@next/swc-darwin-arm64@16.2.9': + resolution: {integrity: sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@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.9': + resolution: {integrity: sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@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.9': + resolution: {integrity: sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@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.9': + resolution: {integrity: sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.2.9': + resolution: {integrity: sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-accessible-icon@1.1.7': + resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-aspect-ratio@1.1.7': + resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-form@0.1.8': + resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.1.15': + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menubar@1.1.16': + resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.2.14': + resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-one-time-password-field@0.1.8': + resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-password-toggle-field@0.1.3': + resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.3.8': + resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.3.6': + resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.2.15': + resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.1.11': + resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toolbar@1.1.11': + resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@shikijs/core@4.0.2': + resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.0.2': + resolution: {integrity: sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@4.0.2': + resolution: {integrity: sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==} + engines: {node: '>=20'} + + '@shikijs/langs@4.0.2': + resolution: {integrity: sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.0.2': + resolution: {integrity: sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==} + engines: {node: '>=20'} + + '@shikijs/themes@4.0.2': + resolution: {integrity: sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==} + engines: {node: '>=20'} + + '@shikijs/types@4.0.2': + resolution: {integrity: sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/node@4.2.4': + resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} + + '@tailwindcss/oxide-android-arm64@4.2.4': + resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.4': + resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} + 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==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.4': + resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} + engines: {node: '>= 20'} + + '@tailwindcss/postcss@4.2.4': + resolution: {integrity: sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + baseline-browser-mapping@2.10.27: + resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==} + engines: {node: '>=6.0.0'} + hasBin: true + + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + enhanced-resolve@5.21.0: + resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} + engines: {node: '>=10.13.0'} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + 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==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + geist@1.7.0: + resolution: {integrity: sha512-ZaoiZwkSf0DwwB1ncdLKp+ggAldqxl5L1+SXaNIBGkPAqcu+xjVJLxlf3/S8vLt9UHx1xu5fz3lbzKCj5iOVdQ==} + peerDependencies: + next: '>=13.2.0' + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + 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==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + markdown-extensions@2.0.0: + 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==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + 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==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + 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.9: + resolution: {integrity: sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + 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==} + + radix-ui@1.4.3: + resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + peerDependencies: + react: ^19.2.6 + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + 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==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + 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==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shiki@4.0.2: + resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==} + engines: {node: '>=20'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@4.2.4: + resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@floating-ui/utils@0.2.11': {} + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.10.0 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mdx-js/loader@3.1.1': + dependencies: + '@mdx-js/mdx': 3.1.1 + source-map: 0.7.6 + transitivePeerDependencies: + - supports-color + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.9 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + acorn: 8.16.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.16.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 19.2.14 + react: 19.2.6 + + '@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: + source-map: 0.7.6 + optionalDependencies: + '@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.9': + optional: true + + '@next/swc-darwin-x64@16.2.9': + optional: true + + '@next/swc-linux-arm64-gnu@16.2.9': + optional: true + + '@next/swc-linux-arm64-musl@16.2.9': + optional: true + + '@next/swc-linux-x64-gnu@16.2.9': + optional: true + + '@next/swc-linux-x64-musl@16.2.9': + optional: true + + '@next/swc-win32-arm64-msvc@16.2.9': + optional: true + + '@next/swc-win32-x64-msvc@16.2.9': + optional: true + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/rect': 1.1.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + use-sync-external-store: 1.6.0(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/rect@1.1.1': {} + + '@shikijs/core@4.0.2': + dependencies: + '@shikijs/primitive': 4.0.2 + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + + '@shikijs/primitive@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/themes@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + + '@shikijs/types@4.0.2': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.2.4': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.21.0 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.4 + + '@tailwindcss/oxide-android-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide@4.2.4': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + + '@tailwindcss/postcss@4.2.4': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + postcss: 8.5.14 + tailwindcss: 4.2.4 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + + '@types/estree@1.0.9': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/node@22.19.17': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.1': {} + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + astring@1.9.0: {} + + bail@2.0.2: {} + + baseline-browser-mapping@2.10.27: {} + + caniuse-lite@1.0.30001792: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + + comma-separated-tokens@2.0.3: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + enhanced-resolve@5.21.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.16.0 + 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 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + extend@3.0.2: {} + + 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.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + + get-nonce@1.0.1: {} + + graceful-fs@4.2.11: {} + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.9 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + html-void-elements@3.0.0: {} + + inline-style-parser@0.2.7: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-hexadecimal@2.0.1: {} + + is-plain-obj@4.1.0: {} + + jiti@2.7.0: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + longest-streak@3.1.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + 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 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + 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 + '@types/hast': 3.0.4 + '@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-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@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-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.1 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + 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 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + 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-factory-whitespace@2.0.1: + dependencies: + 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-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.9 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + ms@2.1.3: {} + + 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.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + '@next/env': 16.2.9 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.10.27 + caniuse-lite: 1.0.30001792 + 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.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' + - babel-plugin-macros + + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + picocolors@1.1.1: {} + + postcss@8.5.14: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.16: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + property-information@7.1.0: {} + + radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + react-dom@19.2.6(react@19.2.6): + dependencies: + react: 19.2.6 + scheduler: 0.27.0 + + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.6): + dependencies: + react: 19.2.6 + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.6) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.6): + dependencies: + react: 19.2.6 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.6) + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.6) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.6) + use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + + react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.6): + dependencies: + get-nonce: 1.0.1 + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + react@19.2.6: {} + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.9 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.9 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.9 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + 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 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + 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: + optional: true + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shiki@4.0.2: + dependencies: + '@shikijs/core': 4.0.2 + '@shikijs/engine-javascript': 4.0.2 + '@shikijs/engine-oniguruma': 4.0.2 + '@shikijs/langs': 4.0.2 + '@shikijs/themes': 4.0.2 + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + styled-jsx@5.1.6(react@19.2.6): + dependencies: + client-only: 0.0.1 + react: 19.2.6 + + tailwind-merge@3.5.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@4.2.4): + dependencies: + tailwindcss: 4.2.4 + + tailwindcss@4.2.4: {} + + tapable@2.3.3: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tslib@2.8.1: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.6): + dependencies: + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.6): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + use-sync-external-store@1.6.0(react@19.2.6): + dependencies: + react: 19.2.6 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + zwitch@2.0.4: {} diff --git a/docs/postcss.config.mjs b/docs/postcss.config.mjs new file mode 100644 index 0000000..c2ddf74 --- /dev/null +++ b/docs/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/docs/public/Geist-Regular.ttf b/docs/public/Geist-Regular.ttf new file mode 100644 index 0000000..07f9e4f Binary files /dev/null and b/docs/public/Geist-Regular.ttf differ diff --git a/docs/public/GeistPixel-Square.ttf b/docs/public/GeistPixel-Square.ttf new file mode 100644 index 0000000..caa0a61 Binary files /dev/null and b/docs/public/GeistPixel-Square.ttf differ diff --git a/docs/public/components/accordion-dark.webp b/docs/public/components/accordion-dark.webp new file mode 100644 index 0000000..f7f3512 Binary files /dev/null and b/docs/public/components/accordion-dark.webp differ diff --git a/docs/public/components/accordion-hero-dark.webp b/docs/public/components/accordion-hero-dark.webp new file mode 100644 index 0000000..ed41647 Binary files /dev/null and b/docs/public/components/accordion-hero-dark.webp differ diff --git a/docs/public/components/accordion-hero-light.webp b/docs/public/components/accordion-hero-light.webp new file mode 100644 index 0000000..ec43316 Binary files /dev/null and b/docs/public/components/accordion-hero-light.webp differ diff --git a/docs/public/components/accordion-light.webp b/docs/public/components/accordion-light.webp new file mode 100644 index 0000000..c7a4361 Binary files /dev/null and b/docs/public/components/accordion-light.webp differ diff --git a/docs/public/components/alert-dark.webp b/docs/public/components/alert-dark.webp new file mode 100644 index 0000000..ac60a7a Binary files /dev/null and b/docs/public/components/alert-dark.webp differ diff --git a/docs/public/components/alert-hero-dark.webp b/docs/public/components/alert-hero-dark.webp new file mode 100644 index 0000000..a692027 Binary files /dev/null and b/docs/public/components/alert-hero-dark.webp differ diff --git a/docs/public/components/alert-hero-light.webp b/docs/public/components/alert-hero-light.webp new file mode 100644 index 0000000..795c672 Binary files /dev/null and b/docs/public/components/alert-hero-light.webp differ diff --git a/docs/public/components/alert-light.webp b/docs/public/components/alert-light.webp new file mode 100644 index 0000000..6354dd2 Binary files /dev/null and b/docs/public/components/alert-light.webp differ diff --git a/docs/public/components/avatar-dark.webp b/docs/public/components/avatar-dark.webp new file mode 100644 index 0000000..9e4b4f6 Binary files /dev/null and b/docs/public/components/avatar-dark.webp differ diff --git a/docs/public/components/avatar-hero-dark.webp b/docs/public/components/avatar-hero-dark.webp new file mode 100644 index 0000000..71262fa Binary files /dev/null and b/docs/public/components/avatar-hero-dark.webp differ diff --git a/docs/public/components/avatar-hero-light.webp b/docs/public/components/avatar-hero-light.webp new file mode 100644 index 0000000..6cbc762 Binary files /dev/null and b/docs/public/components/avatar-hero-light.webp differ diff --git a/docs/public/components/avatar-light.webp b/docs/public/components/avatar-light.webp new file mode 100644 index 0000000..14fddaa Binary files /dev/null and b/docs/public/components/avatar-light.webp differ diff --git a/docs/public/components/badge-dark.webp b/docs/public/components/badge-dark.webp new file mode 100644 index 0000000..261803e Binary files /dev/null and b/docs/public/components/badge-dark.webp differ diff --git a/docs/public/components/badge-hero-dark.webp b/docs/public/components/badge-hero-dark.webp new file mode 100644 index 0000000..fe7a2c6 Binary files /dev/null and b/docs/public/components/badge-hero-dark.webp differ diff --git a/docs/public/components/badge-hero-light.webp b/docs/public/components/badge-hero-light.webp new file mode 100644 index 0000000..5adc7da Binary files /dev/null and b/docs/public/components/badge-hero-light.webp differ diff --git a/docs/public/components/badge-light.webp b/docs/public/components/badge-light.webp new file mode 100644 index 0000000..46f0c8b Binary files /dev/null and b/docs/public/components/badge-light.webp differ diff --git a/docs/public/components/breadcrumb-dark.webp b/docs/public/components/breadcrumb-dark.webp new file mode 100644 index 0000000..8e199a4 Binary files /dev/null and b/docs/public/components/breadcrumb-dark.webp differ diff --git a/docs/public/components/breadcrumb-hero-dark.webp b/docs/public/components/breadcrumb-hero-dark.webp new file mode 100644 index 0000000..aba5738 Binary files /dev/null and b/docs/public/components/breadcrumb-hero-dark.webp differ diff --git a/docs/public/components/breadcrumb-hero-light.webp b/docs/public/components/breadcrumb-hero-light.webp new file mode 100644 index 0000000..805b67c Binary files /dev/null and b/docs/public/components/breadcrumb-hero-light.webp differ diff --git a/docs/public/components/breadcrumb-light.webp b/docs/public/components/breadcrumb-light.webp new file mode 100644 index 0000000..0b1d582 Binary files /dev/null and b/docs/public/components/breadcrumb-light.webp differ diff --git a/docs/public/components/bubble-dark.webp b/docs/public/components/bubble-dark.webp new file mode 100644 index 0000000..5bc2eb1 Binary files /dev/null and b/docs/public/components/bubble-dark.webp differ diff --git a/docs/public/components/bubble-hero-dark.webp b/docs/public/components/bubble-hero-dark.webp new file mode 100644 index 0000000..1992867 Binary files /dev/null and b/docs/public/components/bubble-hero-dark.webp differ diff --git a/docs/public/components/bubble-hero-light.webp b/docs/public/components/bubble-hero-light.webp new file mode 100644 index 0000000..ffa92b8 Binary files /dev/null and b/docs/public/components/bubble-hero-light.webp differ diff --git a/docs/public/components/bubble-light.webp b/docs/public/components/bubble-light.webp new file mode 100644 index 0000000..7477806 Binary files /dev/null and b/docs/public/components/bubble-light.webp differ diff --git a/docs/public/components/button-dark.webp b/docs/public/components/button-dark.webp new file mode 100644 index 0000000..7050b2c Binary files /dev/null and b/docs/public/components/button-dark.webp differ diff --git a/docs/public/components/button-group-dark.webp b/docs/public/components/button-group-dark.webp new file mode 100644 index 0000000..963892b Binary files /dev/null and b/docs/public/components/button-group-dark.webp differ diff --git a/docs/public/components/button-group-hero-dark.webp b/docs/public/components/button-group-hero-dark.webp new file mode 100644 index 0000000..23253bb Binary files /dev/null and b/docs/public/components/button-group-hero-dark.webp differ diff --git a/docs/public/components/button-group-hero-light.webp b/docs/public/components/button-group-hero-light.webp new file mode 100644 index 0000000..87caf3e Binary files /dev/null and b/docs/public/components/button-group-hero-light.webp differ diff --git a/docs/public/components/button-group-light.webp b/docs/public/components/button-group-light.webp new file mode 100644 index 0000000..8470261 Binary files /dev/null and b/docs/public/components/button-group-light.webp differ diff --git a/docs/public/components/button-hero-dark.webp b/docs/public/components/button-hero-dark.webp new file mode 100644 index 0000000..aa142f9 Binary files /dev/null and b/docs/public/components/button-hero-dark.webp differ diff --git a/docs/public/components/button-hero-light.webp b/docs/public/components/button-hero-light.webp new file mode 100644 index 0000000..5d24bf7 Binary files /dev/null and b/docs/public/components/button-hero-light.webp differ diff --git a/docs/public/components/button-icons-dark.webp b/docs/public/components/button-icons-dark.webp new file mode 100644 index 0000000..8dd0acb Binary files /dev/null and b/docs/public/components/button-icons-dark.webp differ diff --git a/docs/public/components/button-icons-light.webp b/docs/public/components/button-icons-light.webp new file mode 100644 index 0000000..a54f73e Binary files /dev/null and b/docs/public/components/button-icons-light.webp differ diff --git a/docs/public/components/button-light.webp b/docs/public/components/button-light.webp new file mode 100644 index 0000000..6122495 Binary files /dev/null and b/docs/public/components/button-light.webp differ diff --git a/docs/public/components/button-sizes-dark.webp b/docs/public/components/button-sizes-dark.webp new file mode 100644 index 0000000..5c65e43 Binary files /dev/null and b/docs/public/components/button-sizes-dark.webp differ diff --git a/docs/public/components/button-sizes-light.webp b/docs/public/components/button-sizes-light.webp new file mode 100644 index 0000000..67ac7b6 Binary files /dev/null and b/docs/public/components/button-sizes-light.webp differ diff --git a/docs/public/components/button-states-dark.webp b/docs/public/components/button-states-dark.webp new file mode 100644 index 0000000..fb968b3 Binary files /dev/null and b/docs/public/components/button-states-dark.webp differ diff --git a/docs/public/components/button-states-light.webp b/docs/public/components/button-states-light.webp new file mode 100644 index 0000000..7302c5f Binary files /dev/null and b/docs/public/components/button-states-light.webp differ diff --git a/docs/public/components/card-dark.webp b/docs/public/components/card-dark.webp new file mode 100644 index 0000000..21525f9 Binary files /dev/null and b/docs/public/components/card-dark.webp differ diff --git a/docs/public/components/card-hero-dark.webp b/docs/public/components/card-hero-dark.webp new file mode 100644 index 0000000..8eb4f39 Binary files /dev/null and b/docs/public/components/card-hero-dark.webp differ diff --git a/docs/public/components/card-hero-light.webp b/docs/public/components/card-hero-light.webp new file mode 100644 index 0000000..83d1df3 Binary files /dev/null and b/docs/public/components/card-hero-light.webp differ diff --git a/docs/public/components/card-light.webp b/docs/public/components/card-light.webp new file mode 100644 index 0000000..1c11311 Binary files /dev/null and b/docs/public/components/card-light.webp differ diff --git a/docs/public/components/chart-area-dark.webp b/docs/public/components/chart-area-dark.webp new file mode 100644 index 0000000..41fa934 Binary files /dev/null and b/docs/public/components/chart-area-dark.webp differ diff --git a/docs/public/components/chart-area-light.webp b/docs/public/components/chart-area-light.webp new file mode 100644 index 0000000..a0d6b68 Binary files /dev/null and b/docs/public/components/chart-area-light.webp differ diff --git a/docs/public/components/chart-bar-dark.webp b/docs/public/components/chart-bar-dark.webp new file mode 100644 index 0000000..0bc505f Binary files /dev/null and b/docs/public/components/chart-bar-dark.webp differ diff --git a/docs/public/components/chart-bar-light.webp b/docs/public/components/chart-bar-light.webp new file mode 100644 index 0000000..8cdb30c Binary files /dev/null and b/docs/public/components/chart-bar-light.webp differ diff --git a/docs/public/components/chart-dark.webp b/docs/public/components/chart-dark.webp new file mode 100644 index 0000000..4f41480 Binary files /dev/null and b/docs/public/components/chart-dark.webp differ diff --git a/docs/public/components/chart-hero-dark.webp b/docs/public/components/chart-hero-dark.webp new file mode 100644 index 0000000..9fb27c9 Binary files /dev/null and b/docs/public/components/chart-hero-dark.webp differ diff --git a/docs/public/components/chart-hero-light.webp b/docs/public/components/chart-hero-light.webp new file mode 100644 index 0000000..4efd641 Binary files /dev/null and b/docs/public/components/chart-hero-light.webp differ diff --git a/docs/public/components/chart-light.webp b/docs/public/components/chart-light.webp new file mode 100644 index 0000000..0be6b1a Binary files /dev/null and b/docs/public/components/chart-light.webp differ diff --git a/docs/public/components/checkbox-dark.webp b/docs/public/components/checkbox-dark.webp new file mode 100644 index 0000000..7cd6e0e Binary files /dev/null and b/docs/public/components/checkbox-dark.webp differ diff --git a/docs/public/components/checkbox-hero-dark.webp b/docs/public/components/checkbox-hero-dark.webp new file mode 100644 index 0000000..11f686d Binary files /dev/null and b/docs/public/components/checkbox-hero-dark.webp differ diff --git a/docs/public/components/checkbox-hero-light.webp b/docs/public/components/checkbox-hero-light.webp new file mode 100644 index 0000000..6bc1260 Binary files /dev/null and b/docs/public/components/checkbox-hero-light.webp differ diff --git a/docs/public/components/checkbox-light.webp b/docs/public/components/checkbox-light.webp new file mode 100644 index 0000000..b3eb9b1 Binary files /dev/null and b/docs/public/components/checkbox-light.webp differ diff --git a/docs/public/components/combobox-dark.webp b/docs/public/components/combobox-dark.webp new file mode 100644 index 0000000..d1a5dd5 Binary files /dev/null and b/docs/public/components/combobox-dark.webp differ diff --git a/docs/public/components/combobox-hero-dark.webp b/docs/public/components/combobox-hero-dark.webp new file mode 100644 index 0000000..70455ce Binary files /dev/null and b/docs/public/components/combobox-hero-dark.webp differ diff --git a/docs/public/components/combobox-hero-light.webp b/docs/public/components/combobox-hero-light.webp new file mode 100644 index 0000000..8deda9b Binary files /dev/null and b/docs/public/components/combobox-hero-light.webp differ diff --git a/docs/public/components/combobox-light.webp b/docs/public/components/combobox-light.webp new file mode 100644 index 0000000..26bf328 Binary files /dev/null and b/docs/public/components/combobox-light.webp differ diff --git a/docs/public/components/dialog-dark.webp b/docs/public/components/dialog-dark.webp new file mode 100644 index 0000000..1ef0ac7 Binary files /dev/null and b/docs/public/components/dialog-dark.webp differ diff --git a/docs/public/components/dialog-hero-dark.webp b/docs/public/components/dialog-hero-dark.webp new file mode 100644 index 0000000..964ffba Binary files /dev/null and b/docs/public/components/dialog-hero-dark.webp differ diff --git a/docs/public/components/dialog-hero-light.webp b/docs/public/components/dialog-hero-light.webp new file mode 100644 index 0000000..6ded088 Binary files /dev/null and b/docs/public/components/dialog-hero-light.webp differ diff --git a/docs/public/components/dialog-light.webp b/docs/public/components/dialog-light.webp new file mode 100644 index 0000000..2e496a5 Binary files /dev/null and b/docs/public/components/dialog-light.webp differ diff --git a/docs/public/components/drawer-dark.webp b/docs/public/components/drawer-dark.webp new file mode 100644 index 0000000..1933c2e Binary files /dev/null and b/docs/public/components/drawer-dark.webp differ diff --git a/docs/public/components/drawer-hero-dark.webp b/docs/public/components/drawer-hero-dark.webp new file mode 100644 index 0000000..62dd5b3 Binary files /dev/null and b/docs/public/components/drawer-hero-dark.webp differ diff --git a/docs/public/components/drawer-hero-light.webp b/docs/public/components/drawer-hero-light.webp new file mode 100644 index 0000000..8a0ae2e Binary files /dev/null and b/docs/public/components/drawer-hero-light.webp differ diff --git a/docs/public/components/drawer-light.webp b/docs/public/components/drawer-light.webp new file mode 100644 index 0000000..ff044a5 Binary files /dev/null and b/docs/public/components/drawer-light.webp differ diff --git a/docs/public/components/dropdown-menu-dark.webp b/docs/public/components/dropdown-menu-dark.webp new file mode 100644 index 0000000..389b820 Binary files /dev/null and b/docs/public/components/dropdown-menu-dark.webp differ diff --git a/docs/public/components/dropdown-menu-hero-dark.webp b/docs/public/components/dropdown-menu-hero-dark.webp new file mode 100644 index 0000000..9d701b7 Binary files /dev/null and b/docs/public/components/dropdown-menu-hero-dark.webp differ diff --git a/docs/public/components/dropdown-menu-hero-light.webp b/docs/public/components/dropdown-menu-hero-light.webp new file mode 100644 index 0000000..83f7c2b Binary files /dev/null and b/docs/public/components/dropdown-menu-hero-light.webp differ diff --git a/docs/public/components/dropdown-menu-light.webp b/docs/public/components/dropdown-menu-light.webp new file mode 100644 index 0000000..929c2ca Binary files /dev/null and b/docs/public/components/dropdown-menu-light.webp differ diff --git a/docs/public/components/icon-dark.webp b/docs/public/components/icon-dark.webp new file mode 100644 index 0000000..d856b1c Binary files /dev/null and b/docs/public/components/icon-dark.webp differ diff --git a/docs/public/components/icon-hero-dark.webp b/docs/public/components/icon-hero-dark.webp new file mode 100644 index 0000000..a5a2214 Binary files /dev/null and b/docs/public/components/icon-hero-dark.webp differ diff --git a/docs/public/components/icon-hero-light.webp b/docs/public/components/icon-hero-light.webp new file mode 100644 index 0000000..a5a9bf7 Binary files /dev/null and b/docs/public/components/icon-hero-light.webp differ diff --git a/docs/public/components/icon-light.webp b/docs/public/components/icon-light.webp new file mode 100644 index 0000000..746f1f0 Binary files /dev/null and b/docs/public/components/icon-light.webp differ diff --git a/docs/public/components/icons/alert-dark.webp b/docs/public/components/icons/alert-dark.webp new file mode 100644 index 0000000..5240284 Binary files /dev/null and b/docs/public/components/icons/alert-dark.webp differ diff --git a/docs/public/components/icons/alert-light.webp b/docs/public/components/icons/alert-light.webp new file mode 100644 index 0000000..49d6657 Binary files /dev/null and b/docs/public/components/icons/alert-light.webp differ diff --git a/docs/public/components/icons/archive-dark.webp b/docs/public/components/icons/archive-dark.webp new file mode 100644 index 0000000..1e36d1d Binary files /dev/null and b/docs/public/components/icons/archive-dark.webp differ diff --git a/docs/public/components/icons/archive-light.webp b/docs/public/components/icons/archive-light.webp new file mode 100644 index 0000000..1cecaeb Binary files /dev/null and b/docs/public/components/icons/archive-light.webp differ diff --git a/docs/public/components/icons/arrow-down-dark.webp b/docs/public/components/icons/arrow-down-dark.webp new file mode 100644 index 0000000..e67ce52 Binary files /dev/null and b/docs/public/components/icons/arrow-down-dark.webp differ diff --git a/docs/public/components/icons/arrow-down-light.webp b/docs/public/components/icons/arrow-down-light.webp new file mode 100644 index 0000000..547be2b Binary files /dev/null and b/docs/public/components/icons/arrow-down-light.webp differ diff --git a/docs/public/components/icons/arrow-right-dark.webp b/docs/public/components/icons/arrow-right-dark.webp new file mode 100644 index 0000000..0e0a672 Binary files /dev/null and b/docs/public/components/icons/arrow-right-dark.webp differ diff --git a/docs/public/components/icons/arrow-right-light.webp b/docs/public/components/icons/arrow-right-light.webp new file mode 100644 index 0000000..5861107 Binary files /dev/null and b/docs/public/components/icons/arrow-right-light.webp differ diff --git a/docs/public/components/icons/arrow-up-dark.webp b/docs/public/components/icons/arrow-up-dark.webp new file mode 100644 index 0000000..cf515dc Binary files /dev/null and b/docs/public/components/icons/arrow-up-dark.webp differ diff --git a/docs/public/components/icons/arrow-up-light.webp b/docs/public/components/icons/arrow-up-light.webp new file mode 100644 index 0000000..832e6c8 Binary files /dev/null and b/docs/public/components/icons/arrow-up-light.webp differ diff --git a/docs/public/components/icons/check-circle-dark.webp b/docs/public/components/icons/check-circle-dark.webp new file mode 100644 index 0000000..4a50d86 Binary files /dev/null and b/docs/public/components/icons/check-circle-dark.webp differ diff --git a/docs/public/components/icons/check-circle-light.webp b/docs/public/components/icons/check-circle-light.webp new file mode 100644 index 0000000..c23bd5e Binary files /dev/null and b/docs/public/components/icons/check-circle-light.webp differ diff --git a/docs/public/components/icons/check-dark.webp b/docs/public/components/icons/check-dark.webp new file mode 100644 index 0000000..20f5f22 Binary files /dev/null and b/docs/public/components/icons/check-dark.webp differ diff --git a/docs/public/components/icons/check-light.webp b/docs/public/components/icons/check-light.webp new file mode 100644 index 0000000..9591b9b Binary files /dev/null and b/docs/public/components/icons/check-light.webp differ diff --git a/docs/public/components/icons/chevron-down-dark.webp b/docs/public/components/icons/chevron-down-dark.webp new file mode 100644 index 0000000..3122e16 Binary files /dev/null and b/docs/public/components/icons/chevron-down-dark.webp differ diff --git a/docs/public/components/icons/chevron-down-light.webp b/docs/public/components/icons/chevron-down-light.webp new file mode 100644 index 0000000..d7c1712 Binary files /dev/null and b/docs/public/components/icons/chevron-down-light.webp differ diff --git a/docs/public/components/icons/chevron-left-dark.webp b/docs/public/components/icons/chevron-left-dark.webp new file mode 100644 index 0000000..0e90dd8 Binary files /dev/null and b/docs/public/components/icons/chevron-left-dark.webp differ diff --git a/docs/public/components/icons/chevron-left-light.webp b/docs/public/components/icons/chevron-left-light.webp new file mode 100644 index 0000000..6c58ece Binary files /dev/null and b/docs/public/components/icons/chevron-left-light.webp differ diff --git a/docs/public/components/icons/chevron-right-dark.webp b/docs/public/components/icons/chevron-right-dark.webp new file mode 100644 index 0000000..69cdcda Binary files /dev/null and b/docs/public/components/icons/chevron-right-dark.webp differ diff --git a/docs/public/components/icons/chevron-right-light.webp b/docs/public/components/icons/chevron-right-light.webp new file mode 100644 index 0000000..850ada3 Binary files /dev/null and b/docs/public/components/icons/chevron-right-light.webp differ diff --git a/docs/public/components/icons/chevron-up-dark.webp b/docs/public/components/icons/chevron-up-dark.webp new file mode 100644 index 0000000..d8bca06 Binary files /dev/null and b/docs/public/components/icons/chevron-up-dark.webp differ diff --git a/docs/public/components/icons/chevron-up-light.webp b/docs/public/components/icons/chevron-up-light.webp new file mode 100644 index 0000000..f22b9a1 Binary files /dev/null and b/docs/public/components/icons/chevron-up-light.webp differ diff --git a/docs/public/components/icons/circle-dot-dark.webp b/docs/public/components/icons/circle-dot-dark.webp new file mode 100644 index 0000000..1601005 Binary files /dev/null and b/docs/public/components/icons/circle-dot-dark.webp differ diff --git a/docs/public/components/icons/circle-dot-light.webp b/docs/public/components/icons/circle-dot-light.webp new file mode 100644 index 0000000..78f9a66 Binary files /dev/null and b/docs/public/components/icons/circle-dot-light.webp differ diff --git a/docs/public/components/icons/clock-dark.webp b/docs/public/components/icons/clock-dark.webp new file mode 100644 index 0000000..00fa37a Binary files /dev/null and b/docs/public/components/icons/clock-dark.webp differ diff --git a/docs/public/components/icons/clock-light.webp b/docs/public/components/icons/clock-light.webp new file mode 100644 index 0000000..ac40d37 Binary files /dev/null and b/docs/public/components/icons/clock-light.webp differ diff --git a/docs/public/components/icons/copy-dark.webp b/docs/public/components/icons/copy-dark.webp new file mode 100644 index 0000000..1f4f6f2 Binary files /dev/null and b/docs/public/components/icons/copy-dark.webp differ diff --git a/docs/public/components/icons/copy-light.webp b/docs/public/components/icons/copy-light.webp new file mode 100644 index 0000000..51bb219 Binary files /dev/null and b/docs/public/components/icons/copy-light.webp differ diff --git a/docs/public/components/icons/download-dark.webp b/docs/public/components/icons/download-dark.webp new file mode 100644 index 0000000..0998bf6 Binary files /dev/null and b/docs/public/components/icons/download-dark.webp differ diff --git a/docs/public/components/icons/download-light.webp b/docs/public/components/icons/download-light.webp new file mode 100644 index 0000000..11f6e46 Binary files /dev/null and b/docs/public/components/icons/download-light.webp differ diff --git a/docs/public/components/icons/edit-dark.webp b/docs/public/components/icons/edit-dark.webp new file mode 100644 index 0000000..83dfe12 Binary files /dev/null and b/docs/public/components/icons/edit-dark.webp differ diff --git a/docs/public/components/icons/edit-light.webp b/docs/public/components/icons/edit-light.webp new file mode 100644 index 0000000..9ca9f6a Binary files /dev/null and b/docs/public/components/icons/edit-light.webp differ diff --git a/docs/public/components/icons/ellipsis-dark.webp b/docs/public/components/icons/ellipsis-dark.webp new file mode 100644 index 0000000..cf88daf Binary files /dev/null and b/docs/public/components/icons/ellipsis-dark.webp differ diff --git a/docs/public/components/icons/ellipsis-light.webp b/docs/public/components/icons/ellipsis-light.webp new file mode 100644 index 0000000..fc8cff6 Binary files /dev/null and b/docs/public/components/icons/ellipsis-light.webp differ diff --git a/docs/public/components/icons/external-link-dark.webp b/docs/public/components/icons/external-link-dark.webp new file mode 100644 index 0000000..d0f274b Binary files /dev/null and b/docs/public/components/icons/external-link-dark.webp differ diff --git a/docs/public/components/icons/external-link-light.webp b/docs/public/components/icons/external-link-light.webp new file mode 100644 index 0000000..78e99e6 Binary files /dev/null and b/docs/public/components/icons/external-link-light.webp differ diff --git a/docs/public/components/icons/eye-dark.webp b/docs/public/components/icons/eye-dark.webp new file mode 100644 index 0000000..75e4ab8 Binary files /dev/null and b/docs/public/components/icons/eye-dark.webp differ diff --git a/docs/public/components/icons/eye-light.webp b/docs/public/components/icons/eye-light.webp new file mode 100644 index 0000000..e1a0cea Binary files /dev/null and b/docs/public/components/icons/eye-light.webp differ diff --git a/docs/public/components/icons/file-text-dark.webp b/docs/public/components/icons/file-text-dark.webp new file mode 100644 index 0000000..17831c7 Binary files /dev/null and b/docs/public/components/icons/file-text-dark.webp differ diff --git a/docs/public/components/icons/file-text-light.webp b/docs/public/components/icons/file-text-light.webp new file mode 100644 index 0000000..3061f12 Binary files /dev/null and b/docs/public/components/icons/file-text-light.webp differ diff --git a/docs/public/components/icons/folder-dark.webp b/docs/public/components/icons/folder-dark.webp new file mode 100644 index 0000000..c65ee85 Binary files /dev/null and b/docs/public/components/icons/folder-dark.webp differ diff --git a/docs/public/components/icons/folder-light.webp b/docs/public/components/icons/folder-light.webp new file mode 100644 index 0000000..3206a7e Binary files /dev/null and b/docs/public/components/icons/folder-light.webp differ diff --git a/docs/public/components/icons/folder-open-dark.webp b/docs/public/components/icons/folder-open-dark.webp new file mode 100644 index 0000000..f56ba07 Binary files /dev/null and b/docs/public/components/icons/folder-open-dark.webp differ diff --git a/docs/public/components/icons/folder-open-light.webp b/docs/public/components/icons/folder-open-light.webp new file mode 100644 index 0000000..bd2ea1a Binary files /dev/null and b/docs/public/components/icons/folder-open-light.webp differ diff --git a/docs/public/components/icons/git-branch-dark.webp b/docs/public/components/icons/git-branch-dark.webp new file mode 100644 index 0000000..01d9a4d Binary files /dev/null and b/docs/public/components/icons/git-branch-dark.webp differ diff --git a/docs/public/components/icons/git-branch-light.webp b/docs/public/components/icons/git-branch-light.webp new file mode 100644 index 0000000..e2b404a Binary files /dev/null and b/docs/public/components/icons/git-branch-light.webp differ diff --git a/docs/public/components/icons/git-merge-dark.webp b/docs/public/components/icons/git-merge-dark.webp new file mode 100644 index 0000000..d11a799 Binary files /dev/null and b/docs/public/components/icons/git-merge-dark.webp differ diff --git a/docs/public/components/icons/git-merge-light.webp b/docs/public/components/icons/git-merge-light.webp new file mode 100644 index 0000000..504bff7 Binary files /dev/null and b/docs/public/components/icons/git-merge-light.webp differ diff --git a/docs/public/components/icons/git-pull-request-dark.webp b/docs/public/components/icons/git-pull-request-dark.webp new file mode 100644 index 0000000..8b9072f Binary files /dev/null and b/docs/public/components/icons/git-pull-request-dark.webp differ diff --git a/docs/public/components/icons/git-pull-request-light.webp b/docs/public/components/icons/git-pull-request-light.webp new file mode 100644 index 0000000..6f939eb Binary files /dev/null and b/docs/public/components/icons/git-pull-request-light.webp differ diff --git a/docs/public/components/icons/info-dark.webp b/docs/public/components/icons/info-dark.webp new file mode 100644 index 0000000..97afc78 Binary files /dev/null and b/docs/public/components/icons/info-dark.webp differ diff --git a/docs/public/components/icons/info-light.webp b/docs/public/components/icons/info-light.webp new file mode 100644 index 0000000..ff54baa Binary files /dev/null and b/docs/public/components/icons/info-light.webp differ diff --git a/docs/public/components/icons/menu-dark.webp b/docs/public/components/icons/menu-dark.webp new file mode 100644 index 0000000..e3617cd Binary files /dev/null and b/docs/public/components/icons/menu-dark.webp differ diff --git a/docs/public/components/icons/menu-light.webp b/docs/public/components/icons/menu-light.webp new file mode 100644 index 0000000..eeb2b83 Binary files /dev/null and b/docs/public/components/icons/menu-light.webp differ diff --git a/docs/public/components/icons/moon-dark.webp b/docs/public/components/icons/moon-dark.webp new file mode 100644 index 0000000..d7ad0ca Binary files /dev/null and b/docs/public/components/icons/moon-dark.webp differ diff --git a/docs/public/components/icons/moon-light.webp b/docs/public/components/icons/moon-light.webp new file mode 100644 index 0000000..461f11d Binary files /dev/null and b/docs/public/components/icons/moon-light.webp differ diff --git a/docs/public/components/icons/music-dark.webp b/docs/public/components/icons/music-dark.webp new file mode 100644 index 0000000..8a8e57f Binary files /dev/null and b/docs/public/components/icons/music-dark.webp differ diff --git a/docs/public/components/icons/music-light.webp b/docs/public/components/icons/music-light.webp new file mode 100644 index 0000000..0f25436 Binary files /dev/null and b/docs/public/components/icons/music-light.webp differ diff --git a/docs/public/components/icons/panel-left-dark.webp b/docs/public/components/icons/panel-left-dark.webp new file mode 100644 index 0000000..99a4c22 Binary files /dev/null and b/docs/public/components/icons/panel-left-dark.webp differ diff --git a/docs/public/components/icons/panel-left-light.webp b/docs/public/components/icons/panel-left-light.webp new file mode 100644 index 0000000..4c5b2d5 Binary files /dev/null and b/docs/public/components/icons/panel-left-light.webp differ diff --git a/docs/public/components/icons/panel-right-dark.webp b/docs/public/components/icons/panel-right-dark.webp new file mode 100644 index 0000000..0634230 Binary files /dev/null and b/docs/public/components/icons/panel-right-dark.webp differ diff --git a/docs/public/components/icons/panel-right-light.webp b/docs/public/components/icons/panel-right-light.webp new file mode 100644 index 0000000..804b562 Binary files /dev/null and b/docs/public/components/icons/panel-right-light.webp differ diff --git a/docs/public/components/icons/pause-dark.webp b/docs/public/components/icons/pause-dark.webp new file mode 100644 index 0000000..c7981a1 Binary files /dev/null and b/docs/public/components/icons/pause-dark.webp differ diff --git a/docs/public/components/icons/pause-light.webp b/docs/public/components/icons/pause-light.webp new file mode 100644 index 0000000..00468b9 Binary files /dev/null and b/docs/public/components/icons/pause-light.webp differ diff --git a/docs/public/components/icons/play-dark.webp b/docs/public/components/icons/play-dark.webp new file mode 100644 index 0000000..70888ae Binary files /dev/null and b/docs/public/components/icons/play-dark.webp differ diff --git a/docs/public/components/icons/play-light.webp b/docs/public/components/icons/play-light.webp new file mode 100644 index 0000000..52723e9 Binary files /dev/null and b/docs/public/components/icons/play-light.webp differ diff --git a/docs/public/components/icons/plus-dark.webp b/docs/public/components/icons/plus-dark.webp new file mode 100644 index 0000000..dcb6c3f Binary files /dev/null and b/docs/public/components/icons/plus-dark.webp differ diff --git a/docs/public/components/icons/plus-light.webp b/docs/public/components/icons/plus-light.webp new file mode 100644 index 0000000..0d045ed Binary files /dev/null and b/docs/public/components/icons/plus-light.webp differ diff --git a/docs/public/components/icons/refresh-cw-dark.webp b/docs/public/components/icons/refresh-cw-dark.webp new file mode 100644 index 0000000..edb0ffa Binary files /dev/null and b/docs/public/components/icons/refresh-cw-dark.webp differ diff --git a/docs/public/components/icons/refresh-cw-light.webp b/docs/public/components/icons/refresh-cw-light.webp new file mode 100644 index 0000000..2dc389a Binary files /dev/null and b/docs/public/components/icons/refresh-cw-light.webp differ diff --git a/docs/public/components/icons/repeat-dark.webp b/docs/public/components/icons/repeat-dark.webp new file mode 100644 index 0000000..c42d568 Binary files /dev/null and b/docs/public/components/icons/repeat-dark.webp differ diff --git a/docs/public/components/icons/repeat-light.webp b/docs/public/components/icons/repeat-light.webp new file mode 100644 index 0000000..cbdd1f5 Binary files /dev/null and b/docs/public/components/icons/repeat-light.webp differ diff --git a/docs/public/components/icons/save-dark.webp b/docs/public/components/icons/save-dark.webp new file mode 100644 index 0000000..bd44a93 Binary files /dev/null and b/docs/public/components/icons/save-dark.webp differ diff --git a/docs/public/components/icons/save-light.webp b/docs/public/components/icons/save-light.webp new file mode 100644 index 0000000..d159e96 Binary files /dev/null and b/docs/public/components/icons/save-light.webp differ diff --git a/docs/public/components/icons/search-dark.webp b/docs/public/components/icons/search-dark.webp new file mode 100644 index 0000000..115827d Binary files /dev/null and b/docs/public/components/icons/search-dark.webp differ diff --git a/docs/public/components/icons/search-light.webp b/docs/public/components/icons/search-light.webp new file mode 100644 index 0000000..0797e37 Binary files /dev/null and b/docs/public/components/icons/search-light.webp differ diff --git a/docs/public/components/icons/send-dark.webp b/docs/public/components/icons/send-dark.webp new file mode 100644 index 0000000..0615ce8 Binary files /dev/null and b/docs/public/components/icons/send-dark.webp differ diff --git a/docs/public/components/icons/send-light.webp b/docs/public/components/icons/send-light.webp new file mode 100644 index 0000000..9c0945a Binary files /dev/null and b/docs/public/components/icons/send-light.webp differ diff --git a/docs/public/components/icons/settings-dark.webp b/docs/public/components/icons/settings-dark.webp new file mode 100644 index 0000000..f975033 Binary files /dev/null and b/docs/public/components/icons/settings-dark.webp differ diff --git a/docs/public/components/icons/settings-light.webp b/docs/public/components/icons/settings-light.webp new file mode 100644 index 0000000..df3a0b7 Binary files /dev/null and b/docs/public/components/icons/settings-light.webp differ diff --git a/docs/public/components/icons/shuffle-dark.webp b/docs/public/components/icons/shuffle-dark.webp new file mode 100644 index 0000000..ef177f7 Binary files /dev/null and b/docs/public/components/icons/shuffle-dark.webp differ diff --git a/docs/public/components/icons/shuffle-light.webp b/docs/public/components/icons/shuffle-light.webp new file mode 100644 index 0000000..1f33dbe Binary files /dev/null and b/docs/public/components/icons/shuffle-light.webp differ diff --git a/docs/public/components/icons/skip-back-dark.webp b/docs/public/components/icons/skip-back-dark.webp new file mode 100644 index 0000000..fac00ef Binary files /dev/null and b/docs/public/components/icons/skip-back-dark.webp differ diff --git a/docs/public/components/icons/skip-back-light.webp b/docs/public/components/icons/skip-back-light.webp new file mode 100644 index 0000000..f5a814f Binary files /dev/null and b/docs/public/components/icons/skip-back-light.webp differ diff --git a/docs/public/components/icons/skip-forward-dark.webp b/docs/public/components/icons/skip-forward-dark.webp new file mode 100644 index 0000000..39c1d54 Binary files /dev/null and b/docs/public/components/icons/skip-forward-dark.webp differ diff --git a/docs/public/components/icons/skip-forward-light.webp b/docs/public/components/icons/skip-forward-light.webp new file mode 100644 index 0000000..099ad0d Binary files /dev/null and b/docs/public/components/icons/skip-forward-light.webp differ diff --git a/docs/public/components/icons/sun-dark.webp b/docs/public/components/icons/sun-dark.webp new file mode 100644 index 0000000..45448c6 Binary files /dev/null and b/docs/public/components/icons/sun-dark.webp differ diff --git a/docs/public/components/icons/sun-light.webp b/docs/public/components/icons/sun-light.webp new file mode 100644 index 0000000..468d5b2 Binary files /dev/null and b/docs/public/components/icons/sun-light.webp differ diff --git a/docs/public/components/icons/terminal-dark.webp b/docs/public/components/icons/terminal-dark.webp new file mode 100644 index 0000000..687cee3 Binary files /dev/null and b/docs/public/components/icons/terminal-dark.webp differ diff --git a/docs/public/components/icons/terminal-light.webp b/docs/public/components/icons/terminal-light.webp new file mode 100644 index 0000000..89d3ef5 Binary files /dev/null and b/docs/public/components/icons/terminal-light.webp differ diff --git a/docs/public/components/icons/trash-dark.webp b/docs/public/components/icons/trash-dark.webp new file mode 100644 index 0000000..b19f0e9 Binary files /dev/null and b/docs/public/components/icons/trash-dark.webp differ diff --git a/docs/public/components/icons/trash-light.webp b/docs/public/components/icons/trash-light.webp new file mode 100644 index 0000000..a6fd4ee Binary files /dev/null and b/docs/public/components/icons/trash-light.webp differ diff --git a/docs/public/components/icons/volume-dark.webp b/docs/public/components/icons/volume-dark.webp new file mode 100644 index 0000000..ee2ff00 Binary files /dev/null and b/docs/public/components/icons/volume-dark.webp differ diff --git a/docs/public/components/icons/volume-light.webp b/docs/public/components/icons/volume-light.webp new file mode 100644 index 0000000..311141f Binary files /dev/null and b/docs/public/components/icons/volume-light.webp differ diff --git a/docs/public/components/icons/wrench-dark.webp b/docs/public/components/icons/wrench-dark.webp new file mode 100644 index 0000000..299cd1f Binary files /dev/null and b/docs/public/components/icons/wrench-dark.webp differ diff --git a/docs/public/components/icons/wrench-light.webp b/docs/public/components/icons/wrench-light.webp new file mode 100644 index 0000000..819e69a Binary files /dev/null and b/docs/public/components/icons/wrench-light.webp differ diff --git a/docs/public/components/icons/x-circle-dark.webp b/docs/public/components/icons/x-circle-dark.webp new file mode 100644 index 0000000..18dc5ff Binary files /dev/null and b/docs/public/components/icons/x-circle-dark.webp differ diff --git a/docs/public/components/icons/x-circle-light.webp b/docs/public/components/icons/x-circle-light.webp new file mode 100644 index 0000000..46eb626 Binary files /dev/null and b/docs/public/components/icons/x-circle-light.webp differ diff --git a/docs/public/components/icons/x-dark.webp b/docs/public/components/icons/x-dark.webp new file mode 100644 index 0000000..6727cf9 Binary files /dev/null and b/docs/public/components/icons/x-dark.webp differ diff --git a/docs/public/components/icons/x-light.webp b/docs/public/components/icons/x-light.webp new file mode 100644 index 0000000..964ed61 Binary files /dev/null and b/docs/public/components/icons/x-light.webp differ diff --git a/docs/public/components/input-dark.webp b/docs/public/components/input-dark.webp new file mode 100644 index 0000000..d9f9360 Binary files /dev/null and b/docs/public/components/input-dark.webp differ diff --git a/docs/public/components/input-group-dark.webp b/docs/public/components/input-group-dark.webp new file mode 100644 index 0000000..2100631 Binary files /dev/null and b/docs/public/components/input-group-dark.webp differ diff --git a/docs/public/components/input-group-hero-dark.webp b/docs/public/components/input-group-hero-dark.webp new file mode 100644 index 0000000..3b29959 Binary files /dev/null and b/docs/public/components/input-group-hero-dark.webp differ diff --git a/docs/public/components/input-group-hero-light.webp b/docs/public/components/input-group-hero-light.webp new file mode 100644 index 0000000..f286680 Binary files /dev/null and b/docs/public/components/input-group-hero-light.webp differ diff --git a/docs/public/components/input-group-light.webp b/docs/public/components/input-group-light.webp new file mode 100644 index 0000000..455fced Binary files /dev/null and b/docs/public/components/input-group-light.webp differ diff --git a/docs/public/components/input-hero-dark.webp b/docs/public/components/input-hero-dark.webp new file mode 100644 index 0000000..fa5729e Binary files /dev/null and b/docs/public/components/input-hero-dark.webp differ diff --git a/docs/public/components/input-hero-light.webp b/docs/public/components/input-hero-light.webp new file mode 100644 index 0000000..16444b0 Binary files /dev/null and b/docs/public/components/input-hero-light.webp differ diff --git a/docs/public/components/input-light.webp b/docs/public/components/input-light.webp new file mode 100644 index 0000000..7979eb3 Binary files /dev/null and b/docs/public/components/input-light.webp differ diff --git a/docs/public/components/list-dark.webp b/docs/public/components/list-dark.webp new file mode 100644 index 0000000..072d87e Binary files /dev/null and b/docs/public/components/list-dark.webp differ diff --git a/docs/public/components/list-hero-dark.webp b/docs/public/components/list-hero-dark.webp new file mode 100644 index 0000000..ae8b226 Binary files /dev/null and b/docs/public/components/list-hero-dark.webp differ diff --git a/docs/public/components/list-hero-light.webp b/docs/public/components/list-hero-light.webp new file mode 100644 index 0000000..6a5b201 Binary files /dev/null and b/docs/public/components/list-hero-light.webp differ diff --git a/docs/public/components/list-light.webp b/docs/public/components/list-light.webp new file mode 100644 index 0000000..450ec9b Binary files /dev/null and b/docs/public/components/list-light.webp differ diff --git a/docs/public/components/markdown-dark.webp b/docs/public/components/markdown-dark.webp new file mode 100644 index 0000000..709c681 Binary files /dev/null and b/docs/public/components/markdown-dark.webp differ diff --git a/docs/public/components/markdown-hero-dark.webp b/docs/public/components/markdown-hero-dark.webp new file mode 100644 index 0000000..781ba7d Binary files /dev/null and b/docs/public/components/markdown-hero-dark.webp differ diff --git a/docs/public/components/markdown-hero-light.webp b/docs/public/components/markdown-hero-light.webp new file mode 100644 index 0000000..9e00555 Binary files /dev/null and b/docs/public/components/markdown-hero-light.webp differ diff --git a/docs/public/components/markdown-light.webp b/docs/public/components/markdown-light.webp new file mode 100644 index 0000000..7ebfcf2 Binary files /dev/null and b/docs/public/components/markdown-light.webp differ diff --git a/docs/public/components/menu-dark.webp b/docs/public/components/menu-dark.webp new file mode 100644 index 0000000..091e560 Binary files /dev/null and b/docs/public/components/menu-dark.webp differ diff --git a/docs/public/components/menu-light.webp b/docs/public/components/menu-light.webp new file mode 100644 index 0000000..397e9ad Binary files /dev/null and b/docs/public/components/menu-light.webp differ diff --git a/docs/public/components/pagination-dark.webp b/docs/public/components/pagination-dark.webp new file mode 100644 index 0000000..b9f08b6 Binary files /dev/null and b/docs/public/components/pagination-dark.webp differ diff --git a/docs/public/components/pagination-hero-dark.webp b/docs/public/components/pagination-hero-dark.webp new file mode 100644 index 0000000..a3b295b Binary files /dev/null and b/docs/public/components/pagination-hero-dark.webp differ diff --git a/docs/public/components/pagination-hero-light.webp b/docs/public/components/pagination-hero-light.webp new file mode 100644 index 0000000..07ce172 Binary files /dev/null and b/docs/public/components/pagination-hero-light.webp differ diff --git a/docs/public/components/pagination-light.webp b/docs/public/components/pagination-light.webp new file mode 100644 index 0000000..f3f0be4 Binary files /dev/null and b/docs/public/components/pagination-light.webp differ diff --git a/docs/public/components/panel-dark.webp b/docs/public/components/panel-dark.webp new file mode 100644 index 0000000..fd77097 Binary files /dev/null and b/docs/public/components/panel-dark.webp differ diff --git a/docs/public/components/panel-hero-dark.webp b/docs/public/components/panel-hero-dark.webp new file mode 100644 index 0000000..b51d6b8 Binary files /dev/null and b/docs/public/components/panel-hero-dark.webp differ diff --git a/docs/public/components/panel-hero-light.webp b/docs/public/components/panel-hero-light.webp new file mode 100644 index 0000000..83ba445 Binary files /dev/null and b/docs/public/components/panel-hero-light.webp differ diff --git a/docs/public/components/panel-light.webp b/docs/public/components/panel-light.webp new file mode 100644 index 0000000..1584ac1 Binary files /dev/null and b/docs/public/components/panel-light.webp differ diff --git a/docs/public/components/progress-dark.webp b/docs/public/components/progress-dark.webp new file mode 100644 index 0000000..4047d1f Binary files /dev/null and b/docs/public/components/progress-dark.webp differ diff --git a/docs/public/components/progress-hero-dark.webp b/docs/public/components/progress-hero-dark.webp new file mode 100644 index 0000000..58c8b6c Binary files /dev/null and b/docs/public/components/progress-hero-dark.webp differ diff --git a/docs/public/components/progress-hero-light.webp b/docs/public/components/progress-hero-light.webp new file mode 100644 index 0000000..57a91e3 Binary files /dev/null and b/docs/public/components/progress-hero-light.webp differ diff --git a/docs/public/components/progress-light.webp b/docs/public/components/progress-light.webp new file mode 100644 index 0000000..2d92d10 Binary files /dev/null and b/docs/public/components/progress-light.webp differ diff --git a/docs/public/components/radio-group-dark.webp b/docs/public/components/radio-group-dark.webp new file mode 100644 index 0000000..5485d21 Binary files /dev/null and b/docs/public/components/radio-group-dark.webp differ diff --git a/docs/public/components/radio-group-light.webp b/docs/public/components/radio-group-light.webp new file mode 100644 index 0000000..05c2159 Binary files /dev/null and b/docs/public/components/radio-group-light.webp differ diff --git a/docs/public/components/radio-hero-dark.webp b/docs/public/components/radio-hero-dark.webp new file mode 100644 index 0000000..78e2396 Binary files /dev/null and b/docs/public/components/radio-hero-dark.webp differ diff --git a/docs/public/components/radio-hero-light.webp b/docs/public/components/radio-hero-light.webp new file mode 100644 index 0000000..759b575 Binary files /dev/null and b/docs/public/components/radio-hero-light.webp differ diff --git a/docs/public/components/resizable-dark.webp b/docs/public/components/resizable-dark.webp new file mode 100644 index 0000000..7565a70 Binary files /dev/null and b/docs/public/components/resizable-dark.webp differ diff --git a/docs/public/components/resizable-hero-dark.webp b/docs/public/components/resizable-hero-dark.webp new file mode 100644 index 0000000..15cc403 Binary files /dev/null and b/docs/public/components/resizable-hero-dark.webp differ diff --git a/docs/public/components/resizable-hero-light.webp b/docs/public/components/resizable-hero-light.webp new file mode 100644 index 0000000..5441af1 Binary files /dev/null and b/docs/public/components/resizable-hero-light.webp differ diff --git a/docs/public/components/resizable-light.webp b/docs/public/components/resizable-light.webp new file mode 100644 index 0000000..930ee48 Binary files /dev/null and b/docs/public/components/resizable-light.webp differ diff --git a/docs/public/components/scroll-dark.webp b/docs/public/components/scroll-dark.webp new file mode 100644 index 0000000..653aacc Binary files /dev/null and b/docs/public/components/scroll-dark.webp differ diff --git a/docs/public/components/scroll-hero-dark.webp b/docs/public/components/scroll-hero-dark.webp new file mode 100644 index 0000000..329be05 Binary files /dev/null and b/docs/public/components/scroll-hero-dark.webp differ diff --git a/docs/public/components/scroll-hero-light.webp b/docs/public/components/scroll-hero-light.webp new file mode 100644 index 0000000..ace5917 Binary files /dev/null and b/docs/public/components/scroll-hero-light.webp differ diff --git a/docs/public/components/scroll-light.webp b/docs/public/components/scroll-light.webp new file mode 100644 index 0000000..f716b4e Binary files /dev/null and b/docs/public/components/scroll-light.webp differ diff --git a/docs/public/components/search-field-dark.webp b/docs/public/components/search-field-dark.webp new file mode 100644 index 0000000..a217e6a Binary files /dev/null and b/docs/public/components/search-field-dark.webp differ diff --git a/docs/public/components/search-field-light.webp b/docs/public/components/search-field-light.webp new file mode 100644 index 0000000..f233b0e Binary files /dev/null and b/docs/public/components/search-field-light.webp differ diff --git a/docs/public/components/select-dark.webp b/docs/public/components/select-dark.webp new file mode 100644 index 0000000..6f5ffd2 Binary files /dev/null and b/docs/public/components/select-dark.webp differ diff --git a/docs/public/components/select-hero-dark.webp b/docs/public/components/select-hero-dark.webp new file mode 100644 index 0000000..588db45 Binary files /dev/null and b/docs/public/components/select-hero-dark.webp differ diff --git a/docs/public/components/select-hero-light.webp b/docs/public/components/select-hero-light.webp new file mode 100644 index 0000000..9a426c2 Binary files /dev/null and b/docs/public/components/select-hero-light.webp differ diff --git a/docs/public/components/select-light.webp b/docs/public/components/select-light.webp new file mode 100644 index 0000000..8f031a2 Binary files /dev/null and b/docs/public/components/select-light.webp differ diff --git a/docs/public/components/separator-dark.webp b/docs/public/components/separator-dark.webp new file mode 100644 index 0000000..2d575ea Binary files /dev/null and b/docs/public/components/separator-dark.webp differ diff --git a/docs/public/components/separator-hero-dark.webp b/docs/public/components/separator-hero-dark.webp new file mode 100644 index 0000000..c06a56c Binary files /dev/null and b/docs/public/components/separator-hero-dark.webp differ diff --git a/docs/public/components/separator-hero-light.webp b/docs/public/components/separator-hero-light.webp new file mode 100644 index 0000000..f6794cb Binary files /dev/null and b/docs/public/components/separator-hero-light.webp differ diff --git a/docs/public/components/separator-light.webp b/docs/public/components/separator-light.webp new file mode 100644 index 0000000..2a6468f Binary files /dev/null and b/docs/public/components/separator-light.webp differ diff --git a/docs/public/components/sheet-dark.webp b/docs/public/components/sheet-dark.webp new file mode 100644 index 0000000..d9d10e6 Binary files /dev/null and b/docs/public/components/sheet-dark.webp differ diff --git a/docs/public/components/sheet-hero-dark.webp b/docs/public/components/sheet-hero-dark.webp new file mode 100644 index 0000000..2160c37 Binary files /dev/null and b/docs/public/components/sheet-hero-dark.webp differ diff --git a/docs/public/components/sheet-hero-light.webp b/docs/public/components/sheet-hero-light.webp new file mode 100644 index 0000000..95554fb Binary files /dev/null and b/docs/public/components/sheet-hero-light.webp differ diff --git a/docs/public/components/sheet-light.webp b/docs/public/components/sheet-light.webp new file mode 100644 index 0000000..415b130 Binary files /dev/null and b/docs/public/components/sheet-light.webp differ diff --git a/docs/public/components/skeleton-dark.webp b/docs/public/components/skeleton-dark.webp new file mode 100644 index 0000000..17de721 Binary files /dev/null and b/docs/public/components/skeleton-dark.webp differ diff --git a/docs/public/components/skeleton-hero-dark.webp b/docs/public/components/skeleton-hero-dark.webp new file mode 100644 index 0000000..927db2b Binary files /dev/null and b/docs/public/components/skeleton-hero-dark.webp differ diff --git a/docs/public/components/skeleton-hero-light.webp b/docs/public/components/skeleton-hero-light.webp new file mode 100644 index 0000000..fc3edc1 Binary files /dev/null and b/docs/public/components/skeleton-hero-light.webp differ diff --git a/docs/public/components/skeleton-light.webp b/docs/public/components/skeleton-light.webp new file mode 100644 index 0000000..8105f2f Binary files /dev/null and b/docs/public/components/skeleton-light.webp differ diff --git a/docs/public/components/slider-dark.webp b/docs/public/components/slider-dark.webp new file mode 100644 index 0000000..39b84e0 Binary files /dev/null and b/docs/public/components/slider-dark.webp differ diff --git a/docs/public/components/slider-hero-dark.webp b/docs/public/components/slider-hero-dark.webp new file mode 100644 index 0000000..6e04a40 Binary files /dev/null and b/docs/public/components/slider-hero-dark.webp differ diff --git a/docs/public/components/slider-hero-light.webp b/docs/public/components/slider-hero-light.webp new file mode 100644 index 0000000..27b0d8f Binary files /dev/null and b/docs/public/components/slider-hero-light.webp differ diff --git a/docs/public/components/slider-light.webp b/docs/public/components/slider-light.webp new file mode 100644 index 0000000..1573083 Binary files /dev/null and b/docs/public/components/slider-light.webp differ diff --git a/docs/public/components/spacer-dark.webp b/docs/public/components/spacer-dark.webp new file mode 100644 index 0000000..3eb0afa Binary files /dev/null and b/docs/public/components/spacer-dark.webp differ diff --git a/docs/public/components/spacer-hero-dark.webp b/docs/public/components/spacer-hero-dark.webp new file mode 100644 index 0000000..e99680e Binary files /dev/null and b/docs/public/components/spacer-hero-dark.webp differ diff --git a/docs/public/components/spacer-hero-light.webp b/docs/public/components/spacer-hero-light.webp new file mode 100644 index 0000000..a68aa93 Binary files /dev/null and b/docs/public/components/spacer-hero-light.webp differ diff --git a/docs/public/components/spacer-light.webp b/docs/public/components/spacer-light.webp new file mode 100644 index 0000000..507f346 Binary files /dev/null and b/docs/public/components/spacer-light.webp differ diff --git a/docs/public/components/spinner-dark.webp b/docs/public/components/spinner-dark.webp new file mode 100644 index 0000000..6452de2 Binary files /dev/null and b/docs/public/components/spinner-dark.webp differ diff --git a/docs/public/components/spinner-hero-dark.webp b/docs/public/components/spinner-hero-dark.webp new file mode 100644 index 0000000..d50b4d6 Binary files /dev/null and b/docs/public/components/spinner-hero-dark.webp differ diff --git a/docs/public/components/spinner-hero-light.webp b/docs/public/components/spinner-hero-light.webp new file mode 100644 index 0000000..d317300 Binary files /dev/null and b/docs/public/components/spinner-hero-light.webp differ diff --git a/docs/public/components/spinner-light.webp b/docs/public/components/spinner-light.webp new file mode 100644 index 0000000..cd8bae3 Binary files /dev/null and b/docs/public/components/spinner-light.webp differ diff --git a/docs/public/components/split-dark.webp b/docs/public/components/split-dark.webp new file mode 100644 index 0000000..2a9ee14 Binary files /dev/null and b/docs/public/components/split-dark.webp differ diff --git a/docs/public/components/split-hero-dark.webp b/docs/public/components/split-hero-dark.webp new file mode 100644 index 0000000..0d34c60 Binary files /dev/null and b/docs/public/components/split-hero-dark.webp differ diff --git a/docs/public/components/split-hero-light.webp b/docs/public/components/split-hero-light.webp new file mode 100644 index 0000000..c6c4b7c Binary files /dev/null and b/docs/public/components/split-hero-light.webp differ diff --git a/docs/public/components/split-light.webp b/docs/public/components/split-light.webp new file mode 100644 index 0000000..fba4d39 Binary files /dev/null and b/docs/public/components/split-light.webp differ diff --git a/docs/public/components/status-bar-dark.webp b/docs/public/components/status-bar-dark.webp new file mode 100644 index 0000000..2e79866 Binary files /dev/null and b/docs/public/components/status-bar-dark.webp differ diff --git a/docs/public/components/status-bar-hero-dark.webp b/docs/public/components/status-bar-hero-dark.webp new file mode 100644 index 0000000..039dd55 Binary files /dev/null and b/docs/public/components/status-bar-hero-dark.webp differ diff --git a/docs/public/components/status-bar-hero-light.webp b/docs/public/components/status-bar-hero-light.webp new file mode 100644 index 0000000..a01c9a0 Binary files /dev/null and b/docs/public/components/status-bar-hero-light.webp differ diff --git a/docs/public/components/status-bar-light.webp b/docs/public/components/status-bar-light.webp new file mode 100644 index 0000000..488a877 Binary files /dev/null and b/docs/public/components/status-bar-light.webp differ diff --git a/docs/public/components/stepper-dark.webp b/docs/public/components/stepper-dark.webp new file mode 100644 index 0000000..9e58f67 Binary files /dev/null and b/docs/public/components/stepper-dark.webp differ diff --git a/docs/public/components/stepper-hero-dark.webp b/docs/public/components/stepper-hero-dark.webp new file mode 100644 index 0000000..1701a2b Binary files /dev/null and b/docs/public/components/stepper-hero-dark.webp differ diff --git a/docs/public/components/stepper-hero-light.webp b/docs/public/components/stepper-hero-light.webp new file mode 100644 index 0000000..ce3a90f Binary files /dev/null and b/docs/public/components/stepper-hero-light.webp differ diff --git a/docs/public/components/stepper-light.webp b/docs/public/components/stepper-light.webp new file mode 100644 index 0000000..009b56b Binary files /dev/null and b/docs/public/components/stepper-light.webp differ diff --git a/docs/public/components/switch-dark.webp b/docs/public/components/switch-dark.webp new file mode 100644 index 0000000..0b7f540 Binary files /dev/null and b/docs/public/components/switch-dark.webp differ diff --git a/docs/public/components/switch-hero-dark.webp b/docs/public/components/switch-hero-dark.webp new file mode 100644 index 0000000..d5daa8a Binary files /dev/null and b/docs/public/components/switch-hero-dark.webp differ diff --git a/docs/public/components/switch-hero-light.webp b/docs/public/components/switch-hero-light.webp new file mode 100644 index 0000000..84059fd Binary files /dev/null and b/docs/public/components/switch-hero-light.webp differ diff --git a/docs/public/components/switch-light.webp b/docs/public/components/switch-light.webp new file mode 100644 index 0000000..ca1808e Binary files /dev/null and b/docs/public/components/switch-light.webp differ diff --git a/docs/public/components/table-dark.webp b/docs/public/components/table-dark.webp new file mode 100644 index 0000000..493bc9e Binary files /dev/null and b/docs/public/components/table-dark.webp differ diff --git a/docs/public/components/table-hero-dark.webp b/docs/public/components/table-hero-dark.webp new file mode 100644 index 0000000..182f254 Binary files /dev/null and b/docs/public/components/table-hero-dark.webp differ diff --git a/docs/public/components/table-hero-light.webp b/docs/public/components/table-hero-light.webp new file mode 100644 index 0000000..0700b7a Binary files /dev/null and b/docs/public/components/table-hero-light.webp differ diff --git a/docs/public/components/table-light.webp b/docs/public/components/table-light.webp new file mode 100644 index 0000000..a59d4b5 Binary files /dev/null and b/docs/public/components/table-light.webp differ diff --git a/docs/public/components/tabs-dark.webp b/docs/public/components/tabs-dark.webp new file mode 100644 index 0000000..4edf893 Binary files /dev/null and b/docs/public/components/tabs-dark.webp differ diff --git a/docs/public/components/tabs-hero-dark.webp b/docs/public/components/tabs-hero-dark.webp new file mode 100644 index 0000000..4788732 Binary files /dev/null and b/docs/public/components/tabs-hero-dark.webp differ diff --git a/docs/public/components/tabs-hero-light.webp b/docs/public/components/tabs-hero-light.webp new file mode 100644 index 0000000..4df4c32 Binary files /dev/null and b/docs/public/components/tabs-hero-light.webp differ diff --git a/docs/public/components/tabs-light.webp b/docs/public/components/tabs-light.webp new file mode 100644 index 0000000..7e7e829 Binary files /dev/null and b/docs/public/components/tabs-light.webp differ diff --git a/docs/public/components/textarea-dark.webp b/docs/public/components/textarea-dark.webp new file mode 100644 index 0000000..c467850 Binary files /dev/null and b/docs/public/components/textarea-dark.webp differ diff --git a/docs/public/components/textarea-hero-dark.webp b/docs/public/components/textarea-hero-dark.webp new file mode 100644 index 0000000..7c1a3bc Binary files /dev/null and b/docs/public/components/textarea-hero-dark.webp differ diff --git a/docs/public/components/textarea-hero-light.webp b/docs/public/components/textarea-hero-light.webp new file mode 100644 index 0000000..bf73ec4 Binary files /dev/null and b/docs/public/components/textarea-hero-light.webp differ diff --git a/docs/public/components/textarea-light.webp b/docs/public/components/textarea-light.webp new file mode 100644 index 0000000..aa6499a Binary files /dev/null and b/docs/public/components/textarea-light.webp differ diff --git a/docs/public/components/timeline-dark.webp b/docs/public/components/timeline-dark.webp new file mode 100644 index 0000000..b99f3e6 Binary files /dev/null and b/docs/public/components/timeline-dark.webp differ diff --git a/docs/public/components/timeline-hero-dark.webp b/docs/public/components/timeline-hero-dark.webp new file mode 100644 index 0000000..9ab609b Binary files /dev/null and b/docs/public/components/timeline-hero-dark.webp differ diff --git a/docs/public/components/timeline-hero-light.webp b/docs/public/components/timeline-hero-light.webp new file mode 100644 index 0000000..5b82042 Binary files /dev/null and b/docs/public/components/timeline-hero-light.webp differ diff --git a/docs/public/components/timeline-light.webp b/docs/public/components/timeline-light.webp new file mode 100644 index 0000000..6592ed9 Binary files /dev/null and b/docs/public/components/timeline-light.webp differ diff --git a/docs/public/components/toggle-dark.webp b/docs/public/components/toggle-dark.webp new file mode 100644 index 0000000..7665d30 Binary files /dev/null and b/docs/public/components/toggle-dark.webp differ diff --git a/docs/public/components/toggle-group-dark.webp b/docs/public/components/toggle-group-dark.webp new file mode 100644 index 0000000..855d298 Binary files /dev/null and b/docs/public/components/toggle-group-dark.webp differ diff --git a/docs/public/components/toggle-group-light.webp b/docs/public/components/toggle-group-light.webp new file mode 100644 index 0000000..e6d223d Binary files /dev/null and b/docs/public/components/toggle-group-light.webp differ diff --git a/docs/public/components/toggle-hero-dark.webp b/docs/public/components/toggle-hero-dark.webp new file mode 100644 index 0000000..fa7923c Binary files /dev/null and b/docs/public/components/toggle-hero-dark.webp differ diff --git a/docs/public/components/toggle-hero-light.webp b/docs/public/components/toggle-hero-light.webp new file mode 100644 index 0000000..86ae2d5 Binary files /dev/null and b/docs/public/components/toggle-hero-light.webp differ diff --git a/docs/public/components/toggle-light.webp b/docs/public/components/toggle-light.webp new file mode 100644 index 0000000..218deea Binary files /dev/null and b/docs/public/components/toggle-light.webp differ diff --git a/docs/public/components/tooltip-dark.webp b/docs/public/components/tooltip-dark.webp new file mode 100644 index 0000000..fef38f9 Binary files /dev/null and b/docs/public/components/tooltip-dark.webp differ diff --git a/docs/public/components/tooltip-hero-dark.webp b/docs/public/components/tooltip-hero-dark.webp new file mode 100644 index 0000000..068b691 Binary files /dev/null and b/docs/public/components/tooltip-hero-dark.webp differ diff --git a/docs/public/components/tooltip-hero-light.webp b/docs/public/components/tooltip-hero-light.webp new file mode 100644 index 0000000..03f5a19 Binary files /dev/null and b/docs/public/components/tooltip-hero-light.webp differ diff --git a/docs/public/components/tooltip-light.webp b/docs/public/components/tooltip-light.webp new file mode 100644 index 0000000..54d74ff Binary files /dev/null and b/docs/public/components/tooltip-light.webp differ diff --git a/docs/public/components/tree-dark.webp b/docs/public/components/tree-dark.webp new file mode 100644 index 0000000..a6ee799 Binary files /dev/null and b/docs/public/components/tree-dark.webp differ diff --git a/docs/public/components/tree-hero-dark.webp b/docs/public/components/tree-hero-dark.webp new file mode 100644 index 0000000..1f55b5a Binary files /dev/null and b/docs/public/components/tree-hero-dark.webp differ diff --git a/docs/public/components/tree-hero-light.webp b/docs/public/components/tree-hero-light.webp new file mode 100644 index 0000000..2ac7735 Binary files /dev/null and b/docs/public/components/tree-hero-light.webp differ diff --git a/docs/public/components/tree-light.webp b/docs/public/components/tree-light.webp new file mode 100644 index 0000000..ea19571 Binary files /dev/null and b/docs/public/components/tree-light.webp differ diff --git a/docs/public/components/virtual-list-dark.webp b/docs/public/components/virtual-list-dark.webp new file mode 100644 index 0000000..e547a3b Binary files /dev/null and b/docs/public/components/virtual-list-dark.webp differ diff --git a/docs/public/components/virtual-list-hero-dark.webp b/docs/public/components/virtual-list-hero-dark.webp new file mode 100644 index 0000000..0c23eb9 Binary files /dev/null and b/docs/public/components/virtual-list-hero-dark.webp differ diff --git a/docs/public/components/virtual-list-hero-light.webp b/docs/public/components/virtual-list-hero-light.webp new file mode 100644 index 0000000..6dfffb9 Binary files /dev/null and b/docs/public/components/virtual-list-hero-light.webp differ diff --git a/docs/public/components/virtual-list-light.webp b/docs/public/components/virtual-list-light.webp new file mode 100644 index 0000000..6146a72 Binary files /dev/null and b/docs/public/components/virtual-list-light.webp differ diff --git a/docs/public/home/calculator-dark.webp b/docs/public/home/calculator-dark.webp new file mode 100644 index 0000000..db67eb8 Binary files /dev/null and b/docs/public/home/calculator-dark.webp differ diff --git a/docs/public/home/calculator-light.webp b/docs/public/home/calculator-light.webp new file mode 100644 index 0000000..1d6d519 Binary files /dev/null and b/docs/public/home/calculator-light.webp differ diff --git a/docs/public/home/deck-dark.webp b/docs/public/home/deck-dark.webp new file mode 100644 index 0000000..e6474fe Binary files /dev/null and b/docs/public/home/deck-dark.webp differ diff --git a/docs/public/home/deck-playlist-dark.webp b/docs/public/home/deck-playlist-dark.webp new file mode 100644 index 0000000..532617e Binary files /dev/null and b/docs/public/home/deck-playlist-dark.webp differ diff --git a/docs/public/home/feed-dark.webp b/docs/public/home/feed-dark.webp new file mode 100644 index 0000000..0b85761 Binary files /dev/null and b/docs/public/home/feed-dark.webp differ diff --git a/docs/public/home/feed-light.webp b/docs/public/home/feed-light.webp new file mode 100644 index 0000000..a63f22a Binary files /dev/null and b/docs/public/home/feed-light.webp differ diff --git a/docs/public/home/markdown-viewer-dark.webp b/docs/public/home/markdown-viewer-dark.webp new file mode 100644 index 0000000..928ec6a Binary files /dev/null and b/docs/public/home/markdown-viewer-dark.webp differ diff --git a/docs/public/home/markdown-viewer-light.webp b/docs/public/home/markdown-viewer-light.webp new file mode 100644 index 0000000..9d33ec3 Binary files /dev/null and b/docs/public/home/markdown-viewer-light.webp differ diff --git a/docs/public/home/notes-dark.webp b/docs/public/home/notes-dark.webp new file mode 100644 index 0000000..1767a97 Binary files /dev/null and b/docs/public/home/notes-dark.webp differ diff --git a/docs/public/home/notes-light.webp b/docs/public/home/notes-light.webp new file mode 100644 index 0000000..47caa56 Binary files /dev/null and b/docs/public/home/notes-light.webp differ diff --git a/docs/public/home/soundboard-dark.webp b/docs/public/home/soundboard-dark.webp new file mode 100644 index 0000000..05745fc Binary files /dev/null and b/docs/public/home/soundboard-dark.webp differ diff --git a/docs/public/home/soundboard-light.webp b/docs/public/home/soundboard-light.webp new file mode 100644 index 0000000..c51cc0f Binary files /dev/null and b/docs/public/home/soundboard-light.webp differ diff --git a/docs/public/home/system-monitor-dark.webp b/docs/public/home/system-monitor-dark.webp new file mode 100644 index 0000000..e483e7c Binary files /dev/null and b/docs/public/home/system-monitor-dark.webp differ diff --git a/docs/public/home/system-monitor-light.webp b/docs/public/home/system-monitor-light.webp new file mode 100644 index 0000000..853c55f Binary files /dev/null and b/docs/public/home/system-monitor-light.webp differ diff --git a/docs/public/home/ui-inbox-macos.png b/docs/public/home/ui-inbox-macos.png new file mode 100644 index 0000000..23f86bf Binary files /dev/null and b/docs/public/home/ui-inbox-macos.png differ diff --git a/docs/public/wasm/component-preview.wasm b/docs/public/wasm/component-preview.wasm new file mode 100755 index 0000000..05c0bed Binary files /dev/null and b/docs/public/wasm/component-preview.wasm differ diff --git a/docs/scripts/check-code-toggle.mjs b/docs/scripts/check-code-toggle.mjs new file mode 100644 index 0000000..43b92a0 --- /dev/null +++ b/docs/scripts/check-code-toggle.mjs @@ -0,0 +1,65 @@ +// Regression pin for the TS | Zig code toggle: assert the *prerendered* +// HTML actually contains the tab headers. The toggle once shipped broken — +// a client component introspected its RSC children, saw one opaque node, +// and silently fell back to stacked fences on every page — and nothing +// caught it because no check looked at rendered output. This one does. +// +// For every page.mdx that uses , the built HTML under +// ${NEXT_DIST_DIR:-.next}/server/app must contain exactly as many +// role="tablist" headers as the source has usages. +// +// Runs after `next build` as part of `pnpm check`. + +import { readFileSync, readdirSync, existsSync, statSync } from "node:fs"; +import { join, relative, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const docsDir = join(dirname(fileURLToPath(import.meta.url)), ".."); +const appDir = join(docsDir, "src", "app"); +const distDir = join(docsDir, process.env.NEXT_DIST_DIR || ".next"); +const htmlDir = join(distDir, "server", "app"); + +function* mdxPages(dir) { + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) yield* mdxPages(full); + else if (entry === "page.mdx") yield full; + } +} + +function count(haystack, needle) { + return haystack.split(needle).length - 1; +} + +let failures = 0; +let togglePages = 0; + +for (const page of mdxPages(appDir)) { + const expected = count(readFileSync(page, "utf8"), ""); + if (expected === 0) continue; + togglePages += 1; + + const route = relative(appDir, dirname(page)); // e.g. "typescript/packages" + const htmlPath = join(htmlDir, `${route}.html`); + if (!existsSync(htmlPath)) { + console.error(`FAIL /${route}: no prerendered HTML at ${htmlPath} — expected a static page with ${expected} code toggle(s)`); + failures += 1; + continue; + } + + const actual = count(readFileSync(htmlPath, "utf8"), 'role="tablist"'); + if (actual !== expected) { + console.error(`FAIL /${route}: ${expected} usage(s) in MDX but ${actual} role="tablist" in prerendered HTML — the toggle is falling back to stacked fences`); + failures += 1; + } else { + console.log(`ok /${route}: ${actual} code toggle(s) rendered with tabs`); + } +} + +if (togglePages === 0) { + console.error("FAIL: no page.mdx uses — if the component was renamed, update scripts/check-code-toggle.mjs so this pin keeps checking rendered output"); + failures += 1; +} + +if (failures > 0) process.exit(1); +console.log(`code-toggle check passed: ${togglePages} page(s) verified`); diff --git a/docs/src/app/api/search/route.ts b/docs/src/app/api/search/route.ts new file mode 100644 index 0000000..32fc874 --- /dev/null +++ b/docs/src/app/api/search/route.ts @@ -0,0 +1,64 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getSearchIndex } from "@/lib/search-index"; + +export async function GET(req: NextRequest) { + const q = req.nextUrl.searchParams.get("q")?.trim().toLowerCase(); + + if (!q) { + return NextResponse.json({ results: [] }); + } + + const index = await getSearchIndex(); + const terms = q.split(/\s+/).filter(Boolean); + + const results = index + .map((entry) => { + const titleLower = entry.title.toLowerCase(); + const contentLower = entry.content.toLowerCase(); + + const titleMatch = terms.every((t) => titleLower.includes(t)); + const contentMatch = terms.every((t) => contentLower.includes(t)); + + if (!titleMatch && !contentMatch) return null; + + let snippet = ""; + if (contentMatch) { + const firstTermIdx = Math.min( + ...terms.map((t) => { + const idx = contentLower.indexOf(t); + return idx === -1 ? Infinity : idx; + }) + ); + if (firstTermIdx !== Infinity) { + const start = Math.max(0, firstTermIdx - 40); + const end = Math.min(entry.content.length, firstTermIdx + 120); + snippet = + (start > 0 ? "..." : "") + + entry.content.slice(start, end).replace(/\n/g, " ") + + (end < entry.content.length ? "..." : ""); + } + } + + return { + title: entry.title, + href: entry.href, + snippet, + score: titleMatch ? 2 : 1, + }; + }) + .filter( + ( + r + ): r is { + title: string; + href: string; + snippet: string; + score: number; + } => r !== null + ) + .sort((a, b) => b.score - a.score) + .slice(0, 20) + .map(({ score: _, ...rest }) => rest); + + return NextResponse.json({ results }, { headers: { "Cache-Control": "public, max-age=60" } }); +} diff --git a/docs/src/app/app-model/layout.tsx b/docs/src/app/app-model/layout.tsx new file mode 100644 index 0000000..699cdcb --- /dev/null +++ b/docs/src/app/app-model/layout.tsx @@ -0,0 +1,7 @@ +import { pageMetadata } from "@/lib/page-metadata"; + +export const metadata = pageMetadata("app-model"); + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/docs/src/app/app-model/page.mdx b/docs/src/app/app-model/page.mdx new file mode 100644 index 0000000..be4b525 --- /dev/null +++ b/docs/src/app/app-model/page.mdx @@ -0,0 +1,163 @@ +import { CodeToggle } from "@/components/code-toggle"; + +# App Model + +A Native SDK app is one loop with four parts: + +- **Model** — a plain data structure holding all app state. +- **Msg** — a tagged union of everything that can happen. +- **update(model, msg)** — the only place state changes. +- **View** — a Native markup file (`.native`, or a Zig view function) that derives the UI from the model. + +The runtime owns everything else: window creation, GPU presentation, resize, pointer and keyboard dispatch, timers, accessibility, and hot reload. Your code never handles a raw event — input lands on a widget, the widget's bound message dispatches into `update`, the view rebuilds from the new model, and the engine repaints what changed. + +The loop is the same in both authoring languages. By default the core is TypeScript (`src/core.ts`, compiled to native code at build time — [TypeScript Cores](/typescript) covers that tier in depth); a Zig core (`src/main.zig`, from `native init --template zig-core`) is first-class by choice, and the rest of this page — wiring, identity, hot reload — applies to both. The Zig-specific wiring sections below are exactly what the build generates for a TypeScript app, so they double as its eject story. + +## The loop in full + +A minimal counter is the complete shape — state, transitions, one pure `update`: + + + +```ts:src/core.ts +export interface Model { + readonly count: number; +} + +export type Msg = + | { readonly kind: "increment" } + | { readonly kind: "decrement" } + | { readonly kind: "reset" }; + +export function initialModel(): Model { + return { count: 0 }; +} + +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 }; + } +} +``` + +```zig:src/main.zig +pub const Msg = union(enum) { + increment, + decrement, + reset, +}; + +pub const Model = struct { + count: i64 = 0, +}; + +pub fn update(model: *Model, msg: Msg) void { + switch (msg) { + .increment => model.count += 1, + .decrement => model.count -= 1, + .reset => model.count = 0, + } +} +``` + + + +The view in `src/app.native` binds the model and names the messages: + +```html + + + {count} + + +``` + +Markup can never mutate state. `{count}` is a read; `on-press="increment"` names a `Msg` variant (in a TypeScript core, the arm's `kind`). Every state change flows through `update`, which makes the app's behavior testable as a plain function — the Zig template's generated `src/tests.zig` drives it with no GUI at all, and a TypeScript core runs under node the same way (`native dev --core`). + +## Wiring + +`native_sdk.UiApp(Model, Msg)` ties the loop to the runtime. A zero-config app never writes this — the build graph generates it (for a TypeScript core, over the transpiled model) — but it is ordinary code you can own any time. From the Zig template's `main`: + +```zig +const CounterApp = native_sdk.UiApp(Model, Msg); + +pub fn main(init: std.process.Init) !void { + // `create` heap-allocates the multi-MB app struct and constructs the + // Model in place — neither ever rides the stack. + const app_state = try CounterApp.create(std.heap.page_allocator, .{ + .name = "my_app", + .scene = shell_scene, // one window, one gpu_surface view + .canvas_label = "main-canvas", // must match the scene's view label + .update_fx = update, // update(model, msg, fx) - or .update for a pure app + .markup = .{ .source = app_markup, .watch_path = "src/app.native", .io = init.io }, + }); + defer app_state.destroy(); + app_state.model = initialModel(); // boot state: assign through the pointer + + try runner.runWithOptions(app_state.app(), .{ ... }, init); +} +``` + +`create` requires every `Model` field to carry a default; the model starts as `.{}` and boot state is assigned through the returned pointer. The `scene` declares the native window and its GPU surface view — see [Windows](/windows) and [Native Surfaces](/native-surfaces) for multi-view scenes. + +## Rebuilds and widget identity + +After every `update`, the runtime rebuilds the view from the model. Rebuilds are cheap and safe by design: + +- **Widget identity is structural.** A widget keeps its id across rebuilds, reorders, and hot reloads, so engine-owned state — scroll offsets, text carets, focus — survives. List items carry `key` (or `global-key` for items that move between containers) to keep identity through reorders. Unkeyed same-kind siblings take positional identity (sibling index), so an `` that inserts or removes an earlier same-kind sibling re-disambiguates the trailing ones — engine-owned state like carets and scroll can hop; keyed items and keyed ancestors hold identity. +- **The source wins.** Engine-retained state (a scroll offset, a toggle) survives rebuilds until the model asserts a different value; then the model's value applies. This is why controlled patterns echo runtime-applied values back through the model — see [State & Data Flow](/state). +- **Errors degrade, they never crash.** A failing `update` arm is caught, recorded in a bounded error ring (visible in [automation](/automation) snapshots as `dispatch_errors=`), and the app keeps running. + +## Hot reload in development + +With `watch_path` set, the runtime watches the `.native` file while the app runs. Edits apply within about two seconds, preserving model state and widget identity. Parse failures keep the last good view on screen and record a diagnostic (`app_state.markup_diagnostic` carries line, column, and message). + +## Compile the markup for release + +In release builds the markup compiles at comptime — no parser in the binary, and markup or binding mistakes become compile errors with line and column: + +```zig +const dev = @import("builtin").mode == .Debug; +const App = native_sdk.UiAppWithFeatures(Model, Msg, .{ .runtime_markup = dev }); +const CompiledView = canvas.CompiledMarkupView(Model, Msg, @embedFile("app.native")); +// options: +.view = CompiledView.build, +.markup = if (dev) .{ .source = app_markup, .watch_path = "src/app.native", .io = init.io } else null, +``` + +Both engines produce the identical widget tree — same structural ids, same typed handler table — so tests, automation scripts, and goldens hold across dev and release. + +## Hybrid views: a Zig root composing markup fragments + +Compiled markup views compose the other way too: a hand-written Zig builder root can build markup fragments as ordinary children. This is the pattern for any UI that mixes custom Zig panes with declarative markup — the root places what the closed grammar cannot express (a scaled `ui.paragraph` display block, a `.band`-series `ui.chart`, per-row native context menus), and the markup keeps everything it can: + +```zig +const CompiledHeaderView = canvas.CompiledMarkupView(Model, Msg, @embedFile("header.native")); + +pub fn rootView(ui: *Ui, model: *const Model) Ui.Node { + return ui.column(.{ .gap = 12, .grow = 1 }, .{ + CompiledHeaderView.build(ui, model), // the markup fragment, as a child + ui.paragraph(.{}, &.{ + .{ .text = model.readout(ui.arena), .monospace = true, .scale = 1.6 }, + }), + }); +} +// options: .view = rootView, +``` + +`examples/calculator` is the smallest live reference (`CompiledKeypadView.build(ui, model)` inside a hand-written root); `system-monitor`, `soundboard`, and `deck` use the same shape. Widget ids, handlers, and dispatch are identical to a pure-markup tree, so tests and automation address the fragment's widgets the usual way. + +A Zig-root app keeps dev-time hot reload for its embedded fragments too: build with `UiAppWithFeatures(Model, Msg, .{ .runtime_markup = dev_markup_reload })` and pass `.markup = .{ .source = ..., .watch_path = "src/header.native", .io = init.io }` gated on `builtin.mode == .Debug` (`null` otherwise) — the wiring in `examples/notes`. Debug builds then reload edits to the watched file in place; release builds compile the runtime engine out entirely. + +## Side effects + +`update` stays pure by routing anything asynchronous — subprocesses, HTTP, file persistence, timers, clipboard — through the effects channel, and results come back as ordinary messages. In a TypeScript core, effects are `Cmd` data returned from `update` and recurring timers are declared `Sub` data — see [TypeScript Cores: Effects](/typescript#effects-are-cmd-data). In a Zig core, declare `.update_fx` instead of `.update` and spawn from message arms; boot-time work goes in `.init_fx`, which runs exactly once before the first paint. See [Native UI: Effects](/native-ui#effects). + +## Dropping down + +`UiApp` is a layer over the lower-level `App`/`Runtime` pair, which any app can use directly — for custom lifecycle callbacks, imperative window and view management, or embedding [web content](/frontend). The [App & Runtime](/runtime) reference documents that layer, and [Embedded App](/embed) covers driving the runtime from an existing host (including iOS and Android). diff --git a/docs/src/app/app-zon/layout.tsx b/docs/src/app/app-zon/layout.tsx new file mode 100644 index 0000000..bc9aa5b --- /dev/null +++ b/docs/src/app/app-zon/layout.tsx @@ -0,0 +1,7 @@ +import { pageMetadata } from "@/lib/page-metadata"; + +export const metadata = pageMetadata("app-zon"); + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/docs/src/app/app-zon/page.mdx b/docs/src/app/app-zon/page.mdx new file mode 100644 index 0000000..631924f --- /dev/null +++ b/docs/src/app/app-zon/page.mdx @@ -0,0 +1,413 @@ +# Config + +The `app.zon` manifest declares app metadata, permissions, security rules, window layout, and packaging inputs. It is read by the CLI and tooling at build, package, and validation time. + +## Example: native-rendered app + +The manifest `native init` generates — identity, one shell window with a GPU surface view, and the minimal permission set: + +```zig:app.zon +.{ + .id = "dev.native_sdk.my-app", + .name = "my-app", + .display_name = "My App", + .description = "A counter that lives in one native window.", + .version = "0.1.0", + .icons = .{"assets/icon.png"}, + .platforms = .{"macos"}, + .permissions = .{ "view", "command" }, + .capabilities = .{ "native_views", "gpu_surfaces" }, + .shell = .{ + .windows = .{ + .{ + .label = "main", + .title = "My App", + .width = 480, + .height = 320, + .restore_state = false, + .restore_policy = "center_on_primary", + .views = .{ + .{ .label = "main-canvas", .kind = "gpu_surface", .fill = true, .role = "Counter canvas", .accessibility_label = "Counter", .gpu_backend = "metal", .gpu_pixel_format = "bgra8_unorm", .gpu_present_mode = "timer", .gpu_alpha_mode = "opaque", .gpu_color_space = "srgb", .gpu_vsync = true }, + }, + }, + }, + }, + .security = .{ + .navigation = .{ + .allowed_origins = .{ "zero://app", "zero://inline" }, + .external_links = .{ .action = "deny" }, + }, + }, + .web_engine = "system", + .cef = .{ .dir = "third_party/cef/macos", .auto_install = false }, +} +``` + +## Example: app with web content, menus, and shortcuts + +A fuller manifest for an app that also [embeds web content](/frontend) and declares commands, shortcuts, menus, and packaging metadata: + +```zig:app.zon +.{ + .id = "dev.native_sdk", + .name = "native-sdk", + .display_name = "native-sdk", + .version = "0.1.0", + .icons = .{"assets/icon.png"}, + .platforms = .{ "macos" }, + .permissions = .{ "command", "view", "dialog", "window", "clipboard", "credentials" }, + .capabilities = .{ "webview", "js_bridge", "native_views", "gpu_surfaces", "menus", "shortcuts", "dialog", "clipboard", "credentials" }, + .bridge = .{ + .commands = .{ + .{ .name = "native.ping", .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.window.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.command.invoke", .permissions = .{ "command" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.view.list", .permissions = .{ "view" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.dialog.showMessage", .permissions = .{ "dialog" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.clipboard.readText", .permissions = .{ "clipboard" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.credentials.get", .permissions = .{ "credentials" }, .origins = .{ "zero://app" } }, + }, + }, + .security = .{ + .navigation = .{ + .allowed_origins = .{ "zero://app", "http://127.0.0.1:5173" }, + .external_links = .{ .action = "deny" }, + }, + }, + .web_engine = "system", + .cef = .{ .dir = "third_party/cef/macos", .auto_install = false }, + .windows = .{ + .{ .label = "main", .title = "native-sdk", .width = 720, .height = 480, .restore_state = true }, + }, + .commands = .{ + .{ .id = "command.palette", .title = "Command Palette" }, + .{ .id = "app.reload", .title = "Reload" }, + }, + .shortcuts = .{ + .{ .id = "command.palette", .key = "p", .modifiers = .{ "primary", "shift" } }, + }, + .menus = .{ + .{ + .title = "View", + .items = .{ + .{ .label = "Command Palette", .command = "command.palette", .key = "p", .modifiers = .{ "primary", "shift" } }, + .{ .separator = true }, + .{ .label = "Reload", .command = "app.reload", .key = "r", .modifiers = .{ "primary" } }, + }, + }, + }, + .file_associations = .{ + .{ + .name = "Markdown Document", + .extensions = .{ "md", ".markdown" }, + .mime_types = .{ "text/markdown" }, + .icon = "assets/markdown.icns", + }, + }, + .url_schemes = .{ + .{ .scheme = "native-sdk" }, + }, +} +``` + +## Fields + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
idReverse-DNS bundle identifier (e.g. com.example.myapp)
nameShort machine name
display_nameHuman-readable app name — the application menu, Dock, app switcher, and About panel all use it, in dev runs and packaged bundles alike
descriptionOptional one-line description shown in the About panel (max 256 bytes, single line)
versionSemver version string — also the version the About panel shows
iconsThe app icon: one square .png (1:1, ideally 1024x1024) or .svg source that packaging turns into every platform's icon artifacts — the macOS .icns gets the platform's rounded-rect icon shape applied automatically. A prebuilt .icns/.ico entry ships untouched for its platform instead
platformsTarget platforms: macos, linux, windows
permissionsRuntime permissions (see Security)
capabilitiesFeature declarations (see Security)
bridgeBridge command policies (see Bridge)
securityNavigation and external link policies (see Security)
web_enginesystem or chromium; Chromium is currently supported for macOS builds (see Web Engines)
webview_layerauto (default), include, or exclude — whether the build ships the embedded web layer (see below)
themeBuilt-in theme pack: house (default) or geist; an unknown name is a build/check error (see Theming)
theme_accentOptional #rrggbb accent layered over the pack — recolors the accent, its knockout ink, the focus ring, and the slider active range together; skipped under high contrast; a malformed value is a build/check error
cefCEF runtime config for Chromium apps: dir and auto_install
assetsLaunch-registered assets for TypeScript-core apps: images entries (.{ .id = 1, .path = "assets/art/cover.jpg" }) are read once at launch and registered on the installing frame; the id is the ImageId markup avatar bindings reference
windowsWindow definitions (see Windows)
shellNative-first window and view tree declarations
commandsShared command metadata for menus, shortcuts, native controls, tray items, and bridge calls
shortcutsKeyboard shortcuts delivered as shortcut events (see Keyboard Shortcuts)
menusNative menu declarations delivered through the command event path (see Menus)
file_associationsFile type registration metadata for packaging
url_schemesCustom URL scheme registration metadata for packaging
frontendFrontend build/dev config (see Frontend Projects)
+ +## `webview_layer` + +Under the default `auto`, the build infers whether to ship the embedded web layer from what the app declares: `"webview"` in `.capabilities`, a `.frontend` block, a `.shell` webview view, or a web engine resolved to Chromium (from `.web_engine` or the `-Dweb-engine`/`--web-engine` flags) each count as web intent, and an app with none of them builds native-only. `include` forces the layer into a build that declares nothing yet; `exclude` promises a native-only app. The `-Dweb-layer` build option overrides the manifest setting for one build. + +A native-only build carries the platform consequences with it: on Windows the executable never references `WebView2Loader.dll` and no loader ships in the package; on Linux the host compiles and links without WebKitGTK entirely, so building needs no WebKitGTK development package and users need no `libwebkitgtk` installed at runtime. + +`exclude` alongside any web declaration is a contradiction, refused with a teaching error at validate, build, and package time instead of shipping an app whose declared webviews cannot work: + +```zig +.capabilities = .{"webview"}, +.webview_layer = "exclude", // rejected: remove the web declarations or drop the exclude +``` + +## `shell` + +The optional `shell` block declares native-first windows with explicit view trees. Existing `windows` entries remain the simple compatibility path; `shell.windows` is the richer contract for native chrome, native controls, WebViews, and GPU surfaces. + +Tooling parses and validates the schema. Runtime code can return the same shape from `App.scene_fn`, materialize a parsed shell window with `runtime.createShellWindow(...)`, or attach a shell view list to an existing window with `runtime.createShellViews(...)`; platform hosts implement view kinds progressively, so unsupported native kinds fail at runtime with an explicit unsupported error until that backend grows support. + +When an app uses both `windows` and `shell.windows`, labels must stay unique across both lists. Use `windows` for the simple compatibility path or `shell.windows` for native-first structure; do not define two window entries with the same label. + +For a scene-first app — a `UiApp` passing its Zig scene (`shell_scene`) to the runner — the scene is authoritative at runtime: it re-applies size, title, and views when it loads. `app.zon`'s `.shell.windows[0]` exists because the host creates the startup window before the scene loads, and create-time-only properties must come from the manifest: `titlebar` chrome, `min_width`/`min_height` floors, and the show mode (canvas-first windows are created hidden and shown after the first frame presents). The numbers appearing in both places is by design — edit the scene for anything that can change after create (size, title, views), and the manifest for create-time chrome and floors. + +```zig +.shell = .{ + .windows = .{ + .{ + .label = "main", + .title = "Acme", + .width = 1100, + .height = 760, + .views = .{ + .{ .label = "toolbar", .kind = "toolbar", .edge = "top", .height = 44, .role = "toolbar" }, + .{ .label = "body", .kind = "split", .fill = true, .axis = "row" }, + .{ .label = "sidebar", .kind = "sidebar", .parent = "body", .width = 280, .min_width = 220, .max_width = 360 }, + .{ .label = "sidebar-stack", .kind = "stack", .parent = "sidebar", .x = 16, .y = 16, .width = 220, .height = 120, .axis = "column" }, + .{ .label = "sidebar-live", .kind = "checkbox", .parent = "sidebar-stack", .accessibility_label = "Toggle live updates", .text = "Live updates" }, + .{ .label = "content", .kind = "webview", .parent = "body", .url = "zero://app/index.html", .fill = true }, + .{ .label = "canvas", .kind = "gpu_surface", .parent = "body", .width = 480, .gpu_backend = "metal", .gpu_pixel_format = "bgra8_unorm", .gpu_present_mode = "timer", .gpu_alpha_mode = "opaque", .gpu_color_space = "srgb", .gpu_vsync = true }, + .{ .label = "status", .kind = "statusbar", .edge = "bottom", .height = 24, .text = "Ready" }, + }, + }, + }, +}, +``` + +Each window takes a `label` plus optional `title`, `width`, `height`, `x`, `y`, `resizable`, `restore_state`, `restore_policy` (`clamp_to_visible_screen` or `center_on_primary`), `min_width`/`min_height` (a content min-size floor the window itself enforces — macOS `contentMinSize`; the first shell window's declaration threads through the startup create like `titlebar`, negative values are a manifest error, 0 means no floor), and `titlebar` (`standard`, `hidden_inset`, `hidden_inset_tall` — the tall variant centers macOS's traffic lights in the 52pt unified band for toolbar-height headers — or `chromeless`, the fully-skinned opt-in that removes all OS chrome including the system buttons; only for apps that draw their own working window controls, see `examples/deck`). `titlebar = "hidden_inset"` hides the titlebar and extends content under it (macOS keeps the traffic lights) — the first shell window's declaration threads through the STARTUP window create, so the main window's chrome is right from the first frame; the app's own header then takes over dragging and inset padding through the `window-drag` attribute and the `on_chrome` hook (see Native UI). Platforms without the concept keep standard chrome. The same `titlebar` field is accepted on top-level `windows` entries. + +Supported `kind` values are `webview`, `toolbar`, `titlebar_accessory`, `sidebar`, `statusbar`, `split`, `stack`, `button`, `icon_button`, `list_item`, `checkbox`, `toggle`, `segmented_control`, `text_field`, `search_field`, `label`, `spacer`, `gpu_surface`, and `progress_indicator`. + +Each view has a required `label` and `kind`. WebView views require `url`. Optional layout fields are `parent`, `edge`, `axis`, `x`, `y`, `width`, `height`, `min_width`, `min_height`, `max_width`, `max_height`, `fill`, and `layer`. Optional behavior and accessibility metadata are `visible`, `enabled`, `role`, `accessibility_label`, `text`, and `command`. `gpu_surface` views may also set `gpu_backend`, `gpu_pixel_format`, `gpu_present_mode`, `gpu_alpha_mode`, `gpu_color_space`, and `gpu_vsync`; those fields are rejected on non-GPU view kinds. The currently implemented macOS system-WebView backend uses `metal`, `bgra8_unorm`, `timer`, `opaque`, `srgb`, and `gpu_vsync = true`. `gpu_backend` also accepts `software` (the CPU reference-renderer path); on Linux and Windows system-WebView hosts any declared backend falls back to software presentation rather than erroring. The `axis` field accepts `row` or `column` on parent containers such as `toolbar`, `sidebar`, `split`, and `stack`; it defaults to `row`. + +`createShellWindow` and `createShellViews` dock `edge` views against the remaining window content, let one or more top-level `fill` views use the final remaining rectangle, clamp resolved frames with any min/max size fields, and lay out parented controls such as toolbar buttons with small native defaults when explicit `x`, `y`, `width`, or `height` values are omitted. Parent containers flow omitted child positions horizontally with `axis = "row"` and vertically with `axis = "column"`. `split` containers use the same axis without inner spacing, so fixed-size children can sit beside a `fill` child. The runtime keeps the shell view slice as a layout binding and reapplies it when the window is resized, so pass data that lives for the lifetime of the window. + +## `commands` + +The optional `commands` list declares shared command metadata. The runtime still dispatches by command id, but this section gives menus, shortcuts, native controls, tray items, and bridge callers one manifest-level source of truth for user-facing labels and default state: + +```zig +.commands = .{ + .{ .id = "app.refresh", .title = "Refresh" }, + .{ .id = "app.sidebar.toggle", .title = "Sidebar", .checked = true }, + .{ .id = "app.sync", .title = "Sync", .enabled = false }, +}, +``` + +`id` is required and must be unique. `title` defaults to an empty string. `enabled` defaults to `true`; `checked` defaults to `false`. + +An app can define up to 256 commands. Command ids can be up to 128 bytes and titles can be up to 128 bytes. + +Generated runners load manifest commands into `RuntimeOptions.commands`. Native code can read the active catalog with `runtime.listCommands(...)`, and trusted WebView code can read it with `window.zero.commands.list()` when the built-in command bridge allows it. Use the catalog to keep menus, shortcuts, toolbar controls, tray items, and bridge callers aligned with the same command ids. + +## `shortcuts` + +The optional `shortcuts` list defines app-level keyboard shortcuts. Generated runners load these automatically: + +```zig +.shortcuts = .{ + .{ .id = "command.palette", .key = "p", .modifiers = .{ "primary", "shift" } }, + .{ .id = "reload", .key = "r", .modifiers = .{ "primary" } }, +}, +``` + +`id` is the event identifier. `key` accepts letters, digits, the unshifted punctuation keys `=`, `-`, `,`, `.`, `/`, `;`, `'`, `[`, `]`, `\`, and `` ` ``, or a named key. `modifiers` accepts `primary`, `command`, `control`, `option`, `alt`, and `shift`. + +Single-character keys and text-entry keys (`enter`, `tab`, `space`, and `backspace`) require at least one modifier. + +An app can define up to 64 shortcuts. Shortcut ids can be up to 64 bytes and keys can be up to 32 bytes. + +Chromium builds are currently macOS-only; use the Linux system WebView backend when an app needs native shortcut events on Linux. + +## `menus` + +The optional `menus` list defines native app menus. Generated runners load these automatically: + +```zig +.menus = .{ + .{ + .title = "View", + .items = .{ + .{ .label = "Refresh", .command = "app.refresh", .key = "r", .modifiers = .{ "primary" } }, + .{ .separator = true }, + .{ .label = "Sidebar", .command = "app.sidebar.toggle", .checked = true }, + }, + }, +}, +``` + +Each menu has a `title` and optional `items`. Command-backed items require `label` and `command`. Optional fields are `key`, `modifiers`, `separator`, `enabled`, and `checked`. `modifiers` accepts the same values as shortcuts. + +An app can define up to 16 menus and 128 total menu items. Menu titles can be up to 64 bytes, item labels up to 128 bytes, command ids up to 128 bytes, and keys up to 32 bytes. + +## File associations and URL schemes + +The optional `file_associations` and `url_schemes` lists declare packaging metadata for document types and custom protocols: + +```zig +.file_associations = .{ + .{ + .name = "Markdown Document", + .role = "editor", + .extensions = .{ "md", ".markdown" }, + .mime_types = .{ "text/markdown" }, + .icon = "assets/markdown.icns", + }, +}, +.url_schemes = .{ + .{ .scheme = "acme-notes" }, +}, +``` + +File associations require a `name` and at least one `extensions` or `mime_types` entry. Extensions may include a leading dot, but packaging tools treat the extension as the same value either way. `icon` is optional and must be a relative path. + +URL schemes require a lowercase custom `scheme`. Reserved schemes such as `http`, `https`, and `file` are rejected. + +`role` defaults to `viewer` and accepts `viewer`, `editor`, `shell`, or `none`. Package generation emits this metadata into macOS `Info.plist` document/protocol declarations, Linux desktop and shared MIME metadata, and a Windows per-user registration script. + +## `frontend.dev` + +The optional `frontend.dev` block configures the managed dev server for `native dev` and `zig build dev`: + +```zig +.frontend = .{ + .dist = "frontend/dist", + .entry = "index.html", + .spa_fallback = true, + .dev = .{ + .url = "http://127.0.0.1:5173/", + .command = .{ "npm", "--prefix", "frontend", "run", "dev" }, + .ready_path = "/", + .timeout_ms = 30000, + }, +}, +``` + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
urlDev server URL to load in the WebView during development
commandCommand to start the dev server (spawned as a child process)
ready_pathHTTP path to poll until the dev server is ready (default /)
timeout_msMilliseconds to wait for the dev server before failing (default 30000)
+ +## Validation + +```bash +native validate app.zon +native doctor --manifest app.zon --strict +``` diff --git a/docs/src/app/automation/layout.tsx b/docs/src/app/automation/layout.tsx new file mode 100644 index 0000000..6bfb053 --- /dev/null +++ b/docs/src/app/automation/layout.tsx @@ -0,0 +1,7 @@ +import { pageMetadata } from "@/lib/page-metadata"; + +export const metadata = pageMetadata("automation"); + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/docs/src/app/automation/page.mdx b/docs/src/app/automation/page.mdx new file mode 100644 index 0000000..9c70ffe --- /dev/null +++ b/docs/src/app/automation/page.mdx @@ -0,0 +1,268 @@ +# Automation + +The automation server exposes runtime state and accepts commands via a file-based protocol. A running app publishes an accessibility snapshot — every widget with its id, role, name, bounds, and state — and accepts scripted clicks, keys, drags, and assertions against it. Use it for integration testing, CI smoke tests, inspecting running apps, and letting AI agents verify their own UI work. + +## Enabling automation + +Build with the automation flag (`native` verbs forward `-D` flags to the underlying `zig build`): + +```bash +native build -Dautomation=true +``` + +In your runner, pass an `automation.Server` to `RuntimeOptions`: + +```zig +const server = native_sdk.automation.Server.init(io, ".zig-cache/native-sdk-automation", "My App"); +var runtime = native_sdk.Runtime.init(.{ + .platform = my_platform, + .automation = server, +}); +``` + +The default directory is `.zig-cache/native-sdk-automation`. + +## File protocol + +When the runtime publishes a snapshot, it writes these files to the automation directory: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileDescription
snapshot.txtRuntime state: source kind, window metadata, native/WebView metadata including role, accessibility label, text, and focus state, ready=true/false, and markup_watch=armed|off in the header — whether the markup hot-reload watch is armed (only in builds where the app wired .markup with a watch_path and io, or registered compiled fragments through fragment_watch — i.e. Debug dev builds)
accessibility.txtAccessibility tree summary with native view roles and accessible names. An explicit label= REPLACES the visible text as the accessible name — snapshot greps and screen readers see the label, not the text, so don't label an element whose visible text your assertions grep for.
windows.txtWindow list: window @w{"{id}"} "{"{title}"}" focused={"{bool}"} per line
screenshot-<view-label>.pngDeterministic PNG of a gpu_surface view, rendered through the CPU reference renderer on screenshot <view-label> [scale]
command-<n>.txtCommand queue: one entry per command, written by the CLI, consumed oldest-first by the runtime (which deletes the entry as its consumption ack)
bridge-response.txtJSON response from the last bridge command
provenance.txtResponse to the last provenance command: where a live widget was authored (file, byte span, line:column, template chain, iteration keys)
+ +## Commands + +The runtime watches the command queue and processes these actions: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ActionDescription
reloadReload the WebView source
waitBlock until the snapshot shows ready=true
resize <width> <height> [<scale>]Dispatch a main-window resize event and relayout native/WebView surfaces
provenance <view-label> (<widget-id> | at <x> <y>)Report where a live widget was authored into provenance.txt: markup file, byte span, line:column, template use-site chain, and iteration keys. Point queries hit-test view-local coordinates.
bridge <json>Send a bridge command with origin zero://inline
menu-command <id>Dispatch a menu command event for the main window
native-command <id> [<view-label>]Dispatch a native view command event for the main window
widget-action <view-label> <widget-id> <action> [<value>]Invoke a retained canvas widget action
widget-click <view-label> <widget-id>Dispatch pointer down/up at a retained canvas widget
widget-hold <view-label> <widget-id>Press-and-hold: pointer down, the reserved hold timer fired, then the suppressed release — the widget's on_hold Msg through the real gesture path
widget-context-press <view-label> <widget-id>Secondary click (right/ctrl-click): presents the widget's context menu, or dispatches on_hold immediately when the route has none
widget-context-menu <view-label> <widget-id> <item-index>Invoke a declared context-menu item by 0-based index (as the snapshot's context_menu=[...] lists them) — the selection dispatches as the same context_menu_action platform event a real pick produces; presentation is skipped because the OS menu's tracking loop cannot be driven. Refuses undeclared menus, out-of-range indices, separators, and disabled items by name
widget-drag <view-label> <widget-id> <start-x-ratio> <end-x-ratio> [<start-y-ratio> <end-y-ratio>]Dispatch pointer down/drag/up across a retained canvas widget
widget-wheel <view-label> <widget-id> <delta-y>Dispatch wheel input at a retained canvas widget
widget-key <view-label> <key> [<text>]Dispatch key input to the focused retained canvas widget
shortcut <id>Dispatch a shortcut command event for the main window
tray-action <item-id>Select a status-item dropdown row (ids from the snapshot's tray-item #id lines)
focus <view-label>Focus a native or WebView-backed view in the main window
focus-next / focus-previousMove focus through visible, enabled views in runtime order
profile <on|off>Toggle per-stage frame timing. While on, the snapshot carries a frame_profile line with rolling p50/p90/max microseconds per pipeline stage (rebuild, layout, reconcile, emit, a11y, plan, patch, encode, present, host_decode, host_draw)
+ +Commands queue as `command-.txt` entries: each `native automate ` claims the next sequence number exclusively (rapid back-to-back invocations can never overwrite each other), the app consumes one entry per presented frame in strict arrival order, and deleting the entry is the consumption ack. The queue is bounded to a handful of entries — a writer finding it full retries until the app drains a slot and fails loudly (non-zero exit) if it never does. `native automate ` prints `delivered -> ` only after the app consumed its entry, so a dead or frozen app is always a loud failure, never a silently dropped command. + +Widget verbs and `screenshot` address a `gpu_surface` view by its label across ALL open windows — snapshots enumerate every window's views and widgets, so a model-declared secondary window's canvas (a settings window) is drivable and capturable exactly like the main one, with no window argument. + +## CLI usage + +The `native automate` subcommand interacts with the automation directory: + +```bash +# Wait for the app to be ready (polls snapshot.txt for ready=true) +native automate wait + +# Assert on the snapshot: each argument is a regex that must match +# (polls up to --timeout-ms, default 30000; --absent inverts) +native automate assert 'gpu_nonblank=true' 'role=button name="Reset"' +native automate assert --absent 'error event=' + +# List running automation-enabled apps +native automate list + +# Dump the current snapshot +native automate snapshot + +# Render a gpu_surface view to screenshot-main-canvas.png +native automate screenshot main-canvas + +# Reload the WebView +native automate reload + +# Resize the main window surface +native automate resize 900 640 + +# Dispatch command-source events +native automate menu-command app.refresh +native automate native-command app.refresh refresh-button +native automate shortcut app.refresh + +# Drive retained canvas widgets +native automate widget-action canvas 2 press +native automate widget-click canvas 3 +native automate widget-hold canvas 3 # press-and-hold (on_hold) +native automate widget-context-press canvas 3 # right-click (presents the menu) +native automate widget-context-menu canvas 3 1 # pick declared menu item 1 + +# Drive focus between native controls and WebViews +native automate focus refresh-button +native automate focus-next +native automate focus-previous + +# Toggle per-stage frame timing; the snapshot then carries a +# frame_profile line (rolling p50/p90/max us per pipeline stage) +native automate profile on +native automate snapshot | grep -o 'frame_profile.*' +native automate profile off + +# Send a bridge command and get the response +native automate bridge '{"id":"1","command":"native.ping","payload":{"source":"automation"}}' +``` + +## Testing with automation + +The WebView and native-shell smoke build steps demonstrate full automation test flows: + +1. Build and start the app with `-Dautomation=true` +2. Run `native automate wait` to block until the app is ready +3. Run `native automate snapshot` to verify window, source, and native/WebView metadata +4. Run `native automate resize ...`, `native automate bridge '...'`, focus traversal, or command-source actions such as `native automate native-command ...` +5. Verify bridge responses, relayout bounds, focus state, and updated snapshots + +```bash +zig build test-webview-smoke -Dplatform=macos +``` + +## Write-back + +Because views are data, automation can go the other way too: select a widget in the running app, jump to the markup that authored it, and write an edit back into the source file — the app's own hot-reload watch picks the change up and repaints. + +The read half is `provenance`. Every markup-built widget's structural id maps back to its authored source, captured at view build time: the file, the node's byte span and line:column, the template instantiation chain (a widget inside a template reports both its definition site and every `` that put it there), and the iteration keys that say which `` row it is. Widgets built with the Zig builder report `authored=zig` honestly — write-back edits markup files only. + +```bash +# Where does this widget come from? (id from the snapshot, or hit-test a point) +native automate provenance kanban-canvas 6624116744891006388 +native automate provenance kanban-canvas at 760 30 +``` + +The write half is `edit`: typed, minimal-diff operations on the file the widget came from. An operation changes only bytes inside the target node's span — whitespace, comments, attribute order, and every other node survive byte for byte, proven by reparsing and diffing the parse trees before anything is written. + +```bash +native automate edit kanban-canvas 6624116744891006388 set-text "Add task" +native automate edit kanban-canvas 6624116744891006388 set-attr variant secondary +native automate edit kanban-canvas 6624116744891006388 remove-attr variant +``` + +Edits refuse rather than guess: a widget authored in Zig, a file that changed on disk since the app loaded it (the provenance response carries the loaded bytes' hash, so concurrent edits are never clobbered), or an edit that would fail markup validation all stop with a teaching error and leave the file untouched. A successful edit needs no reload command — the markup watch (`MarkupOptions.watch_path`, a dev/Debug feature) reloads within its poll interval, so `native automate assert` on the snapshot is the way to await the repaint. + +`zig build test-writeback-smoke` (macOS) drives the whole loop against the kanban example: query provenance, flip the button label through the verb, assert the repaint, verify the byte-exact diff, and flip it back. + +## Custom directory + +Pass a custom path to `automation.Server.init()`: + +```zig +const server = native_sdk.automation.Server.init(io, "/tmp/my-app-automation", "My App"); +``` + +The CLI reads from the default `.zig-cache/native-sdk-automation` unless you specify a directory via the automation subcommand. diff --git a/docs/src/app/bridge/builtin-commands/layout.tsx b/docs/src/app/bridge/builtin-commands/layout.tsx new file mode 100644 index 0000000..aa1af20 --- /dev/null +++ b/docs/src/app/bridge/builtin-commands/layout.tsx @@ -0,0 +1,7 @@ +import { pageMetadata } from "@/lib/page-metadata"; + +export const metadata = pageMetadata("bridge/builtin-commands"); + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/docs/src/app/bridge/builtin-commands/page.mdx b/docs/src/app/bridge/builtin-commands/page.mdx new file mode 100644 index 0000000..5144de0 --- /dev/null +++ b/docs/src/app/bridge/builtin-commands/page.mdx @@ -0,0 +1,498 @@ +# Builtin Commands + +The Native SDK provides built-in bridge commands for app command routing, window management, generic native views, layered WebViews, platform support queries, native dialogs, selected OS capabilities, and credential storage. These are controlled by the `builtin_bridge` policy in `RuntimeOptions`, separate from app-defined bridge handlers. + +## Command Routing + + + + + + + + + + + + + + + + + + + + + +
CommandRequired permissionDescription
native-sdk.command.invokecommandDispatch an Event.command into the app runtime from the calling WebView
native-sdk.command.listcommandList the manifest command catalog loaded into the runtime
+ +Command routing is available through `window.zero.commands.invoke(...)` when `js_window_api` is `true`. The manifest command catalog is available through `window.zero.commands.list()`. When runtime permissions are configured, command helpers require the `command` permission; the legacy `window` permission is still accepted for compatibility. The runtime emits a `CommandEvent` with `source = .bridge`, the calling `window_id`, and the calling view label when the request comes from a named child WebView. + +Native controls can also bind a `command` when created with `runtime.createView(...)` or `window.zero.views.create(...)`. On macOS, Linux, and Windows system WebView builds, native button-style controls dispatch the command with the native `window_id` and the originating view label. Controls inside a toolbar report `source = .toolbar`; other native controls report `source = .native_view`. + +## Platform Support Commands + + + + + + + + + + + + + + + + +
CommandRequired permissionDescription
native-sdk.platform.supportswindowReturn whether the current platform and web engine support a feature
+ +Platform support queries are available through `window.zero.platform.supports(...)` when `js_window_api` is `true`. The command accepts feature names from `PlatformFeature`, including `main_webview`, `child_webviews`, `native_views`, `native_control_commands`, `menus`, `tray`, `shortcuts`, `dialogs`, `clipboard_text`, `clipboard_rich_data`, `open_url`, `reveal_path`, `notifications`, `recent_documents`, `credentials`, `file_drops`, `app_activation_events`, and `gpu_surfaces`. JavaScript callers can also use camelCase aliases such as `mainWebView`, `nativeControlCommands`, `clipboardRichData`, `recentDocuments`, `fileDrops`, `appActivationEvents`, and `gpuSurfaces`. The helper accepts either a string or a selector object with `feature` or `name`; raw bridge payloads may use the same fields. Use an explicit `builtin_bridge` policy when you want per-command origin lists. + +## Window commands + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandRequired permissionDescription
native-sdk.window.listwindowList all open windows
native-sdk.window.createwindowCreate a new window
native-sdk.window.focuswindowFocus a window by ID
native-sdk.window.closewindowClose a window by ID
+ +Window commands are available through `window.zero.windows.*` when `js_window_api` is `true`, but the runtime still checks the request origin and the `window` permission when permissions are configured. Use an explicit `builtin_bridge` policy when you want per-command origin lists. + +## View Commands + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandRequired permissionDescription
native-sdk.view.createviewCreate a generic native view or WebView-backed view in the calling window
native-sdk.view.listviewList generic views and WebViews in the calling window
native-sdk.view.updateviewPatch frame, layer, visibility, enabled state, role, text, command, or WebView URL
native-sdk.view.setFrameviewMove or resize a view
native-sdk.view.setVisibleviewShow or hide a view
native-sdk.view.focusviewFocus a view when the backend supports native focus for that kind
native-sdk.view.focusNext / native-sdk.view.focusPreviousviewMove focus through visible, enabled native controls and WebView-backed views
native-sdk.view.closeviewClose a generic view or child WebView
+ +View commands are available through `window.zero.views.*` when `js_window_api` is `true`. They use origin checks and the `view` permission when runtime permissions are configured; the legacy `window` permission is still accepted for compatibility. `windowId` must match the calling window when provided. The `update(label, patch)`, `focus(label)`, and `close(label)` helpers accept string labels for the calling window; pass selector objects when you need an explicit `windowId`. View responses include frame, layer, visibility, enabled, focus, role, accessibility label, text, command, cursor, and open state. GPU responses also include surface presentation fields, input latency budget fields, retained canvas frame counters, and retained widget counters. `kind: "webview"` routes through the WebView backend. The macOS, Linux, and Windows system-WebView backends support native `toolbar`, `titlebar_accessory`, `sidebar`, `statusbar`, `split`, `stack`, `button`, `icon_button`, `list_item`, `checkbox`, `toggle`, `segmented_control`, `text_field`, `search_field`, `label`, `spacer`, and `progress_indicator` views. The macOS system-WebView backend also supports `gpu_surface`; Chromium hosts and unsupported native kinds return explicit unsupported-backend errors until implemented. + +## WebView Commands + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandRequired permissionDescription
native-sdk.webview.createwindowCreate a named WebView in a window
native-sdk.webview.listwindowList WebViews in the calling window
native-sdk.webview.setFramewindowMove or resize a WebView
native-sdk.webview.navigatewindowNavigate a WebView
native-sdk.webview.setZoomwindowSet page zoom from 0.25 to 5.0
native-sdk.webview.setLayerwindowChange native stack order
native-sdk.webview.closewindowClose a WebView
+ +WebView commands are available through `window.zero.webviews.*` when `js_window_api` is `true`. They use the same origin and `window` permission checks as window commands. WebView URLs are also checked against `security.navigation.allowed_origins`. Backend-specific gaps reject with `invalid_request` and an actionable unsupported-backend message. + +If `windowId` is omitted, the runtime uses the window that sent the bridge message. If provided, `windowId` must match that same calling window. Child WebViews are isolated by default and receive `window.zero` only when created with `bridge: true`. The startup WebView is always listed as `main`; that label is reserved and cannot be used for child WebView creation. + +## Dialog commands + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandRequired permissionDescription
native-sdk.dialog.openFiledialogShow a file open dialog
native-sdk.dialog.saveFiledialogShow a file save dialog
native-sdk.dialog.showMessagedialogShow a message dialog
+ +Dialog commands are **always default-deny** and require an explicit `builtin_bridge` policy. + +## OS Commands + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandRequired permissionDescription
native-sdk.os.openUrlnetworkOpen an allowed http:// or https:// URL in the system browser
native-sdk.os.showNotificationnotificationsShow a native system notification with a title, optional subtitle, and optional body
native-sdk.os.revealPathfilesystemReveal a local file or folder path in the platform file manager
native-sdk.os.addRecentDocumentfilesystemAdd a local path to the platform recent documents list
native-sdk.os.clearRecentDocumentsfilesystemClear recent documents registered by the application where the platform supports it
+ +OS commands are **always default-deny** and require an explicit `builtin_bridge` policy. `native-sdk.os.openUrl` also checks `security.navigation.external_links`; the URL must match the external link allowlist before the platform service is called. macOS, Linux, and Windows system WebView hosts implement `openUrl`, `revealPath`, notifications, and recent-document commands; macOS Chromium also implements the current OS command set. Other platform hosts return `invalid_request` with the standard unsupported-service message until implemented. + +## Credential Commands + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandRequired permissionDescription
native-sdk.credentials.setcredentialsStore a secret by service and account
native-sdk.credentials.getcredentialsRead a secret by service and account, returning null when it is missing
native-sdk.credentials.deletecredentialsDelete a stored secret by service and account
+ +Credential commands are **always default-deny** and require an explicit `builtin_bridge` policy. The macOS system WebView and macOS Chromium hosts store secrets in Keychain. The Linux system WebView host stores secrets through Secret Service/libsecret when available. The Windows system WebView host stores secrets in Credential Manager. Other platform hosts return `invalid_request` with the standard unsupported-service message until implemented. + +## Clipboard Commands + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandRequired permissionDescription
native-sdk.clipboard.readTextclipboardRead text/plain from the system clipboard
native-sdk.clipboard.writeTextclipboardWrite text/plain to the system clipboard
native-sdk.clipboard.readclipboardRead clipboard data by MIME type and return {'{ mimeType, data }'}
native-sdk.clipboard.writeclipboardWrite clipboard data by MIME type
+ +Clipboard commands are **always default-deny** and require an explicit `builtin_bridge` policy. Supported clipboard hosts provide text/plain through the existing text clipboard path. macOS system WebView, macOS Chromium, Linux system WebView, and Windows system WebView also support text/html, text/rtf, and application/rtf. + +## Enabling builtin commands + +```zig +const app_permissions = [_][]const u8{ + native_sdk.security.permission_command, + native_sdk.security.permission_view, + native_sdk.security.permission_dialog, + native_sdk.security.permission_window, + native_sdk.security.permission_network, + native_sdk.security.permission_filesystem, + native_sdk.security.permission_clipboard, + native_sdk.security.permission_notifications, + native_sdk.security.permission_credentials, +}; + +.security = .{ + .permissions = &app_permissions, + .navigation = .{ + .allowed_origins = &.{ "zero://app" }, + .external_links = .{ + .action = .open_system_browser, + .allowed_urls = &.{ "https://example.com/docs/*" }, + }, + }, +}, +.builtin_bridge = .{ + .enabled = true, + .commands = &.{ + .{ .name = "native-sdk.window.list", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.command.invoke", .permissions = .{ "command" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.command.list", .permissions = .{ "command" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.platform.supports", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.window.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.view.create", .permissions = .{ "view" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.view.list", .permissions = .{ "view" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.view.update", .permissions = .{ "view" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.view.setFrame", .permissions = .{ "view" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.view.setVisible", .permissions = .{ "view" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.view.focus", .permissions = .{ "view" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.view.focusNext", .permissions = .{ "view" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.view.focusPrevious", .permissions = .{ "view" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.view.close", .permissions = .{ "view" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.webview.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.webview.list", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.webview.setFrame", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.webview.navigate", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.webview.setZoom", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.webview.setLayer", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.webview.close", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.dialog.openFile", .permissions = .{ "dialog" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.dialog.saveFile", .permissions = .{ "dialog" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.dialog.showMessage", .permissions = .{ "dialog" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.os.openUrl", .permissions = .{ "network" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.os.showNotification", .permissions = .{ "notifications" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.os.revealPath", .permissions = .{ "filesystem" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.os.addRecentDocument", .permissions = .{ "filesystem" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.os.clearRecentDocuments", .permissions = .{ "filesystem" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.clipboard.readText", .permissions = .{ "clipboard" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.clipboard.writeText", .permissions = .{ "clipboard" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.clipboard.read", .permissions = .{ "clipboard" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.clipboard.write", .permissions = .{ "clipboard" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.credentials.set", .permissions = .{ "credentials" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.credentials.get", .permissions = .{ "credentials" }, .origins = .{ "zero://app" } }, + .{ .name = "native-sdk.credentials.delete", .permissions = .{ "credentials" }, .origins = .{ "zero://app" } }, + }, +}, +``` + +## JavaScript usage + +```javascript +await window.zero.windows.create({ + label: "tools", + title: "Tools", + width: 420, + height: 320, +}); + +const preview = await window.zero.webviews.create({ + label: "preview", + url: "https://example.com", + frame: { x: 24, y: 24, width: 480, height: 320 }, + layer: 10, + bridge: false, +}); + +await preview.setZoom(1.25); +await preview.setLayer(20); +await preview.close(); + +const toolbar = await window.zero.views.create({ + label: "toolbar", + kind: "toolbar", + frame: { x: 0, y: 0, width: 720, height: 44 }, + role: "toolbar", +}); + +await window.zero.views.create({ + label: "refresh", + kind: "button", + parent: "toolbar", + frame: { x: 12, y: 8, width: 96, height: 28 }, + accessibilityLabel: "Refresh workspace", + text: "Refresh", + command: "app.refresh", +}); + +await toolbar.setVisible(false); + +await window.zero.commands.invoke("app.save"); +const commands = await window.zero.commands.list(); + +const hasNativeViews = await window.zero.platform.supports("native_views"); + +const files = await window.zero.invoke("native-sdk.dialog.openFile", { + title: "Select a file", + allowMultiple: true, +}); + +const result = await window.zero.invoke("native-sdk.dialog.showMessage", { + style: "warning", + title: "Confirm", + message: "Are you sure?", + primaryButton: "Yes", + secondaryButton: "No", +}); + +await window.zero.os.openUrl("https://example.com/docs/start"); +await window.zero.os.showNotification({ + title: "Build finished", + subtitle: "native-sdk", + body: "All checks passed.", +}); +await window.zero.os.revealPath("/Users/me/Downloads/report.pdf"); +await window.zero.os.addRecentDocument("/Users/me/Downloads/report.pdf"); +await window.zero.os.clearRecentDocuments(); + +await window.zero.clipboard.writeText("Copied from Native SDK"); +const text = await window.zero.clipboard.readText(); +await window.zero.clipboard.write({ + mimeType: "text/html", + data: "Copied from Native SDK", +}); +const html = await window.zero.clipboard.read({ mimeType: "text/html" }); + +await window.zero.credentials.set({ + service: "com.example.app", + account: "current-user", + secret: "session-token", +}); +const token = await window.zero.credentials.get({ + service: "com.example.app", + account: "current-user", +}); +await window.zero.credentials.delete({ + service: "com.example.app", + account: "current-user", +}); +``` + +See also: [Multiple WebViews](/webviews) for frame and layer semantics, [Dialogs](/dialogs) for the full dialog type reference, [Capabilities](/capabilities) for OS capability support, and [Security](/security) for policy details. diff --git a/docs/src/app/bridge/layout.tsx b/docs/src/app/bridge/layout.tsx new file mode 100644 index 0000000..e646967 --- /dev/null +++ b/docs/src/app/bridge/layout.tsx @@ -0,0 +1,7 @@ +import { pageMetadata } from "@/lib/page-metadata"; + +export const metadata = pageMetadata("bridge"); + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/docs/src/app/bridge/page.mdx b/docs/src/app/bridge/page.mdx new file mode 100644 index 0000000..629f250 --- /dev/null +++ b/docs/src/app/bridge/page.mdx @@ -0,0 +1,178 @@ +# Bridge + +For apps that [embed web content](/frontend), the bridge connects JavaScript in the WebView to native Zig handlers via JSON messages. Native-rendered apps have no bridge — markup dispatches typed messages straight into `update` (see [App Model](/app-model)). + +## Architecture + +``` +WebView JS Zig Runtime +────────── ─────────── +window.zero.invoke(cmd, payload) + │ │ + ├──── JSON message ───────────►│ + │ Size check (16 KiB max) + │ Policy check (origin + permissions) + │ Handler lookup + execute + │◄─── JSON response ──────────┤ +``` + +## Defining a handler + +```zig +fn ping(context: *anyopaque, invocation: native_sdk.bridge.Invocation, output: []u8) anyerror![]const u8 { + _ = invocation; + const self: *App = @ptrCast(@alignCast(context)); + self.ping_count += 1; + return std.fmt.bufPrint(output, "{{\"message\":\"pong\",\"count\":{d}}}", .{self.ping_count}); +} +``` + +The handler writes its JSON result into the provided `output` buffer (max 12 KiB) and returns a slice of it. Results must be valid JSON values; invalid raw text is rejected with `handler_failed`. When returning user data as a string, use the bridge helper so quotes and control characters are escaped: + +```zig +return native_sdk.bridge.writeJsonStringValue(output, user_supplied_name); +``` + +## Wiring the dispatcher + +```zig +fn bridge(self: *App) native_sdk.BridgeDispatcher { + self.handlers = .{.{ .name = "native.ping", .context = self, .invoke_fn = ping }}; + return .{ + .policy = .{ .enabled = true, .commands = &policies }, + .registry = .{ .handlers = &self.handlers }, + }; +} +``` + +## Calling from JavaScript + +```javascript +const result = await window.zero.invoke("native.ping", { source: "webview" }); +console.log(result); // { message: "pong from Zig", count: 1 } +``` + +## Invocation + +When a handler is called, it receives an `Invocation` with: + +- `request.id` -- caller-provided request ID (max 64 bytes) +- `request.command` -- command name (max 128 bytes, no `/` or spaces) +- `request.payload` -- JSON payload string +- `source.origin` -- origin of the requesting page (e.g. `zero://app`) +- `source.window_id` -- which window sent the request + +## Size limits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ConstantValue
max_message_bytes16 KiB
max_response_bytes16 KiB
max_result_bytes12 KiB
max_id_bytes64
max_command_bytes128
+ +## Error codes + +When a bridge call fails, the JS promise rejects with an error containing a `code` field: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeCause
invalid_requestMalformed JSON message
unknown_commandNo handler registered
permission_deniedOrigin or permission check failed
handler_failedHandler returned an error
payload_too_largeMessage exceeds 16 KiB
internal_errorUnexpected runtime error
+ +```javascript +try { + const result = await window.zero.invoke("native.ping", {}); +} catch (error) { + console.error(error.code, error.message); +} +``` + +## Bridge types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDescription
BridgeDispatcherCombines policy and registry
BridgePolicyWhether the bridge is enabled and which commands are allowed
BridgeCommandPolicyPer-command: name, permissions, origins
BridgeRegistryMaps command names to handler functions
BridgeHandlername, context, invoke_fn
+ +See also: [Builtin Commands](/bridge/builtin-commands) for `native-sdk.command.*`, `native-sdk.window.*`, `native-sdk.view.*`, `native-sdk.webview.*`, `native-sdk.dialog.*`, `native-sdk.os.*`, `native-sdk.clipboard.*`, and `native-sdk.credentials.*`. diff --git a/docs/src/app/building-components/layout.tsx b/docs/src/app/building-components/layout.tsx new file mode 100644 index 0000000..1ada31b --- /dev/null +++ b/docs/src/app/building-components/layout.tsx @@ -0,0 +1,7 @@ +import { pageMetadata } from "@/lib/page-metadata"; + +export const metadata = pageMetadata("building-components"); + +export default function BuildingComponentsLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/docs/src/app/building-components/page.mdx b/docs/src/app/building-components/page.mdx new file mode 100644 index 0000000..d23685b --- /dev/null +++ b/docs/src/app/building-components/page.mdx @@ -0,0 +1,166 @@ +# Building Components + +The library's built-ins cover the common register, and [theming](/theming) restyles all of them at once. This page is about the pieces the library does not hand you: how to build a component of your own — first as a markup template, then as a Zig view function when the shape needs one — how it themes, and how component files spread across an app. Component code is toolkit-extension territory, so the Zig here applies whatever language the app core is written in: a TypeScript app that needs one custom widget writes that widget in Zig and keeps its core in TypeScript. The mechanics (template grammar, import rules, slots) are specified in [Native UI](/native-ui#templates); this page builds one real component end to end. + +The ownership model in one line: **use and theme the built-ins by default; eject a library composite when you need to own its shape; build new composites from primitives when the library has no shape for it.** The last two are this page. + +## A component in markup + +A dashboard needs the same labeled stat tile three times. That repetition is a `