diff --git a/.gitignore b/.gitignore index 85a837aa1..619844655 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/README.md b/README.md index 938bf6813..2133f8f47 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/build.zig b/build.zig index 220615034..870192d5e 100644 --- a/build.zig +++ b/build.zig @@ -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; @@ -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. @@ -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); @@ -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); } @@ -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), @@ -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), @@ -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 @@ -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 @@ -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, @@ -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"); @@ -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", &.{}), diff --git a/docs/content/docs/app-model.mdx b/docs/content/docs/app-model.mdx index 9df87d176..a8e65383d 100644 --- a/docs/content/docs/app-model.mdx +++ b/docs/content/docs/app-model.mdx @@ -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. diff --git a/docs/content/docs/components/slider.mdx b/docs/content/docs/components/slider.mdx index 5f5a12e2c..100404eb1 100644 --- a/docs/content/docs/components/slider.mdx +++ b/docs/content/docs/components/slider.mdx @@ -21,7 +21,7 @@ For controlled state, store the fraction in `update` and bind it back to `value` ``` -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): diff --git a/docs/content/docs/native-ui.mdx b/docs/content/docs/native-ui.mdx index bb06b668a..072e482da 100644 --- a/docs/content/docs/native-ui.mdx +++ b/docs/content/docs/native-ui.mdx @@ -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/