authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-03-09 04:01:23-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-09 04:01:23-07:00
log304a14de7d6f0b9977c8ecc4bced5b5f359ce02e
treeebdb7308e27c2e02ffa9c3591b539517b27a97e8
parentc585128bbe0311615c58921909467f49600cb85f
signature Signed by PGP key B5690EEEBB952194

Fix users who installed intel builds on arm macs (#641)


10 files changed, 224 insertions(+), 11 deletions(-)

apps/desktop/src/main.ts+17-2
...@@ -31,6 +31,7 @@ import {...@@ -31,6 +31,7 @@ import {
31 reduceDesktopUpdateStateOnNoUpdate,31 reduceDesktopUpdateStateOnNoUpdate,
32 reduceDesktopUpdateStateOnUpdateAvailable,32 reduceDesktopUpdateStateOnUpdateAvailable,
33} from "./updateMachine";33} from "./updateMachine";
34import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch";
3435
35fixPath();36fixPath();
3637
...@@ -80,7 +81,13 @@ let backendLogSink: RotatingFileSink | null = null;...@@ -80,7 +81,13 @@ let backendLogSink: RotatingFileSink | null = null;
80let restoreStdIoCapture: (() => void) | null = null;81let restoreStdIoCapture: (() => void) | null = null;
8182
82let destructiveMenuIconCache: Electron.NativeImage | null | undefined;83let destructiveMenuIconCache: Electron.NativeImage | null | undefined;
83const initialUpdateState = (): DesktopUpdateState => createInitialDesktopUpdateState(app.getVersion());84const desktopRuntimeInfo = resolveDesktopRuntimeInfo({
85 platform: process.platform,
86 processArch: process.arch,
87 runningUnderArm64Translation: app.runningUnderARM64Translation === true,
88});
89const initialUpdateState = (): DesktopUpdateState =>
90 createInitialDesktopUpdateState(app.getVersion(), desktopRuntimeInfo);
8491
85function logTimestamp(): string {92function logTimestamp(): string {
86 return new Date().toISOString();93 return new Date().toISOString();
...@@ -708,6 +715,7 @@ async function downloadAvailableUpdate(): Promise<{ accepted: boolean; completed...@@ -708,6 +715,7 @@ async function downloadAvailableUpdate(): Promise<{ accepted: boolean; completed
708 }715 }
709 updateDownloadInFlight = true;716 updateDownloadInFlight = true;
710 setUpdateState(reduceDesktopUpdateStateOnDownloadStart(updateState));717 setUpdateState(reduceDesktopUpdateStateOnDownloadStart(updateState));
718 autoUpdater.disableDifferentialDownload = isArm64HostRunningIntelBuild(desktopRuntimeInfo);
711 console.info("[desktop-updater] Downloading update...");719 console.info("[desktop-updater] Downloading update...");
712720
713 try {721 try {
...@@ -746,7 +754,7 @@ async function installDownloadedUpdate(): Promise<{ accepted: boolean; completed...@@ -746,7 +754,7 @@ async function installDownloadedUpdate(): Promise<{ accepted: boolean; completed
746function configureAutoUpdater(): void {754function configureAutoUpdater(): void {
747 const enabled = shouldEnableAutoUpdates();755 const enabled = shouldEnableAutoUpdates();
748 setUpdateState({756 setUpdateState({
749 ...createInitialDesktopUpdateState(app.getVersion()),757 ...createInitialDesktopUpdateState(app.getVersion(), desktopRuntimeInfo),
750 enabled,758 enabled,
751 status: enabled ? "idle" : "disabled",759 status: enabled ? "idle" : "disabled",
752 });760 });
...@@ -780,8 +788,15 @@ function configureAutoUpdater(): void {...@@ -780,8 +788,15 @@ function configureAutoUpdater(): void {
780 autoUpdater.channel = DESKTOP_UPDATE_CHANNEL;788 autoUpdater.channel = DESKTOP_UPDATE_CHANNEL;
781 autoUpdater.allowPrerelease = DESKTOP_UPDATE_ALLOW_PRERELEASE;789 autoUpdater.allowPrerelease = DESKTOP_UPDATE_ALLOW_PRERELEASE;
782 autoUpdater.allowDowngrade = false;790 autoUpdater.allowDowngrade = false;
791 autoUpdater.disableDifferentialDownload = isArm64HostRunningIntelBuild(desktopRuntimeInfo);
783 let lastLoggedDownloadMilestone = -1;792 let lastLoggedDownloadMilestone = -1;
784793
794 if (isArm64HostRunningIntelBuild(desktopRuntimeInfo)) {
795 console.info(
796 "[desktop-updater] Apple Silicon host detected while running Intel build; updates will switch to arm64 packages.",
797 );
798 }
799
785 autoUpdater.on("checking-for-update", () => {800 autoUpdater.on("checking-for-update", () => {
786 console.info("[desktop-updater] Looking for updates...");801 console.info("[desktop-updater] Looking for updates...");
787 });802 });
apps/desktop/src/runtimeArch.test.ts created+49
...@@ -0,0 +1,49 @@
1import { describe, expect, it } from "vitest";
2
3import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch";
4
5describe("resolveDesktopRuntimeInfo", () => {
6 it("detects Rosetta-translated Intel builds on Apple Silicon", () => {
7 const runtimeInfo = resolveDesktopRuntimeInfo({
8 platform: "darwin",
9 processArch: "x64",
10 runningUnderArm64Translation: true,
11 });
12
13 expect(runtimeInfo).toEqual({
14 hostArch: "arm64",
15 appArch: "x64",
16 runningUnderArm64Translation: true,
17 });
18 expect(isArm64HostRunningIntelBuild(runtimeInfo)).toBe(true);
19 });
20
21 it("detects native Apple Silicon builds", () => {
22 const runtimeInfo = resolveDesktopRuntimeInfo({
23 platform: "darwin",
24 processArch: "arm64",
25 runningUnderArm64Translation: false,
26 });
27
28 expect(runtimeInfo).toEqual({
29 hostArch: "arm64",
30 appArch: "arm64",
31 runningUnderArm64Translation: false,
32 });
33 expect(isArm64HostRunningIntelBuild(runtimeInfo)).toBe(false);
34 });
35
36 it("passes through non-mac builds without translation", () => {
37 const runtimeInfo = resolveDesktopRuntimeInfo({
38 platform: "linux",
39 processArch: "x64",
40 runningUnderArm64Translation: true,
41 });
42
43 expect(runtimeInfo).toEqual({
44 hostArch: "x64",
45 appArch: "x64",
46 runningUnderArm64Translation: false,
47 });
48 });
49});
apps/desktop/src/runtimeArch.ts created+40
...@@ -0,0 +1,40 @@
1import type { DesktopRuntimeArch, DesktopRuntimeInfo } from "@t3tools/contracts";
2
3interface ResolveDesktopRuntimeInfoInput {
4 readonly platform: NodeJS.Platform;
5 readonly processArch: string;
6 readonly runningUnderArm64Translation: boolean;
7}
8
9function normalizeDesktopArch(arch: string): DesktopRuntimeArch {
10 if (arch === "arm64") return "arm64";
11 if (arch === "x64") return "x64";
12 return "other";
13}
14
15export function resolveDesktopRuntimeInfo(
16 input: ResolveDesktopRuntimeInfoInput,
17): DesktopRuntimeInfo {
18 const appArch = normalizeDesktopArch(input.processArch);
19
20 if (input.platform !== "darwin") {
21 return {
22 hostArch: appArch,
23 appArch,
24 runningUnderArm64Translation: false,
25 };
26 }
27
28 const hostArch =
29 appArch === "arm64" || input.runningUnderArm64Translation ? "arm64" : appArch;
30
31 return {
32 hostArch,
33 appArch,
34 runningUnderArm64Translation: input.runningUnderArm64Translation,
35 };
36}
37
38export function isArm64HostRunningIntelBuild(runtimeInfo: DesktopRuntimeInfo): boolean {
39 return runtimeInfo.hostArch === "arm64" && runtimeInfo.appArch === "x64";
40}
apps/desktop/src/updateMachine.test.ts+12-6
...@@ -13,11 +13,17 @@ import {...@@ -13,11 +13,17 @@ import {
13 reduceDesktopUpdateStateOnUpdateAvailable,13 reduceDesktopUpdateStateOnUpdateAvailable,
14} from "./updateMachine";14} from "./updateMachine";
1515
16const runtimeInfo = {
17 hostArch: "x64",
18 appArch: "x64",
19 runningUnderArm64Translation: false,
20} as const;
21
16describe("updateMachine", () => {22describe("updateMachine", () => {
17 it("clears transient errors when a check starts", () => {23 it("clears transient errors when a check starts", () => {
18 const state = reduceDesktopUpdateStateOnCheckStart(24 const state = reduceDesktopUpdateStateOnCheckStart(
19 {25 {
20 ...createInitialDesktopUpdateState("1.0.0"),26 ...createInitialDesktopUpdateState("1.0.0", runtimeInfo),
21 enabled: true,27 enabled: true,
22 status: "error",28 status: "error",
23 message: "network",29 message: "network",
...@@ -36,7 +42,7 @@ describe("updateMachine", () => {...@@ -36,7 +42,7 @@ describe("updateMachine", () => {
36 it("records a check failure without exposing an action", () => {42 it("records a check failure without exposing an action", () => {
37 const state = reduceDesktopUpdateStateOnCheckFailure(43 const state = reduceDesktopUpdateStateOnCheckFailure(
38 {44 {
39 ...createInitialDesktopUpdateState("1.0.0"),45 ...createInitialDesktopUpdateState("1.0.0", runtimeInfo),
40 enabled: true,46 enabled: true,
41 status: "checking",47 status: "checking",
42 },48 },
...@@ -52,7 +58,7 @@ describe("updateMachine", () => {...@@ -52,7 +58,7 @@ describe("updateMachine", () => {
52 it("preserves available version on download failure for retry", () => {58 it("preserves available version on download failure for retry", () => {
53 const state = reduceDesktopUpdateStateOnDownloadFailure(59 const state = reduceDesktopUpdateStateOnDownloadFailure(
54 {60 {
55 ...createInitialDesktopUpdateState("1.0.0"),61 ...createInitialDesktopUpdateState("1.0.0", runtimeInfo),
56 enabled: true,62 enabled: true,
57 status: "downloading",63 status: "downloading",
58 availableVersion: "1.1.0",64 availableVersion: "1.1.0",
...@@ -70,7 +76,7 @@ describe("updateMachine", () => {...@@ -70,7 +76,7 @@ describe("updateMachine", () => {
70 it("transitions to downloaded and then preserves install retry state", () => {76 it("transitions to downloaded and then preserves install retry state", () => {
71 const downloaded = reduceDesktopUpdateStateOnDownloadComplete(77 const downloaded = reduceDesktopUpdateStateOnDownloadComplete(
72 {78 {
73 ...createInitialDesktopUpdateState("1.0.0"),79 ...createInitialDesktopUpdateState("1.0.0", runtimeInfo),
74 enabled: true,80 enabled: true,
75 status: "downloading",81 status: "downloading",
76 availableVersion: "1.1.0",82 availableVersion: "1.1.0",
...@@ -89,7 +95,7 @@ describe("updateMachine", () => {...@@ -89,7 +95,7 @@ describe("updateMachine", () => {
89 it("clears stale download state when no update is available", () => {95 it("clears stale download state when no update is available", () => {
90 const state = reduceDesktopUpdateStateOnNoUpdate(96 const state = reduceDesktopUpdateStateOnNoUpdate(
91 {97 {
92 ...createInitialDesktopUpdateState("1.0.0"),98 ...createInitialDesktopUpdateState("1.0.0", runtimeInfo),
93 enabled: true,99 enabled: true,
94 status: "error",100 status: "error",
95 availableVersion: "1.1.0",101 availableVersion: "1.1.0",
...@@ -111,7 +117,7 @@ describe("updateMachine", () => {...@@ -111,7 +117,7 @@ describe("updateMachine", () => {
111 it("tracks available, download start, and progress cleanly", () => {117 it("tracks available, download start, and progress cleanly", () => {
112 const available = reduceDesktopUpdateStateOnUpdateAvailable(118 const available = reduceDesktopUpdateStateOnUpdateAvailable(
113 {119 {
114 ...createInitialDesktopUpdateState("1.0.0"),120 ...createInitialDesktopUpdateState("1.0.0", runtimeInfo),
115 enabled: true,121 enabled: true,
116 status: "checking",122 status: "checking",
117 },123 },
apps/desktop/src/updateMachine.ts+8-3
...@@ -1,12 +1,18 @@...@@ -1,12 +1,18 @@
1import type { DesktopUpdateState } from "@t3tools/contracts";1import type { DesktopRuntimeInfo, DesktopUpdateState } from "@t3tools/contracts";
22
3import { getCanRetryAfterDownloadFailure, nextStatusAfterDownloadFailure } from "./updateState";3import { getCanRetryAfterDownloadFailure, nextStatusAfterDownloadFailure } from "./updateState";
44
5export function createInitialDesktopUpdateState(currentVersion: string): DesktopUpdateState {5export function createInitialDesktopUpdateState(
6 currentVersion: string,
7 runtimeInfo: DesktopRuntimeInfo,
8): DesktopUpdateState {
6 return {9 return {
7 enabled: false,10 enabled: false,
8 status: "disabled",11 status: "disabled",
9 currentVersion,12 currentVersion,
13 hostArch: runtimeInfo.hostArch,
14 appArch: runtimeInfo.appArch,
15 runningUnderArm64Translation: runtimeInfo.runningUnderArm64Translation,
10 availableVersion: null,16 availableVersion: null,
11 downloadedVersion: null,17 downloadedVersion: null,
12 downloadPercent: null,18 downloadPercent: null,
...@@ -152,4 +158,3 @@ export function reduceDesktopUpdateStateOnInstallFailure(...@@ -152,4 +158,3 @@ export function reduceDesktopUpdateStateOnInstallFailure(
152 canRetry: true,158 canRetry: true,
153 };159 };
154}160}
155
apps/desktop/src/updateState.test.ts+3
...@@ -12,6 +12,9 @@ const baseState: DesktopUpdateState = {...@@ -12,6 +12,9 @@ const baseState: DesktopUpdateState = {
12 enabled: true,12 enabled: true,
13 status: "idle",13 status: "idle",
14 currentVersion: "1.0.0",14 currentVersion: "1.0.0",
15 hostArch: "x64",
16 appArch: "x64",
17 runningUnderArm64Translation: false,
15 availableVersion: null,18 availableVersion: null,
16 downloadedVersion: null,19 downloadedVersion: null,
17 downloadPercent: null,20 downloadPercent: null,
apps/web/src/components/Sidebar.tsx+34
...@@ -8,6 +8,7 @@ import {...@@ -8,6 +8,7 @@ import {
8 SettingsIcon,8 SettingsIcon,
9 SquarePenIcon,9 SquarePenIcon,
10 TerminalIcon,10 TerminalIcon,
11 TriangleAlertIcon,
11} from "lucide-react";12} from "lucide-react";
12import { useCallback, useEffect, useMemo, useRef, useState } from "react";13import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13import {14import {
...@@ -36,14 +37,18 @@ import { type DraftThreadEnvMode, useComposerDraftStore } from "../composerDraft...@@ -36,14 +37,18 @@ import { type DraftThreadEnvMode, useComposerDraftStore } from "../composerDraft
36import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore";37import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore";
37import { toastManager } from "./ui/toast";38import { toastManager } from "./ui/toast";
38import {39import {
40 getArm64IntelBuildWarningDescription,
39 getDesktopUpdateActionError,41 getDesktopUpdateActionError,
40 getDesktopUpdateButtonTooltip,42 getDesktopUpdateButtonTooltip,
41 isDesktopUpdateButtonDisabled,43 isDesktopUpdateButtonDisabled,
42 resolveDesktopUpdateButtonAction,44 resolveDesktopUpdateButtonAction,
45 shouldShowArm64IntelBuildWarning,
43 shouldHighlightDesktopUpdateError,46 shouldHighlightDesktopUpdateError,
44 shouldShowDesktopUpdateButton,47 shouldShowDesktopUpdateButton,
45 shouldToastDesktopUpdateActionResult,48 shouldToastDesktopUpdateActionResult,
46} from "./desktopUpdate.logic";49} from "./desktopUpdate.logic";
50import { Alert, AlertAction, AlertDescription, AlertTitle } from "./ui/alert";
51import { Button } from "./ui/button";
47import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible";52import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible";
48import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";53import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
49import {54import {
...@@ -887,6 +892,12 @@ export default function Sidebar() {...@@ -887,6 +892,12 @@ export default function Sidebar() {
887 const desktopUpdateButtonAction = desktopUpdateState892 const desktopUpdateButtonAction = desktopUpdateState
888 ? resolveDesktopUpdateButtonAction(desktopUpdateState)893 ? resolveDesktopUpdateButtonAction(desktopUpdateState)
889 : "none";894 : "none";
895 const showArm64IntelBuildWarning =
896 isElectron && shouldShowArm64IntelBuildWarning(desktopUpdateState);
897 const arm64IntelBuildWarningDescription =
898 desktopUpdateState && showArm64IntelBuildWarning
899 ? getArm64IntelBuildWarningDescription(desktopUpdateState)
900 : null;
890 const desktopUpdateButtonInteractivityClasses = desktopUpdateButtonDisabled901 const desktopUpdateButtonInteractivityClasses = desktopUpdateButtonDisabled
891 ? "cursor-not-allowed opacity-60"902 ? "cursor-not-allowed opacity-60"
892 : "hover:bg-accent hover:text-foreground";903 : "hover:bg-accent hover:text-foreground";
...@@ -1030,6 +1041,29 @@ export default function Sidebar() {...@@ -1030,6 +1041,29 @@ export default function Sidebar() {
1030 )}1041 )}
10311042
1032 <SidebarContent className="gap-0">1043 <SidebarContent className="gap-0">
1044 {showArm64IntelBuildWarning && arm64IntelBuildWarningDescription ? (
1045 <SidebarGroup className="px-2 pt-2 pb-0">
1046 <Alert variant="warning" className="rounded-2xl border-warning/40 bg-warning/8">
1047 <TriangleAlertIcon />
1048 <AlertTitle>Intel build on Apple Silicon</AlertTitle>
1049 <AlertDescription>{arm64IntelBuildWarningDescription}</AlertDescription>
1050 {desktopUpdateButtonAction !== "none" ? (
1051 <AlertAction>
1052 <Button
1053 size="xs"
1054 variant="outline"
1055 disabled={desktopUpdateButtonDisabled}
1056 onClick={handleDesktopUpdateButtonClick}
1057 >
1058 {desktopUpdateButtonAction === "download"
1059 ? "Download ARM build"
1060 : "Install ARM build"}
1061 </Button>
1062 </AlertAction>
1063 ) : null}
1064 </Alert>
1065 </SidebarGroup>
1066 ) : null}
1033 <SidebarGroup className="px-2 py-2">1067 <SidebarGroup className="px-2 py-2">
1034 <div className="mb-1 flex items-center justify-between px-2">1068 <div className="mb-1 flex items-center justify-between px-2">
1035 <span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60">1069 <span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60">
apps/web/src/components/desktopUpdate.logic.test.ts+31
...@@ -2,11 +2,13 @@ import { describe, expect, it } from "vitest";...@@ -2,11 +2,13 @@ import { describe, expect, it } from "vitest";
2import type { DesktopUpdateActionResult, DesktopUpdateState } from "@t3tools/contracts";2import type { DesktopUpdateActionResult, DesktopUpdateState } from "@t3tools/contracts";
33
4import {4import {
5 getArm64IntelBuildWarningDescription,
5 getDesktopUpdateActionError,6 getDesktopUpdateActionError,
6 getDesktopUpdateButtonTooltip,7 getDesktopUpdateButtonTooltip,
7 isDesktopUpdateButtonDisabled,8 isDesktopUpdateButtonDisabled,
8 resolveDesktopUpdateButtonAction,9 resolveDesktopUpdateButtonAction,
9 shouldHighlightDesktopUpdateError,10 shouldHighlightDesktopUpdateError,
11 shouldShowArm64IntelBuildWarning,
10 shouldShowDesktopUpdateButton,12 shouldShowDesktopUpdateButton,
11 shouldToastDesktopUpdateActionResult,13 shouldToastDesktopUpdateActionResult,
12} from "./desktopUpdate.logic";14} from "./desktopUpdate.logic";
...@@ -15,6 +17,9 @@ const baseState: DesktopUpdateState = {...@@ -15,6 +17,9 @@ const baseState: DesktopUpdateState = {
15 enabled: true,17 enabled: true,
16 status: "idle",18 status: "idle",
17 currentVersion: "1.0.0",19 currentVersion: "1.0.0",
20 hostArch: "x64",
21 appArch: "x64",
22 runningUnderArm64Translation: false,
18 availableVersion: null,23 availableVersion: null,
19 downloadedVersion: null,24 downloadedVersion: null,
20 downloadPercent: null,25 downloadPercent: null,
...@@ -171,4 +176,30 @@ describe("desktop update UI helpers", () => {...@@ -171,4 +176,30 @@ describe("desktop update UI helpers", () => {
171 }),176 }),
172 ).toBe(false);177 ).toBe(false);
173 });178 });
179
180 it("shows an Apple Silicon warning for Intel builds under Rosetta", () => {
181 const state: DesktopUpdateState = {
182 ...baseState,
183 hostArch: "arm64",
184 appArch: "x64",
185 runningUnderArm64Translation: true,
186 };
187
188 expect(shouldShowArm64IntelBuildWarning(state)).toBe(true);
189 expect(getArm64IntelBuildWarningDescription(state)).toContain("Apple Silicon");
190 expect(getArm64IntelBuildWarningDescription(state)).toContain("Intel build");
191 });
192
193 it("changes the warning copy when a native build update is ready to download", () => {
194 const state: DesktopUpdateState = {
195 ...baseState,
196 hostArch: "arm64",
197 appArch: "x64",
198 runningUnderArm64Translation: true,
199 status: "available",
200 availableVersion: "1.1.0",
201 };
202
203 expect(getArm64IntelBuildWarningDescription(state)).toContain("Download the available update");
204 });
174});205});
apps/web/src/components/desktopUpdate.logic.ts+19
...@@ -32,10 +32,29 @@ export function shouldShowDesktopUpdateButton(state: DesktopUpdateState | null):...@@ -32,10 +32,29 @@ export function shouldShowDesktopUpdateButton(state: DesktopUpdateState | null):
32 return resolveDesktopUpdateButtonAction(state) !== "none";32 return resolveDesktopUpdateButtonAction(state) !== "none";
33}33}
3434
35export function shouldShowArm64IntelBuildWarning(state: DesktopUpdateState | null): boolean {
36 return state?.hostArch === "arm64" && state.appArch === "x64";
37}
38
35export function isDesktopUpdateButtonDisabled(state: DesktopUpdateState | null): boolean {39export function isDesktopUpdateButtonDisabled(state: DesktopUpdateState | null): boolean {
36 return state?.status === "downloading";40 return state?.status === "downloading";
37}41}
3842
43export function getArm64IntelBuildWarningDescription(state: DesktopUpdateState): string {
44 if (!shouldShowArm64IntelBuildWarning(state)) {
45 return "This install is using the correct architecture.";
46 }
47
48 const action = resolveDesktopUpdateButtonAction(state);
49 if (action === "download") {
50 return "This Mac has Apple Silicon, but T3 Code is still running the Intel build under Rosetta. Download the available update to switch to the native Apple Silicon build.";
51 }
52 if (action === "install") {
53 return "This Mac has Apple Silicon, but T3 Code is still running the Intel build under Rosetta. Restart to install the downloaded Apple Silicon build.";
54 }
55 return "This Mac has Apple Silicon, but T3 Code is still running the Intel build under Rosetta. The next app update will replace it with the native Apple Silicon build.";
56}
57
39export function getDesktopUpdateButtonTooltip(state: DesktopUpdateState): string {58export function getDesktopUpdateButtonTooltip(state: DesktopUpdateState): string {
40 if (state.status === "available") {59 if (state.status === "available") {
41 return `Update ${state.availableVersion ?? "available"} ready to download`;60 return `Update ${state.availableVersion ?? "available"} ready to download`;
packages/contracts/src/ipc.ts+11
...@@ -58,10 +58,21 @@ export type DesktopUpdateStatus =...@@ -58,10 +58,21 @@ export type DesktopUpdateStatus =
58 | "downloaded"58 | "downloaded"
59 | "error";59 | "error";
6060
61export type DesktopRuntimeArch = "arm64" | "x64" | "other";
62
63export interface DesktopRuntimeInfo {
64 hostArch: DesktopRuntimeArch;
65 appArch: DesktopRuntimeArch;
66 runningUnderArm64Translation: boolean;
67}
68
61export interface DesktopUpdateState {69export interface DesktopUpdateState {
62 enabled: boolean;70 enabled: boolean;
63 status: DesktopUpdateStatus;71 status: DesktopUpdateStatus;
64 currentVersion: string;72 currentVersion: string;
73 hostArch: DesktopRuntimeArch;
74 appArch: DesktopRuntimeArch;
75 runningUnderArm64Translation: boolean;
65 availableVersion: string | null;76 availableVersion: string | null;
66 downloadedVersion: string | null;77 downloadedVersion: string | null;
67 downloadPercent: number | null;78 downloadPercent: number | null;