const support = @import("test_support.zig");
const std = support.std;
const geometry = support.geometry;
const trace = support.trace;
const json = support.json;
const canvas = support.canvas;
const automation = support.automation;
const bridge = support.bridge;
const app_manifest = support.app_manifest;
const platform = support.platform;
const security = support.security;
const extensions = support.extensions;
const window_state = support.window_state;
const runtime_module = support.runtime_module;
const bridge_payload = support.bridge_payload;
const canvas_frame = support.canvas_frame;
const App = support.App;
const Runtime = support.Runtime;
const Options = support.Options;
const Event = support.Event;
const LifecycleEvent = support.LifecycleEvent;
const CommandEvent = support.CommandEvent;
const Command = support.Command;
const CommandSource = support.CommandSource;
const FrameDiagnostics = support.FrameDiagnostics;
const ShortcutEvent = support.ShortcutEvent;
const Appearance = support.Appearance;
const GpuFrame = support.GpuFrame;
const GpuSurfaceFrameEvent = support.GpuSurfaceFrameEvent;
const GpuSurfaceResizeEvent = support.GpuSurfaceResizeEvent;
const GpuSurfaceInputEvent = support.GpuSurfaceInputEvent;
const CanvasWidgetPointerEvent = support.CanvasWidgetPointerEvent;
const CanvasWidgetKeyboardEvent = support.CanvasWidgetKeyboardEvent;
const CanvasWidgetDisplayListChrome = support.CanvasWidgetDisplayListChrome;
const CanvasPresentationMode = support.CanvasPresentationMode;
const CanvasPresentationResult = support.CanvasPresentationResult;
const CanvasWidgetAccessibilityActionKind = support.CanvasWidgetAccessibilityActionKind;
const CanvasWidgetAccessibilityAction = support.CanvasWidgetAccessibilityAction;
const CanvasWidgetFileDropEvent = support.CanvasWidgetFileDropEvent;
const CanvasWidgetDragEvent = support.CanvasWidgetDragEvent;
const InvalidationReason = support.InvalidationReason;
const TestHarness = support.TestHarness;
const max_canvas_commands_per_view = support.max_canvas_commands_per_view;
const max_canvas_widget_nodes_per_view = support.max_canvas_widget_nodes_per_view;
const jsonStringField = support.jsonStringField;
const jsonNumberField = support.jsonNumberField;
const jsonBoolField = support.jsonBoolField;
const canvasRenderAnimationFinalOverrideNoop = support.canvasRenderAnimationFinalOverrideNoop;
const copyInto = support.copyInto;
const writeViewJson = support.writeViewJson;
const canvasFrameScratchStorage = support.canvasFrameScratchStorage;
const runtimeViewInfo = support.runtimeViewInfo;
const runtimeViewCanvasFrameRenderOverrides = support.runtimeViewCanvasFrameRenderOverrides;
const runtimeViewCanvasRenderAnimationDirtyBoundsForOverrides = support.runtimeViewCanvasRenderAnimationDirtyBoundsForOverrides;
const runtimeViewWidgetSemantics = support.runtimeViewWidgetSemantics;
const runtimeViewSetCanvasWidgetSelected = support.runtimeViewSetCanvasWidgetSelected;
const runtimeViewCanvasWidgetDirtyBounds = support.runtimeViewCanvasWidgetDirtyBounds;
const dispatchAutomationWidgetAction = support.dispatchAutomationWidgetAction;
const shellBoundsForWindow = support.shellBoundsForWindow;
const reloadWindows = support.reloadWindows;
const canvasWidgetSemanticsById = support.canvasWidgetSemanticsById;
const platformWidgetAccessibilityNodeById = support.platformWidgetAccessibilityNodeById;
const builtinBridgeErrorCode = support.builtinBridgeErrorCode;
const builtinBridgeErrorMessage = support.builtinBridgeErrorMessage;
const testViewByLabel = support.testViewByLabel;
const testCanvasWidgetPartId = support.testCanvasWidgetPartId;

test "runtime tracks retained canvas widget cursor intent" {
    const TestApp = struct {
        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-cursor", .source = platform.WebViewSource.html("<h1>GPU</h1>") };
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 160),
    });

    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .button, .frame = geometry.RectF.init(10, 12, 96, 32), .text = "Run" },
        .{ .id = 3, .kind = .text_field, .frame = geometry.RectF.init(10, 52, 140, 32), .text = "Query" },
        .{ .id = 4, .kind = .slider, .frame = geometry.RectF.init(10, 96, 140, 24), .value = 0.5 },
        .{ .id = 5, .kind = .split_divider, .frame = geometry.RectF.init(10, 128, 140, 16) },
    };
    var nodes: [6]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .panel, .children = &children }, geometry.RectF.init(0, 0, 240, 160), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    var snapshot = harness.runtime.automationSnapshot("Cursor");
    try std.testing.expectEqual(platform.Cursor.arrow, testViewByLabel(snapshot.views, "canvas").?.cursor);
    try std.testing.expectEqual(@as(usize, 0), harness.null_platform.view_cursor_count);

    // Buttons follow the NATIVE cursor register: the arrow, exactly like
    // the platform's own controls. Hovering one changes nothing on the
    // cursor channel — no platform write happens — because the pointing
    // hand is a hyperlink affordance, not a control affordance.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{ .window_id = 1, .label = "canvas", .kind = .pointer_move, .x = 20, .y = 24 } });
    snapshot = harness.runtime.automationSnapshot("Cursor");
    try std.testing.expectEqual(platform.Cursor.arrow, testViewByLabel(snapshot.views, "canvas").?.cursor);
    try std.testing.expectEqual(@as(usize, 0), harness.null_platform.view_cursor_count);

    // Editable text keeps the I-beam — the first hover that actually
    // moves the channel, so the platform write (with window and label)
    // is observable here.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{ .window_id = 1, .label = "canvas", .kind = .pointer_move, .x = 20, .y = 64 } });
    snapshot = harness.runtime.automationSnapshot("Cursor");
    try std.testing.expectEqual(platform.Cursor.text, testViewByLabel(snapshot.views, "canvas").?.cursor);
    try std.testing.expectEqual(@as(usize, 1), harness.null_platform.view_cursor_count);
    try std.testing.expectEqual(platform.Cursor.text, harness.null_platform.view_cursor);
    try std.testing.expectEqual(@as(platform.WindowId, 1), harness.null_platform.view_cursor_window_id);
    try std.testing.expectEqualStrings("canvas", harness.null_platform.view_cursor_label_storage[0..harness.null_platform.view_cursor_label_len]);

    const disabled_children = [_]canvas.Widget{
        .{ .id = 2, .kind = .button, .frame = geometry.RectF.init(10, 12, 96, 32), .text = "Run" },
        .{ .id = 3, .kind = .text_field, .frame = geometry.RectF.init(10, 52, 140, 32), .text = "Query", .state = .{ .disabled = true } },
        .{ .id = 4, .kind = .slider, .frame = geometry.RectF.init(10, 96, 140, 24), .value = 0.5 },
        .{ .id = 5, .kind = .split_divider, .frame = geometry.RectF.init(10, 128, 140, 16) },
    };
    var disabled_nodes: [6]canvas.WidgetLayoutNode = undefined;
    const disabled_layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .panel, .children = &disabled_children }, geometry.RectF.init(0, 0, 240, 160), &disabled_nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", disabled_layout);
    snapshot = harness.runtime.automationSnapshot("Cursor");
    try std.testing.expectEqual(platform.Cursor.arrow, testViewByLabel(snapshot.views, "canvas").?.cursor);
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.view_cursor_count);
    try std.testing.expectEqual(platform.Cursor.arrow, harness.null_platform.view_cursor);

    // Sliders keep the arrow too — at rest AND during a drag — matching
    // native sliders on every platform. No cursor write fires.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{ .window_id = 1, .label = "canvas", .kind = .pointer_move, .x = 20, .y = 108 } });
    snapshot = harness.runtime.automationSnapshot("Cursor");
    try std.testing.expectEqual(platform.Cursor.arrow, testViewByLabel(snapshot.views, "canvas").?.cursor);
    try std.testing.expectEqual(@as(usize, 2), harness.null_platform.view_cursor_count);

    // Split dividers are the resize affordance — that part of the
    // register is unchanged.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{ .window_id = 1, .label = "canvas", .kind = .pointer_move, .x = 20, .y = 134 } });
    snapshot = harness.runtime.automationSnapshot("Cursor");
    const canvas_view = testViewByLabel(snapshot.views, "canvas").?;
    try std.testing.expectEqual(platform.Cursor.resize_horizontal, canvas_view.cursor);
    try std.testing.expectEqual(@as(usize, 3), harness.null_platform.view_cursor_count);
    try std.testing.expectEqual(platform.Cursor.resize_horizontal, harness.null_platform.view_cursor);

    var view_json_buffer: [4096]u8 = undefined;
    const view_json = try writeViewJson(canvas_view, &view_json_buffer);
    try std.testing.expect(std.mem.indexOf(u8, view_json, "\"cursor\":\"resize_horizontal\"") != null);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{ .window_id = 1, .label = "canvas", .kind = .pointer_move, .x = 220, .y = 148 } });
    snapshot = harness.runtime.automationSnapshot("Cursor");
    try std.testing.expectEqual(platform.Cursor.arrow, testViewByLabel(snapshot.views, "canvas").?.cursor);
    try std.testing.expectEqual(@as(usize, 4), harness.null_platform.view_cursor_count);
    try std.testing.expectEqual(platform.Cursor.arrow, harness.null_platform.view_cursor);
}

test "composite list row hovers as one surface: wash, cursor, and pressed wash cover the row" {
    const TestApp = struct {
        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-row-hover", .source = platform.WebViewSource.html("<h1>GPU</h1>") };
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 320, 160),
    });

    // The two-line list row shape (title + snippet) the showcase list
    // panes render: text children are hit targets (selection), but hover
    // must attribute to the row.
    const snippet = canvas.Widget{ .id = 6, .kind = .text, .frame = geometry.RectF.init(0, 0, 160, 20), .text = "Snippet line" };
    const inner_row = canvas.Widget{ .id = 5, .kind = .row, .frame = geometry.RectF.init(0, 0, 0, 20), .children = &.{snippet} };
    const title = canvas.Widget{ .id = 4, .kind = .text, .frame = geometry.RectF.init(0, 0, 120, 20), .text = "Title" };
    const column = canvas.Widget{ .id = 3, .kind = .column, .layout = .{ .gap = 8 }, .children = &.{ title, inner_row } };
    const row = canvas.Widget{ .id = 2, .kind = .list_item, .frame = geometry.RectF.init(0, 0, 0, 72), .layout = .{ .padding = geometry.InsetsF.all(10) }, .children = &.{column} };
    var nodes: [6]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &.{row} }, geometry.RectF.init(0, 0, 320, 160), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    const retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    const title_frame = retained.findById(4).?.frame.normalized();
    const snippet_frame = retained.findById(6).?.frame.normalized();
    const row_frame = retained.findById(2).?.frame.normalized();

    // Probe the title interior, the gap between the lines, the snippet
    // interior, and the row's padding corner: every point hovers the ROW
    // and shows the pointer cursor — no dead zones inside one surface.
    const probes = [_]geometry.PointF{
        title_frame.center(),
        .{ .x = title_frame.center().x, .y = (title_frame.maxY() + snippet_frame.y) / 2 },
        snippet_frame.center(),
        .{ .x = row_frame.x + 3, .y = row_frame.y + 3 },
        .{ .x = row_frame.maxX() - 3, .y = snippet_frame.center().y },
    };
    for (probes) |probe| {
        try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
            .window_id = 1,
            .label = "canvas",
            .kind = .pointer_move,
            .x = probe.x,
            .y = probe.y,
        } });
        try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_hovered_id);
        // The hover wash is the row's affordance; the cursor stays the
        // native arrow (list rows are controls, not hyperlinks).
        try std.testing.expectEqual(platform.Cursor.arrow, harness.runtime.views[0].canvas_widget_cursor);
    }

    // A press over the snippet lights the row's pressed wash (the render
    // state resolves it through the same fall-through the click takes).
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = snippet_frame.center().x,
        .y = snippet_frame.center().y,
    } });
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_hovered_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvasWidgetRenderState().pressed_id.?);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_up,
        .x = snippet_frame.center().x,
        .y = snippet_frame.center().y,
    } });

    // Off the row: hover clears and the cursor stays the arrow it never
    // left (the channel is read from the runtime — the platform write
    // only fires on change, and a row hover never changes it).
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_move,
        .x = row_frame.center().x,
        .y = row_frame.maxY() + 20,
    } });
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), harness.runtime.views[0].canvas_widget_hovered_id);
    try std.testing.expectEqual(platform.Cursor.arrow, harness.runtime.views[0].canvas_widget_cursor);
}

test "runtime dispatches routed canvas widget pointer events" {
    const TestApp = struct {
        raw_input_count: u32 = 0,
        widget_pointer_count: u32 = 0,
        widget_keyboard_count: u32 = 0,
        widget_key_down_count: u32 = 0,
        widget_text_input_count: u32 = 0,
        last_view_label: []const u8 = "",
        last_phase: canvas.WidgetPointerPhase = .hover,
        last_keyboard_phase: canvas.WidgetKeyboardPhase = .key_up,
        last_target_id: canvas.ObjectId = 0,
        last_target_kind: canvas.WidgetKind = .stack,
        last_keyboard_target_id: canvas.ObjectId = 0,
        last_keyboard_target_kind: canvas.WidgetKind = .stack,
        last_route_len: usize = 0,
        last_keyboard_route_len: usize = 0,
        last_keyboard_key: []const u8 = "",
        last_keyboard_text: []const u8 = "",
        last_keyboard_shift: bool = false,
        last_keyboard_super: bool = false,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-input", .source = platform.WebViewSource.html("<h1>GPU</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .gpu_surface_input => {
                    self.raw_input_count += 1;
                },
                .canvas_widget_pointer => |pointer_event| {
                    self.widget_pointer_count += 1;
                    self.last_view_label = pointer_event.view_label;
                    self.last_phase = pointer_event.pointer.phase;
                    self.last_route_len = pointer_event.route.len;
                    if (pointer_event.target) |target| {
                        self.last_target_id = target.id;
                        self.last_target_kind = target.kind;
                    } else {
                        self.last_target_id = 0;
                        self.last_target_kind = .stack;
                    }
                },
                .canvas_widget_keyboard => |keyboard_event| {
                    self.widget_keyboard_count += 1;
                    switch (keyboard_event.keyboard.phase) {
                        .key_down => self.widget_key_down_count += 1,
                        .text_input => self.widget_text_input_count += 1,
                        .key_up => {},
                    }
                    self.last_view_label = keyboard_event.view_label;
                    self.last_keyboard_phase = keyboard_event.keyboard.phase;
                    self.last_keyboard_route_len = keyboard_event.route.len;
                    self.last_keyboard_key = keyboard_event.keyboard.key;
                    self.last_keyboard_text = keyboard_event.keyboard.text;
                    self.last_keyboard_shift = keyboard_event.keyboard.modifiers.shift;
                    self.last_keyboard_super = keyboard_event.keyboard.modifiers.super;
                    if (keyboard_event.target) |target| {
                        self.last_keyboard_target_id = target.id;
                        self.last_keyboard_target_kind = target.kind;
                    } else {
                        self.last_keyboard_target_id = 0;
                        self.last_keyboard_target_kind = .stack;
                    }
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 160),
    });

    const children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .button,
            .frame = geometry.RectF.init(10, 12, 96, 32),
            .text = "Run",
        },
        .{
            .id = 3,
            .kind = .text_field,
            .frame = geometry.RectF.init(10, 52, 140, 32),
            .text = "Query",
        },
    };
    const root = canvas.Widget{
        .id = 1,
        .kind = .panel,
        .children = &children,
    };
    var nodes: [4]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(root, geometry.RectF.init(0, 0, 240, 160), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    harness.runtime.invalidated = false;
    harness.runtime.dirty_region_count = 0;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 20,
        .y = 24,
        .button = 0,
    } });

    try std.testing.expectEqual(@as(u32, 1), app_state.widget_pointer_count);
    try std.testing.expectEqual(@as(u32, 1), app_state.raw_input_count);
    try std.testing.expectEqualStrings("canvas", app_state.last_view_label);
    try std.testing.expectEqual(canvas.WidgetPointerPhase.down, app_state.last_phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_target_id);
    try std.testing.expectEqual(canvas.WidgetKind.button, app_state.last_target_kind);
    try std.testing.expectEqual(@as(usize, 3), app_state.last_route_len);
    try std.testing.expect(harness.runtime.views[0].focused);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expect(harness.runtime.invalidated);
    try std.testing.expectEqual(@as(usize, 1), harness.runtime.pendingDirtyRegions().len);
    try std.testing.expectEqualDeep(geometry.RectF.init(10, 12, 96, 32), harness.runtime.pendingDirtyRegions()[0]);

    const snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expectEqual(@as(usize, 3), snapshot.widgets.len);
    try std.testing.expect(!snapshot.widgets[0].focused);
    try std.testing.expect(snapshot.widgets[1].focused);
    try std.testing.expect(snapshot.widgets[1].hovered);
    try std.testing.expect(snapshot.widgets[1].pressed);
    try std.testing.expect(!snapshot.widgets[1].selected);
    try std.testing.expect(!snapshot.widgets[2].focused);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "enter",
        .modifiers = .{ .shift = true, .primary = true },
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_pointer_count);
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_keyboard_count);
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_key_down_count);
    try std.testing.expectEqual(@as(u32, 0), app_state.widget_text_input_count);
    try std.testing.expectEqual(@as(u32, 2), app_state.raw_input_count);
    try std.testing.expectEqual(canvas.WidgetKeyboardPhase.key_down, app_state.last_keyboard_phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_keyboard_target_id);
    try std.testing.expectEqual(canvas.WidgetKind.button, app_state.last_keyboard_target_kind);
    try std.testing.expectEqual(@as(usize, 3), app_state.last_keyboard_route_len);
    try std.testing.expectEqualStrings("enter", app_state.last_keyboard_key);
    try std.testing.expectEqualStrings("", app_state.last_keyboard_text);
    try std.testing.expect(app_state.last_keyboard_shift);
    try std.testing.expect(app_state.last_keyboard_super);

    harness.runtime.invalidated = false;
    harness.runtime.dirty_region_count = 0;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "tab",
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_keyboard_count);
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_key_down_count);
    try std.testing.expectEqual(@as(u32, 0), app_state.widget_text_input_count);
    try std.testing.expectEqual(@as(u32, 3), app_state.raw_input_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.last_keyboard_target_id);
    try std.testing.expectEqual(canvas.WidgetKind.text_field, app_state.last_keyboard_target_kind);
    try std.testing.expectEqualStrings("tab", app_state.last_keyboard_key);
    try std.testing.expect(harness.runtime.invalidated);
    try std.testing.expectEqual(@as(usize, 1), harness.runtime.pendingDirtyRegions().len);
    // Focus dirty bounds include the ring's 2px outside offset.
    try std.testing.expectEqualDeep(geometry.RectF.init(7, 49, 146, 38), harness.runtime.pendingDirtyRegions()[0]);

    const tab_snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expect(!tab_snapshot.widgets[1].focused);
    try std.testing.expect(tab_snapshot.widgets[2].focused);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "a",
        .text = "a",
    } });
    try std.testing.expectEqual(@as(u32, 4), app_state.widget_keyboard_count);
    try std.testing.expectEqual(@as(u32, 3), app_state.widget_key_down_count);
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_text_input_count);
    try std.testing.expectEqual(@as(u32, 4), app_state.raw_input_count);
    try std.testing.expectEqual(canvas.WidgetKeyboardPhase.text_input, app_state.last_keyboard_phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.last_keyboard_target_id);
    try std.testing.expectEqual(canvas.WidgetKind.text_field, app_state.last_keyboard_target_kind);
    try std.testing.expectEqualStrings("a", app_state.last_keyboard_key);
    try std.testing.expectEqualStrings("a", app_state.last_keyboard_text);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "a",
        .text = "a",
        .modifiers = .{ .primary = true, .command = true },
    } });
    try std.testing.expectEqual(@as(u32, 5), app_state.widget_keyboard_count);
    try std.testing.expectEqual(@as(u32, 4), app_state.widget_key_down_count);
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_text_input_count);
    try std.testing.expectEqual(@as(u32, 5), app_state.raw_input_count);
    try std.testing.expectEqual(canvas.WidgetKeyboardPhase.key_down, app_state.last_keyboard_phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.last_keyboard_target_id);
    try std.testing.expectEqual(canvas.WidgetKind.text_field, app_state.last_keyboard_target_kind);
    try std.testing.expectEqualStrings("a", app_state.last_keyboard_key);
    try std.testing.expectEqualStrings("a", app_state.last_keyboard_text);
    try std.testing.expect(app_state.last_keyboard_super);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "tab",
        .modifiers = .{ .shift = true },
    } });
    try std.testing.expectEqual(@as(u32, 6), app_state.widget_keyboard_count);
    try std.testing.expectEqual(@as(u32, 5), app_state.widget_key_down_count);
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_text_input_count);
    try std.testing.expectEqual(@as(u32, 6), app_state.raw_input_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_keyboard_target_id);
    try std.testing.expectEqual(canvas.WidgetKind.button, app_state.last_keyboard_target_kind);
    try std.testing.expect(app_state.last_keyboard_shift);
}

test "only a focused live terminal owns Tab and focus entry suppresses the whole gesture" {
    const TestApp = struct {
        keyboard_count: u32 = 0,
        target_id: canvas.ObjectId = 0,
        target_kind: canvas.WidgetKind = .stack,
        focus_moved: bool = true,

        fn app(self: *@This()) App {
            return .{
                .context = self,
                .name = "gpu-terminal-tab",
                .source = platform.WebViewSource.html("<h1>GPU</h1>"),
                .event_fn = event,
            };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    self.keyboard_count += 1;
                    self.focus_moved = keyboard_event.keyboard.focus_moved;
                    if (keyboard_event.target) |target| {
                        self.target_id = target.id;
                        self.target_kind = target.kind;
                    }
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 320, 180),
    });
    var terminal_grid = canvas.TerminalGrid{
        .background = canvas.Color.rgba(0, 0, 0, 1),
        .foreground = canvas.Color.rgba(1, 1, 1, 1),
        .cursor_color = canvas.Color.rgba(1, 1, 1, 1),
        .selection_color = canvas.Color.rgba(0, 0.5, 1, 1),
    };
    var children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .terminal,
            .frame = geometry.RectF.init(10, 10, 200, 100),
            .terminal = .{ .pty = 7, .grid = &terminal_grid },
        },
        .{
            .id = 3,
            .kind = .button,
            .frame = geometry.RectF.init(220, 10, 80, 32),
            .text = "Run",
        },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(
        .{ .id = 1, .kind = .panel, .children = &children },
        geometry.RectF.init(0, 0, 320, 180),
        &nodes,
    );
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    // Pointer focus starts in the terminal. Tab must route to that same
    // widget with no focus move; the UiApp layer then hands it to the
    // bound terminal session instead of the neighboring button.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 40,
        .y = 40,
    } });
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_focused_id);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "tab",
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.keyboard_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.target_id);
    try std.testing.expectEqual(canvas.WidgetKind.terminal, app_state.target_kind);
    try std.testing.expect(!app_state.focus_moved);

    // A Tab pressed while the terminal already owns focus is terminal
    // input for its whole lifetime, including the release protocols
    // such as kitty keyboard reporting can encode.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_up,
        .key = "tab",
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.keyboard_count);

    // The inverse gesture is focus traversal: Shift+Tab from the button
    // enters the live terminal, but its repeat and release must not be
    // reinterpreted against the newly focused pty.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 240,
        .y = 24,
    } });
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), harness.runtime.views[0].canvas_widget_focused_id);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "tab",
        .modifiers = .{ .shift = true },
    } });
    try std.testing.expectEqual(@as(u32, 3), app_state.keyboard_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.target_id);
    try std.testing.expect(app_state.focus_moved);
    try std.testing.expect(harness.runtime.views[0].canvas_widget_tab_input_focus_entry_held);

    // Auto-repeat while Tab is held, then its key-up: neither produces
    // another widget event at the terminal.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "tab",
        .modifiers = .{ .shift = true },
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_up,
        .key = "tab",
        .modifiers = .{ .shift = true },
    } });
    try std.testing.expectEqual(@as(u32, 3), app_state.keyboard_count);
    try std.testing.expect(!harness.runtime.views[0].canvas_widget_tab_input_focus_entry_held);

    // The same nonzero binding after session exit is no longer an input
    // owner: its published grid says the session cannot accept input,
    // so Tab resumes ordinary traversal to the button.
    terminal_grid.running = false;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "tab",
    } });
    try std.testing.expectEqual(@as(u32, 4), app_state.keyboard_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.target_id);
    try std.testing.expect(app_state.focus_moved);

    // The explicit unbound sentinel behaves the same even if a caller
    // supplied a running-looking grid by mistake: no pty means no Tab
    // input owner.
    terminal_grid.running = true;
    children[0].terminal.pty = 0;
    const unbound_layout = try canvas.layoutWidgetTree(
        .{ .id = 1, .kind = .panel, .children = &children },
        geometry.RectF.init(0, 0, 320, 180),
        &nodes,
    );
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", unbound_layout);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 40,
        .y = 40,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "tab",
    } });
    try std.testing.expectEqual(@as(u32, 5), app_state.keyboard_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.target_id);
    try std.testing.expect(app_state.focus_moved);
}

test "runtime routes captured canvas pointer drags without outside release activation" {
    const TestApp = struct {
        command_count: u32 = 0,
        widget_pointer_count: u32 = 0,
        last_phase: canvas.WidgetPointerPhase = .hover,
        last_target_id: canvas.ObjectId = 0,
        last_route_len: usize = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-pointer-capture", .source = platform.WebViewSource.html("<h1>GPU</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .command => self.command_count += 1,
                .canvas_widget_pointer => |pointer_event| {
                    self.widget_pointer_count += 1;
                    self.last_phase = pointer_event.pointer.phase;
                    self.last_route_len = pointer_event.route.len;
                    self.last_target_id = if (pointer_event.target) |target| target.id else 0;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });

    const children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .button,
            .frame = geometry.RectF.init(12, 16, 96, 32),
            .text = "Run",
            .command = "widget.run",
        },
        .{
            .id = 3,
            .kind = .button,
            .frame = geometry.RectF.init(12, 64, 96, 32),
            .text = "Stop",
        },
    };
    var nodes: [4]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .panel, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 20,
        .y = 28,
    } });
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_pressed_id);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_drag,
        .x = 220,
        .y = 28,
        .delta_x = 200,
    } });
    try std.testing.expectEqual(canvas.WidgetPointerPhase.move, app_state.last_phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_target_id);
    try std.testing.expectEqual(@as(usize, 3), app_state.last_route_len);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_pressed_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 1), harness.runtime.views[0].canvas_widget_hovered_id);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_up,
        .x = 220,
        .y = 28,
    } });
    try std.testing.expectEqual(canvas.WidgetPointerPhase.up, app_state.last_phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_target_id);
    try std.testing.expectEqual(@as(usize, 3), app_state.last_route_len);
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), harness.runtime.views[0].canvas_widget_pressed_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 1), harness.runtime.views[0].canvas_widget_hovered_id);
    try std.testing.expectEqual(@as(u32, 0), app_state.command_count);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 20,
        .y = 28,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_up,
        .x = 20,
        .y = 28,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.command_count);
}

test "runtime cancels captured canvas widget pointers without activation" {
    const TestApp = struct {
        command_count: u32 = 0,
        raw_input_count: u32 = 0,
        widget_pointer_count: u32 = 0,
        last_phase: canvas.WidgetPointerPhase = .hover,
        last_target_id: canvas.ObjectId = 0,
        last_route_len: usize = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-pointer-cancel", .source = platform.WebViewSource.html("<h1>GPU</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .command => self.command_count += 1,
                .gpu_surface_input => self.raw_input_count += 1,
                .canvas_widget_pointer => |pointer_event| {
                    self.widget_pointer_count += 1;
                    self.last_phase = pointer_event.pointer.phase;
                    self.last_route_len = pointer_event.route.len;
                    self.last_target_id = if (pointer_event.target) |target| target.id else 0;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 140),
    });

    const children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .button,
            .frame = geometry.RectF.init(12, 16, 96, 32),
            .text = "Run",
            .command = "widget.run",
        },
        .{
            .id = 3,
            .kind = .toggle,
            .frame = geometry.RectF.init(12, 64, 96, 32),
            .text = "Live",
        },
    };
    var nodes: [4]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .panel, .children = &children }, geometry.RectF.init(0, 0, 240, 140), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 20,
        .y = 28,
    } });
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_hovered_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_pressed_id);
    // A pressed button still shows the native arrow — the cursor channel
    // never leaves arrow for controls, only for links/text/dividers.
    try std.testing.expectEqual(platform.Cursor.arrow, harness.runtime.views[0].canvas_widget_cursor);

    harness.runtime.invalidated = false;
    harness.runtime.dirty_region_count = 0;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_cancel,
        .x = 220,
        .y = 28,
    } });
    try std.testing.expectEqual(canvas.WidgetPointerPhase.cancel, app_state.last_phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_target_id);
    try std.testing.expectEqual(@as(usize, 3), app_state.last_route_len);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), harness.runtime.views[0].canvas_widget_hovered_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), harness.runtime.views[0].canvas_widget_pressed_id);
    try std.testing.expectEqual(platform.Cursor.arrow, harness.runtime.views[0].canvas_widget_cursor);
    try std.testing.expect(harness.runtime.invalidated);
    try std.testing.expect(harness.runtime.pendingDirtyRegions().len > 0);
    try std.testing.expectEqual(@as(u32, 0), app_state.command_count);

    const cancel_snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expectEqual(@as(usize, 3), cancel_snapshot.widgets.len);
    try std.testing.expect(cancel_snapshot.widgets[1].focused);
    try std.testing.expect(!cancel_snapshot.widgets[1].hovered);
    try std.testing.expect(!cancel_snapshot.widgets[1].pressed);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_up,
        .x = 20,
        .y = 28,
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.command_count);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 20,
        .y = 76,
    } });
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), harness.runtime.views[0].canvas_widget_pressed_id);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_cancel,
        .x = 20,
        .y = 76,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_up,
        .x = 20,
        .y = 76,
    } });

    const retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expect(!retained.findById(3).?.widget.state.selected);
    try std.testing.expectEqual(@as(f32, 0), retained.findById(3).?.widget.value);
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), harness.runtime.views[0].canvas_widget_pressed_id);
    try std.testing.expectEqual(@as(u32, 0), app_state.command_count);
    try std.testing.expectEqual(@as(u32, 6), app_state.widget_pointer_count);
    try std.testing.expectEqual(@as(u32, 6), app_state.raw_input_count);
}

test "runtime applies GPU text and IME input to focused canvas text fields" {
    const TestApp = struct {
        widget_keyboard_count: u32 = 0,
        last_keyboard_phase: canvas.WidgetKeyboardPhase = .key_up,
        last_keyboard_target_id: canvas.ObjectId = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-mobile-text-ime", .source = platform.WebViewSource.html("<h1>Hello</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    self.widget_keyboard_count += 1;
                    self.last_keyboard_phase = keyboard_event.keyboard.phase;
                    if (keyboard_event.target) |target| self.last_keyboard_target_id = target.id;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });

    const children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .text_field,
            .frame = geometry.RectF.init(10, 10, 160, 32),
            .text = "hello",
            .text_selection = canvas.TextSelection{ .anchor = 1, .focus = 4 },
        },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "a",
    } });
    var retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    var field = retained.findById(2).?.widget;
    try std.testing.expectEqualStrings("hao", field.text);
    try std.testing.expectEqual(@as(usize, 2), field.text_selection.?.focus);
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_keyboard_count);
    try std.testing.expectEqual(canvas.WidgetKeyboardPhase.text_input, app_state.last_keyboard_phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_keyboard_target_id);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "é",
        .composition_cursor = 2,
    } });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    field = retained.findById(2).?.widget;
    try std.testing.expectEqualStrings("haéo", field.text);
    try std.testing.expect(field.text_composition != null);
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_keyboard_count);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_commit_composition,
    } });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    field = retained.findById(2).?.widget;
    try std.testing.expectEqualStrings("haéo", field.text);
    try std.testing.expect(field.text_composition == null);
    try std.testing.expectEqual(@as(u32, 3), app_state.widget_keyboard_count);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ll",
        .composition_cursor = 2,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_cancel_composition,
    } });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    field = retained.findById(2).?.widget;
    try std.testing.expectEqualStrings("haéo", field.text);
    try std.testing.expect(field.text_composition == null);
    try std.testing.expectEqual(@as(u32, 5), app_state.widget_keyboard_count);
}

test "a claimed key stays claimed when its command rebuilds the tree mid-dispatch" {
    const TestApp = struct {
        committed_count: u32 = 0,
        activations: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-claim-vs-rebuild", .source = platform.WebViewSource.html("<h1>R</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.target != null and keyboard_event.keyboard.phase == .key_down) {
                        // The button's command: rebuild WITHOUT the
                        // button — the exact tree mutation that used to
                        // erase the claim before the committed-text
                        // check ran.
                        self.activations += 1;
                        var nodes: [2]canvas.WidgetLayoutNode = undefined;
                        const layout = canvas.layoutWidgetTree(.{ .kind = .stack, .children = &.{} }, geometry.RectF.init(0, 0, 240, 120), &nodes) catch return;
                        _ = runtime.setCanvasWidgetLayout(1, "canvas", layout) catch return;
                        runtime.views[0].canvas_widget_focused_id = 0;
                        return;
                    }
                    if (keyboard_event.keyboard.phase == .text_input and keyboard_event.target == null) {
                        self.committed_count += 1;
                    }
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .button,
            .frame = geometry.RectF.init(10, 10, 96, 32),
            .text = "Run",
        },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    // ONE physical Space: the focused button claims it (activation) and
    // its command removes the button. The claim was decided against the
    // tree the input routed through, so the same keystroke must NOT
    // also type a literal space through the target-less fallback.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "space",
        .text = " ",
    } });
    try std.testing.expect(app_state.activations > 0);
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);

    // The SPLIT-EVENT shape (AppKit/Windows): the claimed key_down
    // carries no text and the committed character follows as a separate
    // event — the claim must carry across the split, because the
    // activation already rebuilt the tree by the time the text arrives.
    const again = [_]canvas.Widget{
        .{
            .id = 4,
            .kind = .button,
            .frame = geometry.RectF.init(10, 10, 96, 32),
            .text = "Run",
        },
    };
    var nodes_again: [3]canvas.WidgetLayoutNode = undefined;
    const layout_again = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &again }, geometry.RectF.init(0, 0, 240, 120), &nodes_again);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout_again);
    harness.runtime.views[0].canvas_widget_focused_id = 4;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "space",
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = " ",
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);

    // The carry is ONE-SHOT: ordinary typing afterwards still flows.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "x",
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);

    // The carry DIES AT BLUR: it marks "the text arriving next belongs
    // to the claimed key_down just routed", and a focus move breaks
    // that adjacency. A claimed activation that moves focus before its
    // committed text arrives (Enter/Space commands routinely do) must
    // not leave the carry armed to swallow the refocused surface's
    // first unrelated commit.
    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "other",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 120, 240, 120),
    });
    const rearmed = [_]canvas.Widget{
        .{
            .id = 6,
            .kind = .button,
            .frame = geometry.RectF.init(10, 10, 96, 32),
            .text = "Run",
        },
    };
    var nodes_rearmed: [3]canvas.WidgetLayoutNode = undefined;
    const layout_rearmed = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &rearmed }, geometry.RectF.init(0, 0, 240, 120), &nodes_rearmed);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout_rearmed);
    harness.runtime.views[0].canvas_widget_focused_id = 6;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "space",
    } });
    try harness.runtime.focusView(1, "other");
    try harness.runtime.focusView(1, "canvas");
    // The space literal itself — exactly what a surviving carry would
    // swallow — must flow: the grace died at blur.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = " ",
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.committed_count);

    // And the carry is KEYED: armed by a claimed Space, a DIFFERENT
    // key's committed text arriving next (hosts that emit input-method
    // text before its key_down can interleave one) flows instead of
    // feeding the stale latch.
    const rearmed_again = [_]canvas.Widget{
        .{
            .id = 8,
            .kind = .button,
            .frame = geometry.RectF.init(10, 10, 96, 32),
            .text = "Run",
        },
    };
    var nodes_rearmed_again: [3]canvas.WidgetLayoutNode = undefined;
    const layout_rearmed_again = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &rearmed_again }, geometry.RectF.init(0, 0, 240, 120), &nodes_rearmed_again);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout_rearmed_again);
    harness.runtime.views[0].canvas_widget_focused_id = 8;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "space",
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "q",
    } });
    try std.testing.expectEqual(@as(u32, 3), app_state.committed_count);
}

test "a widget-started composition resolves in its starting editor after focus moves" {
    const TestApp = struct {
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-ime-ownership", .source = platform.WebViewSource.html("<h1>W</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .text_field,
            .frame = geometry.RectF.init(10, 10, 160, 32),
            .text = "",
        },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    // The composition STARTS in the field.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ka",
        .composition_cursor = 2,
    } });
    var retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("ka", retained.findById(2).?.widget.text);
    try std.testing.expect(retained.findById(2).?.widget.text_composition != null);

    // Focus leaves the field mid-sequence; the host then commits the
    // marked text unchanged. The commit resolves the EDITOR IT STARTED
    // IN — its marked text becomes plain — and never leaks through the
    // target-less fallback.
    harness.runtime.views[0].canvas_widget_focused_id = 0;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_commit_composition,
        .text = "",
    } });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("ka", retained.findById(2).?.widget.text);
    try std.testing.expect(retained.findById(2).?.widget.text_composition == null);
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);

    // The sequence is closed: target-less typing flows normally again.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "x",
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);
}

test "a target-less converted commit stays target-less past a focus move" {
    const TestApp = struct {
        committed_count: u32 = 0,
        last_committed: [16]u8 = undefined,
        last_committed_len: usize = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-targetless-converted-commit", .source = platform.WebViewSource.html("<h1>T</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                    const text = keyboard_event.keyboard.text;
                    const len = @min(text.len, self.last_committed.len);
                    @memcpy(self.last_committed[0..len], text[0..len]);
                    self.last_committed_len = len;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(10, 10, 160, 32), .text = "" },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;

    // The composition starts TARGET-LESS; a text field then takes
    // focus mid-sequence. The converted commit (cancel-then-text_input)
    // still belongs to the target-less consumer: the trailing text
    // delivers target-less, the field stays untouched.
    harness.runtime.views[0].canvas_widget_focused_id = 0;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ka",
    } });
    harness.runtime.views[0].canvas_widget_focused_id = 2;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_cancel_composition,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "KA",
    } });
    var retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("", retained.findById(2).?.widget.text);
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);
    try std.testing.expectEqualStrings("KA", app_state.last_committed[0..app_state.last_committed_len]);

    // A plain target-less cancel is chased by its own key_down, which
    // disarms the grace: the next typing routes to the focused field.
    harness.runtime.views[0].canvas_widget_focused_id = 0;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ne",
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_cancel_composition,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "escape",
    } });
    harness.runtime.views[0].canvas_widget_focused_id = 2;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "z",
    } });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("z", retained.findById(2).?.widget.text);
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);
}

test "a converted commit's trailing text lands in the composing editor, not the new focus" {
    const TestApp = struct {
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-ime-converted-commit", .source = platform.WebViewSource.html("<h1>C</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const two_fields = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(10, 10, 160, 32), .text = "" },
        .{ .id = 3, .kind = .text_field, .frame = geometry.RectF.init(10, 52, 160, 32), .text = "" },
    };
    var nodes: [4]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &two_fields }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    // Field A composes; focus moves to B mid-sequence. The hosts encode
    // a converted commit (result differs from the marked text) as
    // cancel-then-text_input: BOTH must land in A — the editor that
    // composed — never in B, never target-less.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ka",
        .composition_cursor = 2,
    } });
    harness.runtime.views[0].canvas_widget_focused_id = 3;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_cancel_composition,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "KA",
    } });
    var retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("KA", retained.findById(2).?.widget.text);
    try std.testing.expectEqualStrings("", retained.findById(3).?.widget.text);
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);

    // A PLAIN cancel is chased by its own key_down, which disarms the
    // grace: the user's next typing routes by focus as usual.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ne",
        .composition_cursor = 2,
    } });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("ne", retained.findById(3).?.widget.text);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_cancel_composition,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "escape",
    } });
    harness.runtime.views[0].canvas_widget_focused_id = 2;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "z",
    } });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("KAz", retained.findById(2).?.widget.text);
    try std.testing.expectEqualStrings("", retained.findById(3).?.widget.text);
}

test "a converted commit whose owner vanished after the cancel is swallowed" {
    const TestApp = struct {
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-ime-grace-orphan", .source = platform.WebViewSource.html("<h1>G</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const two_fields = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(10, 10, 160, 32), .text = "" },
        .{ .id = 3, .kind = .text_field, .frame = geometry.RectF.init(10, 52, 160, 32), .text = "" },
    };
    var nodes: [4]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &two_fields }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    // A composes; focus moves to B; the cancel routes to A and arms the
    // grace — and then A is rebuilt away before the trailing commit.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ka",
        .composition_cursor = 2,
    } });
    harness.runtime.views[0].canvas_widget_focused_id = 3;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_cancel_composition,
    } });
    const only_b = [_]canvas.Widget{
        .{ .id = 3, .kind = .text_field, .frame = geometry.RectF.init(10, 52, 160, 32), .text = "" },
    };
    var nodes_b: [3]canvas.WidgetLayoutNode = undefined;
    const layout_b = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &only_b }, geometry.RectF.init(0, 0, 240, 120), &nodes_b);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout_b);

    // The dead owner converts the grace to a swallow: the composed
    // result resolves nowhere — never in B, never target-less.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "KA",
    } });
    const retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("", retained.findById(3).?.widget.text);
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);
}

test "a recreated surface never inherits a stale target-less composition" {
    const TestApp = struct {
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-ime-view-recreate", .source = platform.WebViewSource.html("<h1>V</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    // The FIRST surface composes target-less mid-preedit...
    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    harness.runtime.views[0].focused = true;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ka",
    } });

    // ...and is CLOSED, then recreated under the same window+label,
    // this time with a focused editor. The stale sequence died with the
    // old view: the editor's first composition is ITS OWN, not a
    // continuation to bypass.
    try harness.runtime.closeView(1, "canvas");
    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(10, 10, 160, 32), .text = "" },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ne",
        .composition_cursor = 2,
    } });
    const retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("ne", retained.findById(2).?.widget.text);
    try std.testing.expect(retained.findById(2).?.widget.text_composition != null);
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);
}

test "a programmatic focus move disarms the cancel grace like a pointer one" {
    const TestApp = struct {
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-ime-grace-focusview", .source = platform.WebViewSource.html("<h1>F</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas-b",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 120, 240, 120),
    });
    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(10, 10, 160, 32), .text = "" },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas-a", layout);
    try harness.runtime.focusView(1, "canvas-a");
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    // A composition cancels (arming the grace), then the PROGRAMMATIC
    // focus path — focusView, the third grace-lifecycle entry point —
    // moves focus away and the canvas is rebuilt without the owner.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .ime_set_composition,
        .text = "ka",
        .composition_cursor = 2,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .ime_cancel_composition,
    } });
    try harness.runtime.focusView(1, "canvas-b");
    const replacement = [_]canvas.Widget{
        .{ .id = 3, .kind = .text_field, .frame = geometry.RectF.init(10, 10, 160, 32), .text = "" },
    };
    var nodes_b: [3]canvas.WidgetLayoutNode = undefined;
    const layout_b = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &replacement }, geometry.RectF.init(0, 0, 240, 120), &nodes_b);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas-a", layout_b);
    try harness.runtime.focusView(1, "canvas-a");
    harness.runtime.views[0].canvas_widget_focused_id = 3;

    // The blur disarmed the grace: this fresh commit belongs to the
    // NOW-focused editor — never swallowed with (or routed to) the
    // stale sequence.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .text_input,
        .text = "x",
    } });
    const retained = try harness.runtime.canvasWidgetLayout(1, "canvas-a");
    try std.testing.expectEqualStrings("x", retained.findById(3).?.widget.text);
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);
}

test "a target-less composition owns its surface's keys - and its resolution releases them" {
    const TestApp = struct {
        keydown_count: u32 = 0,
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-ime-key-ownership", .source = platform.WebViewSource.html("<h1>K</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.target != null) return;
                    if (keyboard_event.keyboard.phase == .key_down) self.keydown_count += 1;
                    if (keyboard_event.keyboard.phase == .text_input) self.committed_count += 1;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    harness.runtime.views[0].focused = true;

    // DURING the composition, key_downs are input-method machinery
    // (candidate navigation, the confirming Enter on hosts that surface
    // the key before the commit): never app-level keys — a terminal
    // mapping ArrowDown would scroll while the user is picking a
    // candidate.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "\xe3\x81\x8b",
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "arrowdown",
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "enter",
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.keydown_count);

    // COMMAND CHORDS stay live mid-composition: input methods never
    // consume them, so Ctrl+Alt+C here is a genuine shortcut the app
    // must hear (on Windows alone Ctrl+Alt is AltGr and composes text;
    // this build treats it as the chord it is on this host).
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "c",
        .modifiers = .{ .control = true, .option = true },
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.keydown_count);

    // The commit delivers the composed text and ENDS the ownership: a
    // key_down arriving after the resolution is a genuine keystroke on
    // every host (hosts whose input method consumes the resolving key
    // suppress it at the source), so a candidate committed by MOUSE in
    // the OS popup — no trailing key at all — never costs the next
    // Enter, arrow, or Backspace.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_commit_composition,
        .text = "",
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "enter",
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.keydown_count);

    // A cancel releases the keys the same way.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "\xe3\x81\xad",
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "backspace",
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.keydown_count);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_cancel_composition,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "escape",
    } });
    try std.testing.expectEqual(@as(u32, 3), app_state.keydown_count);
}

test "an input-method-owned key never activates a freshly focused widget" {
    const TestApp = struct {
        activations: u32 = 0,
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-ime-widget-activation", .source = platform.WebViewSource.html("<h1>A</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.target != null and keyboard_event.keyboard.phase == .key_down) {
                        self.activations += 1;
                    }
                    if (keyboard_event.target == null and keyboard_event.keyboard.phase == .text_input) {
                        self.committed_count += 1;
                    }
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    harness.runtime.views[0].focused = true;

    // The composition starts target-less (nothing focused)...
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "\xe3\x81\x8b",
    } });
    // ...then a button takes focus while it is still open. The
    // confirming Enter — surfaced BEFORE the commit on some hosts —
    // belongs to the input method: it must not press the button.
    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .button, .frame = geometry.RectF.init(10, 10, 96, 32), .text = "Run" },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].canvas_widget_focused_id = 2;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "enter",
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.activations);

    // The commit resolves target-less (the composition's owner), and
    // only a GENUINE Enter afterwards presses the button.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_commit_composition,
        .text = "",
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "enter",
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.activations);
}

test "a duplicate cancel disarms the widget grace without re-arming it ownerless" {
    const TestApp = struct {
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-ime-duplicate-cancel", .source = platform.WebViewSource.html("<h1>D</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(10, 10, 160, 32), .text = "" },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    // A widget-owned composition cancels; hosts whose input method
    // consumed the cancelling key follow up with a SYNTHETIC duplicate
    // cancel (the grace's disarm stand-in for the suppressed key_down).
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ka",
        .composition_cursor = 2,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_cancel_composition,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_cancel_composition,
    } });

    // The duplicate DISARMED the grace and released the owner — it must
    // not re-arm ownerless, or this ordinary character would convert to
    // a dead-owner swallow. It lands in the focused editor.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "x",
    } });
    const retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("x", retained.findById(2).?.widget.text);
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);
}

test "a target-less composition dies at its surface's blur - the next composition belongs to the focused editor" {
    const TestApp = struct {
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-ime-targetless-blur", .source = platform.WebViewSource.html("<h1>B</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas-b",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 120, 240, 120),
    });
    try harness.runtime.focusView(1, "canvas-a");

    // A target-less composition starts (no editor focused), then focus
    // leaves the surface WITHOUT a host cancellation and returns.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .ime_set_composition,
        .text = "\xe3\x81\x8b",
    } });
    try harness.runtime.focusView(1, "canvas-b");
    try harness.runtime.focusView(1, "canvas-a");

    // An editor takes focus and a NEW composition begins: it belongs to
    // the editor — a stale target-less preedit surviving the blur would
    // claim it as a continuation and steal its commit.
    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(10, 10, 160, 32), .text = "" },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas-a", layout);
    harness.runtime.views[0].canvas_widget_focused_id = 2;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .ime_set_composition,
        .text = "ne",
        .composition_cursor = 2,
    } });
    const retained = try harness.runtime.canvasWidgetLayout(1, "canvas-a");
    try std.testing.expectEqualStrings("ne", retained.findById(2).?.widget.text);
    try std.testing.expect(retained.findById(2).?.widget.text_composition != null);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .ime_commit_composition,
        .text = "ne",
    } });
    try std.testing.expectEqualStrings("ne", (try harness.runtime.canvasWidgetLayout(1, "canvas-a")).findById(2).?.widget.text);
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);
}

test "a cancel grace dies with its view - the trailing text reaches the new surface's editor" {
    const TestApp = struct {
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-ime-grace-recreate", .source = platform.WebViewSource.html("<h1>D</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    // A target-less composition CANCELS (arming the converted-commit
    // grace, its preedit already zeroed), and the cancel's handling
    // closes the surface and recreates the label with a focused editor.
    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    harness.runtime.views[0].focused = true;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ka",
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_cancel_composition,
    } });
    try harness.runtime.closeView(1, "canvas");
    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(10, 10, 160, 32), .text = "" },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    // The grace died with the old view: this text belongs to the NEW
    // surface's focused editor, never leaked target-less.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = "KA",
    } });
    const retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("KA", retained.findById(2).?.widget.text);
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);
}

test "an orphaned composition is swallowed, never rerouted to another editor" {
    const TestApp = struct {
        committed_count: u32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-ime-orphan", .source = platform.WebViewSource.html("<h1>X</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const two_fields = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(10, 10, 160, 32), .text = "" },
        .{ .id = 3, .kind = .text_field, .frame = geometry.RectF.init(10, 52, 160, 32), .text = "" },
    };
    var nodes: [4]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &two_fields }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    // Field A composes; a rebuild removes it and focus lands on B.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ka",
        .composition_cursor = 2,
    } });
    const only_b = [_]canvas.Widget{
        .{ .id = 3, .kind = .text_field, .frame = geometry.RectF.init(10, 52, 160, 32), .text = "" },
    };
    var nodes_b: [3]canvas.WidgetLayoutNode = undefined;
    const layout_b = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &only_b }, geometry.RectF.init(0, 0, 240, 120), &nodes_b);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout_b);
    harness.runtime.views[0].canvas_widget_focused_id = 3;

    // A PREEDIT UPDATE of the orphaned sequence is swallowed too — the
    // sequence is still open and still belongs to the vanished editor,
    // so field B must not inherit its updates.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "kaa",
        .composition_cursor = 3,
    } });
    var retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("", retained.findById(3).?.widget.text);

    // The orphaned sequence's commit resolves NOWHERE: field B never saw
    // the composition, and the target-less fallback never composed it —
    // swallowed, both by contract, and the close releases the pin.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_commit_composition,
        .text = "ka",
    } });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("", retained.findById(3).?.widget.text);
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);

    // The stale pin cleared: a FRESH composition opens in field B.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ne",
        .composition_cursor = 2,
    } });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("ne", retained.findById(3).?.widget.text);
    try std.testing.expect(retained.findById(3).?.widget.text_composition != null);
}

test "a target-less composition stays target-less when a text field takes focus mid-sequence" {
    const TestApp = struct {
        committed_count: u32 = 0,
        last_committed: [16]u8 = undefined,
        last_committed_len: usize = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-targetless-ime-ownership", .source = platform.WebViewSource.html("<h1>O</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                    const text = keyboard_event.keyboard.text;
                    const len = @min(text.len, self.last_committed.len);
                    @memcpy(self.last_committed[0..len], text[0..len]);
                    self.last_committed_len = len;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .text_field,
            .frame = geometry.RectF.init(10, 10, 160, 32),
            .text = "",
        },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;

    // The composition STARTS target-less (nothing focused): the preedit
    // buffers for the surface's own consumer.
    harness.runtime.views[0].canvas_widget_focused_id = 0;
    try harness.runtime.dispatchPlatformEvent(app, .{
        .gpu_surface_input = .{
            .window_id = 1,
            .label = "canvas",
            .kind = .ime_set_composition,
            .text = "\xe3\x81\x8b", // か
        },
    });
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);

    // Focus lands on a text field mid-sequence. The empty commit
    // resolves the composition it STARTED — the target-less one — not
    // the newly focused editor: the composed bytes reach the
    // target-less consumer and the field's text is untouched.
    harness.runtime.views[0].canvas_widget_focused_id = 2;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_commit_composition,
        .text = "",
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);
    try std.testing.expectEqualStrings("\xe3\x81\x8b", app_state.last_committed[0..app_state.last_committed_len]);
    const retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("", retained.findById(2).?.widget.text);

    // With the sequence resolved, a fresh composition belongs to the
    // focused editor as usual.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .ime_set_composition,
        .text = "ne",
    } });
    const composing = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualStrings("ne", composing.findById(2).?.widget.text);
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);
}

test "a focused widget's claimed key never doubles into target-less committed text" {
    const TestApp = struct {
        committed_count: u32 = 0,
        last_committed: [16]u8 = undefined,
        last_committed_len: usize = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-targetless-precedence", .source = platform.WebViewSource.html("<h1>P</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                    const text = keyboard_event.keyboard.text;
                    const len = @min(text.len, self.last_committed.len);
                    @memcpy(self.last_committed[0..len], text[0..len]);
                    self.last_committed_len = len;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .button,
            .frame = geometry.RectF.init(10, 10, 96, 32),
            .text = "Run",
        },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    harness.runtime.views[0].focused = true;
    harness.runtime.views[0].canvas_widget_focused_id = 2;

    // Space activates the focused button (widget precedence, step 2):
    // the SAME keystroke must not also type a literal space through the
    // target-less committed fallback — on either committed-text carrier.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "space",
        .text = " ",
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .text_input,
        .text = " ",
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);

    // A key the button does NOT claim still flows — typing into a
    // terminal while a button happens to hold focus.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "j",
        .text = "j",
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);
    try std.testing.expectEqualStrings("j", app_state.last_committed[0..app_state.last_committed_len]);

    // With focus off the button, space typing flows again.
    harness.runtime.views[0].canvas_widget_focused_id = 0;
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "space",
        .text = " ",
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.committed_count);
    try std.testing.expectEqualStrings(" ", app_state.last_committed[0..app_state.last_committed_len]);
}

test "target-less IME preedit is per-surface and command chords never commit text" {
    const TestApp = struct {
        committed_count: u32 = 0,
        last_committed: [64]u8 = undefined,
        last_committed_len: usize = 0,
        last_committed_label: []const u8 = "",

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-targetless-ime", .source = platform.WebViewSource.html("<h1>IME</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    // Only the target-less committed synthesis: phase
                    // text_input with no focused-widget target.
                    if (keyboard_event.keyboard.phase != .text_input) return;
                    if (keyboard_event.target != null) return;
                    self.committed_count += 1;
                    const text = keyboard_event.keyboard.text;
                    const len = @min(text.len, self.last_committed.len);
                    @memcpy(self.last_committed[0..len], text[0..len]);
                    self.last_committed_len = len;
                    self.last_committed_label = keyboard_event.view_label;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    // TWO focused surfaces, no widgets anywhere: every text event takes
    // the target-less path.
    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas-b",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 120, 240, 120),
    });
    harness.runtime.views[0].focused = true;
    harness.runtime.views[1].focused = true;

    // Surface A composes; the preedit is provisional — nothing commits.
    try harness.runtime.dispatchPlatformEvent(app, .{
        .gpu_surface_input = .{
            .window_id = 1,
            .label = "canvas-a",
            .kind = .ime_set_composition,
            .text = "\xe3\x81\x8b", // か
        },
    });
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);

    // THE per-surface pin: surface B's empty commit must NOT insert
    // surface A's buffered composition — B owns no preedit.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-b",
        .kind = .ime_commit_composition,
        .text = "",
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.committed_count);

    // A's own empty commit still resolves ITS composition — B's stray
    // commit neither consumed nor cleared it.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .ime_commit_composition,
        .text = "",
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);
    try std.testing.expectEqualStrings("\xe3\x81\x8b", app_state.last_committed[0..app_state.last_committed_len]);
    try std.testing.expectEqualStrings("canvas-a", app_state.last_committed_label);

    // THE chord pin: a command-modified text event is a shortcut, not
    // typing — the same gate focused text widgets apply — so Ctrl+C
    // commits nothing on the target-less path either.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .text_input,
        .key = "c",
        .text = "c",
        .modifiers = .{ .control = true },
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.committed_count);

    // Unmodified typing still commits.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .text_input,
        .text = "x",
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.committed_count);
    try std.testing.expectEqualStrings("x", app_state.last_committed[0..app_state.last_committed_len]);

    // A key_down CARRYING text is the other committed-text shape (hosts
    // whose plain typing rides the key event, no separate text event):
    // it must reach the target-less consumer too.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .key_down,
        .key = "j",
        .text = "j",
    } });
    try std.testing.expectEqual(@as(u32, 3), app_state.committed_count);
    try std.testing.expectEqualStrings("j", app_state.last_committed[0..app_state.last_committed_len]);

    // The same chord gate applies on the key_down carrier: a chorded
    // key never types its literal character.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .key_down,
        .key = "j",
        .text = "j",
        .modifiers = .{ .control = true },
    } });
    try std.testing.expectEqual(@as(u32, 3), app_state.committed_count);

    // A key_down WITHOUT text (a special, or an IM-consumed key echoed
    // for activation) commits nothing.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas-a",
        .kind = .key_down,
        .key = "enter",
    } });
    try std.testing.expectEqual(@as(u32, 3), app_state.committed_count);
}

test "runtime dispatches opted-in canvas widget drag events" {
    const TestApp = struct {
        raw_input_count: u32 = 0,
        widget_pointer_count: u32 = 0,
        widget_drag_count: u32 = 0,
        command_count: u32 = 0,
        last_drag_source_id: canvas.ObjectId = 0,
        last_drag_route_len: usize = 0,
        last_drag_x: f32 = 0,
        last_drag_dx: f32 = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-drag", .source = platform.WebViewSource.html("<h1>GPU</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .gpu_surface_input => self.raw_input_count += 1,
                .canvas_widget_pointer => self.widget_pointer_count += 1,
                .command => self.command_count += 1,
                .canvas_widget_drag => |drag_event| {
                    self.widget_drag_count += 1;
                    self.last_drag_source_id = if (drag_event.source) |source| source.id else 0;
                    self.last_drag_route_len = drag_event.route.len;
                    self.last_drag_x = drag_event.drag.point.x;
                    self.last_drag_dx = drag_event.drag.delta.dx;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });

    const children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .button,
            .frame = geometry.RectF.init(12, 16, 96, 32),
            .text = "Drag",
            .command = "drag.press",
            .semantics = .{ .actions = .{ .drag = true } },
        },
        .{
            .id = 3,
            .kind = .button,
            .frame = geometry.RectF.init(12, 58, 96, 32),
            .text = "Plain",
        },
    };
    var nodes: [4]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .panel, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_drag,
        .x = 44,
        .y = 28,
        .delta_x = 12,
    } });
    try std.testing.expectEqual(@as(u32, 0), app_state.widget_drag_count);
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_pointer_count);
    try std.testing.expectEqual(@as(u32, 1), app_state.raw_input_count);

    // A plain click on a draggable remains a press. It produces no terminal
    // drag and cannot leave a zero-origin landing armed for a later rebuild.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 20,
        .y = 28,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_up,
        .x = 20,
        .y = 28,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.command_count);
    try std.testing.expectEqual(@as(u32, 0), app_state.widget_drag_count);
    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);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 20,
        .y = 28,
    } });
    // Motion inside the slop stays observable to the low-level channel so it
    // owns text selection, but it activates no preview or landing state.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_drag,
        .x = 24,
        .y = 30,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_drag_count);
    try std.testing.expect(harness.runtime.views[0].canvasWidgetRenderState().drag_preview_id == null);
    try std.testing.expect(!harness.runtime.views[0].canvas_widget_drag_layout_motion_armed);

    try harness.runtime.dispatchPlatformEvent(app, .{
        .gpu_surface_input = .{
            .window_id = 1,
            .label = "canvas",
            .kind = .pointer_drag,
            .x = 64,
            .y = 30,
            // Physical hosts report per-event deltas (often zero); widget drag
            // displacement must come from the pointer-down origin instead.
            .delta_x = 0,
            .delta_y = 0,
        },
    });
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_drag_count);
    try std.testing.expectEqual(@as(u32, 6), app_state.widget_pointer_count);
    try std.testing.expectEqual(@as(u32, 6), app_state.raw_input_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_drag_source_id);
    try std.testing.expectEqual(@as(usize, 3), app_state.last_drag_route_len);
    try std.testing.expectEqual(@as(f32, 64), app_state.last_drag_x);
    try std.testing.expectEqual(@as(f32, 44), app_state.last_drag_dx);
    const dragging_state = harness.runtime.views[0].canvasWidgetRenderState();
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), dragging_state.drag_preview_id.?);
    try std.testing.expectEqual(@as(f32, 12), dragging_state.drag_preview_origin.?.x);
    try std.testing.expectEqual(@as(f32, 16), dragging_state.drag_preview_origin.?.y);
    try std.testing.expectEqual(@as(f32, 44), dragging_state.drag_preview_offset.dx);
    try std.testing.expectEqual(@as(f32, 2), dragging_state.drag_preview_offset.dy);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_up,
        .x = 64,
        .y = 30,
    } });
    try std.testing.expectEqual(@as(u32, 3), app_state.widget_drag_count);
    // The captured release ended a real drag, so it cannot also activate the
    // source button's command/on_press path.
    try std.testing.expectEqual(@as(u32, 1), app_state.command_count);
    try std.testing.expect(harness.runtime.views[0].canvasWidgetRenderState().drag_preview_id == null);

    // A drag-driven rebuild keeps truthful final layout geometry while the
    // moved keyed widget begins at its previously presented position and
    // springs to zero offset on recorded frame timestamps.
    const moved_children = [_]canvas.Widget{
        .{
            .id = 2,
            .kind = .button,
            .frame = geometry.RectF.init(12, 58, 96, 32),
            .text = "Drag",
            .semantics = .{ .actions = .{ .drag = true } },
        },
        children[1],
    };
    var moved_nodes: [4]canvas.WidgetLayoutNode = undefined;
    const moved_layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .panel, .children = &moved_children }, geometry.RectF.init(0, 0, 240, 120), &moved_nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", moved_layout);
    var motion_state = harness.runtime.views[0].canvasWidgetRenderState();
    try std.testing.expectEqual(@as(usize, 1), motion_state.layout_motions.len);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), motion_state.layout_motions[0].id);
    try std.testing.expectEqual(@as(f32, 44), motion_state.layout_motions[0].offset.dx);
    try std.testing.expectEqual(@as(f32, -40), motion_state.layout_motions[0].offset.dy);
    try std.testing.expect(motion_state.layout_motions[0].escape_ancestor_clips);
    try harness.runtime.advanceCanvasWidgetDragLayoutMotionForFrame(0, 1_000_000_000);
    motion_state = harness.runtime.views[0].canvasWidgetRenderState();
    try std.testing.expectEqual(@as(f32, 44), motion_state.layout_motions[0].offset.dx);
    try std.testing.expectEqual(@as(f32, -40), motion_state.layout_motions[0].offset.dy);
    try harness.runtime.advanceCanvasWidgetDragLayoutMotionForFrame(0, 1_100_000_000);
    motion_state = harness.runtime.views[0].canvasWidgetRenderState();
    try std.testing.expect(@abs(motion_state.layout_motions[0].offset.dy) < 40);
    try harness.runtime.advanceCanvasWidgetDragLayoutMotionForFrame(0, 2_000_000_000);
    try std.testing.expectEqual(@as(usize, 0), harness.runtime.views[0].canvasWidgetRenderState().layout_motions.len);

    const snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expect(snapshot.widgets[1].actions.drag);
    try std.testing.expect(!snapshot.widgets[2].actions.drag);
}

test "drag routing preserves a nested pointer route until pointer dispatch" {
    const TestApp = struct {
        pointer_target_id: canvas.ObjectId = 0,
        drag_source_id: canvas.ObjectId = 0,
        pointer_route: [5]canvas.WidgetEventRouteEntry = undefined,
        pointer_route_len: usize = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-drag-routes", .source = platform.WebViewSource.html("<h1>GPU</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_pointer => |pointer_event| {
                    if (pointer_event.pointer.phase != .move) return;
                    self.pointer_target_id = if (pointer_event.target) |target| target.id else 0;
                    self.pointer_route_len = @min(pointer_event.route.len, self.pointer_route.len);
                    @memcpy(self.pointer_route[0..self.pointer_route_len], pointer_event.route[0..self.pointer_route_len]);
                },
                .canvas_widget_drag => |drag_event| {
                    self.drag_source_id = if (drag_event.source) |source| source.id else 0;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });

    const label = canvas.Widget{
        .id = 3,
        .kind = .text,
        .frame = geometry.RectF.init(0, 0, 80, 20),
        .text = "Drag label",
    };
    const source = canvas.Widget{
        .id = 2,
        .kind = .row,
        .frame = geometry.RectF.init(0, 0, 0, 40),
        .layout = .{ .padding = geometry.InsetsF.all(8) },
        .semantics = .{ .actions = .{ .drag = true } },
        .children = &.{label},
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(
        .{ .id = 1, .kind = .panel, .children = &.{source} },
        geometry.RectF.init(0, 0, 240, 120),
        &nodes,
    );
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    const point = (try harness.runtime.canvasWidgetLayout(1, "canvas")).findById(3).?.frame.normalized().center();

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = point.x,
        .y = point.y,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_drag,
        .x = point.x + 12,
        .y = point.y,
    } });

    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.pointer_target_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.drag_source_id);
    try std.testing.expectEqual(@as(usize, 5), app_state.pointer_route_len);
    try std.testing.expectEqual(canvas.WidgetEventPhase.capture, app_state.pointer_route[0].phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 1), app_state.pointer_route[0].id);
    try std.testing.expectEqual(canvas.WidgetEventPhase.capture, app_state.pointer_route[1].phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.pointer_route[1].id);
    try std.testing.expectEqual(canvas.WidgetEventPhase.target, app_state.pointer_route[2].phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.pointer_route[2].id);
    try std.testing.expectEqual(canvas.WidgetEventPhase.bubble, app_state.pointer_route[3].phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.pointer_route[3].id);
    try std.testing.expectEqual(canvas.WidgetEventPhase.bubble, app_state.pointer_route[4].phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 1), app_state.pointer_route[4].id);
}

test "unmounted drag source still dispatches one captured cancel" {
    const TestApp = struct {
        widget_drag_count: u32 = 0,
        last_drag_phase: canvas.WidgetDragPhase = .change,
        last_drag_source_id: canvas.ObjectId = 0,
        last_drag_route_len: usize = 0,
        source_unmounted: bool = false,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-drag-unmount", .source = platform.WebViewSource.html("<h1>GPU</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_drag => |drag_event| {
                    self.widget_drag_count += 1;
                    self.last_drag_phase = drag_event.drag.phase;
                    self.last_drag_source_id = if (drag_event.source) |source| source.id else 0;
                    self.last_drag_route_len = drag_event.route.len;
                    if (drag_event.drag.phase == .change and !self.source_unmounted) {
                        self.source_unmounted = true;
                        var empty_nodes: [1]canvas.WidgetLayoutNode = undefined;
                        const empty_layout = try canvas.layoutWidgetTree(
                            .{ .id = 1, .kind = .panel },
                            geometry.RectF.init(0, 0, 240, 120),
                            &empty_nodes,
                        );
                        _ = try runtime.setCanvasWidgetLayout(1, "canvas", empty_layout);
                    }
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });
    const children = [_]canvas.Widget{.{
        .id = 2,
        .kind = .button,
        .frame = geometry.RectF.init(12, 16, 96, 32),
        .text = "Drag",
        .semantics = .{ .actions = .{ .drag = true } },
    }};
    var nodes: [2]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .panel, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 20,
        .y = 28,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_drag,
        .x = 64,
        .y = 30,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_drag_count);
    try std.testing.expectEqual(canvas.WidgetDragPhase.change, app_state.last_drag_phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_drag_source_id);
    try std.testing.expect(!harness.runtime.views[0].canvas_widget_drag_source_attached);
    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",
        .kind = .pointer_up,
        .x = 70,
        .y = 32,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_drag_count);
    try std.testing.expectEqual(canvas.WidgetDragPhase.cancel, app_state.last_drag_phase);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_drag_source_id);
    try std.testing.expectEqual(@as(usize, 3), app_state.last_drag_route_len);
    try std.testing.expectEqual(@as(canvas.ObjectId, 0), harness.runtime.views[0].canvas_widget_drag_source_id);
    try std.testing.expectEqual(@as(u64, 0), harness.runtime.views[0].canvas_widget_drag_pointer_id);

    // The physical sequence is retired with the synthesized cancellation;
    // another terminal edge cannot deliver the captured route twice.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_cancel,
        .x = 70,
        .y = 32,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_drag_count);
}

test "plain escape cancels an active canvas widget drag and release cannot revive it" {
    const TestApp = struct {
        widget_drag_count: u32 = 0,
        widget_keyboard_count: u32 = 0,
        last_drag_phase: canvas.WidgetDragPhase = .change,
        last_drag_point: geometry.PointF = .{},
        last_drag_delta: geometry.OffsetF = .{},

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-drag-escape", .source = platform.WebViewSource.html("<h1>GPU</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_drag => |drag_event| {
                    self.widget_drag_count += 1;
                    self.last_drag_phase = drag_event.drag.phase;
                    self.last_drag_point = drag_event.drag.point;
                    self.last_drag_delta = drag_event.drag.delta;
                },
                .canvas_widget_keyboard => self.widget_keyboard_count += 1,
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 240, 120),
    });

    const children = [_]canvas.Widget{.{
        .id = 2,
        .kind = .button,
        .frame = geometry.RectF.init(12, 16, 96, 32),
        .text = "Drag",
        .semantics = .{ .actions = .{ .drag = true } },
    }};
    var nodes: [2]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .panel, .children = &children }, geometry.RectF.init(0, 0, 240, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 20,
        .y = 28,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_drag,
        .x = 64,
        .y = 30,
        .delta_x = 44,
        .delta_y = 2,
    } });
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_drag_count);
    try std.testing.expectEqual(canvas.WidgetDragPhase.change, app_state.last_drag_phase);

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .key_down,
        .key = "escape",
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_drag_count);
    try std.testing.expectEqual(@as(u32, 0), app_state.widget_keyboard_count);
    try std.testing.expectEqual(canvas.WidgetDragPhase.cancel, app_state.last_drag_phase);
    try std.testing.expectEqualDeep(geometry.PointF.init(64, 30), app_state.last_drag_point);
    try std.testing.expectEqualDeep(geometry.OffsetF.init(44, 2), app_state.last_drag_delta);
    const cancelled_state = harness.runtime.views[0].canvasWidgetRenderState();
    try std.testing.expect(cancelled_state.drag_preview_id == null);
    try std.testing.expect(cancelled_state.pressed_id == null);

    // The app's phase-2 rebuild restores the original keyed slot. Its
    // presentation begins exactly where the opaque floating card was when
    // Escape landed, then eases back to the source frame.
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    const landing_state = harness.runtime.views[0].canvasWidgetRenderState();
    try std.testing.expectEqual(@as(usize, 1), landing_state.layout_motions.len);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), landing_state.layout_motions[0].id);
    try std.testing.expectEqualDeep(geometry.OffsetF.init(44, 2), landing_state.layout_motions[0].offset);

    // The still-held physical pointer no longer owns a press. Its remaining
    // motion and release cannot restart or commit the cancelled drag.
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_drag,
        .x = 72,
        .y = 34,
        .delta_x = 52,
        .delta_y = 6,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_up,
        .x = 72,
        .y = 34,
    } });
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_drag_count);
}

test "runtime resizes retained canvas resizable widgets from pointer drag" {
    const TestApp = struct {
        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-resizable-drag", .source = platform.WebViewSource.html("<h1>GPU</h1>") };
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 260, 120),
    });

    const resizable = canvas.Widget{
        .id = 2,
        .kind = .resizable,
        .frame = geometry.RectF.init(10, 16, 120, 44),
        .text = "Resizable",
        .semantics = .{ .label = "Resizable panel" },
    };
    var nodes: [2]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .stack, .children = &.{resizable} }, geometry.RectF.init(0, 0, 260, 120), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    _ = try harness.runtime.emitCanvasWidgetDisplayList(1, "canvas", .{});
    const baseline_command_count = (try harness.runtime.canvasDisplayList(1, "canvas")).commandCount();

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_down,
        .x = 126,
        .y = 38,
    } });
    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_drag,
        .x = 156,
        .y = 38,
        .delta_x = 30,
    } });

    var retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqualDeep(geometry.RectF.init(10, 16, 150, 44), retained.findById(2).?.frame);
    try std.testing.expectEqualDeep(geometry.RectF.init(10, 16, 150, 44), retained.findById(2).?.widget.frame);

    var display_list = try harness.runtime.canvasDisplayList(1, "canvas");
    // Built-in resize semantics mutate retained geometry in place; they do
    // not also paint the generic translated drag preview.
    try std.testing.expectEqual(baseline_command_count, display_list.commandCount());
    switch (display_list.findCommandById(testCanvasWidgetPartId(2, 5)).?.command) {
        .draw_line => |line| try std.testing.expect(line.from.x > 152),
        else => return error.TestUnexpectedResult,
    }

    try harness.runtime.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{
        .window_id = 1,
        .label = "canvas",
        .kind = .pointer_drag,
        .x = 10,
        .y = 38,
        .delta_x = -200,
    } });

    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqual(@as(f32, 48), retained.findById(2).?.frame.width);

    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqual(@as(f32, 48), retained.findById(2).?.frame.width);
    try std.testing.expectEqual(@as(f32, 48), retained.findById(2).?.widget.frame.width);

    display_list = try harness.runtime.canvasDisplayList(1, "canvas");
    switch (display_list.findCommandById(testCanvasWidgetPartId(2, 5)).?.command) {
        .draw_line => |line| try std.testing.expect(line.from.x < 56),
        else => return error.TestUnexpectedResult,
    }
}

test "runtime dispatches automation canvas widget actions" {
    const TestApp = struct {
        command_count: u32 = 0,
        widget_keyboard_count: u32 = 0,
        widget_drag_count: u32 = 0,
        widget_file_drop_count: u32 = 0,
        file_drop_count: u32 = 0,
        raw_input_count: u32 = 0,
        last_command: []const u8 = "",
        last_keyboard_target_id: canvas.ObjectId = 0,
        last_keyboard_key: []const u8 = "",
        last_drag_source_id: canvas.ObjectId = 0,
        last_drag_dx: f32 = 0,
        last_drop_target_id: canvas.ObjectId = 0,
        last_drop_path_count: usize = 0,
        last_drop_first_path: []const u8 = "",

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-automation-actions", .source = platform.WebViewSource.html("<h1>GPU</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .command => |command| {
                    self.command_count += 1;
                    self.last_command = command.name;
                },
                .gpu_surface_input => self.raw_input_count += 1,
                .canvas_widget_keyboard => |keyboard_event| {
                    self.widget_keyboard_count += 1;
                    if (keyboard_event.target) |target| self.last_keyboard_target_id = target.id;
                    self.last_keyboard_key = keyboard_event.keyboard.key;
                },
                .canvas_widget_drag => |drag_event| {
                    self.widget_drag_count += 1;
                    if (drag_event.source) |source| self.last_drag_source_id = source.id;
                    self.last_drag_dx = drag_event.drag.delta.dx;
                },
                .canvas_widget_file_drop => |drop_event| {
                    self.widget_file_drop_count += 1;
                    if (drop_event.target) |target| self.last_drop_target_id = target.id;
                    self.last_drop_path_count = drop_event.drop.paths.len;
                    self.last_drop_first_path = if (drop_event.drop.paths.len > 0) drop_event.drop.paths[0] else "";
                },
                .files_dropped => self.file_drop_count += 1,
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 320, 180),
    });

    const scroll_items = [_]canvas.Widget{
        .{ .id = 8, .kind = .button, .frame = geometry.RectF.init(0, 0, 0, 32), .text = "Row one" },
        .{ .id = 9, .kind = .button, .frame = geometry.RectF.init(0, 64, 0, 32), .text = "Row two" },
        .{ .id = 10, .kind = .button, .frame = geometry.RectF.init(0, 128, 0, 32), .text = "Row three" },
    };
    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .button, .frame = geometry.RectF.init(10, 10, 96, 32), .text = "Run", .command = "widget.run", .semantics = .{ .actions = .{ .drag = true } } },
        .{ .id = 3, .kind = .checkbox, .frame = geometry.RectF.init(10, 52, 96, 28), .text = "Enabled" },
        .{ .id = 4, .kind = .slider, .frame = geometry.RectF.init(10, 88, 120, 24), .value = 0.5, .semantics = .{ .label = "Amount" } },
        .{ .id = 5, .kind = .text_field, .frame = geometry.RectF.init(10, 122, 150, 32), .text = "Draft" },
        .{ .id = 6, .kind = .list_item, .frame = geometry.RectF.init(170, 10, 120, 32), .text = "Inbox" },
        .{ .id = 7, .kind = .scroll_view, .frame = geometry.RectF.init(170, 52, 120, 48), .children = &scroll_items },
        .{ .id = 11, .kind = .button, .frame = geometry.RectF.init(170, 110, 120, 32), .text = "Upload", .semantics = .{ .actions = .{ .drop_files = true } } },
        .{ .id = 12, .kind = .menu_item, .frame = geometry.RectF.init(170, 146, 120, 28), .text = "Archive" },
    };
    var nodes: [13]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .panel, .children = &children }, geometry.RectF.init(0, 0, 320, 180), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 2, .action = .press });
    try std.testing.expect(harness.runtime.views[0].gpu_input_timestamp_ns > 0);
    try std.testing.expect(harness.runtime.views[0].gpu_pending_input_timestamp_ns > 0);
    try std.testing.expectEqual(@as(u32, 1), app_state.command_count);
    try std.testing.expectEqualStrings("widget.run", app_state.last_command);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_keyboard_target_id);

    // The accessibility press rides the SAME key-driven activation the
    // automation press verb uses: the app hears the enter key on the
    // target and the widget's command dispatches through the real
    // activation path. A direct-command shortcut here would skip the
    // keyboard channel, and any widget wired through message handlers
    // instead of a command string would report success while actuating
    // nothing.
    const keyboard_count_before_ax_press = app_state.widget_keyboard_count;
    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 2, .action = .press });
    try std.testing.expectEqual(@as(u32, 2), app_state.command_count);
    try std.testing.expectEqualStrings("widget.run", app_state.last_command);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), harness.runtime.views[0].canvas_widget_focused_id);
    try std.testing.expectEqual(keyboard_count_before_ax_press + 1, app_state.widget_keyboard_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_keyboard_target_id);
    try std.testing.expectEqualStrings("enter", app_state.last_keyboard_key);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 2, .action = .drag, .value = "18 2" });
    // Automation completes the gesture with the release event too.
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_drag_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_drag_source_id);
    try std.testing.expectEqual(@as(f32, 18), app_state.last_drag_dx);

    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 2, .action = .drag, .text = "8 1" });
    try std.testing.expectEqual(@as(u32, 4), app_state.widget_drag_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_drag_source_id);
    try std.testing.expectEqual(@as(f32, 8), app_state.last_drag_dx);

    try harness.runtime.dispatchPlatformEvent(app, .{ .widget_accessibility_action = .{
        .window_id = 1,
        .label = "canvas",
        .id = 2,
        .action = .drag,
    } });
    try std.testing.expectEqual(@as(u32, 6), app_state.widget_drag_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_drag_source_id);
    try std.testing.expectEqual(@as(f32, 16), app_state.last_drag_dx);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 3, .action = .toggle });
    try std.testing.expectEqual(@as(?f32, 1), runtimeViewWidgetSemantics(&harness.runtime.views[0])[2].value);
    try std.testing.expectError(error.InvalidCommand, dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 3, .action = .drag }));

    // The accessibility toggle is the same space-key toggle: the
    // retained value flips back AND the app hears the key — an
    // echo-only flip would leave the model behind and revert on the
    // next rebuild.
    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 3, .action = .toggle });
    try std.testing.expectEqual(@as(?f32, 0), runtimeViewWidgetSemantics(&harness.runtime.views[0])[2].value);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.last_keyboard_target_id);
    try std.testing.expectEqualStrings("space", app_state.last_keyboard_key);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 4, .action = .increment });
    try std.testing.expectApproxEqAbs(@as(f32, 0.55), runtimeViewWidgetSemantics(&harness.runtime.views[0])[3].value.?, 0.001);
    try std.testing.expectEqual(@as(canvas.ObjectId, 4), app_state.last_keyboard_target_id);
    try std.testing.expectEqualStrings("arrowright", app_state.last_keyboard_key);

    // The accessibility increment on a slider is the same arrow step
    // the automation verb dispatches: value moves through the keyboard
    // intent and the app hears the arrow key on the slider.
    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 4, .action = .increment });
    try std.testing.expectApproxEqAbs(@as(f32, 0.6), runtimeViewWidgetSemantics(&harness.runtime.views[0])[3].value.?, 0.001);
    try std.testing.expectEqual(@as(canvas.ObjectId, 4), app_state.last_keyboard_target_id);
    try std.testing.expectEqualStrings("arrowright", app_state.last_keyboard_key);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 6, .action = .select });
    try std.testing.expectEqual(@as(?f32, 1), runtimeViewWidgetSemantics(&harness.runtime.views[0])[5].value);

    // A lone menu item in an ACTIONS group (no sibling declares a
    // committed row): the select action focuses it but mints no
    // selection — only picker groups with a declared committed row
    // move one.
    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 12, .action = .select });
    const selected_layout = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expect(!selected_layout.findById(12).?.widget.state.selected);
    try std.testing.expectEqual(@as(f32, 0), selected_layout.findById(12).?.widget.value);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 7, .action = .increment });
    var scrolled_layout = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectApproxEqAbs(@as(f32, 40.8), scrolled_layout.findById(7).?.widget.value, 0.001);
    try std.testing.expectEqual(@as(canvas.ObjectId, 7), app_state.last_keyboard_target_id);
    try std.testing.expectEqualStrings("pagedown", app_state.last_keyboard_key);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 7, .action = .decrement });
    scrolled_layout = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqual(@as(f32, 0.0), scrolled_layout.findById(7).?.widget.value);
    try std.testing.expectEqualStrings("pageup", app_state.last_keyboard_key);

    // AX scroll steps ride the page keys the automation verbs use, so
    // the app's keyboard channel hears them like any live page scroll.
    const keyboard_count_before_ax_scroll = app_state.widget_keyboard_count;
    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 7, .action = .increment });
    scrolled_layout = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectApproxEqAbs(@as(f32, 40.8), scrolled_layout.findById(7).?.widget.value, 0.001);
    try std.testing.expectEqual(keyboard_count_before_ax_scroll + 1, app_state.widget_keyboard_count);
    try std.testing.expectEqualStrings("pagedown", app_state.last_keyboard_key);

    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 7, .action = .decrement });
    scrolled_layout = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expectEqual(@as(f32, 0.0), scrolled_layout.findById(7).?.widget.value);
    try std.testing.expectEqual(keyboard_count_before_ax_scroll + 2, app_state.widget_keyboard_count);
    try std.testing.expectEqualStrings("pageup", app_state.last_keyboard_key);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 11, .action = .drop_files, .value = "/tmp/report.csv /tmp/chart.png" });
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_file_drop_count);
    try std.testing.expectEqual(@as(u32, 1), app_state.file_drop_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 11), app_state.last_drop_target_id);
    try std.testing.expectEqual(@as(usize, 2), app_state.last_drop_path_count);
    try std.testing.expectEqualStrings("/tmp/report.csv", app_state.last_drop_first_path);
    try std.testing.expectEqualStrings("drop:files", harness.null_platform.lastWindowEventName());
    try std.testing.expect(std.mem.indexOf(u8, harness.null_platform.lastWindowEventDetail(), "\"paths\":[\"/tmp/report.csv\",\"/tmp/chart.png\"]") != null);

    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 11, .action = .drop_files, .text = "/tmp/accessibility.csv" });
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_file_drop_count);
    try std.testing.expectEqual(@as(u32, 2), app_state.file_drop_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 11), app_state.last_drop_target_id);
    try std.testing.expectEqual(@as(usize, 1), app_state.last_drop_path_count);
    try std.testing.expectEqualStrings("/tmp/accessibility.csv", app_state.last_drop_first_path);
    try std.testing.expect(std.mem.indexOf(u8, harness.null_platform.lastWindowEventDetail(), "\"paths\":[\"/tmp/accessibility.csv\"]") != null);
    try std.testing.expectError(error.InvalidCommand, harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 11, .action = .drop_files }));
    try std.testing.expectError(error.InvalidCommand, dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 11, .action = .drop_files }));
    try std.testing.expectError(error.InvalidCommand, dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 3, .action = .drop_files, .value = "/tmp/report.csv" }));

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 5, .action = .set_text, .value = "Hello world" });
    try std.testing.expectEqualStrings("Hello world", runtimeViewWidgetSemantics(&harness.runtime.views[0])[4].label);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 5, .action = .set_composition, .value = "!" });
    try std.testing.expectEqualStrings("Hello world!", runtimeViewWidgetSemantics(&harness.runtime.views[0])[4].text_value);
    try std.testing.expectEqualDeep(canvas.TextRange.init(11, 12), runtimeViewWidgetSemantics(&harness.runtime.views[0])[4].text_composition.?);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 5, .action = .commit_composition });
    try std.testing.expectEqualStrings("Hello world!", runtimeViewWidgetSemantics(&harness.runtime.views[0])[4].text_value);
    try std.testing.expect(runtimeViewWidgetSemantics(&harness.runtime.views[0])[4].text_composition == null);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 5, .action = .set_composition, .value = " draft" });
    try std.testing.expectEqualStrings("Hello world! draft", runtimeViewWidgetSemantics(&harness.runtime.views[0])[4].text_value);
    try std.testing.expectEqualDeep(canvas.TextRange.init(12, 18), runtimeViewWidgetSemantics(&harness.runtime.views[0])[4].text_composition.?);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 5, .action = .cancel_composition });
    try std.testing.expectEqualStrings("Hello world!", runtimeViewWidgetSemantics(&harness.runtime.views[0])[4].text_value);
    try std.testing.expect(runtimeViewWidgetSemantics(&harness.runtime.views[0])[4].text_composition == null);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 5, .action = .set_selection, .value = "0 5" });
    try std.testing.expectEqualDeep(canvas.TextRange.init(0, 5), runtimeViewWidgetSemantics(&harness.runtime.views[0])[4].text_selection.?);
    const selection_snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expect(selection_snapshot.widgets[4].actions.set_selection);
    try std.testing.expectEqualDeep(automation.snapshot.TextRange{ .start = 0, .end = 5 }, selection_snapshot.widgets[4].text_selection.?);
    try std.testing.expectError(error.InvalidCommand, dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 5, .action = .set_selection, .value = "nope" }));
    try std.testing.expectError(error.InvalidCommand, dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 3, .action = .set_selection, .value = "0 1" }));

    try std.testing.expect(app_state.widget_keyboard_count >= 3);
    try std.testing.expect(app_state.raw_input_count >= 3);
}

test "accessibility press actuates widgets wired through message handlers" {
    // The defect this pins: a widget whose press wiring is a bound
    // message handler (no `command` string — every markup `on-press`
    // and Zig-builder `on_press` widget) published `press` to the
    // platform accessibility tree, and performing it reported success
    // while dispatching nothing the app could hear. The honest route is
    // the key-driven activation the automation press verb uses: the app
    // receives the enter key ON the target, so the same typed dispatch
    // a keyboard user's activation reaches fires the handler.
    const TestApp = struct {
        widget_keyboard_count: u32 = 0,
        last_keyboard_target_id: canvas.ObjectId = 0,
        last_keyboard_key: []const u8 = "",

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-ax-press-handlers", .source = platform.WebViewSource.html("<h1>GPU</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .canvas_widget_keyboard => |keyboard_event| {
                    self.widget_keyboard_count += 1;
                    if (keyboard_event.target) |target| self.last_keyboard_target_id = target.id;
                    self.last_keyboard_key = keyboard_event.keyboard.key;
                },
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 320, 180),
    });

    const children = [_]canvas.Widget{
        // A commandless button: the segmented-switcher shape (a tab
        // that binds on_press and nothing else).
        .{ .id = 2, .kind = .button, .frame = geometry.RectF.init(10, 10, 96, 32), .text = "Songs" },
        // A plain list row with a bound press (the album-card shape):
        // quiet focus makes plain rows transparent to keys, so the
        // dispatch must escalate this target to the ring register or
        // the synthesized enter routes as a target-less event.
        .{ .id = 3, .kind = .list_item, .frame = geometry.RectF.init(10, 52, 200, 40), .text = "Exit Signs", .semantics = .{ .actions = .{ .press = true } } },
        // No press wiring at all: the runtime must refuse, never
        // succeed-and-do-nothing.
        .{ .id = 4, .kind = .text, .frame = geometry.RectF.init(10, 104, 96, 20), .text = "8 of 8" },
    };
    var nodes: [4]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .id = 1, .kind = .panel, .children = &children }, geometry.RectF.init(0, 0, 320, 180), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 2, .action = .press });
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_keyboard_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 2), app_state.last_keyboard_target_id);
    try std.testing.expectEqualStrings("enter", app_state.last_keyboard_key);

    _ = try harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 3, .action = .press });
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_keyboard_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.last_keyboard_target_id);
    try std.testing.expectEqualStrings("enter", app_state.last_keyboard_key);
    // The escalation is observable: the row now holds the ring
    // register, exactly what a Tab-then-Enter would have left.
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), harness.runtime.views[0].canvas_widget_focus_visible_id);

    try std.testing.expectError(error.InvalidCommand, harness.runtime.dispatchCanvasWidgetAccessibilityAction(app, 1, "canvas", .{ .id = 4, .action = .press }));
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_keyboard_count);
}

test "runtime rejects automation canvas widget actions for scroll clipped targets" {
    const TestApp = struct {
        widget_drag_count: u32 = 0,
        widget_file_drop_count: u32 = 0,
        file_drop_count: u32 = 0,
        raw_input_count: u32 = 0,
        last_drag_source_id: canvas.ObjectId = 0,
        last_drop_target_id: canvas.ObjectId = 0,

        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-clipped-automation-actions", .source = platform.WebViewSource.html("<h1>GPU</h1>"), .event_fn = event };
        }

        fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void {
            _ = runtime;
            const self: *@This() = @ptrCast(@alignCast(context));
            switch (event_value) {
                .gpu_surface_input => self.raw_input_count += 1,
                .canvas_widget_drag => |drag_event| {
                    self.widget_drag_count += 1;
                    if (drag_event.source) |source| self.last_drag_source_id = source.id;
                },
                .canvas_widget_file_drop => |drop_event| {
                    self.widget_file_drop_count += 1;
                    if (drop_event.target) |target| self.last_drop_target_id = target.id;
                },
                .files_dropped => self.file_drop_count += 1,
                else => {},
            }
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 160, 48),
    });

    const selectable_children = [_]canvas.Widget{
        .{ .id = 2, .kind = .list_item, .frame = geometry.RectF.init(0, 0, 0, 32), .text = "Hidden" },
        .{ .id = 3, .kind = .list_item, .frame = geometry.RectF.init(0, 48, 0, 32), .text = "Visible" },
    };
    var selectable_nodes: [3]canvas.WidgetLayoutNode = undefined;
    const selectable_layout = try canvas.layoutWidgetTree(
        .{ .id = 1, .kind = .scroll_view, .value = 40, .children = &selectable_children },
        geometry.RectF.init(0, 0, 160, 48),
        &selectable_nodes,
    );
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", selectable_layout);

    var snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expect(snapshot.widgets[1].actions.select);
    try std.testing.expect(snapshot.widgets[2].actions.select);
    try std.testing.expectEqualDeep(geometry.RectF.init(0, -32, 160, 32), snapshot.widgets[1].bounds);
    try std.testing.expectEqualDeep(geometry.RectF.init(0, 16, 160, 32), snapshot.widgets[2].bounds);

    try std.testing.expectError(error.InvalidCommand, dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 2, .action = .select }));
    var retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expect(!retained.findById(2).?.widget.state.selected);
    try std.testing.expectEqual(@as(f32, 0), retained.findById(2).?.widget.value);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 3, .action = .select });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expect(retained.findById(3).?.widget.state.selected);
    try std.testing.expectEqual(@as(f32, 1), retained.findById(3).?.widget.value);

    const drag_children = [_]canvas.Widget{
        .{ .id = 2, .kind = .button, .frame = geometry.RectF.init(0, 0, 0, 32), .text = "Hidden drag", .semantics = .{ .actions = .{ .drag = true } } },
        .{ .id = 3, .kind = .button, .frame = geometry.RectF.init(0, 48, 0, 32), .text = "Visible drag", .semantics = .{ .actions = .{ .drag = true } } },
    };
    var drag_nodes: [3]canvas.WidgetLayoutNode = undefined;
    const drag_layout = try canvas.layoutWidgetTree(
        .{ .id = 1, .kind = .scroll_view, .value = 40, .children = &drag_children },
        geometry.RectF.init(0, 0, 160, 48),
        &drag_nodes,
    );
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", drag_layout);

    snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expect(snapshot.widgets[1].actions.drag);
    try std.testing.expect(snapshot.widgets[2].actions.drag);
    try std.testing.expectError(error.InvalidCommand, dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 2, .action = .drag, .value = "8 0" }));
    try std.testing.expectEqual(@as(u32, 0), app_state.widget_drag_count);
    try std.testing.expectEqual(@as(u32, 0), app_state.raw_input_count);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 3, .action = .drag, .value = "8 0" });
    try std.testing.expectEqual(@as(u32, 2), app_state.widget_drag_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.last_drag_source_id);

    const drop_children = [_]canvas.Widget{
        .{ .id = 2, .kind = .button, .frame = geometry.RectF.init(0, 0, 0, 32), .text = "Hidden drop", .semantics = .{ .actions = .{ .drop_files = true } } },
        .{ .id = 3, .kind = .button, .frame = geometry.RectF.init(0, 48, 0, 32), .text = "Visible drop", .semantics = .{ .actions = .{ .drop_files = true } } },
    };
    var drop_nodes: [3]canvas.WidgetLayoutNode = undefined;
    const drop_layout = try canvas.layoutWidgetTree(
        .{ .id = 1, .kind = .scroll_view, .value = 40, .children = &drop_children },
        geometry.RectF.init(0, 0, 160, 48),
        &drop_nodes,
    );
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", drop_layout);

    snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expect(snapshot.widgets[1].actions.drop_files);
    try std.testing.expect(snapshot.widgets[2].actions.drop_files);
    try std.testing.expectError(error.InvalidCommand, dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 2, .action = .drop_files, .value = "/tmp/hidden.csv" }));
    try std.testing.expectEqual(@as(u32, 0), app_state.widget_file_drop_count);
    try std.testing.expectEqual(@as(u32, 0), app_state.file_drop_count);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 3, .action = .drop_files, .value = "/tmp/visible.csv" });
    try std.testing.expectEqual(@as(u32, 1), app_state.widget_file_drop_count);
    try std.testing.expectEqual(@as(u32, 1), app_state.file_drop_count);
    try std.testing.expectEqual(@as(canvas.ObjectId, 3), app_state.last_drop_target_id);
}

test "runtime rejects automation canvas widget actions for clip content clipped targets" {
    const TestApp = struct {
        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-clip-content-automation-actions", .source = platform.WebViewSource.html("<h1>GPU</h1>") };
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 96, 48),
    });

    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .list_item, .frame = geometry.RectF.init(64, 0, 32, 32), .text = "Clipped" },
        .{ .id = 3, .kind = .list_item, .frame = geometry.RectF.init(0, 0, 32, 32), .text = "Visible" },
    };
    var nodes: [3]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(
        .{ .id = 1, .kind = .stack, .layout = .{ .clip_content = true }, .children = &children },
        geometry.RectF.init(0, 0, 48, 40),
        &nodes,
    );
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    var snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expect(snapshot.widgets[1].actions.select);
    try std.testing.expect(snapshot.widgets[2].actions.select);

    try std.testing.expectError(error.InvalidCommand, dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 2, .action = .select }));
    var retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expect(!retained.findById(2).?.widget.state.selected);

    try dispatchAutomationWidgetAction(&harness.runtime, app, .{ .view_label = "canvas", .id = 3, .action = .select });
    retained = try harness.runtime.canvasWidgetLayout(1, "canvas");
    try std.testing.expect(retained.findById(3).?.widget.state.selected);
    snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expectEqual(@as(?f32, 1), snapshot.widgets[2].value);
}

test "runtime automation protocol refreshes widget-owned canvas display lists" {
    const TestApp = struct {
        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-automation-display-list", .source = platform.WebViewSource.html("<h1>GPU</h1>") };
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 320, 180),
    });

    const list_items = [_]canvas.Widget{
        .{ .id = 4, .kind = .list_item, .frame = geometry.RectF.init(0, 0, 0, 30), .text = "Overview" },
        .{ .id = 5, .kind = .list_item, .frame = geometry.RectF.init(0, 36, 0, 30), .text = "Customers" },
    };
    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(10, 12, 150, 32), .text = "Draft" },
        .{ .id = 3, .kind = .list, .frame = geometry.RectF.init(10, 58, 150, 72), .layout = .{ .gap = 6 }, .children = &list_items },
    };
    var nodes: [5]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 320, 180), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    _ = try harness.runtime.emitCanvasWidgetDisplayList(1, "canvas", .{});

    var display_list = try harness.runtime.canvasDisplayList(1, "canvas");
    var saw_initial_text = false;
    var saw_initial_selection = false;
    for (display_list.commands) |command| {
        switch (command) {
            .draw_text => |text| {
                if (text.id == testCanvasWidgetPartId(2, 3)) {
                    try std.testing.expectEqualStrings("Draft", text.text);
                    saw_initial_text = true;
                }
            },
            .fill_rounded_rect => |fill| {
                if (fill.id == testCanvasWidgetPartId(5, 1)) saw_initial_selection = true;
            },
            else => {},
        }
    }
    try std.testing.expect(saw_initial_text);
    try std.testing.expect(!saw_initial_selection);

    try harness.runtime.dispatchAutomationCommand(app, "widget-action canvas 2 set-text Launch");

    display_list = try harness.runtime.canvasDisplayList(1, "canvas");
    var saw_updated_text = false;
    var saw_stale_text = false;
    var saw_text_caret = false;
    for (display_list.commands) |command| {
        switch (command) {
            .draw_text => |text| {
                if (text.id == testCanvasWidgetPartId(2, 4)) {
                    try std.testing.expectEqualStrings("Launch", text.text);
                    saw_updated_text = true;
                }
                if (std.mem.eql(u8, text.text, "Draft")) saw_stale_text = true;
            },
            .fill_rect => |bar| {
                if (bar.id == testCanvasWidgetPartId(2, 6)) saw_text_caret = true;
            },
            else => {},
        }
    }
    try std.testing.expect(saw_updated_text);
    try std.testing.expect(!saw_stale_text);
    try std.testing.expect(saw_text_caret);

    var snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expectEqualStrings("Launch", snapshot.widgets[0].text_value);
    try std.testing.expectEqualDeep(automation.snapshot.TextRange{ .start = 6, .end = 6 }, snapshot.widgets[0].text_selection.?);

    try harness.runtime.dispatchAutomationCommand(app, "widget-action canvas 5 select");

    display_list = try harness.runtime.canvasDisplayList(1, "canvas");
    var saw_selected_item_fill = false;
    for (display_list.commands) |command| {
        switch (command) {
            .fill_rounded_rect => |fill| {
                if (fill.id == testCanvasWidgetPartId(5, 1)) saw_selected_item_fill = true;
            },
            else => {},
        }
    }
    try std.testing.expect(saw_selected_item_fill);

    snapshot = harness.runtime.automationSnapshot("Widgets");
    try std.testing.expect(!snapshot.widgets[2].selected);
    try std.testing.expect(snapshot.widgets[3].selected);
}

test "runtime preserves canvas chrome when widget-owned display lists refresh" {
    const TestApp = struct {
        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-chrome-display-list", .source = platform.WebViewSource.html("<h1>GPU</h1>") };
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    const app = app_state.app();
    try harness.start(app);

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 320, 180),
    });

    const children = [_]canvas.Widget{
        .{ .id = 2, .kind = .text_field, .frame = geometry.RectF.init(24, 24, 150, 32), .text = "Draft" },
    };
    var nodes: [2]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{ .kind = .stack, .children = &children }, geometry.RectF.init(0, 0, 320, 180), &nodes);

    const stops = [_]canvas.GradientStop{
        .{ .offset = 0, .color = canvas.Color.rgb8(48, 111, 237) },
        .{ .offset = 1, .color = canvas.Color.rgb8(16, 185, 129) },
    };
    var commands: [max_canvas_commands_per_view]canvas.CanvasCommand = undefined;
    var builder = canvas.Builder.init(&commands);
    try builder.drawText(.{
        .id = 10,
        .font_id = 1,
        .size = 12,
        .origin = geometry.PointF.init(16, 16),
        .color = canvas.Color.rgb8(18, 24, 38),
        .text = "Chrome header",
    });
    try layout.emitDisplayList(&builder, .{});
    try builder.fillRect(.{
        .id = 11,
        .rect = geometry.RectF.init(16, 148, 288, 12),
        .fill = .{ .linear_gradient = .{
            .start = geometry.PointF.init(16, 148),
            .end = geometry.PointF.init(304, 148),
            .stops = &stops,
        } },
    });

    _ = try harness.runtime.setCanvasDisplayList(1, "canvas", builder.displayList());
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);
    _ = try harness.runtime.emitCanvasWidgetDisplayListWithChrome(1, "canvas", .{}, .{
        .prefix_command_count = 1,
        .suffix_command_count = 1,
    });

    var display_list = try harness.runtime.canvasDisplayList(1, "canvas");
    try std.testing.expectEqual(@as(usize, 5), display_list.commandCount());
    switch (display_list.findCommandById(10).?.command) {
        .draw_text => |text| try std.testing.expectEqualStrings("Chrome header", text.text),
        else => return error.UnexpectedCanvasCommand,
    }
    try std.testing.expect(display_list.findCommandById(11) != null);
    try std.testing.expect(display_list.findCommandById(testCanvasWidgetPartId(2, 3)) != null);

    try harness.runtime.dispatchAutomationCommand(app, "widget-action canvas 2 set-text Launch");

    display_list = try harness.runtime.canvasDisplayList(1, "canvas");
    switch (display_list.findCommandById(10).?.command) {
        .draw_text => |text| try std.testing.expectEqualStrings("Chrome header", text.text),
        else => return error.UnexpectedCanvasCommand,
    }
    try std.testing.expect(display_list.findCommandById(11) != null);
    switch (display_list.findCommandById(testCanvasWidgetPartId(2, 4)).?.command) {
        .draw_text => |text| try std.testing.expectEqualStrings("Launch", text.text),
        else => return error.UnexpectedCanvasCommand,
    }
}

test "runtime reserves widget-owned canvas display list command headroom" {
    const TestApp = struct {
        fn app(self: *@This()) App {
            return .{ .context = self, .name = "gpu-widget-display-list-headroom", .source = platform.WebViewSource.html("<h1>GPU</h1>") };
        }
    };

    const harness = try TestHarness().create(std.testing.allocator, .{});
    defer harness.destroy(std.testing.allocator);
    harness.null_platform.gpu_surfaces = true;
    var app_state: TestApp = .{};
    try harness.start(app_state.app());

    _ = try harness.runtime.createView(.{
        .window_id = 1,
        .label = "canvas",
        .kind = .gpu_surface,
        .frame = geometry.RectF.init(0, 0, 320, 180),
    });

    var nodes: [1]canvas.WidgetLayoutNode = undefined;
    const layout = try canvas.layoutWidgetTree(.{
        .id = 2,
        .kind = .text,
        .frame = geometry.RectF.init(16, 16, 120, 20),
        .text = "Headroom",
    }, geometry.RectF.init(0, 0, 320, 180), &nodes);
    _ = try harness.runtime.setCanvasWidgetLayout(1, "canvas", layout);

    var info = try harness.runtime.emitCanvasWidgetDisplayListWithChrome(1, "canvas", .{}, .{
        .reserved_command_count = max_canvas_commands_per_view - 1,
    });
    try std.testing.expectEqual(@as(usize, 1), info.canvas_command_count);

    try std.testing.expectError(error.CanvasCommandLimitReached, harness.runtime.emitCanvasWidgetDisplayListWithChrome(1, "canvas", .{}, .{
        .reserved_command_count = max_canvas_commands_per_view,
    }));

    const display_list = try harness.runtime.canvasDisplayList(1, "canvas");
    try std.testing.expectEqual(@as(usize, 1), display_list.commandCount());
    try std.testing.expect(display_list.findCommandById(testCanvasWidgetPartId(2, 1)) != null);

    info = try harness.runtime.emitCanvasWidgetDisplayListWithChrome(1, "canvas", .{}, .{});
    try std.testing.expectEqual(@as(usize, 1), info.canvas_command_count);
}
