experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(v1-removal): Phase 3.7h — collapse window-* fallbacks to strict tile:window:* only

Tile-preload's api.window.* methods previously had a per-call dual
path: strict `tile:window:*` if the tile declared a `window`
capability, else legacy un-gated `window-*` IPC. The fallback was
the last unrestricted access path to window operations from any
tile, including those that hadn't declared the capability.

Audit: every renderer caller of api.window.* either has the right
window subcap or is trustedBuiltin. The one feature that would have
broken (widget-demo's bookmarks widget calling api.window.open) now
declares `window: { create: true }`.

- tile-preload.cts: drop the `hasWindowCapability()` helper and every
fallback branch in api.window.{open, close, getInfo, list, exists,
show, hide, focus, setIgnoreMouseEvents, center, centerAll,
maximize, fullscreen, setOverlayFocusTarget,
setVisibleOnAllWorkspaces}. Every call now routes unconditionally
through the strict tile:window:* handler.
- ipc.ts: delete the 13 legacy `window-*` ipcMain.handle() registrations
(window-open, window-close, window-hide, window-show, window-focus,
window-exists, window-list, window-center, window-center-all,
window-maximize, window-fullscreen, window-set-ignore-mouse-events,
window-set-overlay-focus-target). The `windowOpenHandler` function
body stays — the strict tile:window:open handler in tile-ipc.ts
delegates to it via the exported `invokeWindowOpen` reference.
- features/widget-demo/manifest.json: declare `window: { create: true }`
so its bookmarks widget can still call api.window.open.

Build green, unit tests 588/588.

+100 -559
+14 -386
backend/electron/ipc.ts
··· 2061 2061 return { success: false, error: message }; 2062 2062 } 2063 2063 }; 2064 - ipcMain.handle('window-open', windowOpenHandler); 2064 + // Legacy `window-open` ipcMain handler removed (v1 removal Phase 3.7h). 2065 + // The strict `tile:window:open` handler in tile-ipc.ts delegates to 2066 + // `invokeWindowOpen` (the same implementation) — it's the single 2067 + // source of truth for window creation. 2065 2068 invokeWindowOpen = windowOpenHandler; 2066 2069 2067 - // Toggle click-through on transparent windows 2068 - ipcMain.handle('window-set-ignore-mouse-events', (ev, msg) => { 2069 - const win = msg?.id ? BrowserWindow.fromId(msg.id) : BrowserWindow.fromWebContents(ev.sender); 2070 - if (!win || win.isDestroyed()) return { success: false }; 2071 - win.setIgnoreMouseEvents(msg.ignore, msg.forward ? { forward: true } : undefined); 2072 - return { success: true }; 2073 - }); 2074 - 2075 - 2076 - // Legacy `window-get-bounds` removed (v1 removal Phase 3.7b). Tile- 2077 - // preload's api.window.getBounds uses tile:window:get-bounds. 2078 - 2079 - // Set a focus target on the calling window (used by overlay windows to specify 2080 - // which window should be focused after the overlay closes and restores hidden windows) 2081 - ipcMain.handle('window-set-overlay-focus-target', async (ev, msg) => { 2082 - try { 2083 - const callerWin = BrowserWindow.fromWebContents(ev.sender); 2084 - if (!callerWin) { 2085 - return { success: false, error: 'Caller window not found' }; 2086 - } 2087 - const winInfo = getWindowInfo(callerWin.id); 2088 - if (!winInfo) { 2089 - return { success: false, error: 'Caller window not in window manager' }; 2090 - } 2091 - winInfo.params.overlayFocusTarget = msg.targetWindowId; 2092 - // Also update the coordinator session (single source of truth) 2093 - const coordinator = getIzuiCoordinator(); 2094 - const session = coordinator.getSession(); 2095 - if (session) { 2096 - session.overlayFocusTarget = msg.targetWindowId; 2097 - } 2098 - DEBUG && console.log('window-set-overlay-focus-target:', msg.targetWindowId, 'on overlay:', callerWin.id); 2099 - return { success: true }; 2100 - } catch (error) { 2101 - console.error('Failed to set overlay focus target:', error); 2102 - const message = error instanceof Error ? error.message : String(error); 2103 - return { success: false, error: message }; 2104 - } 2105 - }); 2106 - 2107 - ipcMain.handle('window-close', async (_ev, msg) => { 2108 - try { 2109 - if (!msg.id) { 2110 - return { success: false, error: 'Window ID is required' }; 2111 - } 2112 - 2113 - const win = BrowserWindow.fromId(msg.id); 2114 - if (!win) { 2115 - return { success: false, error: 'Window not found' }; 2116 - } 2117 - 2118 - win.close(); 2119 - return { success: true }; 2120 - } catch (error) { 2121 - console.error('[ipc:window-close] Failed to close window:', error); 2122 - const message = error instanceof Error ? error.message : String(error); 2123 - return { success: false, error: message }; 2124 - } 2125 - }); 2126 - 2127 - ipcMain.handle('window-hide', async (_ev, msg) => { 2128 - DEBUG && console.log('window-hide', msg); 2129 - 2130 - try { 2131 - if (!msg.id) { 2132 - return { success: false, error: 'Window ID is required' }; 2133 - } 2134 - 2135 - const winData = getWindowInfo(msg.id); 2136 - if (!winData) { 2137 - return { success: false, error: 'Window not found in window manager' }; 2138 - } 2139 - 2140 - const win = BrowserWindow.fromId(msg.id); 2141 - if (!win) { 2142 - removeWindow(msg.id); 2143 - return { success: false, error: 'Window not found' }; 2144 - } 2145 - 2146 - win.hide(); 2147 - return { success: true }; 2148 - } catch (error) { 2149 - console.error('Failed to hide window:', error); 2150 - const message = error instanceof Error ? error.message : String(error); 2151 - return { success: false, error: message }; 2152 - } 2153 - }); 2154 - 2155 - ipcMain.handle('window-show', async (_ev, msg) => { 2156 - DEBUG && console.log('window-show', msg); 2157 - 2158 - try { 2159 - if (!msg.id) { 2160 - return { success: false, error: 'Window ID is required' }; 2161 - } 2162 - 2163 - const winData = getWindowInfo(msg.id); 2164 - if (!winData) { 2165 - return { success: false, error: 'Window not found in window manager' }; 2166 - } 2167 - 2168 - const win = BrowserWindow.fromId(msg.id); 2169 - if (!win) { 2170 - removeWindow(msg.id); 2171 - return { success: false, error: 'Window not found' }; 2172 - } 2173 - 2174 - (win as { __lastShowTime?: number }).__lastShowTime = Date.now(); 2175 - win.show(); 2176 - return { success: true }; 2177 - } catch (error) { 2178 - console.error('Failed to show window:', error); 2179 - const message = error instanceof Error ? error.message : String(error); 2180 - return { success: false, error: message }; 2181 - } 2182 - }); 2183 - 2184 - // Legacy `window-move` removed (v1 removal Phase 3.7b). Tile-preload's 2185 - // click-and-hold drag now uses `tile:window:set-bounds` (token-scoped, 2186 - // targets the sender's own window). 2187 - 2188 - ipcMain.handle('window-center', async (ev, msg) => { 2189 - DEBUG && console.log('window-center', msg); 2190 - 2191 - try { 2192 - let win: BrowserWindow | null = null; 2193 - if (msg?.id) { 2194 - win = BrowserWindow.fromId(msg.id); 2195 - } else { 2196 - const wc = ev.sender; 2197 - win = BrowserWindow.fromWebContents(wc); 2198 - } 2199 - 2200 - if (!win || win.isDestroyed()) { 2201 - return { success: false, error: 'Window not found' }; 2202 - } 2203 - 2204 - const bounds = win.getBounds(); 2205 - const display = screen.getDisplayNearestPoint({ 2206 - x: bounds.x + Math.round(bounds.width / 2), 2207 - y: bounds.y + Math.round(bounds.height / 2) 2208 - }); 2209 - const wa = display.workArea; 2210 - const x = wa.x + Math.round((wa.width - bounds.width) / 2); 2211 - const y = wa.y + Math.round((wa.height - bounds.height) / 2); 2212 - win.setBounds({ x, y, width: bounds.width, height: bounds.height }); 2213 - 2214 - return { success: true }; 2215 - } catch (error) { 2216 - console.error('Failed to center window:', error); 2217 - const message = error instanceof Error ? error.message : String(error); 2218 - return { success: false, error: message }; 2219 - } 2220 - }); 2221 - 2222 - ipcMain.handle('window-center-all', async () => { 2223 - DEBUG && console.log('window-center-all'); 2224 - 2225 - try { 2226 - const windows = BrowserWindow.getAllWindows(); 2227 - let centered = 0; 2228 - 2229 - for (const win of windows) { 2230 - if (win.isDestroyed() || !win.isVisible()) continue; 2231 - 2232 - const info = getWindowInfo(win.id); 2233 - if (info?.params?.address === WEB_CORE_ADDRESS) continue; 2234 - 2235 - const bounds = win.getBounds(); 2236 - const display = screen.getDisplayNearestPoint({ 2237 - x: bounds.x + Math.round(bounds.width / 2), 2238 - y: bounds.y + Math.round(bounds.height / 2) 2239 - }); 2240 - const wa = display.workArea; 2241 - const x = wa.x + Math.round((wa.width - bounds.width) / 2); 2242 - const y = wa.y + Math.round((wa.height - bounds.height) / 2); 2243 - win.setBounds({ x, y, width: bounds.width, height: bounds.height }); 2244 - centered++; 2245 - } 2246 - 2247 - return { success: true, centered }; 2248 - } catch (error) { 2249 - console.error('Failed to center all windows:', error); 2250 - const message = error instanceof Error ? error.message : String(error); 2251 - return { success: false, error: message }; 2252 - } 2253 - }); 2254 - 2255 - ipcMain.handle('window-maximize', async (ev, msg) => { 2256 - DEBUG && console.log('window-maximize', msg); 2257 - 2258 - try { 2259 - let win: BrowserWindow | null = null; 2260 - if (msg?.id) { 2261 - win = BrowserWindow.fromId(msg.id); 2262 - } else { 2263 - const wc = ev.sender; 2264 - win = BrowserWindow.fromWebContents(wc); 2265 - } 2266 - 2267 - if (!win || win.isDestroyed()) { 2268 - return { success: false, error: 'Window not found' }; 2269 - } 2270 - 2271 - if (win.isMaximized()) { 2272 - win.unmaximize(); 2273 - } else { 2274 - win.maximize(); 2275 - } 2276 - 2277 - return { success: true }; 2278 - } catch (error) { 2279 - console.error('Failed to maximize window:', error); 2280 - const message = error instanceof Error ? error.message : String(error); 2281 - return { success: false, error: message }; 2282 - } 2283 - }); 2284 - 2285 - ipcMain.handle('window-fullscreen', async (ev, msg) => { 2286 - DEBUG && console.log('window-fullscreen', msg); 2287 - 2288 - try { 2289 - let win: BrowserWindow | null = null; 2290 - if (msg?.id) { 2291 - win = BrowserWindow.fromId(msg.id); 2292 - } else { 2293 - const wc = ev.sender; 2294 - win = BrowserWindow.fromWebContents(wc); 2295 - } 2296 - 2297 - if (!win || win.isDestroyed()) { 2298 - return { success: false, error: 'Window not found' }; 2299 - } 2300 - 2301 - win.setFullScreen(!win.isFullScreen()); 2302 - 2303 - return { success: true }; 2304 - } catch (error) { 2305 - console.error('Failed to toggle fullscreen:', error); 2306 - const message = error instanceof Error ? error.message : String(error); 2307 - return { success: false, error: message }; 2308 - } 2309 - }); 2310 - 2311 - // Legacy `window-resize` removed (v1 removal Phase 3.7b). Tile-preload's 2312 - // api.window.resize uses `tile:window:resize`. 2313 - 2314 - ipcMain.handle('window-focus', async (_ev, msg) => { 2315 - DEBUG && console.log('window-focus', msg); 2316 - 2317 - try { 2318 - if (!msg.id) { 2319 - return { success: false, error: 'Window ID is required' }; 2320 - } 2321 - 2322 - const winData = getWindowInfo(msg.id); 2323 - if (!winData) { 2324 - return { success: false, error: 'Window not found in window manager' }; 2325 - } 2326 - 2327 - const win = BrowserWindow.fromId(msg.id); 2328 - if (!win) { 2329 - removeWindow(msg.id); 2330 - return { success: false, error: 'Window not found' }; 2331 - } 2332 - 2333 - if (!isHeadless()) { 2334 - win.focus(); 2335 - } 2336 - return { success: true }; 2337 - } catch (error) { 2338 - console.error('Failed to focus window:', error); 2339 - const message = error instanceof Error ? error.message : String(error); 2340 - return { success: false, error: message }; 2341 - } 2342 - }); 2343 - 2344 - // window-devtools removed (v1 removal Phase 3.7g). Strict 2345 - // tile:window:devtools handler lives in tile-ipc.ts; cmd panel now 2346 - // calls api.window.devtools(). 2347 - 2348 - // Legacy `window-blur`, `get-display-info`, `window-set-bounds` removed 2349 - // (v1 removal Phase 3.7b). All previously called from preload.js (now 2350 - // deleted); tile-preload uses tile:window:get-display-info / set-bounds / 2351 - // (no blur — was never wrapped in tile-preload). 2352 - 2353 - ipcMain.handle('window-exists', async (_ev, msg) => { 2354 - DEBUG && console.log('window-exists', msg); 2355 - 2356 - try { 2357 - if (!msg.id) { 2358 - return { exists: false, error: 'Window ID is required' }; 2359 - } 2360 - 2361 - const winData = getWindowInfo(msg.id); 2362 - if (!winData) { 2363 - return { exists: false }; 2364 - } 2365 - 2366 - const win = BrowserWindow.fromId(msg.id); 2367 - if (!win || win.isDestroyed()) { 2368 - removeWindow(msg.id); 2369 - return { exists: false }; 2370 - } 2371 - 2372 - return { exists: true }; 2373 - } catch (error) { 2374 - console.error('Failed to check if window exists:', error); 2375 - const message = error instanceof Error ? error.message : String(error); 2376 - return { exists: false, error: message }; 2377 - } 2378 - }); 2379 - 2380 - ipcMain.handle('window-list', async (_ev, msg) => { 2381 - DEBUG && console.log('window-list', msg); 2382 - 2383 - try { 2384 - const windows: Array<{ 2385 - id: number; 2386 - url: string; 2387 - title: string; 2388 - source: string; 2389 - visible: boolean; 2390 - params: Record<string, unknown>; 2391 - }> = []; 2392 - 2393 - for (const [id, winData] of getAllWindows()) { 2394 - const win = BrowserWindow.fromId(id); 2395 - if (win && !win.isDestroyed()) { 2396 - let url = win.webContents.getURL(); 2397 - 2398 - // Check if this is a web page container (peek://app/page) 2399 - // These wrap actual web pages - extract the real URL 2400 - if (url.startsWith('peek://app/page')) { 2401 - try { 2402 - const parsed = new URL(url); 2403 - const actualUrl = parsed.searchParams.get('url'); 2404 - if (actualUrl) { 2405 - url = actualUrl; 2406 - } 2407 - } catch { 2408 - // If URL parsing fails, keep the original 2409 - } 2410 - } 2411 - 2412 - // Skip internal peek:// URLs unless requested 2413 - // (Page containers have had their actual URL extracted above, so web pages pass through) 2414 - if (!msg?.includeInternal && url.startsWith('peek://')) { 2415 - continue; 2416 - } 2417 - 2418 - // Sanitize params to only include serializable values 2419 - // (avoid functions, native objects, circular refs that crash IPC) 2420 - const safeParams: Record<string, unknown> = {}; 2421 - for (const [key, value] of Object.entries(winData.params)) { 2422 - const type = typeof value; 2423 - if (type === 'string' || type === 'number' || type === 'boolean' || value === null) { 2424 - safeParams[key] = value; 2425 - } else if (Array.isArray(value)) { 2426 - // Only include arrays of primitives 2427 - if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null)) { 2428 - safeParams[key] = value; 2429 - } 2430 - } 2431 - // Skip functions, objects, etc. that may not serialize safely 2432 - } 2433 - 2434 - windows.push({ 2435 - id, 2436 - url, 2437 - title: win.getTitle(), 2438 - source: winData.source, 2439 - visible: win.isVisible(), 2440 - params: safeParams 2441 - }); 2442 - } 2443 - } 2444 - 2445 - return { success: true, windows }; 2446 - } catch (error) { 2447 - console.error('Failed to list windows:', error); 2448 - const message = error instanceof Error ? error.message : String(error); 2449 - return { success: false, error: message, windows: [] }; 2450 - } 2451 - }); 2070 + // Legacy `window-*` handlers removed (v1 removal Phase 3.7h). All 2071 + // tile-preload methods route through strict `tile:window:*` channels 2072 + // gated on the tile's `window` capability shape (or trustedBuiltin). 2073 + // Removed: window-open, window-close, window-hide, window-show, 2074 + // window-focus, window-exists, window-list, window-center, 2075 + // window-center-all, window-maximize, window-fullscreen, 2076 + // window-set-ignore-mouse-events, window-set-overlay-focus-target. 2077 + // Earlier removals (Phase 3.7b/g): window-blur, window-resize, 2078 + // window-move, window-get-bounds, window-set-bounds, window-get-position, 2079 + // get-display-info, window-devtools, get-focused-visible-window-id. 2452 2080 2453 2081 // pubsub-stats removed (v1 removal Phase 3.7g). Strict 2454 2082 // tile:pubsub:stats handler lives in tile-ipc.ts (trustedBuiltin only).
+82 -172
backend/electron/tile-preload.cts
··· 537 537 538 538 // ── Window ──────────────────────────────────────────────────────── 539 539 // 540 - // Dual-path implementation mirroring api.shortcuts / api.context: 541 - // 542 - // - STRICT: when the tile's manifest declared a `window` capability 543 - // (`true` or `{ create?, query?, manage?, urls? }`), route through 544 - // `tile:window:*`. The main-process handlers validate the token + 545 - // capability shape + per-op gates + URL allowlist. 546 - // 547 - // - V1-COMPAT: when no window capability was declared, fall back to 548 - // the legacy `window-open` / `window-close` / `window-list` IPC 549 - // channels. Those channels remain available for the Phase 3/4 550 - // migration per the tile-preload trimming plan. 551 - // 552 - // The decision is made per-call because `grantedCapabilities` is 553 - // populated asynchronously by `initialize()`. 554 - 555 - function hasWindowCapability(): boolean { 556 - const wc = grantedCapabilities?.window; 557 - if (wc === true) return true; 558 - if (wc && typeof wc === 'object') return true; 559 - return false; 560 - } 540 + // Strict-only: every call routes through `tile:window:*` and is 541 + // gated on the tile's `window` capability shape (or trustedBuiltin). 542 + // Legacy `window-*` IPC handlers were removed in v1-removal Phase 543 + // 3.7h. Tiles that call api.window.* must declare a `window` 544 + // capability in their manifest. `showSelf`/`hideSelf` and `resize` 545 + // (caller's own window only) remain ungated since the token itself 546 + // is the authority. 561 547 562 548 api.window = { 563 549 /** 564 - * Open a new window. Routes through strict IPC when the tile has 565 - * declared a `window` capability; otherwise falls back to the 566 - * un-gated legacy `window-open` channel. 550 + * Open a new window. Requires `window.create` (or trustedBuiltin). 551 + * Strict handler in tile-ipc.ts validates URL prefix allowlist. 567 552 */ 568 553 open: async (url: string, options?: Record<string, unknown>) => { 569 554 const _valid = await validationPromise; if (!_valid) return Promise.reject(new Error('Not initialized')); 570 - if (hasWindowCapability()) { 571 - return ipcRenderer.invoke('tile:window:open', { 572 - token: tileToken, 573 - url, 574 - options, 575 - }); 576 - } 577 - return ipcRenderer.invoke('window-open', { 578 - source: sourceAddress, 555 + return ipcRenderer.invoke('tile:window:open', { 556 + token: tileToken, 579 557 url, 580 - options: options || {}, 558 + options, 581 559 }); 582 560 }, 583 561 ··· 598 576 599 577 /** 600 578 * Close a window. With no id, closes the caller's own tile window. 601 - * With an id, closes the target window (requires `window.manage` 602 - * under strict mode). Falls back to legacy `window-close` when no 603 - * window capability is declared. 579 + * With an id, closes the target window (requires `window.manage`). 580 + * Honors keepLive — routes through `closeOrHideWindow` in main. 604 581 */ 605 582 close: (id?: number) => { 606 - if (hasWindowCapability()) { 607 - ipcRenderer.send('tile:window:close', { 608 - token: tileToken, 609 - id, 610 - }); 611 - return; 612 - } 613 - if (id == null) { 614 - // No strict grant, no id — best-effort: try legacy close on the 615 - // caller's own window via window-close is unsafe without an id, 616 - // so drop the call silently (matches previous behavior). 617 - return; 618 - } 619 - ipcRenderer.invoke('window-close', { id }); 583 + ipcRenderer.send('tile:window:close', { 584 + token: tileToken, 585 + id, 586 + }); 620 587 }, 621 588 622 589 /** 623 - * Get info about the current window. Routes through strict IPC when 624 - * the tile has declared the capability; otherwise falls back to a 625 - * best-effort minimal object (v1 tiles did not have this API). 590 + * Get info about the current window. Requires the `window` 591 + * capability (or trustedBuiltin). 626 592 */ 627 593 getInfo: () => { 628 - if (hasWindowCapability()) { 629 - return ipcRenderer.invoke('tile:window:info', { 630 - token: tileToken, 631 - }); 632 - } 633 - // No legacy equivalent — return an error consistent with the 634 - // strict path's shape. 635 - return Promise.resolve({ error: 'window capability not granted' }); 594 + return ipcRenderer.invoke('tile:window:info', { 595 + token: tileToken, 596 + }); 636 597 }, 637 598 638 599 /** 639 - * List open windows. Strict-only — requires `window.query`. In 640 - * v1-compat mode this falls back to `window-list` for parity. 600 + * List open windows. Requires `window.query` (or trustedBuiltin). 641 601 */ 642 602 list: (opts?: { includeInternal?: boolean }) => { 643 - if (hasWindowCapability()) { 644 - return ipcRenderer.invoke('tile:window:list', { 645 - token: tileToken, 646 - includeInternal: opts?.includeInternal, 647 - }); 648 - } 649 - return ipcRenderer.invoke('window-list', {}); 603 + return ipcRenderer.invoke('tile:window:list', { 604 + token: tileToken, 605 + includeInternal: opts?.includeInternal, 606 + }); 650 607 }, 651 608 652 609 /** 653 - * Check if a window with the given id is still open. Strict path 654 - * requires `window.manage`; otherwise falls back to the un-gated 655 - * legacy `window-exists` channel. 610 + * Check if a window with the given id is still open. Requires 611 + * `window.manage` (or trustedBuiltin). 656 612 */ 657 613 exists: (id: number) => { 658 - if (hasWindowCapability()) { 659 - return ipcRenderer.invoke('tile:window:exists', { 660 - token: tileToken, 661 - id, 662 - }); 663 - } 664 - return ipcRenderer.invoke('window-exists', { id }); 614 + return ipcRenderer.invoke('tile:window:exists', { 615 + token: tileToken, 616 + id, 617 + }); 665 618 }, 666 619 667 620 /** 668 - * Show a hidden window. Strict path requires `window.manage`; 669 - * otherwise falls back to the un-gated legacy `window-show` 670 - * channel. 621 + * Show a hidden window. Requires `window.manage` (or trustedBuiltin). 671 622 */ 672 623 show: (id: number) => { 673 - if (hasWindowCapability()) { 674 - return ipcRenderer.invoke('tile:window:show', { 675 - token: tileToken, 676 - id, 677 - }); 678 - } 679 - return ipcRenderer.invoke('window-show', { id }); 624 + return ipcRenderer.invoke('tile:window:show', { 625 + token: tileToken, 626 + id, 627 + }); 680 628 }, 681 629 682 630 /** 683 - * Hide a visible window. Strict path requires `window.manage`; 684 - * otherwise falls back to the un-gated legacy `window-hide` 685 - * channel. 631 + * Hide a visible window. Requires `window.manage` (or trustedBuiltin). 686 632 */ 687 633 hide: (id: number) => { 688 - if (hasWindowCapability()) { 689 - return ipcRenderer.invoke('tile:window:hide', { 690 - token: tileToken, 691 - id, 692 - }); 693 - } 694 - return ipcRenderer.invoke('window-hide', { id }); 634 + return ipcRenderer.invoke('tile:window:hide', { 635 + token: tileToken, 636 + id, 637 + }); 695 638 }, 696 639 697 640 /** 698 - * Raise a window to the front. Strict path requires `window.manage`; 699 - * otherwise falls back to the un-gated legacy `window-focus` channel. 641 + * Raise a window to the front. Requires `window.manage` (or 642 + * trustedBuiltin). 700 643 */ 701 644 focus: (id: number) => { 702 - if (hasWindowCapability()) { 703 - return ipcRenderer.invoke('tile:window:focus', { 704 - token: tileToken, 705 - id, 706 - }); 707 - } 708 - return ipcRenderer.invoke('window-focus', { id }); 645 + return ipcRenderer.invoke('tile:window:focus', { 646 + token: tileToken, 647 + id, 648 + }); 709 649 }, 710 650 711 651 /** 712 - * Toggle click-through on a window. Strict path requires 713 - * `window.manage`; otherwise falls back to the un-gated legacy 714 - * `window-set-ignore-mouse-events` channel. 652 + * Toggle click-through on a window. Requires `window.manage` (or 653 + * trustedBuiltin). 715 654 */ 716 655 setIgnoreMouseEvents: ( 717 656 id: number | null | undefined, 718 657 ignore: boolean, 719 658 options?: { forward?: boolean }, 720 659 ) => { 721 - if (hasWindowCapability()) { 722 - return ipcRenderer.invoke('tile:window:set-ignore-mouse', { 723 - token: tileToken, 724 - id: typeof id === 'number' ? id : undefined, 725 - ignore, 726 - options, 727 - }); 728 - } 729 - return ipcRenderer.invoke('window-set-ignore-mouse-events', { 660 + return ipcRenderer.invoke('tile:window:set-ignore-mouse', { 661 + token: tileToken, 730 662 id: typeof id === 'number' ? id : undefined, 731 663 ignore, 732 - forward: options?.forward === true, 664 + options, 733 665 }); 734 666 }, 735 667 736 668 /** 737 - * Center a window on its display. Strict path requires 738 - * `window.manage`; otherwise falls back to legacy `window-center`. 669 + * Center a window on its display. Requires `window.manage` (or 670 + * trustedBuiltin). 739 671 */ 740 672 center: (id?: number) => { 741 - if (hasWindowCapability()) { 742 - return ipcRenderer.invoke('tile:window:center', { 743 - token: tileToken, 744 - id, 745 - }); 746 - } 747 - return ipcRenderer.invoke('window-center', { id }); 673 + return ipcRenderer.invoke('tile:window:center', { 674 + token: tileToken, 675 + id, 676 + }); 748 677 }, 749 678 750 679 /** 751 - * Center every visible window on its nearest display. Strict path 752 - * requires `window.manage`; otherwise falls back to legacy 753 - * `window-center-all`. 680 + * Center every visible window on its nearest display. Requires 681 + * `window.manage` (or trustedBuiltin). 754 682 */ 755 683 centerAll: () => { 756 - if (hasWindowCapability()) { 757 - return ipcRenderer.invoke('tile:window:center-all', { 758 - token: tileToken, 759 - }); 760 - } 761 - return ipcRenderer.invoke('window-center-all'); 684 + return ipcRenderer.invoke('tile:window:center-all', { 685 + token: tileToken, 686 + }); 762 687 }, 763 688 764 689 /** 765 - * Toggle maximize on a window. Strict path requires `window.manage`; 766 - * otherwise falls back to legacy `window-maximize`. 690 + * Toggle maximize on a window. Requires `window.manage` (or 691 + * trustedBuiltin). 767 692 */ 768 693 maximize: (id?: number) => { 769 - if (hasWindowCapability()) { 770 - return ipcRenderer.invoke('tile:window:maximize', { 771 - token: tileToken, 772 - id, 773 - }); 774 - } 775 - return ipcRenderer.invoke('window-maximize', { id }); 694 + return ipcRenderer.invoke('tile:window:maximize', { 695 + token: tileToken, 696 + id, 697 + }); 776 698 }, 777 699 778 700 /** 779 - * Toggle fullscreen on a window. Strict path requires `window.manage`; 780 - * otherwise falls back to legacy `window-fullscreen`. 701 + * Toggle fullscreen on a window. Requires `window.manage` (or 702 + * trustedBuiltin). 781 703 */ 782 704 fullscreen: (id?: number) => { 783 - if (hasWindowCapability()) { 784 - return ipcRenderer.invoke('tile:window:fullscreen', { 785 - token: tileToken, 786 - id, 787 - }); 788 - } 789 - return ipcRenderer.invoke('window-fullscreen', { id }); 705 + return ipcRenderer.invoke('tile:window:fullscreen', { 706 + token: tileToken, 707 + id, 708 + }); 790 709 }, 791 710 792 711 /** ··· 819 738 820 739 /** 821 740 * Set the overlay focus target — captures which window should be 822 - * focused after an overlay closes. Strict path requires 823 - * `window.manage`; otherwise falls back to legacy 824 - * `window-set-overlay-focus-target`. 741 + * focused after an overlay closes. Requires `window.manage` (or 742 + * trustedBuiltin). 825 743 */ 826 744 setOverlayFocusTarget: (targetWindowId: number) => { 827 - if (hasWindowCapability()) { 828 - return ipcRenderer.invoke('tile:window:set-overlay-focus-target', { 829 - token: tileToken, 830 - targetWindowId, 831 - }); 832 - } 833 - return ipcRenderer.invoke('window-set-overlay-focus-target', { 745 + return ipcRenderer.invoke('tile:window:set-overlay-focus-target', { 746 + token: tileToken, 834 747 targetWindowId, 835 748 }); 836 749 }, ··· 843 756 visible: boolean, 844 757 options?: { visibleOnFullScreen?: boolean; skipTransformProcessType?: boolean }, 845 758 ) => { 846 - if (!hasWindowCapability()) { 847 - return Promise.reject(new Error('[tile-preload] setVisibleOnAllWorkspaces requires the window capability')); 848 - } 849 759 return ipcRenderer.invoke('tile:window:set-visible-on-all-workspaces', { 850 760 token: tileToken, 851 761 id: typeof id === 'number' ? id : undefined,
+4 -1
features/widget-demo/manifest.json
··· 27 27 ] 28 28 }, 29 29 "commands": true, 30 - "settings": true 30 + "settings": true, 31 + "window": { 32 + "create": true 33 + } 31 34 }, 32 35 "commands": [ 33 36 {