experiments in a post-browser web
10
fork

Configure Feed

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

fix(page): preserve pre-maximize bounds across renderer maximize trip

The directed-IPC switch broke maximize-restore: main set bounds to the
work area before notifying the renderer, so doMaximize captured the
post-resize bounds as preMaximizeWindowBounds and doUnmaximize then
restored to the work area instead of the original size. Main now
threads the pre-maximize bounds through the maximize-request payload
and the renderer adopts those when present. Drag-out-of-maximize and
the toggle path inherit the fix because they read the same field.
Updates page-layout maximize coverage to drive via api.window.maximize
/ unmaximize (the page:maximize pubsub topic was retired in the same
directed-IPC pass).

+52 -54
+24 -13
app/page/page.js
··· 617 617 } 618 618 } 619 619 620 - async function doMaximize() { 620 + async function doMaximize(injectedWindowBounds) { 621 621 try { 622 622 cancelInFlightInteractions(); 623 623 if (inMaximized()) return; ··· 635 635 width: screenBounds.width, 636 636 height: screenBounds.height, 637 637 }; 638 - try { 639 - const windowBoundsResult = await api.window.getBounds(); 640 - if (windowBoundsResult && windowBoundsResult.success !== false) { 641 - preMaximizeWindowBounds = { 642 - x: windowBoundsResult.x, 643 - y: windowBoundsResult.y, 644 - width: windowBoundsResult.width, 645 - height: windowBoundsResult.height, 646 - }; 638 + if (injectedWindowBounds) { 639 + // IPC-initiated path: main captured bounds before resizing the window, 640 + // so we use those instead of querying after the fact. 641 + preMaximizeWindowBounds = { 642 + x: injectedWindowBounds.x, 643 + y: injectedWindowBounds.y, 644 + width: injectedWindowBounds.width, 645 + height: injectedWindowBounds.height, 646 + }; 647 + } else { 648 + try { 649 + const windowBoundsResult = await api.window.getBounds(); 650 + if (windowBoundsResult && windowBoundsResult.success !== false) { 651 + preMaximizeWindowBounds = { 652 + x: windowBoundsResult.x, 653 + y: windowBoundsResult.y, 654 + width: windowBoundsResult.width, 655 + height: windowBoundsResult.height, 656 + }; 657 + } 658 + } catch { 659 + preMaximizeWindowBounds = null; 647 660 } 648 - } catch { 649 - preMaximizeWindowBounds = null; 650 661 } 651 662 652 663 screenBounds.x = workArea.x; ··· 2341 2352 // centerColumn), so the IPC tells *this* window's renderer to run its 2342 2353 // own non-toggling helpers. No pubsub broadcast — the IPC already 2343 2354 // resolved the target window, so it sends to that webContents only. 2344 - api.window.onMaximizeRequest(() => doMaximize()); 2355 + api.window.onMaximizeRequest((payload) => doMaximize(payload && payload.prevWindowBounds)); 2345 2356 api.window.onUnmaximizeRequest(() => doUnmaximize()); 2346 2357 2347 2358 // --- Web permission prompt ---
+14 -2
backend/electron/tile-ipc.ts
··· 3045 3045 // Targeted notification to the window's renderer — page-host (and 3046 3046 // any other renderer that owns internal layout state) subscribes 3047 3047 // via api.window.onMaximizeRequest to keep its FSM/bounds in sync 3048 - // without going through pubsub broadcast. 3049 - try { win.webContents.send('tile:window:maximize-request'); } catch { /* renderer may be gone */ } 3048 + // without going through pubsub broadcast. Pass prevWindowBounds so 3049 + // the renderer captures the pre-maximize size *before* main resized 3050 + // — querying getBounds() from the renderer after we've already 3051 + // changed them would record the work-area as the "previous" size. 3052 + try { 3053 + win.webContents.send('tile:window:maximize-request', { 3054 + prevWindowBounds: atWorkArea ? null : { 3055 + x: currentBounds.x, 3056 + y: currentBounds.y, 3057 + width: currentBounds.width, 3058 + height: currentBounds.height, 3059 + }, 3060 + }); 3061 + } catch { /* renderer may be gone */ } 3050 3062 return { success: true }; 3051 3063 } catch (err) { 3052 3064 const message = err instanceof Error ? err.message : String(err);
+4 -2
backend/electron/tile-preload.cts
··· 714 714 * Targeted send (no broadcast) — only the resolved window receives 715 715 * the event. 716 716 */ 717 - onMaximizeRequest: (cb: () => void) => { 718 - const handler = () => { try { cb(); } catch (err) { console.error('[onMaximizeRequest]', err); } }; 717 + onMaximizeRequest: (cb: (payload?: { prevWindowBounds?: { x: number; y: number; width: number; height: number } | null }) => void) => { 718 + const handler = (_event: unknown, payload?: { prevWindowBounds?: { x: number; y: number; width: number; height: number } | null }) => { 719 + try { cb(payload); } catch (err) { console.error('[onMaximizeRequest]', err); } 720 + }; 719 721 ipcRenderer.on('tile:window:maximize-request', handler); 720 722 return () => ipcRenderer.off('tile:window:maximize-request', handler); 721 723 },
+10 -37
tests/desktop/page-layout.spec.ts
··· 447 447 448 448 // Publish maximize command 449 449 await sharedBgWindow.evaluate(async (wid: number) => { 450 - (window as any).app.publish( 451 - 'page:maximize', 452 - { windowId: wid } 453 - ); 450 + await (window as any).app.window.maximize(wid); 454 451 }, windowId); 455 452 456 453 // Window bounds should match the display work area ··· 482 479 483 480 // Maximize 484 481 await sharedBgWindow.evaluate(async (wid: number) => { 485 - (window as any).app.publish( 486 - 'page:maximize', 487 - { windowId: wid } 488 - ); 482 + await (window as any).app.window.maximize(wid); 489 483 }, windowId); 490 484 491 485 // Wait for maximize state ··· 495 489 { timeout: 5000 } 496 490 ); 497 491 498 - // Toggle back (restore) 492 + // Restore from maximize 499 493 await sharedBgWindow.evaluate(async (wid: number) => { 500 - (window as any).app.publish( 501 - 'page:maximize', 502 - { windowId: wid } 503 - ); 494 + await (window as any).app.window.unmaximize(wid); 504 495 }, windowId); 505 496 506 497 // Wait for maximized class to be removed ··· 529 520 530 521 // Maximize 531 522 await sharedBgWindow.evaluate(async (wid: number) => { 532 - (window as any).app.publish( 533 - 'page:maximize', 534 - { windowId: wid } 535 - ); 523 + await (window as any).app.window.maximize(wid); 536 524 }, windowId); 537 525 538 526 await pageWindow.waitForFunction( ··· 560 548 561 549 // Maximize 562 550 await sharedBgWindow.evaluate(async (wid: number) => { 563 - (window as any).app.publish( 564 - 'page:maximize', 565 - { windowId: wid } 566 - ); 551 + await (window as any).app.window.maximize(wid); 567 552 }, windowId); 568 553 569 554 await pageWindow.waitForFunction( ··· 615 600 616 601 // Maximize 617 602 await sharedBgWindow.evaluate(async (wid: number) => { 618 - (window as any).app.publish( 619 - 'page:maximize', 620 - { windowId: wid } 621 - ); 603 + await (window as any).app.window.maximize(wid); 622 604 }, windowId); 623 605 624 606 await pageWindow.waitForFunction( ··· 667 649 668 650 // Maximize 669 651 await sharedBgWindow.evaluate(async (wid: number) => { 670 - (window as any).app.publish( 671 - 'page:maximize', 672 - { windowId: wid } 673 - ); 652 + await (window as any).app.window.maximize(wid); 674 653 }, windowId); 675 654 676 655 await pageWindow.waitForFunction( ··· 706 685 707 686 // Maximize 708 687 await sharedBgWindow.evaluate(async (wid: number) => { 709 - (window as any).app.publish( 710 - 'page:maximize', 711 - { windowId: wid } 712 - ); 688 + await (window as any).app.window.maximize(wid); 713 689 }, windowId); 714 690 715 691 await pageWindow.waitForFunction( ··· 785 761 786 762 // Maximize 787 763 await sharedBgWindow.evaluate(async (wid: number) => { 788 - (window as any).app.publish( 789 - 'page:maximize', 790 - { windowId: wid } 791 - ); 764 + await (window as any).app.window.maximize(wid); 792 765 }, windowId); 793 766 794 767 await pageWindow.waitForFunction(