const std = @import("std");
const geometry = @import("geometry");
const canvas = @import("canvas");
const app_manifest = @import("app_manifest");
const core = @import("core.zig");
const ui_app_model = @import("ui_app.zig");
const effects_mod = @import("effects.zig");
const automation = @import("../automation/root.zig");
const zero_platform = @import("../platform/root.zig");
const null_platform_mod = @import("../platform/null_platform.zig");
const canvas_frame_helpers = @import("canvas_frame_helpers.zig");

const canvas_label = "counter-canvas";

const CounterModel = struct {
    count: u32 = 0,
};

const CounterMsg = union(enum) {
    increment,
    reset,
};

const CounterApp = ui_app_model.UiApp(CounterModel, CounterMsg);

fn counterUpdate(model: *CounterModel, msg: CounterMsg) void {
    switch (msg) {
        .increment => model.count += 1,
        .reset => model.count = 0,
    }
}

fn counterView(ui: *CounterApp.Ui, model: *const CounterModel) CounterApp.Ui.Node {
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.text(.{}, ui.fmt("Count {d}", .{model.count})),
        ui.button(.{ .variant = .primary, .on_press = .increment }, "Increment"),
        ui.button(.{ .on_press = .reset }, "Reset"),
    });
}

fn counterCommand(name: []const u8) ?CounterMsg {
    if (std.mem.eql(u8, name, "counter.reset")) return .reset;
    return null;
}

fn counterThemePack(model: *const CounterModel) canvas.ThemePack {
    return if (model.count == 0) .house else .geist;
}

fn counterThemeState(model: *const CounterModel) CounterApp.ThemeState {
    return switch (model.count) {
        0 => .{ .color_scheme = .dark },
        1 => .{ .pack = .geist, .color_scheme = .system, .accent = canvas.Color.rgb8(0, 0x78, 0x6f) },
        else => .{ .pack = .house, .color_scheme = .light, .accent = canvas.Color.rgb8(0xdf, 0x26, 0x70) },
    };
}

fn invalidCounterThemeState(_: *const CounterModel) CounterApp.ThemeState {
    return .{ .invalid_accent = "pink" };
}

const counter_views = [_]app_manifest.ShellView{
    .{ .label = canvas_label, .kind = .gpu_surface, .fill = true, .gpu_backend = .metal },
};
const counter_windows = [_]app_manifest.ShellWindow{.{
    .label = "main",
    .title = "Counter",
    .width = 400,
    .height = 300,
    .views = &counter_views,
}};
const counter_scene: app_manifest.ShellConfig = .{ .windows = &counter_windows };

fn counterOptions() CounterApp.Options {
    return .{
        .name = "ui-app-counter",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = counterUpdate,
        .view = counterView,
        .on_command = counterCommand,
    };
}

const RadioModel = struct {
    choice: u8 = 0,
    change_count: u32 = 0,
};

const RadioMsg = union(enum) {
    choose_first,
    choose_second,
};

const RadioApp = ui_app_model.UiApp(RadioModel, RadioMsg);

fn radioUpdate(model: *RadioModel, msg: RadioMsg) void {
    model.choice = switch (msg) {
        .choose_first => 0,
        .choose_second => 1,
    };
    model.change_count += 1;
}

fn radioView(ui: *RadioApp.Ui, model: *const RadioModel) RadioApp.Ui.Node {
    return ui.el(.radio_group, .{ .semantics = .{ .label = "Plan" } }, .{
        ui.el(.radio, .{ .text = "Free", .checked = model.choice == 0, .on_change = .choose_first }, .{}),
        ui.el(.radio, .{ .text = "Pro", .checked = model.choice == 1, .on_change = .choose_second }, .{}),
    });
}

fn radioOptions() RadioApp.Options {
    return .{
        .name = "ui-app-radio",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = radioUpdate,
        .view = radioView,
    };
}

const software_counter_views = [_]app_manifest.ShellView{
    .{ .label = canvas_label, .kind = .gpu_surface, .fill = true, .gpu_backend = .software },
};
const software_counter_windows = [_]app_manifest.ShellWindow{.{
    .label = "main",
    .title = "Software Counter",
    .width = 400,
    .height = 300,
    .views = &software_counter_views,
}};
const software_counter_scene: app_manifest.ShellConfig = .{ .windows = &software_counter_windows };

fn softwareCounterOptions() CounterApp.Options {
    var options = counterOptions();
    options.name = "ui-app-software-counter";
    options.scene = software_counter_scene;
    return options;
}

fn findWidgetIdByText(tree: anytype, kind: canvas.WidgetKind, text: []const u8) ?canvas.ObjectId {
    return findIn(tree.root, kind, text);
}

fn findIn(widget: canvas.Widget, kind: canvas.WidgetKind, text: []const u8) ?canvas.ObjectId {
    if (widget.kind == kind and std.mem.eql(u8, widget.text, text)) return widget.id;
    for (widget.children) |child| {
        if (findIn(child, kind, text)) |id| return id;
    }
    return null;
}

fn retainedTextExists(runtime: *core.Runtime, text: []const u8) !bool {
    const layout = try runtime.canvasWidgetLayout(1, canvas_label);
    for (layout.nodes) |node| {
        if (node.widget.kind == .text and std.mem.eql(u8, node.widget.text, text)) return true;
    }
    return false;
}

const RelationalUiModel = struct {
    pages: u32 = 0,
    rows: u32 = 0,
    done: u32 = 0,
    execs: u32 = 0,
};

const RelationalUiMsg = union(enum) {
    add,
    db: effects_mod.EffectDbResult,
};

const RelationalUiApp = ui_app_model.UiApp(RelationalUiModel, RelationalUiMsg);
const RelationalUiEffects = RelationalUiApp.Effects;

fn relationalUiInit(_: *RelationalUiModel, fx: *RelationalUiEffects) void {
    fx.dbExec(.{
        .key = 101,
        .statements = &.{.{ .sql = "CREATE TABLE note(id INTEGER PRIMARY KEY) STRICT;" }},
        .on_result = RelationalUiEffects.dbMsg(.db),
    });
    fx.dbSubscribe(.{
        .key = 102,
        .sql = "SELECT id FROM note ORDER BY id;",
        .tables = &.{"note"},
        .on_result = RelationalUiEffects.dbMsg(.db),
    });
    fx.dbExec(.{
        .key = 103,
        .statements = &.{.{ .sql = "INSERT INTO note(id) VALUES(1);" }},
        .on_result = RelationalUiEffects.dbMsg(.db),
    });
}

fn relationalUiUpdate(model: *RelationalUiModel, msg: RelationalUiMsg, fx: *RelationalUiEffects) void {
    switch (msg) {
        .add => fx.dbExec(.{
            .key = 104,
            .statements = &.{.{ .sql = "INSERT INTO note(id) VALUES(2);" }},
            .on_result = RelationalUiEffects.dbMsg(.db),
        }),
        .db => |result| {
            if (result.outcome != .ok) return;
            switch (result.kind) {
                .page => {
                    model.pages += 1;
                    model.rows += std.mem.readInt(u32, result.bytes[4..8], .little);
                },
                .done => model.done += 1,
                .exec => model.execs += 1,
            }
        },
    }
}

fn relationalUiView(ui: *RelationalUiApp.Ui, model: *const RelationalUiModel) RelationalUiApp.Ui.Node {
    return ui.text(.{}, ui.fmt("{d} relational pages", .{model.pages}));
}

test "record-store test harness binds one hermetic in-memory database" {
    const harness = try core.TestHarness().createWithRecordStore(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    try std.testing.expect(harness.record_store != null);
    try std.testing.expect(harness.runtime.options.record_store != null);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, counterOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try app_state.dispatch(&harness.runtime, 1, .increment);
    try std.testing.expect(app_state.effects.record_store_binding != null);
    try harness.stop(app);
}

test "relational test harness binds one hermetic in-memory database" {
    const harness = try core.TestHarness().createWithRelationalStore(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    try std.testing.expect(harness.relational_store != null);
    try std.testing.expect(harness.runtime.options.relational_store != null);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, counterOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try app_state.dispatch(&harness.runtime, 1, .increment);
    try std.testing.expect(app_state.effects.relational_store_binding != null);
    try harness.stop(app);
}

test "ui app flushes relational live queries after init and update command batches" {
    const harness = try core.TestHarness().createWithRelationalStore(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(RelationalUiApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = RelationalUiApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-relational-live",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update_fx = relationalUiUpdate,
        .init_fx = relationalUiInit,
        .view = relationalUiView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // init_fx creates the table, subscribes while it is empty, then inserts.
    // Its one flush therefore delivers the initial empty page and one
    // refreshed one-row page.
    try app_state.drainEffects(&harness.runtime);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.pages);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.rows);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.done);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.execs);

    // A later update_fx transaction must invalidate and rerun the same live
    // query without the Zig app manually reaching into the effects host.
    try app_state.dispatch(&harness.runtime, 1, .add);
    try app_state.drainEffects(&harness.runtime);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.pages);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.rows);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.done);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.execs);
    try harness.stop(app);
}

test "ui app owns install, dispatch, and rebuild end to end" {
    // The runtime and the app are both large structs; keep them off the
    // test thread's stack.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, counterOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    // First gpu frame installs the widget tree and display list.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 0"));

    // Automation clicks flow through typed dispatch into update + rebuild.
    const increment_id = findWidgetIdByText(app_state.tree.?, .button, "Increment").?;
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, increment_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 1"));

    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 2"));

    // Structural identity survives the rebuilds the dispatches triggered.
    try std.testing.expectEqual(increment_id, findWidgetIdByText(app_state.tree.?, .button, "Increment").?);

    // Shell command events map into messages through on_command.
    try harness.runtime.dispatchPlatformEvent(app, .{ .menu_command = .{ .name = "counter.reset", .window_id = 1 } });
    try std.testing.expectEqual(@as(u32, 0), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 0"));
}

// ------------------------------------------------ drag/press arbitration fixture

const DragPayload = struct {
    sourceId: u64,
    phase: u32,
    x: f32,
    y: f32,
    viewWidth: f32,
    viewHeight: f32,
};

const DragModel = struct {
    presses: u32 = 0,
    holds: u32 = 0,
    changes: u32 = 0,
    ends: u32 = 0,
    cancels: u32 = 0,
    last_x: f32 = 0,
    unmount_on_drag: bool = false,
    target_mounted: bool = true,
};

const DragMsg = union(enum) {
    pressed,
    held,
    dragged: DragPayload,
};

const DragApp = ui_app_model.UiApp(DragModel, DragMsg);

fn dragUpdate(model: *DragModel, msg: DragMsg) void {
    switch (msg) {
        .pressed => model.presses += 1,
        .held => model.holds += 1,
        .dragged => |drag| {
            model.last_x = drag.x;
            switch (drag.phase) {
                0 => {
                    model.changes += 1;
                    if (model.unmount_on_drag) model.target_mounted = false;
                },
                1 => model.ends += 1,
                2 => model.cancels += 1,
                else => {},
            }
        },
    }
}

fn dragView(ui: *DragApp.Ui, model: *const DragModel) DragApp.Ui.Node {
    if (!model.target_mounted) return ui.column(.{ .padding = 12 }, .{});
    var target = ui.row(.{
        .height = 40,
        .global_key = canvas.uiKey(@as(u64, 7)),
        .on_press = .pressed,
        .on_hold = .held,
        .on_drag = .{ .dragged = .{
            .sourceId = 7,
            .phase = 0,
            .x = 0,
            .y = 0,
            .viewWidth = 0,
            .viewHeight = 0,
        } },
    }, .{ui.text(.{}, "Drag target")});
    target.widget.text = "Drag target";
    return ui.column(.{ .padding = 12 }, .{target});
}

fn dragOptions() DragApp.Options {
    return .{
        .name = "ui-app-drag-press-arbitration",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = dragUpdate,
        .view = dragView,
    };
}

test "ui app keeps clicks below drag slop and suppresses press after a live drag" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(DragApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = DragApp.init(std.heap.page_allocator, .{}, dragOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    const target_id = findWidgetIdByText(app_state.tree.?, .row, "Drag target").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    const frame = layout.findById(target_id).?.frame.normalized();
    const point = frame.center();

    // No motion: one ordinary press, no terminal drag, and no latent landing
    // state for the press-driven rebuild to consume.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .x = point.x,
        .y = point.y,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_up,
        .x = point.x,
        .y = point.y,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.presses);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.changes);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.ends);
    try std.testing.expect(!harness.runtime.views[0].canvas_widget_drag_layout_motion_armed);

    // A little pointer motion is still a click. It remains below the app drag
    // channel's threshold and cannot arm a landing from the default origin.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .x = point.x,
        .y = point.y,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_drag,
        .x = point.x + 3,
        .y = point.y,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_up,
        .x = point.x + 3,
        .y = point.y,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.model.presses);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.changes);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.ends);
    try std.testing.expect(!harness.runtime.views[0].canvas_widget_drag_layout_motion_armed);
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), harness.runtime.views[0].canvas_widget_drag_landing_source_id);

    // Cross the slop, then return near the origin. The below-slop change is
    // filtered, but the terminal event still belongs to the already-live drag;
    // it dispatches once and the captured release cannot also press.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .x = point.x,
        .y = point.y,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_drag,
        .x = point.x + 3,
        .y = point.y,
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.model.changes);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_drag,
        .x = point.x + 40,
        .y = point.y,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.changes);
    // Crossing drag slop gives this gesture to on-drag and cancels the
    // on-hold interpretation armed by the same pointer-down. Waiting past
    // the hold deadline therefore cannot dispatch a second Msg mid-drag.
    try std.testing.expect(!harness.null_platform.startedTimer(DragApp.press_hold_timer_id).?.active);
    try std.testing.expect(harness.null_platform.fireTimer(DragApp.press_hold_timer_id, 400_000_000) == null);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.holds);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_drag,
        .x = point.x + 2,
        .y = point.y,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_up,
        .x = point.x + 2,
        .y = point.y,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.model.presses);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.changes);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.ends);
    try std.testing.expectEqual(point.x + 2, app_state.model.last_x);

    // A cancellation before activation is likewise just an abandoned press;
    // it cannot dispatch a cancel Msg or arm a zero-origin landing.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .x = point.x,
        .y = point.y,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_cancel,
        .x = point.x,
        .y = point.y,
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.model.cancels);
    try std.testing.expect(!harness.runtime.views[0].canvas_widget_drag_layout_motion_armed);
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), harness.runtime.views[0].canvas_widget_drag_landing_source_id);

    // A second contact cannot steal or silently erase a live drag. Its whole
    // sequence stays outside the drag channel; the initiating pointer keeps
    // moving the original source and is the only pointer that may end it.
    const owner_pointer = zero_platform.touch_pointer_id_bit | 101;
    const interloper_pointer = zero_platform.touch_pointer_id_bit | 202;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .pointer_id = owner_pointer,
        .x = point.x,
        .y = point.y,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_drag,
        .pointer_id = owner_pointer,
        .x = point.x + 40,
        .y = point.y,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.model.changes);
    try std.testing.expectEqual(owner_pointer, harness.runtime.views[0].canvas_widget_drag_pointer_id);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .pointer_id = interloper_pointer,
        .x = point.x + 180,
        .y = point.y + 80,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_drag,
        .pointer_id = interloper_pointer,
        .x = point.x + 200,
        .y = point.y + 90,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_up,
        .pointer_id = interloper_pointer,
        .x = point.x + 200,
        .y = point.y + 90,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.model.changes);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.ends);
    try std.testing.expectEqual(owner_pointer, harness.runtime.views[0].canvas_widget_drag_pointer_id);
    try std.testing.expect(harness.runtime.views[0].canvasWidgetRenderState().drag_preview_id != null);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_drag,
        .pointer_id = owner_pointer,
        .x = point.x + 55,
        .y = point.y,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_up,
        .pointer_id = owner_pointer,
        .x = point.x + 55,
        .y = point.y,
    } });
    try std.testing.expectEqual(@as(u32, 3), app_state.model.changes);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.ends);
    try std.testing.expectEqual(@as(u64, 0), harness.runtime.views[0].canvas_widget_drag_pointer_id);
}

test "ui app delivers cancel when a drag update unmounts its source" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(DragApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = DragApp.init(std.heap.page_allocator, .{ .unmount_on_drag = true }, dragOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    const target_id = findWidgetIdByText(app_state.tree.?, .row, "Drag target").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    const point = layout.findById(target_id).?.frame.normalized().center();
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .x = point.x,
        .y = point.y,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_drag,
        .x = point.x + 40,
        .y = point.y + 4,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.changes);
    try std.testing.expect(!app_state.model.target_mounted);
    try std.testing.expectEqual(target_id, app_state.drag_msg_source_id);
    try std.testing.expect(harness.runtime.views[0].canvasWidgetRenderState().drag_preview_id == null);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_up,
        .x = point.x + 46,
        .y = point.y + 7,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.changes);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.ends);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.cancels);
    try std.testing.expectEqual(point.x + 46, app_state.model.last_x);
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), app_state.drag_msg_source_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), harness.runtime.views[0].canvas_widget_drag_source_id);
}

// -------------------------------------- context-menu fallback fixture

const TaskRowModel = struct {
    completed: u32 = 0,
    deleted: u32 = 0,
};

const TaskRowMsg = union(enum) {
    complete,
    delete,
};

const TaskRowApp = ui_app_model.UiApp(TaskRowModel, TaskRowMsg);

fn taskRowUpdate(model: *TaskRowModel, msg: TaskRowMsg) void {
    switch (msg) {
        .complete => model.completed += 1,
        .delete => model.deleted += 1,
    }
}

fn taskRowView(ui: *TaskRowApp.Ui, model: *const TaskRowModel) TaskRowApp.Ui.Node {
    _ = model;
    var row = ui.el(.list_item, .{
        .padding = 8,
        .context_menu = &.{
            .{ .label = "Complete", .msg = .complete },
            .{ .separator = true },
            .{ .label = "Delete", .msg = .delete },
        },
    }, .{
        ui.text(.{ .grow = 1 }, "Task"),
    });
    row.widget.text = "Task";
    return ui.column(.{ .gap = 8, .padding = 12 }, .{row});
}

fn taskRowOptions() TaskRowApp.Options {
    return .{
        .name = "ui-app-task-rows",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = taskRowUpdate,
        .view = taskRowView,
    };
}

fn retainedWidgetKindExists(runtime: *core.Runtime, kind: canvas.WidgetKind) !bool {
    const layout = try runtime.canvasWidgetLayout(1, canvas_label);
    for (layout.nodes) |node| {
        if (node.widget.kind == kind) return true;
    }
    return false;
}

test "a declared context menu presents as the anchored fallback surface on presenter-less hosts, end to end" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    // A host without a native menu presenter (the mobile toolkit hosts
    // and embed hosts today). Service pointers are captured at init, so
    // re-capture.
    harness.null_platform.context_menus = false;
    harness.runtime.options.platform = harness.null_platform.platform();

    const app_state = try std.testing.allocator.create(TaskRowApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = TaskRowApp.init(std.heap.page_allocator, .{}, taskRowOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // At rest: no surface mounted, no fallback state.
    try std.testing.expect(!try retainedWidgetKindExists(&harness.runtime, .dropdown_menu));
    try std.testing.expect(app_state.tree.?.context_menu_fallback == null);

    // Right-click the row: the SAME declared menu mounts as an anchored
    // canvas surface (no native presenter to hand it to).
    const row_id = findWidgetIdByText(app_state.tree.?, .list_item, "Task").?;
    var command_buffer: [96]u8 = undefined;
    const context_press = try std.fmt.bufPrint(&command_buffer, "widget-context-press {s} {d}", .{ canvas_label, row_id });
    try harness.runtime.dispatchAutomationCommand(app, context_press);
    try std.testing.expectEqual(@as(usize, 0), harness.null_platform.context_menu_request_count);
    const fallback = app_state.tree.?.context_menu_fallback orelse return error.TestUnexpectedResult;
    try std.testing.expectEqual(row_id, fallback.target_id);
    try std.testing.expectEqual(@as(usize, 3), fallback.item_ids.len);
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), fallback.item_ids[1]);
    try std.testing.expect(try retainedWidgetKindExists(&harness.runtime, .dropdown_menu));
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .menu_item, "Delete") != null);

    // Clicking "Delete" routes through the target's .context_menu
    // handler — the same entry a native selection resolves — and closes
    // the surface.
    var click_buffer: [96]u8 = undefined;
    const click_delete = try std.fmt.bufPrint(&click_buffer, "widget-click {s} {d}", .{ canvas_label, fallback.item_ids[2] });
    try harness.runtime.dispatchAutomationCommand(app, click_delete);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.deleted);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.completed);
    try std.testing.expect(app_state.tree.?.context_menu_fallback == null);
    try std.testing.expect(!try retainedWidgetKindExists(&harness.runtime, .dropdown_menu));

    // Reopen and click outside. The fallback was opened from a typically
    // non-focusable secondary-click target, so no focused ancestry is
    // available to find it; the whole-view anchored fallback must still
    // dismiss it. Its internal open state closes and no app Msg fires.
    try harness.runtime.dispatchAutomationCommand(app, context_press);
    _ = app_state.tree.?.context_menu_fallback orelse return error.TestUnexpectedResult;
    harness.runtime.views[0].canvas_widget_focused_id = 0;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .label = canvas_label,
        .kind = .pointer_down,
        .x = 360,
        .y = 280,
    } });
    try std.testing.expect(app_state.tree.?.context_menu_fallback == null);
    try std.testing.expect(!try retainedWidgetKindExists(&harness.runtime, .dropdown_menu));
    try std.testing.expectEqual(@as(u32, 1), app_state.model.deleted);

    // The widget-context-menu verb converges on the same dispatch with
    // no surface involved at all.
    var verb_buffer: [96]u8 = undefined;
    const invoke_complete = try std.fmt.bufPrint(&verb_buffer, "widget-context-menu {s} {d} 0", .{ canvas_label, row_id });
    try harness.runtime.dispatchAutomationCommand(app, invoke_complete);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.completed);
    try std.testing.expect(app_state.tree.?.context_menu_fallback == null);
}

test "the fallback surface mounts at the click point, not the wide row's edge" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    harness.null_platform.context_menus = false;
    harness.runtime.options.platform = harness.null_platform.platform();

    const app_state = try std.testing.allocator.create(TaskRowApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = TaskRowApp.init(std.heap.page_allocator, .{}, taskRowOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // Secondary-click the full-width row far from its left edge: the
    // click point rides the request into the mounted surface's anchor.
    const layout_before = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    const row_id = findWidgetIdByText(app_state.tree.?, .list_item, "Task").?;
    const row_frame = blk: {
        for (layout_before.nodes) |node| {
            if (node.widget.id == row_id) break :blk node.frame;
        }
        return error.TestUnexpectedResult;
    };
    const click = geometry.PointF.init(row_frame.x + 150, row_frame.y + row_frame.height / 2);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = click.x,
        .y = click.y,
        .timestamp_ns = 1_000_000_000,
    } });

    try std.testing.expect(app_state.tree.?.context_menu_fallback != null);
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    const surface_frame = blk: {
        for (layout.nodes) |node| {
            if (node.widget.kind == .dropdown_menu) break :blk node.frame;
        }
        return error.TestUnexpectedResult;
    };
    // The surface corner sits at the pointer (space permits below and to
    // the right here), NOT at the row's bottom-left corner.
    try std.testing.expectEqual(click.x, surface_frame.x);
    try std.testing.expectEqual(click.y, surface_frame.y);
    try std.testing.expect(surface_frame.x != row_frame.x);
}

// ------------------------------------------------- scroll event fixture

const FeedModel = struct {
    /// Elm-style mirror of the scroll offset: on_scroll delivers the
    /// offset the runtime already applied, the model stores it, and the
    /// view echoes it back into `value` — which must never fight the
    /// scroll reconcile rule (the echoed source value equals the runtime
    /// offset).
    offset: f32 = 0,
    viewport_extent: f32 = 0,
    content_extent: f32 = 0,
    scroll_events: u32 = 0,
};

const FeedMsg = union(enum) {
    feed_scrolled: canvas.ScrollState,
};

const FeedApp = ui_app_model.UiApp(FeedModel, FeedMsg);

fn feedUpdate(model: *FeedModel, msg: FeedMsg) void {
    switch (msg) {
        .feed_scrolled => |scroll_state| {
            model.offset = scroll_state.offset_y;
            model.viewport_extent = scroll_state.viewport_extent_y;
            model.content_extent = scroll_state.content_extent_y;
            model.scroll_events += 1;
        },
    }
}

fn feedView(ui: *FeedApp.Ui, model: *const FeedModel) FeedApp.Ui.Node {
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.scroll(.{
            .height = 96,
            .value = model.offset,
            .on_scroll = FeedApp.Ui.scrollMsg(.feed_scrolled),
        }, ui.column(.{ .gap = 4 }, .{
            ui.text(.{ .height = 80 }, "Row one"),
            ui.text(.{ .height = 80 }, "Row two"),
            ui.text(.{ .height = 80 }, "Row three"),
            ui.text(.{ .height = 80 }, "Row four"),
        })),
        ui.text(.{}, ui.fmt("Offset {d:.0}", .{model.offset})),
    });
}

fn feedOptions() FeedApp.Options {
    return .{
        .name = "ui-app-feed",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = feedUpdate,
        .view = feedView,
    };
}

test "ui app on_scroll delivers wheel offsets and the echoed model offset survives the rebuild" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(FeedApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = FeedApp.init(std.heap.page_allocator, .{}, feedOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const scroll_id = findWidgetIdByKind(app_state.tree.?.root, .scroll_view).?;
    var command_buffer: [96]u8 = undefined;
    const wheel = try std.fmt.bufPrint(&command_buffer, "widget-wheel {s} {d} 18", .{ canvas_label, scroll_id });
    try harness.runtime.dispatchAutomationCommand(app, wheel);

    // The wheel dispatched a typed scroll Msg carrying the applied
    // offset and the extents (content spans four 80pt rows + gaps).
    try std.testing.expectEqual(@as(u32, 1), app_state.model.scroll_events);
    try std.testing.expect(app_state.model.offset > 0);
    try std.testing.expect(app_state.model.content_extent > app_state.model.viewport_extent);

    // The dispatch rebuilt with the echoed offset; the retained runtime
    // offset agrees with the model (echoing never fights the reconcile
    // rule, because the echoed source value IS the runtime offset).
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    try std.testing.expectEqual(app_state.model.offset, layout.findById(scroll_id).?.widget.value);

    // A second wheel accumulates from the reconciled offset.
    const first_offset = app_state.model.offset;
    try harness.runtime.dispatchAutomationCommand(app, wheel);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.scroll_events);
    try std.testing.expect(app_state.model.offset > first_offset);
}

test "ui app presents pixels when the packet service is unsupported" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    harness.null_platform.gpu_surface_packets = false;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.testing.allocator, .{}, counterOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    // The installing frame falls back from the failing packet presenter to
    // the CPU pixel path: the widget tree installs and the reference-rendered
    // surface reaches the platform at device resolution.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 0"));
    try std.testing.expectEqual(@as(usize, 0), harness.null_platform.gpu_surface_packet_present_count);
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.gpu_surface_present_count);
    try std.testing.expectEqual(@as(usize, 800), harness.null_platform.gpu_surface_present_width);
    try std.testing.expectEqual(@as(usize, 600), harness.null_platform.gpu_surface_present_height);
    try std.testing.expectEqual(@as(f32, 2), harness.null_platform.gpu_surface_present_scale_factor);
    try std.testing.expectEqual(@as(usize, 800 * 600 * 4), harness.null_platform.gpu_surface_present_byte_len);
    try std.testing.expectEqualStrings(
        canvas_label,
        harness.null_platform.gpu_surface_present_label_storage[0..harness.null_platform.gpu_surface_present_label_len],
    );

    // Model changes keep presenting through the pixel path.
    const increment_id = findWidgetIdByText(app_state.tree.?, .button, "Increment").?;
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, increment_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 2,
        .timestamp_ns = 17_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.gpu_surface_present_count);
    try std.testing.expectEqual(@as(usize, 0), harness.null_platform.gpu_surface_packet_present_count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 1"));
}

test "ui app explicit software canvas bypasses the production packet path" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    harness.runtime.options.pixel_present_retained_baseline = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.testing.allocator, .{}, softwareCounterOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    // Packet services are available and would accept this frame. The
    // immutable software request must still send it directly to the
    // reference renderer without recording a packet fallback.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
    } });

    try std.testing.expect(app_state.installed);
    try std.testing.expectEqual(zero_platform.GpuSurfaceBackend.software, harness.runtime.views[0].gpu_requested_backend);
    try std.testing.expectEqual(@as(usize, 0), harness.null_platform.gpu_surface_packet_present_count);
    try std.testing.expectEqual(@as(usize, 0), harness.null_platform.gpu_surface_image_upload_count);
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.gpu_surface_present_count);
    try std.testing.expectEqual(zero_platform.GpuPresentPath.pixels, harness.runtime.views[0].gpu_present_path);
    try std.testing.expectEqual(zero_platform.GpuPresentFallbackReason.none, harness.runtime.views[0].info().gpu_present_fallback_reason);

    // A changed follow-up frame remains incremental. Treating the central
    // software-policy rejection as a failed packet attempt would force this
    // pixel present back to the full surface.
    try app_state.dispatch(&harness.runtime, 1, .increment);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 2,
        .timestamp_ns = 17_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.gpu_surface_present_count);
    const dirty = harness.null_platform.gpu_surface_present_dirty_bounds orelse return error.TestUnexpectedResult;
    try std.testing.expect(dirty.width < 400 or dirty.height < 300);
}

// ---------------------------------- reflow stale-pixel oracle fixture

const ReflowRow = struct {
    id: u32,
    open: bool,
    in_progress: bool,
    urgent: bool,
    high: bool,
    title: []const u8,
};

const reflow_rows_a = [_]ReflowRow{.{ .id = 1, .open = false, .in_progress = false, .urgent = true, .high = false, .title = "Login fails on retry" }};
const reflow_rows_b = [_]ReflowRow{.{ .id = 2, .open = false, .in_progress = true, .urgent = false, .high = true, .title = "Sync is slow" }};
// Same for-key as rows_a: the `<if>` arms swap INSIDE a stable keyed
// subtree (a status change on the selected item, not a reselection).
const reflow_rows_a_started = [_]ReflowRow{.{ .id = 1, .open = false, .in_progress = true, .urgent = false, .high = true, .title = "Login fails on retry" }};

const ReflowModel = struct {
    selected: []const ReflowRow = &reflow_rows_a,
};

const ReflowMsg = union(enum) {
    select_second,
    start_first,
};

const ReflowApp = ui_app_model.UiApp(ReflowModel, ReflowMsg);

fn reflowUpdate(model: *ReflowModel, msg: ReflowMsg) void {
    switch (msg) {
        .select_second => model.selected = &reflow_rows_b,
        .start_first => model.selected = &reflow_rows_a_started,
    }
}

// The list-detail detail pane shape: a keyed subtree per selection
// wrapping conditional badges plus trailing text, so switching the
// selection removes pills, shrinks the row, and moves what remains.
const reflow_markup =
    \\<column grow="1" background="surface">
    \\  <for each="selected" key="id" as="s">
    \\    <column gap="14" padding="24">
    \\      <text size="heading" wrap="true">{s.title}</text>
    \\      <row gap="8" cross="center">
    \\        <if test="{s.open}"><badge foreground="info">Open</badge></if>
    \\        <if test="{s.in_progress}"><badge foreground="warning">In progress</badge></if>
    \\        <if test="{s.urgent}"><badge foreground="destructive">Urgent priority</badge></if>
    \\        <if test="{s.high}"><badge foreground="warning">High priority</badge></if>
    \\        <text foreground="text_muted">#{s.id} · reported by a user</text>
    \\      </row>
    \\    </column>
    \\  </for>
    \\</column>
;

fn reflowOptions() ReflowApp.Options {
    return .{
        .name = "ui-app-reflow-oracle",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = reflowUpdate,
        .markup = .{ .source = reflow_markup },
    };
}

test "selection-change reflow leaves no stale pixels on the ui app pixel path" {
    const surface = geometry.SizeF.init(400, 300);
    const scale: f32 = 2;
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = surface });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    // Pixel-only host shape (a platform with no packet presenter wired,
    // like the embed hosts): presents ride the CPU pixel path
    // incrementally instead of forcing packet-fallback full repaints.
    harness.runtime.options.platform.services.present_gpu_surface_packet_fn = null;
    harness.runtime.options.platform.services.present_gpu_surface_packet_binary_fn = null;
    // The refined dirty-bounds path: a Msg rebuild derives its damage
    // from the retained key+fingerprint edit script — the derivation
    // packet hosts and embed hosts consume — instead of degrading to
    // the window.
    harness.runtime.options.pixel_present_retained_baseline = true;

    const app_state = try std.testing.allocator.create(ReflowApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ReflowApp.init(std.heap.page_allocator, .{}, reflowOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    // Installing frame: full repaint of selection A through the CPU
    // pixel path into the app's retained pixel buffer.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = surface,
        .scale_factor = scale,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.gpu_surface_present_count);

    const byte_len = (try canvas_frame_helpers.canvasSurfacePixelSize(surface, scale)).byte_len;
    const after_swap = try std.testing.allocator.alloc(u8, byte_len);
    defer std.testing.allocator.free(after_swap);
    const full = try std.testing.allocator.alloc(u8, byte_len);
    defer std.testing.allocator.free(full);
    const full_scratch = try std.testing.allocator.alloc(u8, byte_len);
    defer std.testing.allocator.free(full_scratch);

    // Two reflows: the `<if>` arms swapping inside a stable keyed
    // subtree (status change), then the keyed subtree replaced whole
    // (reselection). Each presents incrementally and must leave the
    // retained buffer byte-identical to a full repaint of the same
    // scene.
    const steps = [_]struct { msg: ReflowMsg, frame_index: u64 }{
        .{ .msg = .start_first, .frame_index = 2 },
        .{ .msg = .select_second, .frame_index = 3 },
    };
    var present_count: usize = 1;
    for (steps) |step| {
        try app_state.dispatch(&harness.runtime, 1, step.msg);
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
            .label = canvas_label,
            .size = surface,
            .scale_factor = scale,
            .frame_index = step.frame_index,
            .timestamp_ns = step.frame_index * 17_000_000,
        } });
        present_count += 1;
        try std.testing.expectEqual(present_count, harness.null_platform.gpu_surface_present_count);
        // Proof the reflow rode the incremental path: the present's
        // dirty bounds are a sub-window region.
        const swap_dirty = harness.null_platform.gpu_surface_present_dirty_bounds.?;
        try std.testing.expect(swap_dirty.width < surface.width or swap_dirty.height < surface.height);
        @memcpy(after_swap, app_state.pixel_buffer[0..byte_len]);

        // Ground truth: the same scene fully repainted into a fresh
        // buffer (a direct render, so the app's incremental buffer
        // stays untouched).
        _ = try harness.runtime.presentNextCanvasFramePixels(1, canvas_label, .{
            .frame_index = step.frame_index + 100,
            .timestamp_ns = (step.frame_index + 100) * 17_000_000,
            .surface_size = surface,
            .scale = scale,
            .full_repaint = true,
        }, harness.runtime.canvasFrameScratchStorage(), full, full_scratch, app_state.effectiveTokens().colors.background);
        present_count += 1;
        try std.testing.expectEqualSlices(u8, full, after_swap);
    }
}

const counter_markup =
    \\<column gap="8" padding="12">
    \\  <text>Count {count}</text>
    \\  <button variant="primary" on-press="increment">Increment</button>
    \\  <button on-press="reset">Reset</button>
    \\</column>
;

const counter_markup_v2 =
    \\<column gap="8" padding="12">
    \\  <text>Count {count}</text>
    \\  <button variant="primary" on-press="increment">Increment</button>
    \\  <button on-press="reset">Start over</button>
    \\</column>
;

fn markupCounterOptions() CounterApp.Options {
    return .{
        .name = "ui-app-markup-counter",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = counterUpdate,
        .markup = .{ .source = counter_markup },
        .on_command = counterCommand,
    };
}

test "markup views drive the ui app loop and hot reload preserves state" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, markupCounterOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 0"));

    // Markup-declared handlers dispatch through the same typed loop.
    const increment_id = findWidgetIdByText(app_state.tree.?, .button, "Increment").?;
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, increment_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 1"));

    // Hot reload: new view source, model state kept, ids stable.
    try app_state.reloadMarkup(counter_markup_v2);
    try app_state.rebuild(&harness.runtime, 1);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 1"));
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Start over") != null);
    try std.testing.expectEqual(increment_id, findWidgetIdByText(app_state.tree.?, .button, "Increment").?);

    // A broken reload keeps the last good view and records the diagnostic.
    try std.testing.expectError(error.MarkupSyntax, app_state.reloadMarkup("<column><oops</column>"));
    try std.testing.expect(app_state.markup_diagnostic != null);
    try app_state.rebuild(&harness.runtime, 1);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Start over") != null);
}

const counter_timer_id: u64 = 42;

fn counterTimer(id: u64, timestamp_ns: u64) ?CounterMsg {
    _ = timestamp_ns;
    if (id == counter_timer_id) return .increment;
    return null;
}

test "ui app maps timer events into messages and rebuilds" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    var options = counterOptions();
    options.on_timer = counterTimer;
    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 0"));

    // A fired timer maps through on_timer into update + rebuild.
    try harness.runtime.startTimer(counter_timer_id, 100_000_000, true);
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(counter_timer_id, 2_000_000).?);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 1"));

    // Cancelled timers stop producing events entirely.
    try harness.runtime.cancelTimer(counter_timer_id);
    try std.testing.expect(harness.null_platform.fireTimer(counter_timer_id, 3_000_000) == null);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
}

// -------------------------------------------------------- transform channel

const SlideModel = struct {
    tick: u32 = 0,
};

const SlideMsg = union(enum) {
    tick,
};

const SlideApp = ui_app_model.UiApp(SlideModel, SlideMsg);

const slide_timer_id: u64 = 43;

fn slideUpdate(model: *SlideModel, msg: SlideMsg) void {
    switch (msg) {
        .tick => model.tick += 1,
    }
}

fn slideOffset(tick: u32) f32 {
    return @as(f32, @floatFromInt(tick)) * 8;
}

fn slideView(ui: *SlideApp.Ui, model: *const SlideModel) SlideApp.Ui.Node {
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.el(.panel, .{
            .transform = canvas.Affine.translate(slideOffset(model.tick), 0),
            .opacity = 0.9,
            .width = 80,
            .height = 40,
        }, .{
            ui.text(.{}, "Slide"),
        }),
    });
}

fn slideTimer(id: u64, timestamp_ns: u64) ?SlideMsg {
    _ = timestamp_ns;
    if (id == slide_timer_id) return .tick;
    return null;
}

fn slideOptions() SlideApp.Options {
    return .{
        .name = "ui-app-slide",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = slideUpdate,
        .view = slideView,
        .on_timer = slideTimer,
    };
}

fn retainedPanelTransform(runtime: *core.Runtime) !canvas.Affine {
    const layout = try runtime.canvasWidgetLayout(1, canvas_label);
    for (layout.nodes) |node| {
        if (node.widget.kind == .panel) return node.widget.transform;
    }
    return error.MissingPanel;
}

test "view-mapped transforms rebuild and invalidate per tick" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(SlideApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = SlideApp.init(std.heap.page_allocator, .{}, slideOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    // Tick 0 authors translate(0, 0): the identity default, so the retained
    // tree starts untransformed.
    try std.testing.expectEqualDeep(canvas.Affine.identity(), try retainedPanelTransform(&harness.runtime));

    // Each fired tick maps model state into a fresh view transform: the
    // rebuilt tree carries it and the dirty machinery schedules a repaint.
    try harness.runtime.startTimer(slide_timer_id, 16_000_000, true);
    harness.runtime.invalidated = false;
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(slide_timer_id, 2_000_000).?);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.tick);
    try std.testing.expectEqualDeep(canvas.Affine.translate(8, 0), try retainedPanelTransform(&harness.runtime));
    try std.testing.expect(harness.runtime.invalidated);

    harness.runtime.invalidated = false;
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(slide_timer_id, 18_000_000).?);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.tick);
    try std.testing.expectEqualDeep(canvas.Affine.translate(16, 0), try retainedPanelTransform(&harness.runtime));
    try std.testing.expect(harness.runtime.invalidated);
}

// ------------------------------------------------------------------ hooks

const ThemedModel = struct {
    count: u32 = 0,
    dark: bool = false,
    high_contrast: bool = false,
    frame_reports: u32 = 0,
    slider_value: f32 = 0.5,
    /// How many times `on_change` reached update — the pointer test
    /// counts one dispatch per gesture step (down, each drag move).
    slider_changes: u32 = 0,
};

const ThemedMsg = union(enum) {
    increment,
    appearance: struct { dark: bool, high_contrast: bool },
    frame_seen,
    slider_changed,
};

const ThemedApp = ui_app_model.UiApp(ThemedModel, ThemedMsg);

const themed_light_background = canvas.Color.rgb8(240, 244, 250);
const themed_dark_background = canvas.Color.rgb8(18, 22, 30);
const themed_chrome_background_id: canvas.ObjectId = 1;
const themed_chrome_footer_id: canvas.ObjectId = 2;

fn themedUpdate(model: *ThemedModel, msg: ThemedMsg) void {
    switch (msg) {
        .increment => model.count += 1,
        .appearance => |appearance| {
            model.dark = appearance.dark;
            model.high_contrast = appearance.high_contrast;
        },
        .frame_seen => model.frame_reports += 1,
        // The value itself arrives through the `sync` hook before this
        // Msg is applied (the runtime-owned slider contract); update
        // only counts the dispatch.
        .slider_changed => model.slider_changes += 1,
    }
}

fn themedView(ui: *ThemedApp.Ui, model: *const ThemedModel) ThemedApp.Ui.Node {
    return ui.column(.{ .gap = 8, .padding = 12, .style_tokens = .{ .background = .background } }, .{
        ui.text(.{}, ui.fmt("Count {d}", .{model.count})),
        ui.button(.{ .variant = .primary, .on_press = .increment }, "Add"),
        ui.el(.slider, .{ .value = model.slider_value, .on_change = .slider_changed, .semantics = .{ .label = "Level" } }, .{}),
    });
}

fn themedTokens(model: *const ThemedModel) canvas.DesignTokens {
    var tokens = canvas.DesignTokens{};
    tokens.colors.background = if (model.dark) themed_dark_background else themed_light_background;
    tokens.pixel_snap = .{ .geometry = true, .text = true };
    return tokens;
}

fn themedChrome(model: *const ThemedModel, builder: *canvas.Builder, size: geometry.SizeF, tokens: canvas.DesignTokens) anyerror!void {
    _ = model;
    // One prefix command (backdrop) and one suffix command (footer rule).
    try builder.fillRect(.{
        .id = themed_chrome_background_id,
        .rect = geometry.RectF.init(0, 0, size.width, size.height),
        .fill = .{ .color = tokens.colors.background },
    });
    try builder.fillRect(.{
        .id = themed_chrome_footer_id,
        .rect = geometry.RectF.init(0, size.height - 1, size.width, 1),
        .fill = .{ .color = tokens.colors.text },
    });
}

fn themedAnimations(model: *const ThemedModel, tree: *const ThemedApp.Ui.Tree, start_ns: u64, out: []canvas.CanvasRenderAnimation) usize {
    _ = model;
    const button_id = findIn(tree.root, .button, "Add") orelse return 0;
    if (out.len < 1) return 0;
    out[0] = .{
        .id = canvas.widgetCommandPartId(.{ .widget_id = button_id, .slot = 1 }),
        .start_ns = start_ns,
        .duration_ms = 400,
        .from_opacity = 0.6,
        .to_opacity = 1,
    };
    return 1;
}

fn themedAppearance(appearance: core.Appearance) ?ThemedMsg {
    return ThemedMsg{ .appearance = .{
        .dark = appearance.color_scheme == .dark,
        .high_contrast = appearance.high_contrast,
    } };
}

fn themedFrame(model: *const ThemedModel, frame: @import("../platform/root.zig").GpuFrame) ?ThemedMsg {
    if (model.frame_reports > 0) return null;
    if (frame.canvas_command_count == 0) return null;
    return .frame_seen;
}

fn themedSync(model: *ThemedModel, layout: canvas.WidgetLayoutTree) void {
    for (layout.nodes) |node| {
        if (node.widget.kind == .slider) model.slider_value = node.widget.value;
    }
}

fn themedOptions() ThemedApp.Options {
    return .{
        .name = "ui-app-themed",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = themedUpdate,
        .view = themedView,
        .tokens_fn = themedTokens,
        .chrome = .{ .prefix_commands = 1, .suffix_commands = 1, .build = themedChrome },
        .animations = themedAnimations,
        .on_appearance = themedAppearance,
        .on_frame = themedFrame,
        .sync = themedSync,
    };
}

fn expectChromeFillRect(display_list: canvas.DisplayList, id: canvas.ObjectId, expected_rect: geometry.RectF, expected_color: canvas.Color) !void {
    const command_ref = display_list.findCommandById(id) orelse return error.MissingChromeCommand;
    switch (command_ref.command) {
        .fill_rect => |fill| {
            try std.testing.expectApproxEqAbs(expected_rect.width, fill.rect.width, 0.001);
            try std.testing.expectApproxEqAbs(expected_rect.height, fill.rect.height, 0.001);
            switch (fill.fill) {
                .color => |actual| try std.testing.expectEqualDeep(expected_color, actual),
                else => return error.UnexpectedChromeCommand,
            }
        },
        else => return error.UnexpectedChromeCommand,
    }
}

test "ui app hooks drive chrome, dynamic tokens, animations, and frame reports" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(ThemedApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ThemedApp.init(std.heap.page_allocator, .{}, themedOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    // Install: the chrome prefix and suffix wrap the widget commands, the
    // model-derived tokens are stored (with the surface scale stamped into
    // pixel snapping), and the animation hook is applied with the install
    // frame timestamp.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    var display_list = try harness.runtime.canvasDisplayList(1, canvas_label);
    try std.testing.expect(display_list.commandCount() > 2);
    try expectChromeFillRect(display_list, themed_chrome_background_id, geometry.RectF.init(0, 0, 400, 300), themed_light_background);
    try std.testing.expect(display_list.findCommandById(themed_chrome_footer_id) != null);
    // The chrome prefix stays first and the suffix stays last around the
    // regenerated widget commands.
    try std.testing.expectEqual(themed_chrome_background_id, display_list.commands[0].fill_rect.id);
    try std.testing.expectEqual(themed_chrome_footer_id, display_list.commands[display_list.commands.len - 1].fill_rect.id);

    const stored_tokens = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    try std.testing.expectEqualDeep(themed_light_background, stored_tokens.colors.background);
    try std.testing.expectEqual(@as(f32, 2), stored_tokens.pixel_snap.scale);

    // The root's style token ref resolved against the model-derived tokens.
    try std.testing.expectEqualDeep(themed_light_background, app_state.tree.?.root.style.background.?);

    const animations = try harness.runtime.canvasRenderAnimations(1, canvas_label);
    try std.testing.expectEqual(@as(usize, 1), animations.len);
    try std.testing.expectEqual(@as(u64, 1_000_000_000), animations[0].start_ns);
    const button_id = findIn(app_state.tree.?.root, .button, "Add").?;
    try std.testing.expectEqual(canvas.widgetCommandPartId(.{ .widget_id = button_id, .slot = 1 }), animations[0].id);

    // A dispatch-driven rebuild keeps the chrome and updates the widgets.
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, button_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 1"));
    display_list = try harness.runtime.canvasDisplayList(1, canvas_label);
    try expectChromeFillRect(display_list, themed_chrome_background_id, geometry.RectF.init(0, 0, 400, 300), themed_light_background);

    // Runtime-owned slider state syncs back into the model before update.
    const slider_id = blk: {
        const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
        for (layout.nodes) |node| {
            if (node.widget.kind == .slider) break :blk node.widget.id;
        }
        return error.TestUnexpectedResult;
    };
    const increment = try std.fmt.bufPrint(&command_buffer, "widget-action {s} {d} increment", .{ canvas_label, slider_id });
    try harness.runtime.dispatchAutomationCommand(app, increment);
    try std.testing.expect(app_state.model.slider_value > 0.5);

    // Appearance changes map into messages; the model-owned scheme drives
    // new tokens and a chrome rebuild.
    try harness.runtime.dispatchPlatformEvent(app, .{ .appearance_changed = .{ .color_scheme = .dark, .high_contrast = true } });
    try std.testing.expect(app_state.model.dark);
    try std.testing.expect(app_state.model.high_contrast);
    const dark_tokens = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    try std.testing.expectEqualDeep(themed_dark_background, dark_tokens.colors.background);
    // Style token refs re-resolve on the retheme rebuild: the same widget
    // now carries the dark token's concrete color.
    try std.testing.expectEqualDeep(themed_dark_background, app_state.tree.?.root.style.background.?);
    display_list = try harness.runtime.canvasDisplayList(1, canvas_label);
    try expectChromeFillRect(display_list, themed_chrome_background_id, geometry.RectF.init(0, 0, 400, 300), themed_dark_background);

    // Resizes rebuild the chrome at the new surface size.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_resized = .{
        .window_id = 1,
        .label = canvas_label,
        .frame = geometry.RectF.init(0, 0, 640, 480),
        .scale_factor = 2,
    } });
    display_list = try harness.runtime.canvasDisplayList(1, canvas_label);
    try expectChromeFillRect(display_list, themed_chrome_background_id, geometry.RectF.init(0, 0, 640, 480), themed_dark_background);

    // Non-installing frames report presented gpu frames through on_frame.
    try std.testing.expectEqual(@as(u32, 0), app_state.model.frame_reports);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(640, 480),
        .scale_factor = 2,
        .frame_index = 2,
        .timestamp_ns = 1_016_000_000,
        .nonblank = true,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.frame_reports);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(640, 480),
        .scale_factor = 2,
        .frame_index = 3,
        .timestamp_ns = 1_032_000_000,
        .nonblank = true,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.frame_reports);
}

test "slider pointer gestures dispatch on_change: rail click commits, drag scrubs" {
    // Regression: the pointer path applied slider values as the visual
    // echo but never dispatched the app's `on_change` — a rail click
    // moved the thumb on screen while the model heard nothing, so a
    // model-driven slider (a transport scrubber) snapped back on its
    // next source rebuild. Pointer changes now drain into
    // `canvas_widget_change` events; this pins the full pipeline:
    // platform pointer input -> runtime echo -> change drain -> the
    // slider's Msg -> `sync` reading the applied value into the model.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(ThemedApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ThemedApp.init(std.heap.page_allocator, .{}, themedOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // The slider's laid-out rail, straight from the runtime's layout.
    const rail = blk: {
        const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
        for (layout.nodes) |node| {
            if (node.widget.kind == .slider) break :blk node.frame.normalized();
        }
        return error.TestUnexpectedResult;
    };
    const rail_y = rail.y + rail.height / 2;

    // Rail click: pressing at 3/4 along the rail (nowhere near the
    // thumb, which sits at the initial 0.5) commits the proportional
    // value in ONE dispatch — the standard native scrubber jump.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .x = rail.x + rail.width * 0.75,
        .y = rail_y,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.slider_changes);
    try std.testing.expectApproxEqAbs(@as(f32, 0.75), app_state.model.slider_value, 0.01);

    // Click-then-drag scrubs continuously: the same gesture keeps
    // dispatching as the pointer moves, and the model follows live.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_drag,
        .x = rail.x + rail.width * 0.25,
        .y = rail_y,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.model.slider_changes);
    try std.testing.expectApproxEqAbs(@as(f32, 0.25), app_state.model.slider_value, 0.01);

    // Releasing where the drag ended applies no new value: one gesture
    // never double-commits its final position.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_up,
        .x = rail.x + rail.width * 0.25,
        .y = rail_y,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.model.slider_changes);
    try std.testing.expectApproxEqAbs(@as(f32, 0.25), app_state.model.slider_value, 0.01);

    // Keyboard steps are UNCHANGED: they dispatch through the keyboard
    // path exactly once — the pointer-change drain must not add a
    // second delivery for the same step.
    const slider_id = blk: {
        const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
        for (layout.nodes) |node| {
            if (node.widget.kind == .slider) break :blk node.widget.id;
        }
        return error.TestUnexpectedResult;
    };
    var command_buffer: [96]u8 = undefined;
    const increment = try std.fmt.bufPrint(&command_buffer, "widget-action {s} {d} increment", .{ canvas_label, slider_id });
    try harness.runtime.dispatchAutomationCommand(app, increment);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.slider_changes);
    try std.testing.expectApproxEqAbs(@as(f32, 0.30), app_state.model.slider_value, 0.01);
}

test "unthemed apps follow the system appearance live; explicit tokens opt out" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    // The counter sets neither `tokens` nor `tokens_fn`: the stock theme
    // follows the system appearance.
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, counterOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    // An appearance delivered BEFORE the install (the platform emits one
    // right after start) makes the very first build dark.
    try harness.runtime.dispatchPlatformEvent(app, .{ .appearance_changed = .{ .color_scheme = .dark } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    const dark = canvas.DesignTokens.theme(.{ .color_scheme = .dark });
    var stored = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    try std.testing.expectEqualDeep(dark.colors.background, stored.colors.background);
    try std.testing.expectEqual(@as(f32, 2), stored.pixel_snap.scale);

    // Flipping the OS setting re-themes the RUNNING app: no restart, no
    // app wiring.
    try harness.runtime.dispatchPlatformEvent(app, .{ .appearance_changed = .{ .color_scheme = .light } });
    const light = canvas.DesignTokens.theme(.{ .color_scheme = .light });
    stored = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    try std.testing.expectEqualDeep(light.colors.background, stored.colors.background);

    // Reduce-motion and high-contrast ride the same channel.
    try harness.runtime.dispatchPlatformEvent(app, .{ .appearance_changed = .{ .color_scheme = .dark, .high_contrast = true, .reduce_motion = true } });
    stored = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    const dark_hc = canvas.DesignTokens.theme(.{ .color_scheme = .dark, .contrast = .high, .reduce_motion = true });
    try std.testing.expectEqualDeep(dark_hc.colors.background, stored.colors.background);
    try std.testing.expectEqualDeep(dark_hc.colors.focus_ring, stored.colors.focus_ring);
    try std.testing.expectEqual(@as(u32, 0), stored.motion.normal_ms);

    // Explicit static tokens OPT OUT: the app owns its look and an
    // appearance flip never restyles it.
    const fixed_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(fixed_state);
    var fixed_options = counterOptions();
    fixed_options.name = "ui-app-counter-fixed";
    fixed_options.tokens = canvas.DesignTokens.theme(.{ .color_scheme = .light });
    fixed_state.* = CounterApp.init(std.heap.page_allocator, .{}, fixed_options);
    defer fixed_state.deinit();
    try std.testing.expectEqualDeep(light.colors.background, fixed_state.effectiveTokens().colors.background);
    fixed_state.system_appearance = .{ .color_scheme = .dark };
    try std.testing.expectEqualDeep(light.colors.background, fixed_state.effectiveTokens().colors.background);
}

test "model-derived theme packs retain live appearance, accent, and surface scale" {
    var options = counterOptions();
    options.theme = .geist; // The model-derived pack wins this fallback.
    options.theme_fn = counterThemePack;
    options.theme_accent = canvas.Color.rgb8(0xdf, 0x26, 0x70);

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    app_state.system_appearance = .{ .color_scheme = .dark, .reduce_motion = true };
    app_state.pixel_snap_scale = 2;

    var house = canvas.DesignTokens.theme(.{
        .color_scheme = .dark,
        .reduce_motion = true,
        .pack = .house,
    });
    house = house.withOverrides(canvas.accentOverrides(options.theme_accent.?, .dark));
    var actual = app_state.effectiveTokens();
    try std.testing.expectEqualDeep(house.colors.background, actual.colors.background);
    try std.testing.expectEqualDeep(house.colors.accent, actual.colors.accent);
    try std.testing.expectEqualDeep(house.colors.focus_ring, actual.colors.focus_ring);
    try std.testing.expectEqual(house.metrics.control_height, actual.metrics.control_height);
    try std.testing.expectEqual(@as(u32, 0), actual.motion.normal_ms);
    try std.testing.expectEqual(@as(f32, 2), actual.pixel_snap.scale);

    app_state.model.count = 1;
    var geist = canvas.DesignTokens.theme(.{
        .color_scheme = .dark,
        .reduce_motion = true,
        .pack = .geist,
    });
    geist = geist.withOverrides(canvas.accentOverrides(options.theme_accent.?, .dark));
    actual = app_state.effectiveTokens();
    try std.testing.expectEqualDeep(geist.colors.background, actual.colors.background);
    try std.testing.expectEqualDeep(geist.colors.accent, actual.colors.accent);
    try std.testing.expectEqualDeep(geist.colors.focus_ring, actual.colors.focus_ring);
    try std.testing.expectEqual(geist.metrics.control_height, actual.metrics.control_height);
    try std.testing.expect(house.metrics.control_height != geist.metrics.control_height);
    try std.testing.expectEqual(@as(u32, 0), actual.motion.normal_ms);
    try std.testing.expectEqual(@as(f32, 2), actual.pixel_snap.scale);
}

test "model-derived theme state composes forced/system schemes and model accent over manifest defaults" {
    var options = counterOptions();
    options.theme = .house;
    options.theme_accent = canvas.Color.rgb8(0x12, 0x34, 0x56);
    options.theme_state_fn = counterThemeState;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    app_state.system_appearance = .{ .color_scheme = .light };
    app_state.pixel_snap_scale = 2;

    // Model-forced dark wins over an OS-light appearance; omitted pack and
    // accent inherit the manifest-backed options.
    var expected = canvas.DesignTokens.theme(.{ .color_scheme = .dark, .pack = .house });
    expected = expected.withOverrides(canvas.accentOverrides(options.theme_accent.?, .dark));
    var actual = app_state.effectiveTokens();
    try std.testing.expectEqualDeep(expected.colors.background, actual.colors.background);
    try std.testing.expectEqualDeep(expected.colors.accent, actual.colors.accent);
    try std.testing.expectEqual(@as(f32, 2), actual.pixel_snap.scale);

    // `system` resumes live OS following while model pack/accent override the
    // manifest values.
    app_state.model.count = 1;
    app_state.theme_state_known = false;
    app_state.system_appearance = .{ .color_scheme = .dark };
    const teal = canvas.Color.rgb8(0, 0x78, 0x6f);
    expected = canvas.DesignTokens.theme(.{ .color_scheme = .dark, .pack = .geist });
    expected = expected.withOverrides(canvas.accentOverrides(teal, .dark));
    actual = app_state.effectiveTokens();
    try std.testing.expectEqualDeep(expected.colors.background, actual.colors.background);
    try std.testing.expectEqualDeep(teal, actual.colors.accent);
    try std.testing.expectEqualDeep(canvas.accentFocusRing(teal, .dark), actual.colors.focus_ring);

    // High contrast keeps the existing accessibility rule: neither the
    // model nor manifest accent layers over the pack's loud register.
    app_state.system_appearance.high_contrast = true;
    actual = app_state.effectiveTokens();
    const loud = canvas.DesignTokens.theme(.{ .color_scheme = .dark, .contrast = .high, .pack = .geist });
    try std.testing.expectEqualDeep(loud.colors.accent, actual.colors.accent);
    try std.testing.expect(!std.meta.eql(teal, actual.colors.accent));
}

test "malformed adapter theme accents reject the rebuild instead of inheriting silently" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var options = counterOptions();
    options.theme_state_fn = invalidCounterThemeState;
    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try std.testing.expectError(error.InvalidThemeAccent, harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } }));
    try std.testing.expect(!app_state.installed);
}

test "forced theme state still follows accessibility axes while scheme-only OS flips do not restyle" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    var options = counterOptions();
    options.theme_state_fn = counterThemeState; // count 0 forces dark.
    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .appearance_changed = .{ .color_scheme = .light } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    var stored = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    const dark = canvas.DesignTokens.theme(.{ .color_scheme = .dark });
    try std.testing.expectEqualDeep(dark.colors.background, stored.colors.background);

    // A scheme-only flip cannot override the forced state.
    try harness.runtime.dispatchPlatformEvent(app, .{ .appearance_changed = .{ .color_scheme = .dark } });
    stored = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    try std.testing.expectEqualDeep(dark.colors.background, stored.colors.background);

    // Accessibility axes remain live even under a forced color scheme.
    try harness.runtime.dispatchPlatformEvent(app, .{ .appearance_changed = .{
        .color_scheme = .light,
        .high_contrast = true,
        .reduce_motion = true,
    } });
    stored = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    const dark_loud = canvas.DesignTokens.theme(.{ .color_scheme = .dark, .contrast = .high, .reduce_motion = true });
    try std.testing.expectEqualDeep(dark_loud.colors.background, stored.colors.background);
    try std.testing.expectEqualDeep(dark_loud.colors.focus_ring, stored.colors.focus_ring);
    try std.testing.expectEqual(@as(u32, 0), stored.motion.normal_ms);
}

test "static tokens carry the surface scale and re-snap on a scale change" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    // The app pins its look with static tokens (geometry snapping on,
    // scale 1 inside — the app never knows the monitor).
    var options = counterOptions();
    var static_tokens = canvas.DesignTokens.theme(.{ .color_scheme = .light });
    static_tokens.pixel_snap = .{ .geometry = true, .text = true };
    options.tokens = static_tokens;
    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    // Install on a 2x surface: the effective tokens are a stamped COPY —
    // the app owns the appearance, the runtime owns the device scale —
    // and the stored tokens carry the real density, so hairlines snap
    // against the physical grid instead of scale 1.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expectEqual(@as(f32, 2), app_state.effectiveTokens().pixel_snap.scale);
    var stored = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    try std.testing.expectEqual(@as(f32, 2), stored.pixel_snap.scale);
    try std.testing.expectEqualDeep(static_tokens.colors.background, stored.colors.background);

    // A frame at a new density (the window dragged to a 1x monitor)
    // rebuilds and re-emits for a static-token app too: the stored
    // tokens re-snap to the new grid without any app involvement. The
    // model is poked directly (no dispatch) so the refreshed retained
    // text proves the FRAME triggered the rebuild.
    app_state.model.count = 5;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 2,
        .timestamp_ns = 2_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 5"));
    try std.testing.expectEqual(@as(f32, 1), app_state.effectiveTokens().pixel_snap.scale);
    stored = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    try std.testing.expectEqual(@as(f32, 1), stored.pixel_snap.scale);
    try std.testing.expectEqualDeep(static_tokens.colors.background, stored.colors.background);
}

test "a resize carrying a new density re-stamps and re-emits at the unchanged logical size" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    // Static tokens make this the strict case: ordinary rebuilds skip
    // the redundant emission, so a re-stamped stored copy below proves
    // the stale-scale re-emit fired.
    var options = counterOptions();
    var static_tokens = canvas.DesignTokens.theme(.{ .color_scheme = .light });
    static_tokens.pixel_snap = .{ .geometry = true, .text = true };
    options.tokens = static_tokens;
    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    var stored = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    try std.testing.expectEqual(@as(f32, 2), stored.pixel_snap.scale);

    // A DPI-only monitor move arrives as a resize whose LOGICAL size is
    // unchanged — only the event's scale differs. The resize path must
    // adopt the density before rebuilding, and the rebuild must re-emit
    // even though the layout inputs are identical: without both, the
    // stored tokens keep snapping against the old grid until the next
    // input. The model is poked directly (no dispatch) so the refreshed
    // retained text proves the RESIZE triggered the rebuild.
    app_state.model.count = 7;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_resized = .{
        .window_id = 1,
        .label = canvas_label,
        .frame = geometry.RectF.init(0, 0, 400, 300),
        .scale_factor = 1,
    } });
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 7"));
    try std.testing.expectEqual(@as(f32, 1), app_state.effectiveTokens().pixel_snap.scale);
    stored = try harness.runtime.canvasWidgetDesignTokens(1, canvas_label);
    try std.testing.expectEqual(@as(f32, 1), stored.pixel_snap.scale);
    try std.testing.expectEqualDeep(static_tokens.colors.background, stored.colors.background);
}

test "markup watch polls from the reserved runtime timer" {
    const io = std.testing.io;
    const watch_path = ".zig-cache/ui-app-markup-watch-test.native";
    const cwd = std.Io.Dir.cwd();
    try cwd.writeFile(io, .{ .sub_path = watch_path, .data = counter_markup });
    defer cwd.deleteFile(io, watch_path) catch {};

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    var options = markupCounterOptions();
    options.markup = .{ .source = counter_markup, .watch_path = watch_path, .io = io };
    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // Install started the reserved repeating watch timer.
    const watch_timer = harness.null_platform.startedTimer(CounterApp.markup_watch_timer_id).?;
    try std.testing.expect(watch_timer.active);
    try std.testing.expect(watch_timer.repeats);
    try std.testing.expectEqual(@as(u64, 500_000_000), watch_timer.interval_ns);

    // An idle poll (file unchanged) issues no frame-chain keepalive request.
    const frame_requests_after_install = harness.null_platform.gpu_surface_frame_request_count;
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 1_500_000).?);
    try std.testing.expectEqual(frame_requests_after_install, harness.null_platform.gpu_surface_frame_request_count);

    // Advance model state, then hot swap the file: the timer poll reloads
    // the markup, rebuilds, and keeps model state.
    const increment_id = findWidgetIdByText(app_state.tree.?, .button, "Increment").?;
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, increment_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);

    try cwd.writeFile(io, .{ .sub_path = watch_path, .data = counter_markup_v2 });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_000_000).?);

    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Start over") != null);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 1"));
}

const watch_import_root_markup =
    \\<import src="ui-app-markup-watch-parts.native"/>
    \\<column gap="8" padding="12">
    \\  <text>Count {count}</text>
    \\  <use template="actions" />
    \\</column>
;

const watch_import_parts_markup =
    \\<template name="actions">
    \\  <column gap="4">
    \\    <button variant="primary" on-press="increment">Increment</button>
    \\    <button on-press="reset">Reset</button>
    \\  </column>
    \\</template>
;

const watch_import_parts_markup_v2 =
    \\<template name="actions">
    \\  <column gap="4">
    \\    <button variant="primary" on-press="increment">Increment</button>
    \\    <button on-press="reset">Start over</button>
    \\  </column>
    \\</template>
;

test "the markup watch reloads when an IMPORTED file changes on disk" {
    const io = std.testing.io;
    const watch_path = ".zig-cache/ui-app-markup-watch-import-test.native";
    const parts_path = ".zig-cache/ui-app-markup-watch-parts.native";
    const cwd = std.Io.Dir.cwd();
    try cwd.writeFile(io, .{ .sub_path = watch_path, .data = watch_import_root_markup });
    defer cwd.deleteFile(io, watch_path) catch {};
    try cwd.writeFile(io, .{ .sub_path = parts_path, .data = watch_import_parts_markup });
    defer cwd.deleteFile(io, parts_path) catch {};

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    // The embedded source set mirrors the on-disk closure (paths relative
    // to the root file's directory), so the first build and the disk poll
    // agree until something actually changes.
    var options = markupCounterOptions();
    options.markup = .{
        .source = watch_import_root_markup,
        .sources = &.{
            .{ .path = "ui-app-markup-watch-parts.native", .source = watch_import_parts_markup },
        },
        .watch_path = watch_path,
        .io = io,
    };
    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    // The imported template built into the view.
    const increment_id = findWidgetIdByText(app_state.tree.?, .button, "Increment").?;

    // An idle poll (closure unchanged) swaps nothing.
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 1_500_000).?);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Reset") != null);

    // Advance model state, then edit only the IMPORTED file: the poll
    // covers the whole closure, so the change reloads and rebuilds while
    // model state and ids hold.
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, increment_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);

    try cwd.writeFile(io, .{ .sub_path = parts_path, .data = watch_import_parts_markup_v2 });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_000_000).?);

    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Start over") != null);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try std.testing.expectEqual(increment_id, findWidgetIdByText(app_state.tree.?, .button, "Increment").?);

    // A broken edit to the imported file keeps the last good view and
    // records a diagnostic naming the imported file.
    try cwd.writeFile(io, .{ .sub_path = parts_path, .data = "<template name=\"actions\"><column><oops</column></template>" });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_500_000).?);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Start over") != null);
    try std.testing.expect(app_state.markup_diagnostic != null);
    try std.testing.expect(std.mem.indexOf(u8, app_state.markup_diagnostic.?.path, "ui-app-markup-watch-parts.native") != null);
}

const watch_import_sources = [_]canvas.ui_markup.SourceFile{
    .{ .path = "ui-app-markup-watch-import-baseline.native", .source = watch_import_root_markup },
    .{ .path = "ui-app-markup-watch-parts.native", .source = watch_import_parts_markup },
};

const CompiledImportCounterView = canvas.CompiledMarkupImports(
    CounterModel,
    CounterMsg,
    "ui-app-markup-watch-import-baseline.native",
    &watch_import_sources,
);

test "with imports the compiled view stays the baseline until the watched closure changes" {
    const io = std.testing.io;
    const watch_path = ".zig-cache/ui-app-markup-watch-import-baseline.native";
    const parts_path = ".zig-cache/ui-app-markup-watch-parts.native";
    const cwd = std.Io.Dir.cwd();
    try cwd.writeFile(io, .{ .sub_path = watch_path, .data = watch_import_root_markup });
    defer cwd.deleteFile(io, watch_path) catch {};
    try cwd.writeFile(io, .{ .sub_path = parts_path, .data = watch_import_parts_markup });
    defer cwd.deleteFile(io, parts_path) catch {};

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    var options = markupCounterOptions();
    options.view = CompiledImportCounterView.build;
    options.markup = .{
        .source = watch_import_root_markup,
        .sources = &watch_import_sources,
        .watch_path = watch_path,
        .io = io,
    };
    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expect(app_state.markup_view == null);

    // The disk closure matches the embedded one (the baseline hash covers
    // imports in the same root-relative path space), so an idle poll never
    // phantom-swaps to the interpreter.
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 1_500_000).?);
    try std.testing.expect(app_state.markup_view == null);

    // An edit to the IMPORTED file flips the baseline to the interpreter.
    try cwd.writeFile(io, .{ .sub_path = parts_path, .data = watch_import_parts_markup_v2 });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_000_000).?);
    try std.testing.expect(app_state.markup_view != null);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Start over") != null);
}

const CompiledCounterView = canvas.CompiledMarkupView(CounterModel, CounterMsg, counter_markup);

test "a compiled markup view drives the ui app with the runtime markup engine compiled out" {
    const LeanApp = ui_app_model.UiAppWithFeatures(CounterModel, CounterMsg, .{ .runtime_markup = false });

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(LeanApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = LeanApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-compiled-counter",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = counterUpdate,
        .view = CompiledCounterView.build,
        .on_command = counterCommand,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 0"));

    // Compiled-markup handlers dispatch through the same typed loop.
    const increment_id = findWidgetIdByText(app_state.tree.?, .button, "Increment").?;
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, increment_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 1"));
    try std.testing.expectEqual(increment_id, findWidgetIdByText(app_state.tree.?, .button, "Increment").?);

    // The runtime engine is compiled out: no watch timer, no reload path.
    try std.testing.expect(harness.null_platform.startedTimer(LeanApp.markup_watch_timer_id) == null);
    try std.testing.expectError(error.MarkupEngineDisabled, app_state.reloadMarkup(counter_markup_v2));
}

test "with view and markup both set the compiled view renders until the watched file changes" {
    const io = std.testing.io;
    const watch_path = ".zig-cache/ui-app-compiled-watch-test.native";
    const cwd = std.Io.Dir.cwd();
    try cwd.writeFile(io, .{ .sub_path = watch_path, .data = counter_markup });
    defer cwd.deleteFile(io, watch_path) catch {};

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    var options = markupCounterOptions();
    options.view = CompiledCounterView.build;
    options.markup = .{ .source = counter_markup, .watch_path = watch_path, .io = io };
    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 0"));

    // The compiled view rendered: the interpreter never parsed anything.
    try std.testing.expect(app_state.markup_view == null);

    // An idle poll (file matches the embedded source) keeps it that way.
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 1_500_000).?);
    try std.testing.expect(app_state.markup_view == null);

    // Advance model state, then edit the file: the interpreter takes over
    // with the new source, keeping model state and structural ids.
    const increment_id = findWidgetIdByText(app_state.tree.?, .button, "Increment").?;
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, increment_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);

    try cwd.writeFile(io, .{ .sub_path = watch_path, .data = counter_markup_v2 });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_000_000).?);

    try std.testing.expect(app_state.markup_view != null);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Start over") != null);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 1"));
    try std.testing.expectEqual(increment_id, findWidgetIdByText(app_state.tree.?, .button, "Increment").?);
}

// ------------------------------------------------- fragment hot reload
// A HYBRID root: a Zig builder view embedding compiled markup fragments.
// The fragment watch (Options.fragment_watch) is what keeps the edit-see
// loop alive for these apps — without it, editing a fragment's .native
// source in dev does nothing because the fragment was compiled at
// comptime.

const fragment_actions_markup =
    \\<column gap="4">
    \\  <button variant="primary" on-press="increment">Increment</button>
    \\  <button on-press="reset">Reset</button>
    \\</column>
;

const fragment_actions_markup_v2 =
    \\<column gap="4">
    \\  <button variant="primary" on-press="increment">Increment</button>
    \\  <button on-press="reset">Start over</button>
    \\</column>
;

const fragment_actions_path = ".zig-cache/ui-app-fragment-actions.native";

const FragmentActionsView = canvas.CompiledMarkupView(CounterModel, CounterMsg, fragment_actions_markup);

fn hybridCounterView(ui: *CounterApp.Ui, model: *const CounterModel) CounterApp.Ui.Node {
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.text(.{}, ui.fmt("Count {d}", .{model.count})),
        FragmentActionsView.build(ui, model),
    });
}

const hybrid_counter_fragments = [_]canvas.MarkupFragment{
    FragmentActionsView.fragment(fragment_actions_path),
};

fn hybridCounterOptions(io: std.Io) CounterApp.Options {
    var options = counterOptions();
    options.name = "ui-app-hybrid-counter";
    options.view = hybridCounterView;
    options.fragment_watch = .{ .fragments = &hybrid_counter_fragments, .io = io };
    return options;
}

/// Install the app on the harness and drive the first gpu frame (the
/// installing frame, which also arms the markup watch).
fn installCounterApp(harness: anytype, app: core.App) !void {
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
}

test "radio accessibility selection dispatches change once per retained transition" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(RadioApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = RadioApp.init(std.heap.page_allocator, .{}, radioOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try installCounterApp(harness, app);

    const pro_id = findWidgetIdByText(app_state.tree.?, .radio, "Pro").?;
    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, canvas_label, .{
        .id = pro_id,
        .action = .select,
    });
    try std.testing.expectEqual(@as(u8, 1), app_state.model.choice);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.change_count);
    try std.testing.expect((try harness.runtime.canvasWidgetLayout(1, canvas_label)).findById(pro_id).?.widget.state.selected);

    // AX selection, pointer activation, and Space all still activate the
    // radio, but an already-selected control has no new `on_change` edge.
    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, canvas_label, .{
        .id = pro_id,
        .action = .select,
    });
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, pro_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try harness.runtime.dispatchAutomationCommand(app, "widget-key counter-canvas space");
    try std.testing.expectEqual(@as(u8, 1), app_state.model.choice);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.change_count);
}

test "the fragment watch reloads a compiled fragment embedded in a Zig view" {
    const io = std.testing.io;
    const cwd = std.Io.Dir.cwd();
    try cwd.writeFile(io, .{ .sub_path = fragment_actions_path, .data = fragment_actions_markup });
    defer cwd.deleteFile(io, fragment_actions_path) catch {};

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, hybridCounterOptions(io));
    defer app_state.deinit();
    const app = app_state.app();
    try installCounterApp(harness, app);
    try std.testing.expect(app_state.installed);

    // Registered fragments arm the watch — the same reserved repeating
    // timer as the single-root watch — and the armed state is honest in
    // the automation snapshot bit even though the app has no markup root.
    try std.testing.expect(harness.null_platform.startedTimer(CounterApp.markup_watch_timer_id) != null);
    try std.testing.expect(harness.runtime.markup_watch_armed);

    // An idle poll (file matches the embedded baseline) keeps the
    // comptime-compiled path: no override document is adopted.
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 1_500_000).?);
    try std.testing.expect(app_state.markup_fragment_slots[0].document == null);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Reset") != null);

    // Advance model state, then edit the fragment's file: the poll
    // reloads THAT fragment through the interpreter and rebuilds while
    // model state and structural ids hold.
    const increment_id = findWidgetIdByText(app_state.tree.?, .button, "Increment").?;
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, increment_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);

    try cwd.writeFile(io, .{ .sub_path = fragment_actions_path, .data = fragment_actions_markup_v2 });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_000_000).?);
    try std.testing.expect(app_state.markup_fragment_slots[0].document != null);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Start over") != null);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Count 1"));
    try std.testing.expectEqual(increment_id, findWidgetIdByText(app_state.tree.?, .button, "Increment").?);

    // Reverting the file byte for byte drops the override: back to the
    // comptime-compiled path, the release-identical one.
    try cwd.writeFile(io, .{ .sub_path = fragment_actions_path, .data = fragment_actions_markup });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_500_000).?);
    try std.testing.expect(app_state.markup_fragment_slots[0].document == null);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Reset") != null);
}

test "a broken fragment save keeps the last good view and the next good save recovers" {
    const io = std.testing.io;
    const cwd = std.Io.Dir.cwd();
    try cwd.writeFile(io, .{ .sub_path = fragment_actions_path, .data = fragment_actions_markup });
    defer cwd.deleteFile(io, fragment_actions_path) catch {};

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, hybridCounterOptions(io));
    defer app_state.deinit();
    const app = app_state.app();
    try installCounterApp(harness, app);
    try std.testing.expect(app_state.installed);

    // A save that does not parse: the fragment keeps its last good view,
    // the app keeps running, and the teaching diagnostic names the file.
    try cwd.writeFile(io, .{ .sub_path = fragment_actions_path, .data = "<column><oops</column>" });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 1_500_000).?);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Reset") != null);
    try std.testing.expect(app_state.markup_diagnostic != null);
    try std.testing.expect(std.mem.indexOf(u8, app_state.markup_diagnostic.?.path, "ui-app-fragment-actions.native") != null);

    // A save that parses but cannot build against the Model (a binding
    // naming no model field — what the compiled engine rejects at
    // comptime): the frame aborts, the last good tree stays up, and the
    // diagnostic reports through the same channel.
    try cwd.writeFile(io, .{ .sub_path = fragment_actions_path, .data = "<column><text>{missing_field}</text></column>" });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_000_000).?);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Reset") != null);
    try std.testing.expect(app_state.markup_diagnostic != null);

    // The next good save recovers in place.
    try cwd.writeFile(io, .{ .sub_path = fragment_actions_path, .data = fragment_actions_markup_v2 });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_500_000).?);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .button, "Start over") != null);
    try std.testing.expect(app_state.markup_diagnostic == null);
}

// Two fragments importing ONE shared component file: the import-closure
// propagation rule is that editing the shared file reloads every fragment
// whose closure reaches it, in the same poll.

const fragment_badge_a_markup =
    \\<import src="ui-app-fragment-parts.native"/>
    \\<row gap="4">
    \\  <text>Badge A</text>
    \\  <use template="chip" />
    \\</row>
;

const fragment_badge_b_markup =
    \\<import src="ui-app-fragment-parts.native"/>
    \\<row gap="4">
    \\  <text>Badge B</text>
    \\  <use template="chip" />
    \\</row>
;

const fragment_parts_markup =
    \\<template name="chip">
    \\  <text>Alpha</text>
    \\</template>
;

const fragment_parts_markup_v2 =
    \\<template name="chip">
    \\  <text>Beta</text>
    \\</template>
;

const fragment_badge_a_path = ".zig-cache/ui-app-fragment-badge-a.native";
const fragment_badge_b_path = ".zig-cache/ui-app-fragment-badge-b.native";
const fragment_parts_path = ".zig-cache/ui-app-fragment-parts.native";

const fragment_badge_a_sources = [_]canvas.ui_markup.SourceFile{
    .{ .path = "ui-app-fragment-badge-a.native", .source = fragment_badge_a_markup },
    .{ .path = "ui-app-fragment-parts.native", .source = fragment_parts_markup },
};
const fragment_badge_b_sources = [_]canvas.ui_markup.SourceFile{
    .{ .path = "ui-app-fragment-badge-b.native", .source = fragment_badge_b_markup },
    .{ .path = "ui-app-fragment-parts.native", .source = fragment_parts_markup },
};

const FragmentBadgeAView = canvas.CompiledMarkupImports(CounterModel, CounterMsg, "ui-app-fragment-badge-a.native", &fragment_badge_a_sources);
const FragmentBadgeBView = canvas.CompiledMarkupImports(CounterModel, CounterMsg, "ui-app-fragment-badge-b.native", &fragment_badge_b_sources);

fn badgesCounterView(ui: *CounterApp.Ui, model: *const CounterModel) CounterApp.Ui.Node {
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        FragmentBadgeAView.build(ui, model),
        FragmentBadgeBView.build(ui, model),
    });
}

const badges_counter_fragments = [_]canvas.MarkupFragment{
    FragmentBadgeAView.fragment(fragment_badge_a_path),
    FragmentBadgeBView.fragment(fragment_badge_b_path),
};

fn countTextIn(widget: canvas.Widget, text: []const u8) usize {
    var count: usize = 0;
    if (widget.kind == .text and std.mem.eql(u8, widget.text, text)) count += 1;
    for (widget.children) |child| count += countTextIn(child, text);
    return count;
}

test "editing a shared imported file reloads every fragment whose imports reach it" {
    const io = std.testing.io;
    const cwd = std.Io.Dir.cwd();
    try cwd.writeFile(io, .{ .sub_path = fragment_badge_a_path, .data = fragment_badge_a_markup });
    defer cwd.deleteFile(io, fragment_badge_a_path) catch {};
    try cwd.writeFile(io, .{ .sub_path = fragment_badge_b_path, .data = fragment_badge_b_markup });
    defer cwd.deleteFile(io, fragment_badge_b_path) catch {};
    try cwd.writeFile(io, .{ .sub_path = fragment_parts_path, .data = fragment_parts_markup });
    defer cwd.deleteFile(io, fragment_parts_path) catch {};

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    var options = counterOptions();
    options.name = "ui-app-badges-counter";
    options.view = badgesCounterView;
    options.fragment_watch = .{ .fragments = &badges_counter_fragments, .io = io };
    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, options);
    defer app_state.deinit();
    const app = app_state.app();
    try installCounterApp(harness, app);
    try std.testing.expect(app_state.installed);

    // Both compiled fragments rendered the shared template.
    try std.testing.expectEqual(@as(usize, 2), countTextIn(app_state.tree.?.root, "Alpha"));

    // An idle poll leaves both fragments compiled: the embedded baseline
    // hash covers the whole import closure, so nothing phantom-reloads.
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 1_500_000).?);
    try std.testing.expect(app_state.markup_fragment_slots[0].document == null);
    try std.testing.expect(app_state.markup_fragment_slots[1].document == null);

    // Editing only the SHARED imported file reloads BOTH fragments in
    // one poll — a file may serve several fragments, and every one of
    // its dependents rebuilds fresh.
    try cwd.writeFile(io, .{ .sub_path = fragment_parts_path, .data = fragment_parts_markup_v2 });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_000_000).?);
    try std.testing.expectEqual(@as(usize, 2), countTextIn(app_state.tree.?.root, "Beta"));
    try std.testing.expectEqual(@as(usize, 0), countTextIn(app_state.tree.?.root, "Alpha"));

    // Editing one fragment's ROOT file reloads only that fragment; the
    // other keeps its current view.
    const badge_a_v2 = try std.mem.concat(std.testing.allocator, u8, &.{ fragment_badge_a_markup, "\n" });
    defer std.testing.allocator.free(badge_a_v2);
    try cwd.writeFile(io, .{ .sub_path = fragment_badge_a_path, .data = badge_a_v2 });
    try harness.runtime.dispatchPlatformEvent(app, harness.null_platform.fireTimer(CounterApp.markup_watch_timer_id, 2_500_000).?);
    try std.testing.expect(app_state.markup_fragment_slots[0].document != null);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .text, "Badge A") != null);
    try std.testing.expect(findWidgetIdByText(app_state.tree.?, .text, "Badge B") != null);
}

test "a hybrid app with no registered fragments keeps the markup watch off" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, counterOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try installCounterApp(harness, app);
    try std.testing.expect(app_state.installed);

    // No markup root, no fragments: the watch never arms and the
    // automation snapshot bit honestly reports off.
    try std.testing.expect(harness.null_platform.startedTimer(CounterApp.markup_watch_timer_id) == null);
    try std.testing.expect(!harness.runtime.markup_watch_armed);
}

const RosterModel = struct {
    row_count: usize = 70,

    pub fn rows(model: *const RosterModel, arena: std.mem.Allocator) []const usize {
        const out = arena.alloc(usize, model.row_count) catch return &.{};
        for (out, 0..) |*slot, index| slot.* = index;
        return out[0..model.row_count];
    }
};

const RosterMsg = union(enum) { noop };
const RosterApp = ui_app_model.UiApp(RosterModel, RosterMsg);

fn rosterUpdate(model: *RosterModel, msg: RosterMsg) void {
    _ = model;
    _ = msg;
}

fn rosterKey(index: *const usize) canvas.UiKey {
    return canvas.uiKey(@as(u64, index.*));
}

fn rosterRow(ui: *RosterApp.Ui, index: *const usize) RosterApp.Ui.Node {
    return ui.row(.{ .gap = 4 }, .{
        ui.checkbox(.{ .on_toggle = .noop }),
        ui.text(.{ .grow = 1 }, ui.fmt("Row {d}", .{index.*})),
    });
}

fn rosterView(ui: *RosterApp.Ui, model: *const RosterModel) RosterApp.Ui.Node {
    return ui.column(.{ .gap = 2 }, ui.each(model.rows(ui.arena), rosterKey, rosterRow));
}

test "widget trees beyond the old 64-node cap install and reconcile" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 2000) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(RosterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = RosterApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-roster",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = rosterUpdate,
        .view = rosterView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 2000),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // 70 keyed rows x (row + checkbox + text) + root column = 211 nodes.
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    try std.testing.expect(layout.nodes.len > 64);
    try std.testing.expectEqual(@as(usize, 211), layout.nodes.len);

    // A rebuild through the reconcile path holds at that size.
    try app_state.rebuild(&harness.runtime, 1);
    try std.testing.expectEqual(@as(usize, 211), (try harness.runtime.canvasWidgetLayout(1, canvas_label)).nodes.len);
}

// ---------------------------------------------------------------- set_text

const mirror_canvas_label = "mirror-canvas";

const MirrorModel = struct {
    draft: canvas.TextBuffer(64) = .{},
    edit_count: u32 = 0,
    submit_count: u32 = 0,
};

const MirrorMsg = union(enum) {
    draft_edit: canvas.TextInputEvent,
    submit,
};

const MirrorApp = ui_app_model.UiApp(MirrorModel, MirrorMsg);

fn mirrorUpdate(model: *MirrorModel, msg: MirrorMsg) void {
    switch (msg) {
        .draft_edit => |edit| {
            model.draft.apply(edit);
            model.edit_count += 1;
        },
        .submit => model.submit_count += 1,
    }
}

fn mirrorView(ui: *MirrorApp.Ui, model: *const MirrorModel) MirrorApp.Ui.Node {
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.textField(.{
            .text = model.draft.text(),
            .placeholder = "Message",
            .on_input = MirrorApp.Ui.inputMsg(.draft_edit),
            .on_submit = .submit,
        }),
        ui.text(.{}, if (model.draft.isEmpty()) "Send disabled" else "Send enabled"),
    });
}

const mirror_views = [_]app_manifest.ShellView{
    .{ .label = mirror_canvas_label, .kind = .gpu_surface, .fill = true, .gpu_backend = .metal },
};
const mirror_windows = [_]app_manifest.ShellWindow{.{
    .label = "main",
    .title = "Mirror",
    .width = 400,
    .height = 300,
    .views = &mirror_views,
}};
const mirror_scene: app_manifest.ShellConfig = .{ .windows = &mirror_windows };

fn findWidgetIdByKind(widget: canvas.Widget, kind: canvas.WidgetKind) ?canvas.ObjectId {
    if (widget.kind == kind) return widget.id;
    for (widget.children) |child| {
        if (findWidgetIdByKind(child, kind)) |id| return id;
    }
    return null;
}

test "automation set_text routes through the input path so the elm mirror stays consistent" {
    // `widget-action <id> set_text` used to write the
    // runtime editor state directly and never dispatch `on_input`, so a
    // TEA app's model still saw an empty buffer (Send stayed disabled
    // while the field visibly held text — a state no real user can
    // produce).
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(MirrorApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = MirrorApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-mirror",
        .scene = mirror_scene,
        .canvas_label = mirror_canvas_label,
        .update = mirrorUpdate,
        .view = mirrorView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = mirror_canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const field_id = findWidgetIdByKind(app_state.tree.?.root, .text_field).?;

    // set_text lands in the runtime editor AND the model mirror.
    try core.testing.dispatchAutomationWidgetAction(&harness.runtime, app, .{
        .view_label = mirror_canvas_label,
        .id = field_id,
        .action = .set_text,
        .value = "ship the fix",
    });
    try std.testing.expectEqualStrings("ship the fix", app_state.model.draft.text());
    try std.testing.expect(app_state.model.edit_count > 0);
    const layout = try harness.runtime.canvasWidgetLayout(1, mirror_canvas_label);
    try std.testing.expectEqualStrings("ship the fix", layout.findById(field_id).?.widget.text);

    // The dependent view state follows the model, not just the editor.
    var found_enabled = false;
    for (layout.nodes) |node| {
        if (node.widget.kind == .text and std.mem.eql(u8, node.widget.text, "Send enabled")) found_enabled = true;
    }
    try std.testing.expect(found_enabled);

    // Replacing existing text keeps model and editor in lockstep.
    try core.testing.dispatchAutomationWidgetAction(&harness.runtime, app, .{
        .view_label = mirror_canvas_label,
        .id = field_id,
        .action = .set_text,
        .value = "second draft",
    });
    try std.testing.expectEqualStrings("second draft", app_state.model.draft.text());
    const replaced_layout = try harness.runtime.canvasWidgetLayout(1, mirror_canvas_label);
    try std.testing.expectEqualStrings("second draft", replaced_layout.findById(field_id).?.widget.text);

    // Clearing through set_text "" also flows through the input path.
    try core.testing.dispatchAutomationWidgetAction(&harness.runtime, app, .{
        .view_label = mirror_canvas_label,
        .id = field_id,
        .action = .set_text,
        .value = "",
    });
    try std.testing.expectEqualStrings("", app_state.model.draft.text());
    const cleared_layout = try harness.runtime.canvasWidgetLayout(1, mirror_canvas_label);
    try std.testing.expectEqualStrings("", cleared_layout.findById(field_id).?.widget.text);

    // The automation snapshot agrees with both.
    const snapshot = harness.runtime.automationSnapshot("Mirror");
    for (snapshot.widgets) |widget| {
        if (widget.id == field_id) try std.testing.expectEqualStrings("", widget.text_value);
    }
}

// -------------------------------------------- edit-derivation choke point

const search_mirror_canvas_label = "search-mirror-canvas";

const SearchMirrorModel = struct {
    query: canvas.TextBuffer(64) = .{},
    edit_count: u32 = 0,
};

const SearchMirrorMsg = union(enum) {
    query_edit: canvas.TextInputEvent,
};

const SearchMirrorApp = ui_app_model.UiApp(SearchMirrorModel, SearchMirrorMsg);

fn searchMirrorUpdate(model: *SearchMirrorModel, msg: SearchMirrorMsg) void {
    switch (msg) {
        .query_edit => |edit| {
            model.query.apply(edit);
            model.edit_count += 1;
        },
    }
}

fn searchMirrorView(ui: *SearchMirrorApp.Ui, model: *const SearchMirrorModel) SearchMirrorApp.Ui.Node {
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.el(.search_field, .{
            .text = model.query.text(),
            .placeholder = "Search",
            .on_input = SearchMirrorApp.Ui.inputMsg(.query_edit),
        }, .{}),
        ui.text(.{}, if (model.query.len == 0) "Unfiltered" else "Filtered"),
    });
}

const search_mirror_views = [_]app_manifest.ShellView{
    .{ .label = search_mirror_canvas_label, .kind = .gpu_surface, .fill = true, .gpu_backend = .metal },
};
const search_mirror_windows = [_]app_manifest.ShellWindow{.{
    .label = "main",
    .title = "SearchMirror",
    .width = 400,
    .height = 300,
    .views = &search_mirror_views,
}};
const search_mirror_scene: app_manifest.ShellConfig = .{ .windows = &search_mirror_windows };

fn startSearchMirror(harness: *core.TestHarness(), app_state: *SearchMirrorApp) !void {
    app_state.* = SearchMirrorApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-search-mirror",
        .scene = search_mirror_scene,
        .canvas_label = search_mirror_canvas_label,
        .update = searchMirrorUpdate,
        .view = searchMirrorView,
    });
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = search_mirror_canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
}

fn searchMirrorHasText(app_state: *const SearchMirrorApp, text: []const u8) bool {
    const tree = app_state.tree orelse return false;
    return widgetTreeHasText(tree.root, text);
}

fn widgetTreeHasText(widget: canvas.Widget, text: []const u8) bool {
    if (widget.kind == .text and std.mem.eql(u8, widget.text, text)) return true;
    for (widget.children) |child| {
        if (widgetTreeHasText(child, text)) return true;
    }
    return false;
}

test "Escape's search-field clear reaches the model through the edit-derivation seam" {
    // The post-launch live-GUI bug: Escape made the runtime editor clear
    // the field VISUALLY while the model's `on_input` mirror never heard
    // anything — the list stayed filtered against a query the screen no
    // longer showed, and the next keystroke dispatched against the stale
    // term. The keyboard derivation now stamps the edit it applies onto
    // the dispatched event, so every formerly runtime-only edit (the
    // Escape clear, the composition cancel, the single-line ArrowUp/Down
    // caret jumps) reaches the model exactly as applied.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(SearchMirrorApp);
    defer std.testing.allocator.destroy(app_state);
    try startSearchMirror(harness, app_state);
    defer app_state.deinit();
    const app = app_state.app();

    const field_id = findWidgetIdByKind(app_state.tree.?.root, .search_field).?;
    const field_frame = (try harness.runtime.canvasWidgetLayout(1, search_mirror_canvas_label)).findById(field_id).?.frame;

    // Click into the (empty) field to focus it and type through the
    // platform text channel.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = search_mirror_canvas_label,
        .kind = .pointer_down,
        .x = field_frame.x + field_frame.width * 0.5,
        .y = field_frame.y + field_frame.height * 0.5,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = search_mirror_canvas_label,
        .kind = .text_input,
        .text = "glass",
    } });
    try std.testing.expectEqualStrings("glass", app_state.model.query.text());
    try std.testing.expect(searchMirrorHasText(app_state, "Filtered"));

    // ArrowUp jumps the caret to the start in a single-line field — a
    // runtime-only derivation before the stamp; the model's selection
    // mirror must follow so its next splice lands where the editor's
    // does.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = search_mirror_canvas_label,
        .kind = .key_down,
        .key = "arrowup",
    } });
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(0), app_state.model.query.selection);

    // THE pin: Escape clears the field AND the model hears it.
    const edits_before_escape = app_state.model.edit_count;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = search_mirror_canvas_label,
        .kind = .key_down,
        .key = "escape",
    } });
    try std.testing.expect(app_state.model.edit_count > edits_before_escape);
    try std.testing.expectEqualStrings("", app_state.model.query.text());
    try std.testing.expect(searchMirrorHasText(app_state, "Unfiltered"));

    // The visual state agrees with the model: the retained editor and
    // the automation snapshot both show the cleared field.
    const cleared_layout = try harness.runtime.canvasWidgetLayout(1, search_mirror_canvas_label);
    try std.testing.expectEqualStrings("", cleared_layout.findById(field_id).?.widget.text);
    const snapshot = harness.runtime.automationSnapshot("SearchMirror");
    for (snapshot.widgets) |widget| {
        if (widget.id == field_id) try std.testing.expectEqualStrings("", widget.text_value);
    }

    // Escape during composition cancels the composition FIRST — and the
    // model hears that too (the second formerly runtime-only arm).
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = search_mirror_canvas_label,
        .kind = .ime_set_composition,
        .text = "ne",
    } });
    try std.testing.expectEqualStrings("ne", app_state.model.query.text());
    try std.testing.expect(app_state.model.query.composition != null);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = search_mirror_canvas_label,
        .kind = .key_down,
        .key = "escape",
    } });
    try std.testing.expectEqualStrings("", app_state.model.query.text());
    try std.testing.expect(app_state.model.query.composition == null);
    const canceled_layout = try harness.runtime.canvasWidgetLayout(1, search_mirror_canvas_label);
    try std.testing.expectEqualStrings("", canceled_layout.findById(field_id).?.widget.text);
}

test "automation composition and selection verbs keep the model mirror consistent" {
    // The automation/accessibility text verbs (`widget-action ...
    // set_composition/commit_composition/cancel_composition`) used to
    // write the runtime editor directly — on-screen composition the
    // model never heard, the `set_text` bug's composition twin. They
    // now ride the SAME ime input events a real IME session produces
    // (journaled, stamped, dispatched); `set_selection` synthesizes the
    // stamped keyboard event the clipboard edits use.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(SearchMirrorApp);
    defer std.testing.allocator.destroy(app_state);
    try startSearchMirror(harness, app_state);
    defer app_state.deinit();
    const app = app_state.app();

    const field_id = findWidgetIdByKind(app_state.tree.?.root, .search_field).?;

    // Compose marked text: the editor shows it AND the model hears it.
    try core.testing.dispatchAutomationWidgetAction(&harness.runtime, app, .{
        .view_label = search_mirror_canvas_label,
        .id = field_id,
        .action = .set_composition,
        .value = "ing",
    });
    try std.testing.expectEqualStrings("ing", app_state.model.query.text());
    try std.testing.expectEqualDeep(@as(?canvas.TextRange, canvas.TextRange.init(0, 3)), app_state.model.query.composition);
    const composing_layout = try harness.runtime.canvasWidgetLayout(1, search_mirror_canvas_label);
    try std.testing.expectEqualStrings("ing", composing_layout.findById(field_id).?.widget.text);

    // Commit: composition resolves to plain text in both.
    try core.testing.dispatchAutomationWidgetAction(&harness.runtime, app, .{
        .view_label = search_mirror_canvas_label,
        .id = field_id,
        .action = .commit_composition,
    });
    try std.testing.expectEqualStrings("ing", app_state.model.query.text());
    try std.testing.expect(app_state.model.query.composition == null);

    // Select a range: the model's selection mirror follows.
    try core.testing.dispatchAutomationWidgetAction(&harness.runtime, app, .{
        .view_label = search_mirror_canvas_label,
        .id = field_id,
        .action = .set_selection,
        .value = "0 3",
    });
    try std.testing.expectEqualDeep(canvas.TextSelection{ .anchor = 0, .focus = 3 }, app_state.model.query.selection);

    // Cancel a fresh composition: the marked run vanishes from both.
    try core.testing.dispatchAutomationWidgetAction(&harness.runtime, app, .{
        .view_label = search_mirror_canvas_label,
        .id = field_id,
        .action = .set_composition,
        .value = "aro",
    });
    try std.testing.expectEqualStrings("aro", app_state.model.query.text());
    try core.testing.dispatchAutomationWidgetAction(&harness.runtime, app, .{
        .view_label = search_mirror_canvas_label,
        .id = field_id,
        .action = .cancel_composition,
    });
    try std.testing.expectEqualStrings("", app_state.model.query.text());
    try std.testing.expect(app_state.model.query.composition == null);
    const canceled_layout = try harness.runtime.canvasWidgetLayout(1, search_mirror_canvas_label);
    try std.testing.expectEqualStrings("", canceled_layout.findById(field_id).?.widget.text);
}

test "macOS Command Backspace keeps the controlled TextBuffer mirror and history in lockstep" {
    if (comptime @import("builtin").os.tag != .macos) return error.SkipZigTest;

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(SearchMirrorApp);
    defer std.testing.allocator.destroy(app_state);
    try startSearchMirror(harness, app_state);
    defer app_state.deinit();
    const app = app_state.app();
    const field_id = findWidgetIdByKind(app_state.tree.?.root, .search_field).?;

    try core.testing.dispatchAutomationWidgetAction(&harness.runtime, app, .{
        .view_label = search_mirror_canvas_label,
        .id = field_id,
        .action = .set_text,
        .value = "second line",
    });
    for (0..5) |_| {
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
            .window_id = 1,
            .label = search_mirror_canvas_label,
            .kind = .key_down,
            .key = "arrowleft",
        } });
    }
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(6), app_state.model.query.selection);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = search_mirror_canvas_label,
        .kind = .key_down,
        .key = "backspace",
        .modifiers = .{ .primary = true, .command = true },
    } });
    try std.testing.expectEqualStrings(" line", app_state.model.query.text());
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(0), app_state.model.query.selection);
    var retained = try harness.runtime.canvasWidgetLayout(1, search_mirror_canvas_label);
    try std.testing.expectEqualStrings(app_state.model.query.text(), retained.findById(field_id).?.widget.text);
    try std.testing.expectEqualDeep(app_state.model.query.selection, retained.findById(field_id).?.widget.text_selection.?);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = search_mirror_canvas_label,
        .kind = .key_down,
        .key = "z",
        .modifiers = .{ .primary = true, .command = true },
    } });
    try std.testing.expectEqualStrings("second line", app_state.model.query.text());
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(6), app_state.model.query.selection);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = search_mirror_canvas_label,
        .kind = .key_down,
        .key = "z",
        .modifiers = .{ .primary = true, .command = true, .shift = true },
    } });
    try std.testing.expectEqualStrings(" line", app_state.model.query.text());
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(0), app_state.model.query.selection);
    retained = try harness.runtime.canvasWidgetLayout(1, search_mirror_canvas_label);
    try std.testing.expectEqualStrings(app_state.model.query.text(), retained.findById(field_id).?.widget.text);
    try std.testing.expectEqualDeep(app_state.model.query.selection, retained.findById(field_id).?.widget.text_selection.?);
}

// --------------------------------- combobox open-arrow (mirror invariant)

const combo_mirror_canvas_label = "combo-mirror-canvas";

const ComboMirrorModel = struct {
    query: canvas.TextBuffer(64) = .{},
    note: canvas.TextBuffer(64) = .{},
    open: bool = false,
    opens: u32 = 0,
    submits: u32 = 0,
    picks: u32 = 0,
    query_edits: u32 = 0,
};

const ComboMirrorMsg = union(enum) {
    open_picker,
    close_picker,
    submit_query,
    pick_query: []const u8,
    query_edit: canvas.TextInputEvent,
    note_edit: canvas.TextInputEvent,
};

const ComboMirrorApp = ui_app_model.UiApp(ComboMirrorModel, ComboMirrorMsg);

fn comboMirrorUpdate(model: *ComboMirrorModel, msg: ComboMirrorMsg) void {
    switch (msg) {
        .open_picker => {
            model.open = true;
            model.opens += 1;
        },
        .close_picker => model.open = false,
        .submit_query => {
            model.open = false;
            model.submits += 1;
        },
        .pick_query => |query| {
            model.query.clear();
            model.query.apply(.{ .insert_text = query });
            model.open = false;
            model.picks += 1;
        },
        .query_edit => |edit| {
            model.query.apply(edit);
            model.query_edits += 1;
        },
        .note_edit => |edit| model.note.apply(edit),
    }
}

fn comboMirrorView(ui: *ComboMirrorApp.Ui, model: *const ComboMirrorModel) ComboMirrorApp.Ui.Node {
    const trigger = ui.el(.combobox, .{
        .text = model.query.text(),
        .placeholder = "Filter",
        .width = 200,
        .expanded = model.open,
        .on_press = .open_picker,
        .on_submit = .submit_query,
        .on_input = ComboMirrorApp.Ui.inputMsg(.query_edit),
    }, .{});
    const picker = if (model.open) ui.stack(.{ .height = 28 }, .{
        trigger,
        ui.el(.dropdown_menu, .{
            .anchor = .below,
            .anchor_alignment = .stretch,
            .width = 200,
            .height = 60,
            .on_dismiss = .close_picker,
        }, .{
            ui.el(.menu_item, .{ .key = .{ .int = 0 }, .text = "glass bead", .height = 26, .on_press = ComboMirrorMsg{ .pick_query = "glass bead" } }, .{}),
            ui.el(.menu_item, .{ .key = .{ .int = 1 }, .text = "glass jar", .height = 26, .on_press = ComboMirrorMsg{ .pick_query = "glass jar" } }, .{}),
        }),
    }) else ui.stack(.{ .height = 28 }, .{trigger});
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        picker,
        ui.el(.text_field, .{
            .text = model.note.text(),
            .placeholder = "Note",
            .width = 200,
            .on_input = ComboMirrorApp.Ui.inputMsg(.note_edit),
        }, .{}),
    });
}

const combo_mirror_views = [_]app_manifest.ShellView{
    .{ .label = combo_mirror_canvas_label, .kind = .gpu_surface, .fill = true, .gpu_backend = .metal },
};
const combo_mirror_windows = [_]app_manifest.ShellWindow{.{
    .label = "main",
    .title = "ComboMirror",
    .width = 400,
    .height = 300,
    .views = &combo_mirror_views,
}};
const combo_mirror_scene: app_manifest.ShellConfig = .{ .windows = &combo_mirror_windows };

fn comboMirrorKey(harness: *core.TestHarness(), app: core.App, key: []const u8) !void {
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = combo_mirror_canvas_label,
        .kind = .key_down,
        .key = key,
    } });
}

fn comboMirrorRetainedSelection(harness: *core.TestHarness(), id: canvas.ObjectId) !?canvas.TextSelection {
    const layout = try harness.runtime.canvasWidgetLayout(1, combo_mirror_canvas_label);
    return layout.findById(id).?.widget.text_selection;
}

test "combobox submit precedence and open-menu selection keep the text mirror consistent" {
    // The split-brain escapee: a CLOSED combobox maps ArrowUp/Down to
    // BOTH its open press (`widgetKeyboardControlIntent`'s menu-open
    // keys) and — through the single-line caret derivation — a stamped
    // caret jump. The app dispatch resolves the press FIRST, so the
    // runtime editor moved its caret while the model's mirror heard
    // nothing, and the next insert landed at two different offsets.
    // Opening wins (the platform convention): the derivation yields no
    // edit, and BOTH sides agree the arrow moved no caret.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(ComboMirrorApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ComboMirrorApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-combo-mirror",
        .scene = combo_mirror_scene,
        .canvas_label = combo_mirror_canvas_label,
        .update = comboMirrorUpdate,
        .view = comboMirrorView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = combo_mirror_canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const combo_id = findWidgetIdByKind(app_state.tree.?.root, .combobox).?;

    // Focus WITHOUT pressing (a click on a combobox IS its open press),
    // type, and walk the caret off the end so a divergence would show.
    try core.testing.dispatchAutomationWidgetAction(&harness.runtime, app, .{
        .view_label = combo_mirror_canvas_label,
        .id = combo_id,
        .action = .focus,
    });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = combo_mirror_canvas_label,
        .kind = .text_input,
        .text = "glass",
    } });
    try comboMirrorKey(harness, app, "arrowleft");
    try comboMirrorKey(harness, app, "arrowleft");
    try std.testing.expectEqualStrings("glass", app_state.model.query.text());
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(3), app_state.model.query.selection);

    // Enter resolves through on_submit BEFORE the trigger's open press.
    // The closed picker stays closed and neither side of the text mirror
    // changes while the submit message commits.
    const edits_before_submit = app_state.model.query_edits;
    try comboMirrorKey(harness, app, "enter");
    try std.testing.expect(!app_state.model.open);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.opens);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.submits);
    try std.testing.expectEqual(edits_before_submit, app_state.model.query_edits);
    try std.testing.expectEqualStrings("glass", app_state.model.query.text());
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(3), app_state.model.query.selection);
    try std.testing.expectEqualDeep(@as(?canvas.TextSelection, canvas.TextSelection.collapsed(3)), try comboMirrorRetainedSelection(harness, combo_id));

    // THE pin: ArrowDown on the closed trigger opens the picker and
    // both carets stay at 3 — no query edit is heard or applied.
    const edits_before_open = app_state.model.query_edits;
    try comboMirrorKey(harness, app, "arrowdown");
    try std.testing.expect(app_state.model.open);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.opens);
    try std.testing.expectEqual(edits_before_open, app_state.model.query_edits);
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(3), app_state.model.query.selection);
    try std.testing.expectEqualDeep(@as(?canvas.TextSelection, canvas.TextSelection.collapsed(3)), try comboMirrorRetainedSelection(harness, combo_id));

    // The OPEN-picker truth: the next arrow walks the keyboard INTO the
    // mounted menu (the focus step consumes it before routing reaches
    // the trigger), so Enter selects that menu item instead of reaching
    // the combobox submit handler again.
    const first_item_id = findWidgetIdByText(app_state.tree.?, .menu_item, "glass bead").?;
    try comboMirrorKey(harness, app, "arrowdown");
    try std.testing.expectEqual(first_item_id, harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(edits_before_open, app_state.model.query_edits);
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(3), app_state.model.query.selection);
    try std.testing.expectEqualDeep(@as(?canvas.TextSelection, canvas.TextSelection.collapsed(3)), try comboMirrorRetainedSelection(harness, combo_id));

    try comboMirrorKey(harness, app, "enter");
    try std.testing.expect(!app_state.model.open);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.submits);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.picks);
    try std.testing.expectEqualStrings("glass bead", app_state.model.query.text());
    try std.testing.expectEqualStrings("glass bead", (try harness.runtime.canvasWidgetLayout(1, combo_mirror_canvas_label)).findById(combo_id).?.widget.text);
    try std.testing.expectEqual(combo_id, harness.runtime.views[0].canvas_widget_focused_id);

    // ArrowUp on the closed trigger is the same open key: opens, and
    // both carets stay put again.
    try comboMirrorKey(harness, app, "arrowup");
    try std.testing.expect(app_state.model.open);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.opens);
    try std.testing.expectEqual(edits_before_open, app_state.model.query_edits);
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed("glass bead".len), app_state.model.query.selection);
    try std.testing.expectEqualDeep(@as(?canvas.TextSelection, canvas.TextSelection.collapsed("glass bead".len)), try comboMirrorRetainedSelection(harness, combo_id));
    try comboMirrorKey(harness, app, "escape");
    try std.testing.expect(!app_state.model.open);

    // The suppression is combobox-only: a plain text field keeps the
    // single-line ArrowUp/Down caret jumps, and the model mirror hears
    // them through the stamped edit (the #129 seam, unregressed).
    const note_id = findWidgetIdByKind(app_state.tree.?.root, .text_field).?;
    const note_frame = (try harness.runtime.canvasWidgetLayout(1, combo_mirror_canvas_label)).findById(note_id).?.frame;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = combo_mirror_canvas_label,
        .kind = .pointer_down,
        .x = note_frame.x + note_frame.width * 0.5,
        .y = note_frame.y + note_frame.height * 0.5,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = combo_mirror_canvas_label,
        .kind = .text_input,
        .text = "abc",
    } });
    try comboMirrorKey(harness, app, "arrowup");
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(0), app_state.model.note.selection);
    try std.testing.expectEqualDeep(@as(?canvas.TextSelection, canvas.TextSelection.collapsed(0)), try comboMirrorRetainedSelection(harness, note_id));
    try comboMirrorKey(harness, app, "arrowdown");
    try std.testing.expectEqualDeep(canvas.TextSelection.collapsed(3), app_state.model.note.selection);
    try std.testing.expectEqualDeep(@as(?canvas.TextSelection, canvas.TextSelection.collapsed(3)), try comboMirrorRetainedSelection(harness, note_id));
}

// ------------------------------------------------- autofocus (notes flow)

const autofocus_canvas_label = "autofocus-canvas"; // shell scene label below

const AutofocusModel = struct {
    editing: bool = false,
    draft: canvas.TextBuffer(64) = .{},
    edit_count: u32 = 0,
};

const AutofocusMsg = union(enum) {
    begin_edit,
    draft_edit: canvas.TextInputEvent,
};

const AutofocusApp = ui_app_model.UiApp(AutofocusModel, AutofocusMsg);

fn autofocusUpdate(model: *AutofocusModel, msg: AutofocusMsg) void {
    switch (msg) {
        .begin_edit => model.editing = true,
        .draft_edit => |edit| {
            model.draft.apply(edit);
            model.edit_count += 1;
        },
    }
}

/// The notes shape: Cmd-N-style command mounts an inline editor that
/// must receive the keyboard without a click.
fn autofocusView(ui: *AutofocusApp.Ui, model: *const AutofocusModel) AutofocusApp.Ui.Node {
    if (!model.editing) {
        return ui.column(.{ .padding = 12 }, .{
            ui.scroll(.{ .height = 96 }, ui.column(.{ .gap = 8 }, .{
                ui.button(.{ .on_press = AutofocusMsg.begin_edit }, "New note"),
                ui.text(.{ .height = 120 }, "Recent notes"),
            })),
        });
    }
    return ui.column(.{ .padding = 12 }, .{
        ui.scroll(.{ .height = 96 }, ui.column(.{ .gap = 8 }, .{
            ui.button(.{ .on_press = AutofocusMsg.begin_edit }, "New note"),
            ui.text(.{ .height = 120 }, "Recent notes"),
            ui.textField(.{
                .autofocus = true,
                .text = model.draft.text(),
                .on_input = AutofocusApp.Ui.inputMsg(.draft_edit),
            }),
        })),
    });
}

fn autofocusCommand(name: []const u8) ?AutofocusMsg {
    if (std.mem.eql(u8, name, "note.new")) return .begin_edit;
    return null;
}

const autofocus_views = [_]app_manifest.ShellView{
    .{ .label = autofocus_canvas_label, .kind = .gpu_surface, .fill = true, .gpu_backend = .metal },
};
const autofocus_windows = [_]app_manifest.ShellWindow{.{
    .label = "main",
    .title = "Autofocus",
    .width = 400,
    .height = 300,
    .views = &autofocus_views,
}};
const autofocus_scene: app_manifest.ShellConfig = .{ .windows = &autofocus_windows };

test "ui app autofocus moves the keyboard to a freshly mounted editor through the real dispatch path" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(AutofocusApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = AutofocusApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-autofocus",
        .scene = autofocus_scene,
        .canvas_label = autofocus_canvas_label,
        .update = autofocusUpdate,
        .view = autofocusView,
        .on_command = autofocusCommand,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = autofocus_canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    var view_index: usize = 0;
    for (harness.runtime.views[0..harness.runtime.view_count], 0..) |view, index| {
        if (std.mem.eql(u8, view.label, autofocus_canvas_label)) view_index = index;
    }
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), harness.runtime.views[view_index].canvas_widget_focused_id);

    // The Cmd-N-shaped command mounts the editor BELOW the scroll
    // viewport; the rebuild's autofocus edge reveals it and moves
    // keyboard focus to it — no click.
    try harness.runtime.dispatchPlatformEvent(app, .{ .menu_command = .{ .name = "note.new", .window_id = 1 } });
    try std.testing.expect(app_state.model.editing);
    const field_id = findWidgetIdByKind(app_state.tree.?.root, .text_field).?;
    const scroll_id = findWidgetIdByKind(app_state.tree.?.root, .scroll_view).?;
    try std.testing.expectEqual(field_id, harness.runtime.views[view_index].canvas_widget_focused_id);
    var retained = try harness.runtime.canvasWidgetLayout(1, autofocus_canvas_label);
    const scroll = retained.findById(scroll_id).?;
    const field = retained.findById(field_id).?;
    try std.testing.expect(scroll.widget.value > 0);
    try std.testing.expect(field.frame.y >= scroll.frame.y);
    try std.testing.expect(field.frame.maxY() <= scroll.frame.maxY());

    // Typing lands in the model through the ordinary input path.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = autofocus_canvas_label,
        .kind = .text_input,
        .text = "Groceries",
    } });
    try std.testing.expectEqualStrings("Groceries", app_state.model.draft.text());
    try std.testing.expect(app_state.model.edit_count > 0);

    // A later rebuild with the flag still true never re-steals focus.
    // Programmatically focusing the now-offscreen button first also proves
    // the shared automation focus verb reveals logical targets; its real
    // click then dispatches begin_edit and rebuilds.
    const button_id = findWidgetIdByKind(app_state.tree.?.root, .button).?;
    var command_buffer: [96]u8 = undefined;
    const focus_command = try std.fmt.bufPrint(&command_buffer, "widget-action {s} {d} focus", .{ autofocus_canvas_label, button_id });
    try harness.runtime.dispatchAutomationCommand(app, focus_command);
    try std.testing.expectEqual(button_id, harness.runtime.views[view_index].canvas_widget_focused_id);
    retained = try harness.runtime.canvasWidgetLayout(1, autofocus_canvas_label);
    try std.testing.expectEqual(@as(f32, 0), retained.findById(scroll_id).?.widget.value);

    const click_command = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ autofocus_canvas_label, button_id });
    try harness.runtime.dispatchAutomationCommand(app, click_command);
    try std.testing.expectEqual(button_id, harness.runtime.views[view_index].canvas_widget_focused_id);
}

// -------------------------------------------------------- layout capacity

const CapacityModel = struct {
    row_count: usize = 4,

    pub fn rows(model: *const CapacityModel, arena: std.mem.Allocator) []const usize {
        const out = arena.alloc(usize, model.row_count) catch return &.{};
        for (out, 0..) |*slot, index| slot.* = index;
        return out;
    }
};

const CapacityMsg = union(enum) {
    start,
    grew: effects_mod.EffectExit,
};

const CapacityApp = ui_app_model.UiApp(CapacityModel, CapacityMsg);
const CapacityEffects = CapacityApp.Effects;
const capacity_key: u64 = 77;

fn capacityUpdate(model: *CapacityModel, msg: CapacityMsg, fx: *CapacityEffects) void {
    switch (msg) {
        .start => fx.spawn(.{
            .key = capacity_key,
            .argv = &.{"grow"},
            .on_exit = CapacityEffects.exitMsg(.grew),
        }),
        // The grown roster far exceeds the per-view widget budget.
        .grew => model.row_count = core.max_canvas_widget_nodes_per_view + 40,
    }
}

fn capacityKey(index: *const usize) canvas.UiKey {
    return canvas.uiKey(@as(u64, index.*));
}

fn capacityRow(ui: *CapacityApp.Ui, index: *const usize) CapacityApp.Ui.Node {
    return ui.text(.{}, ui.fmt("Row {d}", .{index.*}));
}

fn capacityView(ui: *CapacityApp.Ui, model: *const CapacityModel) CapacityApp.Ui.Node {
    return ui.column(.{ .gap = 2 }, ui.each(model.rows(ui.arena), capacityKey, capacityRow));
}

test "an effects-wake rebuild past the widget budget fails tests loudly and degrades in production" {
    // A rebuild that blew max_canvas_widget_nodes_per_view
    // on an effects-wake drain used to vanish into the dispatch-error ring — the
    // test saw a passing dispatch and a silently stale frame.
    // The failing layout warns through std.log (the teaching diagnostic
    // under test would otherwise fail the build runner's stderr check).
    const saved_log_level = std.testing.log_level;
    std.testing.log_level = .err;
    defer std.testing.log_level = saved_log_level;

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 2000) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CapacityApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CapacityApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-capacity",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update_fx = capacityUpdate,
        .view = capacityView,
    });
    defer app_state.deinit();
    app_state.effects.executor = .fake;
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 2000),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // A fake spawn exit flips the model past the budget; the wake drain's
    // rebuild fails — and under the harness's `.propagate` default the
    // error reaches the test instead of leaving a stale frame.
    try app_state.dispatch(&harness.runtime, 1, .start);
    try app_state.effects.feedExit(capacity_key, 0);
    try std.testing.expectError(
        error.WidgetLayoutListFull,
        harness.runtime.dispatchPlatformEvent(app, .wake),
    );
    // Recording still happened before the propagate.
    try std.testing.expectEqual(@as(usize, 1), harness.runtime.dispatchErrors().len);
    try std.testing.expectEqualStrings("effects_wake", harness.runtime.dispatchErrors()[0].event());
    try std.testing.expectEqualStrings("WidgetLayoutListFull", harness.runtime.dispatchErrors()[0].error_name);

    // Production policy: the same failure degrades — recorded in the
    // dispatch-error ring, never fatal.
    harness.runtime.dispatch_error_policy = .degrade;
    app_state.model.row_count = 4;
    try app_state.dispatch(&harness.runtime, 1, .start);
    try app_state.effects.feedExit(capacity_key, 0);
    try harness.runtime.dispatchPlatformEvent(app, .wake);
    try std.testing.expectEqual(@as(usize, 2), harness.runtime.dispatchErrors().len);
    try std.testing.expectEqualStrings("WidgetLayoutListFull", harness.runtime.dispatchErrors()[1].error_name);
}

// --------------------------------------------------- automation degrade

test "a stale automation widget click degrades instead of killing the frame callback" {
    // `frame()` used to `try` the consumed automation
    // command, so a widget-click on an unmounted id escaped the
    // frame_requested platform callback and stopped the whole app
    // (CallbackFailed). Automation misuse always degrades — even under
    // the harness's `.propagate` policy.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, counterOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // Wire a file-backed automation server and inject a click on a
    // widget id that is not mounted.
    const io = std.testing.io;
    const directory = ".zig-cache/test-ui-app-automation-degrade";
    var cwd = std.Io.Dir.cwd();
    cwd.deleteTree(io, directory) catch {};
    try cwd.createDirPath(io, directory);
    defer cwd.deleteTree(io, directory) catch {};
    harness.runtime.options.automation = automation.Server.init(io, directory, "Degrade");
    var command_path_buffer: [128]u8 = undefined;
    const command_path = try std.fmt.bufPrint(&command_path_buffer, "{s}/command-1.txt", .{directory});
    try cwd.writeFile(io, .{ .sub_path = command_path, .data = "widget-click counter-canvas 999999\n" });

    // The frame pump consumes the command without propagating.
    try harness.runtime.dispatchPlatformEvent(app, .frame_requested);
    const errors = harness.runtime.dispatchErrors();
    try std.testing.expectEqual(@as(usize, 1), errors.len);
    try std.testing.expectEqualStrings("automation.widget_click", errors[0].event());
    try std.testing.expectEqualStrings("InvalidCommand", errors[0].error_name);

    // The app is still alive: a real click keeps dispatching.
    const increment_id = findWidgetIdByText(app_state.tree.?, .button, "Increment").?;
    var command_buffer: [96]u8 = undefined;
    const click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, increment_id });
    try harness.runtime.dispatchAutomationCommand(app, click);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
}

test "rapid-fire automation commands all dispatch, one per frame turn" {
    // The field reproduction for the queued dropbox: three commands land
    // back-to-back BEFORE the app drains any of them (the old
    // single-entry slot lost one of these to an overwrite). Zero may be
    // lost, and the drain must stay one-command-per-`frame_requested`
    // turn — the recorded event boundary replay determinism hangs on.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CounterApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CounterApp.init(std.heap.page_allocator, .{}, counterOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const io = std.testing.io;
    const directory = ".zig-cache/test-ui-app-automation-rapid-fire";
    var cwd = std.Io.Dir.cwd();
    cwd.deleteTree(io, directory) catch {};
    try cwd.createDirPath(io, directory);
    defer cwd.deleteTree(io, directory) catch {};
    harness.runtime.options.automation = automation.Server.init(io, directory, "RapidFire");

    // Three increments queued before a single frame runs.
    const increment_id = findWidgetIdByText(app_state.tree.?, .button, "Increment").?;
    var sequence: u64 = 1;
    while (sequence <= 3) : (sequence += 1) {
        var command_path_buffer: [128]u8 = undefined;
        var command_buffer: [128]u8 = undefined;
        try cwd.writeFile(io, .{
            .sub_path = try std.fmt.bufPrint(&command_path_buffer, "{s}/command-{d}.txt", .{ directory, sequence }),
            .data = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}\n", .{ canvas_label, increment_id }),
        });
    }

    // Each frame turn consumes exactly one queued command — never two,
    // never zero — until the queue is drained.
    try harness.runtime.dispatchPlatformEvent(app, .frame_requested);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.count);
    try harness.runtime.dispatchPlatformEvent(app, .frame_requested);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.count);
    try harness.runtime.dispatchPlatformEvent(app, .frame_requested);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.count);
    // Drained: further frames dispatch nothing and no errors were
    // recorded along the way.
    try harness.runtime.dispatchPlatformEvent(app, .frame_requested);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.count);
    try std.testing.expectEqual(@as(usize, 0), harness.runtime.dispatchErrors().len);
}

// ---------------------------------------------------------- webview panes

const preview_canvas_label = "preview-canvas";
const preview_pane_anchor = "preview-pane";
const example_url = "https://example.com/";
const docs_url = "https://native-sdk.dev/";

const PreviewModel = struct {
    show_docs: bool = false,
    reload_token: u64 = 0,

    fn url(model: *const PreviewModel) []const u8 {
        return if (model.show_docs) docs_url else example_url;
    }
};

const PreviewMsg = union(enum) {
    show_docs,
    show_example,
    reload,
};

const PreviewApp = ui_app_model.UiApp(PreviewModel, PreviewMsg);

fn previewUpdate(model: *PreviewModel, msg: PreviewMsg) void {
    switch (msg) {
        .show_docs => model.show_docs = true,
        .show_example => model.show_docs = false,
        .reload => model.reload_token += 1,
    }
}

fn previewView(ui: *PreviewApp.Ui, model: *const PreviewModel) PreviewApp.Ui.Node {
    _ = model;
    return ui.row(.{ .gap = 0 }, .{
        ui.column(.{ .width = 200, .padding = 12, .gap = 8 }, .{
            ui.button(.{ .on_press = .show_docs }, "Docs"),
            ui.button(.{ .on_press = .show_example }, "Example"),
            ui.button(.{ .on_press = .reload }, "Reload"),
        }),
        // The empty panel that reserves the webview region: the pane
        // anchor resolves to this widget's layout frame.
        ui.panel(.{ .grow = 1, .semantics = .{ .label = preview_pane_anchor } }, .{}),
    });
}

fn previewPanes(model: *const PreviewModel, out: []PreviewApp.WebViewPane) usize {
    out[0] = .{
        .label = "preview",
        .anchor = preview_pane_anchor,
        .url = model.url(),
        .reload_token = model.reload_token,
    };
    return 1;
}

const preview_views = [_]app_manifest.ShellView{
    .{ .label = preview_canvas_label, .kind = .gpu_surface, .fill = true, .gpu_backend = .metal },
    .{ .label = "preview", .kind = .webview, .parent = preview_canvas_label, .url = example_url, .x = 200, .y = 0, .width = 440, .height = 480 },
};
const preview_windows = [_]app_manifest.ShellWindow{.{
    .label = "main",
    .title = "Preview",
    .width = 640,
    .height = 480,
    .views = &preview_views,
}};
const preview_scene: app_manifest.ShellConfig = .{ .windows = &preview_windows };
const preview_origins = [_][]const u8{ "https://example.com", "https://native-sdk.dev", "zero://app", "zero://inline" };

fn previewOptions() PreviewApp.Options {
    return .{
        .name = "ui-app-preview",
        .scene = preview_scene,
        .canvas_label = preview_canvas_label,
        .update = previewUpdate,
        .view = previewView,
        .web_panes = previewPanes,
    };
}

fn previewHarnessAndApp(app_state: *PreviewApp) !*core.TestHarness() {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(640, 480) });
    errdefer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    harness.runtime.options.security.navigation.allowed_origins = &preview_origins;

    app_state.* = PreviewApp.init(std.heap.page_allocator, .{}, previewOptions());
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = preview_canvas_label,
        .size = geometry.SizeF.init(640, 480),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    return harness;
}

fn previewNullWebView(harness: *core.TestHarness()) !null_platform_mod.NullWebView {
    for (harness.null_platform.webviews[0..harness.null_platform.webview_count]) |webview| {
        if (std.mem.eql(u8, webview.label, "preview")) return webview;
    }
    return error.TestUnexpectedResult;
}

test "ui app scene with a child webview stays main-webview-free" {
    const app_state = try std.testing.allocator.create(PreviewApp);
    defer std.testing.allocator.destroy(app_state);
    const harness = try previewHarnessAndApp(app_state);
    defer harness.destroy(std.testing.allocator);
    defer app_state.deinit();

    // The canvas-first scene never grows an implicit main webview: the
    // loaded source stays null and only the declared views exist.
    try std.testing.expect(harness.runtime.loaded_source == null);
    var views_buffer: [8]zero_platform.ViewInfo = undefined;
    const views = harness.runtime.listViews(1, &views_buffer);
    try std.testing.expectEqual(@as(usize, 2), views.len);
    for (views) |view| {
        try std.testing.expect(!std.mem.eql(u8, view.label, "main"));
    }
}

test "ui app webview pane snaps the webview to the anchor widget frame" {
    const app_state = try std.testing.allocator.create(PreviewApp);
    defer std.testing.allocator.destroy(app_state);
    const harness = try previewHarnessAndApp(app_state);
    defer harness.destroy(std.testing.allocator);
    defer app_state.deinit();

    try std.testing.expect(app_state.installed);
    const webview = try previewNullWebView(harness);
    try std.testing.expect(webview.open);
    try std.testing.expectEqualStrings(example_url, webview.url);

    // The pane frame is the anchor widget's layout frame: the row's
    // remaining width after the 200pt sidebar column.
    const layout = try harness.runtime.canvasWidgetLayout(1, preview_canvas_label);
    var anchor_frame: ?geometry.RectF = null;
    for (layout.nodes) |node| {
        if (std.mem.eql(u8, node.widget.semantics.label, preview_pane_anchor)) anchor_frame = node.frame;
    }
    try std.testing.expect(anchor_frame != null);
    try std.testing.expect(anchor_frame.?.width > 0);
    try std.testing.expectApproxEqAbs(anchor_frame.?.x, webview.frame.x, 0.5);
    try std.testing.expectApproxEqAbs(anchor_frame.?.y, webview.frame.y, 0.5);
    try std.testing.expectApproxEqAbs(anchor_frame.?.width, webview.frame.width, 0.5);
    try std.testing.expectApproxEqAbs(anchor_frame.?.height, webview.frame.height, 0.5);

    // A resize rebuild follows the anchor to its new frame.
    try harness.runtime.dispatchPlatformEvent(app_state.app(), .{ .gpu_surface_resized = .{
        .label = preview_canvas_label,
        .window_id = 1,
        .frame = geometry.RectF.init(0, 0, 900, 600),
        .scale_factor = 1,
    } });
    const resized = try previewNullWebView(harness);
    try std.testing.expectApproxEqAbs(@as(f32, 900 - 200), resized.frame.width, 0.5);
    try std.testing.expectApproxEqAbs(@as(f32, 600), resized.frame.height, 0.5);
}

test "ui app webview pane navigates on url change and reloads on token bump" {
    const app_state = try std.testing.allocator.create(PreviewApp);
    defer std.testing.allocator.destroy(app_state);
    const harness = try previewHarnessAndApp(app_state);
    defer harness.destroy(std.testing.allocator);
    defer app_state.deinit();

    const navigations_after_install = harness.null_platform.webview_navigate_count;

    // A model-driven URL change navigates the webview.
    try app_state.dispatch(&harness.runtime, 1, .show_docs);
    var webview = try previewNullWebView(harness);
    try std.testing.expectEqualStrings(docs_url, webview.url);
    try std.testing.expectEqual(navigations_after_install + 1, harness.null_platform.webview_navigate_count);

    // A rebuild without a URL change does not renavigate.
    try app_state.dispatch(&harness.runtime, 1, .show_docs);
    try std.testing.expectEqual(navigations_after_install + 1, harness.null_platform.webview_navigate_count);

    // Bumping the reload token renavigates the same URL.
    try app_state.dispatch(&harness.runtime, 1, .reload);
    webview = try previewNullWebView(harness);
    try std.testing.expectEqualStrings(docs_url, webview.url);
    try std.testing.expectEqual(navigations_after_install + 2, harness.null_platform.webview_navigate_count);
}

// ----------------------------------------------------------- status item

const StatusModel = struct {
    refresh_count: u32 = 0,
};

const StatusMsg = union(enum) {
    refresh,
};

const StatusApp = ui_app_model.UiApp(StatusModel, StatusMsg);

fn statusUpdate(model: *StatusModel, msg: StatusMsg) void {
    switch (msg) {
        .refresh => model.refresh_count += 1,
    }
}

fn statusView(ui: *StatusApp.Ui, model: *const StatusModel) StatusApp.Ui.Node {
    return ui.column(.{ .padding = 12 }, .{
        ui.text(.{}, ui.fmt("Refreshed {d}", .{model.refresh_count})),
    });
}

fn statusCommand(name: []const u8) ?StatusMsg {
    if (std.mem.eql(u8, name, "app.refresh")) return .refresh;
    return null;
}

const status_items = [_]zero_platform.TrayMenuItem{
    .{ .id = 1, .label = "Refresh", .command = "app.refresh" },
    .{ .separator = true },
    .{ .id = 2, .label = "About" },
};

test "ui app status item installs a tray and dispatches its commands" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(StatusApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = StatusApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-status",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = statusUpdate,
        .view = statusView,
        .on_command = statusCommand,
        .status_item = .{
            .title = "NS",
            .tooltip = "native-sdk status",
            .items = &status_items,
        },
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try std.testing.expectEqual(@as(usize, 0), harness.null_platform.trayCreateCount());

    // The installing frame creates the status item exactly once.
    const frame_event = zero_platform.GpuSurfaceFrameEvent{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    };
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = frame_event });
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.trayCreateCount());
    try std.testing.expectEqualStrings("NS", harness.null_platform.lastTrayTitle());
    try std.testing.expectEqualStrings("native-sdk status", harness.null_platform.lastTrayTooltip());
    try std.testing.expectEqual(@as(usize, 3), harness.null_platform.trayItems().len);
    var second_frame = frame_event;
    second_frame.frame_index = 2;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = second_frame });
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.trayCreateCount());

    // Selecting the item dispatches its command through on_command.
    try harness.runtime.dispatchPlatformEvent(app, .{ .tray_action = .{ .item_id = 1 } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.refresh_count);
    // Items without commands fall back to the generic name and map to
    // no Msg here.
    try harness.runtime.dispatchPlatformEvent(app, .{ .tray_action = .{ .item_id = 2 } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.refresh_count);
}

const MultiStatusModel = struct {
    spend: u32 = 7,
    controls_visible: bool = true,
    spend_visible: bool = true,
};

const MultiStatusMsg = union(enum) { bump_spend, toggle_controls, remove_spend };
const MultiStatusApp = ui_app_model.UiApp(MultiStatusModel, MultiStatusMsg);

fn multiStatusUpdate(model: *MultiStatusModel, msg: MultiStatusMsg) void {
    switch (msg) {
        .bump_spend => model.spend += 1,
        .toggle_controls => model.controls_visible = !model.controls_visible,
        .remove_spend => model.spend_visible = false,
    }
}

fn multiStatusView(ui: *MultiStatusApp.Ui, model: *const MultiStatusModel) MultiStatusApp.Ui.Node {
    return ui.text(.{}, ui.fmt("Spend {d}", .{model.spend}));
}

fn multiStatusCommand(name: []const u8) ?MultiStatusMsg {
    if (std.mem.eql(u8, name, "spend.bump")) return .bump_spend;
    if (std.mem.eql(u8, name, "control.toggle") or std.mem.eql(u8, name, "control.hide") or std.mem.eql(u8, name, "control.show")) return .toggle_controls;
    if (std.mem.eql(u8, name, "spend.remove")) return .remove_spend;
    return null;
}

fn multiStatusItems(model: *const MultiStatusModel, scratch: *MultiStatusApp.StatusItemsScratch) []const MultiStatusApp.StatusItemDescriptor {
    var count: usize = 0;
    if (model.spend_visible) {
        const title = std.fmt.bufPrint(&scratch.title_buffers[count], "${d}", .{model.spend}) catch "$";
        scratch.items[0] = .{ .id = 1, .label = "Refresh spend", .command = "spend.bump" };
        scratch.items[1] = .{ .id = 2, .label = "Remove spend", .command = "spend.remove" };
        scratch.status_items[count] = .{
            .id = 100,
            .state = .{
                .presentation = .{ .title = title, .monospaced = true },
                .icon_path = if (model.spend % 2 == 0) "spend-even.png" else "spend-odd.png",
                .tooltip = "Vercel spend",
                .items = scratch.items[0..2],
            },
        };
        count += 1;
    }
    const row_start = zero_platform.max_tray_items;
    scratch.items[row_start] = .{
        .id = 1,
        .label = if (model.controls_visible) "Hide controls" else "Show controls",
        .command = "control.toggle",
    };
    scratch.status_items[count] = .{
        .id = 200,
        .visible = model.controls_visible,
        .state = .{
            .presentation = .{ .title = "V" },
            .icon_path = if (model.controls_visible) "control-on.png" else "control-off.png",
            .tooltip = if (model.controls_visible) "Controls visible" else "Controls hidden",
            .activation_command = if (model.controls_visible) "control.hide" else "control.show",
            .items = scratch.items[row_start .. row_start + 1],
        },
    };
    count += 1;
    return scratch.status_items[0..count];
}

test "ui app reconciles multiple status items independently by id" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(MultiStatusApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = MultiStatusApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-multi-status",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = multiStatusUpdate,
        .view = multiStatusView,
        .on_command = multiStatusCommand,
        .status_items_fn = multiStatusItems,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.statusItemCount());
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.trayCreateCount());
    try std.testing.expectEqualStrings("$7", harness.null_platform.statusItemTitle(100));
    try std.testing.expectEqualStrings("V", harness.null_platform.statusItemTitle(200));
    try std.testing.expectEqualStrings("spend-odd.png", harness.null_platform.statusItemIconPath(100));
    try std.testing.expectEqualStrings("control.hide", harness.null_platform.statusItemActivationCommand(200));

    // Both menus deliberately reuse row id 1; the status-item id keeps
    // command resolution independent.
    try harness.runtime.dispatchPlatformEvent(app, .{ .tray_action = .{ .status_item_id = 100, .item_id = 1 } });
    try std.testing.expectEqual(@as(u32, 8), app_state.model.spend);
    try std.testing.expectEqualStrings("$8", harness.null_platform.statusItemTitle(100));
    try std.testing.expectEqualStrings("spend-even.png", harness.null_platform.statusItemIconPath(100));
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.trayCreateCount());

    try harness.runtime.dispatchPlatformEvent(app, .{ .tray_action = .{ .status_item_id = 200, .item_id = 1 } });
    try std.testing.expect(!harness.null_platform.statusItemVisible(200));
    try std.testing.expectEqualStrings("control-off.png", harness.null_platform.statusItemIconPath(200));
    try std.testing.expectEqualStrings("Controls hidden", harness.null_platform.statusItemTooltip(200));
    try std.testing.expectEqualStrings("control.show", harness.null_platform.statusItemActivationCommand(200));
    try std.testing.expectEqualStrings("Show controls", harness.null_platform.statusItemMenu(200)[0].label);
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.trayCreateCount());
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.trayShellUpdateCount());
    try std.testing.expectEqual(@as(usize, 3), harness.null_platform.trayUpdateCount());

    try harness.runtime.dispatchPlatformEvent(app, .{ .tray_action = .{ .status_item_id = 100, .item_id = 2 } });
    try std.testing.expect(!harness.null_platform.statusItemExists(100));
    try std.testing.expect(harness.null_platform.statusItemExists(200));
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.statusItemCount());
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.trayRemoveCount());
}

// ------------------------------------------------ model-driven status item

const TrayStateModel = struct {
    open_count: u32 = 3,
    selected_issue: u32 = 0,
    issues: [2][]const u8 = .{ "Fix crash on resize", "Adopt the tray seam" },
};

const TrayStateMsg = union(enum) {
    refresh,
    select_issue: u32,
};

const TrayStateApp = ui_app_model.UiApp(TrayStateModel, TrayStateMsg);

fn trayStateUpdate(model: *TrayStateModel, msg: TrayStateMsg) void {
    switch (msg) {
        .refresh => model.open_count += 1,
        .select_issue => |index| model.selected_issue = index,
    }
}

fn trayStateView(ui: *TrayStateApp.Ui, model: *const TrayStateModel) TrayStateApp.Ui.Node {
    return ui.column(.{ .padding = 12 }, .{
        ui.text(.{}, ui.fmt("Selected {d}", .{model.selected_issue})),
    });
}

fn trayStateCommand(name: []const u8) ?TrayStateMsg {
    if (std.mem.eql(u8, name, "app.refresh")) return .refresh;
    if (std.mem.eql(u8, name, "issue.select.0")) return .{ .select_issue = 0 };
    if (std.mem.eql(u8, name, "issue.select.1")) return .{ .select_issue = 1 };
    return null;
}

/// A desktop issues-client menu-bar extra shape: an open-count badge in
/// the title and the latest issues in the dropdown, each row selecting
/// its issue.
fn trayStateStatusItem(model: *const TrayStateModel, scratch: *TrayStateApp.StatusItemScratch) TrayStateApp.StatusItemState {
    const title = std.fmt.bufPrint(&scratch.title_buffer, "ZN {d}", .{model.open_count}) catch "ZN";
    scratch.items[0] = .{ .id = 1, .label = "Refresh", .command = "app.refresh" };
    scratch.items[1] = .{ .separator = true };
    const commands = [_][]const u8{ "issue.select.0", "issue.select.1" };
    for (model.issues, 0..) |issue_title, index| {
        scratch.items[2 + index] = .{
            .id = @intCast(10 + index),
            .label = issue_title,
            .command = commands[index],
            // The selected row reads as such (also exercises menu-change
            // detection when the selection moves).
            .enabled = model.selected_issue != index,
        };
    }
    return .{ .title = title, .items = scratch.items[0..4] };
}

test "ui app status_item_fn drives the tray title and menu from the model" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(TrayStateApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = TrayStateApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-tray-state",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = trayStateUpdate,
        .view = trayStateView,
        .on_command = trayStateCommand,
        .status_item = .{ .tooltip = "issue tracker" },
        .status_item_fn = trayStateStatusItem,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    // The installing frame creates the tray FROM THE MODEL: derived
    // title, derived dropdown, static tooltip.
    const frame_event = zero_platform.GpuSurfaceFrameEvent{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    };
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = frame_event });
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.trayCreateCount());
    try std.testing.expectEqualStrings("ZN 3", harness.null_platform.lastTrayTitle());
    try std.testing.expectEqualStrings("issue tracker", harness.null_platform.lastTrayTooltip());
    try std.testing.expectEqual(@as(usize, 4), harness.null_platform.trayItems().len);
    try std.testing.expectEqualStrings("Fix crash on resize", harness.null_platform.trayItems()[2].label);
    const updates_after_install = harness.null_platform.trayUpdateCount();
    try std.testing.expectEqual(@as(usize, 0), harness.null_platform.trayTitleUpdateCount());

    // A model change that only affects the TITLE re-titles the live
    // button without rebuilding the menu or re-creating the item.
    try harness.runtime.dispatchPlatformEvent(app, .{ .tray_action = .{ .item_id = 1 } });
    try std.testing.expectEqual(@as(u32, 4), app_state.model.open_count);
    try std.testing.expectEqualStrings("ZN 4", harness.null_platform.lastTrayTitle());
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.trayTitleUpdateCount());
    try std.testing.expectEqual(updates_after_install, harness.null_platform.trayUpdateCount());
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.trayCreateCount());

    // Selecting a dropdown row closes the loop: tray action -> command ->
    // Msg -> model, and the menu (not the title) re-applies.
    try harness.runtime.dispatchPlatformEvent(app, .{ .tray_action = .{ .item_id = 11 } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.selected_issue);
    try std.testing.expectEqual(updates_after_install + 1, harness.null_platform.trayUpdateCount());
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.trayTitleUpdateCount());
    try std.testing.expect(!harness.null_platform.trayItems()[3].enabled);

    // A rebuild whose tray output is unchanged touches nothing (the
    // repeat selection Msg still rebuilds the view).
    try harness.runtime.dispatchPlatformEvent(app, .{ .tray_action = .{ .item_id = 11 } });
    try std.testing.expectEqual(updates_after_install + 1, harness.null_platform.trayUpdateCount());
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.trayTitleUpdateCount());
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.trayCreateCount());
}

test "ui app tray state rides automation snapshots and tray-action drives a row" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(TrayStateApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = TrayStateApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-tray-automation",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = trayStateUpdate,
        .view = trayStateView,
        .on_command = trayStateCommand,
        .status_item = .{ .tooltip = "issue tracker" },
        .status_item_fn = trayStateStatusItem,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    // Before the tray installs, snapshots carry no tray lines.
    {
        var buffer: [32768]u8 = undefined;
        var writer = std.Io.Writer.fixed(&buffer);
        try automation.snapshot.writeText(harness.runtime.automationSnapshot("Tray"), &writer);
        try std.testing.expect(std.mem.indexOf(u8, writer.buffered(), "tray title=") == null);
    }

    const frame_event = zero_platform.GpuSurfaceFrameEvent{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    };
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = frame_event });

    // The snapshot exposes the applied tray: model-derived title, every
    // dropdown row with its action and rich presentation fields, and separators —
    // the menu bar is outside every window capture, so this is the only
    // automation-visible evidence the model-driven tray exists.
    {
        var buffer: [32768]u8 = undefined;
        var writer = std.Io.Writer.fixed(&buffer);
        try automation.snapshot.writeText(harness.runtime.automationSnapshot("Tray"), &writer);
        const text = writer.buffered();
        try std.testing.expect(std.mem.indexOf(u8, text, "tray #1 title=\"ZN 3\" visible=true items=4\n") != null);
        try std.testing.expect(std.mem.indexOf(u8, text, "  tray-item #1 label=\"Refresh\" command=\"app.refresh\" enabled=true detail=\"\" role=command key=\"\" modifiers=(primary=false,command=false,control=false,option=false,shift=false)\n") != null);
        try std.testing.expect(std.mem.indexOf(u8, text, "  tray-item separator\n") != null);
        // The fixture disables the SELECTED row (initially issue 0).
        try std.testing.expect(std.mem.indexOf(u8, text, "  tray-item #10 label=\"Fix crash on resize\" command=\"issue.select.0\" enabled=false detail=\"\" role=command key=\"\" modifiers=(primary=false,command=false,control=false,option=false,shift=false)\n") != null);
    }

    // `tray-action <id>` drives a dropdown row through the same platform
    // event a real NSStatusItem menu click emits: command -> Msg -> model,
    // and the re-derived tray re-applies.
    try harness.runtime.dispatchAutomationCommand(app, "tray-action 11");
    try std.testing.expectEqual(@as(u32, 1), app_state.model.selected_issue);
    {
        var buffer: [32768]u8 = undefined;
        var writer = std.Io.Writer.fixed(&buffer);
        try automation.snapshot.writeText(harness.runtime.automationSnapshot("Tray"), &writer);
        const text = writer.buffered();
        try std.testing.expect(std.mem.indexOf(u8, text, "  tray-item #11 label=\"Adopt the tray seam\" command=\"issue.select.1\" enabled=false detail=\"\" role=command key=\"\" modifiers=(primary=false,command=false,control=false,option=false,shift=false)\n") != null);
        try std.testing.expect(std.mem.indexOf(u8, text, "  tray-item #10 label=\"Fix crash on resize\" command=\"issue.select.0\" enabled=true detail=\"\" role=command key=\"\" modifiers=(primary=false,command=false,control=false,option=false,shift=false)\n") != null);
    }

    // Unknown or malformed item ids are loud driver misuse, never a
    // silent no-op or a fallback command dispatch.
    try std.testing.expectError(error.InvalidCommand, harness.runtime.dispatchAutomationCommand(app, "tray-action 11"));
    try std.testing.expectError(error.InvalidCommand, harness.runtime.dispatchAutomationCommand(app, "tray-action 99"));
    try std.testing.expectError(error.InvalidCommand, harness.runtime.dispatchAutomationCommand(app, "tray-action open"));
    try std.testing.expectEqual(@as(u32, 1), app_state.model.selected_issue);
}

const SegmentedAutomationModel = struct { selected: bool = true };
const SegmentedAutomationMsg = union(enum) { enable, disable };
const SegmentedAutomationApp = ui_app_model.UiApp(SegmentedAutomationModel, SegmentedAutomationMsg);

fn segmentedAutomationUpdate(model: *SegmentedAutomationModel, msg: SegmentedAutomationMsg) void {
    switch (msg) {
        .enable => model.selected = true,
        .disable => model.selected = false,
    }
}

fn segmentedAutomationView(ui: *SegmentedAutomationApp.Ui, _: *const SegmentedAutomationModel) SegmentedAutomationApp.Ui.Node {
    return ui.text(.{}, "Segments");
}

fn segmentedAutomationCommand(name: []const u8) ?SegmentedAutomationMsg {
    if (std.mem.eql(u8, name, "segment.enable")) return .enable;
    if (std.mem.eql(u8, name, "segment.disable")) return .disable;
    return null;
}

fn segmentedAutomationStatusItem(model: *const SegmentedAutomationModel, scratch: *SegmentedAutomationApp.StatusItemScratch) SegmentedAutomationApp.StatusItemState {
    scratch.segment_options[0] = .{ .id = 20, .label = "On", .command = "segment.enable", .selected = model.selected, .enabled = false };
    scratch.segment_options[1] = .{ .id = 21, .label = "Off", .command = "segment.disable", .selected = !model.selected };
    scratch.items[0] = .{ .role = .segmented, .segmented = .{ .options = scratch.segment_options[0..2] } };
    return .{ .items = scratch.items[0..1] };
}

test "automation tray-action rejects disabled segmented options" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(SegmentedAutomationApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = SegmentedAutomationApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-tray-segmented-automation",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = segmentedAutomationUpdate,
        .view = segmentedAutomationView,
        .on_command = segmentedAutomationCommand,
        .status_item_fn = segmentedAutomationStatusItem,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    try std.testing.expectError(error.InvalidCommand, harness.runtime.dispatchAutomationCommand(app, "tray-action 20"));
    try std.testing.expect(app_state.model.selected);
    try harness.runtime.dispatchAutomationCommand(app, "tray-action 21");
    try std.testing.expect(!app_state.model.selected);
}

const TaskModel = struct {
    completed: u32 = 0,
    deleted: u32 = 0,
};

const TaskMsg = union(enum) {
    complete,
    delete,
};

const TaskApp = ui_app_model.UiApp(TaskModel, TaskMsg);

fn taskUpdate(model: *TaskModel, msg: TaskMsg) void {
    switch (msg) {
        .complete => model.completed += 1,
        .delete => model.deleted += 1,
    }
}

fn taskView(ui: *TaskApp.Ui, model: *const TaskModel) TaskApp.Ui.Node {
    _ = model;
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.el(.list_item, .{
            .text = "Ship the release",
            .context_menu = &.{
                .{ .label = "Complete", .msg = .complete },
                .{ .separator = true },
                .{ .label = "Delete", .msg = .delete },
            },
        }, .{}),
    });
}

test "ui app dispatches native context menu selections as typed messages" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(TaskApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = TaskApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-context-menu",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = taskUpdate,
        .view = taskView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // Right-click inside the row's retained frame.
    const row_id = findIn(app_state.tree.?.root, .list_item, "Ship the release").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    var row_frame: geometry.RectF = .{};
    for (layout.nodes) |node| {
        if (node.widget.id == row_id) row_frame = node.frame;
    }
    try std.testing.expect(!row_frame.isEmpty());
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 2_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.context_menu_request_count);
    // The request carries a minted per-request token (opaque to the
    // platform, which only echoes it back on the action event).
    try std.testing.expect(harness.null_platform.context_menu_token != 0);
    try std.testing.expectEqual(@as(usize, 3), harness.null_platform.contextMenuItems().len);

    // Selecting "Delete" (item id 3 = third declared entry) dispatches
    // the declared Msg through update.
    try harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = harness.null_platform.context_menu_token,
        .item_id = 3,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.deleted);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.completed);
}

// --------------------------------------- context-menu rebuild-race fixture

const ReorderModel = struct {
    reordered: bool = false,
    completed: u32 = 0,
    deleted: u32 = 0,
};

const ReorderMsg = union(enum) {
    reorder,
    complete,
    delete,
};

const ReorderApp = ui_app_model.UiApp(ReorderModel, ReorderMsg);

fn reorderUpdate(model: *ReorderModel, msg: ReorderMsg) void {
    switch (msg) {
        .reorder => model.reordered = true,
        .complete => model.completed += 1,
        .delete => model.deleted += 1,
    }
}

fn reorderView(ui: *ReorderApp.Ui, model: *const ReorderModel) ReorderApp.Ui.Node {
    // The conditional-menu shape the rebuild race exists for: an effect
    // (here a button press standing in for a timer) reorders the row's
    // declared items while the OS menu is open.
    const before = [_]ReorderApp.Ui.ContextMenuItem{
        .{ .label = "Complete", .msg = .complete },
        .{ .separator = true },
        .{ .label = "Delete", .msg = .delete },
    };
    const after = [_]ReorderApp.Ui.ContextMenuItem{
        .{ .label = "Delete", .msg = .delete },
        .{ .separator = true },
        .{ .label = "Complete", .msg = .complete },
    };
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.el(.list_item, .{
            .text = "Ship the release",
            .context_menu = if (model.reordered) &after else &before,
        }, .{}),
        ui.button(.{ .on_press = .reorder }, "Reorder"),
    });
}

test "a rebuild while the native menu is open never redirects the visible selection" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(ReorderApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ReorderApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-context-menu-race",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = reorderUpdate,
        .view = reorderView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // Right-click the row: the platform presents [Complete, —, Delete]
    // and the pending request is armed with a minted token.
    const row_id = findIn(app_state.tree.?.root, .list_item, "Ship the release").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    var row_frame: geometry.RectF = .{};
    var button_frame: geometry.RectF = .{};
    for (layout.nodes) |node| {
        if (node.widget.id == row_id) row_frame = node.frame;
        if (node.widget.kind == .button) button_frame = node.frame;
    }
    try std.testing.expect(!row_frame.isEmpty());
    try std.testing.expect(!button_frame.isEmpty());
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 2_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.context_menu_request_count);
    const shown_token = harness.null_platform.context_menu_token;
    const recorded = harness.null_platform.contextMenuItems();
    try std.testing.expectEqualStrings("Complete", recorded[0].label);

    // The GTK popover is asynchronous: while it is open, a press
    // rebuilds the tree with the items REVERSED (a timer or effect
    // reordering conditional items behaves identically).
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .x = button_frame.x + 4,
        .y = button_frame.y + 4,
        .timestamp_ns = 3_000_000,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_up,
        .x = button_frame.x + 4,
        .y = button_frame.y + 4,
        .timestamp_ns = 4_000_000,
    } });
    try std.testing.expect(app_state.model.reordered);

    // The user picks the FIRST visible item — the menu still shows
    // "Complete" there. Resolution must come from the presented menu's
    // snapshot, never the rebuilt live tree (whose index 0 is now
    // "Delete").
    try harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = shown_token,
        .item_id = 1,
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.model.deleted);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.completed);

    // Dismissal after a rebuild stays inert, and the consumed token
    // cannot resolve again.
    try harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = shown_token,
        .item_id = 0,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = shown_token,
        .item_id = 1,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.completed);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.deleted);
}

// ------------------------------------- context-menu arena-payload fixture

const ArenaPayloadModel = struct {
    generation: u32 = 0,
    sends: u32 = 0,
    received_storage: [64]u8 = undefined,
    received_len: usize = 0,
    /// Address of the dispatched payload's first byte: the pinned-arena
    /// design promises the ORIGINAL slice, so native selection carries
    /// the same pointer identity the fallback surface and the
    /// automation verb dispatch.
    received_ptr: usize = 0,
};

const ArenaPayloadMsg = union(enum) {
    bump,
    send: []const u8,
    /// The error-union shape: pinning is shape-blind (the ORIGINAL
    /// value dispatches), so a payload behind an error union needs no
    /// special handling — this arm proves it end to end.
    send_result: error{Overloaded}![]const u8,
};

const ArenaPayloadApp = ui_app_model.UiApp(ArenaPayloadModel, ArenaPayloadMsg);

fn arenaPayloadUpdate(model: *ArenaPayloadModel, msg: ArenaPayloadMsg) void {
    switch (msg) {
        .bump => model.generation += 1,
        .send => |bytes| recordArenaPayload(model, bytes),
        .send_result => |result| recordArenaPayload(model, result catch &.{}),
    }
}

fn recordArenaPayload(model: *ArenaPayloadModel, bytes: []const u8) void {
    const len = @min(bytes.len, model.received_storage.len);
    @memcpy(model.received_storage[0..len], bytes[0..len]);
    model.received_len = len;
    model.received_ptr = @intFromPtr(bytes.ptr);
    model.sends += 1;
}

fn arenaPayloadView(ui: *ArenaPayloadApp.Ui, model: *const ArenaPayloadModel) ArenaPayloadApp.Ui.Node {
    // Keep the build arena a single address-stable chunk: the first
    // allocation of every build is a slab larger than the whole build,
    // filled with sentinel bytes, so every later allocation lands at
    // the same offset build after build. Under the pinned-arena design
    // nothing here is ever overwritten while the menu is open; if the
    // pin regresses, this determinism makes the failure exact — the
    // reset arena rewrites the payload's storage with the next
    // generation's bytes.
    const slab = ui.arena.alloc(u8, 256 * 1024) catch @panic("arena slab");
    @memset(slab, '!');
    // The documented Msg-payload shape: a display string formatted into
    // the BUILD ARENA and carried by a context-menu item's Msg. Every
    // build formats a same-length, generation-stamped payload, so after
    // the arena reset the next build's bytes land exactly where a stale
    // present-time slice points.
    const payload = ui.fmt("payload-gen-{d:0>4}", .{model.generation});
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.el(.list_item, .{
            .text = "Ship the release",
            .context_menu = &.{
                .{ .label = "Send", .msg = .{ .send = payload } },
                .{ .label = "Send result", .msg = .{ .send_result = payload } },
            },
        }, .{}),
        ui.button(.{ .on_press = .bump }, "Rebuild"),
        ui.el(.text_field, .{ .text = "notes", .width = 200 }, .{}),
    });
}

test "context menu selection dispatches the original arena payload across rebuilds (pinned build arena)" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    // The leak-detecting backing allocator is part of the assertion:
    // every snapshot copy must be freed — superseded, dismissed, or
    // still armed at teardown.
    const app_state = try std.testing.allocator.create(ArenaPayloadApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ArenaPayloadApp.init(std.testing.allocator, .{}, .{
        .name = "ui-app-context-menu-arena",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = arenaPayloadUpdate,
        .view = arenaPayloadView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const row_id = findIn(app_state.tree.?.root, .list_item, "Ship the release").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    var row_frame: geometry.RectF = .{};
    var button_frame: geometry.RectF = .{};
    for (layout.nodes) |node| {
        if (node.widget.id == row_id) row_frame = node.frame;
        if (node.widget.kind == .button) button_frame = node.frame;
    }
    try std.testing.expect(!row_frame.isEmpty());
    try std.testing.expect(!button_frame.isEmpty());

    // Round 1 — the plain const slice. Present the menu (GTK popovers
    // are asynchronous: it stays on the glass while the app keeps
    // rebuilding underneath), then rebuild TWICE: view builds
    // double-buffer two arenas, so the second rebuild resets the arena
    // the presented tree was built in and overwrites the payload's
    // storage with the next generation's bytes.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 2_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.context_menu_request_count);
    const shown_token = harness.null_platform.context_menu_token;
    try std.testing.expect(shown_token != 0);
    // The presented build's arena is pinned while the request is
    // pending, so the eventual dispatch is the ORIGINAL Msg value —
    // capture its payload address at present time.
    try std.testing.expect(app_state.context_menu_pin != null);
    const presented_send = app_state.tree.?.msgForContextMenu(row_id, 0).?;
    const presented_send_ptr = @intFromPtr(presented_send.send.ptr);

    for (0..2) |press| {
        const base: u64 = 3_000_000 + @as(u64, @intCast(press)) * 2_000_000;
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
            .window_id = 1,
            .label = canvas_label,
            .kind = .pointer_down,
            .x = button_frame.x + 4,
            .y = button_frame.y + 4,
            .timestamp_ns = base,
        } });
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
            .window_id = 1,
            .label = canvas_label,
            .kind = .pointer_up,
            .x = button_frame.x + 4,
            .y = button_frame.y + 4,
            .timestamp_ns = base + 1_000_000,
        } });
    }
    try std.testing.expectEqual(@as(u32, 2), app_state.model.generation);

    // The user picks "Send": the dispatched Msg must carry the bytes
    // the user SAW at present time, never whatever the reset build
    // arena holds at selection time.
    try harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = shown_token,
        .item_id = 1,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.sends);
    try std.testing.expectEqualStrings("payload-gen-0000", app_state.model.received_storage[0..app_state.model.received_len]);
    // Pointer identity: the dispatched slice IS the presented slice —
    // the same value the fallback surface or the automation verb would
    // have dispatched — and the resolved request released the pin.
    try std.testing.expectEqual(presented_send_ptr, app_state.model.received_ptr);
    try std.testing.expect(app_state.context_menu_pin == null);

    // Round 2 — the error-union payload: pinning is shape-blind, so
    // the same two-rebuild race dispatches the presented generation's
    // bytes (and address), not the arena's current ones.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 8_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.context_menu_request_count);
    const result_token = harness.null_platform.context_menu_token;
    try std.testing.expect(result_token != shown_token);
    const presented_result = app_state.tree.?.msgForContextMenu(row_id, 1).?;
    const presented_result_ptr = @intFromPtr((presented_result.send_result catch unreachable).ptr);

    for (0..2) |press| {
        const base: u64 = 9_000_000 + @as(u64, @intCast(press)) * 2_000_000;
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
            .window_id = 1,
            .label = canvas_label,
            .kind = .pointer_down,
            .x = button_frame.x + 4,
            .y = button_frame.y + 4,
            .timestamp_ns = base,
        } });
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
            .window_id = 1,
            .label = canvas_label,
            .kind = .pointer_up,
            .x = button_frame.x + 4,
            .y = button_frame.y + 4,
            .timestamp_ns = base + 1_000_000,
        } });
    }
    try std.testing.expectEqual(@as(u32, 4), app_state.model.generation);

    try harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = result_token,
        .item_id = 2,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.model.sends);
    try std.testing.expectEqualStrings("payload-gen-0002", app_state.model.received_storage[0..app_state.model.received_len]);
    try std.testing.expectEqual(presented_result_ptr, app_state.model.received_ptr);
    try std.testing.expect(app_state.context_menu_pin == null);

    // Round 3 — supersession and dismissal: a re-presented menu
    // replaces the previous snapshot and pin, and the runtime's
    // dismissed notice releases the successor's — an abandoned menu
    // must not exempt an arena generation from resets forever.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 15_000_000,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 16_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 4), harness.null_platform.context_menu_request_count);
    const superseding_token = harness.null_platform.context_menu_token;
    try std.testing.expect(app_state.context_menu_pin != null);
    try harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = superseding_token,
        .item_id = 0,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.model.sends);
    try std.testing.expect(app_state.context_menu_pin == null);
}

test "any superseding presentation or dispatch releases the app menu's snapshot and pin" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(ArenaPayloadApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ArenaPayloadApp.init(std.testing.allocator, .{}, .{
        .name = "ui-app-context-menu-supersede",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = arenaPayloadUpdate,
        .view = arenaPayloadView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const row_id = findIn(app_state.tree.?.root, .list_item, "Ship the release").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    var row_frame: geometry.RectF = .{};
    var field_frame: geometry.RectF = .{};
    for (layout.nodes) |node| {
        if (node.widget.id == row_id) row_frame = node.frame;
        if (node.widget.kind == .text_field) field_frame = node.frame;
    }
    try std.testing.expect(!row_frame.isEmpty());
    try std.testing.expect(!field_frame.isEmpty());

    // Present the row's app menu: snapshot armed, build generation
    // pinned.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 2_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.context_menu_request_count);
    try std.testing.expect(app_state.context_menu_pin != null);
    try std.testing.expect(app_state.context_menu_shown_token != 0);

    // A right-click on the editable field presents the DEFAULT edit
    // menu — a different pending kind, but it supersedes the app
    // menu's request all the same, so the snapshot and pin release.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = field_frame.x + 4,
        .y = field_frame.y + 4,
        .timestamp_ns = 3_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.context_menu_request_count);
    try std.testing.expect(app_state.context_menu_pin == null);
    try std.testing.expectEqual(@as(u64, 0), app_state.context_menu_shown_token);

    // Re-present the app menu, then drive the item through the
    // automation verb: its direct dispatch supersedes the open
    // presentation (releasing snapshot and pin) and resolves against
    // the live tree.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 4_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 3), harness.null_platform.context_menu_request_count);
    try std.testing.expect(app_state.context_menu_pin != null);
    var command_buffer: [96]u8 = undefined;
    const command = try std.fmt.bufPrint(&command_buffer, "widget-context-menu {s} {d} 0", .{ canvas_label, row_id });
    try harness.runtime.dispatchAutomationCommand(app, command);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.sends);
    try std.testing.expectEqualStrings("payload-gen-0000", app_state.model.received_storage[0..app_state.model.received_len]);
    try std.testing.expect(app_state.context_menu_pin == null);
    try std.testing.expectEqual(@as(u64, 0), app_state.context_menu_shown_token);
}

test "rebuilds under an open menu stay bounded at two trees" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(ArenaPayloadApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ArenaPayloadApp.init(std.testing.allocator, .{}, .{
        .name = "ui-app-context-menu-bounded",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = arenaPayloadUpdate,
        .view = arenaPayloadView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    const row_id = findIn(app_state.tree.?.root, .list_item, "Ship the release").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    var row_frame: geometry.RectF = .{};
    var button_frame: geometry.RectF = .{};
    for (layout.nodes) |node| {
        if (node.widget.id == row_id) row_frame = node.frame;
        if (node.widget.kind == .button) button_frame = node.frame;
    }

    // Present the menu and hold it open for the whole test.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 2_000_000,
    } });
    try std.testing.expect(app_state.context_menu_pin != null);

    const press = struct {
        fn once(h: *core.TestHarness(), a: core.App, frame: geometry.RectF, base: u64) !void {
            try h.runtime.dispatchPlatformEvent(a, .{ .gpu_surface_input = .{
                .window_id = 1,
                .label = canvas_label,
                .kind = .pointer_down,
                .x = frame.x + 4,
                .y = frame.y + 4,
                .timestamp_ns = base,
            } });
            try h.runtime.dispatchPlatformEvent(a, .{ .gpu_surface_input = .{
                .window_id = 1,
                .label = canvas_label,
                .kind = .pointer_up,
                .x = frame.x + 4,
                .y = frame.y + 4,
                .timestamp_ns = base + 1_000_000,
            } });
        }
    };

    // Warm the partner arena's capacity to its fixed point, then drive
    // many more rebuilds: while the pin freezes the presented
    // generation, every rebuild routes through the partner with its
    // normal reset cadence, so total build storage holds at exactly
    // two trees — capacity must not grow with the rebuild count.
    for (0..4) |index| {
        try press.once(harness, app, button_frame, 3_000_000 + @as(u64, @intCast(index)) * 2_000_000);
    }
    const warmed = app_state.arenas[0].queryCapacity() + app_state.arenas[1].queryCapacity();
    for (0..10) |index| {
        try press.once(harness, app, button_frame, 20_000_000 + @as(u64, @intCast(index)) * 2_000_000);
    }
    try std.testing.expectEqual(@as(u32, 14), app_state.model.generation);
    try std.testing.expectEqual(warmed, app_state.arenas[0].queryCapacity() + app_state.arenas[1].queryCapacity());
    try std.testing.expect(app_state.context_menu_pin != null);
}

// ---------------------------------------- pinned rebuild failure recovery

const PinFailureModel = struct {
    row_count: usize = 4,
    sends: u32 = 0,
    received_storage: [64]u8 = undefined,
    received_len: usize = 0,

    pub fn rows(model: *const PinFailureModel, arena: std.mem.Allocator) []const usize {
        const out = arena.alloc(usize, model.row_count) catch return &.{};
        for (out, 0..) |*slot, index| slot.* = index;
        return out;
    }
};

const PinFailureMsg = union(enum) {
    set_rows: usize,
    send: []const u8,
};

const PinFailureApp = ui_app_model.UiApp(PinFailureModel, PinFailureMsg);

fn pinFailureUpdate(model: *PinFailureModel, msg: PinFailureMsg) void {
    switch (msg) {
        .set_rows => |count| model.row_count = count,
        .send => |bytes| {
            const len = @min(bytes.len, model.received_storage.len);
            @memcpy(model.received_storage[0..len], bytes[0..len]);
            model.received_len = len;
            model.sends += 1;
        },
    }
}

fn pinFailureKey(index: *const usize) canvas.UiKey {
    return canvas.uiKey(@as(u64, index.*));
}

fn pinFailureRow(ui: *PinFailureApp.Ui, index: *const usize) PinFailureApp.Ui.Node {
    return ui.text(.{}, ui.fmt("Row {d}", .{index.*}));
}

fn pinFailureView(ui: *PinFailureApp.Ui, model: *const PinFailureModel) PinFailureApp.Ui.Node {
    const payload = ui.fmt("payload-rows-{d:0>4}", .{model.row_count});
    return ui.column(.{ .gap = 2, .padding = 12 }, .{
        ui.el(.list_item, .{
            .text = "Ship the release",
            .context_menu = &.{
                .{ .label = "Send", .msg = .{ .send = payload } },
                // The poison item: its update pushes the roster far past
                // the per-view widget budget, so the selection's rebuild
                // fails.
                .{ .label = "Grow", .msg = .{ .set_rows = core.max_canvas_widget_nodes_per_view + 40 } },
            },
        }, .{}),
        ui.button(.{ .on_press = PinFailureMsg{ .set_rows = 4 } }, "Shrink"),
        ui.column(.{ .gap = 2 }, ui.each(model.rows(ui.arena), pinFailureKey, pinFailureRow)),
    });
}

test "a failing rebuild routed into the live arena under an open menu drops the tree instead of dangling" {
    // The failing layout warns through std.log (the teaching diagnostic
    // under test would otherwise fail the build runner's stderr check).
    const saved_log_level = std.testing.log_level;
    std.testing.log_level = .err;
    defer std.testing.log_level = saved_log_level;

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 2000) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(PinFailureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = PinFailureApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-pin-rebuild-failure",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = pinFailureUpdate,
        .view = pinFailureView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 2000),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const row_id = findIn(app_state.tree.?.root, .list_item, "Ship the release").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    const row_frame = layout.findById(row_id).?.frame;

    // Present the row's menu: the presenting build's arena is pinned.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 2_000_000,
    } });
    const shown_token = harness.null_platform.context_menu_token;
    try std.testing.expect(app_state.context_menu_pin != null);

    // One successful rebuild under the open menu lands in the partner
    // arena; from here every rebuild reuses that LIVE side (the pinned
    // side stays frozen).
    try app_state.dispatch(&harness.runtime, 1, .{ .set_rows = 5 });
    try std.testing.expect(app_state.tree != null);

    // The over-budget rebuild resets the live arena and fails mid-pass
    // under the harness's `.propagate` policy: the tree reference must
    // drop with it — a handler table dangling into reset, partially
    // rewritten storage must never stay dispatchable.
    try std.testing.expectError(
        error.WidgetLayoutListFull,
        app_state.dispatch(&harness.runtime, 1, .{ .set_rows = core.max_canvas_widget_nodes_per_view + 40 }),
    );
    try std.testing.expect(app_state.tree == null);

    // Recovery: the next in-budget rebuild restores the tree.
    try app_state.dispatch(&harness.runtime, 1, .{ .set_rows = 4 });
    try std.testing.expect(app_state.tree != null);

    // The pinned presentation rode through the failure untouched: the
    // user's pick still dispatches the presented generation's payload
    // from the frozen arena.
    try std.testing.expect(app_state.context_menu_pin != null);
    try harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = shown_token,
        .item_id = 1,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.sends);
    try std.testing.expectEqualStrings("payload-rows-0004", app_state.model.received_storage[0..app_state.model.received_len]);
    try std.testing.expect(app_state.context_menu_pin == null);
}

test "dismissing the menu after a failed pinned rebuild restores the dropped tree" {
    // The failing layout warns through std.log (the teaching diagnostic
    // under test would otherwise fail the build runner's stderr check).
    const saved_log_level = std.testing.log_level;
    std.testing.log_level = .err;
    defer std.testing.log_level = saved_log_level;

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 2000) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(PinFailureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = PinFailureApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-pin-dismiss-restore",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = pinFailureUpdate,
        .view = pinFailureView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 2000),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const row_id = findIn(app_state.tree.?.root, .list_item, "Ship the release").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    const row_frame = layout.findById(row_id).?.frame;

    // Present the menu, rebuild once under it, then fail the rebuild
    // routed into the live arena: the tree reference drops.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 2_000_000,
    } });
    const shown_token = harness.null_platform.context_menu_token;
    try app_state.dispatch(&harness.runtime, 1, .{ .set_rows = 5 });
    try std.testing.expectError(
        error.WidgetLayoutListFull,
        app_state.dispatch(&harness.runtime, 1, .{ .set_rows = core.max_canvas_widget_nodes_per_view + 40 }),
    );
    try std.testing.expect(app_state.tree == null);

    // The model comes back in budget WITHOUT a Msg (an effect result,
    // or the failing state was transient) — no rebuild has run yet.
    app_state.model.row_count = 4;

    // The user dismisses the open menu. Its resolution dispatches no
    // Msg, so the release itself must restore the dropped tree —
    // otherwise every handler no-ops (no tree, no Msgs) until an
    // unrelated resize or effect happens to rebuild.
    try harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = shown_token,
        .item_id = 0,
    } });
    try std.testing.expect(app_state.tree != null);
    try std.testing.expect(app_state.context_menu_pin == null);
}

test "a selection whose update breaks the build budget keeps the live tree and input alive" {
    // The failing layout warns through std.log (the teaching diagnostic
    // under test would otherwise fail the build runner's stderr check).
    const saved_log_level = std.testing.log_level;
    std.testing.log_level = .err;
    defer std.testing.log_level = saved_log_level;

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 2000) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(PinFailureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = PinFailureApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-pin-selection-failure",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = pinFailureUpdate,
        .view = pinFailureView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 2000),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const row_id = findIn(app_state.tree.?.root, .list_item, "Ship the release").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    const row_frame = layout.findById(row_id).?.frame;

    // Present the menu, then rebuild once under it: the live tree moves
    // to the partner arena, adjacent to the pinned generation.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 2_000_000,
    } });
    const shown_token = harness.null_platform.context_menu_token;
    try app_state.dispatch(&harness.runtime, 1, .{ .set_rows = 5 });

    // Selecting "Grow" dispatches from the snapshot; its update pushes
    // the model past the widget budget and the rebuild fails. The pin
    // released before the dispatch, so the rebuild routed into the
    // partner arena — the LIVE tree survives the failure and input
    // keeps working (production's degraded contract; the harness's
    // `.propagate` policy surfaces the recorded error here).
    try std.testing.expectError(error.WidgetLayoutListFull, harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = shown_token,
        .item_id = 2,
    } }));
    try std.testing.expect(app_state.tree != null);
    try std.testing.expect(app_state.context_menu_pin == null);

    // The app's own controls recover the model THROUGH the surviving
    // handler table: a real pointer click on "Shrink" (still routed by
    // the retained layout of the last successful build) dispatches its
    // Msg and the next rebuild succeeds.
    const retained = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    var button_frame: geometry.RectF = .{};
    for (retained.nodes) |node| {
        if (node.widget.kind == .button) button_frame = node.frame;
    }
    try std.testing.expect(!button_frame.isEmpty());
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .x = button_frame.x + 4,
        .y = button_frame.y + 4,
        .timestamp_ns = 3_000_000,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_up,
        .x = button_frame.x + 4,
        .y = button_frame.y + 4,
        .timestamp_ns = 4_000_000,
    } });
    try std.testing.expectEqual(@as(usize, 4), app_state.model.row_count);
    try std.testing.expect(app_state.tree != null);
}

test "a menu presented while the tree is dropped still resolves once the model recovers" {
    // The failing layout warns through std.log (the teaching diagnostic
    // under test would otherwise fail the build runner's stderr check).
    const saved_log_level = std.testing.log_level;
    std.testing.log_level = .err;
    defer std.testing.log_level = saved_log_level;

    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 2000) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(PinFailureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = PinFailureApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-pin-shown-recovery",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = pinFailureUpdate,
        .view = pinFailureView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 2000),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const row_id = findIn(app_state.tree.?.root, .list_item, "Ship the release").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    const row_frame = layout.findById(row_id).?.frame;
    const right_click: zero_platform.Event = .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_down,
        .button = 1,
        .x = row_frame.x + 4,
        .y = row_frame.y + 4,
        .timestamp_ns = 2_000_000,
    } };

    // Menu A is open when a rebuild routed into the live arena fails:
    // the tree drops while the model stays unbuildable.
    try harness.runtime.dispatchPlatformEvent(app, right_click);
    try app_state.dispatch(&harness.runtime, 1, .{ .set_rows = 5 });
    try std.testing.expectError(
        error.WidgetLayoutListFull,
        app_state.dispatch(&harness.runtime, 1, .{ .set_rows = core.max_canvas_widget_nodes_per_view + 40 }),
    );
    try std.testing.expect(app_state.tree == null);

    // Superseding A with menu B: A's dismissal releases the snapshot
    // and its restore attempt fails loudly (the model is still past the
    // budget under the harness's `.propagate` policy). B commits
    // runtime-side, but no snapshot could arm for it.
    try std.testing.expectError(
        error.WidgetLayoutListFull,
        harness.runtime.dispatchPlatformEvent(app, right_click),
    );
    const b_token = harness.null_platform.context_menu_token;
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.context_menu_request_count);

    // The model recovers WITHOUT a rebuild (an effect result fixed it).
    app_state.model.row_count = 4;

    // Selecting from menu B resolves snapshot-less: the handler
    // restores the dropped tree before resolving, so the selection
    // dispatches its Msg instead of falling through a null tree.
    try harness.runtime.dispatchPlatformEvent(app, .{ .context_menu_action = .{
        .window_id = 1,
        .view_label = canvas_label,
        .token = b_token,
        .item_id = 1,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.sends);
    try std.testing.expectEqualStrings("payload-rows-0004", app_state.model.received_storage[0..app_state.model.received_len]);
    try std.testing.expect(app_state.tree != null);
}

// ------------------------------------------------- press fall-through fixture

const RowsModel = struct {
    picked: u32 = 0,
    picks: u32 = 0,
    button_hits: u32 = 0,
};

const RowsMsg = union(enum) {
    pick: u32,
    button_hit,
};

const RowsApp = ui_app_model.UiApp(RowsModel, RowsMsg);

fn rowsUpdate(model: *RowsModel, msg: RowsMsg) void {
    switch (msg) {
        .pick => |id| {
            model.picked = id;
            model.picks += 1;
        },
        .button_hit => model.button_hits += 1,
    }
}

fn rowsView(ui: *RowsApp.Ui, model: *const RowsModel) RowsApp.Ui.Node {
    _ = model;
    // The showcase row shape the press fall-through exists for: pressable
    // panels whose visible content is plain (selectable) text — no
    // empty-text overlays, no duplicated handlers on the text leaves.
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.panel(.{ .on_press = RowsMsg{ .pick = 1 }, .height = 48 }, .{
            ui.row(.{ .gap = 8, .padding = 8 }, .{
                ui.text(.{ .grow = 1 }, "Alpha row label"),
            }),
        }),
        ui.panel(.{ .on_press = RowsMsg{ .pick = 2 }, .height = 48 }, .{
            ui.row(.{ .gap = 8, .padding = 8 }, .{
                ui.text(.{ .grow = 1 }, "Beta row label"),
                ui.button(.{ .on_press = .button_hit }, "Open"),
            }),
        }),
    });
}

fn rowsOptions() RowsApp.Options {
    return .{
        .name = "ui-app-rows",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = rowsUpdate,
        .view = rowsView,
    };
}

fn rowsWidgetCenter(runtime: *core.Runtime, id: canvas.ObjectId) !geometry.PointF {
    const layout = try runtime.canvasWidgetLayout(1, canvas_label);
    return layout.findById(id).?.frame.normalized().center();
}

fn rowsPointer(kind: zero_platform.GpuSurfaceInputKind, point: geometry.PointF, timestamp_ns: u64) zero_platform.Event {
    return .{ .gpu_surface_input = .{
        .label = canvas_label,
        .kind = kind,
        .timestamp_ns = timestamp_ns,
        .x = point.x,
        .y = point.y,
    } };
}

fn rowsTextSelection(runtime: *core.Runtime, id: canvas.ObjectId) !?canvas.TextSelection {
    const layout = try runtime.canvasWidgetLayout(1, canvas_label);
    return layout.findById(id).?.widget.text_selection;
}

test "presses on a pressable row's plain text land on the row, live and via automation" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(RowsApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = RowsApp.init(std.heap.page_allocator, .{}, rowsOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    const tree = app_state.tree.?;
    const alpha_row_id = tree.root.children[0].id;
    const alpha_text_id = findIn(tree.root, .text, "Alpha row label").?;
    const button_id = findIn(tree.root, .button, "Open").?;

    // A live click on the row's plain text lands on the row's on_press.
    const text_center = try rowsWidgetCenter(&harness.runtime, alpha_text_id);
    try harness.runtime.dispatchPlatformEvent(app, rowsPointer(.pointer_down, text_center, 2_000_000));
    try harness.runtime.dispatchPlatformEvent(app, rowsPointer(.pointer_up, text_center, 2_100_000));
    try std.testing.expectEqual(@as(u32, 1), app_state.model.picks);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.picked);

    // A button inside a pressable row claims its own press — the button
    // wins, the row does not fire.
    const button_center = try rowsWidgetCenter(&harness.runtime, button_id);
    try harness.runtime.dispatchPlatformEvent(app, rowsPointer(.pointer_down, button_center, 3_000_000));
    try harness.runtime.dispatchPlatformEvent(app, rowsPointer(.pointer_up, button_center, 3_100_000));
    try std.testing.expectEqual(@as(u32, 1), app_state.model.button_hits);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.picks);

    // The automation click path agrees with the live hit test: clicking
    // the row id (whose center is covered by its text) and clicking the
    // text id both land on the row.
    var command_buffer: [96]u8 = undefined;
    const row_click = try std.fmt.bufPrint(&command_buffer, "widget-click {s} {d}", .{ canvas_label, alpha_row_id });
    try harness.runtime.dispatchAutomationCommand(app, row_click);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.picks);
    var text_command_buffer: [96]u8 = undefined;
    const text_click = try std.fmt.bufPrint(&text_command_buffer, "widget-click {s} {d}", .{ canvas_label, alpha_text_id });
    try harness.runtime.dispatchAutomationCommand(app, text_click);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.picks);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.button_hits);
}

test "selection drag inside a pressable row selects without pressing; a click still presses" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(RowsApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = RowsApp.init(std.heap.page_allocator, .{}, rowsOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    const alpha_text_id = findIn(app_state.tree.?.root, .text, "Alpha row label").?;
    const layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    const text_frame = layout.findById(alpha_text_id).?.frame.normalized();
    const start = geometry.PointF.init(text_frame.x + 2, text_frame.center().y);
    const end = geometry.PointF.init(text_frame.x + text_frame.width * 0.7, text_frame.center().y);

    // Down + drag + up: the gesture selects text within the row and
    // presses NOTHING — dragging selects, clicking presses.
    try harness.runtime.dispatchPlatformEvent(app, rowsPointer(.pointer_down, start, 2_000_000));
    try harness.runtime.dispatchPlatformEvent(app, rowsPointer(.pointer_drag, end, 2_050_000));
    try harness.runtime.dispatchPlatformEvent(app, rowsPointer(.pointer_up, end, 2_100_000));
    const selection = (try rowsTextSelection(&harness.runtime, alpha_text_id)).?;
    try std.testing.expect(!selection.isCollapsed("Alpha row label".len));
    try std.testing.expectEqual(@as(u32, 0), app_state.model.picks);

    // A plain click on the same text collapses the selection on the way
    // down and the press lands on the row.
    const center = try rowsWidgetCenter(&harness.runtime, alpha_text_id);
    try harness.runtime.dispatchPlatformEvent(app, rowsPointer(.pointer_down, center, 3_000_000));
    try harness.runtime.dispatchPlatformEvent(app, rowsPointer(.pointer_up, center, 3_100_000));
    try std.testing.expectEqual(@as(u32, 1), app_state.model.picks);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.picked);
}

// --------------------------------------------------------------- on_chrome

const ChromeInsetModel = struct {
    leading: f32 = 0,
    top: f32 = 0,
    /// The traffic-light cluster's vertical centerline — what a header
    /// centers its controls against in the tall band.
    buttons_center_y: f32 = 0,
    deliveries: u32 = 0,
};

const ChromeInsetMsg = union(enum) {
    chrome: zero_platform.WindowChrome,
};

const ChromeInsetApp = ui_app_model.UiApp(ChromeInsetModel, ChromeInsetMsg);

fn chromeInsetUpdate(model: *ChromeInsetModel, msg: ChromeInsetMsg) void {
    switch (msg) {
        .chrome => |chrome| {
            model.leading = chrome.insets.left;
            model.top = chrome.insets.top;
            model.buttons_center_y = chrome.buttons.y + chrome.buttons.height / 2;
            model.deliveries += 1;
        },
    }
}

fn chromeInsetView(ui: *ChromeInsetApp.Ui, model: *const ChromeInsetModel) ChromeInsetApp.Ui.Node {
    return ui.column(.{ .gap = 8 }, .{
        ui.row(.{ .window_drag = true, .height = 40 }, .{
            ui.el(.stack, .{ .width = model.leading }, .{}),
            ui.text(.{}, "Header"),
        }),
        ui.text(.{}, ui.fmt("leading {d}", .{model.leading})),
    });
}

fn chromeInsetMap(chrome: zero_platform.WindowChrome) ?ChromeInsetMsg {
    return .{ .chrome = chrome };
}

test "ui app delivers chrome overlay geometry before install and on change" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    // Model a tall hidden-titlebar macOS host: unified band 52pt tall,
    // traffic lights ending 78pt in and vertically centered in the band.
    harness.null_platform.window_chrome = .{
        .insets = .{ .top = 52, .left = 78 },
        .buttons = geometry.RectF.init(12, 19, 54, 14),
    };

    const app_state = try std.testing.allocator.create(ChromeInsetApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ChromeInsetApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-chrome",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = chromeInsetUpdate,
        .view = chromeInsetView,
        .on_chrome = chromeInsetMap,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    // The insets land in the model BEFORE the first view build, so the
    // installing frame already renders the padded header.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.deliveries);
    try std.testing.expectEqual(@as(f32, 78), app_state.model.leading);
    try std.testing.expectEqual(@as(f32, 52), app_state.model.top);
    // The buttons frame carries the vertical truth: centerline at
    // 19 + 14/2 = 26, the tall band's midpoint.
    try std.testing.expectEqual(@as(f32, 26), app_state.model.buttons_center_y);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "leading 78"));

    // A resize with unchanged insets dispatches nothing extra.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_resized = .{
        .window_id = 1,
        .label = canvas_label,
        .frame = geometry.RectF.init(0, 0, 640, 480),
        .scale_factor = 2,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.deliveries);

    // Fullscreen hides the chrome: the resize that accompanies the
    // transition re-queries and delivers the zeroed geometry.
    harness.null_platform.window_chrome = .{};
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_resized = .{
        .window_id = 1,
        .label = canvas_label,
        .frame = geometry.RectF.init(0, 0, 1440, 900),
        .scale_factor = 2,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.model.deliveries);
    try std.testing.expectEqual(@as(f32, 0), app_state.model.leading);
    try std.testing.expectEqual(@as(f32, 0), app_state.model.buttons_center_y);
    try std.testing.expect(try retainedTextExists(&harness.runtime, "leading 0"));
}

// ---------------------------------- window-control clearance fixtures

const CaptionModel = struct {
    /// The trailing chrome reservation the CONTRACT app consumes (the
    /// soundboard pattern: a spacer sized to `insets.right`); the naive
    /// app leaves it unwired at 0, exactly like an app that never
    /// subscribed to the chrome channel.
    trailing: f32 = 0,
};

const CaptionMsg = union(enum) {
    chrome: zero_platform.WindowChrome,
};

const CaptionApp = ui_app_model.UiApp(CaptionModel, CaptionMsg);

fn captionUpdate(model: *CaptionModel, msg: CaptionMsg) void {
    switch (msg) {
        .chrome => |chrome| model.trailing = chrome.insets.right,
    }
}

/// A hidden-titlebar header that never consumed the chrome channel:
/// title leading, status text trailing — the system-monitor shape whose
/// status line rendered UNDER the Windows caption buttons.
fn captionNaiveView(ui: *CaptionApp.Ui, model: *const CaptionModel) CaptionApp.Ui.Node {
    _ = model;
    return ui.column(.{}, .{
        ui.row(.{ .window_drag = true, .height = 40 }, .{
            ui.text(.{}, "Monitor"),
            ui.el(.stack, .{ .grow = 1 }, .{}),
            ui.text(.{}, "sampling"),
        }),
        ui.text(.{}, "body"),
    });
}

/// The documented contract shape (the soundboard pattern): the header
/// ends with a spacer sized to `insets.right`, so its own content
/// already clears the caption cluster and the runtime reservation must
/// stay out of the way.
fn captionContractView(ui: *CaptionApp.Ui, model: *const CaptionModel) CaptionApp.Ui.Node {
    return ui.column(.{}, .{
        ui.row(.{ .window_drag = true, .height = 40 }, .{
            ui.text(.{}, "Monitor"),
            ui.el(.stack, .{ .grow = 1 }, .{}),
            ui.text(.{}, "sampling"),
            ui.el(.stack, .{ .width = model.trailing }, .{}),
        }),
        ui.text(.{}, "body"),
    });
}

/// The centered-title pattern: ONE grow text spanning the header row.
/// Its FRAME runs under the caption cluster, but the centered glyphs
/// sit well clear of it — nothing here needs (or may pay for) the
/// clearance retry.
fn captionCenteredView(ui: *CaptionApp.Ui, model: *const CaptionModel) CaptionApp.Ui.Node {
    _ = model;
    return ui.column(.{}, .{
        ui.row(.{ .window_drag = true, .height = 40 }, .{
            ui.text(.{ .grow = 1, .text_alignment = .center }, "Monitor"),
        }),
        ui.text(.{}, "body"),
    });
}

fn captionChromeMap(chrome: zero_platform.WindowChrome) ?CaptionMsg {
    return .{ .chrome = chrome };
}

fn captionTextFrame(runtime: *core.Runtime, text: []const u8) !geometry.RectF {
    const layout = try runtime.canvasWidgetLayout(1, canvas_label);
    for (layout.nodes) |node| {
        if (node.widget.kind == .text and std.mem.eql(u8, node.widget.text, text)) return node.frame;
    }
    return error.TestUnexpectedResult;
}

test "drag header trailing content stays clear of the Windows caption cluster" {
    // Windows-shaped hidden-titlebar chrome on a 400pt window: the DWM
    // caption cluster overlays the trailing 138pt of the top band, and
    // the platform reports it through the same chrome channel macOS
    // reports the traffic lights on. The app never consumed the
    // channel, so its right-aligned header status would lay out flush
    // to the window edge — UNDER the min/max/close buttons. The runtime
    // detects the collision after layout and re-lays the drag header
    // with the cluster reserved, so the status text ends at the
    // cluster's leading edge instead.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    harness.null_platform.window_chrome = .{
        .insets = .{ .top = 32, .right = 138 },
        .buttons = geometry.RectF.init(262, 0, 138, 32),
    };

    const app_state = try std.testing.allocator.create(CaptionApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CaptionApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-caption-naive",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = captionUpdate,
        .view = captionNaiveView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // The trailing status text ends at (or before) the cluster's
    // leading edge; without the reservation it ended at the window's
    // right edge, inside the cluster.
    const status = try captionTextFrame(&harness.runtime, "sampling");
    try std.testing.expect(status.maxX() <= 262 + 0.01);
    // The leading title and the body below the band are untouched.
    const title = try captionTextFrame(&harness.runtime, "Monitor");
    try std.testing.expectEqual(@as(f32, 0), title.x);
}

test "drag header that already pads the caption cluster keeps its layout" {
    // The contract app (the soundboard shape): its header ends with a
    // spacer consuming `insets.right`, so nothing collides and the
    // runtime reservation must NOT fire — a double reservation would
    // shove the status text a full cluster-width further left.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    harness.null_platform.window_chrome = .{
        .insets = .{ .top = 32, .right = 138 },
        .buttons = geometry.RectF.init(262, 0, 138, 32),
    };

    const app_state = try std.testing.allocator.create(CaptionApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CaptionApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-caption-contract",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = captionUpdate,
        .view = captionContractView,
        .on_chrome = captionChromeMap,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);
    try std.testing.expectEqual(@as(f32, 138), app_state.model.trailing);

    // The app's own spacer puts the status text at the cluster's edge;
    // the runtime must not reserve on top of it (a double reservation
    // would land it near 262 - 138 = 124).
    const status = try captionTextFrame(&harness.runtime, "sampling");
    try std.testing.expect(@abs(status.maxX() - 262) < 0.5);
}

test "drag header centered title never pays the clearance retry" {
    // The false positive the painted-bounds scan removes: a grow text
    // spanning the header row with centered glyphs. Its FRAME runs
    // under the caption cluster, but nothing inked does — a frame-based
    // scan paid the one retry here, and the remedy's trimmed content
    // box then visibly shifted the centered title left. The scan judges
    // aligned painted bounds, so no retry fires and the layout is
    // byte-identical to an unstamped build: the title's grow frame
    // still spans the full row, cluster and all.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    harness.null_platform.window_chrome = .{
        .insets = .{ .top = 32, .right = 138 },
        .buttons = geometry.RectF.init(262, 0, 138, 32),
    };

    const app_state = try std.testing.allocator.create(CaptionApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CaptionApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-caption-centered",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = captionUpdate,
        .view = captionCenteredView,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 1,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // No reservation fired: the title's frame still spans the whole
    // row. A paid retry would have trimmed the drag row's content box
    // at the cluster's leading edge (262) and re-centered the glyphs
    // inside the trimmed frame — the visible title shift.
    const title = try captionTextFrame(&harness.runtime, "Monitor");
    try std.testing.expectEqual(@as(f32, 0), title.x);
    try std.testing.expectEqual(@as(f32, 400), title.maxX());
}

// -------------------------------------- windowed virtual list fixture

const VirtualFeedModel = struct {
    /// Items currently loaded; `load_more` appends batches to a cap.
    loaded: usize = 400,
    /// Every reach-end dispatch, including the ones past the cap.
    fetches: u32 = 0,
};

const VirtualFeedMsg = union(enum) {
    load_more,
};

const VirtualFeedApp = ui_app_model.UiApp(VirtualFeedModel, VirtualFeedMsg);

const virtual_feed_batch: usize = 100;
const virtual_feed_cap: usize = 600;
const virtual_row_extent: f32 = 24;

fn virtualFeedUpdate(model: *VirtualFeedModel, msg: VirtualFeedMsg) void {
    switch (msg) {
        .load_more => {
            model.fetches += 1;
            if (model.loaded < virtual_feed_cap) model.loaded += virtual_feed_batch;
        },
    }
}

fn virtualFeedOptions(model: *const VirtualFeedModel) VirtualFeedApp.Ui.VirtualListOptions {
    return .{
        .id = "vfeed",
        .item_count = model.loaded,
        .item_extent = virtual_row_extent,
        .overscan = 2,
        .grow = 1,
        .on_reach_end = .load_more,
    };
}

/// The windowed pattern: ask for the visible range, build ONLY those
/// rows from the model, hand both to `virtualList`. Deliberately no
/// `on_scroll` binding — the runtime re-derives the view on scroll for
/// mounted virtual lists on its own.
fn virtualFeedView(ui: *VirtualFeedApp.Ui, model: *const VirtualFeedModel) VirtualFeedApp.Ui.Node {
    const options = virtualFeedOptions(model);
    const window = ui.virtualWindow(options);
    const rows = ui.arena.alloc(VirtualFeedApp.Ui.Node, window.itemCount()) catch {
        ui.failed = true;
        return ui.column(.{}, .{});
    };
    for (rows, 0..) |*row, offset| {
        const index = window.start_index + offset;
        var node = ui.listItem(.{}, ui.fmt("Item {d}", .{index}));
        node.key = .{ .int = @intCast(index) };
        row.* = node;
    }
    return ui.virtualList(options, window, .{rows});
}

fn virtualFeedAppOptions() VirtualFeedApp.Options {
    return .{
        .name = "ui-app-virtual-feed",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = virtualFeedUpdate,
        .view = virtualFeedView,
    };
}

test "windowed virtual list scrolls, re-windows, budgets to the viewport, and fires reach-end with hysteresis" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(VirtualFeedApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = VirtualFeedApp.init(std.heap.page_allocator, .{}, virtualFeedAppOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // The first build materialized only the first window (300pt viewport
    // at 24pt rows + 2 overscan = 15 rows), never the 400 loaded items.
    const list_id = canvas.globalWidgetId(.scroll_view, .{ .str = "vfeed" });
    try std.testing.expectEqual(list_id, app_state.tree.?.root.id);
    try std.testing.expectEqual(@as(usize, 15), app_state.tree.?.root.children.len);
    try std.testing.expectEqual(@as(usize, 0), app_state.tree.?.root.layout.virtual_first_index);

    // A wheel scroll with NO on_scroll binding still re-windows the
    // view: the runtime owns the offset, the scroll observation itself
    // triggers the re-derivation.
    var command_buffer: [96]u8 = undefined;
    var wheel = try std.fmt.bufPrint(&command_buffer, "widget-wheel {s} {d} 240", .{ canvas_label, list_id });
    try harness.runtime.dispatchAutomationCommand(app, wheel);
    try std.testing.expectEqual(@as(usize, 8), app_state.tree.?.root.layout.virtual_first_index);
    try std.testing.expect(findIn(app_state.tree.?.root, .list_item, "Item 8") != null);
    try std.testing.expect(findIn(app_state.tree.?.root, .list_item, "Item 0") == null);
    const retained = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    try std.testing.expectEqual(@as(f32, 240), retained.findById(list_id).?.widget.value);

    // Row identity is the item, not the slot: item 10 was in both
    // windows and kept its structural id across the shift.
    // (The id is derived from the list id and the item key alone.)
    try std.testing.expect(findIn(app_state.tree.?.root, .list_item, "Item 10") != null);

    // Scroll to the end of the loaded 400 items (content 9600, viewport
    // 300, max offset 9300): the approach-end signal fires ONCE and the
    // model appends a batch.
    try std.testing.expectEqual(@as(u32, 0), app_state.model.fetches);
    wheel = try std.fmt.bufPrint(&command_buffer, "widget-wheel {s} {d} 9060", .{ canvas_label, list_id });
    try harness.runtime.dispatchAutomationCommand(app, wheel);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.fetches);
    try std.testing.expectEqual(@as(usize, 500), app_state.model.loaded);

    // The appended batch grew the extent (12_000), pulling the offset
    // out of the band — the next small scroll re-arms, no fire.
    wheel = try std.fmt.bufPrint(&command_buffer, "widget-wheel {s} {d} 24", .{ canvas_label, list_id });
    try harness.runtime.dispatchAutomationCommand(app, wheel);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.fetches);

    // Ride to the new end: fires again (offset 9324 + 2376 = 11_700 =
    // the new max), appends to the 600 cap.
    wheel = try std.fmt.bufPrint(&command_buffer, "widget-wheel {s} {d} 2376", .{ canvas_label, list_id });
    try harness.runtime.dispatchAutomationCommand(app, wheel);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.fetches);
    try std.testing.expectEqual(@as(usize, 600), app_state.model.loaded);

    // At the cap the model stops appending, so the extent stops
    // growing — riding the end must NOT dispatch a fetch storm.
    wheel = try std.fmt.bufPrint(&command_buffer, "widget-wheel {s} {d} 24", .{ canvas_label, list_id });
    try harness.runtime.dispatchAutomationCommand(app, wheel);
    wheel = try std.fmt.bufPrint(&command_buffer, "widget-wheel {s} {d} 2376", .{ canvas_label, list_id });
    try harness.runtime.dispatchAutomationCommand(app, wheel);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.fetches);
    wheel = try std.fmt.bufPrint(&command_buffer, "widget-wheel {s} {d} -24", .{ canvas_label, list_id });
    try harness.runtime.dispatchAutomationCommand(app, wheel);
    wheel = try std.fmt.bufPrint(&command_buffer, "widget-wheel {s} {d} 24", .{ canvas_label, list_id });
    try harness.runtime.dispatchAutomationCommand(app, wheel);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.fetches);

    // Budget stays viewport-sized throughout: ~19 mounted rows against
    // 600 loaded items, and the scroll semantics report the full extent.
    const final_layout = try harness.runtime.canvasWidgetLayout(1, canvas_label);
    try std.testing.expect(final_layout.nodes.len < 40);
    const scroll_state = harness.runtime.views[0].canvasWidgetScrollStateById(list_id).?;
    try std.testing.expectEqual(@as(f32, 600 * virtual_row_extent), scroll_state.content_extent_y);

    // A window-growing resize converges within ONE rebuild: the first
    // build pass reads the stale 300pt viewport, the coverage check sees
    // the fresh 600pt geometry under-covered, and the retry pass builds
    // the wider window against it.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_resized = .{
        .window_id = 1,
        .label = canvas_label,
        .frame = geometry.RectF.init(0, 0, 400, 600),
        .scale_factor = 2,
    } });
    const root = app_state.tree.?.root;
    const first_index = root.layout.virtual_first_index;
    const mounted = root.children.len;
    // Offset clamps to the new max (14_400 - 600 = 13_800): visible rows
    // 575..600 must all be inside the built window.
    try std.testing.expect(first_index <= 575);
    try std.testing.expectEqual(@as(usize, 600), first_index + mounted);
    try std.testing.expect(mounted >= 25);
}

// ------------------------------------------------------- pinch channel

const ZoomModel = struct {
    /// Cumulative gesture scale: the running product of `1 + delta`
    /// across change events — the semantics the channel documents.
    scale: f32 = 1,
    begins: u32 = 0,
    ends: u32 = 0,
    anchor_x: f32 = 0,
    anchor_y: f32 = 0,
    /// Source-identity mirrors: the window and view the last pinch
    /// event named (x/y are view-local, so a coordinate without its
    /// view is not a position — multi-window apps tell pinches apart
    /// by these).
    window_id: u64 = 0,
    label: []const u8 = "",
};

const ZoomMsg = union(enum) {
    pinch: zero_platform.PinchEvent,
};

const ZoomApp = ui_app_model.UiApp(ZoomModel, ZoomMsg);

fn zoomUpdate(model: *ZoomModel, msg: ZoomMsg) void {
    switch (msg) {
        .pinch => |pinch| {
            model.window_id = pinch.window_id;
            model.label = pinch.label;
            switch (pinch.phase) {
                .begin => {
                    model.begins += 1;
                    model.anchor_x = pinch.x;
                    model.anchor_y = pinch.y;
                },
                .change => model.scale *= (1 + pinch.scale),
                .end => model.ends += 1,
            }
        },
    }
}

fn zoomView(ui: *ZoomApp.Ui, model: *const ZoomModel) ZoomApp.Ui.Node {
    return ui.column(.{ .gap = 8, .padding = 12 }, .{
        ui.text(.{}, ui.fmt("Zoom {d:.2}", .{model.scale})),
    });
}

fn zoomPinch(pinch: zero_platform.PinchEvent) ?ZoomMsg {
    return .{ .pinch = pinch };
}

test "trackpad pinch reaches the app through on_pinch with product-of-deltas scale" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(ZoomApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ZoomApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-zoom",
        .scene = counter_scene,
        .canvas_label = canvas_label,
        .update = zoomUpdate,
        .view = zoomView,
        .on_pinch = zoomPinch,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // begin -> change -> change -> change -> end, the host's phase
    // stream: the model hears every phase, the pointer anchor rides
    // x/y, and the cumulative scale is the PRODUCT of (1 + delta) —
    // two +25% steps land on 1.5625, never the 1.45 a sum-of-deltas
    // would produce (deltas are raw NSEvent.magnification, the
    // multiplicative per-event delta per the engine convention; see
    // the doctrine note in appkit_host.m).
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pinch_begin,
        .x = 120,
        .y = 80,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.begins);
    try std.testing.expectEqual(@as(f32, 120), app_state.model.anchor_x);
    try std.testing.expectEqual(@as(f32, 80), app_state.model.anchor_y);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pinch_change,
        .x = 120,
        .y = 80,
        .scale = 0.25,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pinch_change,
        .x = 120,
        .y = 80,
        .scale = 0.25,
    } });
    try std.testing.expectEqual(@as(f32, 1.5625), app_state.model.scale);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.ends);
    // A terminal-delta Ended: AppKit's Ended/Cancelled event still
    // carries the magnification since the previous event, so the host
    // forwards it as one last change BEFORE the end marker — the
    // product folds it in (1.5625 * 1.25 = 1.953125, binary-exact).
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pinch_change,
        .x = 120,
        .y = 80,
        .scale = 0.25,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pinch_end,
        .x = 120,
        .y = 80,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.ends);
    try std.testing.expectEqual(@as(f32, 1.953125), app_state.model.scale);
    // The dispatched Msgs rebuilt the view from the model.
    try std.testing.expect(try retainedTextExists(&harness.runtime, "Zoom 1.95"));

    // A zero-delta Ended is just the end marker: begin then end moves
    // no scale.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pinch_begin,
        .x = 120,
        .y = 80,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pinch_end,
        .x = 120,
        .y = 80,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.model.begins);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.ends);
    try std.testing.expectEqual(@as(f32, 1.953125), app_state.model.scale);

    // Non-pinch raw input never leaks into the channel: a scroll leaves
    // the model untouched.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .scroll,
        .delta_y = 24,
    } });
    try std.testing.expectEqual(@as(f32, 1.953125), app_state.model.scale);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.begins);

    // The automation verb drives the same real events: one full gesture
    // whose change carries scale - 1, anchor defaulting to the view
    // center, so tests and users can pinch without a trackpad.
    app_state.model = .{};
    var command_buffer: [96]u8 = undefined;
    const pinch_default = try std.fmt.bufPrint(&command_buffer, "widget-pinch {s} 1.5", .{canvas_label});
    try harness.runtime.dispatchAutomationCommand(app, pinch_default);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.begins);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.ends);
    try std.testing.expectEqual(@as(f32, 1.5), app_state.model.scale);
    try std.testing.expectEqual(@as(f32, 200), app_state.model.anchor_x);
    try std.testing.expectEqual(@as(f32, 150), app_state.model.anchor_y);

    // An explicit anchor point rides through; the cumulative scale keeps
    // compounding across gestures exactly as deltas compound within one.
    const pinch_at = try std.fmt.bufPrint(&command_buffer, "widget-pinch {s} 0.5 40 60", .{canvas_label});
    try harness.runtime.dispatchAutomationCommand(app, pinch_at);
    try std.testing.expectEqual(@as(f32, 0.75), app_state.model.scale);
    try std.testing.expectEqual(@as(f32, 40), app_state.model.anchor_x);
    try std.testing.expectEqual(@as(f32, 60), app_state.model.anchor_y);

    // A malformed scale is loud driver misuse, not a dispatched gesture
    // (the product of 1 + delta can never reach a non-positive scale).
    const pinch_bad = try std.fmt.bufPrint(&command_buffer, "widget-pinch {s} 0", .{canvas_label});
    try std.testing.expectError(error.InvalidCommand, harness.runtime.dispatchAutomationCommand(app, pinch_bad));
    try std.testing.expectEqual(@as(u32, 2), app_state.model.begins);

    // The f32 wire can betray the parser's `scale > 0` guard: a tiny
    // positive scale rounds `scale - 1` to exactly -1 — factor 0 on the
    // wire — so the dispatch refuses it with the wire-minimum teaching
    // instead of emitting a zoom through zero scale. Nothing dispatches:
    // no begin, no journaled partial gesture.
    const pinch_tiny = try std.fmt.bufPrint(&command_buffer, "widget-pinch {s} 1e-20", .{canvas_label});
    try std.testing.expectError(error.PinchScaleBelowWireMinimum, harness.runtime.dispatchAutomationCommand(app, pinch_tiny));
    try std.testing.expectEqual(@as(u32, 2), app_state.model.begins);
    try std.testing.expectEqual(@as(f32, 0.75), app_state.model.scale);

    // The smallest accepted scale — the first f32 above 2^-25 — round-
    // trips with a positive factor: its delta rounds to -1 + 2^-24, so
    // the factor is exactly 2^-24 and the model's product stays > 0
    // (0.75 * 2^-24 = 0x1.8p-25, binary-exact).
    const pinch_min = try std.fmt.bufPrint(&command_buffer, "widget-pinch {s} 2.9802326e-8", .{canvas_label});
    try harness.runtime.dispatchAutomationCommand(app, pinch_min);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.begins);
    try std.testing.expectEqual(@as(u32, 3), app_state.model.ends);
    try std.testing.expect(app_state.model.scale > 0);
    try std.testing.expectEqual(@as(f32, 0x1.8p-25), app_state.model.scale);
}

const zoom_pair_main_views = [_]app_manifest.ShellView{
    .{ .label = "zoom-main-canvas", .kind = .gpu_surface, .fill = true, .gpu_backend = .metal },
};
const zoom_pair_inspector_views = [_]app_manifest.ShellView{
    .{ .label = "zoom-inspector-canvas", .kind = .gpu_surface, .fill = true, .gpu_backend = .metal },
};
const zoom_pair_windows = [_]app_manifest.ShellWindow{ .{
    .label = "main",
    .title = "Zoom",
    .width = 400,
    .height = 300,
    .views = &zoom_pair_main_views,
}, .{
    .label = "inspector",
    .title = "Zoom Inspector",
    .width = 300,
    .height = 300,
    .views = &zoom_pair_inspector_views,
} };
const zoom_pair_scene: app_manifest.ShellConfig = .{ .windows = &zoom_pair_windows };

test "pinch identity distinguishes windows and views in the Msg" {
    // Two windows, two gpu-surface views: the pinch channel forwards
    // the source identity (window_id + view label) the journaled
    // platform event already carries, so a multi-window app hears WHICH
    // view a gesture zoomed — x/y are view-local, and a coordinate
    // without its view is not a position.
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(ZoomApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ZoomApp.init(std.heap.page_allocator, .{}, .{
        .name = "ui-app-zoom-pair",
        .scene = zoom_pair_scene,
        .canvas_label = "zoom-main-canvas",
        .update = zoomUpdate,
        .view = zoomView,
        .on_pinch = zoomPinch,
    });
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = "zoom-main-canvas",
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try std.testing.expect(app_state.installed);

    // A gesture on the main window's canvas names its source on every
    // phase.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "zoom-main-canvas",
        .kind = .pinch_begin,
        .x = 100,
        .y = 50,
    } });
    try std.testing.expectEqual(@as(u64, 1), app_state.model.window_id);
    try std.testing.expectEqualStrings("zoom-main-canvas", app_state.model.label);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "zoom-main-canvas",
        .kind = .pinch_change,
        .x = 100,
        .y = 50,
        .scale = 0.25,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "zoom-main-canvas",
        .kind = .pinch_end,
        .x = 100,
        .y = 50,
    } });
    try std.testing.expectEqual(@as(f32, 1.25), app_state.model.scale);

    // A gesture on the second window's view is distinguishable: same
    // channel, different identity in the Msg.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 2,
        .label = "zoom-inspector-canvas",
        .kind = .pinch_begin,
        .x = 10,
        .y = 20,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 2,
        .label = "zoom-inspector-canvas",
        .kind = .pinch_change,
        .x = 10,
        .y = 20,
        .scale = -0.5,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 2,
        .label = "zoom-inspector-canvas",
        .kind = .pinch_end,
        .x = 10,
        .y = 20,
    } });
    try std.testing.expectEqual(@as(u64, 2), app_state.model.window_id);
    try std.testing.expectEqualStrings("zoom-inspector-canvas", app_state.model.label);
    // Both gestures compounded the one model's zoom (1.25 * 0.5,
    // binary-exact) and every phase was heard.
    try std.testing.expectEqual(@as(f32, 0.625), app_state.model.scale);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.begins);
    try std.testing.expectEqual(@as(u32, 2), app_state.model.ends);
}

// ------------------------------------------------------------ hover fixture

const HoverEntry = union(enum) {
    outer_enter,
    outer_leave,
    row_enter: u32,
    row_leave: u32,
    vanish_enter,
    vanish_leave,
};

const HoverModel = struct {
    log: [24]HoverEntry = undefined,
    log_len: usize = 0,
    show_vanish: bool = true,
    outer_bound: bool = true,

    fn push(model: *HoverModel, entry: HoverEntry) void {
        if (model.log_len >= model.log.len) return;
        model.log[model.log_len] = entry;
        model.log_len += 1;
    }

    fn entries(model: *const HoverModel) []const HoverEntry {
        return model.log[0..model.log_len];
    }
};

const HoverMsg = union(enum) {
    outer_enter,
    outer_leave,
    row_enter: u32,
    row_leave: u32,
    vanish_enter,
    vanish_leave,
    unbind_outer,
};

const HoverApp = ui_app_model.UiApp(HoverModel, HoverMsg);

fn hoverUpdate(model: *HoverModel, msg: HoverMsg) void {
    switch (msg) {
        .outer_enter => model.push(.outer_enter),
        .outer_leave => model.push(.outer_leave),
        .row_enter => |id| model.push(.{ .row_enter = id }),
        .row_leave => |id| model.push(.{ .row_leave = id }),
        .vanish_enter => {
            // The enter handler unmounts the hovered element: the very
            // next drain pass owes (and delivers) the captured leave.
            model.push(.vanish_enter);
            model.show_vanish = false;
        },
        .vanish_leave => model.push(.vanish_leave),
        .unbind_outer => model.outer_bound = false,
    }
}

/// A listening panel (y 0..120) holding two listening rows (y 0..40 and
/// 40..80; the band at 80..120 is the panel's own), then a listening row
/// (y 120..160) whose ENTER unmounts it. Dead space below. Fixed heights
/// so the tests probe laid-out geometry deterministically.
fn hoverView(ui: *HoverApp.Ui, model: *const HoverModel) HoverApp.Ui.Node {
    const rows = ui.column(.{ .gap = 0 }, .{
        ui.row(.{ .height = 40, .on_hover_enter = .{ .row_enter = 1 }, .on_hover_leave = .{ .row_leave = 1 } }, .{
            ui.text(.{}, "Row one"),
        }),
        ui.row(.{ .height = 40, .on_hover_enter = .{ .row_enter = 2 }, .on_hover_leave = .{ .row_leave = 2 } }, .{
            ui.text(.{}, "Row two"),
        }),
    });
    const outer = if (model.outer_bound)
        ui.panel(.{ .height = 120, .on_hover_enter = .outer_enter, .on_hover_leave = .outer_leave }, .{rows})
    else
        ui.panel(.{ .height = 120 }, .{rows});
    if (model.show_vanish) {
        return ui.column(.{ .gap = 0 }, .{
            outer,
            ui.row(.{ .height = 40, .on_hover_enter = .vanish_enter, .on_hover_leave = .vanish_leave }, .{
                ui.text(.{}, "Vanish"),
            }),
        });
    }
    return ui.column(.{ .gap = 0 }, .{outer});
}

const hover_views = [_]app_manifest.ShellView{
    .{ .label = canvas_label, .kind = .gpu_surface, .fill = true, .gpu_backend = .metal },
};
const hover_windows = [_]app_manifest.ShellWindow{.{
    .label = "main",
    .title = "Hover",
    .width = 400,
    .height = 300,
    .views = &hover_views,
}};
const hover_scene: app_manifest.ShellConfig = .{ .windows = &hover_windows };

fn hoverOptions() HoverApp.Options {
    return .{
        .name = "ui-app-hover",
        .scene = hover_scene,
        .canvas_label = canvas_label,
        .update = hoverUpdate,
        .view = hoverView,
    };
}

fn hoverMove(harness: anytype, app: anytype, x: f32, y: f32) !void {
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_move,
        .x = x,
        .y = y,
    } });
}

fn expectHoverLog(model: *const HoverModel, expected: []const HoverEntry) !void {
    try std.testing.expectEqualSlices(HoverEntry, expected, model.entries());
}

test "hover msgs dispatch containment edges: nested listeners, cancel, and coalesced moves" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(HoverApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = HoverApp.init(std.heap.page_allocator, .{}, hoverOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // Dead space: nothing listens, nothing dispatches, and the runtime
    // holds no standing chain (unbound surfaces pay nothing).
    try hoverMove(harness, app, 50, 220);
    try expectHoverLog(&app_state.model, &.{});
    try std.testing.expectEqual(@as(usize, 0), harness.runtime.views[0].canvas_widget_hover_msg_chain_len);

    // Onto row one (over its plain TEXT child — containment falls
    // through to the listeners): enters fire outermost first.
    try hoverMove(harness, app, 50, 20);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 } });

    // Row one to row two: the shared panel ancestor stays entered — the
    // pointer never left it, the DOM mouseenter/mouseleave rule.
    try hoverMove(harness, app, 50, 60);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 }, .{ .row_leave = 1 }, .{ .row_enter = 2 } });

    // Onto the panel's own band below the rows: only the row leaves.
    try hoverMove(harness, app, 50, 100);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 }, .{ .row_leave = 1 }, .{ .row_enter = 2 }, .{ .row_leave = 2 } });

    // Off the panel entirely.
    try hoverMove(harness, app, 50, 220);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 }, .{ .row_leave = 1 }, .{ .row_enter = 2 }, .{ .row_leave = 2 }, .outer_leave });

    // Back inside, then the pointer LEAVES THE VIEW (the pointer-cancel
    // hosts emit on window exit): leaves fire innermost first.
    app_state.model = .{};
    try hoverMove(harness, app, 50, 60);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 2 } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_cancel,
        .x = 50,
        .y = 60,
    } });
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 2 }, .{ .row_leave = 2 }, .outer_leave });
}

test "unbinding an outer listener leaves it alone: the standing inner row never flickers" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(HoverApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = HoverApp.init(std.heap.page_allocator, .{}, hoverOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // Inside row one: panel and row both stand.
    try hoverMove(harness, app, 50, 20);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 } });

    // A rebuild removes only the PANEL's hover bindings: the panel's
    // leave dispatches, and the row — whose containment never changed —
    // dispatches NOTHING (per-listener containment, not a positional
    // chain).
    try app_state.dispatch(&harness.runtime, 1, .unbind_outer);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 }, .outer_leave });

    // The row's own exit still delivers its captured leave.
    try hoverMove(harness, app, 50, 220);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 }, .outer_leave, .{ .row_leave = 1 } });
}

test "hover enter that unmounts its element still delivers the captured leave" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(HoverApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = HoverApp.init(std.heap.page_allocator, .{}, hoverOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // The enter handler hides the row; the rebuild prunes it from the
    // standing chain and the SAME event's drain delivers the leave Msg
    // captured when the enter dispatched — the live tree can no longer
    // resolve it. No enter without its eventual leave.
    try hoverMove(harness, app, 50, 140);
    try expectHoverLog(&app_state.model, &.{ .vanish_enter, .vanish_leave });
    try std.testing.expectEqual(@as(usize, 0), harness.runtime.views[0].canvas_widget_hover_msg_chain_len);

    // The pointer now stands over dead space (the row is gone); moving
    // again dispatches nothing further.
    try hoverMove(harness, app, 50, 141);
    try expectHoverLog(&app_state.model, &.{ .vanish_enter, .vanish_leave });
}

// ------------------------------------------------- hover-under-scroll fixture

const ScrollHoverModel = struct {
    log: [24]HoverEntry = undefined,
    log_len: usize = 0,

    fn push(model: *ScrollHoverModel, entry: HoverEntry) void {
        if (model.log_len >= model.log.len) return;
        model.log[model.log_len] = entry;
        model.log_len += 1;
    }

    fn entries(model: *const ScrollHoverModel) []const HoverEntry {
        return model.log[0..model.log_len];
    }
};

const ScrollHoverApp = ui_app_model.UiApp(ScrollHoverModel, HoverMsg);

fn scrollHoverUpdate(model: *ScrollHoverModel, msg: HoverMsg) void {
    switch (msg) {
        .row_enter => |id| model.push(.{ .row_enter = id }),
        .row_leave => |id| model.push(.{ .row_leave = id }),
        else => {},
    }
}

/// Ten listening rows of 40 points inside a 120-point scroll viewport:
/// scrolling moves rows under a stationary pointer.
fn scrollHoverView(ui: *ScrollHoverApp.Ui, model: *const ScrollHoverModel) ScrollHoverApp.Ui.Node {
    _ = model;
    var rows: [10]ScrollHoverApp.Ui.Node = undefined;
    for (&rows, 0..) |*node, index| {
        const id: u32 = @intCast(index + 1);
        node.* = ui.row(.{
            .height = 40,
            .key = canvas.uiKey(@as(u64, id)),
            .on_hover_enter = .{ .row_enter = id },
            .on_hover_leave = .{ .row_leave = id },
        }, .{ui.text(.{}, ui.fmt("Row {d}", .{id}))});
    }
    const row_nodes: []const ScrollHoverApp.Ui.Node = rows[0..];
    return ui.scroll(.{ .height = 120 }, .{ui.column(.{ .gap = 0 }, row_nodes)});
}

fn scrollHoverOptions() ScrollHoverApp.Options {
    return .{
        .name = "ui-app-scroll-hover",
        .scene = hover_scene,
        .canvas_label = canvas_label,
        .update = scrollHoverUpdate,
        .view = scrollHoverView,
    };
}

test "hover msgs re-resolve when content scrolls under a stationary pointer" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(ScrollHoverApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = ScrollHoverApp.init(std.heap.page_allocator, .{}, scrollHoverOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // Park the pointer on row 1.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_move,
        .x = 50,
        .y = 20,
    } });
    try std.testing.expectEqualSlices(HoverEntry, &.{.{ .row_enter = 1 }}, app_state.model.entries());

    // A wheel scroll of 80 points slides row 3 under the stationary
    // pointer: the same leave/enter a real move off row 1 onto row 3
    // would dispatch — content moved, containment followed.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .scroll,
        .timestamp_ns = 2_000_000,
        .x = 50,
        .y = 20,
        .delta_y = 80,
    } });
    try std.testing.expectEqualSlices(HoverEntry, &.{ .{ .row_enter = 1 }, .{ .row_leave = 1 }, .{ .row_enter = 3 } }, app_state.model.entries());
}

test "touch-shaped input (down/up/drag without hover) never synthesizes hover msgs" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(HoverApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = HoverApp.init(std.heap.page_allocator, .{}, hoverOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // A tap (down/up) and a scrub (drag) over a listening row — the
    // touch input shape, which never includes a hover-phase move —
    // dispatch no hover Msgs and leave no standing containment: touch
    // cannot hover, mechanically.
    for ([_]zero_platform.GpuSurfaceInputKind{ .pointer_down, .pointer_drag, .pointer_up }) |kind| {
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
            .window_id = 1,
            .label = canvas_label,
            .kind = kind,
            .x = 50,
            .y = 20,
        } });
    }
    try expectHoverLog(&app_state.model, &.{});
    try std.testing.expectEqual(@as(usize, 0), harness.runtime.views[0].canvas_widget_hover_msg_chain_len);

    // A TOUCH-STAMPED hover-phase move (the mouse-shaped float Windows
    // synthesizes for taps, marked by the host with the reserved
    // pointer-id bit) earns no proof either: the fake float dispatches
    // nothing and leaves no standing containment.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_move,
        .pointer_id = zero_platform.touch_pointer_id_bit | 1,
        .x = 50,
        .y = 20,
    } });
    try expectHoverLog(&app_state.model, &.{});
    try std.testing.expectEqual(@as(usize, 0), harness.runtime.views[0].canvas_widget_hover_msg_chain_len);

    // A hover-phase move proves a hover-capable pointer; from then on
    // clicks and drags keep full mouse fidelity.
    try hoverMove(harness, app, 50, 20);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_drag,
        .x = 50,
        .y = 220,
    } });
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 }, .{ .row_leave = 1 }, .outer_leave });
}

// ------------------------------------------------ hover capture fixture

const CaptureModel = struct {
    churns: u32 = 0,
    show_row: bool = true,
    bind_leave: bool = true,
    exploded: bool = false,
    two_entered: u32 = 0,
    two_left: u32 = 0,
    field: canvas.TextBuffer(16) = .{},
    left_storage: [32]u8 = [_]u8{0} ** 32,
    left_len: usize = 0,

    fn leftText(model: *const CaptureModel) []const u8 {
        return model.left_storage[0..model.left_len];
    }

    pub fn fieldText(model: *const CaptureModel) []const u8 {
        return model.field.text();
    }
};

const CaptureMsg = union(enum) {
    churn,
    hide_row,
    entered,
    left: []const u8,
    entered_two,
    left_two,
    bind_leave_now,
    explode,
    calm,
    field_edit: canvas.TextInputEvent,
};

const CaptureApp = ui_app_model.UiApp(CaptureModel, CaptureMsg);

fn captureUpdate(model: *CaptureModel, msg: CaptureMsg) void {
    switch (msg) {
        .churn => model.churns += 1,
        .hide_row => model.show_row = false,
        .entered => {},
        .left => |text| {
            const len = @min(text.len, model.left_storage.len);
            @memcpy(model.left_storage[0..len], text[0..len]);
            model.left_len = len;
        },
        .entered_two => model.two_entered += 1,
        .left_two => model.two_left += 1,
        .bind_leave_now => model.bind_leave = true,
        .explode => model.exploded = true,
        .calm => model.exploded = false,
        .field_edit => |edit| {
            // Any edit — the standalone accessibility selection verb
            // included — unmounts the hovered row, the shape that
            // strands a leave if the standalone event skips the drain.
            model.field.apply(edit);
            model.show_row = false;
        },
    }
}

/// The leave Msg's payload is a `ui.fmt` slice — build-arena bytes that
/// the arena pair recycles two builds later. The capture must own them.
/// `exploded` renders a view past the per-view widget-node budget, so
/// its rebuild FAILS AT PUBLICATION — `self.tree` moved to a build the
/// runtime never adopted, the exact stale-tree seam the hover drain's
/// currency invariant covers.
fn captureView(ui: *CaptureApp.Ui, model: *const CaptureModel) CaptureApp.Ui.Node {
    if (model.exploded) {
        var rows: [1100]CaptureApp.Ui.Node = undefined;
        for (&rows, 0..) |*node, index| {
            node.* = ui.text(.{ .key = canvas.uiKey(@as(u64, index)) }, "x");
        }
        const row_nodes: []const CaptureApp.Ui.Node = rows[0..];
        return ui.column(.{ .gap = 0 }, row_nodes);
    }
    if (!model.show_row) {
        return ui.column(.{ .gap = 0 }, .{ui.text(.{}, ui.fmt("churn {d}", .{model.churns}))});
    }
    return ui.column(.{ .gap = 0 }, .{
        ui.row(.{
            .height = 40,
            .on_hover_enter = .entered,
            .on_hover_leave = if (model.bind_leave) CaptureMsg{ .left = ui.fmt("left-after-{d}-churns", .{model.churns}) } else null,
        }, .{ui.text(.{}, "Capture row")}),
        ui.row(.{
            .height = 40,
            .on_hover_enter = .entered_two,
            .on_hover_leave = .left_two,
        }, .{ui.text(.{}, "Second row")}),
        ui.el(.text_field, .{
            .height = 30,
            .text = model.fieldText(),
            .placeholder = "notes",
            .on_input = CaptureApp.Ui.inputMsg(.field_edit),
        }, .{}),
        ui.text(.{}, ui.fmt("churn {d}", .{model.churns})),
    });
}

fn captureCommand(name: []const u8) ?CaptureMsg {
    if (std.mem.eql(u8, name, "capture.churn")) return .churn;
    if (std.mem.eql(u8, name, "capture.hide")) return .hide_row;
    return null;
}

fn captureOptions() CaptureApp.Options {
    return .{
        .name = "ui-app-hover-capture",
        .scene = hover_scene,
        .canvas_label = canvas_label,
        .update = captureUpdate,
        .view = captureView,
        .on_command = captureCommand,
    };
}

test "a captured hover-leave msg owns its arena payload across rebuilds and unmount" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CaptureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CaptureApp.init(std.heap.page_allocator, .{}, captureOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // Enter the row: its leave Msg carries a `ui.fmt` payload built in
    // THIS build's arena.
    try hoverMove(harness, app, 50, 20);

    // Churn rebuilds until the arena pair has recycled the bytes the
    // enter-time payload lived in. The capture deep-copies each build's
    // LATEST binding (kept fresh as the trees change), so the payload
    // is always owned bytes, never a pointer into a recycled arena.
    for (0..3) |_| {
        try harness.runtime.dispatchPlatformEvent(app, .{ .menu_command = .{ .name = "capture.churn", .window_id = 1 } });
    }
    try std.testing.expectEqual(@as(u32, 3), app_state.model.churns);

    // Unmount the row under the stationary pointer: the captured leave
    // dispatches the last build's binding, bytes intact.
    try harness.runtime.dispatchPlatformEvent(app, .{ .menu_command = .{ .name = "capture.hide", .window_id = 1 } });
    try std.testing.expectEqualStrings("left-after-3-churns", app_state.model.leftText());
}

test "a direct dispatch that unmounts the hovered element delivers its leave immediately" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CaptureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CaptureApp.init(std.heap.page_allocator, .{}, captureOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try hoverMove(harness, app, 50, 20);

    // A DIRECT dispatch (a command handler, an embedder, a test) that
    // unmounts the hovered row settles the leave at its own tail — no
    // later platform event required.
    try app_state.dispatch(&harness.runtime, 1, .hide_row);
    try std.testing.expectEqualStrings("left-after-0-churns", app_state.model.leftText());
}

test "another pointer's contact never rides a mouse's hover proof" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(HoverApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = HoverApp.init(std.heap.page_allocator, .{}, hoverOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // The mouse (pointer 0) proves hover over row one.
    try hoverMove(harness, app, 50, 20);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 } });

    // A touch contact (another pointer id) taps row two and lifts with
    // a cancel: on a host that distinguishes pointers, none of it moves
    // the mouse's standing containment.
    for ([_]zero_platform.GpuSurfaceInputKind{ .pointer_down, .pointer_up, .pointer_cancel }) |kind| {
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
            .window_id = 1,
            .label = canvas_label,
            .kind = kind,
            .pointer_id = 7,
            .x = 50,
            .y = 60,
        } });
    }
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 } });

    // The mouse's own departure retires it.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_cancel,
        .x = 50,
        .y = 20,
    } });
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 }, .{ .row_leave = 1 }, .outer_leave });
}

test "a leave handler added while the listener stands is captured before the unmount needs it" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CaptureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CaptureApp.init(std.heap.page_allocator, .{}, captureOptions());
    defer app_state.deinit();
    app_state.model.bind_leave = false;
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // Enter with NO leave handler bound: nothing to capture yet.
    try hoverMove(harness, app, 50, 20);
    try std.testing.expectEqualStrings("", app_state.model.leftText());

    // A rebuild ADDS the leave binding while the listener stands: the
    // capture refresh picks it up (id-identical chains included), so
    // the later unmount can still deliver the pair.
    try app_state.dispatch(&harness.runtime, 1, .bind_leave_now);
    try app_state.dispatch(&harness.runtime, 1, .hide_row);
    try std.testing.expectEqualStrings("left-after-0-churns", app_state.model.leftText());
}

test "hover edges defer while a tree is stale and deliver after recovery" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CaptureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CaptureApp.init(std.heap.page_allocator, .{}, captureOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // White-box: mark the main tree stale, the state a failure INSIDE
    // the install window leaves behind (publication adopted, handler
    // tree not yet) — the narrow seam no budget lever reaches
    // deterministically from outside. An enter-only transition then
    // has nothing deliverable: the enter DEFERS rather than resolving
    // through the stale tree (any owed leave would still dispatch and
    // its rebuild would heal the staleness — the pure-enter case is
    // the one that must wait).
    app_state.main_tree_current = false;
    try hoverMove(harness, app, 50, 60);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.two_entered);

    // Recovery: the next successful rebuild restores currency at the
    // install seam, and its own drain delivers the deferred enter.
    try app_state.dispatch(&harness.runtime, 1, .churn);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.two_entered);

    // Leaves never wait on currency: stale again, moving off the row
    // still delivers the captured leave — and that dispatch's own
    // successful rebuild heals the flag at the install seam.
    app_state.main_tree_current = false;
    try hoverMove(harness, app, 50, 220);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.two_left);
    try std.testing.expect(app_state.main_tree_current);
}

test "a failed build keeps the still-matching pair live: edges flow through it" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CaptureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CaptureApp.init(std.heap.page_allocator, .{}, captureOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try hoverMove(harness, app, 50, 20);

    // The explode rebuild fails at LAYOUT — before publication, before
    // `self.tree` moves — so the OLD handler tree still matches the
    // runtime's retained state and stays CURRENT: a failure outside
    // the install window must never defer hover edges (an idle app
    // might perform no further rebuild to lift the deferral).
    if (app_state.dispatch(&harness.runtime, 1, .explode)) |_| {
        return error.TestUnexpectedResult;
    } else |_| {}
    try std.testing.expect(app_state.model.exploded);

    // The pointer moves to the second row (hit-tested against the
    // retained tree the failed build never replaced): row one's
    // captured leave AND row two's enter both flow through the
    // still-matching pair. Each edge's own dispatch rebuild fails (the
    // model remains exploded), so the event ERRORS after the Msgs
    // landed — the degradation contract: edges delivered, failures
    // loud.
    if (hoverMove(harness, app, 50, 60)) |_| {
        return error.TestUnexpectedResult;
    } else |_| {}
    try std.testing.expectEqualStrings("left-after-0-churns", app_state.model.leftText());
    try std.testing.expectEqual(@as(u32, 1), app_state.model.two_entered);

    // Recovery is clean: a successful rebuild changes nothing owed.
    try app_state.dispatch(&harness.runtime, 1, .calm);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.two_entered);

    // And the pair completes on exit.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = canvas_label,
        .kind = .pointer_cancel,
        .x = 50,
        .y = 60,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.model.two_left);
}

// -------------------------------------------- over-aligned payload probe

const AlignedModel = struct {
    bytes: [64]u8 align(64) = [_]u8{'a'} ** 64,
    show_row: bool = true,
    left_len: usize = 0,
    z_payload: [4]u8 = [_]u8{0} ** 4,

    fn payload(model: *const AlignedModel) []align(64) const u8 {
        return @alignCast(model.bytes[0..8]);
    }
};

const AlignedMsg = union(enum) {
    entered,
    left: []align(64) const u8,
    left_z: [3:0]u8,
    hide,
};

/// Markup-free on purpose: the runtime markup interpreter's coercion
/// vocabulary is plain `[]const u8`, so the over-aligned payload rides a
/// builder-only app — which is exactly where such a payload can exist.
const AlignedApp = ui_app_model.UiAppWithFeatures(AlignedModel, AlignedMsg, .{ .runtime_markup = false });

fn alignedUpdate(model: *AlignedModel, msg: AlignedMsg) void {
    switch (msg) {
        .entered => {},
        .left => |bytes| model.left_len = bytes.len,
        .left_z => |arr| {
            // Record the declared elements AND the sentinel storage the
            // copy must have stamped.
            model.z_payload = .{ arr[0], arr[1], arr[2], arr[3] };
        },
        .hide => model.show_row = false,
    }
}

fn alignedView(ui: *AlignedApp.Ui, model: *const AlignedModel) AlignedApp.Ui.Node {
    if (!model.show_row) return ui.column(.{}, .{ui.text(.{}, "empty")});
    return ui.column(.{ .gap = 0 }, .{
        ui.row(.{
            .height = 40,
            .on_hover_enter = .entered,
            .on_hover_leave = .{ .left = model.payload() },
        }, .{ui.text(.{}, "Aligned row")}),
        ui.row(.{
            .height = 40,
            .on_hover_enter = .entered,
            .on_hover_leave = .{ .left_z = .{ 'x', 'y', 'z' } },
        }, .{ui.text(.{}, "Sentinel row")}),
    });
}

fn alignedOptions() AlignedApp.Options {
    return .{
        .name = "ui-app-hover-aligned",
        .scene = hover_scene,
        .canvas_label = canvas_label,
        .update = alignedUpdate,
        .view = alignedView,
    };
}

test "an over-aligned slice leave payload captures, survives unmount, and delivers" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(AlignedApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = AlignedApp.init(std.heap.page_allocator, .{}, alignedOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // The capture deep-copies a `[]align(64) const u8` payload (the
    // alignment-preserving allocation path), and the unmount-driven
    // leave delivers it.
    try hoverMove(harness, app, 50, 20);
    try app_state.dispatch(&harness.runtime, 1, .hide);
    try std.testing.expectEqual(@as(usize, 8), app_state.model.left_len);
}

test "a standalone accessibility edit that unmounts the hovered listener drains its leave" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CaptureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CaptureApp.init(std.heap.page_allocator, .{}, captureOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try hoverMove(harness, app, 50, 20);

    // The accessibility selection edit dispatches a STANDALONE keyboard
    // event — no gpu-surface input cycle follows it — and its Msg
    // unmounts the hovered row: the drain must run at the standalone
    // event's own tail, or the leave strands until an unrelated event.
    const field_id = findWidgetIdByText(app_state.tree.?, .text_field, "").?;
    var command_buffer: [96]u8 = undefined;
    const command = try std.fmt.bufPrint(&command_buffer, "widget-action {s} {d} set-selection 0 0", .{ canvas_label, field_id });
    try harness.runtime.dispatchAutomationCommand(app, command);
    try std.testing.expect(!app_state.model.show_row);
    try std.testing.expectEqualStrings("left-after-0-churns", app_state.model.leftText());
}

test "two dispatches in one cycle deliver the LATEST leave binding on unmount" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(CaptureApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = CaptureApp.init(std.heap.page_allocator, .{}, captureOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });
    try hoverMove(harness, app, 50, 20);

    // White-box cycle shape: two dispatches with the drain deferred to
    // the cycle's end (an event handler dispatching twice). The first
    // moves the leave payload, the second unmounts — the capture must
    // refresh at EACH rebuild commit, not just at drain time against
    // the final (unmounted) tree.
    app_state.hover_msg_event_depth = 1;
    try app_state.dispatch(&harness.runtime, 1, .churn);
    try app_state.dispatch(&harness.runtime, 1, .hide_row);
    app_state.hover_msg_event_depth = 0;
    try harness.runtime.dispatchPlatformEvent(app, .wake);
    try std.testing.expectEqualStrings("left-after-1-churns", app_state.model.leftText());
}

test "a secondary drag released outside the view retires the entered chain" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(HoverApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = HoverApp.init(std.heap.page_allocator, .{}, hoverOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    try hoverMove(harness, app, 50, 20);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 } });

    // A right-drag: the consumed secondary stream freezes containment
    // through the gesture (a menu gesture is not hovering) — but a
    // release OUTSIDE the view is the pointer already gone, the leave
    // the freeze suppressed. No motion-leave will fire (the implicit
    // grab held it), so the release itself must retire the chain.
    for ([_]struct { kind: zero_platform.GpuSurfaceInputKind, x: f32, y: f32 }{
        .{ .kind = .pointer_down, .x = 50, .y = 20 },
        .{ .kind = .pointer_drag, .x = 450, .y = 320 },
        .{ .kind = .pointer_up, .x = 450, .y = 320 },
    }) |step| {
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
            .window_id = 1,
            .label = canvas_label,
            .kind = step.kind,
            .button = 1,
            .x = step.x,
            .y = step.y,
        } });
    }
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 }, .{ .row_leave = 1 }, .outer_leave });
    try std.testing.expectEqual(@as(usize, 0), harness.runtime.views[0].canvas_widget_hover_msg_chain_len);
}

test "repeated re-entry with rebuilding edge handlers never exhausts capture slots" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(HoverApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = HoverApp.init(std.heap.page_allocator, .{}, hoverOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // Every edge Msg rebuilds, and each rebuild's capture refresh can
    // fill a mid-transition slot the enter loop then replaces: cycling
    // far past the slot budget proves replacement always releases —
    // a leaked claim per cycle would exhaust the slots and trap.
    for (0..40) |_| {
        try hoverMove(harness, app, 50, 60);
        try hoverMove(harness, app, 50, 220);
    }
    var used: usize = 0;
    for (app_state.hover_msg_slot_used) |slot_used| used += @intFromBool(slot_used);
    try std.testing.expectEqual(@as(usize, 0), used);
}

// ---------------------------------------------- unownable payload probe

const PtrModel = struct {
    entered: u32 = 0,
    anchor: u32 = 7,
    bind_ptr: bool = true,
};

const PtrMsg = union(enum) {
    entered,
    left_ptr: *const u32,
    bind_ptr_now,
};

/// Markup-free like the aligned probe: a single-item-pointer payload is
/// builder-only territory.
const PtrApp = ui_app_model.UiAppWithFeatures(PtrModel, PtrMsg, .{ .runtime_markup = false });

fn ptrUpdate(model: *PtrModel, msg: PtrMsg) void {
    switch (msg) {
        .entered => model.entered += 1,
        .left_ptr => {},
        .bind_ptr_now => model.bind_ptr = true,
    }
}

fn ptrView(ui: *PtrApp.Ui, model: *const PtrModel) PtrApp.Ui.Node {
    return ui.column(.{ .gap = 0 }, .{
        ui.row(.{
            .height = 40,
            .on_hover_enter = .entered,
            .on_hover_leave = if (model.bind_ptr) PtrMsg{ .left_ptr = &model.anchor } else null,
        }, .{ui.text(.{}, "Pointer row")}),
    });
}

fn ptrOptions() PtrApp.Options {
    return .{
        .name = "ui-app-hover-ptr",
        .scene = hover_scene,
        .canvas_label = canvas_label,
        .update = ptrUpdate,
        .view = ptrView,
    };
}

test "an unownable leave payload disables the pair: no enter, no dangling leave" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(PtrApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = PtrApp.init(std.heap.page_allocator, .{}, ptrOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // The leave payload is a single-item pointer no copy can own: the
    // PAIR is refused — the enter never dispatches (no enter without a
    // deliverable leave), the exit owes nothing, and the refusal
    // settles instead of retrying every event.
    try hoverMove(harness, app, 50, 20);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.entered);
    try hoverMove(harness, app, 50, 220);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.entered);

    // Re-entry retries the refusal (a rebind could have made the
    // payload ownable); with the same binding it refuses identically.
    try hoverMove(harness, app, 50, 20);
    try std.testing.expectEqual(@as(u32, 0), app_state.model.entered);
}

test "a sentinel-terminated array payload captures with its sentinel stamped" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(AlignedApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = AlignedApp.init(std.heap.page_allocator, .{}, alignedOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // Hover the sentinel row, unmount, and read the delivered array's
    // FULL storage: the declared elements and the zero sentinel one
    // past them — never undefined bytes.
    try hoverMove(harness, app, 50, 60);
    try app_state.dispatch(&harness.runtime, 1, .hide);
    try std.testing.expectEqualSlices(u8, &.{ 'x', 'y', 'z', 0 }, &app_state.model.z_payload);
}

test "an unownable leave rebind on a standing element degrades loudly, never traps" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(PtrApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = PtrApp.init(std.heap.page_allocator, .{}, ptrOptions());
    defer app_state.deinit();
    app_state.model.bind_ptr = false;
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    // Enter with NO leave bound: the enter dispatches (an ownable-free
    // pair is fine).
    try hoverMove(harness, app, 50, 20);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.entered);

    // A rebuild BINDS an unownable leave while the element stands: the
    // refresh degrades with a warning (once) and the mirror settles —
    // subsequent rebuilds and moves neither trap nor re-enter.
    try app_state.dispatch(&harness.runtime, 1, .bind_ptr_now);
    try hoverMove(harness, app, 55, 20);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.entered);

    // The exit resolves the leave from the live tree (the element still
    // stands), so the standing pair still closes — through live
    // resolution rather than a capture.
    try hoverMove(harness, app, 50, 220);
    try std.testing.expectEqual(@as(u32, 1), app_state.model.entered);
}

test "a primary drag released outside the view retires containment and its proof" {
    const harness = try core.TestHarness().create(std.testing.allocator, .{ .size = geometry.SizeF.init(400, 300) });
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;

    const app_state = try std.testing.allocator.create(HoverApp);
    defer std.testing.allocator.destroy(app_state);
    app_state.* = HoverApp.init(std.heap.page_allocator, .{}, hoverOptions());
    defer app_state.deinit();
    const app = app_state.app();
    try harness.start(app);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_frame = .{
        .label = canvas_label,
        .size = geometry.SizeF.init(400, 300),
        .scale_factor = 2,
        .frame_index = 1,
        .timestamp_ns = 1_000_000,
        .nonblank = true,
    } });

    try hoverMove(harness, app, 50, 20);
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 } });

    // A primary drag released beyond the surface: hosts held a grab
    // through it (no motion-leave fired) and send no later cancel, so
    // the release itself retires containment AND the hover proof — an
    // overflowing listener can never stay entered off-view, and no
    // rebuild can re-enter one at a parked off-view anchor.
    for ([_]struct { kind: zero_platform.GpuSurfaceInputKind, x: f32, y: f32 }{
        .{ .kind = .pointer_down, .x = 50, .y = 20 },
        .{ .kind = .pointer_drag, .x = 450, .y = 320 },
        .{ .kind = .pointer_up, .x = 450, .y = 320 },
    }) |step| {
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
            .window_id = 1,
            .label = canvas_label,
            .kind = step.kind,
            .button = 0,
            .x = step.x,
            .y = step.y,
        } });
    }
    try expectHoverLog(&app_state.model, &.{ .outer_enter, .{ .row_enter = 1 }, .{ .row_leave = 1 }, .outer_leave });
    try std.testing.expectEqual(@as(usize, 0), harness.runtime.views[0].canvas_widget_hover_msg_chain_len);
    try std.testing.expect(!harness.runtime.views[0].canvas_widget_hover_pointer_live);
}
