Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .codex
Empty file.
30 changes: 30 additions & 0 deletions apps/desktop/src/desktopSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
readDesktopSettings,
resolveDefaultDesktopSettings,
setDesktopServerExposurePreference,
setDesktopTitleBarModePreference,
setDesktopUpdateChannelPreference,
writeDesktopSettings,
} from "./desktopSettings.ts";
Expand Down Expand Up @@ -37,6 +38,7 @@ describe("desktopSettings", () => {
serverExposureMode: "local-only",
updateChannel: "nightly",
updateChannelConfiguredByUser: false,
titleBarMode: "custom",
});
});

Expand All @@ -47,12 +49,14 @@ describe("desktopSettings", () => {
serverExposureMode: "network-accessible",
updateChannel: "latest",
updateChannelConfiguredByUser: true,
titleBarMode: "native",
});

expect(readDesktopSettings(settingsPath, "0.0.17")).toEqual({
serverExposureMode: "network-accessible",
updateChannel: "latest",
updateChannelConfiguredByUser: true,
titleBarMode: "native",
});
});

Expand All @@ -63,13 +67,15 @@ describe("desktopSettings", () => {
serverExposureMode: "local-only",
updateChannel: "latest",
updateChannelConfiguredByUser: false,
titleBarMode: "custom",
},
"network-accessible",
),
).toEqual({
serverExposureMode: "network-accessible",
updateChannel: "latest",
updateChannelConfiguredByUser: false,
titleBarMode: "custom",
});
});

Expand All @@ -80,13 +86,34 @@ describe("desktopSettings", () => {
serverExposureMode: "local-only",
updateChannel: "latest",
updateChannelConfiguredByUser: false,
titleBarMode: "custom",
},
"nightly",
),
).toEqual({
serverExposureMode: "local-only",
updateChannel: "nightly",
updateChannelConfiguredByUser: true,
titleBarMode: "custom",
});
});

it("persists the requested titlebar mode", () => {
expect(
setDesktopTitleBarModePreference(
{
serverExposureMode: "local-only",
updateChannel: "latest",
updateChannelConfiguredByUser: false,
titleBarMode: "custom",
},
"native",
),
).toEqual({
serverExposureMode: "local-only",
updateChannel: "latest",
updateChannelConfiguredByUser: false,
titleBarMode: "native",
});
});

Expand All @@ -105,6 +132,7 @@ describe("desktopSettings", () => {
serverExposureMode: "local-only",
updateChannel: "nightly",
updateChannelConfiguredByUser: false,
titleBarMode: "custom",
});
});

Expand All @@ -123,6 +151,7 @@ describe("desktopSettings", () => {
serverExposureMode: "local-only",
updateChannel: "nightly",
updateChannelConfiguredByUser: false,
titleBarMode: "custom",
});
});

Expand All @@ -142,6 +171,7 @@ describe("desktopSettings", () => {
serverExposureMode: "local-only",
updateChannel: "latest",
updateChannelConfiguredByUser: true,
titleBarMode: "custom",
});
});
});
22 changes: 21 additions & 1 deletion apps/desktop/src/desktopSettings.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import * as FS from "node:fs";
import * as Path from "node:path";
import type { DesktopServerExposureMode, DesktopUpdateChannel } from "@t3tools/contracts";
import type {
DesktopServerExposureMode,
DesktopTitleBarMode,
DesktopUpdateChannel,
} from "@t3tools/contracts";

import { resolveDefaultDesktopUpdateChannel } from "./updateChannels.ts";

export interface DesktopSettings {
readonly serverExposureMode: DesktopServerExposureMode;
readonly updateChannel: DesktopUpdateChannel;
readonly updateChannelConfiguredByUser: boolean;
readonly titleBarMode: DesktopTitleBarMode;
}

export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
serverExposureMode: "local-only",
updateChannel: "latest",
updateChannelConfiguredByUser: false,
titleBarMode: "custom",
};

export function resolveDefaultDesktopSettings(appVersion: string): DesktopSettings {
Expand Down Expand Up @@ -46,6 +52,18 @@ export function setDesktopUpdateChannelPreference(
};
}

export function setDesktopTitleBarModePreference(
settings: DesktopSettings,
requestedMode: DesktopTitleBarMode,
): DesktopSettings {
return settings.titleBarMode === requestedMode
? settings
: {
...settings,
titleBarMode: requestedMode,
};
}

export function readDesktopSettings(settingsPath: string, appVersion: string): DesktopSettings {
const defaultSettings = resolveDefaultDesktopSettings(appVersion);

Expand All @@ -59,6 +77,7 @@ export function readDesktopSettings(settingsPath: string, appVersion: string): D
readonly serverExposureMode?: unknown;
readonly updateChannel?: unknown;
readonly updateChannelConfiguredByUser?: unknown;
readonly titleBarMode?: unknown;
};
const parsedUpdateChannel =
parsed.updateChannel === "nightly" || parsed.updateChannel === "latest"
Expand All @@ -77,6 +96,7 @@ export function readDesktopSettings(settingsPath: string, appVersion: string): D
? parsedUpdateChannel
: defaultSettings.updateChannel,
updateChannelConfiguredByUser,
titleBarMode: parsed.titleBarMode === "native" ? "native" : "custom",
};
} catch {
return defaultSettings;
Expand Down
36 changes: 36 additions & 0 deletions apps/desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { MenuItemConstructorOptions, OpenDialogOptions } from "electron";
import type {
ClientSettings,
DesktopTheme,
DesktopTitleBarMode,
DesktopAppBranding,
DesktopServerExposureMode,
DesktopServerExposureState,
Expand All @@ -41,6 +42,7 @@ import {
DEFAULT_DESKTOP_SETTINGS,
readDesktopSettings,
setDesktopServerExposurePreference,
setDesktopTitleBarModePreference,
setDesktopUpdateChannelPreference,
writeDesktopSettings,
} from "./desktopSettings.ts";
Expand Down Expand Up @@ -102,6 +104,8 @@ const SET_SAVED_ENVIRONMENT_SECRET_CHANNEL = "desktop:set-saved-environment-secr
const REMOVE_SAVED_ENVIRONMENT_SECRET_CHANNEL = "desktop:remove-saved-environment-secret";
const GET_SERVER_EXPOSURE_STATE_CHANNEL = "desktop:get-server-exposure-state";
const SET_SERVER_EXPOSURE_MODE_CHANNEL = "desktop:set-server-exposure-mode";
const GET_TITLE_BAR_MODE_CHANNEL = "desktop:get-titlebar-mode";
const SET_TITLE_BAR_MODE_CHANNEL = "desktop:set-titlebar-mode";
const BASE_DIR = process.env.T3CODE_HOME?.trim() || Path.join(OS.homedir(), ".t3");
const STATE_DIR = Path.join(BASE_DIR, "userdata");
const DESKTOP_SETTINGS_PATH = Path.join(STATE_DIR, "desktop-settings.json");
Expand Down Expand Up @@ -435,6 +439,14 @@ function getSafeTheme(rawTheme: unknown): DesktopTheme | null {
return null;
}

function getSafeTitleBarMode(rawMode: unknown): DesktopTitleBarMode | null {
if (rawMode === "custom" || rawMode === "native") {
return rawMode;
}

return null;
}

async function waitForBackendHttpReady(
baseUrl: string,
options?: Parameters<typeof waitForHttpReady>[1],
Expand Down Expand Up @@ -1669,6 +1681,26 @@ function registerIpcHandlers(): void {
return nextState;
});

ipcMain.removeHandler(GET_TITLE_BAR_MODE_CHANNEL);
ipcMain.handle(GET_TITLE_BAR_MODE_CHANNEL, async () => desktopSettings.titleBarMode);

ipcMain.removeHandler(SET_TITLE_BAR_MODE_CHANNEL);
ipcMain.handle(SET_TITLE_BAR_MODE_CHANNEL, async (_event, rawMode: unknown) => {
const mode = getSafeTitleBarMode(rawMode);
if (!mode) {
throw new Error("Invalid titlebar mode input.");
}

if (desktopSettings.titleBarMode === mode) {
return mode;
}

desktopSettings = setDesktopTitleBarModePreference(desktopSettings, mode);
writeDesktopSettings(DESKTOP_SETTINGS_PATH, desktopSettings);
relaunchDesktopApp(`titleBarMode=${mode}`);
return mode;
});

ipcMain.removeHandler(PICK_FOLDER_CHANNEL);
ipcMain.handle(PICK_FOLDER_CHANNEL, async (_event, rawOptions: unknown) => {
const owner = BrowserWindow.getFocusedWindow() ?? mainWindow;
Expand Down Expand Up @@ -1879,6 +1911,10 @@ function getInitialWindowBackgroundColor(): string {
}

function getWindowTitleBarOptions(): WindowTitleBarOptions {
if (desktopSettings.titleBarMode === "native") {
return {};
}

if (process.platform === "darwin") {
return {
titleBarStyle: "hiddenInset",
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const SET_SAVED_ENVIRONMENT_SECRET_CHANNEL = "desktop:set-saved-environment-secr
const REMOVE_SAVED_ENVIRONMENT_SECRET_CHANNEL = "desktop:remove-saved-environment-secret";
const GET_SERVER_EXPOSURE_STATE_CHANNEL = "desktop:get-server-exposure-state";
const SET_SERVER_EXPOSURE_MODE_CHANNEL = "desktop:set-server-exposure-mode";
const GET_TITLE_BAR_MODE_CHANNEL = "desktop:get-titlebar-mode";
const SET_TITLE_BAR_MODE_CHANNEL = "desktop:set-titlebar-mode";

contextBridge.exposeInMainWorld("desktopBridge", {
getAppBranding: () => {
Expand Down Expand Up @@ -53,6 +55,8 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.invoke(REMOVE_SAVED_ENVIRONMENT_SECRET_CHANNEL, environmentId),
getServerExposureState: () => ipcRenderer.invoke(GET_SERVER_EXPOSURE_STATE_CHANNEL),
setServerExposureMode: (mode) => ipcRenderer.invoke(SET_SERVER_EXPOSURE_MODE_CHANNEL, mode),
getTitleBarMode: () => ipcRenderer.invoke(GET_TITLE_BAR_MODE_CHANNEL),
setTitleBarMode: (mode) => ipcRenderer.invoke(SET_TITLE_BAR_MODE_CHANNEL, mode),
pickFolder: (options) => ipcRenderer.invoke(PICK_FOLDER_CHANNEL, options),
confirm: (message) => ipcRenderer.invoke(CONFIRM_CHANNEL, message),
setTheme: (theme) => ipcRenderer.invoke(SET_THEME_CHANNEL, theme),
Expand Down
41 changes: 41 additions & 0 deletions apps/web/src/components/settings/SettingsPanels.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@
import { ConnectionsSettings } from "./ConnectionsSettings";
import { GeneralSettingsPanel } from "./SettingsPanels";

vi.mock("../../env", () => {
// Export a runtime getter for `isElectron` so tests that don't provide a
// QueryClientProvider can keep rendering the simpler AboutVersionTitle,
// while tests that set up a desktop bridge/nativeApi will exercise the
// full AboutVersionSection which calls `useQueryClient()`.
return {
get isElectron() {
return (
typeof window !== "undefined" &&
(window.desktopBridge !== undefined || window.nativeApi !== undefined)
);
},
};
});

const authAccessHarness = vi.hoisted(() => {
type Snapshot = AuthAccessSnapshot;
let snapshot: Snapshot = {
Expand Down Expand Up @@ -260,6 +275,8 @@
readonly serverExposureState?: Awaited<ReturnType<DesktopBridge["getServerExposureState"]>>;
readonly setServerExposureMode?: DesktopBridge["setServerExposureMode"];
readonly setUpdateChannel?: DesktopBridge["setUpdateChannel"];
readonly titleBarMode?: Awaited<ReturnType<DesktopBridge["getTitleBarMode"]>>;
readonly setTitleBarMode?: DesktopBridge["setTitleBarMode"];
}): DesktopBridge => {
const idleUpdateState: DesktopUpdateState = {
enabled: false,
Expand Down Expand Up @@ -310,6 +327,8 @@
pickFolder: vi.fn().mockResolvedValue(null),
confirm: vi.fn().mockResolvedValue(false),
setTheme: vi.fn().mockResolvedValue(undefined),
getTitleBarMode: vi.fn().mockResolvedValue(overrides?.titleBarMode ?? "custom"),
setTitleBarMode: overrides?.setTitleBarMode ?? vi.fn().mockImplementation(async (mode) => mode),
showContextMenu: vi.fn().mockResolvedValue(null),
openExternal: vi.fn().mockResolvedValue(true),
onMenuAction: () => () => {},
Expand Down Expand Up @@ -453,6 +472,28 @@
.toBeInTheDocument();
});

it("changes desktop titlebar mode after confirmation", async () => {
const desktopBridge = createDesktopBridgeStub({
titleBarMode: "custom",
});
vi.mocked(desktopBridge.confirm).mockResolvedValue(true);
window.desktopBridge = desktopBridge;
setServerConfigSnapshot(createBaseServerConfig());

mounted = await render(
<AppAtomRegistryProvider>
<GeneralSettingsPanel />
</AppAtomRegistryProvider>,
);

await expect.element(page.getByLabelText("Window titlebar mode")).toBeInTheDocument();

Check failure on line 489 in apps/web/src/components/settings/SettingsPanels.browser.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

[chromium] src/components/settings/SettingsPanels.browser.tsx > GeneralSettingsPanel observability > changes desktop titlebar mode after confirmation

VitestBrowserElementError: Cannot find element with locator: getByLabel('Window titlebar mode') <body style="background-color: rgb(255, 255, 255);" > <div> <div class="flex-1 overflow-y-auto p-6 sm:p-8" > <div … /> </div> </div> </body>... ❯ toBeInTheDocument src/components/settings/SettingsPanels.browser.tsx:489:70 Caused by: Caused by: Error: Matcher did not succeed in time. ❯ src/components/settings/SettingsPanels.browser.tsx:489:4
await page.getByLabelText("Window titlebar mode").click();
await page.getByText("Native", { exact: true }).click();
await vi.waitFor(() => {
expect(desktopBridge.setTitleBarMode).toHaveBeenCalledWith("native");
});
});

it("creates and shows a pairing link when network access is enabled", async () => {
window.desktopBridge = createDesktopBridgeStub({
serverExposureState: {
Expand Down
Loading
Loading