Skip to content

v0.5.0

Latest

Choose a tag to compare

@github-actions github-actions released this 13 Jul 03:47
e2627ee

New Features

  • TypeScript authoring — write app cores in TypeScript: native init now scaffolds a TypeScript app by default — src/core.ts (logic), src/app.native (view), app.zon (manifest), zero Zig to write — and the build transpiles the core to readable arena-backed Zig, so the shipped binary carries no JS engine and no GC and keeps the native dispatch path (~83ns per update). The @native-sdk/core package (the SDK cores import, plus the transpiler the CLI runs) publishes to npm with this release at the SDK's version. Zig cores remain first-class (native init --template zig-core), mixed TS-core + Zig-helper apps fall out of tree detection naturally, and a TypeScript app can eject to its emitted Zig at any time. The entries below are the pieces of this one feature (#119).
  • examples/ai-chat-ts — an AI chat client authored entirely in TypeScript + Native markup: a conversation UI over an OpenAI-compatible chat-completions endpoint as two subset modules and zero Zig — Cmd.fetch with routed {status, body} results, the JSON wire format as pure byte math (src/api.ts encodes requests and parses choices[0].message.content / error.message, refusing anything malformed), conversation history in the Model, the composer on the SDK byte-splice text engine, endpoint/model/key through the env channel (no baked endpoint, no key anywhere in the tree — a teaching state until all three arrive), a model-level in-flight guard, and honest failed states that keep the history with a Retry. The e2e battery (tests/ts-core/ai_chat_e2e_tests.zig, in zig build test-ts-core-e2e) pins the exact request bytes turn by turn and replays a recorded conversation — transport failure and retry included — byte-identically with zero network; the README states the v1 boundaries plainly (buffered responses, compile-time fetch headers) (#119).
  • Docs: "Where Packages Go" (/typescript/packages): the four first-class patterns behind "can I use npm?" — HTTP/AI APIs through Cmd.fetch (with a complete transpile-checked core sample), npm-heavy UIs as embedded web frontends, Node libraries as Cmd.spawn sidecars, and pure utilities vendored under src/ or imported from the curated @native-sdk/core/* channel — with the core's no-npm boundary stated as the thing that buys replay, headless testing, and native dispatch speed (#119).
  • Eval wave 2 — six dual-track authoring cases, one language-blind spec each: the eval harness gains "frontend": "app-dual" cases that run the SAME realistic ask on both authoring tracks — <case>@ts scaffolds a full TypeScript app (native init --template ts-core), <case>@zig the Zig app template — graded by shared checks plus per-track behavioral harnesses asserting one spec: fetch-JSON-into-a-sortable-table, debounced notes autosave (starter provided), a pomodoro timer with a completion sound, a seeded stale-cache delete bug to root-cause, a module split with byte-exact CSV export, and a shell-command system-info panel. The ts harnesses decode the Cmd/Sub wire format (evals/harness-lib/cmdview.zig); the zig harnesses ride the SDK's fake effects executor inside the workspace's own native test graph. --track ts|zig selects a lane; each track gets its current skills (ts-core+native-ui vs native-ui+zig), and pnpm metrics now reports per-track teaching-error encounters alongside first-pass compliance and the violation taxonomy (#119).
  • Transpiler: two real-app fixes the wave surfaced: byte-element stores of computed values (buf[i] = src[j]) now emit with JS's exact ToUint8 wrap instead of stopping as invalid Zig, and records referenced from a declared text-input mirror union stay by value even when the core also stores its editor state in the Model — previously that pointer promotion silently broke on-input resolution (runtime view build and native check's model contract alike) for any core keeping a TextEditState in its model (#119).
  • examples/soundboard-ts — the soundboard authored entirely in TypeScript + Native markup: the launch-bar port of the Zig soundboard as three files of truth and zero Zig — src/core.ts (the committed catalog as const tables, REAL audio through the Cmd.audioPlay stream with the engine's local-then-URL cascade, the play-next queue, Copy Title on the clipboard, a motion-gated Sub.timer playback clock, and search through the full byte-splice text engine), src/app.native (the whole view: grid, album detail, songs library, context menus, the now-playing transport), and app.zon. An end-to-end suite (zig build test-ts-core-e2e) drives the shipping core and markup through playback, auto-advance, the stale-event window, volume, clipboard, search, record→replay, and a dispatch-latency budget; the README carries an honest ledger of where the port diverges from the Zig original (#119).
  • Generated TS wiring resolves the theme and the audio cache: app.zon's .theme pack now reaches TypeScript apps (composed with the live system appearance), and the platform caches directory is resolved at launch so a core's URL audio playback caches under the conventional content-addressed path with no cachePath in the core (#119).
  • Transpiler emit-contract fixes: the global undefined VALUE now emits the optional empty (null) — it previously emitted Zig undefined, uninitialized memory — and an early-exit null guard whose narrowed value goes unread emits a plain null test instead of an unused (uncompilable) capture (#119).
  • examples/system-monitor-ts — the system monitor authored entirely in TypeScript + Native markup: the spawn-showcase port of the Zig system monitor as three files of truth and zero Zig — src/core.ts (the 2 s sampling cadence as a declarative Sub.timer, collect-mode Cmd.spawn for ps/vm_stat/meminfo, pure byte parsers over the collected stdout, the exact top-128-by-CPU selection, the confirmed SIGTERM action, and a runtime boot probe that discovers the host's sampler conventions where the Zig original switches at comptime — with the honest "no sampler for this OS" state when nothing answers), src/app.native (the whole view: chromeMsg-driven hidden-inset header, <chart> sparklines over the core's NaN-padded windows, the real table register with per-row context menus and controlled scroll, the modal confirmation), and app.zon. An end-to-end suite (zig build test-ts-core-e2e) drives the shipping core and markup through the probe cascade, the Zig example's committed real captures, the timer cadence with pause, search/sort, the kill round trip, and record→replay; the README carries an honest ledger of where the port diverges from the Zig original (#119).
  • Markup chart series bind f64 iterables: <series values="{binding}"> now accepts []const f64 model fields, decls, and fns alongside f32 — transpiled TS cores carry every number array as f64, so markup charts were unreachable from a TS model — narrowed per sample into the f32 chart pipeline in both engines, the validator, and native check's model contract (#119).
  • The everyday string methods on core bytes — familiar spellings, byte-honest semantics: TypeScript cores can now write s.toUpperCase(), s.trim().toLowerCase().includes(q), s.repeat(n), s.padStart(w), s.split(sep), s.startsWith(p), s.indexOf(t)/s.lastIndexOf(t), and s.at(i) directly on Uint8Array text. Every length/offset is a BYTE measure, search is byte-wise (includes/indexOf/lastIndexOf dispatch by argument type: bytes needle = substring search, number = the TypedArray element search), case mapping is Unicode 17 SIMPLE case mapping (locale-free, generated tables — 3.1 KiB in the binary — with invalid UTF-8 passing through unchanged), trim strips the exact JS whitespace set over UTF-8, and split returns a locally-owned Uint8Array[]. Native lowers each call onto rt kernel helpers; node runs the same methods from the same generated tables (devhost polyfill), so both runtimes produce identical bytes by construction — pinned by run-fidelity cases across Greek/Cyrillic casing, growth mappings, invalid-UTF-8 passthrough, repeat/pad edges, and split shapes, plus a machine-checked method matrix (#119).
  • The stays-out spellings teach their reason: charCodeAt/charAt/codePointAt/normalize/replace/replaceAll on bytes teach the new NS1060 (byte text speaks the byte-honest method set), the locale family teaches NS1005, and the regex-taking methods teach NS1040 — never a bare "property does not exist". trimAsciiSpaces stays for LF-preserving line parsing; .trim() is the canonical whitespace trim (#119).
  • TS cores: generics, local function values, and the complete-language tail: user-declared generic functions, interfaces, and type aliases now compile via per-call-site monomorphization from tsc's own resolved type arguments — one readable Zig fn per distinct instantiation (pick__Task, pick__f64), deduped, covering records/unions/arrays/optionals, recursion inside a generic, generics calling generics, and structural instantiation of generic types (Box<Task>Box__Task); unresolvable call sites teach the new NS1053 (#119).
  • Const-bound local function values hoist: const scale = (x: number): number => x * 3; (arrow or function expression) becomes an ordinary module-level fn when capture-free and fully annotated, usable by direct call (recursion included) or as an array-method callback (xs.map(scale)); captures, reassignment, storing/returning the value, and record-field calls teach the new NS1054 — and capturing a locally-owned array ends its ownership at the capture (NS1051 machinery) (#119).
  • The small-fry tail lands with node-byte-identical pins: for (const [i, x] of xs.entries()) (the destructured-pair loop form), ++/--/assignments in provably order-exact value positions (arr[i++], const n = ++count), ?.[i] and ?.method() optional-chain hops on supported receivers, plain number/string switch scrutinees (if/else chains with JS strict-equality and default-position semantics), and typeof CONST type-query aliases. Float-valued template holes stay deferred — there is no JS-exact f64 formatter in the runtime yet, and node/native divergence is never an option (#119).
  • TS cores: exceptions and data classes — the complete language, tier 2: throw/try/catch/finally compile as deterministic control flow — a thrown subset value (one error shape per core, the new NS1057) unwinds through a native payload slot to the nearest catch, across helper calls and out of .map/.filter callbacks; finally lowers to a scoped defer running on every exit (control flow inside it teaches the new NS1058, JS's own no-unsafe-finally), the catch binding narrows once (const err = e as ParseError;), rethrow and nested try work, and an uncaught throw is a defined panic at the exported boundary — exactly where node's process would crash. Node-parity is pinned in the run-fidelity corpus, throw-mid-mutation of an owned array included (#119).
  • Data classes, no inheritance: class Task { fields; constructor; methods } emits as a plain struct plus module-level functions; new Task(...) constructs a record-shaped value, this reaches fields and methods, and instance mutation (field writes, this-writing methods) follows the same local-ownership rule as arrays (NS1001/NS1051 at the boundaries). The class tail teaches by name: extends/super/abstract (new NS1055), accessors/statics/privates/#/class expressions/escaping this (new NS1056), generic classes (NS1053), instanceof (NS1041); class instances stay local values — Model storage teaches toward records (flagged follow-up) (#119).
  • Two reconcile potholes: arrays OF byte buffers (const parts: Uint8Array[] = []) now route through array ownership — parts.push(chunk) on your own array works and runs node-identically (bytes VALUES keep their own discipline) — and a bare reference to a module-level function or const helper is a callback (xs.map(encodeTurn), xs.toSorted(byAscending) — the same lowering as the arrow spelled inline), plus shorthand members in union literals ({ kind: "range", v }) (#119).
  • TypeScript apps are the native init default: the scaffold is three files of truth and zero Zig — src/core.ts (logic), src/app.native (view), app.zon (manifest) — and the build detects the core from the tree (a src/core.ts transpiles at build time through generated wiring; src/main.zig stays the Zig core; both at once is a teaching error). The Zig template remains first-class via native init --template zig-core (#119).
  • native dev --core: the TypeScript core-logic loop under node — dispatch Msgs as JSON lines, watch the model and effect transcript, run Sub timers and Cmd.delay on a virtual clock. Logic only, honestly not a renderer; native dev runs the real app (#119).
  • native check checks TypeScript cores: a src/core.ts runs the @native-sdk/core subset checker first (NS diagnostics verbatim), then markup and app.zon as before — with a fresh model contract the markup pass type-checks bindings against the core's emitted model (#119).
  • Markup text input reaches TypeScript cores: a core declares its own TextInputEvent mirror union and on-input matches it structurally, translating each runtime event into the core's union at dispatch — markup text field to TS update to re-render, end to end (#119).
  • Exported model helpers bind from markup: an exported single-Model-parameter helper also emits as a Model declaration (doneCount binds as {done_count}), and export const viewUnbound = [...] emits the view_unbound lint opt-out — NS1031/NS1032 teach the collision and typo cases (#119).
  • TS cores: runtime fetch header values and journaled env deliveries — the two gaps the ai-chat example surfaced, closed. Cmd.fetch header VALUES may now be runtime bytes ({ authorization: bearerToken(model.apiKey) } — header NAMES stay compile-time ASCII, NS1029/NS1030 bounds still gate what is knowable at build time and the engine err-arm rejects the rest); no wire change was needed — record 0x09 always carried values as length-prefixed byte fields built at dispatch time, only the emitter's literal-only rule and the node SDK's FetchSpec type moved. And the envMsgs channel now journals each launch delivery (an additive .env effect record: value in payload, arm name in stderr_tail, dispatch index in key), so replay feeds the RECORDED values with zero env reads — a session recorded with credentials set replays byte-identically on a machine where they are unset or different; journals without env records (older recordings) re-derive from the launch configuration exactly as before. examples/ai-chat-ts now sends a real Authorization: Bearer <key> header instead of the access_token query-parameter workaround, and its replay e2e drives both the unset-env and changed-env launches. Plus NS1051: an un-annotated spread local (const turns = [...model.turns, next]) now teaches its array-type annotation instead of the generic emit-time stop (#119).
  • TypeScript subset: grammar completeness: the subset now means "TypeScript minus the ecosystem minus the purity violations" — never minus basic syntax. New in the mapping: do...while (body-first, continue re-tests, exactly node), labeled statements with labeled break/continue (loops and blocks, lowered to Zig labels), default arms on union-kind switches (the else prong over unnamed arms; dead-code defaults emit nothing), **/**= (JS pow corners pinned: NaN exponents, ±1 ** ±Infinity, right-associativity), the shifts << >> >>> and ~ (ToInt32 with the count masked & 31), the full compound-assignment family (*= /= %= &= |= ^= <<= >>= >>>= plus guarded &&= ||= ??=), unary +, const record destructuring (const { total, done: doneCount } = stats;), namespace imports over the core's own modules (import * as util — values, calls, and qualified types resolve to the flat emitted names), multi-counter for-inits and comma incrementors (for (let lo = 0, hi = n; lo < hi; lo++, hi--)), countdown incrementors (i--), hole-free template literals, satisfies, and the empty statement (#119).
  • Every exclusion now teaches: twelve new rules close every generic-error hole — NS1039 (namespace aliases are dot-syntax, SDK intrinsics import by name), NS1040 (regexes), NS1041 (runtime type/shape tests: typeof/in/instanceof/Object.*), NS1042 (generators), NS1043 (comma/void/assignment-as-value), NS1044 (BigInt/Symbol), NS1045 (destructuring beyond const record fields), NS1046 (nested/stored function values and ?.()), NS1047 (default exports, export =, value re-exports), NS1048 (loose ==), NS1049 (var), NS1050 (generic declarations) — and NS1019 broadens to the full fixed-arity story (rest params, arguments, call spreads). var and generic helpers previously emitted broken Zig silently; both now stop with teachings, and same-file type/value homonyms are caught by NS1038 instead of colliding in the emitted module (#119).
  • The grammar matrix: a new machine-checked suite (packages/core/test/grammar_matrix.test.ts) enumerates every statement, operator, expression, and declaration form of the language and pins each to its verdict (SUPPORTED emits and compiles under Zig; BANNED names its teaching rule; tsc-rejected stays tsc's), so the grammar can never grow a silent gap again. New run-fidelity corpora pin the node-vs-native behavior of every new mapping (pow corners, shift wrapping, -0 preservation, do-while ordering, labeled-jump targets, default-arm matching, destructured aliases, ??= on 0-vs-null) (#119).
  • Inference fixes surfaced by the matrix: the float-demotion fixpoint no longer terminates one pass early (a two-hop chain — field → destructured alias → local — previously stranded a phantom NS1016 conflict), and shorthand { x } properties now wire the VALUE symbol into inference (a shorthand from a host-boundary parameter previously kept a false integer proof and emitted mismatched Zig) (#119).
  • Stock-IDE support for TypeScript apps, working before the npm publish: native init scaffolds package.json (the app's name plus an exact @native-sdk/core pin) and tsconfig.json (the checker's own compiler options, so editor errors match native check reality), and the CLI materializes node_modules/@native-sdk/core itself — a copy of exactly what the published package will contain — so VS Code et al. resolve @native-sdk/core and @native-sdk/core/text with full IntelliSense today. native check|dev|build|test keep the copy fresh (one info line on refresh) and native doctor reports version skew; once the package is on npm, a plain npm install writes identical content and the CLI recognizes the version and leaves it alone. None of it is build truth: builds transpile against the SDK checkout and work with node_modules deleted, tree detection still keys on src/core.ts alone, and the Zig template is untouched (#119).
  • @native-sdk/core is publish-shaped: files: ["sdk"], a typed exports map for . and ./text, no bin and no runtime dependencies (the transpiler dependency moved to devDependencies), with a package test pinning the manifest shape. The soundboard-ts and system-monitor-ts example ports carry the same editor surface, and a ts-core e2e suite proves the whole contract with the real tsc: fresh scaffold and both ports typecheck with zero injected paths, and the transpiler still takes the core clean after rm -rf node_modules (#119).
  • TS cores: mutating array methods are legal on locally-owned arrays: an array your function creates (a literal or a .slice()/.map()/.filter()/.concat()/.toSorted() copy) now takes the full mutating set — push (any seed, not just the empty builder), pop/shift (returning the find-miss optional), unshift, splice (JS index clamping, the removed array as its value), reverse, fill, in-place sort (the stable toSorted machinery applied in place), and indexed writes — with node-byte-identical semantics pinned by a new run-fidelity corpus (parser stacks, splice corners, shift/unshift order, sort stability, pop-on-empty) and a machine-checked mutation matrix beside the grammar matrix (#119).
  • Teaching errors now fire only at the true semantic boundaries: shared data keeps NS1001/NS1022 (both rewritten around ownership, NS1022 naming the now-legal const copy = xs.slice(); copy.sort(cmp); idiom), and the new NS1051 teaches mutation after an escape — returned from a callback, passed to a call, stored, or aliased — with the escape kind and line named; an escape inside a loop gates the whole loop body, and early-exit returns stay legal (#119).
  • TypeScript cores reach the full app surface: markup sliders and split dividers deliver their applied 0..1 fraction to a core's one-number float arm (on-change="scrubbed" — scrub-to-seek from markup), controlled scroll round-trips through a core-declared ScrollState mirror, and the generated wiring detects five host-event channels from plain exports — frameMsg (presented frames), keyMsg (the app-level key fallback), appearanceMsg and chromeMsg (system appearance and hidden-titlebar geometry as Msg arms), and envMsgs (launch environment variables as journaled boot Msgs) — plus app.zon .assets.images registered at launch as the ImageIds 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 page covers the app-core subset, Cmd/Sub effects, text-is-bytes, the node dev loop, capacity knobs, and the eject story. Toolkit-extension pages stay Zig on purpose: that tier is the machinery itself (#119).
  • Docs code presentation: the TypeScript | Zig code toggle now renders as the same segmented pill control as the component previews' Default | Geist theme-pack toggle (one shared primitive, identical in dark mode), and code samples can carry a filename header — a file glyph plus the path (```ts:src/core.ts), integrated into the toggle's header bar opposite the segments — applied across the quick-start, TypeScript, app-model, native-ui, and config pages' complete-file samples; copied markdown renders the path as a labeled line above a plain fence (#119).
  • Markup binds your model's field names exactly as you wrote them: TypeScript cores now emit Zig with the TS spellings intact — fields, exported single-model helpers, Msg payload records, and locals alike — so nextId binds as {nextId} and doneCount as {doneCount}, ending the dual-naming rule (camelCase in core.ts, snake_case in app.native) that every author had to hold in their head. Zig cores are untouched: their fields were already the names markup binds. The whole pipeline follows from the one change — the model contract, native check's typed pass, both markup engines, hot reload, and the eject story (the emitted module now mirrors your source, and markup keeps binding the same names after you adopt it) (#119).
  • The TS-track host surfaces that matched emitted names structurally now speak the TS SDK spellings (timestampMs/intervalMs, colorScheme/reduceMotion/highContrast, tabsProjected, the audio arm's positionMs/durationMs), and the declared scroll-state mirror accepts the canvas spelling or the TS spelling — never a mix (#119).
  • NS1031 collisions are now exact-name collisions (doneCount the helper vs doneCount the field); viewUnbound entries were already the TS names and stay so. Scaffold templates, both ports' views, the ai-chat view, docs, and the skill teach the one rule (#119).
  • zig-core starter parity: native init --template zig-core now scaffolds the same app as the TypeScript template — counter, a ticking switch driving a repeating 1s fx.startTimer, a Stamp button reading the journaled clock (fx.wallMs), a bindable total helper, and the matching markup — with generated full-loop tests covering the timer and clock seams; the quick-start code toggle now shows both starters verbatim (#119).

Bug Fixes

  • Packaging fails loudly when signing fails: native package --signing adhoc|identity no longer exits 0 while shipping an unsigned bundle — output paths with spaces (--output "My App.app") now sign correctly (the signing pipeline execs argv arrays instead of shell strings, which also unbreaks spaced paths and spaced identities in the notarization helpers), every signed bundle must pass codesign --verify --deep --strict before packaging succeeds, any codesign failure stops the package with codesign's own reason, and the package report proves the outcome with a signing: adhoc (signed, verified) line (#118).
  • Per-thread memory no longer scales with the canvas scratch: the render planner's fixed scratch buffers lived in static thread-local storage, so on Windows every thread the process spawned (window host, COM, accessibility, workers) privately committed a full ~6.5 MB copy — most of a small app's working set. The scratch now allocates lazily on the one thread that actually plans frames: a scaffolded counter app's private working set drops ~4x, its .tls section shrinks from ~6.5 MB to under 200 bytes, and the executable itself is ~6.5 MB smaller. Linux and macOS binaries shed the same per-thread TLS block (#117).
  • Transpiler: a ternary initializer under null-guard fusion parenthesizes: const x = c ? f(a) : g(a); if (x === null) <exit> lowers to Zig's orelse fusion, and the conditional's if/else expression now wraps in parens — bare, the orelse bound to the ELSE arm alone (a type error at best, the wrong value at worst). The same guard covers ?? over a ternary left side; pinned in the conformance corpus and the node/native run-fidelity corpus (#119).

Contributors