Releases: vercel-labs/native
Releases · vercel-labs/native
Release list
v0.5.3
New Features
<media-surface>— the dynamic texture channel: a new element compositing textures produced outside the widget tree (video decoders, camera pipelines, external renderers like mpv) into the layout like any widget, with a stable Zig-tier producer API (runtime.acquireMediaSurfaceProducer) pushing RGBA8 frames from any thread — latest-wins, damage-tracked, paced by the compositor's presented-frame clock.- Pushes wake an idle compositor: a push staging new bytes requests one coalesced frame through the platform's thread-safe cross-thread frame request (the automation watcher's wake path), so video keeps playing in an idle demand-driven app and a late-starting producer is adopted promptly; damage-skipped and stale-handle pushes wake nothing, and teardown disarms the binding under the same fence the wake call holds, so an orphaned producer can never wake a dead host.
- Markup, both engines, and tooling:
surface="{binding}"binds the model-owned u64 surface id in the runtime-image-id grammar (binding-only, required, media-surface-scoped with teaching errors), wired through the validator, the compiled and interpreter engines,native check's model contract, LSP hover docs, and a docs component page plus a "Media Producers" recipe. - Deterministic by policy: texture contents are presentation chrome — goldens, reference screenshots, and session fingerprints see only the surface's id-derived placeholder, so a session recorded with a live producer replays fingerprint-identical with no producer attached; live GPU hosts composite the real texture through the existing image upload pipeline.
- Reserved id namespace: bit 63 of the ImageId space now belongs to media-surface textures;
registerCanvasImagerejects ids with it set (error.InvalidImageId) so producer textures and registered images can never collide. - Cover fit stays inside the frame: a
cover-fit media surface carries the image widget's rectangular clip around its texture draw, so the fit-expanded texture can never paint over siblings on hosts that only mask corner radii. - Adopted-texture memory is on-demand: each channel's texture buffer is one lazy frame-budget allocation from the new
Runtime.Options.allocatorat first adoption (freed by the newRuntime.deinit), so a runtime with no media producers carries zero media-texture bytes — an embedded pool would have put 32 MiB in every Runtime (measured on the docs wasm preview host: 169.5 MB → 137.5 MB per component tile). The allocator freezes into the runtime at init: mutatingoptions.allocatoron a live runtime never retargets ownership, so allocations and their frees always pair on one allocator. - Trackpad pinch reaches apps: pinch-to-zoom now flows from the macOS host (
magnifyWithEvent:) through phase-explicitpinch_begin/pinch_change/pinch_endinput events into a view-global app channel — Zig cores declareOptions.on_pinch, TypeScript cores exportpinchMsg(pinch)withPinchPhase/PinchEventin@native-sdk/core/events; each change carries a multiplicative delta (cumulative gesture scale is the product of1 + scale, applied memorylessly —zoom *= 1 + scale) and the pointer anchor rides view-localx/y(the pointer location during the gesture — zoom-at-cursor anchoring, not a between-the-fingers midpoint); every event names its source window and view (window_id/labelin Zig,windowId/labelin TypeScript), so multi-window apps tell pinches apart; a terminal Ended/Cancelled event that still measured a nonzero delta arrives as one last change before the end, so the product always matches what the OS reported. On macOS the delta is AppKit's raw per-eventNSEvent.magnification, forwarded untransformed: raw magnification IS the multiplicative per-event delta — the convention every browser engine ships — so the product of1 + scaleis the zoom users already experience for the same gesture in Safari and Chrome. The one guard is a per-event floor: a single event's magnification at or below -1 (a zoom inverting through zero scale — physically impossible, only a driver glitch could report it) clamps just above -1, so every emitted factor stays positive. Windows precision-touchpad and GTK gesture sources are staged follow-ups. widget-pinchautomation verb:native automate widget-pinch <view-label> <scale> [x y]drives the real pinch event stream without a trackpad (<scale>is the gesture's FINAL multiplicative zoom — one change carryingscale - 1, anchor point defaulting to the view center), journaled like every synthesized input so recorded sessions replay the identical zoom. Automation protocol bumps to v7.- Session journal v5: gpu-surface input records gain the pinch
scalefield and the pinch kinds; readers refuse v4 journals loudly in both directions, per the format's skew discipline — re-record sessions with this build.
Bug Fixes
- Registered CJK fonts render dense glyphs: glyph outline budgets are now sized from real CJK faces (Noto Sans JP/SC/TC/KR measured; 1024 points / 128 contours per glyph), so everyday dense kanji like 鬱 ink as real outlines instead of notdef blocks; the per-font registration size bound rose to 24 MiB so full CJK faces register.
- Glyph complexity validates at registration, not render: a face whose
maxpdeclares glyphs denser than the outline budgets — simple maxima and flattened-composite maxima (maxCompositePoints/maxCompositeContours) alike — is refused at registration with a teaching that names its numbers against the budgets (error.FontExceedsGlyphBudgets), instead of silently degrading individual glyphs to blocks at render time. - Font bytes are heap-allocated on demand: the registry copies each registered file into an exact-size allocation from the runtime's new
Options.allocator(freed by the newRuntime.deinit), so the 24 MiB bound is validation, not a storage reservation — a runtime with no registered fonts carries zero font bytes, where a reservation-shaped pool would have embedded 192 MiB in every Runtime (measured on the docs wasm preview host: 313.5 MB → 121.5 MB per component tile). EmbeddedApp.deinitcompletes the embed lifecycle: direct embedders end an embedded app withdefer embedded.deinit()(idempotent), which returns the runtime's heap-owned registrations — without it, a host creating and destroying apps in one process leaked the registered font storage per cycle. The wrapper hosts and the C ABI'snative_sdk_app_destroyroute their teardown through the same deinit, one lifecycle owner.- Glyph raster budgets are derived from the registration gate: the vector core's glyph-fill capacities (
GlyphRasterizer: 18,560 edges/crossings, flattening clamped at 16 segments per curve) are computed from the outline budgets registration admits, so a truthfully-declared budget-maximal glyph — a 1024-point zigzag contour included — rasterizes at any size instead of hittingVectorPathTooComplexand degrading to the block fallback the gate promises cannot happen; the clamp binds only above ~128-px ems and keeps the polyline within 0.2% of the em, so existing renders are byte-identical. - Single-line fields never hold or paint line breaks: pasting multi-line text into an input, text field, search field, or combobox now strips the line breaks at the edit seam (the HTML value-sanitization rule — lines join with nothing between them), covering clipboard paste from the shortcut and the context menu, typed and automation
text_input, and IME composition, with the app'son_inputhearing the same sanitized bytes the editor applied; a paste of only newlines inserts nothing. - Defensive render containment: a single-line value that still holds a
\n(a model-set value, an old journal) now paints as one line — breaks present as spaces — under a forced content-rect clip, so text can never escape the field's rounded border on any renderer.
Contributors
v0.5.2
New Features
- Anchored tooltips with hover intent:
<tooltip anchor="above|below">beside its trigger in a stack floats against the trigger and hands visibility to the runtime — it shows after the pointer has rested on the trigger for the show delay (default 600ms) and hides on leave, so sweeping a toolbar flashes nothing; after a pointer-hovered tooltip hides on leave, a shared 400ms warm window shows the next trigger's tooltip instantly (the other hide causes below — focus moving on, Escape, a press, view blur — deliberately open no warm window). Delay, warm window, and the behaviors below match shadcn/ui's defaults (Base UI). The model never hears hover, both engines lower it identically, and every transition steps on the recorded input/frame clock, so recorded hover-dwell sessions replay their show/hide frames byte-identically. tooltip-delayattribute: per-tooltip show delay in milliseconds (0shows the instant the trigger is hovered); absent follows the newtooltip_show_delay_ms/tooltip_warm_window_msmetric tokens. Registry attr code 80; static (non-anchored) tooltips keep their classic paint-when-rendered behavior.- Keyboard focus reveals immediately: tabbing onto a tooltip-owning trigger shows its tooltip with no dwell (keyboard navigation is deliberate, and content revealed on hover or focus must not depend on pointer timing); focus moving on or Escape hides it just as immediately, without warming the pointer's skip window.
- Hoverable content: a shown tooltip's own bounds hold it open (WCAG 1.4.13; Base UI's
hoverabledefault) — the pointer can cross the anchor gap into the tooltip along a bounded safe-polygon corridor, and it hides only after leaving both the trigger and the tooltip (with the usual warm window); the corridor resolves on the recorded frame clock, so replays stay byte-identical. - Scroll steps the machine: every scroll path (wheel, kinetic steps, native drivers, keyboard scrolling) routes its hover change through the same intent transition a pointer move takes — a trigger scrolled out from under the pointer disarms/hides, and a trigger scrolled under it arms per normal.
- Press dismisses: ANY pointer-down — primary or secondary, including downs consumed by the context-menu gesture or a window-drag region — or Space/Enter on the focused trigger cancels a pending reveal, dismisses a shown tooltip, and closes the warm window, so an activated control never re-explains itself on the post-click hover. Keyboard activation and Escape also spend the standing focus reveal: a rebuild that replaces or rekeys the tooltip (the activation's own model update, typically) cannot resurrect it — it stays down while focus rests on the trigger, until the keyboard genuinely leaves and returns; the focus ring stays painted, and a later pointer hover re-earns the dwell normally.
- Rebuild hygiene: a rebuild that removes, rekeys, disables, or re-parents a tooltip's trigger resets that tooltip's armed/shown/warm state and re-stamps it hidden, even when the tooltip node itself survives.
- View blur resets: a canvas view that loses focus (to a sibling view or with the window) drops its entire tooltip conversation — armed delay, shown tooltip (keyboard- and pointer-owned), warm window — and re-stamps hidden, so no stale tooltip floats in a view the keyboard left. The window key-loss reset fires on the flag's own focused→unfocused edge, so it holds however the host announces the change: one gain event (macOS) or loss-before-gain (Windows, GTK), including a loss with no gain at all (every window inactive).
Improvements
- Search empty state names its scope: the system monitor's no-match state now says search only sees the top 128 processes by CPU, so a miss on a quiet process reads as scope, not absence (both tiers).
Bug Fixes
- Dark-scheme accent focus rings settle down — and stay visible: a
theme_accent(orcanvas.accentOverrides) now derives its dark-appearance focus ring at half the accent's saturation instead of the raw brand hue, contrast-floored at 3:1 (WCAG non-text) against the lightest dark tone controls commonly sit on (the house muted surface#262626— rings draw outside controls, so clearing the lightest adjacent container tone clears the page background and card surface too, in both shipped packs) — desaturation alone can cost a deep accent the bar it cleared (#008000fell from 3.9:1 to ~2.6:1 on the background; the floor lifts it back over 3:1 on background and card surface alike) — so the soundboard search field's ring no longer glares neon in dark mode;canvas.accentFocusRingexports the derivation so hand-authored token sets (the Zig soundboard's theme) state the identical ring. - Breaking:
canvas.accentOverridesnow takes the resolvedColorSchemealongside the accent — a deliberate break while the toolkit is pre-1.0, so the one function under the natural name states the scheme it layers over; pass.lightto reproduce the previous output exactly. - Escape in a search field now reaches your core: Escape's clear (and its composition cancel) was a runtime-local editor operation — the field emptied on screen while the model kept the stale query and the list stayed filtered. Every keyboard-driven editor mutation now derives ONE edit that is applied to the retained editor AND stamped onto the dispatched event, so
on-inputhears Escape exactly like typing, paste, and the clear affordance — on both authoring tiers, and byte-identically under record→replay. - Automation and accessibility composition verbs ride the real input path:
widget-action set_composition/commit_composition/cancel_compositionnow dispatch the same ime input events a live IME session produces (journaled, mirrored to the core), andset_selectionreaches the core's selection mirror through the stamped-edit channel. - Accessibility actions replay without double-dispatch: a journaled assistive action (press, toggle, set_text, drag, ...) no longer also journals the key/text events its verb synthesizes — replaying the action re-derives them, so recorded AX-driven sessions replay each input exactly once instead of twice.
- Direct verb calls journal outer-wins too: an embed host's
widgetActionand automationwidget_actioncommands now record the same singlewidget_accessibility_actionrecord the platform accessibility path does (the enum gained the composition kinds), so replay re-runs the verb — focus included — instead of delivering its untargeted key/ime children to whichever field the session happened to leave focused. - Grids keep their declared column slots: children fewer than a grid's declared
columnsnow keep the column-slot width and fill the leading slots, instead of stretching across the freed row — a search that narrows the soundboard album grid below its column count leaves image-forward tiles at their natural size. - "terminate request delivered" retires itself: the system monitor's delivery notice now clears on the next applied sample instead of sitting in the footer forever; failure notes keep sticking (both tiers).
- System monitor footer says UTC: the sample-time stamp renders from the journaled clock in UTC, and the footer now labels it "UTC" instead of passing it off as local time — local rendering would need a journaled timezone channel to stay replay-byte-identical, so the label is the honest fix (both the Zig example and the TS port).
Contributors
v0.5.1
Improvements
- The TS scaffold's status bar earns its empty state: a fresh scaffold said "stamped: -1ms" until the first Stamp press; the template's markup now branches on
{stampedMs < 0}and says "press Stamp for a timestamp" instead — teaching the if/else markup shape in the starter while it's at it.
Bug Fixes
- Exact-fit text no longer elides under geometry pixel snapping on Windows: edge snapping rounds a frame's two edges independently, so a hug-sized text box at a fractional position could come back up to a full device pixel narrower than the label it was measured for — past the elision slack, so the TS scaffold's centered counter painted "…" instead of its digit at 100% scale. The wrap/elision budget (
textWrapMaxWidth) now hands back the full snap quantum (1/scale, was 0.5/scale), and the epsilon policy is documented at the seam: painted width may exceed the snapped frame by less than1/scale + text_elision_slack, always below the smallest real overflow (a glyph). - Spawn teardown crash window closed: the effects channel now joins every spawn, fetch, and file worker thread that converges before its teardown returns (previously it gave up after ~5s and abandoned them), so cancelling or quitting while a real child is still streaming can no longer leave a stale worker writing into freed memory.
- Spawn cancel reaches the whole process tree: each spawned child runs in its own process group and cancel/teardown signals the group (POSIX), so shell-wrapped commands (
sh -c "a; b") no longer leave orphaned grandchildren holding the stream open past the cancel. - Every worker class tears down bounded: spawn, fetch, and file workers now share one terminal guarantee — teardown returns within a budget and never frees memory a live thread can still touch. A file worker stuck in blocking I/O that nothing can converge (a write to a FIFO with no reader, a stalled network filesystem), or a spawn worker held hostage by a descendant that escaped the kill's process group (
setsiddaemonization, a shell'sset -mbackground job) while holding the stdout pipe open, no longer hangs teardown: teardown interrupts the blocked syscall best-effort at half its budget and, past the full 15s, abandons the worker with one warning and a small deliberate leak. Everything an abandoned worker can still reach lives in process-lifetime storage, so the leak stays safe even when the app tears down the allocator behind the channel right after. - A fetch that cannot start cancellably is rejected, never run inline: when the executor cannot start the exchange as a cancelable task, the fetch now delivers one honest
.rejectedterminal instead of silently running an exchange that would evadecancel, the timeout, and teardown's join. - The npm-installed CLI carries its TypeScript toolchain: @typescript/typescript6 — and @typescript/old, the exactly-pinned alias of the real compiler its one-line entrypoint re-exports — are now regular dependencies of
@native-sdk/cli, installed by npm in the same transaction as the CLI itself. The firstnative check|dev|build|teston a TypeScript app needs no network and no install step, and never runs npm: it works offline right after install, on read-only (system-owned) prefixes, and underNODE_ENV=productionalike, because the bundled transpiler resolves the toolchain by node's own ancestornode_moduleswalk across every layout npm or pnpm produces. A repo checkout whosepackages/coreinstall hasn't happened yet is taught the one command (cd <sdk>/packages/core && npm ci --include=dev), and a directzig buildagainst an unresolvable toolchain fails with the same clean teaching and a resolved SDK path instead of a panic stack trace. TypeScript apps need Node.js 22.15+ (on the 23 line: 23.5+) on every layout — repo checkouts included, because every .ts module rides the samemodule.registerHookstype stripping: on a node without the hook the runner fails fast with a one-line upgrade teaching instead of node's rawERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING. - Drag headers lay out clear of the Windows caption buttons: on hidden-titlebar windows, a
window-dragheader whose app never consumed the chrome channel's trailing inset rendered right-aligned content UNDER the DWM min/max/close cluster (system-monitor-ts's status text was truncated by the caption punch-out). The runtime now detects the collision after layout and re-lays the view once with the cluster reserved (DesignTokens.window_controls, stamped liketext_measure), so drag-header content stops at the cluster's edge on every app — markup and builder, Zig and TS — while headers that already pad through the chrome insets (soundboard's trailing spacer) keep a byte-identical layout. The same mechanism covers the macOS mirror (a leading traffic-light cluster). Anchored floating children of the drag header resolve against the cleared rect too, so an end-aligned floater moves out from under the buttons just like flow content; anchored children of non-drag widgets keep byte-identical placement. - Standard titlebars follow dark mode on Windows: windows with standard chrome now set
DWMWA_USE_IMMERSIVE_DARK_MODEfrom the OS app color scheme — at creation (before first show, so a dark launch never flashes a light caption) and again on every appearance broadcast — so a dark-themed app no longer sits under a glaring white titlebar. Hidden-titlebar windows keep their higher-fidelity pixel-sampled caption color, and chromeless windows have no caption to tint. - Windows release exes are GUI-subsystem:
native build(and therefore everythingnative package --target windowswraps) no longer produces console-subsystem binaries, so launching a packaged app never flashes a terminal window behind it. Debug exes keep the console — the dev loop's logs live there — and redirected logging (app.exe > log 2>&1) still works on GUI exes. Packaging now reads the exe's PE subsystem, warns loudly when a console-subsystem binary is wrapped (stale zig-out or hand-supplied--binary), and carries the finding in the package stats, pinned by tests over synthetic PE headers. The web-frontend scaffold's standalonebuild.zig(Next/Vite/React/Svelte/Vue andnative init --full) emits the same release-only assignment, so scaffolded apps get the posture without the SDK build graph.
Contributors
v0.5.0
New Features
- TypeScript authoring — write app cores in TypeScript:
native initnow 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/corepackage (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.fetchwith routed{status, body}results, the JSON wire format as pure byte math (src/api.tsencodes requests and parseschoices[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, inzig 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 throughCmd.fetch(with a complete transpile-checked core sample), npm-heavy UIs as embedded web frontends, Node libraries asCmd.spawnsidecars, and pure utilities vendored undersrc/or imported from the curated@native-sdk/core/*channel — with the core's no-npm boundary stated as the thing that buys replay, headless testing, and native dispatch speed (#119). - Eval wave 2 — six dual-track authoring cases, one language-blind spec each: the eval harness gains
"frontend": "app-dual"cases that run the SAME realistic ask on both authoring tracks —<case>@tsscaffolds a full TypeScript app (native init --template ts-core),<case>@zigthe 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 ownnative testgraph.--track ts|zigselects a lane; each track gets its current skills (ts-core+native-uivsnative-ui+zig), andpnpm metricsnow 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 brokeon-inputresolution (runtime view build andnative check's model contract alike) for any core keeping aTextEditStatein 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 theCmd.audioPlaystream with the engine's local-then-URL cascade, the play-next queue, Copy Title on the clipboard, a motion-gatedSub.timerplayback 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), andapp.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
.themepack 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 nocachePathin the core (#119). - Transpiler emit-contract fixes: the global
undefinedVALUE now emits the optional empty (null) — it previously emitted Zigundefined, 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 declarativeSub.timer, collect-modeCmd.spawnforps/vm_stat/meminfo, pure byte parsers over the collected stdout, the exact top-128-by-CPU selection, the confirmed SIGTERM action, and a runtime boot probe that discovers the host's sampler conventions where the Zig original switches at comptime — with the honest "no sampler for this OS" state when nothing answers),src/app.native(the whole view: chromeMsg-driven hidden-inset header,<chart>sparklines over the core's NaN-padded windows, the real table register with per-row context menus and controlled scroll, the modal confirmation), andapp.zon. An end-to-end suite (zig build test-ts-core-e2e) drives the shipping core and markup through the probe cascade, the Zig example's committed real captures, the timer cadence with pause, search/sort, the kill round trip, and record→replay; the README carries an honest ledger of where the port diverges from the Zig original (#119). - Markup chart series bind f64 iterables:
<series values="{binding}">now accepts[]const f64model 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, andnative 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), ands.at(i)directly onUint8Arraytext. Every length/offset is a BYTE measure, search is byte-wise (includes/indexOf/lastIndexOfdispatch 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, andsplitreturns a locally-ownedUint8Array[]. 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/replaceAllon 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".trimAsciiSpacesstays for LF-preserving line parsing;.trim()is the canonical whitespace trim (#119). - TS cores: generics, local function values, and the complete-language tail: user-declared generic functions, interfaces, and type aliases now compile via per-call-site monomorphization from tsc's own resolved type arguments — one readable Zig fn per distinct instantiation (
pick__Task,pick__f64), deduped, covering records/unions/arrays/optionals, recursion inside a generic, generics calling generics, and structural instantiation of generic types (Box<Task>→Box__Task); unresolvable call sites teach the new NS1053 (#119). - Const-bound local function values hoist:
const scale = (x: number): number => x * 3;(arrow or function expression) becomes an ordinary module-level fn when capture-free and fully annotated, usable by direct call (recursion included) or as an array-method callback (xs.map(scale)); captures, reassignment, storing/returning the value, and record-field calls teach the new NS1054 — and capturing a locally-owned array ends its ownership at the capture (NS1051 machinery) (#119). - The small-fry tail lands with node-byte-identical pins:
for (const [i, x] of xs.entries())(the destructured-pair loop form),++/--/assignments in provably order-exact value positions (arr[i++],const n = ++count),?.[i]and?.method()optional-chain hops on supported receivers, plain `numb...
v0.4.4
New Features
- Native-only host builds (Windows): the build graph now infers web use from app.zon — a
.frontendblock, the"webview"capability, a.shellwebview 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, noWebView2Loader.dllinstalled, 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 teachingWebViewLayerNotBuilterror.native checkand 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 linkswebkitgtk-6.0nor references anywebkit_/jsc_symbol, building needs no WebKitGTK development package, and users need nolibwebkitgtkat 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 packagerefuses 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
zigskill (native skills get zig) maps each pre-0.16 std idiom's compile error to the current one —std.Iofile IO and writers, unmanagedArrayList,main(std.process.Init), spawning, clocks,{t}/{f}formatting,build.zigmodules — with the same content for humans as the docs' Zig 0.16 Notes page; the native-ui skill carries the short table, and a failingnative build|test|devnow 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
WebKitWebViewon 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
v0.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|testno longer fails withexpected path relative to build root; found absolute pathwhen the app and the npm-installed SDK live on different drives — the generated build graph now bridges volumes with a.native/sdkdirectory 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-shapenative 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
v0.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/nativerepository andnative-sdk.dev;version:syncstamps repository/homepage metadata into platform packages andversion:checkrejects 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 reportedWebViewNotFoundat runtime (#86). - WebView2 host conformance fixes: a missing lambda capture in the bridge message handler, a mingw-compatible WRL event-handler factory, an
EventTokenshim, 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 buildinstalls the architecture's loader next to the executable,zig build runresolves it during dev runs, generated frontend/package commands carryNATIVE_SDK_PATH, andnative package --target windowsincludes 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
v0.4.1
v0.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 arenative_sdk(native_sdk.addApp,native_sdk.addMobileLib), the embed C ABI prefix isnative_sdk_*, and the npm CLI package is@native-sdk/cli. - Native-rendered apps by default:
native initscaffolds a native-rendered app — a declarative.nativemarkup view plus Zig logic on theUiAppruntime (aModel, aMsgunion,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, typedon-*message dispatch,for/if/elsestructure tags (multi-childforbodies,<else>empty states), and keyed identity; a deliberately closed grammar keeps logic in Zig. - Comptime compilation: views compile at build time into direct field access — release binaries carry no parser, and markup or binding mistakes are compile errors with line and column.
- Hot reload: dev builds watch every
.nativefile — imported components and fragments embedded in Zig views included — and update the running window in place, preserving model state, selection, and widget identity. - Expressions in bindings: arithmetic, comparisons, boolean logic, string concatenation, and a closed 17-function formatting library (
fixed,thousands,date/time,pad,plural, ...), evaluated bit-identically by both markup engines; string-producing model functions bind directly through the build arena. - Cross-file components:
<import>splices template files (transitively, with cycle and duplicate diagnostics), template args take literal defaults,<slot/>marks where use-site children land, andnative eject componenttransfers 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-elementopacity/transformrender channels for animated composition.
- Native markup: HTML-inspired views with flex layout,
- The model–view contract, checked in both directions:
native checkverifies every binding path, iterable, key, message tag, payload type, and expression in every.nativefile against the app's reflectedModel/Msgsurface 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 dumpover the canonical serialized document format, and thenative-uiagent skill — the complete authoring reference, served through the skills CLI. - Two-way tooling:
native automate provenancereports where a live widget was authored (file, byte span, template instantiation chain), andnative automate editwrites minimal-diff attribute and text edits back into the markup source — validated before anything touches the file, with hot reload closing the loop. - Full component catalog: every built-in component is expressible in markup — tabs, tables, dialogs, drawers, sheets, selects, comboboxes, accordions, menus, badges, avatars, tooltips, inputs, and more — implemented in both engines with parity tests, alongside new composites in markup and Zig:
- Charts (
<chart>/ui.chart): line, area, bar, and band series drawn through the vector path pipeline with design-token colors, deterministic downsampling past 256 points, axis labels on a nice-step lattice, and pointer hover details. - Markdown (
<markdown>/native_sdk.markdown): a GitHub-flavored subset — headings, inline styles, links, lists, task lists, fenced code, blockquotes, pipe tables, autolinks, and model-driven collapsibles — that degrades malformed input to text and never fails a build. - Disclosure trees with the full ARIA tree keymap, steppers and timeline items, input groups with focus-within rings, chat bubbles with reaction pills and thread-width caps, and a
ui.navpush/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_startfor 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.
- Charts (
- 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
overflowpolicy knob keeps the deliberate hard cut available, and word wrap is an explicit opt-in — paint always agrees with measurement. heading/displaytypography 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 —
updategains 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.spawnruns subprocesses with streamed lines or whole-output collect mode (stderr tail included), raisable per-effect line bounds, and cancellation;fx.fetchruns HTTP(S) requests with an explicit failure taxonomy, timeouts, and a streaming response mode for line-oriented endpoints.fx.readFile/fx.writeFilepersistence,fx.startTimer/fx.cancelTimer,fx.writeClipboard/fx.readClipboard,fx.registerImageBytesfor runtime images,fx.closeWindow/fx.minimizeWindow, and theinit_fxboot hook so loading states are in the very first paint.- A facade time API (
nowMs,monotonicMs) plusClock/TestClockseams for deterministic time-dependent logic.
- Audio, end to end on five platforms:
fx.playAudiowith 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
bufferingstates 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.
- 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
- 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 fullychromelessstyles; markupwindow-dragregions; and anon_chromehook carrying the real overlay insets and control-cluster frames — with real system window controls preserved on Linux client-side decorations and Windows DWM caption buttons. - Native context menus, declared per widget in Zig or markup (
<context-menu>): the real OS menu where one exists, an anchored canvas surface elsewhere, editable-text cut/copy/paste defaults, and full automation support for enumerating and invoking items. - A menu-bar status item with model-driven title and menu; canvas and WebView panes composed in one window; adoption of app-owned native views into the layout (
adoptViewSurface); and native scroll drivers on macOS that give every scroll region OS momentum, rubber-band overscroll, and the system overlay scrollbar with zero app code.
- Titlebar control on all three desktops:
- **Experimental iOS and Android host tiers — the toolkit...
v0.3.0
New Features
- Keyboard shortcuts: Add app-level keyboard shortcuts with manifest and runtime configuration, native delivery to Zig
Event.shortcut, and typed JavaScriptwindow.zeroshortcut events (#62). - Manifest-driven runner shortcuts: Load
app.zonshortcuts automatically in generated runners, with aRunOptions.shortcutsoverride for apps that build shortcut lists in Zig (#62).
Improvements
- Shortcut documentation and validation: Document the
app.zonshortcut 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).