experiments in a post-browser web
10
fork

Configure Feed

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

fix(tile-preload): add getBounds / getDisplayInfo / setBounds + unwrap getWindowId result

Three more tile-v2-migration gaps in the api.window surface, same
class of bug as the earlier getWindowId / api.ipc fixes:

1. api.window.getBounds() was called by page.js for maximize pre-save
but never added to tile-preload.cts. Wrapped the existing legacy
window-get-bounds IPC under a new tile:window:get-bounds channel
that is token-gated (no cap — a tile always knows its own bounds).

2. api.window.getDisplayInfo() and api.window.setBounds() similarly
missing. Added both following the same pattern.

3. api.window.getWindowId returns { success, id } but page.js was
storing the whole result object into myWindowId and comparing it
against a plain number in pubsub handlers (msg.windowId !== myWindowId).
The object-vs-number compare is always true, so every page:*
event with a windowId filter was being silently skipped. Unwrapped
the id at assignment time.

Fixes all 14 page-layout tests + all 4 page-navbar tests (the
latter were failing because getWindowId comparison blocked
page:show-navbar handlers from reaching show()).

+115 -2
+7 -2
app/page/page.js
··· 39 39 40 40 // Cache our window ID for filtering GLOBAL pubsub messages 41 41 let myWindowId = null; 42 - api.window.getWindowId().then(id => { myWindowId = id; }).catch(err => { 42 + api.window.getWindowId().then(result => { 43 + // tile:window:get-id returns { success, id } — unwrap the numeric id 44 + myWindowId = (result && typeof result === 'object' && 'id' in result) ? result.id : result; 45 + }).catch(err => { 43 46 console.error('[page] Failed to get window ID, retrying:', err); 44 47 setTimeout(() => { 45 - api.window.getWindowId().then(id => { myWindowId = id; }).catch(err2 => { 48 + api.window.getWindowId().then(result => { 49 + myWindowId = (result && typeof result === 'object' && 'id' in result) ? result.id : result; 50 + }).catch(err2 => { 46 51 console.error('[page] Failed to get window ID on retry:', err2); 47 52 }); 48 53 }, 1000);
+74
backend/electron/tile-ipc.ts
··· 3206 3206 return win ? { success: true, id: win.id } : { success: false, error: 'Window not found' }; 3207 3207 }); 3208 3208 3209 + // ── tile:window:get-bounds ──────────────────────────────────────── 3210 + // 3211 + // Return the full bounds (x, y, width, height) for the calling renderer's 3212 + // window. No `window` capability required — a tile always has an inherent 3213 + // right to know its own bounds. Token validity is still enforced so 3214 + // un-initialised callers cannot probe bounds. 3215 + ipcMain.handle('tile:window:get-bounds', (_event, args: { 3216 + token: string; 3217 + }) => { 3218 + const grant = getGrantForToken(args.token); 3219 + if (!grant) return { success: false, error: 'Invalid token' }; 3220 + 3221 + const win = BrowserWindow.fromWebContents(_event.sender); 3222 + if (!win || win.isDestroyed()) { 3223 + return { success: false, error: 'Window not found' }; 3224 + } 3225 + const bounds = win.getBounds(); 3226 + return { success: true, ...bounds }; 3227 + }); 3228 + 3229 + // ── tile:window:get-display-info ────────────────────────────────── 3230 + // 3231 + // Return display info (workArea, bounds, scaleFactor) for the display 3232 + // that contains the calling window. No `window` capability required — 3233 + // a tile can always query the display it lives on. 3234 + ipcMain.handle('tile:window:get-display-info', (_event, args: { 3235 + token: string; 3236 + }) => { 3237 + const grant = getGrantForToken(args.token); 3238 + if (!grant) return { success: false, error: 'Invalid token' }; 3239 + 3240 + const win = BrowserWindow.fromWebContents(_event.sender); 3241 + if (!win || win.isDestroyed()) { 3242 + return { success: false, error: 'Window not found' }; 3243 + } 3244 + const display = screen.getDisplayMatching(win.getBounds()); 3245 + return { 3246 + success: true, 3247 + workArea: display.workArea, 3248 + bounds: display.bounds, 3249 + scaleFactor: display.scaleFactor, 3250 + data: { workArea: display.workArea, bounds: display.bounds, scaleFactor: display.scaleFactor }, 3251 + }; 3252 + }); 3253 + 3254 + // ── tile:window:set-bounds ──────────────────────────────────────── 3255 + // 3256 + // Set the position and/or size of the calling window. No `window` 3257 + // capability required — a tile can always reposition its own window. 3258 + // (Cannot target other windows through this channel.) 3259 + ipcMain.handle('tile:window:set-bounds', (_event, args: { 3260 + token: string; 3261 + x?: number; 3262 + y?: number; 3263 + width?: number; 3264 + height?: number; 3265 + }) => { 3266 + const grant = getGrantForToken(args.token); 3267 + if (!grant) return { success: false, error: 'Invalid token' }; 3268 + 3269 + const win = BrowserWindow.fromWebContents(_event.sender); 3270 + if (!win || win.isDestroyed()) { 3271 + return { success: false, error: 'Window not found' }; 3272 + } 3273 + const current = win.getBounds(); 3274 + win.setBounds({ 3275 + x: args.x ?? current.x, 3276 + y: args.y ?? current.y, 3277 + width: args.width ?? current.width, 3278 + height: args.height ?? current.height, 3279 + }); 3280 + return { success: true }; 3281 + }); 3282 + 3209 3283 // ── tile:window:resize ──────────────────────────────────────────── 3210 3284 // 3211 3285 // Resize the caller's own tile window. Used by tiles that expand/collapse
+34
backend/electron/tile-preload.cts
··· 870 870 if (!tokenValid) return Promise.reject(new Error('Not initialized')); 871 871 return ipcRenderer.invoke('tile:window:get-id', { token: tileToken }); 872 872 }, 873 + 874 + /** 875 + * Get the full bounds (x, y, width, height) for the calling window. 876 + * 877 + * Routes strictly through `tile:window:get-bounds`. No `window` 878 + * capability required — a tile always knows its own bounds. 879 + */ 880 + getBounds: () => { 881 + if (!tokenValid) return Promise.reject(new Error('Not initialized')); 882 + return ipcRenderer.invoke('tile:window:get-bounds', { token: tileToken }); 883 + }, 884 + 885 + /** 886 + * Get display info (workArea, bounds, scaleFactor) for the display 887 + * containing the calling window. 888 + * 889 + * Routes strictly through `tile:window:get-display-info`. No `window` 890 + * capability required — a tile can always query its own display. 891 + */ 892 + getDisplayInfo: () => { 893 + if (!tokenValid) return Promise.reject(new Error('Not initialized')); 894 + return ipcRenderer.invoke('tile:window:get-display-info', { token: tileToken }); 895 + }, 896 + 897 + /** 898 + * Set the position and/or size of the calling window. 899 + * 900 + * Routes strictly through `tile:window:set-bounds`. No `window` 901 + * capability required — a tile can always reposition its own window. 902 + */ 903 + setBounds: (bounds: { x?: number; y?: number; width?: number; height?: number }) => { 904 + if (!tokenValid) return Promise.reject(new Error('Not initialized')); 905 + return ipcRenderer.invoke('tile:window:set-bounds', { token: tileToken, ...bounds }); 906 + }, 873 907 }; 874 908 875 909 // ── Datastore (if granted) ──