Skip to content
Open
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
92 changes: 86 additions & 6 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,65 @@ test "service archive support matches ScriptC localized object formats" {
try std.testing.expect(app_build.serviceArchiveSupported(windows_host, native_windows_msvc));
}

test "root TypeScript markup discovery classification and resolver budgets" {
const app_build = @import("build/app.zig");
try std.testing.expect(app_build.isRootMarkupSourcePath("components/card.native"));
try std.testing.expect(app_build.isRootMarkupSourcePath("feature/nested/panel.native"));
try std.testing.expect(!app_build.isRootMarkupSourcePath("app.native"));
try std.testing.expect(!app_build.isRootMarkupSourcePath("windows/settings.native"));
try std.testing.expect(!app_build.isRootMarkupSourcePath("components/card.ts"));

// The authored paths are relative to src/, while native check and the
// desktop hot-reload resolver see the leading `src/` too. Pin the exact
// source-relative boundaries that keep those full paths within 24
// segments and 200 bytes.
const max_segments_path = "a/" ** 22 ++ "a";
try std.testing.expect(app_build.markupSourcePathWithinBudget(max_segments_path));
try std.testing.expect(!app_build.markupSourcePathWithinBudget(max_segments_path ++ "/a"));
try std.testing.expect(app_build.markupSourcePathWithinBudget("a" ** 196));
try std.testing.expect(!app_build.markupSourcePathWithinBudget("a" ** 197));
}

test "generated TypeScript runners install the compiled root markup view" {
const desktop = @embedFile("src/app_runner/ts_core_main.zig");
try std.testing.expect(std.mem.indexOf(u8, desktop, "TsUiAppWithFeatures(core, .{ .runtime_markup = dev })") != null);
try std.testing.expect(std.mem.indexOf(u8, desktop, "if (dev) void else @import(\"app_markup_root\")") != null);
try std.testing.expect(std.mem.indexOf(u8, desktop, "CompiledMarkupImports(core.Model, core.Msg, \"app.native\", &app_markup_sources)") != null);
try std.testing.expect(std.mem.indexOf(u8, desktop, ".view = CompiledAppView.build") != null);

const mobile = @embedFile("src/app_runner/ts_core_mobile.zig");
try std.testing.expect(std.mem.indexOf(u8, mobile, "pub const features: native_sdk.UiAppFeatures = .{ .runtime_markup = false }") != null);
try std.testing.expect(std.mem.indexOf(u8, mobile, "TsUiAppWithFeatures(core, features)") != null);
try std.testing.expect(std.mem.indexOf(u8, mobile, "const app_markup_root = @import(\"app_markup_root\")") != null);
try std.testing.expect(std.mem.indexOf(u8, mobile, "CompiledMarkupImports(core.Model, core.Msg, \"app.native\", &app_markup_sources)") != null);
try std.testing.expect(std.mem.indexOf(u8, mobile, ".view = CompiledAppView.build") != null);
try std.testing.expect(std.mem.indexOf(u8, mobile, ".markup =") == null);
}

test "Debug TypeScript root markup stays outside the staged app module" {
const source = @embedFile("build/app.zig");
try std.testing.expect(std.mem.indexOf(
u8,
source,
"_ = staged.addCopyFile(b.path(appPath(b, app_root, \"src/app.native\")), \"app.native\");",
) == null);
try std.testing.expect(std.mem.indexOf(
u8,
source,
"_ = release_markup.addCopyFile(b.path(appPath(b, app_root, \"src/app.native\")), \"app.native\");",
) != null);
try std.testing.expect(std.mem.indexOf(u8, source, "if (optimize != .Debug)") != null);
}

test "native check preserves the app markup root for component files" {
const source = @embedFile("tools/native-sdk/main.zig");
try std.testing.expect(std.mem.indexOf(
u8,
source,
"checkFiles(allocator, io, markup_files.items, .{ .import_root = \"src\" })",
) != null);
}

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const host_target = b.graph.host;
Expand Down Expand Up @@ -397,6 +456,13 @@ pub fn build(b: *std.Build) void {
const evals_cmdview_mod = module(b, target, optimize, "evals/harness-lib/cmdview.zig");
const evals_cmdview_tests = testArtifact(b, evals_cmdview_mod);
const build_graph_tests = testArtifact(b, module(b, host_target, optimize, "build.zig"));
const invalid_import_compile_mod = module(b, target, optimize, "tests/ts-core/invalid_import_compile.zig");
invalid_import_compile_mod.addImport("canvas", canvas_mod);
const invalid_import_compile = b.addObject(.{
.name = "ts-invalid-import-compile",
.root_module = invalid_import_compile_mod,
});
invalid_import_compile.expect_errors = .{ .contains = "imported files define templates only - this file has a view root element; move the view to its own file and import just the templates" };

// `native version` names the commit the binary was built from, so
// binary/framework skew ("your native binary may be stale") is a
Expand Down Expand Up @@ -621,6 +687,7 @@ pub fn build(b: *std.Build) void {
};

const test_step = b.step("test", "Run package and framework tests");
test_step.dependOn(&invalid_import_compile.step);
test_step.dependOn(&b.addRunArtifact(build_graph_tests).step);
test_step.dependOn(&b.addRunArtifact(geometry_tests).step);
test_step.dependOn(&b.addRunArtifact(assets_tests).step);
Expand Down Expand Up @@ -665,6 +732,10 @@ pub fn build(b: *std.Build) void {
const ai_chat_e2e_run = b.addRunArtifact(ts_core_artifacts.ai_chat);
const feed_reader_e2e_run = b.addRunArtifact(ts_core_artifacts.feed_reader);
const services_e2e_run = b.addRunArtifact(ts_core_artifacts.services);
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);
markup_components_e2e_step.dependOn(&invalid_import_compile.step);
ts_services_e2e_step.dependOn(&feed_reader_e2e_run.step);
ts_services_e2e_step.dependOn(&services_e2e_run.step);
// The same fixture through the in-process carrier (ServicePool over
Expand Down Expand Up @@ -2400,12 +2471,12 @@ pub fn build(b: *std.Build) void {
\\provenance="$("$cli" automate provenance kanban-canvas "$button_id" 2>/dev/null)"
\\case "$provenance" in *"authored=markup"*"root=src/app.native"*) ;; *) echo "writeback smoke: button provenance was not markup-authored: $provenance" >&2; exit 1 ;; esac
\\case "$provenance" in *"node file=src/app.native"*) ;; *) echo "writeback smoke: button provenance named the wrong file: $provenance" >&2; exit 1 ;; esac
\\# 2. Loop provenance: the boot view is deliberately self-contained,
\\# so a card title reports its node in app.native plus its iteration key.
\\# 2. Imported-loop provenance: a card title reports its authored
\\# component file plus its iteration key.
\\card_id="$(printf '%s\n' "$snapshot" | sed -n 's/.*widget @w1\/kanban-canvas#\([0-9][0-9]*\) role=text name="Retry failed agent runs".*/\1/p' | head -n 1)"
\\case "$card_id" in ''|*[!0-9]*) echo "writeback smoke: card text id was missing from the snapshot" >&2; exit 1 ;; esac
\\card_provenance="$("$cli" automate provenance kanban-canvas "$card_id" 2>/dev/null)"
\\case "$card_provenance" in *"node file=src/app.native"*) ;; *) echo "writeback smoke: card provenance named the wrong file: $card_provenance" >&2; exit 1 ;; esac
\\case "$card_provenance" in *"node file=src/components/board-column.native"*) ;; *) echo "writeback smoke: card provenance named the wrong file: $card_provenance" >&2; exit 1 ;; esac
\\case "$card_provenance" in *"keys="*) ;; *) echo "writeback smoke: card provenance missed the iteration key: $card_provenance" >&2; exit 1 ;; esac
\\# 3. Write-back: flip the Todo heading through the verb; the app's own
\\# hot-reload watch picks the file change up and repaints.
Expand Down Expand Up @@ -3360,9 +3431,17 @@ fn tsCoreE2eArtifact(
e2e_mod.addImport("native_sdk", desktop_mod);
e2e_mod.addImport("ts_core_fixture", fixture_mod);

// The markup battery: the .native view + automation + record/replay
// guarantees over the markup fixture's compiled core.
const markup_e2e_mod = module(b, target, optimize, "tests/ts-core/markup_e2e_tests.zig");
// The markup battery: the imported .native view + automation +
// record/replay guarantees over the markup fixture's compiled core.
const markup_view_stage = b.addWriteFiles();
const markup_view_root = markup_view_stage.addCopyFile(b.path("tests/ts-core/markup_e2e_tests.zig"), "markup_e2e_tests.zig");
_ = markup_view_stage.addCopyFile(b.path("tests/ts-core/markup_view.native"), "markup_view.native");
_ = markup_view_stage.addCopyFile(b.path("tests/ts-core/components/actions.native"), "components/actions.native");
const markup_e2e_mod = b.createModule(.{
.root_source_file = markup_view_root,
.target = target,
.optimize = optimize,
});
markup_e2e_mod.addImport("native_sdk", desktop_mod);
markup_e2e_mod.addImport("ts_markup_fixture", markup_fixture_mod);

Expand All @@ -3378,6 +3457,7 @@ fn tsCoreE2eArtifact(
const kanban_stage = b.addWriteFiles();
const kanban_root = kanban_stage.addCopyFile(b.path("tests/ts-core/kanban_e2e_tests.zig"), "kanban_e2e_tests.zig");
_ = kanban_stage.addCopyFile(b.path("examples/kanban/src/app.native"), "app.native");
_ = kanban_stage.addCopyFile(b.path("examples/kanban/src/components/board-column.native"), "components/board-column.native");
const kanban_mod = b.createModule(.{
.root_source_file = kanban_root,
.target = target,
Expand Down
Loading
Loading