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.

docs/architecture/multi-window-ownership.mdmodified+4-1
Changes to docs/architecture/multi-window-ownership.md
@@ -28,7 +28,10 @@ WebKitGTK/WebView2 view; names select a target only after that sender is known.
2828Replies go back to the requesting view, and lifecycle events go to the changed
2929window plus its named handle's creator. Linux must register its `GtkApplication`
3030before constructing the first `GtkApplicationWindow`, because the CLI creates
31that window before entering `g_application_run()`.
31that window before entering `g_application_run()`. On Windows, the controller
32passed to the asynchronous WebView2 completion callback is borrowed. Each live
33window retains its own controller reference until native destruction; otherwise
34WebView2 can close before the page bridge registers or navigation begins.
3235
3336## Per-window state
3437
packages/zig/src/windows.zigmodified+4-0
Changes to packages/zig/src/windows.zig
@@ -567,6 +567,10 @@ const ControllerCompletedHandler = extern struct {
567567 return perm_hr;
568568 }
569569
570 // The callback only lends us the controller. Keep our own reference
571 // past Invoke so WebView2 does not close before Window.create uses it.
572 _ = controller.lpVtbl.AddRef(controller);
573
570574 // Store results
571575 self.ctx.controller = controller;
572576 self.ctx.webview = webview;
scripts/verify-desktop-multi-window.tsmodified+3-0
Changes to scripts/verify-desktop-multi-window.ts
@@ -58,6 +58,9 @@ const mainPage = `<!doctype html><script>
5858 await window.craft.window._call('close', {}, 'settings')
5959 await closed
6060 await report('closed')
61 const survivingMain = await window.craft.window._call('getBounds', {}, 'main')
62 if (!(survivingMain.width > 0 && survivingMain.height > 0))
63 throw new Error('closing child invalidated main controller')
6164 let staleRejected = false
6265 try { await window.craft.window._call('getBounds', {}, 'settings') }
6366 catch (_) { staleRejected = true }