ReviewOS

also looking at this

craft-native/craft

feat: add Linux and Windows named-window routing

#325
Merged glennmichael123 wants to merge codex/multi-window-linux-windows into main
29 files +2250 -200

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

packages/zig/build.zigmodified+10-0
Changes to packages/zig/build.zig
@@ -1279,6 +1279,14 @@ pub fn build(b: *std.Build) void {
12791279 }),
12801280 });
12811281
1282 const desktop_window_events_tests = b.addTest(.{
1283 .root_module = b.createModule(.{
1284 .root_source_file = b.path("src/desktop_window_events.zig"),
1285 .target = target,
1286 .optimize = optimize,
1287 }),
1288 });
1289
12821290 const desktop_script_encoding_tests = b.addTest(.{
12831291 .root_module = b.createModule(.{
12841292 .root_source_file = b.path("src/desktop_script_encoding.zig"),
@@ -1744,6 +1752,7 @@ pub fn build(b: *std.Build) void {
17441752 const run_desktop_window_registry_tests = b.addRunArtifact(desktop_window_registry_tests);
17451753 const run_desktop_bridge_envelope_tests = b.addRunArtifact(desktop_bridge_envelope_tests);
17461754 const run_desktop_bridge_text_tests = b.addRunArtifact(desktop_bridge_text_tests);
1755 const run_desktop_window_events_tests = b.addRunArtifact(desktop_window_events_tests);
17471756 const run_desktop_script_encoding_tests = b.addRunArtifact(desktop_script_encoding_tests);
17481757 const run_external_link_tests = b.addRunArtifact(external_link_tests);
17491758 const run_webview_recovery_tests = b.addRunArtifact(webview_recovery_tests);
@@ -1897,6 +1906,7 @@ pub fn build(b: *std.Build) void {
18971906 test_step.dependOn(&run_desktop_window_registry_tests.step);
18981907 test_step.dependOn(&run_desktop_bridge_envelope_tests.step);
18991908 test_step.dependOn(&run_desktop_bridge_text_tests.step);
1909 test_step.dependOn(&run_desktop_window_events_tests.step);
19001910 test_step.dependOn(&run_desktop_script_encoding_tests.step);
19011911 test_step.dependOn(&run_external_link_tests.step);
19021912 test_step.dependOn(&run_webview_recovery_tests.step);
packages/zig/src/desktop_window_events.zigadded+38-0
Changes to packages/zig/src/desktop_window_events.zig
@@ -0,0 +1,38 @@
1//! JS delivery for a native desktop window transition. Platform code chooses
2//! the actual live WebView; this module only formats the event payload.
3
4const std = ;
5const bridge_error = ;
6
7pub fn format(
8 allocator: std.mem.Allocator,
9 event_name: []const u8,
10 detail_json: []const u8,
11 window_name: ?[]const u8,
12) ![]u8 {
13 var script: std.ArrayListUnmanaged(u8) = .empty;
14 errdefer script.deinit(allocator);
15 try script.appendSlice(allocator, "if(window.__craftDeliverWindowEvent)window.__craftDeliverWindowEvent(\"");
16 try bridge_error.appendJsonEscaped(allocator, &script, event_name);
17 try script.appendSlice(allocator, "\",");
18 try script.appendSlice(allocator, if (detail_json.len == 0) "{}" else detail_json);
19 if (window_name) |name| {
20 try script.appendSlice(allocator, ",\"");
21 try bridge_error.appendJsonEscaped(allocator, &script, name);
22 try script.append(allocator, '"');
23 }
24 try script.appendSlice(allocator, ");");
25 return script.toOwnedSlice(allocator);
26}
27
28test "a local transition addresses only the page's main handle" {
29 const script = try format(std.testing.allocator, "focus", "", null);
30 defer std.testing.allocator.free(script);
31 try std.testing.expectEqualStrings("if(window.__craftDeliverWindowEvent)window.__craftDeliverWindowEvent(\"focus\",{});", script);
32}
33
34test "a creator receives the named child event with escaped id" {
35 const script = try format(std.testing.allocator, "resize", "{\"width\":640}", "set\"tings");
36 defer std.testing.allocator.free(script);
37 try std.testing.expectEqualStrings("if(window.__craftDeliverWindowEvent)window.__craftDeliverWindowEvent(\"resize\",{\"width\":640},\"set\\\"tings\");", script);
38}
packages/zig/src/desktop_window_registry.zigmodified+88-0
Changes to packages/zig/src/desktop_window_registry.zig
@@ -7,6 +7,23 @@
77const std = ;
88
99pub const capacity = 32;
10pub const Geometry = struct {
11 x: i32,
12 y: i32,
13 width: u32,
14 height: u32,
15};
16
17pub const GeometryChange = struct {
18 moved: bool,
19 resized: bool,
20};
21
22pub const StateChange = struct {
23 minimized: ?bool,
24 fullscreen: ?bool,
25};
26
1027pub const Entry = struct {
1128 id: u32,
1229 window: usize,
@@ -15,6 +32,9 @@ pub const Entry = struct {
1532 context: usize = 0,
1633 /// WebView2 subscription to remove before releasing a closed webview.
1734 message_token: ?i64 = null,
35 geometry: ?Geometry = null,
36 minimized: bool = false,
37 fullscreen: bool = false,
1838};
1939
2040pub const Registry = struct {
@@ -81,6 +101,42 @@ pub const Registry = struct {
81101 return false;
82102 }
83103
104 pub fn observeGeometry(self: *Registry, window: usize, next: Geometry) ?GeometryChange {
105 if (window == 0) return null;
106 for (&self.entries) |*slot| {
107 if (slot.*) |*entry| {
108 if (entry.window == window) {
109 const previous = entry.geometry;
110 entry.geometry = next;
111 if (previous) |old| return .{
112 .moved = old.x != next.x or old.y != next.y,
113 .resized = old.width != next.width or old.height != next.height,
114 };
115 return .{ .moved = false, .resized = false };
116 }
117 }
118 }
119 return null;
120 }
121
122 pub fn observeState(self: *Registry, window: usize, minimized: bool, fullscreen: bool) ?StateChange {
123 if (window == 0) return null;
124 for (&self.entries) |*slot| {
125 if (slot.*) |*entry| {
126 if (entry.window == window) {
127 const change: StateChange = .{
128 .minimized = if (entry.minimized != minimized) minimized else null,
129 .fullscreen = if (entry.fullscreen != fullscreen) fullscreen else null,
130 };
131 entry.minimized = minimized;
132 entry.fullscreen = fullscreen;
133 return change;
134 }
135 }
136 }
137 return null;
138 }
139
84140 pub fn latest(self: *const Registry) ?Entry {
85141 var result: ?Entry = null;
86142 for (self.entries) |slot| {
@@ -185,6 +241,38 @@ test "a web message subscription belongs only to its live window" {
185241 try std.testing.expect(registry.byWebview(0x1001) == null);
186242}
187243
244test "geometry changes belong to one live window and reset on reopen" {
245 var registry: Registry = .{};
246 _ = registry.remember(0x1000, 0x1001);
247 _ = registry.remember(0x2000, 0x2001);
248 const initial: Geometry = .{ .x = 10, .y = 20, .width = 800, .height = 600 };
249 const first = registry.observeGeometry(0x1000, initial).?;
250 try std.testing.expect(!first.moved and !first.resized);
251 const changed = registry.observeGeometry(0x1000, .{ .x = 30, .y = 20, .width = 900, .height = 600 }).?;
252 try std.testing.expect(changed.moved and changed.resized);
253 try std.testing.expect(registry.byWindow(0x2000).?.geometry == null);
254 _ = registry.forgetWindow(0x1000);
255 try std.testing.expect(registry.observeGeometry(0x1000, initial) == null);
256 _ = registry.remember(0x1000, 0x3001);
257 try std.testing.expect(registry.byWindow(0x1000).?.geometry == null);
258}
259
260test "minimize and fullscreen transitions do not leak across windows" {
261 var registry: Registry = .{};
262 _ = registry.remember(0x1000, 0x1001);
263 _ = registry.remember(0x2000, 0x2001);
264 const minimized = registry.observeState(0x1000, true, false).?;
265 try std.testing.expectEqual(@as(?bool, true), minimized.minimized);
266 try std.testing.expect(minimized.fullscreen == null);
267 const same = registry.observeState(0x1000, true, false).?;
268 try std.testing.expect(same.minimized == null and same.fullscreen == null);
269 const restored = registry.observeState(0x1000, false, true).?;
270 try std.testing.expectEqual(@as(?bool, false), restored.minimized);
271 try std.testing.expectEqual(@as(?bool, true), restored.fullscreen);
272 try std.testing.expect(!registry.byWindow(0x2000).?.minimized);
273 try std.testing.expect(!registry.byWindow(0x2000).?.fullscreen);
274}
275
188276test "a window keeps its own platform control object" {
189277 var registry: Registry = .{};
190278 const first = registry.rememberWithContext(0x1000, 0x1001, 0x1002).?;