Compare commits

...

97 Commits

Author SHA1 Message Date
Chris Tate 222b8a79ff fix(tray): harden rich row contracts 2026-08-17 22:59:53 -05:00
Chris Tate 42f5cc287b fix(tray): keep rich rows model-owned 2026-08-17 21:32:10 -05:00
Chris Tate 03a9693666 feat(tray): add typed rich rows
- Add typed metric, segmented-choice, and bounded chart rows across Zig and TypeScript.
- Render native macOS controls with command routing and accessible platform fallbacks.
- Support independently styled persistent menu-bar titles with configurable size, weight, and number style.
2026-08-17 20:23:41 -05:00
Chris Tate b25cefe318 chore(release): prepare v0.9.3 (#380)
- Synchronize CLI, core, platform package, and example versions to 0.9.3.
- Add release notes and contributors for all changes since v0.9.2.
2026-08-17 15:14:54 -05:00
Chris Tate ef3ba18168 Add platform-correct Command Backspace (#377)
* Add platform-correct Command Backspace

- Add semantic line-start deletion across Zig and TypeScript text mirrors.
- Preserve platform keymaps, controlled state, undo, and replay behavior.
- Cover all editable widgets and document the shortcut.

Co-authored-by: Mohak Bajaj <77928693+MohakBajaj@users.noreply.github.com>

* Fix Command Backspace edge cases

* Fix platform-specific Command Backspace test

---------

Co-authored-by: Mohak Bajaj <77928693+MohakBajaj@users.noreply.github.com>
2026-08-17 14:46:23 -05:00
Chris Tate 465a163e27 feat: add model-driven theme state (#378)
* feat: add model-driven theme state

- Add the TypeScript themeState helper across checker, generated ABI, and UiApp runtime.
- Preserve system accessibility behavior while supporting model pack, scheme, and accent precedence.
- Document the API and migrate gpu-components with runtime and replay coverage.

Co-authored-by: MohakBajaj <77928693+MohakBajaj@users.noreply.github.com>

* fix: constrain theme state optional fields

---------

Co-authored-by: MohakBajaj <77928693+MohakBajaj@users.noreply.github.com>
2026-08-17 08:51:57 -05:00
Chris Tate a33d579177 fix(macos): preserve file drop targets (#374)
* fix(macos): preserve file drop targets

- Route AppKit drops with labeled, view-local coordinates across canvas, WebView, and window fallback paths.
- Preserve drop metadata through Zig and cover widget routing, TypeScript contracts, docs, and examples.

Co-authored-by: Mohak Bajaj <77928693+MohakBajaj@users.noreply.github.com>

* fix(macos): preserve file drop coordinates

* fix: keep file drop targets sized

* fix: keep capability webview interactive

* fix: constrain capabilities window width

---------

Co-authored-by: Mohak Bajaj <77928693+MohakBajaj@users.noreply.github.com>
2026-08-16 23:11:12 -05:00
Chris Tate 393a0ed36e fix(app-runner): load manifest menus and commands (#376)
* fix(app-runner): load manifest menus and commands

- Resolve app.zon commands, menus, and shortcuts consistently across live and replay runners.
- Add zero-config TypeScript coverage, automation, and documentation.

Co-authored-by: MohakBajaj <77928693+MohakBajaj@users.noreply.github.com>

* test(app-runner): verify manifest menu registration

* fix(automation): escape menu snapshot catalogs

---------

Co-authored-by: MohakBajaj <77928693+MohakBajaj@users.noreply.github.com>
2026-08-16 23:04:07 -05:00
Chris Tate e8f9e4ee50 Fix large TypeScript Msg union compilation (#375)
- Derive comptime scan quotas from Msg shape and identifier bytes across generated shims, persistence, channels, and environment routing.
- Keep tag-skew diagnostics precise and teach future generated-code quota failures without blaming contracts.
- Add 160-arm compile/link and full TypeScript pipeline regression coverage.

Co-authored-by: Mohak Bajaj <77928693+MohakBajaj@users.noreply.github.com>
2026-08-16 21:46:23 -05:00
Chris Tate 659c893b29 Fix combobox Enter submit precedence (#373)
- Prefer bound combobox submit handlers over Enter trigger activation.
- Cover builder, markup, runtime, menu-selection, and command dispatch paths.
- Document the TypeScript-first on-submit composition and keyboard behavior.

Co-authored-by: Mohak Bajaj <77928693+MohakBajaj@users.noreply.github.com>
2026-08-16 21:46:14 -05:00
Chris Tate 8d0da34e62 chore(release): prepare v0.9.2 (#372)
- Synchronize CLI, core, platform package, and example versions to 0.9.2.
- Add release notes and contributors for all changes since v0.9.1.
2026-08-16 20:22:43 -05:00
Chris Tate 1c1fba0c0f fix(macos): place fresh windows correctly (#369)
* fix(macos): place fresh windows correctly

- Separate restored, explicit, and default window placement from persistence policy.
- Honor explicit origins and restore policy across platform seams and both macOS hosts.
- Add placement coverage, update docs, and remove obsolete example workarounds.

* fix(macos): honor window placement policies

* fix(macos): correct window placement policies

* fix(macos): preserve window placement contracts

* fix(runtime): restore secondary window placement
2026-08-16 19:06:29 -05:00
Chris Tate 86fecf6cee fix(macos): report content rects in window frame events (#370)
* fix(macos): report content rects in window frame events

- Emit content geometry consistently from the AppKit and CEF hosts.
- Preserve layout and restore round-trips without titlebar drift.

Co-authored-by: Sepehr Safari <25853688+sepehr-safari@users.noreply.github.com>

* fix(macos): restore CEF content geometry

---------

Co-authored-by: Sepehr Safari <25853688+sepehr-safari@users.noreply.github.com>
2026-08-16 17:18:48 -05:00
Chris Tate 23d0f5908a feat(images): decode photos to fit runtime budgets (#366)
* feat(images): decode photos to fit runtime budgets

- Decode encoded photos to fit the app's registered-pixel budget across platform codecs.
- Add a validated 1-8 MiB app.zon image budget and independent 8 MiB source bound.
- Cover deterministic pixels, 1080p loading, replay, ABI plumbing, and documentation.

Co-authored-by: Sepehr Safari <25853688+sepehr-safari@users.noreply.github.com>

* fix(images): honor raised budgets across hosts

* fix(images): cover generated and non-mac hosts

* fix(images): harden source and replay limits

* fix(images): fit Android panoramas before validation

---------

Co-authored-by: Sepehr Safari <25853688+sepehr-safari@users.noreply.github.com>
2026-08-16 16:26:36 -05:00
Chris Tate 919d0e6cdf fix(canvas): respect link span underline flag (#368)
- Render span underlines only when explicitly enabled.
- Preserve conventional underlines for Markdown-generated links.
- Cover linked spans with and without underline decoration.

Co-authored-by: Sepehr Safari <25853688+sepehr-safari@users.noreply.github.com>
2026-08-16 16:02:39 -05:00
Chris Tate 5d7fee8262 schema and rendering fixes (#367)
* canvas: allow checkbox and radio label content

* canvas: add interaction washes to actionable surfaces

* canvas: keep selection control geometry and fills consistent

* canvas: separate switch thumb and track colors

* canvas: preserve resolved colors through disabled washes

* canvas: skip zero-width badge and button strokes

* canvas: tint destructive alerts and align compact content

* docs: regenerate canvas control previews

* canvas: preserve text attributes on text-bearing controls

* canvas: fast-path idle layout container rendering

* canvas: keep authored row fills as the rest state

* bench: recalibrate full-view toggle budget

* canvas: complete schema and rendering fixes

- Enable checkbox and radio labels consistently across schema, markup engines, accessibility, docs, and previews.
- Unify actionable state, disabled-color, variant-accent, and zero-width stroke behavior across controls and surfaces.
- Correct compact control geometry and destructive alert styling/alignment with comprehensive golden and gate coverage.

* canvas: address schema review findings

* canvas: fix actionable state routing

* canvas: fix drag menus and disabled control tokens
2026-08-16 15:05:24 -05:00
Chris Tate 5c32accf12 fix(runtime): reveal autofocus targets and carets (#364)
* fix(runtime): reveal autofocus targets and carets

- Share scroll-then-verify focus reveal across keyboard, autofocus, and automation.
- Preserve selection while revealing collapsed end carets inside text editors.
- Cover scrolled focus flows and document the autofocus contract.

* fix(runtime): make focus reveals transactional

* fix(runtime): make widget focus reveal transactional
2026-08-16 08:02:28 -05:00
Chris Tate d575734635 Fix anchored surface correctness (#363)
* fix(canvas): dismiss anchored surfaces without focus

* fix(canvas): relayout anchors after scroll restore

* fix(canvas): place modal surfaces against root

* fix(canvas): address anchored surface review issues

* fix(canvas): unify window surface behavior
2026-08-15 22:26:04 -05:00
Chris Tate ee63266095 Implement logical canvas radio groups (#361)
* Implement logical canvas radio groups

- Scope nested radios as one roving-focus, single-selection group.
- Align radio keyboard, pointer, and handler dispatch behavior.
- Expose radiogroup accessibility semantics and document the contract.

* Fix radio group accessibility edge cases

* Fix radio group focus traversal edge cases

* Fix radio group keyboard and naming semantics

* fix: preserve radio selection semantics
2026-08-15 18:36:17 -05:00
Chris Tate e7c161970c Add configurable accessory startup (#358)
* Add configurable accessory startup

- Add manifest, runner, and macOS host support for flash-free Accessory startup.\n- Require a tray affordance and cover launch/runtime composition with tests.\n- Update the TypeScript menu-bar example and lifecycle documentation.

* Fix flash-free accessory startup

* Enforce tray requirement when packaging accessories
2026-08-15 00:52:34 -05:00
Chris Tate ddd975e4ea chore(ovation): checkpoint agent step (#357) 2026-08-14 18:03:46 -05:00
Chris Tate 474cb5e364 fix(core): pin scriptc tuple normalization fix (#356)
- Upgrade Native SDK compiler dependencies and generated references to scriptc 0.0.31.
- Add a real compiled-core ABI regression for bare-model and effect-tuple returns.
- Centralize test pin lookup and remove release numbers from comments and prose.

Co-authored-by: John Lindquist <36073+johnlindquist@users.noreply.github.com>
2026-08-14 17:27:26 -05:00
Chris Tate e6ac6ac4fc fix(linux): avoid GTK alert dialog crash (#354)
- Initialize GtkAlertDialog with a valid empty format string.
- Add regression coverage preventing a NULL constructor argument.

Co-authored-by: ElSebas41 <189925713+ElSebas41@users.noreply.github.com>
2026-08-14 09:53:19 -05:00
Chris Tate 0ecdc7d2e9 feat(core): expose model-declared windows to TypeScript (#351)
* feat(core): expose model-declared windows to TypeScript

- Add canonical TypeScript window descriptors with close-policy and close-command routing.
- Compile and hot-reload label-addressed secondary-window markup in generated launchers.
- Cover quit/hide behavior end to end and port the TypeScript system-monitor settings window.

* fix(core): harden TypeScript window declarations

* fix(core): validate TypeScript window views

* fix(core): validate returned window descriptors

* fix(core): harden TypeScript window contracts
2026-08-14 09:30:18 -05:00
Chris Tate e924d7fcac feat(examples): make the feed reader the end-to-end services showcase (#352)
* feat(examples): make the feed reader the end-to-end services showcase

* fix(examples): contain feed results in scroll pane

* fix(core): preserve service facade unbound metadata
2026-08-13 22:34:57 -05:00
Chris Tate 41c4cdc47a feat(files): add delete file effects (#350)
* feat(files): add delete file effects

- Expose Cmd.deleteFile as the primary TypeScript API with checked routing and documentation.
- Add real, fake, permission, journal, and replay support through the shared file-effect engine.
- Cover compiled cores across macOS, Linux, and Windows, including reliable Windows append behavior.

* fix(files): await Windows append completion

* fix(files): preserve final symlink on delete
2026-08-13 21:00:48 -05:00
Chris Tate 31d5b202bc feat(mobile): compile TypeScript cores and services for mobile targets (#346)
* feat(mobile): compile TypeScript cores and services for mobile targets

The external core and service compile drivers admit the pinned
compiler's three mobile triples — aarch64 iOS device and simulator
(macOS build host, iOS 15.0 floor) and aarch64 Android (any desktop
host, API 26 floor) — as library archives only, mapping the build
graph's Zig triples onto the compiler's own spellings and threading
the Android NDK location the way the graph already threads its zig.
The service executable lane refuses mobile targets with the
in-process pointer: no child process exists there, so the carrier
resolution turns "auto" into the in-process pool on iOS/Android and
teaches on an explicit "child", while desktop behavior is unchanged.

Mobile app builds stage a generated mobile entry beside the desktop
wiring: it satisfies the embed host's AppDef contract over the same
mirror, markup, and registry, drives the canonical mobile scene plus
the manifest's declared chrome, owns the service pool (markers and
stream relays live in the shim-installed app-data directory), and
delivers that directory through envMsgs. The compiled archives merge
into the embed static library the host tiers already link — flattened
to plain objects on Android, where Zig's ELF static-library emission
stores archive inputs as nested members the NDK link would skip.

A mobile execution lane (scripts/mobile-e2e.sh, NATIVE_SDK_MOBILE=1
in either gate tier) stages a battery over the service fixture's
compiled core and service archives, packages the fixture app for both
mobile targets and a services-free example for Android, then executes
the battery on a booted iPhone simulator and a headless arm64
emulator: typed pool results across update round trips, trap
isolation poisoning exactly one instance, and a journal replay that
reproduces the recorded model without initializing the archive. All
checks pass on both device classes. The TypeScript and services
chapters and the ts-core/ts-services skills state the mobile matrix;
persistence, boot images, and URL media caching remain unwired on
mobile.

* fix mobile TypeScript app wiring

* fix(mobile): validate TypeScript package outputs
2026-08-13 18:13:37 -05:00
Chris Tate 2bc942db46 feat: support close policies on secondary windows (#349)
- Expose UiApp.WindowDescriptor.close_policy and forward it through window creation.
- Preserve hide/reopen identity while retaining existing platform safety gates.
- Document and test quit, hide, and unsupported-host lifecycle behavior.
2026-08-13 17:37:27 -05:00
Chris Tate 199b89e06d feat: add actionable notifications (#347)
* feat: add actionable notifications

- Add stable replacement identifiers and paired notification actions across SDK APIs.
- Dispatch activations through the normal command path on macOS, Windows, and Linux.
- Advance the TypeScript wire generation and cover validation, test platforms, and docs.

* fix: secure notification action dispatch
2026-08-13 16:33:37 -05:00
Chris Tate 716eb27c53 feat(core): classify subset rules as guarantee or deferred (#345)
Every NS rule in the diagnostics catalogue carries class: "guarantee" |
"deferred". Guarantee rules protect a core invariant (determinism and
replay, fixed shapes, immutability of shared data, the one text
representation) and are permanent; deferred rules (NS1011, NS1019,
NS1040, NS1042, NS1044) wait on an easing decision and their
diagnostics say the capability is deliberately deferred, not
impossible.

Fix text names the concrete service alternative where one exists:
NS1002 (network via the generated client), NS1005 (clock reads),
NS1011 (Map/Set transforms), NS1040 (regex matching), NS1041 (JSON
parsing returning a typed record).

Docs and skills frame the core and services by role: the core is the
app's deterministic logic; services do the app's imperative work.
2026-08-13 14:15:06 -05:00
Chris Tate baef0d96d3 feat(storage): harden file effects (#339)
* feat(storage): harden file effects

- Add bounded streaming reads, atomic write sinks, stat, and append effects.
- Gate external paths with symlink-safe filesystem permission checks.
- Preserve deterministic record/replay through content-addressed stream blobs.

* fix(storage): harden file effect lifecycles

* fix(storage): harden stream replay and key ownership

* fix(storage): keep stream sink on rejected chunks

* fix(storage): address file stream review findings
2026-08-13 14:10:10 -05:00
Chris Tate f228e861d1 Upgrade scriptc to 0.0.29 (#344)
- Pin scriptc and its compiler/runtime lock data to 0.0.29.
- Refresh service contracts, compatibility fixtures, calibration, and documentation.
- Regenerate the service compile-surface reference; the 0.0.29 surface manifest carries no entry changes from 0.0.28 and adds a coverage statement for the mobile library targets (aarch64-apple-ios, aarch64-apple-ios-simulator, aarch64-linux-android).
2026-08-13 13:56:28 -05:00
Chris Tate 6ec836c491 feat: support multiple macOS status items (#343)
- Add keyed status-item lifecycle and independent live updates across the runtime and macOS hosts.
- Expose model-driven TypeScript and Zig collection APIs with status-item-aware events.
- Extend automation, journaling, documentation, and regression coverage.
2026-08-13 13:14:16 -05:00
Chris Tate 9ce0370181 chore(release): prepare v0.9.0 (#341)
- Add complete v0.9.0 release notes and contributor credits.
- Synchronize CLI, core, platform, tool, and example version pins.
- Discover every TypeScript example during version synchronization.
2026-08-13 02:00:28 -05:00
Chris Tate a7665807f3 feat(cores): compile TypeScript cores for cross desktop targets (#340)
The external core compile driver now enforces the same host/target
pairing matrix as the service compile lane: same-triple compiles keep
the native lane, Linux and Windows GNU targets cross-compile from any
macOS/Linux/Windows build host over the compiler's zig-cc lane, macOS
targets need a macOS build host, and every refused pairing teaches
before compiler work starts. The co-emitted contract sidecar is
target-independent: a macOS-native, x86_64-windows-gnu, and
x86_64-linux-musl compile of one staged tree emit byte-identical
documents with identical integer-class decisions, and the COFF and ELF
archives declare the same nsc_core_* symbol surface as the Mach-O one.

The SDK's fixture graph compiles corewire for the build host, so the
battery lanes configure under a cross -Dtarget, and a new
stage-cross-e2e step installs the host-fixture, markup, and in-process
service pool batteries under <prefix>/e2e for execution on the target
machine. scripts/cross-e2e.sh drives the lane end to end: it
cross-builds the batteries, the kanban example, and the service fixture
app (in-process carrier — the core and service archives linked into one
executable, with no defined-symbol overlap between the localized
service archive and the core's contract surface) for x86_64-windows-gnu
and x86_64-linux-musl, then executes the batteries on the Windows box
over ssh and in an amd64 Alpine container. gate.sh runs the lane in
either tier when NATIVE_SDK_CROSS=1 is set and skips it otherwise.

All three batteries pass on both targets: 29 host-fixture, 10 markup,
and 19 in-process pool tests on Windows (the three posix-spawn tests
skip there), and 32/10/19 in the musl container. The TypeScript chapter
and ts-core skill state the supported build matrix, including the Linux
glibc spelling and the unchanged desktop-only scope for mobile.
2026-08-13 01:28:42 -05:00
Chris Tate fe92cff10a feat(services): extend the in-process carrier opt-in to Windows and cross targets (#337)
* feat(services): extend the in-process carrier opt-in to Windows and cross targets

scriptc 0.0.28 localizes archive runtime symbols format-aware (ELF,
COFF, Mach-O) on every desktop host and for cross targets, so the
explicit `.service_carrier = "in_process"` opt-in no longer needs the
host-native macOS/Linux gate. Carrier resolution and the SDK fixture
lane's archive gate now admit the compiler's build matrix: Linux and
Windows targets from any macOS/Linux/Windows build host, and macOS
targets from a macOS host. Auto still resolves to the isolated child
carrier everywhere, and child stays selectable on every shape.

The service compile lane replaces its build-host-only refusal with the
same matrix. Same-triple compiles keep the native lane; admitted cross
pairings run the compiler's zig-cc lane (SCRIPTC_CC=zigcc,
SCRIPTC_TARGET=<triple>, and the build's own zig at the front of PATH
through the new --zig-exe argument); refused pairings keep a precise
teaching. The child executable's name follows the target OS instead of
the host's.

Cross Linux targets carry one encoded caveat: a bare `-gnu` spelling
lands on Zig's default glibc floor, which predates arc4random_buf — a
symbol the compiled service runtime references — so carrier resolution
teaches the `-gnu.2.36`+ (or `-musl`) spelling at configure time, and a
stated glibc version now rides the platform triple into the compile.
Windows targets link ws2_32, iphlpapi, and advapi32 beside the compiled
archives: the fixture modules get a shared helper, and the app lane's
Windows platform block adds advapi32 for the archive's CSPRNG.

Both carrier e2e suites pass natively on Windows (14 child-carrier
tests, 19 in-process pool tests: parallel keys, per-key FIFO,
cooperative cancellation and deadlines, trap isolation, streaming, and
replay), a macOS-built x86_64-windows-gnu service archive links and
initializes on Windows, and the linux-musl child executable and archive
cross-compile from macOS. The docs services chapter, packages page, and
ts-services skill state the widened opt-in and the cross-target scope.

* fix(services): align cross-target ScriptC archives

* fix(services): gate in-process archive architectures

* fix(build): keep ScriptC floor checks step-local

* fix(services): tighten cross-target toolchain gates
2026-08-12 23:51:19 -05:00
Railly Hugo 7d67158444 fix(docs): redirect /docs to the introduction page (#338)
/docs returned 404 while every page beneath it resolved and every legacy
URL form already redirected into /docs/*. docsSlugs() only yields nested
slugs, so the /docs segment itself had neither a route nor a generated
redirect, and /docs/ compounded it by 308ing into that dead end.

/docs is the parent of every documentation link on the site and the
likeliest hand-typed entry point, so open it on the Introduction. The
.md sibling keeps the Markdown surface whole for agents that reach for
it, matching the pairing legacyDocsRedirects applies to every slug.

Fixes #331

Co-authored-by: Cris <cristian.correa.cs@gmail.com>
2026-08-13 01:20:52 -03:00
Chris Tate c71a7b4638 Upgrade scriptc to 0.0.28 (#336)
* Upgrade scriptc to 0.0.28

- Pin scriptc and its compiler/runtime lock data to 0.0.28.
- Refresh service contracts, compatibility fixtures, calibration, and documentation.
- Regenerate the service compile-surface reference; the 0.0.28 surface manifest carries no entry changes from 0.0.27.

* docs: state the carrier default correctly on the packages page
2026-08-12 19:56:19 -05:00
Chris Tate d7aeea1ea9 Promote credentials to core effects (#335)
* Promote credentials to core effects

- Add app-scoped TypeScript and Zig credential effects with capability and permission gates plus platform-backed storage.

- Redact credential journal results, synthesize deterministic replay placeholders, and provide hermetic devhost and test stores.

- Update checker diagnostics, mobile plumbing, SDK surfaces, documentation, skills, and conformance coverage.

* fix(runtime): harden credential effect lifecycle

* fix: harden credential effect handling
2026-08-12 19:21:30 -05:00
Chris Tate 8b2a97ffe7 feat(services): add the in-process TypeScript service carrier (#334)
* feat(services): add the in-process TypeScript service carrier

Compile src/services into a thread-instanced, runtime-localized library
archive (scriptc 0.0.27 library mode) linked into the app binary, and run
it on a small worker-thread pool: one archive instance per pool thread,
same-key requests strictly FIFO, distinct keys in parallel across
instances. The carrier preserves the child-process seam exactly — the
same HostCallBinding, poll-based completion delivery, journaled results,
lazy start, and replay that never initializes the archive.

Cancellation and deadlines ride the same cooperative marker-file token
the child publishes, with the same grace; an operation that ignores its
token is abandoned (thread detached, timeout routed, pool refilled), and
a detected trap routes kind service_trap through the per-instance panic
sink while other instances keep answering. Streaming chunks relay live
through a per-request framed file the pool's supervisor thread tails, so
chunks keep preceding the typed terminal mid-operation.

In-process is the default carrier on host-native macOS/Linux builds;
app.zon .service_carrier (and -Dservice-carrier) select explicitly, with
.service_pool_size (-Dservice-pool-size) setting the pool width (default
min(4, cores)). Windows and cross builds keep the child carrier.

corewire grows the two in-process projections (library facade entry and
compiler profile), the service compile script grows an --out-archive
lane, and bench-service-host now measures both carriers. The new pool
e2e suite covers success/throw routing, duplicate and unkeyed keys,
live streaming with cancellation, queued deadlines, parallelism,
per-key FIFO, trap isolation, the registry/archive pairing fence, and
journal replay against the pool.

* test(services): absorb runner load in the pool parallelism proof

One retry of the parallel batch keeps the timing assertion meaningful
on saturated runners; a pool that serialized distinct keys fails both
attempts deterministically.

* fix(services): harden in-process carrier supervision

* fix(services): harden pool queue scheduling

Wake the supervisor when newly admitted work can move the next deadline earlier, and scan the full queue so large busy-key bursts cannot hide runnable independent work.

* fix(services): drain streams after grace race

Let completion-owned requests remain in the supervisor stream polling path when the grace poison CAS loses, and cover the boundary with a deterministic regression.
2026-08-12 16:25:50 -05:00
Chris Tate 473cad71ef feat(storage): ship checked relational SQLite (#326)
* feat(storage): ship checked relational SQLite

- Add capability-gated SQLite effects, migrations, transactions, live queries, and replay across desktop and mobile.
- Validate schemas and named SQL at build time, generating typed command and subscription APIs.
- Add the relational notes flagship, documentation, tests, skills, and SDK mirror support.

* fix relational SQLite correctness gaps

* fix(storage): harden relational SQLite checks

* fix relational runtime policy parity

* fix sqlite tooling on node 22

* fix(storage): harden relational sqlite boundaries

* fix(storage): address relational review findings

* fix(storage): address remaining relational review findings

* fix(storage): address relational review issues

* fix(sqlite): retire stale live queries before replacements
2026-08-12 14:26:00 -05:00
Chris Tate 06b6ccd53c Upgrade scriptc to 0.0.27 (#333)
- Pin scriptc and its compiler/runtime lock data to 0.0.27.
- Refresh service contracts, compatibility fixtures, calibration, and documentation.
- Regenerate the service compile-surface reference; the 0.0.27 surface manifest carries no entry changes from 0.0.26.
2026-08-12 12:41:59 -05:00
Chris Tate ed84e35975 tools: scriptc surface-manifest diff and service-capability gate (#328)
* feat(tooling): gate capability claims on the compiler surface manifest

- Add surface_manifest_diff.mjs: mechanical diff of two scriptc surface
  manifests keyed by stable entry ids — flips to static (with the
  staging-transform/caveat retirements each flip makes due), tier
  regressions, easings, added/removed entries, SC-code and semantic
  note changes; human-readable and --json output.
- Add gen_service_surface.mjs: generate the author-facing service
  compile-surface reference (skill-data/ts-services/references/
  service-surface.md) byte-derived from the pinned compiler's manifest,
  plus a --check mode that fails on a stale or hand-edited reference,
  a manifest/pin version mismatch, any hand-written SC-code claim in
  docs/skills/skill-data prose, or a compiler version literal that does
  not match the pin.
- Wire the check into scripts/gate.sh as the surface-claims step:
  fast tier when docs/, skills/, skill-data/, or packages/core/ change;
  always in full.
- Point skill-data/ts-services/SKILL.md at the generated reference and
  cover the tooling in packages/core/test/surface_tools.test.ts.

* fix(tooling): harden service surface checks

* fix(tooling): close service surface audit gaps

* fix(tooling): close manifest review gaps

* fix(tooling): harden surface claim auditing

* fix(tooling): keep surface audits layer-aware
2026-08-12 09:50:03 -05:00
Chris Tate 5fa8074f7c feat(bench): add service-host carrier benchmark (#329)
Measure the out-of-process TypeScript service carrier through its
production HostCallBinding against a bytes-echo service compiled by the
production service lane (frontend contract -> corewire host/registry ->
exact-pinned plain-scriptc executable). The echo operation returns its
request unchanged, so the numbers are the carrier's — lazy child spawn,
hello fence, framing, pipes, worker-thread queueing — not a workload's.

Scenarios: cold start (fresh host, first keyed request from admission to
polled completion, including the lazy spawn), warm round trips for 64 B
and 256 KiB payloads (p50/p90/p99), and queued throughput for N keyed
requests drained through the single worker thread.

Run: zig build bench-service-host -Doptimize=ReleaseFast
(requires node and npm ci in packages/core, like the services e2e lane).
2026-08-12 01:43:40 -05:00
Chris Tate 8d7946edbd docs: add the TypeScript Services chapter (#327)
* docs: add the TypeScript Services chapter

Give TypeScript services their own documentation page covering the
two-tier split, service authority and the sanitized environment,
operation authoring and kind-tagged errors, the generated typed client
with keys/timeouts/cancellation/streaming, exact vendored npm with the
calibrated coverage results, and the runtime behavior (lazy start,
supervision, replay without the service child, desktop scope).

Condense the inline services section on the TypeScript Cores page to a
summary that links the chapter, point the packages page at the new
route, wire navigation and page titles, and correct the keyed-effect
note: service requests reject duplicate live keys rather than
replacing them.

* docs: restyle the TypeScript Services chapter as reference documentation

Rewrite the chapter in a terse reference style: noun-phrase sections,
short declarative paragraphs, code examples and tables first. All
technical content is unchanged: the two tiers, service authority and
the environment allowlist, operation rules and boundary types,
kind-tagged errors, the generated client, keys/timeouts/cancellation,
streaming, exact vendored npm with the calibration results, runtime
behavior, the dev loop, and the NS1065-NS1067 teachings.

Replace vague failure-mode wording on the cores page with the precise
outcomes: dropped requests dispatch no message, stream cancellation
routes cancelled to err, and truncated streaming fetches end with
err: truncated.
2026-08-12 01:32:56 -05:00
Chris Tate 011caa9183 Upgrade scriptc to 0.0.26 (#325)
- Pin scriptc and its compiler/runtime lock data to 0.0.26.
- Refresh service contracts, compatibility fixtures, calibration, and documentation.
- Invalidate cached service frontends whenever the compiler manifest changes.
2026-08-11 22:38:24 -05:00
Chris Tate b7c493bab0 feat(ts-services): add typed boundary and ecosystem (#321)
* feat(ts-services): add typed boundary and ecosystem

- generate record-typed service clients/codecs and enforce hermetic 100% npm-static coverage

- run services in the devhost with native-format record/replay parity

- add typed streaming, cooperative cancellation, deadlines, fixtures, and guidance

* fix(ts-services): harden request admission

* fix(ts-services): align devhost runtime behavior

* fix(ts-services): enforce request deadlines
2026-08-11 21:25:48 -05:00
Chris Tate aa7ed9aa52 feat(storage): add SQLite-backed record store (#320)
* feat(storage): add SQLite-backed record store

- add capability-shed SQLite storage with deterministic effects, replay, and atomic record operations
- expose matching TypeScript and Zig APIs across desktop and mobile hosts
- add hermetic coverage, devhost support, documentation, and a worked example

Co-authored-by: carvalab <1446654+carvalab@users.noreply.github.com>

* fix(storage): restore pristine SQLite amalgamation

- restore the upstream byte removed during whitespace cleanup so the vendored source matches its documented checksum

Co-authored-by: carvalab <1446654+carvalab@users.noreply.github.com>

* fix(storage): harden store result delivery

* fix(storage): align devhost store semantics

* fix(storage): address review findings

---------

Co-authored-by: carvalab <1446654+carvalab@users.noreply.github.com>
2026-08-11 19:59:33 -05:00
Chris Tate 833e79e44a Implement the TypeScript service seam (#317)
* Implement the TypeScript service seam

- Classify src/services as ordinary static-tier TypeScript and generate a checked service contract.
- Compile, package, and supervise a pinned out-of-process service host behind Cmd.request.
- Cover service authority, failures, restart, timeout, replay, docs, skills, and showcase fixtures.

* fix service boundary validation and staging

* fix TypeScript service packaging and validation

* fix TypeScript service review findings
2026-08-11 14:23:22 -05:00
Chris Tate cafbf206e8 Implement model persistence (#316)
* Implement model persistence

- Add atomic engine-owned snapshots with generated codecs, restore and migration routes, backup recovery, debounce, and replay support.
- Gate persistence through app manifests and native check while keeping the TypeScript, Zig, and devhost surfaces in parity.
- Add store and end-to-end coverage, documentation, and a persisted TypeScript example.

Co-authored-by: carvalab <1446654+carvalab@users.noreply.github.com>

* Address model persistence review findings

* Address remaining model persistence review findings

* Fix persistence identity and restore route checks

* fix persistence replay and rollback safety

---------

Co-authored-by: carvalab <1446654+carvalab@users.noreply.github.com>
2026-08-11 13:01:01 -05:00
Chris Tate 0126d20f30 feat(ts): expose platform service commands (#315)
* feat(ts): expose platform service commands

- Add first-class Cmd APIs for credentials, external URLs, path reveal, and local-time formatting.

- Route commands through validated runtime services and native macOS, Linux, and Windows backends.

- Document effect semantics and cover TypeScript encoding, platform behavior, and runtime delivery.

* fix(windows): apply timestamp DST rules

* fix(platform): align local time formatting
2026-08-11 08:46:23 -05:00
Chris Tate 2fd7c4c3dc Add macOS lifecycle hooks for TypeScript apps (#314)
* Add macOS lifecycle hooks for TypeScript apps

- Expose hidden-window, fullscreen, Dock-presence, and launch-at-login controls through manifests and TypeScript effects.
- Implement AppKit and SMAppService behavior across both macOS hosts with safe cross-platform fallbacks.
- Cover the new wire/runtime paths with tests and document the menu-bar app migration workflow.

* fix: address platform lifecycle review findings

* fix: harden macOS lifecycle hooks

* fix: preserve macOS activation behavior

* fix: preserve hidden startup window state
2026-08-11 00:04:47 -05:00
Chris Tate 4c95b04539 Improve retained desktop frame performance (#313)
* Improve retained desktop frame performance

1. Fix retained animation pumping and Windows frame wake scheduling.

2. Make latency and frame profiling monotonic, observable, and regression-tested.

3. Add physical-display-aware macOS and Windows performance gates.

* Fix PR performance and package checks

- Align NativeSdkViewInfo declarations with runtime GPU telemetry.

- Calibrate hosted macOS animation budgets without weakening physical-device defaults.

* Harden Windows performance sampling and shutdown

- Correlate physical hover samples with their responding visual frames.

- Stop due-frame callbacks immediately when the Windows host exits.
2026-08-10 21:22:03 -05:00
Chris Tate c7e64b647c feat: add dynamic TypeScript menu bar items (#311)
* feat: add dynamic TypeScript menu bar items

- Drive status-item presentation and rich menus from committed TypeScript model state.
- Render and route dynamic status items through the native platform hosts.
- Add UTF-8 byte encoding, strict ASCII diagnostics, docs, examples, and end-to-end coverage.

* fix tray menu platform parity

* fix dynamic tray menu behavior

* fix: preserve dynamic tray platform state

* fix tray presentation and command routing
2026-08-10 19:33:42 -05:00
Chris Tate a404ca166d chore(release): prepare v0.8.4 (#310)
- Add v0.8.4 release notes and contributor credits.
- Synchronize CLI, core, platform, tool, and example version pins.
2026-08-10 13:26:30 -05:00
Chris Tate 7f6830a15b Polish component gallery interactions (#308)
* Polish component gallery interactions

- Fix keyboard navigation, scrolling, and focus-visible behavior across interactive canvas widgets.
- Rebuild gpu-components as an isolated TypeScript and Native markup gallery with complete interactive specimens.
- Add live model-driven Default and Geist theme switching with validation, documentation, and smoke coverage.

* fix component gallery focus and controls

* Fix TypeScript setup for component smoke CI
2026-08-10 12:32:31 -05:00
Chris Tate 7a29661384 Fix TS core PTY event key contract (#307)
- Align echoed PTY keys with the byte-text host and facade contract.
- Regenerate SDK declarations and update PTY conformance coverage.
- Compile the PTY event arm through the external-core E2E fixture.

Co-authored-by: NyxTools-M <243123737+NyxTools-M@users.noreply.github.com>
2026-08-10 10:24:13 -05:00
Chris Tate a727b1db68 Add customizable macOS DMG packaging (#304)
* Add customizable macOS DMG packaging

- Build polished drag-to-Applications archives with Retina-aware generated or custom backgrounds.

- Add manifest controls for Finder geometry, positioned items, files, directories, and links.

- Correct default optical alignment and document the packaging workflow.

* Fix DMG package validation gaps

* fix(packaging): make DMG generation reliable

* Validate TIFF payload ranges

* fix: tighten dmg validation and staging
2026-08-10 01:00:54 -05:00
Chris Tate 7e3a3157d0 feat(core): support streaming fetch responses (#300)
* feat(core): support streaming fetch responses

- Add a typed line-streaming Cmd.fetch overload and carry it through the command wire and runtime host.

- Keep stream lifecycle deterministic with loud cancellation and duplicate-key rejection.

- Cover the feature with contract, conformance, runtime, compiled-core, harness, example, and documentation updates.

* feat: stream AI chat through Vercel gateway

- Render chat-completion SSE deltas as they arrive.
- Pin the example to Vercel AI Gateway with official key config.
- Cover streaming, failure, and replay paths end to end.

* feat: add streaming fetch and chatbot example

* feat(chatbot): refine streaming chat experience

- Add a compact live model picker and immediate Stop action.

- Improve conversation layout, prompt focus, and caret retention.

- Expand chatbot documentation and end-to-end regression coverage.

* fix: harden streaming fetch limits

* fix: harden streaming fetch and textarea behavior

* fix(canvas): render lifted rich text
2026-08-09 21:02:29 -05:00
Chris Tate 283ab804c0 Add cross-platform audio capture and voice memo example (#303)
* Add cross-platform audio capture and voice memo example

- Add microphone and system-audio capture effects for macOS, Windows, and TypeScript core.
- Add a polished voice memo example with private app-data WAV saving and playback.
- Document platform support and packaging requirements with runtime and conformance coverage.

Co-authored-by: Marcus Schiesser <17126+marcusschiesser@users.noreply.github.com>

* Fix audio capture teardown and permissions

Co-authored-by: Marcus Schiesser <17126+marcusschiesser@users.noreply.github.com>

* Fix audio capture lifecycle and metadata

Co-authored-by: Marcus Schiesser <17126+marcusschiesser@users.noreply.github.com>

* Fix audio capture lifecycle edge cases

Co-authored-by: Marcus Schiesser <17126+marcusschiesser@users.noreply.github.com>

* fix: harden audio capture lifecycle

Co-authored-by: Marcus Schiesser <17126+marcusschiesser@users.noreply.github.com>

---------

Co-authored-by: Marcus Schiesser <17126+marcusschiesser@users.noreply.github.com>
2026-08-09 17:09:23 -05:00
Chris Tate 83a7aee721 fix(macos): smooth dialog backdrop blur (#299)
* fix(macos): smooth dialog backdrop blur

- Replace the flat box blur with an optimized three-pass Gaussian approximation.

- Preserve shadcn-compatible blur and scrim values while covering the host path with a build check.

* Fix macOS backdrop blur invalidation
2026-08-08 20:11:39 -05:00
Chris Tate 30c1410c10 chore(release): prepare v0.8.3 (#298)
- Add release notes for packaged assets, drag landing polish, and the expanded Kanban showcase.
- Synchronize CLI, core, platform package, and TypeScript example versions.
2026-08-08 17:26:42 -05:00
Chris Tate bfcc5ff8df fix(kanban): polish packaged board interactions (#297)
- Resolve boot image assets from packaged macOS bundle resources so agent avatars render after launch.
- Keep drag landing motion above swimlane clips while preserving clipped neighbor reflow.
- Double the seeded Jira-style tickets and remove issue glyphs from card metadata.
2026-08-08 16:41:23 -05:00
Chris Tate 3a580c9a8d chore: prepare v0.8.2 release (#296)
- Bump CLI, core, platform packages, and TypeScript examples to v0.8.2.
- Add complete release notes and contributors for the v0.8.2 range.
- Keep the Kanban core pin covered by release version sync and validation.
2026-08-08 14:44:47 -05:00
Chris Tate 4269233703 feat(examples): make kanban an agent ticket board (#295)
* feat(examples): make kanban an agent ticket board

- Add numbered agent tickets with OpenAI and Claude avatars from SVGL.
- Simplify the titlebar to an icon-only add action.
- Keep drag geometry and end-to-end coverage aligned.

* fix(examples): make kanban columns scrollable
2026-08-08 14:26:34 -05:00
Chris Tate b230b140b8 feat(core): add native drag and drop to TypeScript apps (#285)
* feat(core): add native drag and drop to TypeScript apps

- Carry native file drops and widget drag events through the TypeScript core contract.
- Rebuild Kanban in TypeScript with animated reordering, cross-column moves, and Escape cancellation.
- Add runtime, ABI, rendering, end-to-end, documentation, and example coverage.

Co-authored-by: John Lindquist <36073+johnlindquist@users.noreply.github.com>

* fix: correct canvas drag lifecycle

* fix: harden canvas drag dispatch

* fix drag lifecycle and preview rendering

* fix(runtime): preserve drag gesture arbitration

---------

Co-authored-by: John Lindquist <36073+johnlindquist@users.noreply.github.com>
2026-08-08 13:22:16 -05:00
Railly Hugo 04125b7d62 fix(canvas): preserve explicit zero padding (#288) 2026-08-08 14:41:13 -03:00
Chris Tate 21f6057041 Make TypeScript the clear authoring default (#284)
* Make TypeScript the clear authoring default

- Route agents to TypeScript cores and Native markup for new apps.
- Put TypeScript examples and code first across human-facing entry points.
- Keep Zig clearly documented as an explicit alternative and extension tier.

* Clarify TypeScript authoring guidance

* docs: clarify TypeScript native UI guidance
2026-08-04 22:15:13 -05:00
Chris Tate 4f0b57f2c2 feat(effects): add desktop notifications (#283)
* feat(effects): add desktop notifications

- Expose fire-and-forget notifications through the UiApp effects facade.
- Validate requests and suppress external alerts during fake execution and replay.
- Add focused runtime coverage and document the native Zig API.

* feat(ts-core): add desktop notifications

- Expose fire-and-forget notifications through Cmd.showNotification.
- Route the additive command wire opcode through the native platform effect.
- Cover compiled-core delivery and document the primary authoring path.
2026-08-04 20:14:54 -05:00
Chris Tate b21849c1bf chore: prepare v0.8.1 release (#282)
- Synchronize CLI, core, platform, and example package versions to 0.8.1.
- Add complete release notes for Markdown HTML, image handling, and payload-free HTTP writes.
- Record release-process improvements and contributor attribution.
2026-08-04 19:17:38 -05:00
Chris Tate ff6a1c2c32 fix(markdown): harden resolved image handling (#281) 2026-08-04 18:52:27 -05:00
Chris Tate b01851d03c feat(markdown): support safe HTML rendering (#280)
* feat(markdown): support safe HTML rendering

- Lower common GitHub-style inline and block HTML onto native widgets.
- Keep scripts, styles, embeds, and unsupported markup inert and literal.
- Add focused, hostile-input, documentation, and example coverage.

* fix(markdown): harden safe HTML block handling

* fix(canvas): handle multiline HTML markdown blocks

* fix(markdown): harden safe HTML rendering

* fix(markdown): harden HTML parsing

* fix(markdown): render deployment comment images

* fix(markdown): center table cell content
2026-08-04 18:40:42 -05:00
Railly Hugo 0f990c2007 fix(effects): send a zero-length body for payload-free POST, PUT and PATCH (#277)
`runFetch` routed every request without a payload to
`std.http.Client.sendBodiless`, which asserts the method carries no
body. A POST, PUT or PATCH with no payload therefore panicked on
`unreachable` and aborted the process in Debug builds; in ReleaseFast
the assert is compiled out and the request went out with no
Content-Length at all.

Send an explicit `content-length: 0` body for those methods instead.

The regression test drives a payload-free POST through the fixture
server and asserts the request head carries `content-length: 0`.
Without the fix it terminates with SIGABRT in `sendBodilessUnflushed`.
2026-08-04 13:02:41 -03:00
Chris Tate 4ceffdbbd5 chore: write changelog during releases (#278)
- Remove the changelog fragment workflow and merge script.
- Have the release agent write the complete entry from release history.
2026-08-04 10:10:24 -05:00
Chris Tate 030bf8df74 chore: prepare v0.8.0 release (#276)
- Synchronize SDK, core, platform, and example package versions
- Merge pending release notes into the v0.8.0 changelog
- Record release markers and contributor attribution
2026-08-03 12:42:10 -05:00
Chris Tate d976ab6351 native check ends with the compiler-truth pass (#275)
* feat: native check ends with the compiler-truth pass

- A TypeScript core's check now finishes by running the pinned external core compiler's analyzer over the entry with the shipped SDK declarations mapped, so the check verdict and the build verdict come from one compiler.
- Analyzer type errors fail the check with the compiler's diagnostics verbatim; an analyzer that cannot reach a verdict defers to the build instead of wedging check.
- The in-process 6.x provider remains the teachings engine, and its adapter header now states the split (the 7.x line ships no in-process API).

* fix: resolve the typecheck driver's own path portably

- fileURLToPath instead of URL.pathname, which percent-encodes spaces and mangles Windows drive paths — install locations with either broke the check pass.
2026-08-03 12:18:12 -05:00
Chris Tate d26428e11b TypeScript cores compile through the external compiler by default; the TS-to-Zig emitter is deleted (#271)
* feat: add a check-only frontend mode

- The @native-sdk/core CLI without -o checks the core (and writes the contract when asked) and emits nothing.
- native check runs the frontend in check-only mode; no scratch emission under .native/check.

* feat: TypeScript cores compile through the external core compiler by default

- The transpiled lane is gone: src/core.ts builds through the external core compiler with nothing stated, and core_compiler = "transpiler" is refused with a teaching naming the release that removed it.
- Mobile targets with a TypeScript core are taught before lane selection: TS cores are desktop-only until the external toolchain grows mobile targets; Zig/markup cores stay fully supported on mobile.

* feat: retarget the TypeScript-core suites to the compiled lane

- test-ts-core-e2e compiles every fixture core through the external core compiler in the build graph (no env gating; the compiler is a package dependency), with the markup battery in its own binary — one archive per process is the C-ABI contract.
- The paired/byte-compare machinery is gone (paired_core, gen_paired, extract.zig, test-contract-equivalence, test-compiled-core-parity); the conformance suite pins mirrors over frontend contracts and committed goldens, and the new test-external-core-abi suite holds the ABI laws over a real archive.
- The soundboard core-only dispatch budget is re-measured for the compiled lane (~5.3us on an M-class laptop, Debug; the C ABI crossing plus snapshot decode) and stays pinned at 1ms.

* feat: carry the mixed pair-return idiom on the compiled lane

- The contract sidecar gains additive init_returns_bare/update_returns_bare facts, and the generated facade narrows `Model | [Model, Cmd<Msg>]` returns (a tuple carries its command; a bare model the empty buffer).
- The scaffold starter bounds its counters with literal comparisons so the compiler's integer range proof takes them.

* feat: delete the TS-to-Zig emitter

- The transpiled lane's machinery is gone: emitter.ts (11.9k lines), the rt.zig kernel, the run1k gate, and the emitter/effects/run-fidelity suites; execution truth lives in the ts-core e2e batteries over real compiled archives.
- The frontend (transpile.ts -> frontend.ts) checks and emits the contract sidecar only; the CLI refuses -o with a teaching naming the release that removed the emitter.
- The conformance corpus and grammar matrices re-adjudicate: emitter-only gates (97 corpus cases, 7 matrix rows) are marked as accepted with their former emit-time rules kept readable.

* fix: ship the compiled lane in the npm CLI payload

- copy-framework mirrors packages/core/compile-surface + scripts and tools/corewire (the build compiles corewire from the dependency); the sync and files lists follow.
- scriptc rides as a regular dependency of @native-sdk/cli, pinned equal to packages/core by check-version-sync, and the build graph resolves its entry by node's ancestor walk from packages/core.

* ci: the compiled lane rides the package dependency

- Every ts-core-building job gets the compiler with the one npm ci in packages/core; the separate compiler install, the archive/sidecar env plumbing, and the opt-in example step are gone.
- The parity job becomes Core Compiler Fences: stage-core-contracts plus the determinism-fence negative control (the positive batteries ride zig build test in the Zig Core job).
- No compiler cache action on purpose: hosted runners are ephemeral, so runs stay hermetic by machine lifecycle.

* docs: the compiled lane is the documented truth

- The TypeScript docs, quick start, component pages, example READMEs, the scaffold templates, and the ts-core skill describe the check-and-compile pipeline; the eject-story and rt-kernel/frame-cap claims are gone, and the core dev loop is stated as restart-shaped with native dev --core for fast logic iteration.
- The evals grader checks cores with the frontend and grades ts harnesses against externally compiled archives through generated mirrors.
- The changelog fragment states the breaks deliberately: default lane switch, transpiled lane removed, mobile teaching, dev-loop latency, and the compiler dependency.

* fix: ownership.ts joins the frontend staleness set

- The checker, inference, and type layers import ownership.ts, so an edit there must re-run every cached check and contract step; the staleness array now carries it.

* chore: drop the unused TypeScript compat wrapper dependency (#274)

- Nothing imports the @typescript/typescript6 wrapper at run time — the frontend loads the exactly pinned @typescript/old alias directly — so the wrapper leaves both manifests and the lockfile.
- The version-sync check pins the alias on its own, the toolchain doctrine comments describe a stray consumer-tree wrapper (which resolution still ignores, as the twins' fixtures keep proving), and the prose pins follow the reworded doctrine.
2026-08-03 11:17:30 -05:00
Chris Tate 31c140e26f TypeScript cores gain an opt-in external compilation lane (#268)
* feat: emit the contract sidecar from the frontend

- packages/core emits core.contract.json directly from checked analysis (--contract), byte-identical to the extraction path, Wyhash identities included
- every corewire consumer (conformance shims, stage-core-contracts projections) now reads the frontend document
- test-contract-equivalence pins the two producers byte-identical per ts-core fixture

* feat: ship SDK declarations and the pinned external compiler

- generate and ship sdk/*.d.ts with the real tsc (freshness-pinned, ambient-clean), never hand-written
- add the external core compiler as the package's one exact-pinned runtime dependency and bump the compiled-core pin to 0.0.22
- promote the static compile surface into the shipped package (compile-surface/core.ts), one copy for fixtures and apps

* feat: opt-in external core compiler lane

- ts-core apps opt in via app.zon .core_compiler = "external" or -Dcore-compiler=external; the frontend still checks and emits the contract, corewire projects the compile entry/profile, the exact-pinned toolchain builds the archive, and the app links the generated mirror over it
- the staged module keeps the transpiler lane's exact shape, so the generated wiring runs unchanged over either lane
- env-gated example pin: soundboard-ts builds and tests on the lane in the compiled-core parity CI job

* fix: accept an argument-carrying external compiler command

- The compile driver took --compiler as one executable filename, so an interpreter-plus-script override failed with the missing-version teaching; a path that exists is taken whole, anything else splits on whitespace.

* fix: ship the compiled lane in the npm CLI payload

- copy-framework mirrors packages/core/compile-surface + scripts and tools/corewire (the build compiles corewire from the dependency); the sync and files lists follow.
- scriptc rides as a regular dependency of @native-sdk/cli, pinned equal to packages/core by check-version-sync, and the build graph resolves its entry by node's ancestor walk from packages/core.
2026-08-03 08:53:39 -05:00
Chris Tate e84ee28cbe chore(ovation): checkpoint agent step (#267) 2026-08-02 23:29:30 -05:00
Chris Tate 1789c68049 Add diff annotations to code component (#266)
* Add diff annotations to code component

- Add Geist-style added and removed line annotations across the Zig and markup APIs.
- Preserve diff rendering through layout, editing, scrolling, and retained invalidation with focused tests.
- Ship dedicated light/dark docs previews, rebuilt live WASM, documentation, and a changelog fragment.

* Fix code diff metadata and selection layering
2026-08-02 18:44:13 -05:00
Chris Tate a14d225f0f fix(canvas): match disabled button themes (#265)
* fix(canvas): match disabled button themes

- Match shadcn and Geist disabled colors across button variants.

- Add regression coverage and refresh the docs WASM preview.

* fix(canvas): remove disabled primary outline

- Keep the implicit shadcn primary border transparent when disabled.

- Refresh regression coverage and the docs WASM preview.

* fix(canvas): honor disabled button border overrides

* docs: refresh component preview wasm

* fix(canvas): preserve disabled button overrides
2026-08-02 17:17:23 -05:00
Chris Tate 8fc933b9db fix(windows): accelerate gpu surface presentation (#258)
* fix(windows): accelerate gpu surface presentation

- Render retained binary canvas packets through Direct2D and DirectWrite.
- Preserve dirty-region updates and GPU effects with a safe software fallback.
- Wire and validate the renderer across supported Windows build paths.

Co-authored-by: Omer Shatzberg <131801941+oshtz@users.noreply.github.com>

* fix(windows): harden Direct2D presentation

* fix(sdk): separate GPU backend request types

* fix(windows): honor GPU surface fallback contracts

* fix(windows): scale transformed blur kernels

* fix: harden Windows GPU surface presentation

* fix(windows): harden gpu blur and caption sampling

* fix(runtime): bypass packets for software surfaces

* fix(windows): preserve precise gpu surface updates

* fix(windows): reconcile GPU presenter state

* fix: preserve retained canvas resources

* fix: preserve Windows canvas packet fidelity

---------

Co-authored-by: Omer Shatzberg <131801941+oshtz@users.noreply.github.com>
2026-08-02 15:59:44 -05:00
Chris Tate 8600d7e5d5 fix: align Geist tabs with design system (#259)
* fix: align Geist tabs with design system

- Match Geist primary-tab sizing, spacing, icon treatment, and full-width rails while preserving default-theme pills.

- Add a scrollable component explorer with focused specimen views and a content-hugging default theme picker.

- Expand layout, rendering, semantics, interaction, and pixel-regression coverage for both themes.

* build: refresh docs WASM preview

- Recompile the checked-in component preview module against the updated Geist tab renderer.
- Verify the production docs build and live WASM scene instantiation.

* fix: extend Geist tab rails in flow layouts

- Let primary Geist tab lists claim available row and column width while preserving default-theme flex behavior.
- Add flow-layout regression coverage and refresh the docs WASM preview.

* docs: correct tabs markup example

- Compare the tab enum against quoted tag literals and show content for every tab.
- Use the cross-theme row and spacer composition for compact house tabs and full-width Geist rails.

* fix(canvas): correct tree, scroll, and tab layout

* fix(canvas): keep tree indentation author-owned

* fix(canvas): align Geist tab underlines with content

* fix(canvas): preserve wide Geist tab rails

* fix: preserve tab layout intent across themes

* fix(canvas): bound indented Geist tab rails

* fix: preserve Geist tab layout contracts
2026-08-02 12:22:58 -05:00
Chris Tate bdfce1ea7d ci: switch to vercel setup-zig (#255)
- Replace Zig setup across CI and release workflows.

- Keep generated scaffolds, tests, docs, and changelog aligned.
2026-08-01 15:35:51 -05:00
Chris Tate 8f1da1831b fix: keep Windows effect spawns hidden (#253)
* fix: keep Windows effect spawns hidden

- Pass CREATE_NO_WINDOW for background Effects.spawn children on Windows.
- Verify redirected child output remains available without an attached console.
- Document the user-visible Windows fix in a changelog fragment.

Co-authored-by: Omer Shatzberg <131801941+oshtz@users.noreply.github.com>

* test: run Windows effect spawn probe in CI

---------

Co-authored-by: Omer Shatzberg <131801941+oshtz@users.noreply.github.com>
2026-08-01 15:05:56 -05:00
Chris Tate 476173b6b5 docs: add canonical machine-readable routes (#248)
* docs: add canonical machine-readable routes

- Move documentation under /docs with permanent legacy redirects and explicit canonical metadata.
- Serve synchronized .md siblings and llms.txt from the canonical MDX sources.
- Gate redirects, metadata, sitemap entries, and internal links against SEO regressions.

* fix(docs): harden canonical route migration

* fix(docs): preserve query strings in legacy redirects

* fix(docs): preserve MDX content in markdown routes

* fix(docs): decode MDX string expressions
2026-08-01 14:14:25 -05:00
Chris Tate 19519dd5ea chore: prepare v0.7.1 (#247)
- Synchronize the CLI, core, platform, and example package versions.
- Merge pending changelog fragments into the marked v0.7.1 release notes.
- Credit release contributors and retire the v0.7.0 release markers.
2026-07-31 20:27:26 -05:00
Chris Tate 04b97cc2b7 feat: add native code editor example (#245)
* feat: add native code editor example

- Add an editable syntax-highlighted code surface with robust selection, large-file rendering, and expanded language coverage.
- Add the declarative Code Editor example with folder navigation, previews, permanent tabs, inline rename, save, and multi-window support.
- Extend tree interactions, macOS folder picking, component docs, tests, and live WASM previews.

* fix: address code editor review findings

* fix code editor large-file stability

* fix: harden large code editor state

* fix: polish code editor interactions

* fix: title empty code explorer windows

* fix: stabilize code editor rendering

* fix: harden code editor edge cases

* fix: address code editor review findings

* fix: resolve remaining code editor review findings

* fix: harden code editing and click handling

* fix: balance code editor tree spacing

* fix: load code editor folders on demand
2026-07-31 20:09:24 -05:00
Chris Tate 8014e796a4 CI: the compiled-core parity battery gates every PR, with determinism fences proven to fire (#246)
* compiled-core builds pin the external toolchain release

- tests/compiled-core/core_compiler_pin is the one place the release pin lives
- build_core.sh verifies the supplied command reports that release and refuses any other, because the profile's fence table is release-pinned data

* determinism fences get a pinned negative control

- fence_check.sh compiles the pristine markup fixture and asserts its co-emitted sidecar attests deterministic: true
- the same fixture with one injected ambient read (Date.now() in update) must refuse, naming the fenced surface id, with no archive or sidecar emitted
- skip-clean when no external toolchain is supplied, like the parity battery

* CI: compiled-core parity battery runs on every PR

- new ubuntu job installs the pinned external core compiler, stages the contract artifacts, and builds all five fixture cores through build_core.sh
- runs the determinism-fence negative control, then the full test-compiled-core-parity battery over the compiled archives
- the release pin is read from tests/compiled-core/core_compiler_pin, so a bump stays a one-line change

* comments carry prose unwrapped

- one line per comment paragraph across the new CI job, the pin check, and the fence-check script

* ci: isolate the dispatch-budget measurement from runner contention

- The parity battery runs its test binaries serially in CI: five suites racing on a two-core runner turn scheduler contention into dispatch-budget failures the budgets were never meant to catch.
- A budget failure now prints the measured whole-pipeline and core-only nanoseconds, so a real regression and a noisy runner are distinguishable from the log alone.

* test: the paired lane budgets a paired dispatch

- The paired-core module declares paired_lanes, the staged root re-exports it, and the soundboard dispatch pin reads it: a dispatch that runs both lanes and byte-compares every seam gets half the frame budget, while the single-lane suite keeps the strict microseconds-class pin.
- The measured CI shape that set the number: core-only paired dispatch at just over the single-core budget on a shared runner, with the whole pipeline well inside its frame.
2026-07-31 19:10:33 -05:00
Chris Tate a59015a246 corewire --facade emits the complete compiled-core entry; hand adapters deleted (#244)
* Contract sidecar: carry authored payload member names and type origins

- The transpiler emits payload_members on each union (the authored member name of every single-payload arm) and a module-level type_origins table (the declaring module of every named contract-table type).
- The sidecar extractor reads both tables into additive fields: "member" on message and union arms, "origin" on type-table entries.
- The sidecar reader parses both as optional facts, so older sidecars keep reading clean.

* corewire --facade emits the complete compiled-core entry module

- The facade is now the generated twin of a hand-written adapter: it imports the author's core module, re-exports every named contract type from its declaring module, wraps init/update/subscriptions and the model helpers, and implements the full ABI dispatch surface (boot_cmd, the nine dispatch entries, the wired channel entries, subscriptions, model_snapshot, helper_call) over one committed model, with the inline wire codec and the guard-and-trunc wholeness proof at every i64-classed ingress.
- The profile designates the facade's own entries (init/coreUpdate, coreSubscriptions only when the contract subscribes), maps plain export names to prefixed symbols, and carries the contract's integer_slots through; --f64-slot demotes a named record slot to f64 across the whole invocation for values that reach the f64-exact boundary.
- The sidecar reader accepts the external compiler's additive synthesized marker on struct entries.

* Compiled-core lane builds from generated facades and profiles

- build_core.sh stages the generated entry module and compiler profile from zig-out/core-contracts (stage-core-contracts now installs core_profile.json beside the sidecar and facade, with the host fixture's Model.pastBytes carried as f64 — it holds 2^53 by design, past the honest i64 window).
- The five hand adapters, the shared wire codec, and the hand profiles are deleted: the generated facade carries the whole surface, and the parity batteries hold every byte to the transpiler lane.
- The conformance suite keeps the mirror axes (fingerprints, contract artifacts, envelope unpacking); the compiled side's encodings are proven at full behavioral depth by the parity batteries, whose entry the generated facade now is. The hand markup contract gains the member and origin facts.

* fix(corewire): harden generated facade contracts

* fix(corewire): harden facade dispatch projection

* fix(corewire): harden generated facade contracts

* fix: accept legacy sidecars in parity tests

* fix: close corewire facade gaps

* fix(corewire): avoid false subscription inference

* fix(corewire): preserve unbound name precedence

* fix(corewire): align effective facade contracts
2026-07-31 17:20:24 -05:00
Chris Tate ad6fa36f3e Compiled-core parity: fixture profiles declare and prove their integer slots (#241)
* test: declare i64 slots in the markup and host-fixture core profiles

- The shared wire codec, the markup adapter, and the markup frame channel prove decoded integer slots in place: bind the value, range-guard it with ordered comparisons, and state wholeness with Math.trunc at the write.
- Fixture counter bumps saturate at the i64 class's provable ceiling, ±(2^53 − 1), so range discharges.
- Model.pastBytes stays f64-classed: it holds 2^53 by design, past the provable window.

* test: declare i64 slots in the ai-chat core profile

- The text SDK gains one provable selection constructor: every editor-produced selection is range-guarded and stated whole with Math.trunc, keeping the proof local to the construction site.
- The ai-chat composer binds composition bounds and proves them at the write; -1 stays the no-composition sentinel.
- The chat_response status and nextId bumps prove in place with the same guard-and-trunc idiom.

* test: declare i64 slots in the system-monitor core profile

- Samples, probe results, and ps rows bind, range-guard, and Math.trunc their counts at each classed write; unprovable values keep the previous sample or skip the line like any other malformed row.
- The adapter proves dispatch values and classed helper returns in place with the same guard-and-trunc idiom.
- Structurally identical union arms lower to one record shape, so one code slot and one pid slot carry the class for their siblings (SC4009 keeps colliding declarations out).

* test: declare i64 slots in the soundboard core profile

- Catalog ids, playback clocks, and queue counters bind, range-guard, and Math.trunc at each classed write; the clock-tick cap comparison replaces Math.min so the proof stays in view.
- The adapter proves its ten classed helper returns in place with the same guard-and-trunc idiom.
- The id-carrying Msg arms share one lowered record shape with QueueEntry, which carries the class for all of them (SC4009 keeps colliding declarations out).

* test: carry i64-classed slots on the i64 wire encoding

- Snapshot, helper-result, record, and channel encoders write classed slots with wI64 (optionals via wOptionalI64), matching the contract each adapter now attests.
- Text-input decoding reads selection offsets and composition cursors as i64; selection offsets ride a saturating reader because select-all sends the maxInt to-the-end sentinel, which every consumer snaps to the text's length.
- Classed helper results route through their proven wrappers and encode with wI64.

* test: declare the coalesced arm slots and prove the decode ingress

- The ten same-shaped Msg arm slots join the soundboard and system-monitor profiles, so the declared set covers every provable reference slot and only the deliberate 2^53 boundary probe stays f64.
- Decode ingress proves in place where synthesized record slots carry write obligations: soundboard's id dispatches and audio clocks, and the shared composition-cursor decode, each range-guarded with wholeness stated by Math.trunc.
- The ai-chat and markup profiles drop a subscriptions_export naming a function their entry modules never exported; a dangling name refuses instead of resolving silently.

* test: preserve compiled-core numeric dispatch classes
2026-07-31 08:08:47 -05:00
Chris Tate 7636ec3686 chore: prepare v0.7.0 release (#239)
- Sync the CLI, core, examples, and platform packages to 0.7.0.
- Merge release notes and mark the v0.7.0 changelog entry.
2026-07-30 14:30:44 -05:00
Chris Tate bd3aab4b48 feat(canvas): add reusable code component (#235)
* feat(canvas): add reusable code component

- Add highlighted code surfaces with optional line numbers and horizontal scrolling.
- Route Markdown fences through Code while preserving indentation and first-line list alignment.
- Cover the markup API with tests, documentation, previews, and changelog fragments.

* fix(canvas): preserve multiline code rendering

* fix(canvas): bound code layout capacity

* fix(canvas): bound code span retention

* fix(canvas): address code component review feedback

* fix(canvas): polish code block rendering

* fix(canvas): address remaining code review feedback

* fix(canvas): preserve long code rendering

* fix(canvas): preserve numbered code selection

* fix(canvas): fold long span selections

* fix(canvas): preserve empty code and paged selection

* fix(canvas): address code rendering review findings

* fix(canvas): resolve remaining code review issues

* fix: bound transformed code rendering
2026-07-30 13:56:52 -05:00
Chris Tate 6a871356b4 ci: harden Windows canvas snapshot reads (#234)
- Retry structural snapshot reads that race frame publication.
- Preserve Wine caption-offset geometry for reliable textbox input.
2026-07-29 14:24:36 -05:00
Chris Tate c1bad63c5f chore: prepare v0.6.3 release (#233)
- Sync all CLI, core, platform, and example version references to 0.6.3.
- Merge the textarea fixes into the marked v0.6.3 changelog entry.
2026-07-29 13:45:49 -05:00
Chris Tate 3636af4b45 fix: improve textarea editing shortcuts (#231)
* fix: improve textarea editing shortcuts

- Align Command and vertical arrow navigation with native multiline behavior.
- Add bounded undo and redo while keeping controlled text buffers synchronized.
- Cover retained runtime and markdown-viewer editing paths.

* fix: harden textarea editing behavior

* fix: harden textarea editor lifetimes

* fix(canvas): harden textarea history and navigation

* fix textarea wrap navigation and macOS undo menu

* fix textarea navigation and history edge cases

* fix textarea reconciliation failure atomicity

* fix(runtime): make canvas text history replay transactional

* fix: preserve textarea indentation and shift-click selection

* fix: harden textarea navigation and selection

* fix: harden textarea caret boundaries

* fix: harden textarea history and CRLF editing

* fix(canvas): keep carets outside CRLF boundaries

* fix(core): keep CRLF edits atomic

* fix(canvas): preserve CRLF editing boundaries

* fix(canvas): match native selection navigation

* fix(runtime): reroute compound textarea history events

* fix(runtime): normalize reconciled CRLF selections

* fix textarea left navigation across soft wraps

* Fix IME history across CRLF boundaries
2026-07-29 13:06:47 -05:00
Chris Tate ef1f8d9cdd chore: prepare v0.6.2 release (#230)
- Bump CLI, core, and platform package versions to 0.6.2.
- Merge overlay and container background notes into the release changelog.
- Credit release contributors and rotate release markers.
2026-07-28 19:12:07 -05:00
884 changed files with 394484 additions and 47508 deletions
+31 -11
View File
@@ -1,14 +1,14 @@
#!/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
# Exercises the Windows gpu_surface Direct2D path (src/platform/windows/
# webview2_host.cpp: child HWND, WM_TIMER frame events, retained binary
# packets) under Wine: cross-compiles examples/ui-inbox for
# x86_64-windows-gnu, runs the .exe under Xvfb + Wine, and asserts against
# the automation snapshot:
#
# 1. snapshot ready=true (app booted, automation server live)
# 2. gpu_backend=software (the SetDIBitsToDevice path is active)
# 2. gpu_backend=direct2d (the retained packet path is active)
# 3. gpu_nonblank=true (real pixels were presented)
# 4. widget-click "Add task" -> '4 open' (automation input mutates state)
# 5. real X11 click + typing lands in the draft textbox (XTEST -> Wine ->
@@ -77,6 +77,21 @@ poll() {
return 1
}
# Print the first snapshot match, retrying reads that land between the
# runtime's truncate and rewrite on a presented frame.
snapshot_match() {
local match=""
for _ in $(seq 1 20); do
match=$(grep -o "$1" "$snap" | head -1)
if [ -n "$match" ]; then
printf '%s\n' "$match"
return 0
fi
sleep 0.1
done
return 1
}
# ---- build ----------------------------------------------------------------
(cd "$repo_root" && zig build) || fail "root zig build (CLI) failed"
(cd "$app_dir" && zig build -Dtarget=x86_64-windows-gnu -Dplatform=windows -Dweb-engine=system -Dautomation=true) \
@@ -101,9 +116,9 @@ app_pid=$!
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 --------------------
# ---- 2 + 3: Direct2D 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"
poll 10 'gpu_backend=direct2d' || fail "gpu_backend is not direct2d"
echo "== canvas: $(grep -o 'gpu_backend=[a-z]*' "$snap" | head -1)" \
"$(grep -o 'gpu_nonblank=[a-z]*' "$snap" | head -1)" \
"$(grep -o 'gpu_sample=0x[0-9a-f]*' "$snap" | head -1)" \
@@ -111,7 +126,7 @@ echo "== canvas: $(grep -o 'gpu_backend=[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" \
add_id=$(snapshot_match 'widget @w1/inbox-canvas#[0-9]* role=button name="Add task"' \
| grep -o '#[0-9]*' | tr -d '#')
[ -n "$add_id" ] || fail "Add task button not found in snapshot"
"$cli" automate widget-click inbox-canvas "$add_id" || fail "CLI widget-click failed"
@@ -137,15 +152,20 @@ for w in $(xdotool search --name "." 2>/dev/null); do
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
# The runtime rewrites snapshot.txt on every presented frame. Retry the
# extraction instead of treating a read that lands between truncate and
# write as a standard-frame window: that fallback erases the 30px Wine
# caption correction and turns the real-input receipt into a flaky miss.
client_line=$(snapshot_match 'window @w1 "[^"]*" bounds=([^)]*)')
[ -n "$client_line" ] || fail "could not read client bounds from snapshot"
client_h=$(printf '%s\n' "$client_line" | sed -n 's/.*x\([0-9]*\)[^x]*$/\1/p')
[ -n "$client_h" ] || fail "could not read client height from snapshot"
y_off=$((client_h - HEIGHT))
[ "$y_off" -ge 0 ] 2>/dev/null || y_off=0
echo "== x window $win: pos=($X,$Y) size=${WIDTH}x${HEIGHT} client_h=$client_h y_off=$y_off"
xdotool windowactivate "$win" >/dev/null 2>&1 || xdotool windowfocus "$win" >/dev/null 2>&1
draft_line=$(grep -o 'widget @w1/inbox-canvas#[0-9]* role=textbox[^|]*' "$snap" | head -1)
draft_line=$(snapshot_match 'widget @w1/inbox-canvas#[0-9]* role=textbox[^|]*')
[ -n "$draft_line" ] || fail "draft textbox not found in snapshot"
bounds=$(echo "$draft_line" | grep -o 'bounds=([^)]*)')
bx=$(echo "$bounds" | sed -n 's/bounds=(\([0-9.]*\),.*/\1/p')
+3 -3
View File
@@ -9,7 +9,7 @@
# the app's trace log:
#
# 1. snapshot ready=true (app booted, automation server live)
# 2. gpu_backend=software + nonblank (the canvas presented real pixels)
# 2. gpu_backend=direct2d + nonblank (the canvas presented real pixels)
# 3. widget-click "Start stream" (fx.spawn launches cmd.exe under
# Wine; streamed lines land in the
# model and grow the snapshot)
@@ -118,9 +118,9 @@ app_pid=$!
poll 180 'ready=true' || fail "snapshot never became ready"
echo "== ready: $(head -1 "$snap" | cut -d'|' -f1)"
# ---- 2: software backend presented non-blank pixels ------------------------
# ---- 2: Direct2D 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"
grep -q 'gpu_backend=direct2d' "$snap" || fail "gpu_backend is not direct2d"
echo "== canvas: $(grep -o 'gpu_backend=[a-z]*' "$snap" | head -1)" \
"$(grep -o 'gpu_nonblank=[a-z]*' "$snap" | head -1)"
grep -q 'idle' "$snap" || fail "probe did not start idle"
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
+83 -29
View File
@@ -15,28 +15,68 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- uses: actions/setup-node@v4
with:
node-version: 22
# The TypeScript core suites transpile at build/test time; without
# node they skip silently, so CI must provide it.
# TypeScript cores compile through the external core compiler at
# build/test time; the compiler and the frontend's toolchain both
# arrive with this one install (without it the ts-core suites skip
# silently, so CI must provide it). No SCRIPTC_NO_CACHE and no
# cache action on purpose: hosted runners are ephemeral, so any
# per-run compiler cache dies with the VM and runs stay hermetic
# across commits by machine lifecycle.
- run: npm ci --prefix packages/core
- name: Service surface tooling and claims
run: node --test packages/core/test/surface_tools.test.ts
- run: zig build test
- run: zig build validate
core-compiler-fences:
name: Core Compiler Fences
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- uses: actions/setup-node@v4
with:
node-version: 22
# The exact-pinned external core compiler and the frontend's
# toolchain, one install (packages/core/package.json is the one
# place the pin lives).
- run: npm ci --prefix packages/core
# Per-fixture contract artifacts the fixture driver consumes: the effective sidecar plus its generated entry module and compiler profile, under zig-out/core-contracts.
- run: zig build stage-core-contracts
# Determinism-fence negative control: the pristine markup fixture compiles and its co-emitted sidecar attests deterministic: true, then one injected ambient read (Date.now() in update) must be refused by the profile's fences — proving the fences fire, not merely that clean cores pass under them. The positive batteries (every fixture's e2e suite over its real archive) ride `zig build test` in the Zig Core job; this job holds the refusal half.
- name: Determinism fences fire (negative control)
run: tests/compiled-core/fence_check.sh .zig-cache/fence-check
macos-webview:
name: macOS WebView
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- uses: actions/setup-node@v4
with:
node-version: 22
# gpu-components is a TypeScript-core app, so its smoke build needs
# the frontend compiler and exact-pinned TypeScript toolchain.
- run: npm ci --prefix packages/core
# The mobile aggregate runs on Linux for Android. Exercise the other
# store-capable cross-target here against the real iPhone simulator SDK.
- run: zig build test-example-mobile-canvas-lib-ios-store
- run: zig build test-webview-system-link
- run: zig build test-webview-smoke
# The zero-config TypeScript runner must load app.zon menus before
# automation can select their registered command ids.
- run: zig build test-menu-bar-smoke
# Signed-package seal pin: an ad-hoc signed package must pass
# codesign --verify --strict (macOS runners are the only tier with
# codesign; the step skips loudly anywhere else).
@@ -59,12 +99,12 @@ jobs:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
# 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.
# first-frame latency, 5 steady-state widget clicks asserting p90 input
# latency, then a reset-scoped retained-animation cadence sample.
# Its own job so a shared-runner slowdown is visible in isolation and
# never blocks the correctness smokes.
# Shared macos-14 runners are far noisier than a dev box (first CI run
@@ -75,13 +115,20 @@ jobs:
env:
NATIVE_SDK_PERF_BUDGET_MS: "1500"
NATIVE_SDK_PERF_INPUT_BUDGET_MS: "500"
# The virtual display is commonly 25-35 Hz even though AppKit
# reports the window visible. Keep physical/dev defaults at the
# strict 60 Hz-class 45/20/34; this hosted job remains a
# step-function regression sentinel rather than a display gate.
NATIVE_SDK_PERF_ANIMATION_MIN_FRAMES: "30"
NATIVE_SDK_PERF_ANIMATION_P90_MS: "50"
NATIVE_SDK_PERF_ANIMATION_MAX_MS: "100"
linux-webkitgtk:
name: Linux WebKitGTK
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- name: Install WebKitGTK dependencies
@@ -102,7 +149,7 @@ jobs:
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
# Builds the WebView example with the system engine, compiling the
@@ -112,6 +159,10 @@ jobs:
# include path, or a conformance error in the embedded layer — is a
# compile failure here, not a silent WebViewNotFound at runtime.
- run: zig build test-webview-system-link -Dplatform=windows
# Effects.spawn is a pipe-backed background transport. Run its
# Windows-only PowerShell probe natively so removing CREATE_NO_WINDOW
# cannot leave the platform-neutral and Wine lanes green.
- run: zig build test-windows-effects-no-console
# The registered-font receipt, natively on Windows: runs the
# font-registry suite — registration validation, the glyph-budget
# gate, present/reference pixel parity, and the Chinese-receipt
@@ -128,14 +179,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- uses: actions/setup-node@v4
with:
node-version: 22
# The TypeScript core suites transpile at build/test time; without
# node they skip silently, so CI must provide it.
# The TypeScript core suites compile through the external core
# compiler at build/test time; without the install they skip
# silently, so CI must provide it.
- run: npm ci --prefix packages/core
- run: zig build test-tooling
@@ -167,14 +219,15 @@ jobs:
step: test-examples-native-shard-4
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- uses: actions/setup-node@v4
with:
node-version: 22
# The TypeScript examples transpile at build time; the transpiler
# needs its installed dependency.
# The TypeScript examples compile through the external core
# compiler at build time; the compiler and the frontend toolchain
# arrive with this install.
- run: npm ci --prefix packages/core
# Every example test uses the null backend, so this lane needs no
# GTK/WebKitGTK packages. The root build owns the round-robin shard
@@ -186,7 +239,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
# Declare-to-use, proven on real Windows executables: the
@@ -218,7 +271,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
# Deliberately NO libwebkitgtk-6.0-dev: ui-inbox declares no web
@@ -244,7 +297,7 @@ jobs:
# C diagnostics on failure, which is exactly the escalation this
# step pins against. The throwaway cache dir keeps the compile
# cold: on a cache hit zig replays nothing, stderr included, so a
# warm cache (setup-zig restores one) would hide the diagnostics
# warm compiler cache would hide the diagnostics
# this step exists to catch.
- name: WebKitGTK stub compile is diagnostic-free
run: |
@@ -300,14 +353,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- uses: actions/setup-node@v4
with:
node-version: 22
# The scaffold default is the TypeScript core; its transpiler runs
# under node at build time from this checkout's packages/core.
# The scaffold default is the TypeScript core; its frontend and
# compiler run at build time from this checkout's packages/core.
- run: npm ci --prefix packages/core
# No WebKitGTK dev package, same as linux-canvas-smoke: the scaffold
# declares no web use, so its host compiles with the stub seam.
@@ -330,7 +383,7 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- name: Install Wine, Xvfb, and xdotool
@@ -352,8 +405,8 @@ jobs:
exit 1
fi
# 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,
# retained Direct2D packet path (child HWND + WM_TIMER) under Wine:
# snapshot ready, gpu_backend=direct2d, gpu_nonblank=true,
# automation widget-click, and real XTEST pointer/keyboard input.
# Wineprefix init happens inline in the script (measured 21s from
# scratch in an ubuntu-24.04 container, so no cache step).
@@ -366,7 +419,7 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- name: Install Wine and Xvfb
@@ -385,7 +438,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- run: zig build test-examples-frontends
@@ -395,7 +448,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- run: zig build test-examples-mobile
@@ -405,14 +458,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- uses: actions/setup-node@v4
with:
node-version: 22
# The default scaffold is a TypeScript core: its build runs the
# @native-sdk/core transpiler from this checkout's own install.
# @native-sdk/core frontend and the external core compiler from
# this checkout's own install.
- run: npm ci --prefix packages/core
- run: zig build
- name: Scaffold and test the zero-config native app
+1 -1
View File
@@ -92,7 +92,7 @@ jobs:
uses: actions/checkout@v4
- name: Setup Zig
uses: mlugg/setup-zig@v2
uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
+2
View File
@@ -22,6 +22,7 @@ packages/native-sdk/build.zig.zon
packages/native-sdk/app.zon
packages/native-sdk/third_party/
packages/native-sdk/packages/
packages/native-sdk/tools/
packages/native-sdk/LICENSE
# npm pack output
packages/native-sdk/*.tgz
@@ -41,6 +42,7 @@ docs/tsconfig.tsbuildinfo
# Dev-tool dependency trees (installed per package, never committed)
packages/core/node_modules/
.pnpm-store/
# The CLI-materialized editor copy of @native-sdk/core inside the TS example
# apps (node_modules is editor surface, never source — the same entry the
+10 -1
View File
@@ -2,6 +2,15 @@
Guidance for agents (and humans) working on this repository.
## App authoring default
Native SDK itself is implemented in Zig, but Native SDK **apps are authored in TypeScript + Native markup by default**. Do not infer the app-authoring language from this repository's implementation language or from older Zig-core examples.
- For a new app, use `native init <path>` and expect `src/core.ts`, `src/app.native`, and `app.zon`. Ordinary compiled TypeScript work that needs filesystem, process, JSON, regex, classes, or other static-tier APIs belongs under optional `src/services/`, reached from the core with `Cmd.request`; do not import a service from the core. Do not add Zig app code unless the user explicitly chooses `--template zig-core` or the feature requires a toolkit extension.
- Before changing an existing app, inspect its tree. A `src/core.ts` app stays TypeScript; a `src/main.zig` app stays Zig unless the task is specifically a migration.
- For default app work, read `skill-data/native-ui/SKILL.md` and `skill-data/ts-core/SKILL.md`; also read `skill-data/ts-services/SKILL.md` when the tree has `src/services/` or the task needs ordinary TypeScript beyond the core subset. `skill-data/core/SKILL.md` covers shared/runtime concerns; `skill-data/zig/SKILL.md` is for Zig-core apps and SDK implementation work.
- The `-ts` suffix on a few examples only distinguishes ports from older Zig originals. New TypeScript apps need no suffix because TypeScript is the default.
## Build, test, and gate
```bash
@@ -20,7 +29,7 @@ Pinned goldens (pixel signatures, schema fingerprints, command counts) are updat
## 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.
Do not edit `CHANGELOG.md` as part of regular feature or fix work. The release agent reviews the git history since the previous release and writes the complete changelog entry during release preparation; see [RELEASING.md](./RELEASING.md).
## Where things live
+324 -3
View File
@@ -2,10 +2,333 @@
All notable changes to the Native SDK (formerly zero-native) will be documented in this file.
## 0.6.1
## 0.9.3
<!-- release:start -->
### New Features
- **Model-driven TypeScript theme state**: Zero-config TypeScript apps can now derive the built-in pack, color scheme, and accent from committed model state while preserving manifest fallback, live system accessibility settings, deterministic replay, and the existing `themePack` helper (#378).
- **Platform-correct line deletion**: Command+Backspace on macOS now deletes to the start of a field or logical textarea line across every editable canvas control, with matching TypeScript text helpers, controlled-state behavior, undo, and replay (#377).
### Bug Fixes
- **Precise macOS file-drop routing**: AppKit drops now retain labeled canvas and WebView targets with top-left, view-local coordinates, while unlabeled window regions fall back to content coordinates (#374).
- **Manifest menus in generated runners**: Zero-config TypeScript and Zig-core apps now load `app.zon` commands, shortcuts, and menus consistently in live and replay runners, including ejected-runner fallbacks (#376).
- **Large TypeScript message unions compile reliably**: Generated shims now derive comptime scan quotas from message shape and identifier size, allowing wide unions to compile across persistence, channels, environment routing, and the full external-core pipeline (#375).
- **Correct combobox Enter precedence**: A bound `on-submit` now handles Enter before trigger activation, so query submission no longer opens the picker or dispatches the wrong command (#373).
### Contributors
- @ctate
- @MohakBajaj
<!-- release:end -->
## 0.9.2
### New Features
- **Flash-free accessory startup**: Apps can opt into accessory activation from `app.zon` to launch without a Dock icon or foreground flash, with tray-affordance validation, runtime composition, packaging support, and an updated menu-bar example (#358).
- **Logical canvas radio groups**: Nested radios now form accessible single-selection groups with roving focus and consistent keyboard, pointer, handler, and naming semantics (#361).
- **Budget-aware photo decoding**: Dynamic encoded images are downsampled across desktop and mobile codecs to fit a configurable registered-pixel budget, with independent source bounds, deterministic replay, and platform-level regression coverage (#366).
### Bug Fixes
- **Correct anchored surfaces**: Floating and modal surfaces now dismiss without requiring focus, relayout after scroll restoration, resolve against the correct root, and behave consistently across window contexts (#363).
- **Reliable autofocus and caret reveal**: Keyboard focus, autofocus, and automation now transactionally reveal offscreen targets while preserving collapsed end-caret selections in text editors (#364).
- **Explicit link decoration**: Linked text spans now honor their underline flag while Markdown-generated links retain conventional underlines (#368).
- **Stable macOS window geometry**: Fresh windows now distinguish restored, explicit, and default placement, while AppKit and CEF frame events consistently report content geometry without titlebar drift (#369, #370).
### Improvements
- **Consistent canvas controls and surfaces**: Checkbox and radio labels can contain markup consistently, while actionable states, disabled colors, variant accents, selection geometry, compact layouts, and zero-width strokes now render uniformly across the schema, runtime, accessibility tree, and documentation (#367).
### Contributors
- @ctate
- @sepehr-safari
## 0.9.1
### New Features
- **Multi-item macOS menu bars**: Apps can now manage independent, keyed status items with model-driven updates, events, automation, journaling, and regression coverage (#343).
- **Complete TypeScript file effects**: Secure, permission-gated effects now support bounded streaming reads, atomic writes, stat, append, and deletion while preserving deterministic record and replay behavior (#339, #350).
- **Actionable desktop notifications**: Notification replacement identifiers and actions dispatch through the ordinary command path on macOS, Windows, and Linux (#347).
- **Secondary-window lifecycle control**: Window descriptors can declare quit or hide-on-close behavior, preserve hidden-window identity when reopened, and expose the same model-driven window contract to TypeScript apps (#349, #351).
- **Mobile TypeScript cores and services**: TypeScript apps with services now compile into iOS and Android library archives, with mobile packaging and device-level runtime coverage (#346).
### Bug Fixes
- **Safe Linux alert dialogs**: GTK alert dialogs now initialize with a valid empty format string, avoiding a crash from a null constructor argument (#354).
- **Correct compiled-core tuple returns**: The SDK now pins the scriptc tuple-normalization fix and verifies bare-model and effect-tuple ABI returns with a compiled-core regression (#356).
### Improvements
- **Stronger TypeScript core guidance and diagnostics**: Subset rules now distinguish permanent guarantees from deliberately deferred capabilities and point authors to the appropriate service alternative (#345).
- **End-to-end services showcase**: The Feed Reader example now demonstrates the full TypeScript service workflow with typed feed parsing, shared data, fixtures, and replay coverage (#352).
- **Updated compiler integration**: scriptc advances through 0.0.31 with refreshed generated contracts, compatibility fixtures, and compiler-surface references (#344, #356).
### Contributors
- @ctate
- @ElSebas41
- @johnlindquist
## 0.9.0
### New Features
- **Ordinary TypeScript services behind a typed boundary**: Apps can place filesystem, process, JSON, regex, class, and exact-vendored npm work under `src/services/`; Native SDK generates the checked client and codecs, compiles a pinned static service executable, and carries keyed requests, typed streaming, cooperative cancellation, deadlines, supervision, and deterministic replay across the isolated boundary (#317, #321).
- **Optional in-process TypeScript services**: Services can use the same boundary through a linked, runtime-localized worker pool with per-key FIFO ordering, parallel independent keys, streaming, timeout and trap isolation, and replay that never starts the carrier; the explicit opt-in now follows the compiler's Windows, Linux, macOS, and cross-target matrix while the isolated child remains the automatic default (#334, #337).
- **Engine-owned model persistence**: TypeScript cores can persist committed models through capability-gated, atomically replaced snapshots with generated codecs, debounced writes, backup recovery, explicit restore and migration routes, rollback safety, and journal/replay support (#316).
- **SQLite record storage**: TypeScript and Zig apps can use a capability-gated record store for deterministic atomic CRUD effects backed by bundled SQLite across desktop and mobile hosts, with devhost parity and a complete Record Store example (#320).
- **Checked relational SQLite**: Append-only migrations, build-validated named SQL, transactions, generated typed commands and live-query subscriptions, replay, and the Relational Notes example make relational SQLite a first-class offline data layer across desktop and mobile (#326).
- **Model-driven menu-bar apps**: TypeScript apps can derive status-item labels, icons, tooltips, and rich menus from committed model state, while new macOS effects control hidden startup, fullscreen, Dock visibility, and launch-at-login behavior across both native hosts (#311, #314).
- **Platform services for TypeScript cores**: Typed effects now open external URLs, reveal filesystem paths, and format local time through validated macOS, Linux, and Windows backends (#315).
- **App-scoped credentials**: TypeScript and Zig cores can store, load, and delete credentials through capability- and permission-gated native providers, with redacted journals, deterministic replay placeholders, and hermetic devhost stores across desktop and mobile (#335).
- **Cross-compiled TypeScript cores**: The external core compiler now builds Linux and Windows GNU targets from macOS, Linux, or Windows and macOS targets from macOS, with target-independent contracts and cross-platform end-to-end batteries for Windows and Linux musl (#340).
### Bug Fixes
- **Working documentation root**: `/docs`, `/docs/`, and the matching Markdown route now resolve to the Introduction instead of ending at a 404 (#338).
### Improvements
- **Faster retained desktop frames**: Animation pumping and Windows wake scheduling now avoid stalled or redundant work, profiling uses monotonic frame-correlated telemetry, and physical macOS and Windows performance gates protect input latency and frame budgets (#313).
- **Measured, compiler-truth service tooling**: A dedicated TypeScript Services reference documents the two-tier model and failure semantics; production-carrier benchmarks measure cold start, latency, and throughput; generated compiler-surface references and manifest diffs keep capability claims mechanically honest; and scriptc advances through 0.0.28 with refreshed contracts and calibration (#325, #327, #328, #329, #333, #336).
### Contributors
- @ctate
- @carvalab
- @Railly
- @camilocbarrera
## 0.8.4
### New Features
- **Streaming fetch responses for TypeScript cores**: `Cmd.fetch` can now deliver line-framed HTTP responses through typed message arms with deterministic terminal errors, loud cancellation, duplicate-key rejection, and bounded line sizes; the rebuilt Chatbot example streams Vercel AI Gateway replies with live model selection and a Stop action (#300).
- **Desktop audio capture**: TypeScript cores can start bounded, timestamped microphone or system-output PCM streams on macOS and Windows with explicit lifecycle, permission, drop-count, and replay handling; the new Voice Memo example records, saves, and plays WAV files (#303).
- **Customizable macOS DMG packaging**: `native package` now creates polished drag-to-Applications disk images with generated or custom Retina backgrounds, configurable Finder geometry, positioned app and Applications entries, and staged files, directories, or links (#304).
- **Live TypeScript theme packs**: zero-config TypeScript apps can export `themePack(model)` to switch the built-in theme pack from app state without losing live system scheme, accessibility, accent, or scale inputs (#308).
### Bug Fixes
- **Smooth macOS dialog blur**: Host backdrop blur now uses an optimized three-pass Gaussian approximation and correct dirty-region invalidation, eliminating flat or stale dialog backgrounds while preserving the established scrim treatment (#299).
- **Byte-accurate PTY event keys**: TypeScript PTY event routes now expose echoed session keys as `Uint8Array`, matching the byte-text host, generated facade, and external-core contract (#307).
- **Reliable keyboard widget navigation**: Interactive canvas lists, trees, menus, and anchored controls now retain logical focus across clipped rows, scroll keyboard targets into view, and paint active and focus-visible states consistently (#308).
### Improvements
- **TypeScript component gallery**: The GPU component showcase is now a TypeScript core and Native markup app with isolated interactive specimens, model-driven Default and Geist switching, clearer navigation, and dedicated smoke coverage (#308).
### Contributors
- @ctate
- @marcusschiesser
- @NyxTools-M
## 0.8.3
### Bug Fixes
- **Packaged TypeScript app assets**: Runtime asset lookup now finds bundled macOS resources before the process working directory, so Native markup boot images such as the Kanban agent avatars render after launch (#297).
- **Unclipped drag landing motion**: Dropped cards now stay in the lifted drag layer through their landing animation while neighboring reflow remains clipped within its swimlane (#297).
### Improvements
- **Denser Kanban showcase**: The seeded board now includes twice as many Jira-style tickets and removes redundant issue glyphs from card metadata (#297).
### Contributors
- @ctate
## 0.8.2
### New Features
- **Native drag and drop for TypeScript apps**: Native markup's new `on-drag` channel delivers live, release, and cancellation geometry to compiled cores while the renderer lifts the source under the pointer, preserves one globally keyed insertion slot, animates neighboring items, and supports Escape cancellation; TypeScript cores can also map native multi-file drops into ordinary deterministic messages through `dropMsg` (#285).
- **Desktop notifications from model cores**: TypeScript apps can return fire-and-forget `Cmd.showNotification` effects and Zig apps can call `fx.showNotification`, with bounded validation and suppression during fake execution and session replay (#283).
### Bug Fixes
- **Explicit zero canvas padding**: Programmatic and compiled or interpreted Native markup views now preserve `padding="0"` instead of replacing it with the widget kind's default padding (#288).
### Improvements
- **TypeScript-first app authoring guidance**: Repository instructions, bundled skills, examples, package documentation, and the docs site now consistently lead with TypeScript cores and Native markup for new apps while keeping Zig as the explicit alternative and toolkit-extension tier (#284).
- **Agent ticket Kanban showcase**: The TypeScript Kanban example now presents numbered OpenAI- and Claude-assigned tickets, uses an icon-only add action, keeps columns scrollable, and extends its end-to-end coverage for the updated drag geometry (#295).
### Contributors
- @ctate
- @johnlindquist
- @Railly
## 0.8.1
### New Features
- **Safe presentational HTML in Markdown**: Markdown now lowers common GitHub-style inline and block HTML into native widgets, including links, details, aligned containers, and caller-resolved images, while scripts, styles, forms, embeds, event attributes, and unsupported or malformed markup remain inert literal text (#280).
### Bug Fixes
- **Reliable resolved Markdown images**: image discovery now follows renderable block starts, canonicalizes entity-encoded URLs consistently between loading and lookup, preserves aspect ratios within declared bounds, honors centered and end alignment, and ignores images inside comments, unsupported markup, code, and preformatted blocks (#281).
- **Payload-free HTTP write requests**: `Effects.fetch` now sends an explicit zero-length body for POST, PUT, and PATCH requests without a payload, preventing debug-build crashes and emitting the required `Content-Length: 0` header (#277).
### Improvements
- **History-driven release notes**: release preparation now builds the complete changelog entry and contributor list from the commits since the previous release, replacing the per-change fragment workflow (#278).
### Contributors
- @ctate
- @Railly
## 0.8.0
### New Features
- **Compiler-truth checks for TypeScript cores**: `native check` now ends with the pinned external core compiler's analyzer over the entry with the shipped SDK declarations mapped, so check and build share one compiler verdict. Type errors the frontend's own line would miss fail with the compiler's diagnostics verbatim; an analyzer that cannot reach a verdict defers to the build instead of wedging check.
- **TypeScript cores compile through the external core compiler**: the frontend checks `src/core.ts` and emits its contract sidecar, the exact-pinned compiler builds a native archive, and the app links a generated mirror over it — no JS runtime in the binary, nothing to configure.
- **The TS-to-Zig transpiled lane is removed** (a deliberate pre-1.0 break): `core_compiler = "transpiler"` in app.zon (and `-Dcore-compiler=transpiler`) is refused with a teaching, and `native check` runs the checker and contract only — no emitted Zig lands under `.native/check/`.
- **The compiler is a package dependency**: it ships exact-pinned with the SDK's `packages/core` (repo checkouts install it with `npm ci` there; an npm-installed CLI carries it automatically).
- **The core dev loop is restart-shaped**: markup hot reload and the instant `native dev --core` node loop are unchanged, and a core edit now pays a native compile measured in seconds on rebuild.
- **TypeScript cores are desktop-only for now**: a mobile target with `src/core.ts` is taught before lane selection (the external toolchain does not target mobile yet); Zig and markup cores stay fully supported on mobile.
- **Shipped type declarations**: `@native-sdk/core` now ships generated `sdk/*.d.ts` declaration files beside its TypeScript sources, so external tooling can resolve the SDK's types without compiling them.
### Improvements
- **Leaner TypeScript toolchain installs**: the unused `@typescript/typescript6` compatibility wrapper is no longer a dependency of `@native-sdk/cli` or `@native-sdk/core`. The frontend already imports its compiler directly through the exactly pinned `@typescript/old` alias, while consumer trees carrying their own wrapper remain unaffected.
### Contributors
- @ctate
## 0.7.2
### New Features
- **Geist-style code diffs**: `ui.code` and `<code>` can mark added and removed logical lines with theme-aware full-row washes, renderer-owned `+`/`-` markers, optional line numbers, and unchanged syntax-highlighted clipboard source.
### Improvements
- **Verified Zig setup**: repository and generated CI workflows now install Zig with `vercel-labs/setup-zig`, including signed archive and checksum verification.
### Bug Fixes
- **Theme-accurate disabled buttons**: disabled buttons now keep shadcn's knockout label treatment in the default theme and use Geist's gray-100/gray-700 swap, gray-400 edge, and distinct half-opacity tertiary register in the Geist theme.
- **Canonical documentation routes**: documentation now lives under `/docs/`, with permanent redirects from every previous URL, explicit canonical metadata, `.md` siblings, and a generated `llms.txt` that stays aligned with each page's canonical MDX source.
- **Geist primary tabs match the design system**: tab strips now use the reference 50px row, full-width bottom rail, content-hugging 14px triggers, 24px spacing, and 16px icon treatment without changing default-theme pill tabs; the GPU component gallery now pairs a compact theme picker with a scrollable component tree and focused specimen views.
- **Quiet Windows subprocesses**: `Effects.spawn` no longer opens or flashes a console window when a GUI or tray app launches a console-subsystem helper such as `node.exe`; interactive terminal children remain on the separate PTY API.
- **Responsive Windows GPU surfaces**: Windows now renders retained binary canvas packets with Direct2D and DirectWrite, applies dirty-region patches (including edge-safe GPU-resident backdrop blur), and limits RGBA-to-BGRA conversion and invalidation to dirty pixels when software fallback is required.
- **Exact Windows packet text and chrome**: Packet rendering now refuses when the bundled/custom font path cannot preserve engine-planned metrics, preserves explicitly positioned glyph runs, prevents system glyph substitution, and samples a covered, changed hidden-titlebar pixel for native caption contrast.
- **Truthful GPU backend types**: TypeScript creation options now expose only portable backend requests while view and frame state can report the concrete Direct2D renderer; explicit software requests bypass packet encoding and image uploads and stay on the reference renderer and pixel presenter.
- **Reliable registered-image replacement**: Unregistering and then re-registering identical pixels now recreates the removed GPU resource instead of retaining a stale cache key and silently omitting the image.
### Contributors
- @ctate
- @oshtz
## 0.7.1
### New Features
- **Declarative folder-to-code editor example**: `examples/code-editor` authors its complete view in hot-reloadable `.native` markup, unifies its titlebar, file pane, tab-strip canvas, and editor background, centers the opened folder name beside a trailing ghost Save icon in a custom titlebar, opens or replaces the focused window's folder with Cmd+O, creates independent editor windows with Cmd+N, builds a clean bounded folders-first disclosure tree with outline-free selection-only Up/Down navigation, leaf-to-parent Left movement, Left/Right expansion, in-place disk-backed Enter rename, Cmd+Enter permanent tabs, and folder focus independent from the active editor, and presents editable syntax-highlighted files (including `.mjs` and large practical sources) in a resizable second pane with flat VS Code-style tabs whose active tab has no top accent and breaks the baseline to meet the editor, replaceable italic previews that pin when double-clicked, dirty dots, active/hover close buttons, native Close/Close Others tab menus, wrapping Cmd+Shift+[/] tab cycling, Cmd+W tab-or-empty-window closing, and serialized disk-backed Save/Cmd+S.
- **Generated compiled-core facade**: `corewire --facade` now emits the complete compiler entry and matching profile from the contract sidecar, including explicit `--f64-slot` demotions, authored type provenance, and signed or unsigned integer proofs at every host ingress.
- **Facade contract hardening**: generated entries preserve subdirectory module paths, reconstruct private reachable types without invalid imports, preserve Model-first resolution for homonymous unbound bindings, decode optional and composite record fields with a running cursor, prove nullable integer helpers, handle signed and unsigned text-selection sentinels consistently, and refuse legacy sidecars that lack the authored facts a facade requires.
- **Effective sidecar projection**: `corewire --effective-sidecar` emits the contract after explicit slot demotions, and staged facade/profile/sidecar triples now describe one compiled layout.
- **Editable highlighted code**: `ui.code` and `<code>` keep their read-only default, while `editable` plus `on-input` opts into a syntax-colored multiline editor in both retained and direct rendering, with selection, caret-row highlighting, IME, clipboard, undo/redo, indentation-aware Tab input (tabs or inferred 28-space widths, defaulting to two spaces), and no textarea chrome.
- **Markdown source highlighting**: `markdown`/`md` joins the code lexer names with themed headings, lists, emphasis, links, inline and fenced code, and comments; the code-editor example selects it for Markdown files.
- **Stable line-number gutter**: numbered code reserves at least three marker columns, so short files keep a useful gutter while larger line counts still expand it.
- **Double-click messages in Native markup**: `on-double-press` exposes the canvas runtime's additive double-click channel to `.native` views, so the first click can select or preview and the second can perform or pin without a timer. Multi-click chains stay scoped to one control and physical pointer, and a third click returns to the ordinary press action instead of repeating the double action.
### Improvements
- **Composable code presentation**: `ui.code` and `<code>` now provide bare highlighted content without their own background, border, radius, shadow, or padding; wrap them in a panel or card when surface chrome is wanted. An enabled line-number gutter remains opaque while horizontally scrolling so source glyphs cannot clash with its pinned markers.
- **Flat tree keyboard hierarchy**: `treeitem` rows can declare a one-based `tree-level`, letting Left/Right find logical parents and children in loop-rendered flat trees, while `on-change` can keep arrow-key selection distinct from pointer activation.
### Bug Fixes
- **Live code docs preview**: The Code component page now loads its real WASM-backed engine scene instead of silently remaining on the static screenshot fallback.
- **Reliable large-code editing**: editable code now repaints only visible selected glyphs and caches longest-line width measurements, keeping large selections and steady-state no-wrap rendering inside bounded display-list and host-measurement budgets.
- **Complete wrapped long lines**: scrolling a single logical line beyond 128 wrapped rows now pages its visible glyphs instead of leaving the remainder blank.
- **Stable code-editor reads**: switching tabs no longer cancels a pinned file's load, and reopened secondary windows keep monotonic file-effect keys so late completions cannot populate a newer document.
- **Unsaved-edit protection**: opening another folder or closing a secondary editor window now refuses while that window still has dirty documents.
- **Steady editor tabs**: active and inactive tabs now share the same background and label alignment, so filenames no longer shift when selection changes.
- **Balance explorer rows**: file-tree hover and selection backgrounds now keep even visual gutters beside the sidebar edge and split handle while preserving compact label alignment.
- **Complete repository roots**: the explorer now indexes a folder when it expands instead of spending its bounded tree budget in an eager depth-first walk, so large subtrees cannot hide root files or unexplored sibling folders; `.next` and `.pnpm-store` remain visible but are not recursively indexed.
- **Familiar file opening**: Command+Down Arrow now opens the selected tree file as a persistent tab; Command+Enter remains available to the focused control.
- **Visible active tabs**: inactive tabs retain their bottom divider, and opening, clicking, or keyboard-cycling to a tab now minimally scrolls it into view horizontally without shifting an already visible tab.
- **Distinct new windows**: Command+N now opens each editor window slightly down and to the right of the active window so the new window is immediately apparent.
- **Clear empty-window title**: editor windows now show “Code Explorer” in the title bar until a folder is opened.
- **Stable editable-code repainting**: syntax-highlighted editors now keep unique retained command IDs while edited text and highlighted spans occupy different runtime storage, preventing a selected editor from crashing when the app deactivates.
- **Safe large widget text**: views keep their ordinary 64 KiB text pools inline and allocate practical source-file capacity only when a large layout or edit needs it, while edit, presentation, and context-menu workspaces stay off constrained native stacks and large single-line pastes continue stripping line breaks.
- **Folder-only macOS open dialogs**: `allow_directories = true` now matches Linux and Windows by selecting directories rather than allowing files alongside them in AppKit and CEF hosts.
- **Code-editor presentation polish**: JavaScript and TypeScript object keys and typed bindings now use the same syntax color as variables, while CSS declaration names retain their property color; numbered editors also use their full trailing width so fitting lines do not produce false horizontal scrolling.
- **Complete JSX and TSX syntax highlighting**: JSX-family code blocks now combine JavaScript or TypeScript token coloring with JSX tags and attributes instead of treating the whole file as plain HTML outside `{…}` expressions.
- **YAML syntax highlighting**: code surfaces, Markdown fences, and the code-editor example now recognize `yaml` and `yml`, coloring mapping keys, scalars, document markers, anchors, tags, and comments.
### Contributors
- @ctate
## 0.7.0
### New Features
- **Code component**: `ui.code` and markup `<code>` render highlighted source with the Geist Code Block palette in both built-in themes, wrapping by default, opt-in logical line numbers, unwrapped horizontal scrolling, and vertical scrolling for height-constrained surfaces; Markdown fences share the same component.
### Bug Fixes
- **Bounded transformed code rendering**: heavily scaled code surfaces now degrade within the shared command and text-byte budgets instead of rejecting the entire display-list refresh.
- **Polished Markdown lists and code blocks**: bullet and ordered-list markers now align with the first content line, while fenced code preserves source indentation and applies theme-aware highlighting with richer HTML/JSX tags and attributes.
### Contributors
- @ctate
## 0.6.3
### Bug Fixes
- **Native textarea editing shortcuts**: Up/Down now moves or extends the caret across visual lines, Command+Left/Right uses the current line boundary even through unbroken soft wraps, Command+Up/Down reaches the document boundary, and Command+Z / Command+Shift+Z provides bounded per-editor undo and redo from either the keyboard or macOS Edit menu while keeping controlled `TextBuffer` models synchronized.
- **Textarea indentation**: spaces typed at the start of an empty line now remain visible and advance the caret under word wrapping.
- **Textarea pointer selection**: Shift-click now extends the selection from the existing caret instead of replacing it.
- **Textarea line endings**: caret movement, deletion, and controlled selections now treat CRLF line endings as one indivisible boundary.
### Contributors
- @ctate
## 0.6.2
### New Features
- **Reliable desktop overlay windows**: window declarations and runtime creation now support transparent, always-on-top, click-through, and passive-show presentation applied before first visibility; canvas windows reveal after their first alpha-correct present without stealing focus, fall back to a late reveal if rendering wedges, and Windows composites multiple canvas layers while rejecting child surfaces its layered presenter cannot display.
- **Honest backend constraints**: Linux main WebViews inherit transparent-window alpha, macOS Chromium rejects transparent windows because windowed CEF content cannot supply alpha, and transparent Windows windows require chromeless chrome with no application menu because the layered compositor cannot capture Win32 non-client pixels.
- **Resizable transparent Windows windows**: the layered presenter keeps the nearly invisible system resize frame pointer-targetable without filling intentional alpha-zero regions in the client.
- **Hybrid overlay lifecycle**: canvas-only overlay windows stay free of implicit main WebViews in mixed WebView scenes and across hot reloads, while passive Linux windows restore from minimization without taking focus.
- **Reliable explicit focus on macOS**: focusing a system-WebView window now activates the app before asking AppKit to make the window key, so an inactive app can come forward as requested.
- **Imperative canvas overlays**: `runtime.createWindow` and `window.zero.windows.create` keep transparent windows without an explicit source canvas-only across hot reloads, and JavaScript can select the chromeless titlebar Windows requires.
- **Idle overlays stay idle**: transparent canvas windows retain their last presented image without entering a display-rate repaint loop, while software presenters still rebuild fully once when their shared pixel buffer changes surfaces.
### Bug Fixes
- **Container backgrounds render**: explicit backgrounds on `stack`, `row`, and `column` now paint across the laid-out frame with their configured radius.
### Contributors
- @ctate
- @jasonkneen
- @sepehr-safari
## 0.6.1
### Bug Fixes
- **Layered macOS cursors**: GPU surfaces now yield their cursor regions to higher-layer embedded webviews, so links, selectable text, and canvas widgets use the correct cursor in mixed canvas/webview windows such as Workbench.
@@ -15,8 +338,6 @@ All notable changes to the Native SDK (formerly zero-native) will be documented
- @ctate
<!-- release:end -->
## 0.6.0
### New Features
+2 -2
View File
@@ -123,6 +123,6 @@ Branch from `main` (fork first if you don't have push access), keep the change f
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.
Do not edit `CHANGELOG.md` as part of a feature or fix PR; the release agent writes the complete entry from the release-range history. Open the PR against `main` describing what changed and why; for larger changes, open an issue first so the design can be discussed.
Commits must be cryptographically signed (`git commit -S`, or set `commit.gpgsign = true`) so they show as **Verified** — the `Signed-off-by` trailer from `git commit -s` is a DCO attestation, not a signature.
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.
+5 -2
View File
@@ -93,17 +93,20 @@ Read the full guide at [native-sdk.dev/quick-start](https://native-sdk.dev/quick
## 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`.
The apps pictured above live in [examples/](./examples), most as zero-config projects — `app.zon` plus `src/`, no build files — run straight from their directory with `native dev`. Start with the TypeScript examples when learning the primary authoring path. The `-ts` suffix on `soundboard-ts` and `system-monitor-ts` is historical because those apps are ports kept beside older Zig originals. Chatbot is TypeScript-only and follows the unsuffixed naming used by new apps created with `native init`.
| Example | What it shows |
| --- | --- |
| [`chatbot`](./examples/chatbot) | TypeScript + Native markup end to end: modules, a text editor, streaming fetch effects, and replay-safe configuration. |
| [`soundboard-ts`](./examples/soundboard-ts) | The full music-player showcase in TypeScript + Native markup: audio, search, assets, timers, and context menus. |
| [`system-monitor-ts`](./examples/system-monitor-ts) | A live process monitor in TypeScript + Native markup: subprocess effects, tables, charts, and timers. |
| [`calculator`](./examples/calculator) | A complete small app: markup keypad, keyboard input, chrome shortcuts, theming. |
| [`notes`](./examples/notes) | Persistence through the effects channel: debounced writes, restore on boot, dialogs, search. |
| [`soundboard`](./examples/soundboard) | Album grid with decoded cover art, context menus, timers, and a custom theme. |
| [`deck`](./examples/deck) | The soundboard player rebuilt as a dense hardware chassis: two windows, same widgets, different tokens. |
| [`feed`](./examples/feed) | A 100,000-row list, virtualized with runtime-owned scrolling. |
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.
The unsuffixed showcase apps above predate the TypeScript default and retain their Zig cores as first-class alternative implementations. The full catalog in [examples/README.md](./examples/README.md) also covers guarded OS capabilities, GPU surfaces, WebView composition, web-frontend shells, and the iOS/Android embed hosts.
## Platforms
+10 -5
View File
@@ -7,11 +7,16 @@ 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 `<!-- release:start -->` and `<!-- release:end -->` 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 `<!-- release:start -->` and `<!-- release:end -->` markers from the previous release entry; only the latest release should have markers
8. Open a PR and merge to `main`
4. Review the git history since the previous release and write the complete changelog entry at the top of `CHANGELOG.md`, under a new `## <version>` heading wrapped in `<!-- release:start -->` and `<!-- release:end -->` markers
5. Populate the entry's `### Contributors` from commit authors and `Co-authored-by` trailers in the release range, using GitHub handles when available; this marked block is also the GitHub release body
6. Remove the `<!-- release:start -->` and `<!-- release:end -->` markers from the previous release entry; only the latest release should have markers
7. Open a PR and merge to `main`
## Writing the changelog
Follow the existing format and voice. Group changes under descriptive headings such as `### New Features`, `### Bug Fixes`, and `### Improvements`. Give each bullet a bold lead-in followed by a concise description, and include PR numbers when available. Do not prefix entries with commit hashes.
The release entry should cover the complete git range since the previous release, including changes whose individual PRs did not touch `CHANGELOG.md`.
CI compares the version in `packages/native-sdk/package.json` to what's on npm. If it differs, it cross-builds the CLI for every platform, creates the GitHub release with the binaries, publishes the per-platform binary packages (`packages/native-sdk/npm/*`), and publishes `@native-sdk/cli` last — so the main package only lands once every binary package it pins is live. If npm already has the version but the GitHub release is missing assets, CI recreates the GitHub release from the marked changelog entry.
+1400 -809
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -27,6 +27,7 @@
"src",
"templates",
"tests",
"third_party/sqlite",
"tools",
},
}
+1478 -102
View File
File diff suppressed because it is too large Load Diff
+6 -8
View File
@@ -61,15 +61,13 @@ if (typeof module.registerHooks !== 'function') {
load(url, context, nextLoad) {
if (url.startsWith('file:') && url.endsWith('.ts')) {
const filePath = fileURLToPath(url);
// The transpiler's own pinned compiler, resolved from the target
// The frontend's own pinned compiler, resolved from the target
// module's location (packages/core/node_modules after the taught
// `npm ci`, or the dependency npm installed beside the CLI). The
// ALIAS is required directly — not the @typescript/typescript6
// wrapper — because the wrapper's re-export resolves
// "@typescript/old" from the WRAPPER's own location, where a
// consumer tree's conflicting hoisted copy would win nearest-wins
// over our exact pin; resolving from the target finds our own
// nested/hoisted pin first (same reasoning as typed_ast.ts).
// `npm ci`, or the dependency npm installed beside the CLI):
// resolving from the target finds our own nested/hoisted exact
// pin first, so a consumer tree's conflicting hoisted typescript
// never wins nearest-wins over it (same reasoning as
// typed_ast.ts).
if (ts === null) {
try {
ts = createRequire(targetPath)('@typescript/old');
-29
View File
@@ -1,29 +0,0 @@
# 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/<slug>.md`, where `<slug>` 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.
-7
View File
@@ -1,7 +0,0 @@
feature: **Reliable desktop overlay windows**: window declarations and runtime creation now support transparent, always-on-top, click-through, and passive-show presentation applied before first visibility; canvas windows reveal after their first alpha-correct present without stealing focus, fall back to a late reveal if rendering wedges, and Windows composites multiple canvas layers while rejecting child surfaces its layered presenter cannot display.
- **Honest backend constraints**: Linux main WebViews inherit transparent-window alpha, macOS Chromium rejects transparent windows because windowed CEF content cannot supply alpha, and transparent Windows windows require chromeless chrome with no application menu because the layered compositor cannot capture Win32 non-client pixels.
- **Resizable transparent Windows windows**: the layered presenter keeps the nearly invisible system resize frame pointer-targetable without filling intentional alpha-zero regions in the client.
- **Hybrid overlay lifecycle**: canvas-only overlay windows stay free of implicit main WebViews in mixed WebView scenes and across hot reloads, while passive Linux windows restore from minimization without taking focus.
- **Reliable explicit focus on macOS**: focusing a system-WebView window now activates the app before asking AppKit to make the window key, so an inactive app can come forward as requested.
- **Imperative canvas overlays**: `runtime.createWindow` and `window.zero.windows.create` keep transparent windows without an explicit source canvas-only across hot reloads, and JavaScript can select the chromeless titlebar Windows requires.
- **Idle overlays stay idle**: transparent canvas windows retain their last presented image without entering a display-rate repaint loop, while software presenters still rebuild fully once when their shared pixel buffer changes surfaces.
@@ -1 +0,0 @@
fix: **Container backgrounds render**: explicit backgrounds on `stack`, `row`, and `column` now paint across the laid-out frame with their configured radius.
+1
View File
@@ -4,3 +4,4 @@ next-env.d.ts
.next-gate/
.next-agent/
.next-check/
.next-final/
+34 -1
View File
@@ -1,5 +1,8 @@
import createMDX from "@next/mdx";
import { createRequire } from "node:module";
import { readdirSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
// Resolve the plugin to an absolute path (still a string, so the config
// stays serializable for Turbopack). A bare "remark-gfm" is require()d
@@ -7,6 +10,19 @@ import { createRequire } from "node:module";
// module isolation cannot see this app's dependencies — production
// builds resolved it, the Turbopack dev server did not.
const require = createRequire(import.meta.url);
const docsContentDir = fileURLToPath(new URL("./src/app/docs", import.meta.url));
function docsSlugs(dir = docsContentDir, segments = []) {
const slugs = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (entry.isDirectory()) {
slugs.push(...docsSlugs(path.join(dir, entry.name), [...segments, entry.name]));
} else if (entry.name === "page.mdx" && segments.length > 0) {
slugs.push(segments.join("/"));
}
}
return slugs;
}
const withMDX = createMDX({
options: {
@@ -30,9 +46,26 @@ const nextConfig = {
ignored: ["**/.next-gate/**", "**/.next-check/**"],
},
async redirects() {
// Config redirects preserve the request query string. Keeping these out
// of the prerendered catch-all route avoids baking a query-less Location
// header into every legacy URL's static response.
const legacyDocsRedirects = docsSlugs().flatMap((slug) => [
{ source: `/${slug}`, destination: `/docs/${slug}`, permanent: true },
{ source: `/${slug}.md`, destination: `/docs/${slug}.md`, permanent: true },
{ source: `/md/${slug}`, destination: `/docs/${slug}.md`, permanent: true },
]);
return [
// The Philosophy page became the Introduction, the opening page of the docs.
{ source: "/philosophy", destination: "/introduction", permanent: true },
{ source: "/philosophy", destination: "/docs/introduction", permanent: true },
// docsSlugs() only yields nested slugs, so the /docs segment itself has
// neither a route nor a generated redirect and 404s. It is the parent of
// every documentation link on the site and the likeliest hand-typed entry
// point, so open it on the Introduction instead. The .md sibling keeps the
// Markdown surface whole for agents that reach for it.
{ source: "/docs", destination: "/docs/introduction", permanent: true },
{ source: "/docs.md", destination: "/docs/introduction.md", permanent: true },
...legacyDocsRedirects,
];
},
};
+2 -2
View File
@@ -9,7 +9,7 @@
"build": "next build",
"start": "next start",
"typecheck": "tsc --noEmit",
"check": "pnpm typecheck && pnpm build && node scripts/check-code-toggle.mjs"
"check": "pnpm typecheck && pnpm build && node scripts/check-doc-routes.mjs && node scripts/check-code-toggle.mjs && node scripts/check-wasm-preview.mjs"
},
"dependencies": {
"@mdx-js/loader": "^3",
@@ -41,4 +41,4 @@
"postcss@<8.5.10": ">=8.5.10"
}
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 854 B

After

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.
+203
View File
@@ -0,0 +1,203 @@
// SEO regression gate for the /docs migration. Every page.mdx must build at
// one canonical HTML URL and one canonical Markdown sibling; the former route
// must redirect permanently rather than render duplicate content. Canonical
// metadata, sitemap entries, llms.txt, and rendered internal links must all
// point directly into /docs so crawlers never have to choose between copies.
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { dirname, join, relative, sep } from "node:path";
import { fileURLToPath } from "node:url";
const docsDir = join(dirname(fileURLToPath(import.meta.url)), "..");
const sourceDir = join(docsDir, "src", "app", "docs");
const distDir = join(docsDir, process.env.NEXT_DIST_DIR || ".next");
const appOutputDir = join(distDir, "server", "app");
const siteUrl = "https://native-sdk.dev";
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 readRequired(file, route) {
if (!existsSync(file)) {
throw new Error(`${route}: missing build output ${file}`);
}
return readFileSync(file, "utf8");
}
const routesManifest = JSON.parse(
readRequired(join(distDir, "routes-manifest.json"), "redirect manifest"),
);
function assertConfiguredRedirect(source, destination) {
const redirect = routesManifest.redirects.find((candidate) => candidate.source === source);
if (!redirect || redirect.statusCode !== 308 || redirect.destination !== destination) {
throw new Error(
`${source}: expected a query-preserving 308 config redirect to ${destination}, got ${JSON.stringify(redirect)}`,
);
}
}
function proseLines(markdown) {
const lines = [];
let fence = null;
for (const line of markdown.split("\n")) {
const marker = line.trimStart().match(/^(```|~~~)/)?.[1];
if (marker) {
if (fence === marker) fence = null;
else if (fence === null) fence = marker;
continue;
}
if (fence === null) lines.push(line);
}
return lines;
}
function headings(markdown) {
return proseLines(markdown)
.map((line) => line.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/))
.filter(Boolean)
.map((match) => `${match[1]} ${match[2]}`);
}
function assertCleanMarkdown(source, markdown, route) {
// Generic type spellings such as `Sub<Msg>` are legitimate inside inline
// code and must not be mistaken for unresolved MDX components.
const prose = proseLines(markdown).join("\n").replace(/`[^`\n]*`/g, "");
const unresolved = prose.match(/<[A-Z][A-Za-z0-9]*(?:\s|\/?>)/)?.[0];
if (unresolved) {
throw new Error(`${route}: unresolved MDX component in Markdown output: ${unresolved}`);
}
const unresolvedExpression = prose.match(/\{"[^"\\\r\n]*"\}|\{'[^'\\\r\n]*'\}/)?.[0];
if (unresolvedExpression) {
throw new Error(
`${route}: unresolved MDX string expression in Markdown output: ${unresolvedExpression}`,
);
}
const renderedHeadings = new Set(headings(markdown));
for (const heading of headings(source)) {
if (!renderedHeadings.has(heading)) {
throw new Error(`${route}: Markdown output dropped source heading ${heading}`);
}
}
for (const component of source.matchAll(/<EjectSection\s+components=\{\[([\s\S]*?)\]\}\s*\/>/g)) {
if (!renderedHeadings.has("## Eject")) {
throw new Error(`${route}: EjectSection did not render its heading`);
}
for (const name of component[1].matchAll(/"([^"]+)"/g)) {
const command = `native eject component ${name[1]}`;
if (!markdown.includes(command)) {
throw new Error(`${route}: EjectSection did not render command ${command}`);
}
}
}
}
const pageFiles = [...mdxPages(sourceDir)];
const canonicalPaths = new Set(
pageFiles.map((page) => {
const slug = relative(sourceDir, dirname(page)).split(sep).join("/");
return `/docs/${slug}`;
}),
);
const canonicalUrls = new Set([...canonicalPaths].map((route) => `${siteUrl}${route}`));
const canonicalMarkdownUrls = new Set(
[...canonicalPaths].map((route) => `${siteUrl}${route}.md`),
);
let pages = 0;
for (const page of pageFiles) {
pages += 1;
const slug = relative(sourceDir, dirname(page)).split(sep).join("/");
const canonicalPath = `/docs/${slug}`;
const canonicalUrl = `${siteUrl}${canonicalPath}`;
const output = join(appOutputDir, "docs", slug);
const html = readRequired(`${output}.html`, canonicalPath);
const source = readFileSync(page, "utf8");
if (!html.includes(`<link rel="canonical" href="${canonicalUrl}"/>`)) {
throw new Error(`${canonicalPath}: missing its exact canonical link tag`);
}
if (!html.includes(`<meta property="og:url" content="${canonicalUrl}"/>`)) {
throw new Error(`${canonicalPath}: Open Graph URL is not canonical`);
}
for (const match of html.matchAll(/<a\b[^>]*\bhref="([^"]+)"/g)) {
const href = match[1];
if (href?.startsWith("/") && href !== "/" && !href.startsWith("/docs/")) {
throw new Error(`${canonicalPath}: rendered internal link bypasses /docs: ${href}`);
}
if (href?.startsWith("/docs/")) {
const target = href.split(/[?#]/, 1)[0];
if (target && !canonicalPaths.has(target)) {
throw new Error(`${canonicalPath}: rendered internal link targets no docs page: ${href}`);
}
}
}
const markdownMeta = JSON.parse(readRequired(`${output}.md.meta`, `${canonicalPath}.md`));
if (
markdownMeta.status !== 200 ||
markdownMeta.headers?.["content-type"] !== "text/markdown; charset=utf-8"
) {
throw new Error(`${canonicalPath}.md: expected a static text/markdown response`);
}
if (markdownMeta.headers?.link !== `<${canonicalUrl}>; rel="canonical"`) {
throw new Error(`${canonicalPath}.md: missing its exact canonical HTTP Link header`);
}
const markdown = readRequired(`${output}.md.body`, `${canonicalPath}.md`);
assertCleanMarkdown(source, markdown, `${canonicalPath}.md`);
assertConfiguredRedirect(`/${slug}`, canonicalPath);
assertConfiguredRedirect(`/${slug}.md`, `${canonicalPath}.md`);
assertConfiguredRedirect(`/md/${slug}`, `${canonicalPath}.md`);
for (const legacyOutput of [
join(appOutputDir, `${slug}.html`),
join(appOutputDir, `${slug}.meta`),
join(appOutputDir, `${slug}.md.meta`),
join(appOutputDir, "md", `${slug}.meta`),
]) {
if (existsSync(legacyOutput)) {
throw new Error(`/${slug}: legacy URL was prerendered instead of using its config redirect`);
}
}
}
if (pages === 0) throw new Error("no docs page.mdx files found");
const sitemap = readRequired(join(appOutputDir, "sitemap.xml.body"), "/sitemap.xml");
const sitemapUrls = new Set([...sitemap.matchAll(/<loc>([^<]+)<\/loc>/g)].map((match) => match[1]));
for (const match of sitemap.matchAll(/<loc>([^<]+)<\/loc>/g)) {
const url = match[1];
if (url !== `${siteUrl}/` && !canonicalUrls.has(url)) {
throw new Error(`/sitemap.xml: non-canonical documentation URL ${url}`);
}
}
for (const url of canonicalUrls) {
if (!sitemapUrls.has(url)) throw new Error(`/sitemap.xml: missing canonical page ${url}`);
}
const llms = readRequired(join(appOutputDir, "llms.txt.body"), "/llms.txt");
const llmsUrls = new Set();
for (const line of llms.split("\n")) {
if (!line.startsWith("- [")) continue;
const url = line.match(/^- \[[^\]]+\]\(([^)]+)\)$/)?.[1];
if (!url || !canonicalMarkdownUrls.has(url)) {
throw new Error(`/llms.txt: non-canonical documentation link: ${line}`);
}
llmsUrls.add(url);
}
for (const url of canonicalMarkdownUrls) {
if (!llmsUrls.has(url)) throw new Error(`/llms.txt: missing canonical page ${url}`);
}
console.log(
`docs route check passed: ${pages} canonical pages, Markdown siblings, and query-preserving legacy redirects verified`,
);
+63
View File
@@ -0,0 +1,63 @@
// Regression pin for the Code component docs previews. ComponentPreview
// deliberately keeps a webp fallback under its live canvas, so a stale
// or incompatible wasm scene can otherwise fail silently and leave the
// page looking correct while it is only showing the screenshot.
//
// Require the checked-in module to instantiate the exact `code` and
// `code-diff` scenes used by /components/code. Runs after `next build`
// as part of `pnpm check`.
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const docsDir = join(dirname(fileURLToPath(import.meta.url)), "..");
const wasmPath = join(docsDir, "public", "wasm", "component-preview.wasm");
const vocabPath = join(docsDir, "src", "lib", "component-vocab.json");
const bytes = readFileSync(wasmPath);
const vocab = JSON.parse(readFileSync(vocabPath, "utf8"));
const { instance } = await WebAssembly.instantiate(bytes, {});
const exports = instance.exports;
const sceneNames = ["code", "code-diff"];
if (
typeof exports.preview_code_diff_metadata_round_trip !== "function" ||
exports.preview_code_diff_metadata_round_trip() !== 1
) {
throw new Error(
"the checked-in component-preview.wasm truncates code-diff metadata above line 96 on wasm32",
);
}
for (const sceneName of sceneNames) {
const scene = new TextEncoder().encode(sceneName);
const scenePtr = exports.preview_alloc(scene.length);
if (!scenePtr) {
throw new Error(`${sceneName} WASM preview check could not allocate its scene name`);
}
new Uint8Array(exports.memory.buffer).set(scene, scenePtr);
const handle = exports.preview_create(scenePtr, scene.length, 0);
exports.preview_free(scenePtr, scene.length);
if (!handle) {
throw new Error(
`the checked-in component-preview.wasm cannot create the \`${sceneName}\` scene; rebuild it with \`zig build docs-wasm-preview\``,
);
}
const width = exports.preview_logical_width(handle);
const height = exports.preview_logical_height(handle);
exports.preview_destroy(handle);
const expectedWidth = vocab.previews[sceneName].width / 2;
const expectedHeight = vocab.previews[sceneName].height / 2;
if (width !== expectedWidth || height !== expectedHeight) {
throw new Error(
`the ${sceneName} WASM preview is ${width}x${height}; expected the catalog's ${expectedWidth}x${expectedHeight} scene`,
);
}
console.log(`${sceneName} WASM preview check passed: live scene instantiated at ${width}x${height}`);
}
+63
View File
@@ -0,0 +1,63 @@
import { readFile, readdir } from "node:fs/promises";
import path from "node:path";
import { mdxToCleanMarkdown } from "@/lib/mdx-to-markdown";
import { siteUrl } from "@/lib/site";
/**
* Serve every canonical docs page as clean Markdown beside its HTML route.
* Legacy URLs are config redirects so Next can preserve each request's query
* string; this route stays fully static and only emits the canonical siblings.
*/
export const dynamic = "force-static";
export const dynamicParams = false;
const docsDir = () => path.join(process.cwd(), "src", "app", "docs");
export async function generateStaticParams(): Promise<{ slug: string[] }[]> {
const params: { slug: string[] }[] = [];
async function walk(dir: string, slug: string[]): Promise<void> {
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
await walk(path.join(dir, entry.name), [...slug, entry.name]);
} else if (entry.name === "page.mdx" && slug.length > 0) {
const markdownSlug = [...slug];
markdownSlug[markdownSlug.length - 1] += ".md";
params.push({ slug: ["docs", ...markdownSlug] });
}
}
}
await walk(docsDir(), []);
return params;
}
export async function GET(_request: Request, context: { params: Promise<{ slug: string[] }> }) {
const { slug } = await context.params;
if (slug[0] !== "docs") return new Response("Not found", { status: 404 });
const sourceSlug = slug.slice(1);
const filename = sourceSlug.at(-1);
if (!filename?.endsWith(".md") || filename === ".md") {
return new Response("Not found", { status: 404 });
}
sourceSlug[sourceSlug.length - 1] = filename.slice(0, -3);
const canonicalUrl = `${siteUrl}/docs/${sourceSlug.join("/")}`;
const filePath = path.join(docsDir(), ...sourceSlug, "page.mdx");
// Static params come from the filesystem walk above, but never follow
// a path that escapes src/app/docs.
if (!filePath.startsWith(docsDir() + path.sep)) {
return new Response("Not found", { status: 404 });
}
try {
const source = await readFile(filePath, "utf8");
return new Response(mdxToCleanMarkdown(source) + "\n", {
headers: {
"Content-Type": "text/markdown; charset=utf-8",
Link: `<${canonicalUrl}>; rel="canonical"`,
},
});
} catch {
return new Response("Not found", { status: 404 });
}
}
-31
View File
@@ -1,31 +0,0 @@
import { ComponentPreview } from "@/components/component-preview";
import { AttrTable } from "@/components/attr-table";
# Markdown
Renders a markdown string (a GFM subset, pipe tables included) as native widgets through the same text pipeline as every other component — deterministic layout, selectable text. `source` is required and must be one `{binding}`; the element takes no children. Links dispatch `on-link` with the URL as payload (bare URLs autolink), `<details>` blocks toggle through `on-details` plus a model-owned `details-expanded` flag list, and `#123` references linkify through `issue-link-base`.
<ComponentPreview name="markdown" alt="A markdown document rendered by the engine" caption="headings, emphasis, inline code, lists, links, and a code block" />
## Markup
```html
<markdown source="{release_notes}" on-link="open_link" issue-link-base="https://github.com/native-sdk/native/issues/"></markdown>
```
## Programmatic construction (Zig)
The builder is the `canvas.markdown` module, parameterized over the app's Msg type; `on_link` pairs with `Ui.linkMsg(.tag)`.
```zig
const Md = native_sdk.canvas.markdown.Markdown(Msg);
Md.view(ui, model.release_notes, .{
.on_link = Ui.linkMsg(.open_link),
.issue_link_base = "https://github.com/native-sdk/native/issues/",
})
```
## Attributes
<AttrTable element="markdown" attrs={["source", "on-link", "on-details", "details-expanded", "issue-link-base"]} />
-59
View File
@@ -1,59 +0,0 @@
import { ComponentPreview } from "@/components/component-preview";
import { AttrTable } from "@/components/attr-table";
import { CodeToggle } from "@/components/code-toggle";
# Radio
The single-choice value control, grouped by a `radio-group` row container. Like [checkbox](/components/checkbox), the label rides the `text` attribute — radio is not a text-bearing element, so text content between the tags is rejected with a teaching error. One model field holds the group's selection: render it with `{a == b}` equalities on each radio's `checked`, and let each radio's `on-toggle` dispatch the Msg that sets the field — the engine never flips state on its own.
<ComponentPreview name="radio-group" alt="A radio group rendered by the engine" caption="a radio group with one selected and one disabled option" />
## Markup
```html
<radio-group gap="12">
<radio checked="{density == default}" on-toggle="set_default" text="Default" />
<radio checked="{density == comfortable}" on-toggle="set_comfortable" text="Comfortable" />
<radio checked="{density == compact}" disabled="true" text="Compact" />
</radio-group>
```
One field holds the group's choice — a string-literal union in a TypeScript core, an enum in a Zig core — and each radio's arm sets it:
<CodeToggle>
```ts
// model: { readonly density: "default" | "comfortable" | "compact" }
case "set_comfortable":
return { ...model, density: "comfortable" };
```
```zig
// model: density: enum { default, comfortable, compact } = .default,
.set_comfortable => model.density = .comfortable,
```
</CodeToggle>
## Programmatic construction (Zig)
In a Zig view, the `canvas.Ui` builder constructs the same tree programmatically:
```zig
ui.el(.radio_group, .{ .gap = 12 }, .{
ui.el(.radio, .{ .text = "Default", .checked = model.density == .default, .on_toggle = .set_default }, .{}),
ui.el(.radio, .{ .text = "Comfortable", .checked = model.density == .comfortable, .on_toggle = .set_comfortable }, .{}),
ui.el(.radio, .{ .text = "Compact", .checked = model.density == .compact, .disabled = true }, .{}),
})
```
## Attributes
<AttrTable
attrs={[
"text",
"checked",
"disabled",
"on-toggle",
]}
/>
-41
View File
@@ -1,41 +0,0 @@
import { ComponentPreview } from "@/components/component-preview";
import { AttrTable } from "@/components/attr-table";
# Textarea
Multi-line text entry. Like [input](/components/input), `text` and `placeholder` bind from the model and `on-input` names a Msg variant that receives every edit as a text-input event — see [input](/components/input) for the core-side contract in both languages. Enter (and Shift+Enter) inserts a newline instead of submitting; when a textarea carries `on-submit`, the submit rides the primary chord — Cmd+Enter on macOS, Ctrl+Enter elsewhere. Give it a definite `width` and `height` (or a `grow`) to size the editing box.
<ComponentPreview name="textarea" alt="A textarea rendered by the engine" />
## Markup
```html
<textarea width="320" height="96" placeholder="Write a release note" text="{draft}" on-input="draft_edited" />
```
## Programmatic construction (Zig)
In a Zig view, the `canvas.Ui` builder constructs the same tree programmatically:
```zig
ui.el(.textarea, .{
.width = 320,
.height = 96,
.placeholder = "Write a release note",
.text = model.draft,
.on_input = Ui.inputMsg(.draft_edited),
}, .{})
```
## Attributes
<AttrTable
attrs={[
"text",
"placeholder",
"disabled",
"autofocus",
"on-input",
"on-submit",
]}
/>
-62
View File
@@ -1,62 +0,0 @@
import { ComponentPreview } from "@/components/component-preview";
import { AttrTable } from "@/components/attr-table";
# Tree
A disclosure-tree container. The rows are ordinary elements (usually [list items](/components/list)) carrying `role="treeitem"`, and every such descendant joins one roving keyboard focus set: Up/Down walk the visible rows, Left collapses a row or moves to its parent, Right expands or moves to the first child, Home/End jump to the edges. Expandable rows bind `expanded` and dispatch `on-toggle`; selection follows each row's `on-press`. The model owns both states — children of a collapsed row are simply not rendered.
<ComponentPreview name="tree" alt="A file tree with an expanded folder and a selected row rendered by the engine" caption="an expanded folder, indented children, and a collapsed sibling" />
## Markup
```html
<tree width="340" gap="2">
<list-item icon="folder-open" role="treeitem" expanded="{src_open}" on-toggle="toggle_src" on-press="select_src">src</list-item>
<if test="{src_open}">
<row>
<column width="20" />
<column gap="2" grow="1">
<list-item icon="file-text" role="treeitem" selected="{file == main}" on-press="select_main">main.zig</list-item>
<list-item icon="file-text" role="treeitem" selected="{file == view}" on-press="select_view">view.zig</list-item>
</column>
</row>
</if>
<list-item icon="folder" role="treeitem" expanded="{assets_open}" on-toggle="toggle_assets" on-press="select_assets">assets</list-item>
</tree>
```
The indent is plain layout — a fixed-width spacer column beside the children. Omit `expanded` on leaf rows; only rows that bind it participate in Left/Right disclosure.
## Programmatic construction (Zig)
In a Zig view, the `canvas.Ui` builder constructs the same tree programmatically:
```zig
ui.tree(.{ .width = 340, .gap = 2 }, .{
ui.listItem(.{
.icon = "folder-open",
.expanded = model.src_open,
.on_toggle = .toggle_src,
.on_press = .select_src,
.semantics = .{ .role = .treeitem },
}, "src"),
if (model.src_open) ui.row(.{}, .{
ui.column(.{ .width = 20 }, .{}),
ui.column(.{ .gap = 2, .grow = 1 }, .{
ui.listItem(.{ .icon = "file-text", .selected = model.file == .main, .on_press = .select_main, .semantics = .{ .role = .treeitem } }, "main.zig"),
ui.listItem(.{ .icon = "file-text", .selected = model.file == .view, .on_press = .select_view, .semantics = .{ .role = .treeitem } }, "view.zig"),
}),
}) else ui.stack(.{}, .{}),
ui.listItem(.{
.icon = "folder",
.expanded = model.assets_open,
.on_toggle = .toggle_assets,
.on_press = .select_assets,
.semantics = .{ .role = .treeitem },
}, "assets"),
})
```
## Attributes
<AttrTable attrs={["role", "expanded", "selected", "on-toggle", "on-press", "gap", "label"]} />
@@ -11,7 +11,7 @@ A Native SDK app is one loop with four parts:
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 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](/docs/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 the blueprint for porting a core to Zig by hand.
## The loop in full
@@ -81,7 +81,7 @@ Markup can never mutate state. `{count}` is a read; `on-press="increment"` names
## 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`:
`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 compiled core's 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);
@@ -103,15 +103,15 @@ pub fn main(init: std.process.Init) !void {
}
```
`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.
`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](/docs/windows) and [Native Surfaces](/docs/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 `<if>` 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.
- **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](/docs/state).
- **Errors degrade, they never crash.** A failing `update` arm is caught, recorded in a bounded error ring (visible in [automation](/docs/automation) snapshots as `dispatch_errors=`), and the app keeps running.
## Hot reload in development
@@ -156,8 +156,8 @@ A Zig-root app keeps dev-time hot reload for its embedded fragments too: build w
## 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).
`update` stays pure by routing anything that leaves the model — subprocesses, HTTP, file persistence, timers, clipboard, desktop notifications — through the effects channel, and routed 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](/docs/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](/docs/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).
`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](/docs/frontend). The [App & Runtime](/docs/runtime) reference documents that layer, and [Embedded App](/docs/embed) covers driving the runtime from an existing host (including iOS and Android).
@@ -24,8 +24,6 @@ The manifest `native init` generates — identity, one shell window with a GPU s
.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 },
},
@@ -45,7 +43,7 @@ The manifest `native init` generates — identity, one shell window with a GPU s
## 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:
A fuller manifest for an app that also [embeds web content](/docs/frontend) and declares commands, shortcuts, menus, and packaging metadata:
```zig:app.zon
.{
@@ -148,25 +146,41 @@ A fuller manifest for an app that also [embeds web content](/frontend) and decla
<td><code>platforms</code></td>
<td>Target platforms: <code>macos</code>, <code>linux</code>, <code>windows</code></td>
</tr>
<tr>
<td><code>dmg</code></td>
<td>Optional macOS DMG presentation: volume name, PNG/JPEG/TIFF background (with automatic adjacent <code>@2x</code> discovery), usable Finder canvas and icon sizes, simple app/Applications positions, or an explicit positioned <code>items</code> list of the app, Applications alias, project files/directories, and absolute links. The zero-config defaults produce a complete drag-to-Applications layout.</td>
</tr>
<tr>
<td><code>permissions</code></td>
<td>Runtime permissions (see <a href="/security">Security</a>)</td>
<td>Runtime permissions (see <a href="/docs/security">Security</a>). Audio capture uses <code>microphone</code> and <code>system_audio</code>; macOS packaging emits the matching microphone, audio-capture, and screen-capture usage descriptions only when declared</td>
</tr>
<tr>
<td><code>capabilities</code></td>
<td>Feature declarations (see <a href="/security">Security</a>)</td>
<td>Feature declarations (see <a href="/docs/security">Security</a>). <code>"store"</code> links the engine-owned record store; <code>"sqlite"</code> links relational SQL effects. They share one capability-shed SQLite object but use separate databases; see <a href="/docs/record-store">Record Store</a> and <a href="/docs/sqlite">Relational SQLite</a></td>
</tr>
<tr>
<td><code>dock_visible</code></td>
<td>Initial macOS Dock and app-switcher presence. Defaults to <code>true</code>. Set <code>false</code> for an Accessory/menu-bar app; the policy is applied before startup windows are created, so no Dock tile flashes. Accessory apps must declare the <code>"tray"</code> capability. Runtime <code>Cmd.setDockPresence</code> can still promote or demote the app later.</td>
</tr>
<tr>
<td><code>persist</code></td>
<td>Engine-owned Model snapshot config: monotonic <code>version</code>, optional <code>debounce_ms</code>, and the <code>restore</code> Msg routes (<code>ok</code>/<code>none</code>/<code>err</code>). Requires <code>"persist"</code> in capabilities — see <a href="/docs/persistence">Model Persistence</a></td>
</tr>
<tr>
<td><code>images</code></td>
<td>Optional registered-image budget: <code>.images = .&#123; .max_image_pixel_bytes = 8_388_608 &#125;</code>. The default is 1 MiB and accepted values are 18 MiB. Encoded photos decode aspect-preservingly to fit; storage is lazy per used slot, but 16 fully used 8 MiB slots are a declared 128 MiB high-water. See <a href="/docs/dynamic-images">Dynamic Images</a>.</td>
</tr>
<tr>
<td><code>bridge</code></td>
<td>Bridge command policies (see <a href="/bridge">Bridge</a>)</td>
<td>Bridge command policies (see <a href="/docs/bridge">Bridge</a>)</td>
</tr>
<tr>
<td><code>security</code></td>
<td>Navigation and external link policies (see <a href="/security">Security</a>)</td>
<td>Navigation and external link policies (see <a href="/docs/security">Security</a>)</td>
</tr>
<tr>
<td><code>web_engine</code></td>
<td><code>system</code> or <code>chromium</code>; Chromium is currently supported for macOS builds (see <a href="/web-engines">Web Engines</a>)</td>
<td><code>system</code> or <code>chromium</code>; Chromium is currently supported for macOS builds (see <a href="/docs/web-engines">Web Engines</a>)</td>
</tr>
<tr>
<td><code>webview_layer</code></td>
@@ -174,7 +188,7 @@ A fuller manifest for an app that also [embeds web content](/frontend) and decla
</tr>
<tr>
<td><code>theme</code></td>
<td>Built-in theme pack: <code>house</code> (default) or <code>geist</code>; an unknown name is a build/check error (see <a href="/theming">Theming</a>)</td>
<td>Built-in theme pack: <code>house</code> (default) or <code>geist</code>; an unknown name is a build/check error (see <a href="/docs/theming">Theming</a>)</td>
</tr>
<tr>
<td><code>theme_accent</code></td>
@@ -190,7 +204,7 @@ A fuller manifest for an app that also [embeds web content](/frontend) and decla
</tr>
<tr>
<td><code>windows</code></td>
<td>Window definitions (see <a href="/windows">Windows</a>)</td>
<td>Window definitions (see <a href="/docs/windows">Windows</a>)</td>
</tr>
<tr>
<td><code>shell</code></td>
@@ -202,11 +216,11 @@ A fuller manifest for an app that also [embeds web content](/frontend) and decla
</tr>
<tr>
<td><code>shortcuts</code></td>
<td>Keyboard shortcuts delivered as <code>shortcut</code> events (see <a href="/keyboard-shortcuts">Keyboard Shortcuts</a>)</td>
<td>Keyboard shortcuts delivered as <code>shortcut</code> events (see <a href="/docs/keyboard-shortcuts">Keyboard Shortcuts</a>)</td>
</tr>
<tr>
<td><code>menus</code></td>
<td>Native menu declarations delivered through the command event path (see <a href="/menus">Menus</a>)</td>
<td>Native menu declarations delivered through the command event path (see <a href="/docs/menus">Menus</a>)</td>
</tr>
<tr>
<td><code>file_associations</code></td>
@@ -218,7 +232,7 @@ A fuller manifest for an app that also [embeds web content](/frontend) and decla
</tr>
<tr>
<td><code>frontend</code></td>
<td>Frontend build/dev config (see <a href="/frontend">Frontend Projects</a>)</td>
<td>Frontend build/dev config (see <a href="/docs/frontend">Frontend Projects</a>)</td>
</tr>
</tbody>
</table>
@@ -269,15 +283,15 @@ For a scene-first app — a `UiApp` passing its Zig scene (`shell_scene`) to the
},
```
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 <a href="/native-ui">Native UI</a>). Platforms without the concept keep standard chrome. The same `titlebar` field is accepted on top-level `windows` entries.
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`), `initially_hidden` (default false; create the native window ordered out until an explicit show/focus), `allows_fullscreen` (default true; false disables native fullscreen on macOS without disabling ordinary resizing), `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`). `restore_state` defaults to true and controls only whether the state store is consulted: a store hit restores the saved frame, an authored `x` or `y` is explicit placement, and a fresh window with neither is default placement. On macOS the default `clamp_to_visible_screen` policy keeps restored and explicit frames on their matching or nearest display, centers the main fresh window, and cascades fresh secondary windows; `center_on_primary` centers restored and fresh default frames on the primary display. `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 <a href="/docs/native-ui">Native UI</a>). Platforms without the concept keep standard chrome. The same fields are accepted on top-level `windows` entries. `dock_visible` is top-level app policy, not a window field: setting it false removes the Dock/app-switcher presence but does not hide a window; pair it with `initially_hidden = true` when the app should launch behind its status item.
Overlay presentation is also fixed at create time: `transparent` makes the top-level window alpha-capable, `always_on_top` selects its floating/topmost level, `click_through` passes pointer input to windows underneath, and `activate_on_show = false` reveals it without activating the app or taking focus. Canvas windows already use present-before-show, so these flags are applied while the window is hidden and its first alpha-correct frame becomes the first visible frame. Pair `transparent = true` with a non-opaque `gpu_alpha_mode`; `UiApp.WindowDescriptor` makes that canvas-alpha choice and uses an alpha-zero clear automatically. See the <a href="/windows#overlay-windows">overlay window recipe</a>. These fields are accepted on top-level `windows`, `shell.windows`, runtime `WindowCreateOptions`, and `UiApp.WindowDescriptor`.
Overlay presentation is also fixed at create time: `transparent` makes the top-level window alpha-capable, `always_on_top` selects its floating/topmost level, `click_through` passes pointer input to windows underneath, `activate_on_show = false` reveals it without activating the app or taking focus, and `allows_fullscreen = false` removes macOS fullscreen participation while keeping the window resizable. Canvas windows already use present-before-show, so these flags are applied while the window is hidden and its first alpha-correct frame becomes the first visible frame. `initially_hidden = true` is stronger: it suppresses that reveal until `Cmd.showWindow` or explicit focus. Pair `transparent = true` with a non-opaque `gpu_alpha_mode`; `UiApp.WindowDescriptor` makes that canvas-alpha choice and uses an alpha-zero clear automatically. See the <a href="/docs/windows#overlay-windows">overlay window recipe</a>. These fields are accepted on top-level `windows` and `shell.windows`. Runtime `WindowCreateOptions` exposes the same controls but spells the hidden mode `.show = .hidden`; `UiApp.WindowDescriptor` exposes the overlay controls but not the initially-hidden mode.
Windows also take `close_policy` (`quit`, the default — the close affordance really closes, behavior unchanged for every existing app — or `hide`, the menu-bar-app shape: close hides the window and the app keeps running behind its status item). Like `titlebar`, close handling is host window state fixed at create, and the first shell window's declaration threads through the startup create. `hide` is supported on macOS and Windows — on Windows it additionally requires the `"tray"` capability (the tray icon is the only re-show affordance there; a declaration without it is refused at build time with a teaching). Linux has no status item to bring a hidden window back, so the declaration is refused at build time with a teaching. See <a href="/windows#close-policy">Windows</a> and the <a href="/tray#the-menu-bar-app-lifecycle">tray lifecycle recipe</a>. The same `close_policy` field is accepted on top-level `windows` entries.
Windows also take `close_policy` (`quit`, the default — the close affordance really closes, behavior unchanged for every existing app — or `hide`, the menu-bar-app shape: close hides the window and the app keeps running behind its status item). Like `titlebar`, close handling is host window state fixed at create, and the first shell window's declaration threads through the startup create. `hide` is supported on macOS and Windows — on Windows it additionally requires the `"tray"` capability (the tray icon is the only re-show affordance there; a declaration without it is refused at build time with a teaching). Linux has no status item to bring a hidden window back, so the declaration is refused at build time with a teaching. See <a href="/docs/windows#close-policy">Windows</a> and the <a href="/docs/tray#the-menu-bar-app-lifecycle">tray lifecycle recipe</a>. The same `close_policy` 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`.
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 macOS system-WebView host presents with Metal, while the Windows host presents representable binary canvas packets with Direct2D/DirectWrite; frame events expose the concrete `metal` or `direct2d` backend. `gpu_backend` accepts `metal` or `software` as portable requests; omit it to select the default. Linux falls back to software presentation, and Windows uses software for unrepresentable commands, transparent layered windows, or when Direct2D is unavailable. 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.
@@ -297,11 +311,11 @@ The optional `commands` list declares shared command metadata. The runtime still
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.
Generated zero-config TypeScript and Zig-core runners load manifest commands into `RuntimeOptions.commands`; ejected runners use the same fallback. 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:
The optional `shortcuts` list defines app-level keyboard shortcuts. Generated zero-config TypeScript and Zig-core runners load these automatically, as do ejected runners:
```zig
.shortcuts = .{
@@ -320,7 +334,7 @@ Chromium builds are currently macOS-only; use the Linux system WebView backend w
## `menus`
The optional `menus` list defines native app menus. Generated runners load these automatically:
The optional `menus` list defines native app menus. Generated zero-config TypeScript and Zig-core runners load these automatically, as do ejected runners:
```zig
.menus = .{
@@ -36,7 +36,7 @@ When the runtime publishes a snapshot, it writes these files to the automation d
<tbody>
<tr>
<td><code>snapshot.txt</code></td>
<td>Runtime state: source kind, window metadata, native/WebView metadata including role, accessibility label, text, and focus state, <code>ready=true/false</code>, and <code>markup_watch=armed|off</code> in the header — whether the markup hot-reload watch is armed (only in builds where the app wired <code>.markup</code> with a <code>watch_path</code> and <code>io</code>, or registered compiled fragments through <code>fragment_watch</code> — i.e. Debug dev builds)</td>
<td>Runtime state: source kind, window metadata, native/WebView metadata including role, accessibility label, text, and focus state, the configured command and app-menu catalogs, <code>ready=true/false</code>, and <code>markup_watch=armed|off</code> in the header — whether the markup hot-reload watch is armed (only in builds where the app wired <code>.markup</code> with a <code>watch_path</code> and <code>io</code>, or registered compiled fragments through <code>fragment_watch</code> — i.e. Debug dev builds)</td>
</tr>
<tr>
<td><code>accessibility.txt</code></td>
@@ -146,8 +146,8 @@ The runtime watches the command queue and processes these actions:
<td>Dispatch a shortcut command event for the main window</td>
</tr>
<tr>
<td><code>tray-action &lt;item-id&gt;</code></td>
<td>Select a status-item dropdown row (ids from the snapshot's <code>tray-item #id</code> lines)</td>
<td><code>tray-action &lt;item-id&gt;</code> or <code>tray-action &lt;status-item-id&gt; &lt;item-id&gt;</code></td>
<td>Select a status-item dropdown row. The one-id form targets primary status item <code>#1</code>; multiple-item snapshots print <code>tray #id</code> headers</td>
</tr>
<tr>
<td><code>focus &lt;view-label&gt;</code></td>
@@ -261,6 +261,12 @@ Edits refuse rather than guess: a widget authored in Zig, a file that changed on
`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.
## Session recording and blob growth
`NATIVE_SDK_SESSION_RECORD=/path/session.journal` records platform events and effect results; `NATIVE_SDK_SESSION_REPLAY` replays that journal without touching the original network, process, database, or file. Large effect payloads—including every streamed file-read chunk—live content-addressed under the sibling `blobs/` directory, and the journal stores each hash and length. Keep the journal and `blobs/` together when copying a recording.
The blob store deduplicates identical chunks but is intentionally unbounded today: there is no automatic size quota or garbage collector. Long recordings and repeated large imports can therefore grow the directory substantially. Treat a recording as one disposable artifact, monitor its directory size in long automation runs, and delete the journal plus its sibling `blobs/` directory when it is no longer needed. A future GC must reason over every retained journal before removing an unreferenced content address; Tier 5 does not guess at that retention policy.
## Custom directory
Pass a custom path to `automation.Server.init()`:
@@ -49,7 +49,7 @@ Native controls can also bind a `command` when created with `runtime.createView(
</tbody>
</table>
Platform support queries are available through `window.zero.platform.supports(...)` when `js_window_api` is `true`. The command accepts every feature name from `PlatformFeature`: `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`, `gpu_surfaces`, `gpu_surface_scroll_drivers`, `context_menus`, `view_surface_adoption`, `audio_playback`, `audio_streaming`, `audio_spectrum`, and `window_hide_on_close`. JavaScript callers can also use the camelCase aliases — `mainWebView`, `childWebViews`, `nativeViews`, `nativeControlCommands`, `clipboardText`, `clipboardRichData`, `openUrl`, `revealPath`, `recentDocuments`, `fileDrops`, `appActivationEvents`, `gpuSurfaces`, `gpuSurfaceScrollDrivers`, `contextMenus`, `viewSurfaceAdoption`, `audioPlayback`, `audioStreaming`, `audioSpectrum`, and `windowHideOnClose`. 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.
Platform support queries are available through `window.zero.platform.supports(...)` when `js_window_api` is `true`. The command accepts every feature name from `PlatformFeature`: `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`, `gpu_surfaces`, `gpu_surface_scroll_drivers`, `context_menus`, `view_surface_adoption`, `audio_playback`, `audio_streaming`, `audio_spectrum`, `microphone_capture`, `system_audio_capture`, and `window_hide_on_close`. JavaScript callers can also use the camelCase aliases — `mainWebView`, `childWebViews`, `nativeViews`, `nativeControlCommands`, `clipboardText`, `clipboardRichData`, `openUrl`, `revealPath`, `recentDocuments`, `fileDrops`, `appActivationEvents`, `gpuSurfaces`, `gpuSurfaceScrollDrivers`, `contextMenus`, `viewSurfaceAdoption`, `audioPlayback`, `audioStreaming`, `audioSpectrum`, `microphoneCapture`, `systemAudioCapture`, and `windowHideOnClose`. 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
@@ -246,7 +246,7 @@ Dialog commands are **always default-deny** and require an explicit `builtin_bri
<tr>
<td><code>native-sdk.os.showNotification</code></td>
<td><code>notifications</code></td>
<td>Show a native system notification with a title, optional subtitle, and optional body</td>
<td>Show or replace a native notification; optional paired <code>actionLabel</code> / <code>actionCommand</code> fields dispatch an app command</td>
</tr>
<tr>
<td><code>native-sdk.os.revealPath</code></td>
@@ -266,7 +266,7 @@ Dialog commands are **always default-deny** and require an explicit `builtin_bri
</tbody>
</table>
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.
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. An actionable notification additionally requires an allowed `native-sdk.command.invoke` policy and the `command` permission for the same origin, because activating it enters the normal app-command path. 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
@@ -466,9 +466,12 @@ const result = await window.zero.invoke("native-sdk.dialog.showMessage", {
await window.zero.os.openUrl("https://example.com/docs/start");
await window.zero.os.showNotification({
id: "build-status",
title: "Build finished",
subtitle: "native-sdk",
body: "All checks passed.",
actionLabel: "Open results",
actionCommand: "build.open-results",
});
await window.zero.os.revealPath("/Users/me/Downloads/report.pdf");
await window.zero.os.addRecentDocument("/Users/me/Downloads/report.pdf");
@@ -497,4 +500,4 @@ await window.zero.credentials.delete({
});
```
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.
See also: [Multiple WebViews](/docs/webviews) for frame and layer semantics, [Dialogs](/docs/dialogs) for the full dialog type reference, [Capabilities](/docs/capabilities) for OS capability support, and [Security](/docs/security) for policy details.
@@ -1,6 +1,6 @@
# 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)).
For apps that [embed web content](/docs/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](/docs/app-model)).
## Architecture
@@ -175,4 +175,4 @@ try {
</tbody>
</table>
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.*`.
See also: [Builtin Commands](/docs/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.*`.
@@ -1,6 +1,6 @@
# 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 library's built-ins cover the common register, and [theming](/docs/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](/docs/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.
@@ -89,11 +89,11 @@ Args carry values; a `<slot/>` carries markup. A template body may mark one inse
</column>
```
This is the container-component pattern: the template owns the frame, the caller owns the content. The full rules (one slot per body, children without a slot are an error, ids hash as if inlined) are in [Native UI § Components](/native-ui#components).
This is the container-component pattern: the template owns the frame, the caller owns the content. The full rules (one slot per body, children without a slot are an error, ids hash as if inlined) are in [Native UI § Components](/docs/native-ui#components).
## When a component needs Zig
The markup grammar is deliberately closed, and a few shapes sit outside it — the honest list is in [Native UI § Elements](/native-ui#elements): components that carry **image ids** (pixels registered at runtime; the avatar's `image="{binding}"` is the one declarative exception), **per-cell templates** (a data grid's arbitrary render-per-column callbacks), and **Zig-side floating surfaces** (`popover`, `menu_surface`; the anchored `dropdown-menu` covers the declarative case). Beyond those, anything needing per-state styling past tokens (`ElementOptions.style`) or logic past the expression language belongs in Zig.
The markup grammar is deliberately closed, and a few shapes sit outside it — the honest list is in [Native UI § Elements](/docs/native-ui#elements): components that carry **image ids** (pixels registered at runtime; the avatar's `image="{binding}"` is the one declarative exception), **per-cell templates** (a data grid's arbitrary render-per-column callbacks), and **Zig-side floating surfaces** (`popover`, `menu_surface`; the anchored `dropdown-menu` covers the declarative case). Beyond those, anything needing per-state styling past tokens (`ElementOptions.style`) or logic past the expression language belongs in Zig.
A Zig component is just a function that takes the view builder and returns a node — the same primitives markup lowers to, with the same structural identity rules:
@@ -159,7 +159,7 @@ Zig components distribute the ordinary Zig way: a file per component (or a `comp
Three moves, in order of preference:
**Theme it.** Every built-in reads the token register, and [theme packs, overrides, and full design systems](/theming) reach every visual decision — colors, radii, control metrics, state washes, type. If your need is "the stepper, but in our palette," that is a token change, not a component.
**Theme it.** Every built-in reads the token register, and [theme packs, overrides, and full design systems](/docs/theming) reach every visual decision — colors, radii, control metrics, state washes, type. If your need is "the stepper, but in our palette," that is a token change, not a component.
**Eject it.** When you need to own a composite's *shape* — reorder its parts, change its structure, grow it a feature — `native eject component <name>` writes the library composite's canonical source into `src/components/`, and from then on it is your code: edit freely, SDK updates never touch it. The ejectable set is exactly the library views that are honest compositions of primitives — today `stepper` and `timeline-item` (Zig view functions; their conditional structure and formatted text sit outside the markup grammar) and `timeline` (a markup template, since the container is pure composition). Each ejected file builds a widget tree identical to its library form at the moment of ejection — held by tests in the SDK — so ejecting changes ownership, never pixels. Ejected templates are reached through `<use template="timeline" ...>`, never a new element name, so the built-in element keeps working at unmigrated call sites; ejecting twice errors instead of overwriting your edits. Engine controls (buttons, text fields, tabs, ...) are deliberately not on the menu: their behavior lives in the runtime, and the way to change them is the token system, not a fork.
@@ -0,0 +1,7 @@
import { pageMetadata } from "@/lib/page-metadata";
export const metadata = pageMetadata("built-in-components");
export default function Layout({ children }: { children: React.ReactNode }) {
return children;
}
@@ -1,7 +1,7 @@
# Built-in Components
This page moved. The catalog now has a full [Components section](/components) — one page per component with engine-rendered light/dark previews, validated Native markup, the Zig builder equivalent, and attribute tables generated from the markup vocabulary.
This page moved. The catalog now has a full [Components section](/docs/components) — one page per component with engine-rendered light/dark previews, validated Native markup, the Zig builder equivalent, and attribute tables generated from the markup vocabulary.
The short version: the Native SDK ships a native-rendered component catalog with house-style defaults — neutral surfaces, Geist typography, subtle borders, focus states, and token-driven color, radius, shadow, blur, and motion — owned by the SDK and rendered through the retained canvas surface, not platform widget skins. Every component is expressible in Native markup through its element and programmatically through the `canvas.Ui` builder.
The canonical catalog lives in `native_sdk.canvas.builtin_component_kinds` and `builtin_component_names`; each descriptor reports its style, root widget kind, semantic role, and composite flag, and `native_sdk.canvas.builtinComponentWidget(...)` constructs a component's default foundation directly. See [Native UI](/native-ui) for markup semantics and the runtime contract.
The canonical catalog lives in `native_sdk.canvas.builtin_component_kinds` and `builtin_component_names`; each descriptor reports its style, root widget kind, semantic role, and composite flag, and `native_sdk.canvas.builtinComponentWidget(...)` constructs a component's default foundation directly. See [Native UI](/docs/native-ui) for markup semantics and the runtime contract.
@@ -0,0 +1,7 @@
import { pageMetadata } from "@/lib/page-metadata";
export const metadata = pageMetadata("capabilities");
export default function Layout({ children }: { children: React.ReactNode }) {
return children;
}
@@ -1,8 +1,8 @@
# Capabilities
Native SDK capabilities are native OS services and app events exposed through `PlatformServices`, runtime methods, lifecycle events, and — for apps that [embed web content](/frontend) — guarded bridge commands. Native code reaches them directly; web content does not receive capability access by default. In a [`UiApp`](/app-model), clipboard access rides the effects channel (`fx.writeClipboard` / `fx.readClipboard`) so `update` never needs a runtime handle.
Native SDK capabilities are native OS services and app events exposed through `PlatformServices`, runtime methods, lifecycle events, and — for apps that [embed web content](/docs/frontend) — guarded bridge commands. Native code reaches them directly; web content does not receive capability access by default. In a [model core](/docs/app-model), OS work rides the effects channel (`Cmd.*` in TypeScript and `fx.*` in Zig) so `update` never needs a runtime handle.
Web content itself is declare-to-use: an app ships the embedded web layer only when it declares web intent — `"webview"` in `.capabilities`, a `.frontend` block, a `.shell` webview view, or a web engine resolved to Chromium (`.web_engine = "chromium"` in app.zon, or the `-Dweb-engine`/`--web-engine` flags) — and an app that declares none of them builds native-only, where any attempt to create a webview fails with a teaching error instead of loading a layer the app never asked for. The [`webview_layer`](/app-zon) manifest field overrides the inference in either direction. Native-only builds shed the platform web stack for real: the Windows executable carries no `WebView2Loader.dll` reference, and the Linux host neither links WebKitGTK nor requires `libwebkitgtk` on user machines.
Web content itself is declare-to-use: an app ships the embedded web layer only when it declares web intent — `"webview"` in `.capabilities`, a `.frontend` block, a `.shell` webview view, or a web engine resolved to Chromium (`.web_engine = "chromium"` in app.zon, or the `-Dweb-engine`/`--web-engine` flags) — and an app that declares none of them builds native-only, where any attempt to create a webview fails with a teaching error instead of loading a layer the app never asked for. The [`webview_layer`](/docs/app-zon) manifest field overrides the inference in either direction. Native-only builds shed the platform web stack for real: the Windows executable carries no `WebView2Loader.dll` reference, and the Linux host neither links WebKitGTK nor requires `libwebkitgtk` on user machines.
## Current capability pack
@@ -10,7 +10,7 @@ Web content itself is declare-to-use: an app ships the embedded web layer only w
<thead>
<tr>
<th>Capability</th>
<th>Zig API</th>
<th>Native API</th>
<th>JavaScript command</th>
<th>Bridge permission</th>
<th>Current native support</th>
@@ -19,17 +19,38 @@ Web content itself is declare-to-use: an app ships the embedded web layer only w
<tbody>
<tr>
<td>Open URL in system browser</td>
<td><code>runtime.openExternalUrl(url)</code></td>
<td><code>Cmd.openExternalUrl(url)</code> / <code>runtime.openExternalUrl(url)</code></td>
<td><code>native-sdk.os.openUrl</code></td>
<td><code>network</code></td>
<td>macOS, Linux, and Windows system WebView; macOS Chromium</td>
<td>macOS, Linux, and Windows model cores and system WebView; macOS Chromium</td>
</tr>
<tr>
<td>System notification</td>
<td><code>runtime.showNotification(options)</code></td>
<td><code>Cmd.showNotification(spec)</code> / <code>fx.showNotification(options)</code> / <code>runtime.showNotification(options)</code></td>
<td><code>native-sdk.os.showNotification</code></td>
<td><code>notifications</code></td>
<td>macOS, Linux, and Windows system WebView; macOS Chromium</td>
<td>macOS, Linux, and Windows model cores and system WebView; macOS Chromium</td>
</tr>
<tr>
<td>Model persistence</td>
<td><code>Cmd.persist()</code> / <code>fx.persist()</code></td>
<td>None. Model-core effect only.</td>
<td>None. Gated by the <code>persist</code> build capability.</td>
<td>Generated TypeScript app runners on every app-data platform; Zig-core hosts receive the same named <code>core.persist</code> effect seam</td>
</tr>
<tr>
<td>Record store</td>
<td><code>Cmd.store.set/get/delete/scan/setMany</code> / <code>fx.storeSet/storeGet/storeDelete/storeScan/storeSetMany</code></td>
<td>None. Model-core effect only.</td>
<td>None. Gated by the <code>store</code> build capability.</td>
<td>SQLite-backed engine store in the per-app data directory; replay remains offline and the core devhost uses a process-local map</td>
</tr>
<tr>
<td>Relational SQLite</td>
<td><code>Cmd.q&lt;Name&gt;</code> / <code>Cmd.qTx</code> / <code>Sub.q&lt;Name&gt;</code> / raw <code>Cmd.db.query/exec</code> / <code>fx.dbQuery/dbExec/dbSubscribe</code></td>
<td>None. Model-core effect only.</td>
<td>None. Gated by the <code>sqlite</code> build capability.</td>
<td>Checked migrations and named SQL over engine-owned <code>app.db</code>; real in-memory SQLite in check, devhost, and tests; journal-only replay</td>
</tr>
<tr>
<td>Native dialogs</td>
@@ -40,10 +61,10 @@ Web content itself is declare-to-use: an app ships the embedded web layer only w
</tr>
<tr>
<td>Reveal path in file manager</td>
<td><code>runtime.revealPath(path)</code></td>
<td><code>Cmd.revealPath(path)</code> / <code>runtime.revealPath(path)</code></td>
<td><code>native-sdk.os.revealPath</code></td>
<td><code>filesystem</code></td>
<td>macOS, Linux, and Windows system WebView; macOS Chromium</td>
<td>macOS, Linux, and Windows model cores and system WebView; macOS Chromium</td>
</tr>
<tr>
<td>Recent documents</td>
@@ -82,10 +103,17 @@ Web content itself is declare-to-use: an app ships the embedded web layer only w
</tr>
<tr>
<td>Credential store</td>
<td><code>runtime.setCredential(options)</code> / <code>runtime.getCredential(key)</code> / <code>runtime.deleteCredential(key)</code></td>
<td><code>Cmd.credentials.set/get/delete</code> / <code>fx.credentialsSet/Get/Delete</code> / runtime equivalents</td>
<td><code>native-sdk.credentials.set</code> / <code>native-sdk.credentials.get</code> / <code>native-sdk.credentials.delete</code></td>
<td><code>credentials</code></td>
<td>macOS system WebView and macOS Chromium through Keychain; Linux system WebView through Secret Service/libsecret when available; Windows system WebView through Credential Manager</td>
<td><code>credentials</code>. Model cores require both the build capability and permission; WebView commands require the bridge permission.</td>
<td>macOS model cores, system WebView, and Chromium through Keychain; Linux model cores and system WebView through Secret Service/libsecret when available; Windows model cores and system WebView through Credential Manager; iOS toolkit apps through generic-password Keychain entries; Android toolkit apps through an AndroidKeyStore AES-GCM key and authenticated ciphertext in app-private preferences. Core dev and test hosts use hermetic memory.</td>
</tr>
<tr>
<td>Local date/time formatting</td>
<td><code>Cmd.formatLocalTime(timestampMs, style, route)</code> / <code>runtime.formatLocalTime(...)</code></td>
<td>None. Effects/runtime API.</td>
<td>None. No bridge surface.</td>
<td>macOS, Linux, and Windows model cores; locale- and time-zone-aware, with results captured by session recording</td>
</tr>
<tr>
<td>App activation events</td>
@@ -132,6 +160,8 @@ const support = {
audioPlayback: await window.zero.platform.supports("audio_playback"),
audioStreaming: await window.zero.platform.supports("audio_streaming"),
audioSpectrum: await window.zero.platform.supports("audio_spectrum"),
microphoneCapture: await window.zero.platform.supports("microphone_capture"),
systemAudioCapture: await window.zero.platform.supports("system_audio_capture"),
};
```
@@ -143,6 +173,7 @@ OS bridge commands are default-deny. Add explicit `builtin_bridge` entries for e
.builtin_bridge = .{
.enabled = true,
.commands = &.{
.{ .name = "native-sdk.command.invoke", .permissions = .{ "command" }, .origins = .{ "zero://app" } },
.{ .name = "native-sdk.platform.supports", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "native-sdk.os.openUrl", .permissions = .{ "network" }, .origins = .{ "zero://app" } },
.{ .name = "native-sdk.os.showNotification", .permissions = .{ "notifications" }, .origins = .{ "zero://app" } },
@@ -163,6 +194,8 @@ OS bridge commands are default-deny. Add explicit `builtin_bridge` entries for e
},
```
Because the JavaScript example below supplies a notification action, the app/runtime permission list must also include `command`. Display-only notifications need only the `native-sdk.os.showNotification` policy and `notifications` permission.
Opening external URLs is also gated by the external-link policy:
```zig
@@ -181,9 +214,12 @@ Opening external URLs is also gated by the external-link policy:
```javascript
await window.zero.os.openUrl("https://example.com/docs/start");
await window.zero.os.showNotification({
id: "build-status",
title: "Build finished",
subtitle: "native-sdk",
body: "All checks passed.",
actionLabel: "Open results",
actionCommand: "build.open-results",
});
await window.zero.os.revealPath("/Users/me/Downloads/report.pdf");
await window.zero.os.addRecentDocument("/Users/me/Downloads/report.pdf");
@@ -220,4 +256,88 @@ window.zero.on("app:activate", () => {
});
```
See also: [Builtin Commands](/bridge/builtin-commands) and [Security](/security).
Use the `app.zon` app id as `service` when WebView code and a model core need to address the same entry. Core credential effects apply that namespace automatically; their key corresponds to the bridge `account` field.
## Model cores
Model-driven apps request notifications as effects. Delivery is fire-and-forget because OS focus modes and user settings remain authoritative after the host accepts the request; fake-executor tests and session replay do not display notifications. An optional nonempty `id` replaces the app's earlier notification with the same id. Supply `actionLabel` and `actionCommand` together to route activation through the same application command path as menus, shortcuts, and bridge command invocation. Action-command delivery is process-scoped: a notification from an earlier execution cannot dispatch its command into a later one. Linux notification shells may still D-Bus-activate an exited app when the notification or its action is clicked, but the stale action is rejected rather than entering the new runtime.
### TypeScript
```ts
case "build_finished":
return [model, Cmd.showNotification({
id: asciiBytes("build-status"),
title: asciiBytes("Build finished"),
subtitle: asciiBytes("native-sdk"),
body: asciiBytes("All checks passed."),
actionLabel: asciiBytes("Open results"),
actionCommand: asciiBytes("build.open-results"),
})];
```
### Zig
```zig
.build_finished => fx.showNotification(.{
.id = "build-status",
.title = "Build finished",
.subtitle = "native-sdk",
.body = "All checks passed.",
.action_label = "Open results",
.action_command = "build.open-results",
}),
```
Apps using the lower-level runtime can call the same platform seam directly with `try runtime.showNotification(options)`.
### Credentials
Credentials are app-scoped effects, not Model data. Declare both gates; `native check` reports NS1071 for a missing capability and NS1072 for a missing permission when a TypeScript core uses `Cmd.credentials.*`. NS1073 reserves the underlying `core.credentials.*` request names for these typed factories:
```zig:app.zon
.capabilities = .{ "credentials" },
.permissions = .{ "credentials" },
```
The manifest app id is the OS keychain service namespace and is bounded at 128 bytes, so authored code supplies only a key. Keys are NUL-free UTF-8 through 256 bytes and secrets are bounded at 2,560 bytes, the largest binary value every first-party OS backend can store whole. A get miss routes the error arm with `miss`; the other closed outcomes are `denied`, `locked`, `io_failed`, `over_bound`, and `rejected`. Set and delete route empty bytes on success, and delete is idempotent.
```ts
case "save_token":
return [model, Cmd.credentials.set("api-token", msg.token, {
key: "save-token",
ok: "token_saved",
err: "credential_failed",
})];
case "load_token":
return [model, Cmd.credentials.get("api-token", {
key: "load-token",
ok: "token_loaded",
err: "credential_failed",
})];
case "token_loaded":
// Consume msg.token immediately to construct the next effect. Do not
// copy it into Model, where persistence and state fingerprints can see it.
return [model, Cmd.fetch({
url: asciiBytes("https://api.example.com/me"),
headers: { authorization: msg.token },
}, { ok: "profile_loaded", err: "profile_failed" })];
```
```zig
.save_token => |token| fx.credentialsSet(.{
.key = 41,
.credential_key = "api-token",
.secret = token,
.on_result = Effects.credentialsMsg(.credential_result),
}),
.load_token => fx.credentialsGet(.{
.key = 42,
.credential_key = "api-token",
.on_result = Effects.credentialsMsg(.credential_result),
}),
```
Credential results still cross the effect boundary, but recording never writes secret bytes to the journal or blob store. A successful recorded get keeps only its length, a per-session salt, and a placeholder digest that is deliberately independent of the secret (so the artifact is not a password-guessing oracle). Replay supplies deterministic placeholder bytes of the same length and never opens the live keychain. Consequently, replay is suitable for control-flow verification but cannot perform a new authenticated exchange with the original token. The core devhost also stores credentials only in process memory and prints `<redacted, N bytes>`.
See also: [Builtin Commands](/docs/bridge/builtin-commands) and [Security](/docs/security).
@@ -29,11 +29,11 @@ native dev [dir] --core [--script msgs.ndjson] [--watch]
native dev --binary <path> [--manifest app.zon] [--url <url>] [--command "<cmd>"] [--timeout-ms <n>]
```
Build and run the app in the current (or given) app directory — a Debug build by default, printing a one-line completion and naming any failing step. The markup hot-reload watcher and the Debug-only teaching diagnostics are compiled in only in Debug; pass `-Doptimize=...` to override. Apps with a frontend dev config also get the managed dev server — see [Dev Server](/cli/dev).
Build and run the app in the current (or given) app directory — a Debug build by default, printing a one-line completion and naming any failing step. The markup hot-reload watcher and the Debug-only teaching diagnostics are compiled in only in Debug; pass `-Doptimize=...` to override. Apps with a frontend dev config also get the managed dev server — see [Dev Server](/docs/cli/dev).
<dl>
<dt><code>--core</code></dt>
<dd>Run the TypeScript core's logic loop under node instead of building the app: dispatch Msgs as JSON lines on stdin, watch the committed model and effect transcript, advance a virtual clock to fire timers. Honestly not a renderer — plain <code>native dev</code> runs the real app. See <a href="/typescript#the-dev-loop">TypeScript Cores</a>.</dd>
<dd>Run the TypeScript core's logic loop under node instead of building the app: dispatch Msgs as JSON lines on stdin, watch the committed model and effect transcript, advance a virtual clock to fire timers. Honestly not a renderer — plain <code>native dev</code> runs the real app. See <a href="/docs/typescript#the-dev-loop">TypeScript Cores</a>.</dd>
<dt><code>--script</code></dt>
<dd>With <code>--core</code>: replay a newline-delimited JSON message file instead of reading stdin.</dd>
<dt><code>--watch</code></dt>
@@ -72,7 +72,7 @@ Run the app's test suite, printing the zig build summary (step/test tally) plus
native check [dir] [--strict]
```
Validate the whole tree without building the app. A TypeScript core (`src/core.ts`) runs the subset checker first — real tsc semantics plus the app-core rules, diagnostics verbatim — then every `src/**.native` markup file and `app.zon` are checked as before. With a fresh model contract (`zig-out/model-contract.zon`, refreshed by `native test`) it also checks bindings, iterables, and message tags against your `Model`/`Msg` — for a TypeScript core, against its emitted model — and warns on model state no view uses. Without the artifact it degrades to structural checking and says so loudly: "model contract: not yet built - bindings checked structurally only; run `native test` to enable typed checks". Markup accessibility findings are reported per file in full, and a failing `src/*.native` file that no Zig source embeds gets a leftover-file hint.
Validate the whole tree without building the app. A TypeScript core (`src/core.ts`) runs the subset checker first — real tsc semantics plus the app-core rules, diagnostics verbatim — then every `src/**.native` markup file and `app.zon` are checked as before. With a fresh model contract (`zig-out/model-contract.zon`, refreshed by `native test`) it also checks bindings, iterables, and message tags against your `Model`/`Msg` — for a TypeScript core, against its model contract — and warns on model state no view uses. Without the artifact it degrades to structural checking and says so: "model contract: not yet built - bindings checked structurally only; run `native test` to enable typed checks". Markup accessibility findings are reported per file in full, and a failing `src/*.native` file that no Zig source embeds gets a leftover-file hint.
<dl>
<dt><code>--strict</code></dt>
@@ -87,7 +87,7 @@ Validate the whole tree without building the app. A TypeScript core (`src/core.t
native eject [dir]
```
Write an owned `build.zig`/`build.zig.zon` into the app (once); the verbs then drive your files via `zig build`. Ejecting is only for owning the build files — it is never a prerequisite: zero-config apps build, test, and [package](/packaging) directly (`native package` works on the zero-config build as-is).
Write an owned `build.zig`/`build.zig.zon` into the app (once); the verbs then drive your files via `zig build`. Ejecting is only for owning the build files — it is never a prerequisite: zero-config apps build, test, and [package](/docs/packaging) directly (`native package` works on the zero-config build as-is).
### `native eject component`
@@ -95,7 +95,7 @@ Write an owned `build.zig`/`build.zig.zon` into the app (once); the verbs then d
native eject component <name> [dir]
```
Write an owned copy of a library composite into `src/components/` (once, never overwriting — ejecting again errors with the file to delete first). Ejectable today: `stepper`, `timeline`, `timeline-item` — the library views that are honest compositions of primitives; engine controls are not on the menu (theme them through [tokens](/theming) instead). `timeline` lands as a markup template (use it via `<use template="timeline" ...>`), the others as Zig view functions; each file opens with a header comment walking through the call-site migration, and each builds a widget tree identical to its library form at the moment of ejection. Unknown names get a did-you-mean plus the full ejectable list. See [Building Components](/building-components#use-eject-or-build).
Write an owned copy of a library composite into `src/components/` (once, never overwriting — ejecting again errors with the file to delete first). Ejectable today: `stepper`, `timeline`, `timeline-item` — the library views that are honest compositions of primitives; engine controls are not on the menu (theme them through [tokens](/docs/theming) instead). `timeline` lands as a markup template (use it via `<use template="timeline" ...>`), the others as Zig view functions; each file opens with a header comment walking through the call-site migration, and each builds a widget tree identical to its library form at the moment of ejection. Unknown names get a did-you-mean plus the full ejectable list. See [Building Components](/docs/building-components#use-eject-or-build).
### `native doctor`
@@ -103,7 +103,7 @@ Write an owned copy of a library composite into `src/components/` (once, never o
native doctor [--strict] [--manifest app.zon] [--web-engine system|chromium] [--cef-dir path] [--cef-auto-install]
```
Check host environment, WebView, manifest, and CEF. See [native doctor](/debugging/doctor) for what each check means.
Check host environment, WebView, manifest, and CEF. See [native doctor](/docs/debugging/doctor) for what each check means.
### `native validate`
@@ -151,19 +151,20 @@ Package the app for distribution. The manifest is picked up at `app.zon` and the
<dt><code>--team-id</code></dt>
<dd>Apple Developer Team ID.</dd>
<dt><code>--archive</code></dt>
<dd>Create a distributable archive.</dd>
<dd>Create a distributable archive. On macOS this is a styled DMG with the app, an Applications alias, a generated or custom background, and the Finder layout declared by <code>app.zon</code>.</dd>
</dl>
### Platform shortcuts
```sh
native package-windows [--output path] [--binary path]
native package-linux [--output path] [--binary path]
native package-windows [--output path] [--binary path] [--service-binary path]
native package-linux [--output path] [--binary path] [--service-binary path]
native package-ios [--output path] [--binary path]
native package-android [--output path] [--binary path]
```
Per-platform shortcuts for `native package --target <platform>`.
The desktop shortcuts use an explicit `--service-binary` when supplied; otherwise, service-bearing projects discover the normal `zig-out/bin/<app>_services[.exe]` build output just like the canonical command.
### `native bundle-assets`
@@ -200,7 +201,7 @@ Work with Native markup files directly, outside the app verbs.
native automate <command>
```
Interact with the automation server of a running automation-enabled app. See [Automation](/automation) for the full workflow.
Interact with the automation server of a running automation-enabled app. See [Automation](/docs/automation) for the full workflow.
<dl>
<dt><code>automate list</code></dt>
@@ -237,8 +238,8 @@ Interact with the automation server of a running automation-enabled app. See [Au
<dd>Dispatch a trackpad pinch gesture at a gpu-surface view (<code>scale</code> is the final multiplicative zoom for the gesture; the anchor point defaults to the view center).</dd>
<dt><code>automate shortcut &lt;id&gt;</code></dt>
<dd>Dispatch a shortcut command event.</dd>
<dt><code>automate tray-action &lt;item-id&gt;</code></dt>
<dd>Select a status-item dropdown row.</dd>
<dt><code>automate tray-action &lt;item-id&gt;</code> / <code>automate tray-action &lt;status-item-id&gt; &lt;item-id&gt;</code></dt>
<dd>Select a status-item dropdown row; the one-id shorthand targets primary status item <code>#1</code>.</dd>
<dt><code>automate focus &lt;view-label&gt;</code></dt>
<dd>Focus a native or WebView-backed view.</dd>
<dt><code>automate focus-next</code> / <code>automate focus-previous</code></dt>
@@ -259,7 +260,7 @@ native skills get <name> [--full]
native skills get --all [--full]
```
List and print the built-in AI agent skills the CLI ships — the version-matched content the `npx skills add vercel-labs/native` discovery skill loads from. See [Agent Skills](/skills) for the one-command agent install, what each skill covers, and how to deliver one to an agent.
List and print the built-in AI agent skills the CLI ships — the version-matched content the `npx skills add vercel-labs/native` discovery skill loads from. See [Agent Skills](/docs/skills) for the one-command agent install, what each skill covers, and how to deliver one to an agent.
<dl>
<dt><code>skills list</code></dt>
@@ -4,7 +4,7 @@ import { CodeToggle } from "@/components/code-toggle";
# Accordion
A disclosure surface with a header. The header label comes from the `text` attribute, children show while `selected` is true, and a header press dispatches `on-toggle` — the model owns the open state, in the usual [modelmessage loop](/native-ui). Items render the house accordion look — borderless rows divided by hairline separators, with the chevron on the trailing edge rotating as the item expands — and size themselves: the header band alone while closed, header plus content while open, so a toggle reflows the column with no hand-managed heights.
A disclosure surface with a header. The header label comes from the `text` attribute, children show while `selected` is true, and a header press dispatches `on-toggle` — the model owns the open state, in the usual [modelmessage loop](/docs/native-ui). Items render the house accordion look — borderless rows divided by hairline separators, with the chevron on the trailing edge rotating as the item expands — and size themselves: the header band alone while closed, header plus content while open, so a toggle reflows the column with no hand-managed heights.
<ComponentPreview name="accordion" alt="Open and closed accordion items rendered by the engine" caption="an open item and a closed one" />
@@ -18,7 +18,7 @@ An inline callout surface. The title comes from the `text` attribute and renders
</column>
```
For a message the user can dismiss, render the alert under an `if` and clear the flag from a button inside it — the model owns the visibility, as with every surface. For modal interruptions reach for [dialog](/components/dialog), [drawer](/components/drawer), or [sheet](/components/sheet) instead.
For a message the user can dismiss, render the alert under an `if` and clear the flag from a button inside it — the model owns the visibility, as with every surface. For modal interruptions reach for [dialog](/docs/components/dialog), [drawer](/docs/components/drawer), or [sheet](/docs/components/sheet) instead.
## Programmatic construction (Zig)

Some files were not shown because too many files have changed in this diff Show More