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/typescript/src/__tests__/window-routing.test.tsmodified+18-0
Changes to packages/typescript/src/__tests__/window-routing.test.ts
@@ -140,6 +140,24 @@ describe('typed window-handle routing', () => {
140140 expect(created.id).toBe(id)
141141 })
142142
143 it('uses the injected window envelope in WebView2 as well as WebKit', async () => {
144 const host = globalThis.window as unknown as {
145 webkit?: unknown
146 chrome?: { webview: { postMessage: () => void } }
147 }
148 Reflect.deleteProperty(host, 'webkit')
149 host.chrome = { webview: { postMessage: () => {} } }
150 const id = fixtureId('webview2')
151 const created = await windowManager.create({ id, html: '<h1>Windows</h1>' })
152 await created.setTitle('Windows')
153 callResult = id
154
155 expect(open).toHaveBeenCalledWith({ id, html: '<h1>Windows</h1>' })
156 expect(call).toHaveBeenCalledWith('setTitle', { title: 'Windows' }, id)
157 expect(await windowManager.getFocused()).toBe(created)
158 expect(call).toHaveBeenCalledWith('getFocused', undefined, 'main')
159 })
160
143161 it('refuses the local main alias before opening a child window', async () => {
144162 await expect(windowManager.create({ id: 'main', html: '<h1>Orphan</h1>' }))
145163 .rejects.toThrow('reserved for the current window')
packages/typescript/src/api/window.tsmodified+4-4
Changes to packages/typescript/src/api/window.ts
@@ -5,7 +5,7 @@
55 */
66
77import { getBridge } from '../bridge/core.js'
8import { isWebKitHost } from '../bridge/webkit-pending.js'
8import { isDesktopWebViewHost } from '../bridge/webkit-pending.js'
99
1010interface InjectedWindowBridge {
1111 _call<T>(action: string, data: Record<string, any> | undefined, windowId: string): Promise<T>
@@ -740,7 +740,7 @@ export class Window {
740740 // ==========================================================================
741741
742742 private async _call<T = void>(action: string, data?: Record<string, any>): Promise<T> {
743 if (isWebKitHost()) {
743 if (isDesktopWebViewHost()) {
744744 const injected = getInjectedWindowBridge()
745745 if (!injected?._call) {
746746 throw new Error('Craft window bridge is unavailable')
@@ -813,7 +813,7 @@ class WindowManager {
813813 }
814814 const existing = this._windows.get(id)
815815
816 if (isWebKitHost()) {
816 if (isDesktopWebViewHost()) {
817817 const injected = getInjectedWindowBridge()
818818 if (!injected?.open) {
819819 throw new Error('Craft window bridge is unavailable')
@@ -844,7 +844,7 @@ class WindowManager {
844844 * Get focused window
845845 */
846846 async getFocused(): Promise<Window | null> {
847 if (isWebKitHost()) {
847 if (isDesktopWebViewHost()) {
848848 const injected = getInjectedWindowBridge()
849849 if (!injected?._call) {
850850 throw new Error('Craft window bridge is unavailable')
packages/typescript/src/bridge/webkit-pending.tsmodified+8-0
Changes to packages/typescript/src/bridge/webkit-pending.ts
@@ -73,6 +73,14 @@ export function isWebKitHost(): boolean {
7373 return typeof window !== 'undefined' && !!window.webkit?.messageHandlers?.craft
7474}
7575
76/** Desktop window handles use the injected Craft envelope on both WebKit and WebView2. */
77export function isDesktopWebViewHost(): boolean {
78 if (isWebKitHost()) return true
79 if (typeof window === 'undefined') return false
80 const host = window as Window & { chrome?: { webview?: { postMessage?: (message: unknown) => void } } }
81 return typeof host.chrome?.webview?.postMessage === 'function'
82}
83
7684/**
7785 * Send a fire-and-forget message into the WKWebView host. Returns true if
7886 * the message was delivered, false otherwise.