Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ packages/core/node_modules/
# The CLI-materialized editor copy of @native-sdk/core inside the TS example
# apps (node_modules is editor surface, never source — the same entry the
# TS scaffold writes into a new app's .gitignore)
examples/soundboard-ts/node_modules/
examples/system-monitor-ts/node_modules/
examples/soundboard/node_modules/
examples/system-monitor/node_modules/
examples/habits/node_modules/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ App cores use a checked TypeScript subset and compile to native code. To write a
The Soundboard example rendered by the Native SDK engine. More apps are in [examples/](./examples):

- [Chatbot](./examples/chatbot): streaming responses, text editing, and TypeScript effects.
- [Soundboard](./examples/soundboard-ts): audio playback, search, assets, and context menus.
- [System monitor](./examples/system-monitor-ts): subprocess effects, tables, charts, and timers.
- [Soundboard](./examples/soundboard): audio playback, search, assets, and context menus.
- [System monitor](./examples/system-monitor): subprocess effects, tables, charts, and timers.
- [Calculator](./examples/calculator): markup, keyboard input, and theming.

See the [example catalog](./examples/README.md) for additional apps and their authoring languages.
Expand Down
72 changes: 59 additions & 13 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,26 @@ pub fn build(b: *std.Build) void {
native_driver_step.dependOn(&text_policy_driver_run.step);
ts_core_e2e_step.dependOn(&text_policy_driver_run.step);
test_step.dependOn(&text_policy_driver_run.step);
const habits_reference_run = managedExampleRun(b, host_cli_exe, &.{ "test", "-Dplatform=null" });
habits_reference_run.setCwd(b.path("examples/habits"));
habits_reference_run.has_side_effects = true;
habits_reference_run.setEnvironmentVariable("NATIVE_SDK_TEST_VIEW_BACKEND", "zig");
habits_reference_run.setEnvironmentVariable("NATIVE_SDK_TEST_VIEW_REFERENCE", b.pathFromRoot(".zig-cache/habits-view-reference"));
_ = habits_reference_run.captureStdOut(.{});
_ = habits_reference_run.captureStdErr(.{});
const habits_driver_run = managedExampleRun(b, host_cli_exe, &.{ "test", "-Dplatform=null", "-Dtypescript-view=true" });
habits_driver_run.setCwd(b.path("examples/habits"));
habits_driver_run.has_side_effects = true;
habits_driver_run.setEnvironmentVariable("NATIVE_SDK_TEST_VIEW_BACKEND", "typescript");
habits_driver_run.setEnvironmentVariable("NATIVE_SDK_TEST_VIEW_REFERENCE", b.pathFromRoot(".zig-cache/habits-view-reference"));
_ = habits_driver_run.captureStdOut(.{});
_ = habits_driver_run.captureStdErr(.{});
habits_driver_run.step.dependOn(&habits_reference_run.step);
native_driver_step.dependOn(&habits_driver_run.step);
ts_core_e2e_step.dependOn(&habits_driver_run.step);
test_step.dependOn(&habits_driver_run.step);
b.step("test-habits", "Compare complete Habits snapshots and replay across view backends").dependOn(&habits_driver_run.step);

const code_workbench_reference_run = managedExampleRun(b, host_cli_exe, &.{ "test", "-Dplatform=null" });
code_workbench_reference_run.setCwd(b.path("examples/code-workbench"));
code_workbench_reference_run.has_side_effects = true;
Expand Down Expand Up @@ -1252,8 +1272,14 @@ pub fn build(b: *std.Build) void {
const persist_e2e_run = b.addRunArtifact(ts_core_artifacts.persist);
const markup_e2e_run = b.addRunArtifact(ts_core_artifacts.markup);
const kanban_e2e_run = b.addRunArtifact(ts_core_artifacts.kanban);
const habits_e2e_run = b.addRunArtifact(ts_core_artifacts.habits);
addTestStep(b, "test-ts-habits-e2e", "Compare compiled Habits with the native behavior reference", ts_core_artifacts.habits);
ts_core_e2e_step.dependOn(&habits_e2e_run.step);
test_step.dependOn(&habits_e2e_run.step);
const soundboard_e2e_run = b.addRunArtifact(ts_core_artifacts.soundboard);
const monitor_e2e_run = b.addRunArtifact(ts_core_artifacts.system_monitor);
addTestStep(b, "test-ts-soundboard-e2e", "Run the compiled Soundboard application battery", ts_core_artifacts.soundboard);
addTestStep(b, "test-ts-system-monitor-e2e", "Run the compiled System Monitor application battery", ts_core_artifacts.system_monitor);
const scaffold_ide_e2e_run = b.addRunArtifact(ts_core_artifacts.scaffold_ide);
// The suite scaffolds and typechecks real trees under .zig-cache;
// no build inputs/outputs to hash, so always run it.
Expand All @@ -1269,6 +1295,7 @@ pub fn build(b: *std.Build) void {
ts_core_e2e_step.dependOn(&surface_layout_run.step);
test_step.dependOn(&surface_layout_run.step);
const services_e2e_run = b.addRunArtifact(ts_core_artifacts.services);
addTestStep(b, "test-ts-services-journal", "Verify devhost recordings replay through the packaged service carrier", filteredTestArtifact(b, ts_core_artifacts.services.root_module, "ts-services-journal-tests", &.{"a devhost-recorded service session"}));
const markup_components_e2e_step = b.step("test-ts-markup-components-e2e", "Run root component-file compiled, interpreter, automation, and replay coverage");
markup_components_e2e_step.dependOn(&markup_e2e_run.step);
markup_components_e2e_step.dependOn(&kanban_e2e_run.step);
Expand Down Expand Up @@ -2279,6 +2306,9 @@ pub fn build(b: *std.Build) void {
addTestStep(b, "test-app-runner-window-placement", "Run app-runner window placement decision tests", app_runner_window_placement_tests);
addTestStep(b, "test-canvas", "Run canvas display list tests", canvas_tests);
addTestStep(b, "test-desktop", "Run Native SDK framework tests", desktop_tests);
addTestStep(b, "test-session-replay", "Run complete session codecs, recording, and replay tests", filteredTestArtifact(b, desktop_mod, "session-replay-tests", &.{
"runtime.session_journal.test", "runtime.session_record.test", "runtime.session_tests.test", "runtime.session_replay.test",
}));
for (desktop_test_shard_specs, desktop_test_shards) |spec, shard_tests| {
addTestStep(b, b.fmt("test-desktop-{s}", .{spec.name}), spec.description, shard_tests);
}
Expand Down Expand Up @@ -2437,7 +2467,6 @@ pub fn build(b: *std.Build) void {
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-habits", "Run markup habits example tests", "examples/habits", .managed),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-soundboard", "Run soundboard example tests", "examples/soundboard", .managed),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-video-player", "Run video player example tests", "examples/video-player", .managed),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-soundboard-ts", "Run soundboard-ts example tests", "examples/soundboard-ts", .managed),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-voice-memo", "Run voice memo example tests", "examples/voice-memo", .managed),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-deck", "Run deck example tests", "examples/deck", .managed),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-markdown-viewer", "Run markdown viewer example tests", "examples/markdown-viewer", .managed),
Expand All @@ -2448,7 +2477,6 @@ pub fn build(b: *std.Build) void {
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-system-monitor", "Run system monitor example tests", "examples/system-monitor", .managed),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-terminal", "Run terminal example tests", "examples/terminal", .owned),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-workbench", "Run workbench example tests", "examples/workbench", .owned),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-system-monitor-ts", "Run system-monitor-ts example tests", "examples/system-monitor-ts", .managed),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-effects-probe", "Run effects probe example tests", "examples/effects-probe", .managed),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-channel-monitor", "Run channel monitor example tests", "examples/channel-monitor", .managed),
addExampleTestStep(b, host_cli_exe, native_examples_step, "test-example-menu-bar", "Run menu-bar lifecycle example tests", "examples/menu-bar", .managed),
Expand Down Expand Up @@ -3930,7 +3958,7 @@ fn testArtifact(b: *std.Build, mod: *std.Build.Module) *std.Build.Step.Compile {
}

/// The two transpiled-core end-to-end test binaries: the host/markup
/// fixture suite (tests/ts-core) and the soundboard-ts example suite —
/// fixture suite (tests/ts-core) and the soundboard example suite —
/// the launch-gate port driven as a REAL app (its committed core, its
/// shipping markup). Each runs the @native-sdk/core transpiler (node)
/// over the TS core at build time, pairs the emitted core with its rt
Expand All @@ -3948,6 +3976,7 @@ const TsCoreE2eArtifacts = struct {
/// — every fixture battery links exactly its own core.
markup: *std.Build.Step.Compile,
kanban: *std.Build.Step.Compile,
habits: *std.Build.Step.Compile,
soundboard: *std.Build.Step.Compile,
system_monitor: *std.Build.Step.Compile,
/// The stock-IDE contract: a fresh scaffold (and the committed TS
Expand Down Expand Up @@ -4139,19 +4168,35 @@ fn tsCoreE2eArtifact(
kanban_mod.addImport("native_sdk", desktop_mod);
kanban_mod.addImport("ts_kanban_core", kanban_core_mod);

// The soundboard-ts example's core and markup, tested as one app:
const habits_fixture = externalCoreFixtureModule(b, target, optimize, node, corewire_exe, .{
.entry = "examples/habits/src/core.ts",
.src_dir = b.path("examples/habits/src"),
.name = "habits_core",
.typescript_view = true,
});
const habits_stage = b.addWriteFiles();
const habits_root = habits_stage.addCopyFile(b.path("tests/ts-core/habits_e2e_tests.zig"), "habits_e2e_tests.zig");
_ = habits_stage.addCopyFile(b.path("tests/ts-core/habits_reference.zig"), "habits_reference.zig");
_ = habits_stage.addCopyFile(b.path("tests/ts-core/habits_reference_tests.zig"), "habits_reference_tests.zig");
_ = habits_stage.addCopyFile(b.path("tests/ts-core/habits_reference.native"), "habits_reference.native");
_ = habits_stage.addCopyFile(b.path("examples/habits/src/app.native"), "app.native");
const habits_mod = b.createModule(.{ .root_source_file = habits_root, .target = target, .optimize = optimize });
habits_mod.addImport("native_sdk", desktop_mod);
habits_mod.addImport("ts_habits_core", habits_fixture.module);

// The soundboard example's core and markup, tested as one app:
// the test root stages beside a copy of the example's app.native so
// the compiled markup engine builds the SHIPPING view over the
// core's model.
const soundboard_fixture = externalCoreFixtureModule(b, target, optimize, node, corewire_exe, .{
.entry = "examples/soundboard-ts/src/core.ts",
.src_dir = b.path("examples/soundboard-ts/src"),
.entry = "examples/soundboard/src/core.ts",
.src_dir = b.path("examples/soundboard/src"),
.name = "soundboard_core",
});
const soundboard_core_mod = soundboard_fixture.module;
const soundboard_stage = b.addWriteFiles();
const soundboard_root = soundboard_stage.addCopyFile(b.path("tests/ts-core/soundboard_e2e_tests.zig"), "soundboard_e2e_tests.zig");
_ = soundboard_stage.addCopyFile(b.path("examples/soundboard-ts/src/app.native"), "app.native");
_ = soundboard_stage.addCopyFile(b.path("examples/soundboard/src/app.native"), "app.native");
const soundboard_mod = b.createModule(.{
.root_source_file = soundboard_root,
.target = target,
Expand All @@ -4160,20 +4205,20 @@ fn tsCoreE2eArtifact(
soundboard_mod.addImport("native_sdk", desktop_mod);
soundboard_mod.addImport("ts_soundboard_core", soundboard_core_mod);

// The system-monitor-ts example's core and markup, tested the same
// The system-monitor example's core and markup, tested the same
// way — plus the ORIGINAL Zig example's committed sampler captures,
// staged as fixtures so both ports parse the same recorded truth.
const monitor_fixture = externalCoreFixtureModule(b, target, optimize, node, corewire_exe, .{
.entry = "examples/system-monitor-ts/src/core.ts",
.src_dir = b.path("examples/system-monitor-ts/src"),
.entry = "examples/system-monitor/src/core.ts",
.src_dir = b.path("examples/system-monitor/src"),
.name = "system_monitor_core",
});
const monitor_core_mod = monitor_fixture.module;
const monitor_stage = b.addWriteFiles();
const monitor_root = monitor_stage.addCopyFile(b.path("tests/ts-core/system_monitor_e2e_tests.zig"), "system_monitor_e2e_tests.zig");
_ = monitor_stage.addCopyFile(b.path("examples/system-monitor-ts/src/app.native"), "app.native");
_ = monitor_stage.addCopyFile(b.path("examples/system-monitor-ts/src/windows/settings.native"), "settings.native");
_ = monitor_stage.addCopyFile(b.path("examples/system-monitor-ts/src/windows/components/sampling.native"), "components/sampling.native");
_ = monitor_stage.addCopyFile(b.path("examples/system-monitor/src/app.native"), "app.native");
_ = monitor_stage.addCopyFile(b.path("examples/system-monitor/src/windows/settings.native"), "settings.native");
_ = monitor_stage.addCopyFile(b.path("examples/system-monitor/src/windows/components/sampling.native"), "components/sampling.native");
_ = monitor_stage.addCopyFile(b.path("examples/system-monitor/src/fixtures/sysctl.txt"), "fixtures/sysctl.txt");
_ = monitor_stage.addCopyFile(b.path("examples/system-monitor/src/fixtures/ps.txt"), "fixtures/ps.txt");
_ = monitor_stage.addCopyFile(b.path("examples/system-monitor/src/fixtures/vm_stat.txt"), "fixtures/vm_stat.txt");
Expand Down Expand Up @@ -4488,6 +4533,7 @@ fn tsCoreE2eArtifact(
.persist = filteredTestArtifact(b, persist_mod, "ts-persist-e2e-tests", &.{}),
.markup = filteredTestArtifact(b, markup_e2e_mod, "ts-markup-e2e-tests", &.{}),
.kanban = filteredTestArtifact(b, kanban_mod, "ts-kanban-e2e-tests", &.{}),
.habits = filteredTestArtifact(b, habits_mod, "ts-habits-e2e-tests", &.{}),
.soundboard = filteredTestArtifact(b, soundboard_mod, "ts-soundboard-e2e-tests", &.{}),
.system_monitor = filteredTestArtifact(b, monitor_mod, "ts-system-monitor-e2e-tests", &.{}),
.scaffold_ide = filteredTestArtifact(b, scaffold_ide_mod, "ts-scaffold-ide-e2e-tests", &.{}),
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/app-model.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ pub fn rootView(ui: *Ui, model: *const Model) Ui.Node {
// options: .view = rootView,
```

`examples/calculator` is the smallest live reference (`CompiledKeypadView.build(ui, model)` inside a hand-written root); `system-monitor`, `soundboard`, and `deck` use the same shape. Widget ids, handlers, and dispatch are identical to a pure-markup tree, so tests and automation address the fragment's widgets the usual way.
`examples/calculator` is the smallest live reference (`CompiledKeypadView.build(ui, model)` inside a hand-written root); `deck` uses the same shape. Widget ids, handlers, and dispatch are identical to a pure-markup tree, so tests and automation address the fragment's widgets the usual way.

A Zig-root app keeps dev-time hot reload for its embedded fragments too: build with `UiAppWithFeatures(Model, Msg, .{ .runtime_markup = dev_markup_reload })` and pass `.markup = .{ .source = ..., .watch_path = "src/header.native", .io = init.io }` gated on `builtin.mode == .Debug` (`null` otherwise) — the wiring in `examples/notes`. Debug builds then reload edits to the watched file in place; release builds compile the runtime engine out entirely.

Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/components/slider.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ For controlled state, store the fraction in `update` and bind it back to `value`
</column>
```

With a one-number float arm — `{ kind: "volume_changed"; fraction: number }` in a TypeScript core, `volume_changed: f32` in a Zig core — the arm receives the applied fraction directly (the `examples/soundboard-ts` seek bar is the live pattern):
With a one-number float arm — `{ kind: "volume_changed"; fraction: number }` in a TypeScript core, `volume_changed: f32` in a Zig core — the arm receives the applied fraction directly (the `examples/soundboard` seek bar is the live pattern):

<CodeToggle>

Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/native-ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ For timestamps, the facade owns the clocks (Zig 0.16 puts `std.time` behind `std

## Secondary windows

In a default TypeScript app, export `windows(model): readonly WindowDescriptor[]` and put each possible window's markup at `src/windows/<label>.native`; the generated launcher compiles those files, maps descriptor labels to them, and includes shared components they import from nested paths under `src/windows/`. The main `src/app.native` view can import components anywhere under `src/`. Construct descriptors with `windowDescriptor`. `restorePolicy: "center_on_primary"` centers a fresh descriptor with no `x`/`y` on macOS; Windows and Linux currently keep their native default placement. The default policy is `"clamp_to_visible_screen"`. `closePolicy: "quit"` routes `onCloseCommand` through `commandMsg`; `"hide"` retains the window and dispatches no close command. `titlebar` includes `"chromeless"` for fully skinned windows. `examples/system-monitor-ts` is the complete default-path reference.
In a default TypeScript app, export `windows(model): readonly WindowDescriptor[]` and put each possible window's markup at `src/windows/<label>.native`; the generated launcher compiles those files, maps descriptor labels to them, and includes shared components they import from nested paths under `src/windows/`. The main `src/app.native` view can import components anywhere under `src/`. Construct descriptors with `windowDescriptor`. `restorePolicy: "center_on_primary"` centers a fresh descriptor with no `x`/`y` on macOS; Windows and Linux currently keep their native default placement. The default policy is `"clamp_to_visible_screen"`. `closePolicy: "quit"` routes `onCloseCommand` through `commandMsg`; `"hide"` retains the window and dispatches no close command. `titlebar` includes `"chromeless"` for fully skinned windows. `examples/system-monitor` is the complete default-path reference.

In Zig cores and custom wiring, windows are model-declared exactly like the tray: `Options.windows_fn` returns the window descriptors that should exist right now (presence IS liveness), and `Options.window_view` builds each declared window's whole canvas tree, keyed by the descriptor's window label. After every dispatched Msg the runtime reconciles: windows the model started declaring are created (a source-less native window wearing one `gpu_surface` view with the descriptor's `canvas_label`, inheriting the main canvas's gpu options), windows it stopped declaring close, and every open window's view rebuilds from the same model — a theme picked in the settings window restyles the main window on the same dispatch. There is no `visible` flag: transient visibility is host state changed through `hideWindow`/`showWindow` or a `.hide` close policy; stop declaring a window to really close it and release its retained views.

Expand Down
Loading
Loading