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
40 changes: 25 additions & 15 deletions src/app_runner/ts_compiled_view.zig
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ fn virtualView(ui: *Ui, label: []const u8, video: []const u8) !Ui.Node {
.viewport_fallback = request.viewportFallback,
.index_base = request.indexBase,
.anchor = if (request.trailing) .trailing else .leading,
.extent_estimate = if (request.estimateHelper >= 0) core.Model.virtualExtentEstimate else null,
.extent_estimate = if (request.estimateHelper >= 0) core.virtualExtentEstimate else null,
.extent_context = if (request.estimateHelper >= 0) @ptrFromInt(@as(usize, @intCast(request.estimateHelper)) + 1) else null,
};
target.range = ui.virtualWindow(target.options);
Expand All @@ -245,8 +245,7 @@ fn virtualView(ui: *Ui, label: []const u8, video: []const u8) !Ui.Node {
return decodeVirtual(ui, core.nativeVirtualView(label, context, video, ui.arena), resolved);
}

/// Concrete callbacks for generated compiled-model apps; the legacy builders
/// retain their full Model signature for independent reference fixtures.
/// Concrete callbacks for generated compiled-model apps.
pub fn buildRuntime(ui: *Ui, model: *const core.RuntimeModel) Ui.Node {
_ = model;
return buildCommitted(ui);
Expand All @@ -257,9 +256,21 @@ pub fn buildRuntimeWindow(ui: *Ui, model: *const core.RuntimeModel, label: []con
return buildCommittedWindow(ui, label);
}

pub fn build(ui: *Ui, model: *const core.Model) Ui.Node {
_ = model;
return buildCommitted(ui);
/// Callbacks for any host model representation. The compiled view never
/// reads the model it is handed, so harnesses that keep another host model
/// install the same committed tree source as generated apps.
pub fn ViewFor(comptime HostModel: type) type {
return struct {
pub fn build(ui: *Ui, model: *const HostModel) Ui.Node {
_ = model;
return buildCommitted(ui);
}

pub fn buildWindow(ui: *Ui, model: *const HostModel, label: []const u8) Ui.Node {
_ = model;
return buildCommittedWindow(ui, label);
}
};
}

fn buildCommitted(ui: *Ui) Ui.Node {
Expand All @@ -269,11 +280,6 @@ fn buildCommitted(ui: *Ui) Ui.Node {
return decode(ui, bytes) catch @panic("invalid compiled TypeScript view data");
}

pub fn buildWindow(ui: *Ui, model: *const core.Model, label: []const u8) Ui.Node {
_ = model;
return buildCommittedWindow(ui, label);
}

fn buildCommittedWindow(ui: *Ui, label: []const u8) Ui.Node {
var context: [20]u8 = undefined;
if (comptime @hasDecl(core, "nativeVirtualRequests")) return virtualView(ui, label, videoContext(ui, &context)) catch @panic("invalid compiled virtual window view data");
Expand Down Expand Up @@ -1225,6 +1231,10 @@ test "compiled code diff ordinals preserve every mask word and reject invalid tr
}
}

/// Reference markup in these checks binds nothing, so it compiles against an
/// empty model rather than the app's source model.
const BindingFreeModel = struct {};

// The native construction oracle supplies authored data. Compiled views also
// attach the appearance owner to every node; compare that callback explicitly
// instead of erasing it from the decoded tree.
Expand All @@ -1246,8 +1256,8 @@ test "compiled tooltip records match native anchored and static primitives" {
var actual = try decode(&ui, bytes);
@memset(bytes, 0);
var reference_ui = Ui.init(arena.allocator());
const Reference = sdk.canvas.CompiledMarkupView(core.Model, core.Msg, "<stack><button>Run</button><tooltip anchor=\"above\" anchor-alignment=\"end\" anchor-offset=\"8\" tooltip-delay=\"250\">Run café</tooltip><tooltip>Static hint</tooltip></stack>");
const model: core.Model = undefined;
const Reference = sdk.canvas.CompiledMarkupView(BindingFreeModel, core.Msg, "<stack><button>Run</button><tooltip anchor=\"above\" anchor-alignment=\"end\" anchor-offset=\"8\" tooltip-delay=\"250\">Run café</tooltip><tooltip>Static hint</tooltip></stack>");
const model: BindingFreeModel = .{};
const expected = withCompiledAppearance(Reference.build(&reference_ui, &model));
// The runtime-only callback differs by frontend; every authored
// primitive field, child, owned string and anchor must match.
Expand Down Expand Up @@ -1422,10 +1432,10 @@ pub fn testInlineParagraphRecords() !void {
var actual = try decode(&ui, bytes);
@memset(bytes, 0);
var reference_ui = Ui.init(arena.allocator());
const Reference = sdk.canvas.CompiledMarkupView(core.Model, core.Msg,
const Reference = sdk.canvas.CompiledMarkupView(BindingFreeModel, core.Msg,
\\<text text-alignment="end" size="display" key="readout" label="Result">Value <span weight="medium" mono="true" italic="true" underline="true" scale="1.5" foreground="accent">café</span>.</text>
);
const model: core.Model = undefined;
const model: BindingFreeModel = .{};
const expected = withCompiledAppearance(Reference.build(&reference_ui, &model));
actual.widget.interaction_policy = null;
const actual_tree = try ui.finalize(actual);
Expand Down
2 changes: 1 addition & 1 deletion src/app_runner/ts_core_main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ fn appOptions(io: ?std.Io) Adapter.Options {
.theme_accent = comptime runner.manifestThemeAccent(),
};
if (comptime ts_view.enabled) options.view = ts_view.buildRuntime else if (comptime !dev) options.view = CompiledAppView.build;
if (comptime @hasDecl(core.Model, "windows")) {
if (comptime core.has_windows) {
if (comptime ts_view.enabled) {
options.window_view = ts_view.buildRuntimeWindow;
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/app_runner/ts_core_mobile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub fn initModel() Model {
}

pub fn mobileOptions() Adapter.Options {
if (comptime @hasDecl(core.Model, "windows")) {
if (comptime core.has_windows) {
@compileError("model-declared secondary windows are a desktop shell capability; omit windows(model) from mobile targets");
}
var options: Adapter.Options = .{
Expand Down
3 changes: 2 additions & 1 deletion tests/ts-core/canvas_layers_e2e_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const std = @import("std");
const sdk = @import("native_sdk");
const core = @import("canvas_layers_core");
const decoder = @import("canvas_layers_decoder");
const CompiledView = decoder.ViewFor(core.Model);
const parity = @import("effects_media_parity.zig");
const canvas = sdk.canvas;
const geometry = sdk.geometry;
Expand Down Expand Up @@ -83,7 +84,7 @@ pub fn nativeSuffix(model: *const NativeModel, builder: *canvas.Builder, _: geom
if (model.phase == 1) try builder.strokePath(.{ .id = 7, .elements = &open_elements, .stroke = .{ .width = 6.5, .fill = .{ .color = .rgba(0.75, 0.5, 0.25, 1) } }, .cap = .butt });
}
fn options(comptime compiled: bool) Adapter.Options {
return .{ .name = "canvas-layers", .scene = scene, .canvas_label = "layers-canvas", .view = if (compiled) decoder.build else View.build };
return .{ .name = "canvas-layers", .scene = scene, .canvas_label = "layers-canvas", .view = if (compiled) CompiledView.build else View.build };
}
fn pointerClick(runtime: *sdk.Runtime, app: sdk.App, root: *const canvas.Widget, name: []const u8, timestamp: u64) !void {
const widget = parity.find(root, name) orelse return error.WidgetNotFound;
Expand Down
5 changes: 3 additions & 2 deletions tests/ts-core/canvas_preview_e2e_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const std = @import("std");
const native_sdk = @import("native_sdk");
const core = @import("canvas_preview_core");
const decoder = @import("canvas_preview_decoder");
const CompiledView = decoder.ViewFor(core.Model);
const main = @import("canvas_preview_reference.zig");
const canvas = native_sdk.canvas;
const geometry = native_sdk.geometry;
Expand All @@ -16,7 +17,7 @@ fn createApp() !*PreviewApp {
.name = "canvas-preview",
.scene = main.shell_scene,
.canvas_label = main.canvas_label,
.view = decoder.build,
.view = CompiledView.build,
.on_command = core.commandMsg,
});
}
Expand Down Expand Up @@ -96,7 +97,7 @@ fn viewParity(native: *const main.Model, model: *const core.Model) !void {
const View = canvas.CompiledMarkupView(core.Model, core.Msg, @embedFile("app.native"));
for (0..2) |backend| {
var b = Ui.init(arena.allocator());
const root = if (backend == 0) View.build(&b, model) else decoder.build(&b, model);
const root = if (backend == 0) View.build(&b, model) else CompiledView.build(&b, model);
core.rt.frameReset();
const actual = try b.finalize(root);
try equal(expected.root, actual.root);
Expand Down
7 changes: 4 additions & 3 deletions tests/ts-core/channel_monitor_e2e_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const std = @import("std");
const sdk = @import("native_sdk");
const core = @import("channel_monitor_core");
const decoder = @import("channel_monitor_decoder");
const CompiledView = decoder.ViewFor(core.Model);
const reference = @import("channel-monitor-reference/main.zig");
const parity = @import("effects_media_parity.zig");
const Host = sdk.TsCoreHost(core);
Expand Down Expand Up @@ -58,7 +59,7 @@ fn viewParity(native: *const reference.Model, model: *const core.Model) !void {
const View = canvas.CompiledMarkupView(core.Model, core.Msg, @embedFile("app.native"));
for (0..2) |backend| {
var b = Ui.init(arena.allocator());
const actual = try b.finalize(if (backend == 0) View.build(&b, model) else decoder.build(&b, model));
const actual = try b.finalize(if (backend == 0) View.build(&b, model) else CompiledView.build(&b, model));
core.rt.frameReset();
try parity.equal(expected.root, actual.root);
try testing.expectEqual(expected.handlers.len, actual.handlers.len);
Expand Down Expand Up @@ -346,7 +347,7 @@ fn monitorReplay(comptime compiled_view: bool, failed: bool) !void {
defer if (full_model) |bytes| testing.allocator.free(bytes);
defer if (persistence) |bytes| testing.allocator.free(bytes);
{
const state = try Adapter.create(testing.allocator, .{}, .{ .name = "channel-monitor", .scene = shell_scene, .canvas_label = canvas_label, .view = if (compiled_view) decoder.build else View.build });
const state = try Adapter.create(testing.allocator, .{}, .{ .name = "channel-monitor", .scene = shell_scene, .canvas_label = canvas_label, .view = if (compiled_view) CompiledView.build else View.build });
defer state.destroy();
state.effects.executor = .fake;
state.effects.channel_sources = .{ .context = &source, .start_fn = Source.start };
Expand Down Expand Up @@ -386,7 +387,7 @@ fn monitorReplay(comptime compiled_view: bool, failed: bool) !void {
try testing.expect(recorder.effect_count >= 38);
}
const calls = source.calls;
const state = try Adapter.create(testing.allocator, .{}, .{ .name = "channel-monitor", .scene = shell_scene, .canvas_label = canvas_label, .view = if (compiled_view) decoder.build else View.build });
const state = try Adapter.create(testing.allocator, .{}, .{ .name = "channel-monitor", .scene = shell_scene, .canvas_label = canvas_label, .view = if (compiled_view) CompiledView.build else View.build });
defer state.destroy();
state.effects.channel_sources = .{ .context = &source, .start_fn = Source.start };
const harness = try sdk.TestHarness().create(testing.allocator, .{ .size = .init(560, 420) });
Expand Down
3 changes: 2 additions & 1 deletion tests/ts-core/chart_app_e2e_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const std = @import("std");
const sdk = @import("native_sdk");
const core = @import("chart_core");
const decoder = @import("chart_decoder");
const CompiledView = decoder.ViewFor(core.Model);
const exact = @import("component_construction_e2e_tests.zig").exact;
const canvas = sdk.canvas;
const Ui = canvas.Ui(core.Msg);
Expand All @@ -20,7 +21,7 @@ test "compiled app chart preserves complete trees geometry raw words dispatch ef
var before_root = View.build(&reference, &model);
stampReference(&before_root);
const expected = try reference.finalize(before_root);
const root = decoder.build(&compiled, &model);
const root = CompiledView.build(&compiled, &model);
core.rt.frameReset();
const actual = try compiled.finalize(root);
try exact(expected.root, actual.root);
Expand Down
3 changes: 2 additions & 1 deletion tests/ts-core/code_app_e2e_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const std = @import("std");
const sdk = @import("native_sdk");
const core = @import("code_core");
const decoder = @import("code_decoder");
const CompiledView = decoder.ViewFor(core.Model);
const parity = @import("effects_media_parity.zig");
const canvas = sdk.canvas;
const Ui = canvas.Ui(core.Msg);
Expand Down Expand Up @@ -42,7 +43,7 @@ test "compiled app code preserves complete generated trees geometry input effect
var reference = Ui.init(arena.allocator());
var compiled = Ui.init(arena.allocator());
const expected = try reference.finalize(View.build(&reference, &model));
const root = decoder.build(&compiled, &model);
const root = CompiledView.build(&compiled, &model);
core.rt.frameReset();
const actual = try compiled.finalize(root);
try parity.equal(expected.root, actual.root);
Expand Down
3 changes: 2 additions & 1 deletion tests/ts-core/code_editor_e2e_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const std = @import("std");
const sdk = @import("native_sdk");
const core = @import("code_editor_core");
const decoder = @import("code_editor_decoder");
const CompiledView = decoder.ViewFor(core.Model);
const reference = @import("code-editor-reference/main.zig");
const Host = sdk.TsCoreHost(core);
const testing = std.testing;
Expand Down Expand Up @@ -442,7 +443,7 @@ fn viewEqual(expected: *const reference.AppModel, actual: *const core.Model) !vo
var a = reference.BrowserUi.init(arena.allocator());
const old = try a.finalize(reference.CompiledCodeEditorView.build(&a, &session.browser));
var b = canvas.Ui(core.Msg).init(arena.allocator());
const new = try b.finalize(if (index == 0) decoder.build(&b, actual) else decoder.buildWindow(&b, actual, labels[index]));
const new = try b.finalize(if (index == 0) CompiledView.build(&b, actual) else CompiledView.buildWindow(&b, actual, labels[index]));
core.rt.frameReset();
try widgetEqual(old.root, new.root);
try testing.expectEqual(old.handlers.len, new.handlers.len);
Expand Down
3 changes: 2 additions & 1 deletion tests/ts-core/composition_app_e2e_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const std = @import("std");
const sdk = @import("native_sdk");
const core = @import("composition_core");
const decoder = @import("composition_decoder");
const CompiledView = decoder.ViewFor(core.Model);
const parity = @import("effects_media_parity.zig");
const canvas = sdk.canvas;
const Ui = canvas.Ui(core.Msg);
Expand Down Expand Up @@ -56,7 +57,7 @@ test "compiled app composition preserves whole generated trees geometry handlers
var reference = Ui.init(arena.allocator());
var compiled = Ui.init(arena.allocator());
const expected = try reference.finalize(View.build(&reference, &model));
const root = decoder.build(&compiled, &model);
const root = CompiledView.build(&compiled, &model);
core.rt.frameReset();
const actual = try compiled.finalize(root);
try parity.equal(expected.root, actual.root);
Expand Down
7 changes: 4 additions & 3 deletions tests/ts-core/deck_e2e_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const wire = @import("shim_rt.zig");
const ref = @import("deck_reference_access.zig");
const reference = ref.model;
const decoder = @import("deck_decoder");
const CompiledView = decoder.ViewFor(core.Model);
const parity = @import("effects_media_parity.zig");
const Host = sdk.TsCoreHost(core);

Expand Down Expand Up @@ -329,7 +330,7 @@ fn viewParity(native: *const reference.Model, model: *const core.Model, playlist
var a = ref.view.Ui.init(alloc);
const expected = try a.finalize(if (playlist) ref.view.playlistView(&a, native) else ref.view.rootView(&a, native));
var b = sdk.canvas.Ui(core.Msg).init(alloc);
const actual = try b.finalize(if (playlist) decoder.buildWindow(&b, model, "playlist") else decoder.build(&b, model));
const actual = try b.finalize(if (playlist) CompiledView.buildWindow(&b, model, "playlist") else CompiledView.build(&b, model));
core.rt.frameReset();
try parity.equal(expected.root, actual.root);
try std.testing.expectEqual(expected.handlers.len, actual.handlers.len);
Expand Down Expand Up @@ -463,8 +464,8 @@ fn wholeOptions(comptime Bridge: type) Bridge.Options {
.name = "deck", .scene = comptime sdk.app_manifest.shellConfigFrom(@import("deck_manifest")),
.canvas_label = "deck-canvas", .fonts = &ref.main.app_fonts,
.on_command = core.commandMsg,
.view = if (Bridge.Model == core.RuntimeModel) decoder.buildRuntime else decoder.build,
.window_view = if (Bridge.Model == core.RuntimeModel) decoder.buildRuntimeWindow else decoder.buildWindow,
.view = if (Bridge.Model == core.RuntimeModel) decoder.buildRuntime else CompiledView.build,
.window_view = if (Bridge.Model == core.RuntimeModel) decoder.buildRuntimeWindow else CompiledView.buildWindow,
};
}
fn wholeImages(comptime Bridge: type) [8]Bridge.BootImage {
Expand Down
7 changes: 4 additions & 3 deletions tests/ts-core/effects_probe_e2e_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const std = @import("std");
const sdk = @import("native_sdk");
const core = @import("effects_probe_core");
const decoder = @import("effects_probe_decoder");
const CompiledView = decoder.ViewFor(core.Model);
const reference = @import("effects_probe_reference.zig");
const parity = @import("effects_media_parity.zig");
const canvas = sdk.canvas;
Expand Down Expand Up @@ -41,7 +42,7 @@ fn viewParity(native: *const reference.Model, model: *const core.Model) !void {
const View = canvas.CompiledMarkupView(core.Model, core.Msg, @embedFile("app.native"));
for (0..2) |backend| {
var b = Ui.init(arena.allocator());
const actual = try b.finalize(if (backend == 0) View.build(&b, model) else decoder.build(&b, model));
const actual = try b.finalize(if (backend == 0) View.build(&b, model) else CompiledView.build(&b, model));
core.rt.frameReset();
try parity.equal(expected.root, actual.root);
try testing.expectEqual(expected.handlers.len, actual.handlers.len);
Expand Down Expand Up @@ -167,7 +168,7 @@ fn probeReplay(comptime compiled_view: bool) !void {
var full_model: ?[]u8 = null;
defer if (full_model) |bytes| testing.allocator.free(bytes);
{
const state = try Adapter.create(testing.allocator, .{}, .{ .name = "effects-probe", .scene = reference.shell_scene, .canvas_label = reference.canvas_label, .view = if (compiled_view) decoder.build else View.build });
const state = try Adapter.create(testing.allocator, .{}, .{ .name = "effects-probe", .scene = reference.shell_scene, .canvas_label = reference.canvas_label, .view = if (compiled_view) CompiledView.build else View.build });
defer state.destroy();
state.effects.executor = .fake;
const harness = try sdk.TestHarness().create(testing.allocator, .{ .size = .init(560, 480) });
Expand Down Expand Up @@ -195,7 +196,7 @@ fn probeReplay(comptime compiled_view: bool) !void {
try testing.expect(!recorder.failed);
try testing.expect(recorder.effect_count >= 32);
}
const state = try Adapter.create(testing.allocator, .{}, .{ .name = "effects-probe", .scene = reference.shell_scene, .canvas_label = reference.canvas_label, .view = if (compiled_view) decoder.build else View.build });
const state = try Adapter.create(testing.allocator, .{}, .{ .name = "effects-probe", .scene = reference.shell_scene, .canvas_label = reference.canvas_label, .view = if (compiled_view) CompiledView.build else View.build });
defer state.destroy();
const harness = try sdk.TestHarness().create(testing.allocator, .{ .size = .init(560, 480) });
defer harness.destroy(testing.allocator);
Expand Down
Loading
Loading