···11351135 DEBUG && console.log(`[window-open] cmd-ui window (${cmdUiEntry}): assigned trustedBuiltin tile-preload token`);
11361136 }
1137113711381138+ // Special-case: about:blank is opened by IZUI tests (and potentially other
11391139+ // callers) as an empty child window. Under v1 preload.js, all windows
11401140+ // received a preload unconditionally. Now that preload.js is deleted,
11411141+ // about:blank gets nothing — window.app is undefined in the renderer.
11421142+ // Mint a trustedBuiltin token so tile-preload is injected and window.app
11431143+ // is available (matches the same pattern as diagnostic, settings, hud-overlay, cmd-ui).
11441144+ if (!tileWebPrefs && url === 'about:blank') {
11451145+ const BLANK_ID = 'about-blank';
11461146+ const BLANK_ENTRY = 'main';
11471147+ const blankGrant = createTrustedBuiltinGrant(BLANK_ID);
11481148+ const blankToken = generateToken(BLANK_ID, BLANK_ENTRY, blankGrant);
11491149+ tileWebPrefs = {
11501150+ preload: getTilePreloadPath(),
11511151+ additionalArguments: [
11521152+ `--tile-id=${BLANK_ID}`,
11531153+ `--tile-entry=${BLANK_ENTRY}`,
11541154+ `--tile-token=${blankToken}`,
11551155+ ],
11561156+ tileId: BLANK_ID,
11571157+ entryId: BLANK_ENTRY,
11581158+ };
11591159+ DEBUG && console.log('[window-open] about:blank window: assigned trustedBuiltin tile-preload token');
11601160+ }
11611161+11381162 // Canvas page host windows use tile-preload with a trustedBuiltin grant so
11391163 // page.js can use api.window.*, api.datastore.*, api.context.*, api.profiles.*,
11401164 // api.chromeExtensions.*, api.app.*, api.subscribe/publish, etc. The token is
···12121236 // they're just the IPC sender. Only content windows count as parents for
12131237 // IZUI child-window semantics (ESC closes child, focuses parent).
12141238 const openerWindow = BrowserWindow.fromWebContents(ev.sender);
12391239+ // Internal/infrastructure windows are not real parents for IZUI child-window semantics.
12401240+ // The test-fixture window (peek://test/) is also internal — it acts as bgWindow in tests.
12151241 const INTERNAL_URLS = ['peek://app/background.html'];
12421242+ const INTERNAL_URL_PREFIXES = ['peek://test/'];
12161243 const openerUrl = openerWindow && !openerWindow.isDestroyed() ? openerWindow.webContents?.getURL() ?? '' : '';
12171217- const isRealParent = openerWindow && !openerWindow.isDestroyed() && !INTERNAL_URLS.some(u => openerUrl === u);
12441244+ const isRealParent = openerWindow && !openerWindow.isDestroyed() &&
12451245+ !INTERNAL_URLS.some(u => openerUrl === u) &&
12461246+ !INTERNAL_URL_PREFIXES.some(p => openerUrl.startsWith(p));
1218124712191248 // Center window on parent if opener exists and no explicit position
12201249 // Priority: explicit x/y > center on parent > center on screen
+8
backend/electron/tile-ipc.ts
···9292 getRunningExtensions,
9393 getAllRegisteredExtensions,
9494 reloadExtension,
9595+ getWindowInfo,
9596} from './main.js';
9697import {
9798 registerGlobalShortcut,
···2575257625762577 ipcMain.handle('tile:window:list', async (_event, args: {
25772578 token: string;
25792579+ includeInternal?: boolean;
25782580 }) => {
25792581 const grant = getGrantForToken(args.token);
25802582 const check = checkWindowAllowed(grant, 'list');
···25902592 title: string;
25912593 focused: boolean;
25922594 visible: boolean;
25952595+ params: Record<string, unknown>;
25932596 }> = [];
25942597 for (const win of BrowserWindow.getAllWindows()) {
25952598 if (win.isDestroyed()) continue;
25992599+ const info = getWindowInfo(win.id);
26002600+ // When includeInternal is false/absent, skip windows without registry
26012601+ // entries (e.g. purely internal Electron windows). When true, include all.
26022602+ if (!args.includeInternal && !info) continue;
25962603 windows.push({
25972604 id: win.id,
25982605 url: win.webContents.getURL(),
25992606 title: win.getTitle(),
26002607 focused: win.isFocused(),
26012608 visible: win.isVisible(),
26092609+ params: info ? info.params : {},
26022610 });
26032611 }
26042612 return { success: true, windows };
+2-1
backend/electron/tile-preload.cts
···565565 * List open windows. Strict-only — requires `window.query`. In
566566 * v1-compat mode this falls back to `window-list` for parity.
567567 */
568568- list: () => {
568568+ list: (opts?: { includeInternal?: boolean }) => {
569569 if (hasWindowCapability()) {
570570 return ipcRenderer.invoke('tile:window:list', {
571571 token: tileToken,
572572+ includeInternal: opts?.includeInternal,
572573 });
573574 }
574575 return ipcRenderer.invoke('window-list', {});