From 46daf1f9abaeb8472898113643f085d807c05291 Mon Sep 17 00:00:00 2001 From: ishaanko Date: Wed, 21 Oct 2026 11:23:13 -0700 Subject: [PATCH 1/6] fix(desktop): keep the macOS app bundle name stable across updates --- docs/operations/connect-setup.md | 6 ++--- docs/operations/release.md | 4 ++- scripts/build-desktop-artifact.test.ts | 35 ++++++++++++++++++++++++++ scripts/build-desktop-artifact.ts | 9 +++++++ 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/docs/operations/connect-setup.md b/docs/operations/connect-setup.md index 899d5c4a33a8e..3e576421f2f36 100644 --- a/docs/operations/connect-setup.md +++ b/docs/operations/connect-setup.md @@ -119,15 +119,15 @@ actual web and server ports. For example, with the default ports: ```sh VITE_DEV_SERVER_URL=http://127.0.0.1:5733 \ T3CODE_PORT=13773 \ - "/Applications/T3 Code (Alpha).app/Contents/MacOS/T3 Code (Alpha)" + "/Applications/T3 Code (Beta).app/Contents/MacOS/T3 Code (Beta)" ``` Rebuild the signed app after native dependency, main-process, preload, entitlement, provisioning, or signing changes. Renderer edits can reuse it. Verify the installed bundle before testing: ```sh -codesign --verify --deep --strict "/Applications/T3 Code (Alpha).app" -codesign -d --entitlements :- "/Applications/T3 Code (Alpha).app" +codesign --verify --deep --strict "/Applications/T3 Code (Beta).app" +codesign -d --entitlements :- "/Applications/T3 Code (Beta).app" ``` ## Restricting sign-ups diff --git a/docs/operations/release.md b/docs/operations/release.md index c6725efdcdad4..0dfe71ff13363 100644 --- a/docs/operations/release.md +++ b/docs/operations/release.md @@ -7,7 +7,9 @@ This document covers the unified release workflow for stable and nightly desktop Beta is the product label, not a release channel or version suffix. Release channels remain stable and nightly. To ship 0.1.0 Beta, use `channel=stable` and `version=0.1.0`. Removing the Beta label later is a branding change independent of -the version. +the version. The macOS `.app` filename is pinned separately (`MAC_BUNDLE_FILENAME` in +`scripts/build-desktop-artifact.ts`) so label and channel changes never rename the +installed bundle; Squirrel.Mac fails to relaunch after a rename. ## What the workflow does diff --git a/scripts/build-desktop-artifact.test.ts b/scripts/build-desktop-artifact.test.ts index 6ad4413adb567..5ce1404207cb6 100644 --- a/scripts/build-desktop-artifact.test.ts +++ b/scripts/build-desktop-artifact.test.ts @@ -473,6 +473,41 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { }), ); + it.effect("keeps the macOS bundle filename stable across product names", () => + Effect.gen(function* () { + const stable = yield* createBuildConfig( + "mac", + "dmg", + "0.1.0", + false, + false, + undefined, + undefined, + ); + const nightly = yield* createBuildConfig( + "mac", + "dmg", + "0.1.1-nightly.20261017.3238", + false, + false, + undefined, + undefined, + ); + + assert.notEqual(stable.productName, nightly.productName); + assert.deepStrictEqual( + (stable.mac as { executableName: string }).executableName, + (nightly.mac as { executableName: string }).executableName, + ); + }).pipe( + Effect.provide( + ConfigProvider.layer( + ConfigProvider.fromEnv({ env: { GITHUB_REPOSITORY: "pingdotgg/t3code" } }), + ), + ), + ), + ); + it.effect("omits update feeds for pull request preview builds", () => Effect.gen(function* () { const preview = yield* createBuildConfig( diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index d2cff47263455..93396e1544b96 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -63,6 +63,14 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process"; const LINUX_ICON_SIZES = [16, 22, 24, 32, 48, 64, 128, 256, 512] as const; const DESKTOP_APP_ID = "com.t3tools.t3code"; + +// On-disk name of the macOS .app (and its executable). electron-builder would +// otherwise derive it from productName, and Squirrel.Mac renames the installed +// bundle to the update's name but then tries to relaunch from the old path, so +// any bundle rename leaves the app closed after an update. Display names still +// come from productName. Never change this value: it is the name most installs +// have had since 0.1.0, and changing it fails every install's next relaunch. +const MAC_BUNDLE_FILENAME = "T3 Code (Beta)"; const APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/u; const BuildPlatform = Schema.Literals(["mac", "linux", "win"]); @@ -2881,6 +2889,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( const path = yield* Path.Path; const repoRoot = yield* RepoRoot; buildConfig.mac = { + executableName: MAC_BUNDLE_FILENAME, target: target === "dmg" ? [target, "zip"] : [target], icon: "icon.icns", category: "public.app-category.developer-tools", From 218046ee6df5b142a15d83dfe8d7bfbd4043b94f Mon Sep 17 00:00:00 2001 From: ishaanko Date: Wed, 21 Oct 2026 11:46:41 -0700 Subject: [PATCH 2/6] test(desktop): pin the exact macOS bundle filename --- scripts/build-desktop-artifact.test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/build-desktop-artifact.test.ts b/scripts/build-desktop-artifact.test.ts index 5ce1404207cb6..60604f5a153ca 100644 --- a/scripts/build-desktop-artifact.test.ts +++ b/scripts/build-desktop-artifact.test.ts @@ -494,11 +494,10 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { undefined, ); + // Existing installs live at this path; changing it fails their relaunch. assert.notEqual(stable.productName, nightly.productName); - assert.deepStrictEqual( - (stable.mac as { executableName: string }).executableName, - (nightly.mac as { executableName: string }).executableName, - ); + assert.equal((stable.mac as { executableName: string }).executableName, "T3 Code (Beta)"); + assert.equal((nightly.mac as { executableName: string }).executableName, "T3 Code (Beta)"); }).pipe( Effect.provide( ConfigProvider.layer( From a8271635365ca1fb5c5fd1a4686683c1658098b1 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Mon, 26 Oct 2026 04:43:23 +0000 Subject: [PATCH 3/6] fix(desktop): clarify the fixed macos bundle release path --- docs/operations/release.md | 7 ++++--- scripts/build-desktop-artifact.ts | 7 +------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/operations/release.md b/docs/operations/release.md index d59c602336aee..d21823ceb02a3 100644 --- a/docs/operations/release.md +++ b/docs/operations/release.md @@ -7,9 +7,10 @@ This document covers the unified release workflow for stable and nightly desktop Beta is the product label, not a release channel or version suffix. Release channels remain stable and nightly. To ship 0.1.0 Beta, use `channel=stable` and `version=0.1.0`. Removing the Beta label later is a branding change independent of -the version. The macOS `.app` filename is pinned separately (`MAC_BUNDLE_FILENAME` in -`scripts/build-desktop-artifact.ts`) so label and channel changes never rename the -installed bundle; Squirrel.Mac fails to relaunch after a rename. +the version. Keep the macOS bundle filename `T3 Code (Beta).app` unchanged so +Squirrel.Mac can relaunch after label or channel changes. When the first nightly +with this filename ships, update the external `t3-code@nightly` Homebrew cask +to use `app "T3 Code (Beta).app"`. ## What the workflow does diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index 683202a8fae75..2a2f8335f1b06 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -64,12 +64,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process"; const LINUX_ICON_SIZES = [16, 22, 24, 32, 48, 64, 128, 256, 512] as const; const DESKTOP_APP_ID = "com.t3tools.t3code"; -// On-disk name of the macOS .app (and its executable). electron-builder would -// otherwise derive it from productName, and Squirrel.Mac renames the installed -// bundle to the update's name but then tries to relaunch from the old path, so -// any bundle rename leaves the app closed after an update. Display names still -// come from productName. Never change this value: it is the name most installs -// have had since 0.1.0, and changing it fails every install's next relaunch. +// Keep the installed path stable: Squirrel.Mac relaunches from the old path after an update. const MAC_BUNDLE_FILENAME = "T3 Code (Beta)"; const APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/u; From 13553262062672b6d727b0bca350abc57daec58c Mon Sep 17 00:00:00 2001 From: ishaanko Date: Fri, 30 Oct 2026 22:51:29 -0700 Subject: [PATCH 4/6] fix(desktop): nightly DMG installs beside stable under its own name --- scripts/build-desktop-artifact.test.ts | 5 +++++ scripts/build-desktop-artifact.ts | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/build-desktop-artifact.test.ts b/scripts/build-desktop-artifact.test.ts index 288cfad9f3904..f79579ef170d3 100644 --- a/scripts/build-desktop-artifact.test.ts +++ b/scripts/build-desktop-artifact.test.ts @@ -521,6 +521,11 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { assert.notEqual(stable.productName, nightly.productName); assert.equal((stable.mac as { executableName: string }).executableName, "T3 Code (Beta)"); assert.equal((nightly.mac as { executableName: string }).executableName, "T3 Code (Beta)"); + // A fresh nightly install must not land on, and replace, the stable app. + const dmgAppName = (config: typeof stable) => + (config.dmg as { contents: ReadonlyArray<{ name?: string }> }).contents[0]?.name; + assert.equal(dmgAppName(stable), "T3 Code (Beta).app"); + assert.equal(dmgAppName(nightly), "T3 Code (Nightly).app"); }).pipe( Effect.provide( ConfigProvider.layer( diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index 846155067dc45..6e64f83e93468 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -2880,7 +2880,9 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( height: 432, }, contents: [ - { x: 166, y: 214, type: "file" }, + // Name the app after its channel so a nightly installs beside stable. Squirrel.Mac + // never renames a bundle whose folder differs from its executable, so it stays put. + { x: 166, y: 214, type: "file", name: `${resolveDesktopProductName(version)}.app` }, { x: 474, y: 214, type: "link", path: "/Applications" }, ], iconSize: 120, From 5de1613f700467b5e312800c63be407e2e81921f Mon Sep 17 00:00:00 2001 From: ishaanko Date: Fri, 30 Oct 2026 23:29:20 -0700 Subject: [PATCH 5/6] docs(release): the nightly cask keeps its app name --- docs/operations/release.md | 8 ++++---- scripts/build-desktop-artifact.ts | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/operations/release.md b/docs/operations/release.md index e0512a0a3bf70..8f6cf7567ce00 100644 --- a/docs/operations/release.md +++ b/docs/operations/release.md @@ -7,10 +7,10 @@ This document covers the unified release workflow for stable and nightly desktop Beta is the product label, not a release channel or version suffix. Release channels remain stable and nightly. To ship 0.1.0 Beta, use `channel=stable` and `version=0.1.0`. Removing the Beta label later is a branding change independent of -the version. Keep the macOS bundle filename `T3 Code (Beta).app` unchanged so -Squirrel.Mac can relaunch after label or channel changes. When the first nightly -with this filename ships, update the external `t3-code@nightly` Homebrew cask -to use `app "T3 Code (Beta).app"`. +the version. Keep the macOS executable name `T3 Code (Beta)` unchanged so +Squirrel.Mac can relaunch after label or channel changes. The nightly DMG still +installs as `T3 Code (Nightly).app`, beside stable, so the `t3-code@nightly` +Homebrew cask needs no change. ## What the workflow does diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index 6e64f83e93468..892573a5f6c54 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -64,7 +64,8 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process"; const LINUX_ICON_SIZES = [16, 22, 24, 32, 48, 64, 128, 256, 512] as const; const DESKTOP_APP_ID = "com.t3tools.t3code"; -// Keep the installed path stable: Squirrel.Mac relaunches from the old path after an update. +// Squirrel.Mac renames an install named after its executable to the update's executable, +// then relaunches from the old path. One executable name for every channel keeps it in place. const MAC_BUNDLE_FILENAME = "T3 Code (Beta)"; const APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/u; From 7242402cb92b1cdb8341b75ece4b41cffb37e8b3 Mon Sep 17 00:00:00 2001 From: ishaanko Date: Fri, 30 Oct 2026 23:50:28 -0700 Subject: [PATCH 6/6] docs(release): note the one-time move for lone pre-pin nightly installs --- docs/operations/release.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/operations/release.md b/docs/operations/release.md index 8f6cf7567ce00..ca0a12fc3e2f3 100644 --- a/docs/operations/release.md +++ b/docs/operations/release.md @@ -10,7 +10,10 @@ remain stable and nightly. To ship 0.1.0 Beta, use `channel=stable` and the version. Keep the macOS executable name `T3 Code (Beta)` unchanged so Squirrel.Mac can relaunch after label or channel changes. The nightly DMG still installs as `T3 Code (Nightly).app`, beside stable, so the `t3-code@nightly` -Homebrew cask needs no change. +Homebrew cask needs no change. One exception, for the first nightly with this name: a +nightly installed before it, with no stable app beside it, moves to `T3 Code (Beta).app` +and doesn't relaunch, leaving the cask's app missing. Say so in that nightly's notes: +`brew reinstall --cask t3-code@nightly`, then delete the moved `T3 Code (Beta).app`. ## What the workflow does