Skip to content
Merged
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
35 changes: 34 additions & 1 deletion apps/desktop/src/app/DesktopAppIdentity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ describe("DesktopAppIdentity", () => {
const identity = yield* DesktopAppIdentity.DesktopAppIdentity;
yield* identity.configure;

assert.deepEqual(calls.setName, ["T3 Code (Alpha)"]);
assert.deepEqual(calls.setName, ["T3 Code Alpha"]);
assert.equal(calls.setAboutPanelOptions[0]?.applicationName, "T3 Code (Alpha)");
assert.equal(calls.setAboutPanelOptions[0]?.applicationVersion, "1.2.3");
assert.equal(calls.setAboutPanelOptions[0]?.version, "0123456789ab");
Expand All @@ -237,6 +237,39 @@ describe("DesktopAppIdentity", () => {
);
});

it.effect.each([
{ stage: "Alpha", environment: {} },
{
stage: "Nightly",
environment: { appVersion: "0.0.43-nightly.20260929.2428" },
},
{
stage: "Dev",
environment: { env: { VITE_DEV_SERVER_URL: "http://localhost:5173" } },
},
])("uses a valid native User-Agent product name for $stage", ({ stage, environment }) => {
const calls: ElectronAppCalls = {
setAboutPanelOptions: [],
setDockIcon: [],
setName: [],
};

return withIdentity(
Effect.gen(function* () {
const identity = yield* DesktopAppIdentity.DesktopAppIdentity;
yield* identity.configure;

const runtimeName = calls.setName[0];
assert.isDefined(runtimeName);
assert.equal(runtimeName, `T3 Code ${stage}`);
// RFC 9110's token grammar, after Electron removes ASCII spaces.
assert.match(runtimeName.replaceAll(" ", ""), /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/);
assert.equal(calls.setAboutPanelOptions[0]?.applicationName, `T3 Code (${stage})`);
}),
{ calls, environment },
);
});

it.effect("sets the dock icon only when running unpackaged", () => {
const calls: ElectronAppCalls = {
setAboutPanelOptions: [],
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src/app/DesktopAppIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ export const make = Effect.gen(function* () {

const configure = Effect.gen(function* () {
const commitHash = yield* resolveAboutCommitHash;
yield* electronApp.setName(environment.displayName);
// Electron removes spaces from this name to build the native User-Agent
// product token, but leaves parentheses intact. Keep the runtime name valid
// without rewriting preview sessions (which breaks Turnstile, #7110).
yield* electronApp.setName(
`${environment.branding.baseName} ${environment.branding.stageLabel}`,
);
yield* electronApp.setAboutPanelOptions({
applicationName: environment.displayName,
applicationVersion: environment.appVersion,
Expand Down
36 changes: 35 additions & 1 deletion apps/desktop/src/window/DesktopApplicationMenu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const layerElectronMenu = (
const configureMenu = (
selectedAction: Deferred.Deferred<string>,
applicationMenuTemplate: Deferred.Deferred<readonly Electron.MenuItemConstructorOptions[]>,
environment: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {},
) =>
Effect.gen(function* () {
const menu = yield* DesktopApplicationMenu.DesktopApplicationMenu;
Expand All @@ -116,7 +117,7 @@ const configureMenu = (
Layer.provideMerge(layerElectronDialog),
Layer.provideMerge(layerElectronApp),
Layer.provideMerge(
DesktopEnvironment.layer(environmentInput).pipe(
DesktopEnvironment.layer({ ...environmentInput, ...environment }).pipe(
Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({}))),
),
),
Expand All @@ -125,6 +126,39 @@ const configureMenu = (
);

describe("DesktopApplicationMenu", () => {
it.effect("keeps display branding in the macOS application menu", () =>
Effect.gen(function* () {
const selectedAction = yield* Deferred.make<string>();
const applicationMenuTemplate =
yield* Deferred.make<readonly Electron.MenuItemConstructorOptions[]>();

yield* configureMenu(selectedAction, applicationMenuTemplate, {
platform: "darwin",
appVersion: "0.0.43-nightly.20260929.2428",
});

const template = yield* Deferred.await(applicationMenuTemplate);
const applicationMenu = template[0];
assert.isDefined(applicationMenu);
assert.equal(applicationMenu.label, "T3 Code (Nightly)");
if (!Array.isArray(applicationMenu.submenu)) {
throw new Error("Expected application menu submenu to be an array.");
}
assert.equal(
applicationMenu.submenu.find((item) => item.role === "about")?.label,
"About T3 Code (Nightly)",
);
assert.equal(
applicationMenu.submenu.find((item) => item.role === "hide")?.label,
"Hide T3 Code (Nightly)",
);
assert.equal(
applicationMenu.submenu.find((item) => item.role === "quit")?.label,
"Quit T3 Code (Nightly)",
);
}),
);

it.effect("installs the native menu and routes Settings through DesktopWindow", () =>
Effect.gen(function* () {
const selectedAction = yield* Deferred.make<string>();
Expand Down
11 changes: 4 additions & 7 deletions apps/desktop/src/window/DesktopApplicationMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as Schema from "effect/Schema";
import type * as Electron from "electron";

import { makeComponentLogger } from "../app/DesktopObservability.ts";
import * as ElectronApp from "../electron/ElectronApp.ts";
import * as ElectronDialog from "../electron/ElectronDialog.ts";
import * as ElectronMenu from "../electron/ElectronMenu.ts";
import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
Expand Down Expand Up @@ -107,10 +106,8 @@ const handleCheckForUpdatesMenuClick = Effect.gen(function* () {

/** @public Service construction is part of the canonical Effect module API. */
export const make = Effect.gen(function* () {
const electronApp = yield* ElectronApp.ElectronApp;
const electronMenu = yield* ElectronMenu.ElectronMenu;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const appName = yield* electronApp.name;
const context = yield* Effect.context<DesktopApplicationMenuRuntimeServices>();
const runPromise = Effect.runPromiseWith(context);

Expand Down Expand Up @@ -157,9 +154,9 @@ export const make = Effect.gen(function* () {

if (environment.platform === "darwin") {
template.push({
label: appName,
label: environment.displayName,
submenu: [
{ role: "about" },
{ role: "about", label: `About ${environment.displayName}` },
{
label: "Check for Updates...",
click: checkForUpdatesClick,
Expand All @@ -173,11 +170,11 @@ export const make = Effect.gen(function* () {
{ type: "separator" },
{ role: "services" },
{ type: "separator" },
{ role: "hide" },
{ role: "hide", label: `Hide ${environment.displayName}` },
{ role: "hideOthers" },
{ role: "unhide" },
{ type: "separator" },
{ role: "quit" },
{ role: "quit", label: `Quit ${environment.displayName}` },
],
});
}
Expand Down
Loading