authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-08-18 21:29:02-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-18 21:29:02-04:00
logefcf7d1ac03e784bdb26843d61ae0ff81c03cec6
tree3374261eb7129c5c739121cf5235e53b4172626a
parente7f6a30caba5c390d5bbc9c300de89b68601c057
signature Signed by PGP key B5690EEEBB952194

fix(desktop): boot the main window unthrottled so cold start paints at full speed (#7460)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

2 files changed, 43 insertions(+), 1 deletions(-)

apps/desktop/src/window/DesktopWindow.test.ts+32-1
...@@ -80,6 +80,7 @@ function makeFakeBrowserWindow() {...@@ -80,6 +80,7 @@ function makeFakeBrowserWindow() {
80 reload: vi.fn(),80 reload: vi.fn(),
81 replaceMisspelling: vi.fn(),81 replaceMisspelling: vi.fn(),
82 send: vi.fn(),82 send: vi.fn(),
83 setBackgroundThrottling: vi.fn(),
83 setWindowOpenHandler: vi.fn(),84 setWindowOpenHandler: vi.fn(),
84 };85 };
8586
...@@ -124,6 +125,7 @@ function makeFakeBrowserWindow() {...@@ -124,6 +125,7 @@ function makeFakeBrowserWindow() {
124 reload: webContents.reload,125 reload: webContents.reload,
125 send: webContents.send,126 send: webContents.send,
126 setZoomLevel: webContents.setZoomLevel,127 setZoomLevel: webContents.setZoomLevel,
128 setBackgroundThrottling: webContents.setBackgroundThrottling,
127 setAutoHideCursor: window.setAutoHideCursor,129 setAutoHideCursor: window.setAutoHideCursor,
128 webContentsListeners,130 webContentsListeners,
129 windowListeners,131 windowListeners,
...@@ -455,7 +457,7 @@ describe("DesktopWindow", () => {...@@ -455,7 +457,7 @@ describe("DesktopWindow", () => {
455 assert.isUndefined(createdWindowOptions[0]?.x);457 assert.isUndefined(createdWindowOptions[0]?.x);
456 assert.isUndefined(createdWindowOptions[0]?.y);458 assert.isUndefined(createdWindowOptions[0]?.y);
457 assert.isTrue(createdWindowOptions[0]?.disableAutoHideCursor);459 assert.isTrue(createdWindowOptions[0]?.disableAutoHideCursor);
458 assert.isUndefined(createdWindowOptions[0]?.webPreferences?.backgroundThrottling);460 assert.isFalse(createdWindowOptions[0]?.webPreferences?.backgroundThrottling);
459 assert.deepEqual(fakeWindow.setAutoHideCursor.mock.calls, [[false]]);461 assert.deepEqual(fakeWindow.setAutoHideCursor.mock.calls, [[false]]);
460 assert.deepEqual(fakeWindow.loadURL.mock.calls[0], ["t3code-dev://app/"]);462 assert.deepEqual(fakeWindow.loadURL.mock.calls[0], ["t3code-dev://app/"]);
461 assert.equal(fakeWindow.openDevTools.mock.calls.length, 1);463 assert.equal(fakeWindow.openDevTools.mock.calls.length, 1);
...@@ -604,6 +606,35 @@ describe("DesktopWindow", () => {...@@ -604,6 +606,35 @@ describe("DesktopWindow", () => {
604 }),606 }),
605 );607 );
606608
609 // The window boots hidden with throttling disabled so first paint runs at
610 // full speed; the first reveal must hand it back to normal hidden-window
611 // throttling or a minimized window stays expensive forever.
612 it.effect("re-enables background throttling on first reveal", () =>
613 Effect.gen(function* () {
614 const fakeWindow = makeFakeBrowserWindow();
615 const createCount = yield* Ref.make(0);
616 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
617 const layer = makeTestLayer({
618 window: fakeWindow.window,
619 createCount,
620 mainWindow,
621 });
622
623 yield* Effect.gen(function* () {
624 const desktopWindow = yield* DesktopWindow.DesktopWindow;
625 yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773"));
626
627 assert.equal(fakeWindow.setBackgroundThrottling.mock.calls.length, 0);
628 const readyToShow = fakeWindow.windowListeners.get("ready-to-show");
629 if (!readyToShow) {
630 return yield* Effect.die("window ready-to-show listener was not registered");
631 }
632 readyToShow();
633 assert.deepEqual(fakeWindow.setBackgroundThrottling.mock.calls, [[true]]);
634 }).pipe(Effect.provide(layer));
635 }),
636 );
637
607 it.effect("debounces move and resize bounds updates", () =>638 it.effect("debounces move and resize bounds updates", () =>
608 Effect.gen(function* () {639 Effect.gen(function* () {
609 const fakeWindow = makeFakeBrowserWindow();640 const fakeWindow = makeFakeBrowserWindow();
apps/desktop/src/window/DesktopWindow.ts+11
...@@ -359,6 +359,12 @@ export const make = Effect.gen(function* () {...@@ -359,6 +359,12 @@ export const make = Effect.gen(function* () {
359 ...getWindowTitleBarOptions(shouldUseDarkColors, environment.platform),359 ...getWindowTitleBarOptions(shouldUseDarkColors, environment.platform),
360 webPreferences: {360 webPreferences: {
361 preload: environment.preloadPath,361 preload: environment.preloadPath,
362 // The window boots hidden (show: false until ready-to-show), and
363 // Chromium throttles hidden renderers: timers coalesce and rAF stops,
364 // which stalls first paint. Boot unthrottled; the first-reveal trigger
365 // re-enables throttling so a hidden or minimized window goes back to
366 // being cheap after it has been shown once.
367 backgroundThrottling: false,
362 contextIsolation: true,368 contextIsolation: true,
363 nodeIntegration: false,369 nodeIntegration: false,
364 sandbox: true,370 sandbox: true,
...@@ -725,6 +731,11 @@ export const make = Effect.gen(function* () {...@@ -725,6 +731,11 @@ export const make = Effect.gen(function* () {
725 revealSubscribers.push((fire) => window.webContents.once("did-finish-load", fire));731 revealSubscribers.push((fire) => window.webContents.once("did-finish-load", fire));
726 }732 }
727 bindFirstRevealTrigger(revealSubscribers, () => {733 bindFirstRevealTrigger(revealSubscribers, () => {
734 // Boot is done; hand the window back to normal hidden-window throttling
735 // (see the backgroundThrottling comment on the create options above).
736 if (!window.isDestroyed()) {
737 window.webContents.setBackgroundThrottling(true);
738 }
728 // Reveal the real window, then close the connecting splash (if any) so the739 // Reveal the real window, then close the connecting splash (if any) so the
729 // two don't overlap and there's no blank gap between them.740 // two don't overlap and there's no blank gap between them.
730 if (persistedSettings.mainWindowMaximized) {741 if (persistedSettings.mainWindowMaximized) {