authorgravatar for 91240327+shivamhwp@users.noreply.github.comshivam <91240327+shivamhwp@users.noreply.github.com> 2026-09-15 21:37:03+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-15 21:37:03+05:30
logc1b2210411da79350a3f9f5a65a4a64971e4acad
tree4a9b21f1d20b80f776b79a781c319a5b93280cf5
parentae53072af0560c0606202b87b5fdb7e52d48ac5c
signature Signed by PGP key B5690EEEBB952194

fix(desktop): keep the sidebar brand and window buttons aligned (#11906)

Co-authored-by: T3 Code Test <t3code-test@example.com>

5 files changed, 78 insertions(+), 6 deletions(-)

apps/desktop/src/preload.ts+14-1
......@@ -6,7 +6,7 @@ import type {
66 DesktopSnapShotEvent,
77} from "@t3tools/contracts";
88import { exposeClerkBridge } from "@clerk/electron/preload";
9import { contextBridge, ipcRenderer } from "electron";
9import { contextBridge, ipcRenderer, webFrame } from "electron";
1010
1111import * as IpcChannels from "./ipc/channels.ts";
1212
......@@ -32,6 +32,19 @@ exposeClerkBridge({ passkeys: true });
3232// oxlint-disable-next-line t3code/no-global-process-runtime -- Electron exposes the client platform in its sandboxed preload process.
3333const clientPlatform = process.platform;
3434
35if (clientPlatform === "darwin") {
36 // Native window buttons do not scale with Chromium zoom. Keep their reserved
37 // space in native points, including when a zoomed page is reloaded.
38 const syncWindowControlInset = () => {
39 document.documentElement.style.setProperty(
40 "--desktop-window-controls-inset",
41 `${90 / webFrame.getZoomFactor()}px`,
42 );
43 };
44 window.addEventListener("DOMContentLoaded", syncWindowControlInset, { once: true });
45 window.addEventListener("resize", syncWindowControlInset);
46}
47
3548function unwrapEnsureSshEnvironmentResult(result: unknown) {
3649 if (
3750 typeof result === "object" &&
apps/desktop/src/window/DesktopWindow.test.ts+36
......@@ -77,6 +77,7 @@ function makeFakeBrowserWindow() {
7777 isDestroyed: vi.fn(() => false),
7878 getURL: vi.fn(() => "t3code-dev://app/"),
7979 getZoomLevel: vi.fn(() => zoomLevel),
80 getZoomFactor: vi.fn(() => 1.2 ** zoomLevel),
8081 setZoomLevel: vi.fn((level: number) => {
8182 zoomLevel = level;
8283 }),
......@@ -118,6 +119,7 @@ function makeFakeBrowserWindow() {
118119 setOpacity: vi.fn(),
119120 setTitle: vi.fn(),
120121 setTitleBarOverlay: vi.fn(),
122 setWindowButtonPosition: vi.fn(),
121123 show: vi.fn(),
122124 webContents,
123125 };
......@@ -136,6 +138,7 @@ function makeFakeBrowserWindow() {
136138 reload: webContents.reload,
137139 send: webContents.send,
138140 setZoomLevel: webContents.setZoomLevel,
141 setWindowButtonPosition: window.setWindowButtonPosition,
139142 setBackgroundThrottling: webContents.setBackgroundThrottling,
140143 setAutoHideCursor: window.setAutoHideCursor,
141144 setFullScreen: window.setFullScreen,
......@@ -742,6 +745,39 @@ describe("DesktopWindow", () => {
742745 }),
743746 );
744747
748 it.effect("keeps macOS window buttons centered when zooming and leaving fullscreen", () =>
749 Effect.gen(function* () {
750 const fakeWindow = makeFakeBrowserWindow();
751 const createCount = yield* Ref.make(0);
752 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
753 const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow });
754
755 yield* Effect.gen(function* () {
756 const desktopWindow = yield* DesktopWindow.DesktopWindow;
757 yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773"));
758
759 for (const direction of ["in", "in", "out", "reset", "out"] as const) {
760 yield* desktopWindow.zoomMain(direction);
761 const position = fakeWindow.setWindowButtonPosition.mock.lastCall?.[0];
762 assert.isDefined(position);
763 // The 14-point native buttons should share the zoomed 52px header's center.
764 const headerCenter = 26 * fakeWindow.window.webContents.getZoomFactor();
765 assert.isAtMost(Math.abs(position.y + 7 - headerCenter), 0.5);
766 assert.equal(position.x, 16);
767 }
768
769 fakeWindow.isFullScreen.mockReturnValue(true);
770 fakeWindow.setWindowButtonPosition.mockClear();
771 yield* desktopWindow.zoomMain("reset");
772 assert.equal(fakeWindow.setWindowButtonPosition.mock.calls.length, 0);
773
774 fakeWindow.isFullScreen.mockReturnValue(false);
775 fakeWindow.windowListeners.get("leave-full-screen")?.();
776 assert.deepEqual(fakeWindow.setWindowButtonPosition.mock.lastCall, [{ x: 16, y: 19 }]);
777 }).pipe(Effect.provide(layer));
778 }),
779 );
780
745781 it.effect("uses the persisted main window bounds when opening the window", () =>
746782 Effect.gen(function* () {
747783 const fakeWindow = makeFakeBrowserWindow();
apps/desktop/src/window/DesktopWindow.ts+23-1
......@@ -31,6 +31,22 @@ import * as ElectronApp from "../electron/ElectronApp.ts";
3131import { makeQuitShortcutHandler } from "./QuitHold.ts";
3232
3333const TITLEBAR_HEIGHT = 40;
34// Matches --workspace-topbar-height in apps/web/src/index.css. Native macOS
35// buttons are 14 points tall and do not scale with the renderer's zoom.
36const MACOS_WORKSPACE_TOPBAR_HEIGHT = 52;
37const MACOS_WINDOW_BUTTON_RADIUS = 7;
38
39function syncMacosWindowButtons(window: Electron.BrowserWindow): void {
40 if (window.isDestroyed() || window.isFullScreen()) return;
41 window.setWindowButtonPosition({
42 x: 16,
43 y: Math.round(
44 (MACOS_WORKSPACE_TOPBAR_HEIGHT * window.webContents.getZoomFactor()) / 2 -
45 MACOS_WINDOW_BUTTON_RADIUS,
46 ),
47 });
48}
49
3450const TITLEBAR_COLOR = "#01000000"; // #00000000 does not work correctly on Linux
3551const TITLEBAR_LIGHT_SYMBOL_COLOR = "#1f2937";
3652const TITLEBAR_DARK_SYMBOL_COLOR = "#f8fafc";
......@@ -241,7 +257,10 @@ function getWindowTitleBarOptions(
241257 if (platform === "darwin") {
242258 return {
243259 titleBarStyle: "hiddenInset",
244 trafficLightPosition: { x: 16, y: 18 },
260 trafficLightPosition: {
261 x: 16,
262 y: MACOS_WORKSPACE_TOPBAR_HEIGHT / 2 - MACOS_WINDOW_BUTTON_RADIUS,
263 },
245264 };
246265 }
247266
......@@ -660,6 +679,7 @@ export const make = Effect.gen(function* () {
660679 window.webContents.send(WINDOW_FULLSCREEN_STATE_CHANNEL, true);
661680 });
662681 window.on("leave-full-screen", () => {
682 syncMacosWindowButtons(window);
663683 window.webContents.send(WINDOW_FULLSCREEN_STATE_CHANNEL, false);
664684 });
665685 }
......@@ -720,6 +740,7 @@ export const make = Effect.gen(function* () {
720740 clearDevelopmentLoadRetry();
721741 developmentLoadRetryIndex = 0;
722742 window.setTitle(environment.displayName);
743 if (environment.platform === "darwin") syncMacosWindowButtons(window);
723744 });
724745 window.webContents.on(
725746 "did-fail-load",
......@@ -990,6 +1011,7 @@ export const make = Effect.gen(function* () {
9901011 webContents.setZoomLevel(
9911012 direction === "reset" ? 0 : webContents.getZoomLevel() + (direction === "in" ? 0.5 : -0.5),
9921013 );
1014 if (environment.platform === "darwin") syncMacosWindowButtons(window.value);
9931015 // Chromium pushes the new level down to embedded guests, which would zoom
9941016 // the previewed page along with the app UI. The preview browser keeps its
9951017 // own zoom, so put each guest back where the preview left it.
apps/web/src/components/AppSidebarLayout.tsx+1-1
......@@ -46,7 +46,7 @@ import {
4646} from "./ui/sidebar";
4747import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
4848
49const MACOS_TRAFFIC_LIGHTS_LEFT_INSET = "90px";
49const MACOS_TRAFFIC_LIGHTS_LEFT_INSET = "var(--desktop-window-controls-inset, 90px)";
5050
5151function subscribeToViewportWidth(onChange: () => void): () => void {
5252 window.addEventListener("resize", onChange);
apps/web/src/components/sidebar/SidebarChrome.tsx+4-3
......@@ -91,11 +91,12 @@ function SidebarBrand({ onBackdrop }: { onBackdrop: boolean }) {
9191 )}
9292 to="/"
9393 >
94 <span className="inline-flex min-w-0 items-baseline gap-1">
95 <T3Wordmark aria-label="T3" className="h-2.5 w-auto shrink-0" />
94 {/* Center the visible capitals, without the font's ascender/descender space. */}
95 <span className="inline-flex min-w-0 items-baseline gap-1 text-sm font-medium tracking-tight">
96 <T3Wordmark aria-label="T3" className="h-[1cap] w-auto shrink-0" />
9697 <span
9798 className={cn(
98 "truncate text-sm font-medium tracking-tight",
99 "truncate [text-box:trim-both_cap_alphabetic]",
99100 onBackdrop ? "text-white/70" : "text-muted-foreground",
100101 )}
101102 >