From de05d3e5ab395362f0e6d1d9be65e9de4de71989 Mon Sep 17 00:00:00 2001 From: Jono Kemball Date: Sun, 15 Mar 2026 23:37:48 +1300 Subject: [PATCH 1/4] feat(web): move update button to sidebar footer as dismissable pill Move the desktop update button from the sidebar header to the footer, displayed as a pill above the Settings button. The pill shows contextual states: "Update available", "Downloading (X%)", and "Restart to update". Add a dismiss button that hides the notification until the next app launch. --- apps/web/src/components/Sidebar.tsx | 96 +++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 25 deletions(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index a8a58a13b2..96d6ed9efa 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -2,14 +2,16 @@ import { ArrowLeftIcon, ArrowUpDownIcon, ChevronRightIcon, + DownloadIcon, FolderIcon, GitPullRequestIcon, PlusIcon, - RocketIcon, + RotateCwIcon, SettingsIcon, SquarePenIcon, TerminalIcon, TriangleAlertIcon, + XIcon, } from "lucide-react"; import { autoAnimate } from "@formkit/auto-animate"; import { useCallback, useEffect, useMemo, useRef, useState, type MouseEvent } from "react"; @@ -63,7 +65,6 @@ import { isDesktopUpdateButtonDisabled, resolveDesktopUpdateButtonAction, shouldShowArm64IntelBuildWarning, - shouldHighlightDesktopUpdateError, shouldShowDesktopUpdateButton, shouldToastDesktopUpdateActionResult, } from "./desktopUpdate.logic"; @@ -408,6 +409,7 @@ export default function Sidebar() { const dragInProgressRef = useRef(false); const suppressProjectClickAfterDragRef = useRef(false); const [desktopUpdateState, setDesktopUpdateState] = useState(null); + const [updateDismissed, setUpdateDismissed] = useState(false); const selectedThreadIds = useThreadSelectionStore((s) => s.selectedThreadIds); const toggleThreadSelection = useThreadSelectionStore((s) => s.toggleThread); const rangeSelectTo = useThreadSelectionStore((s) => s.rangeSelectTo); @@ -1484,7 +1486,7 @@ export default function Sidebar() { }; }, []); - const showDesktopUpdateButton = isElectron && shouldShowDesktopUpdateButton(desktopUpdateState); + const showDesktopUpdateButton = isElectron && shouldShowDesktopUpdateButton(desktopUpdateState) && !updateDismissed; const desktopUpdateTooltip = desktopUpdateState ? getDesktopUpdateButtonTooltip(desktopUpdateState) @@ -1511,9 +1513,12 @@ export default function Sidebar() { : shouldHighlightDesktopUpdateError(desktopUpdateState) ? "text-rose-500 animate-pulse" : "text-amber-500 animate-pulse"; - const newThreadShortcutLabel = - shortcutLabelForCommand(keybindings, "chat.newLocal") ?? - shortcutLabelForCommand(keybindings, "chat.new"); + const newThreadShortcutLabel = useMemo( + () => + shortcutLabelForCommand(keybindings, "chat.newLocal") ?? + shortcutLabelForCommand(keybindings, "chat.new"), + [keybindings], + ); const handleDesktopUpdateButtonClick = useCallback(() => { const bridge = window.desktopBridge; @@ -1621,25 +1626,6 @@ export default function Sidebar() { <> {wordmark} - {showDesktopUpdateButton && ( - - - - - } - /> - {desktopUpdateTooltip} - - )} ) : ( @@ -1820,6 +1806,66 @@ export default function Sidebar() { + {showDesktopUpdateButton && ( +
+
+
+ + + {desktopUpdateButtonAction === "install" ? ( + <> + + Restart to update + + ) : desktopUpdateState?.status === "downloading" ? ( + <> + + + Downloading{typeof desktopUpdateState.downloadPercent === "number" ? ` (${Math.floor(desktopUpdateState.downloadPercent)}%)` : "…"} + + + ) : ( + <> + + Update available + + )} + + } + /> + {desktopUpdateTooltip} + + {desktopUpdateButtonAction === "download" && ( + + setUpdateDismissed(true)} + > + + + } + /> + Dismiss until next launch + + )} +
+
+ )} {isOnSettings ? ( From b42450e53c7c35408ec4216502d1832f92f75c75 Mon Sep 17 00:00:00 2001 From: Jono Kemball Date: Sun, 15 Mar 2026 23:58:47 +1300 Subject: [PATCH 2/4] refactor(web): extract SidebarUpdatePill into self-contained component Move all desktop update UI (pill + arm64 warning) into a single SidebarUpdatePill component that owns its own state subscription, action handlers, and dismiss logic. Removes ~220 lines from Sidebar.tsx. --- apps/web/src/components/Sidebar.tsx | 222 +----------------- .../components/sidebar/SidebarUpdatePill.tsx | 197 ++++++++++++++++ 2 files changed, 199 insertions(+), 220 deletions(-) create mode 100644 apps/web/src/components/sidebar/SidebarUpdatePill.tsx diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 96d6ed9efa..2f82c0f223 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -2,16 +2,12 @@ import { ArrowLeftIcon, ArrowUpDownIcon, ChevronRightIcon, - DownloadIcon, FolderIcon, GitPullRequestIcon, PlusIcon, - RotateCwIcon, SettingsIcon, SquarePenIcon, TerminalIcon, - TriangleAlertIcon, - XIcon, } from "lucide-react"; import { autoAnimate } from "@formkit/auto-animate"; import { useCallback, useEffect, useMemo, useRef, useState, type MouseEvent } from "react"; @@ -32,7 +28,6 @@ import { restrictToFirstScrollableAncestor, restrictToVerticalAxis } from "@dnd- import { CSS } from "@dnd-kit/utilities"; import { DEFAULT_MODEL_BY_PROVIDER, - type DesktopUpdateState, ProjectId, ThreadId, type GitStatusResult, @@ -58,18 +53,6 @@ import { useComposerDraftStore } from "../composerDraftStore"; import { useHandleNewThread } from "../hooks/useHandleNewThread"; import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore"; import { toastManager } from "./ui/toast"; -import { - getArm64IntelBuildWarningDescription, - getDesktopUpdateActionError, - getDesktopUpdateButtonTooltip, - isDesktopUpdateButtonDisabled, - resolveDesktopUpdateButtonAction, - shouldShowArm64IntelBuildWarning, - shouldShowDesktopUpdateButton, - shouldToastDesktopUpdateActionResult, -} from "./desktopUpdate.logic"; -import { Alert, AlertAction, AlertDescription, AlertTitle } from "./ui/alert"; -import { Button } from "./ui/button"; import { Collapsible, CollapsibleContent } from "./ui/collapsible"; import { Menu, MenuGroup, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuTrigger } from "./ui/menu"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; @@ -101,6 +84,7 @@ import { sortProjectsForSidebar, sortThreadsForSidebar, } from "./Sidebar.logic"; +import { SidebarUpdatePill } from "./sidebar/SidebarUpdatePill"; import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; const EMPTY_KEYBINDINGS: ResolvedKeybindingsConfig = []; @@ -408,8 +392,6 @@ export default function Sidebar() { const renamingInputRef = useRef(null); const dragInProgressRef = useRef(false); const suppressProjectClickAfterDragRef = useRef(false); - const [desktopUpdateState, setDesktopUpdateState] = useState(null); - const [updateDismissed, setUpdateDismissed] = useState(false); const selectedThreadIds = useThreadSelectionStore((s) => s.selectedThreadIds); const toggleThreadSelection = useThreadSelectionStore((s) => s.toggleThread); const rangeSelectTo = useThreadSelectionStore((s) => s.rangeSelectTo); @@ -1453,66 +1435,6 @@ export default function Sidebar() { }; }, [clearSelection, selectedThreadIds.size]); - useEffect(() => { - if (!isElectron) return; - const bridge = window.desktopBridge; - if ( - !bridge || - typeof bridge.getUpdateState !== "function" || - typeof bridge.onUpdateState !== "function" - ) { - return; - } - - let disposed = false; - let receivedSubscriptionUpdate = false; - const unsubscribe = bridge.onUpdateState((nextState) => { - if (disposed) return; - receivedSubscriptionUpdate = true; - setDesktopUpdateState(nextState); - }); - - void bridge - .getUpdateState() - .then((nextState) => { - if (disposed || receivedSubscriptionUpdate) return; - setDesktopUpdateState(nextState); - }) - .catch(() => undefined); - - return () => { - disposed = true; - unsubscribe(); - }; - }, []); - - const showDesktopUpdateButton = isElectron && shouldShowDesktopUpdateButton(desktopUpdateState) && !updateDismissed; - - const desktopUpdateTooltip = desktopUpdateState - ? getDesktopUpdateButtonTooltip(desktopUpdateState) - : "Update available"; - - const desktopUpdateButtonDisabled = isDesktopUpdateButtonDisabled(desktopUpdateState); - const desktopUpdateButtonAction = desktopUpdateState - ? resolveDesktopUpdateButtonAction(desktopUpdateState) - : "none"; - const showArm64IntelBuildWarning = - isElectron && shouldShowArm64IntelBuildWarning(desktopUpdateState); - const arm64IntelBuildWarningDescription = - desktopUpdateState && showArm64IntelBuildWarning - ? getArm64IntelBuildWarningDescription(desktopUpdateState) - : null; - const desktopUpdateButtonInteractivityClasses = desktopUpdateButtonDisabled - ? "cursor-not-allowed opacity-60" - : "hover:bg-accent hover:text-foreground"; - const desktopUpdateButtonClasses = - desktopUpdateState?.status === "downloaded" - ? "text-emerald-500" - : desktopUpdateState?.status === "downloading" - ? "text-sky-400" - : shouldHighlightDesktopUpdateError(desktopUpdateState) - ? "text-rose-500 animate-pulse" - : "text-amber-500 animate-pulse"; const newThreadShortcutLabel = useMemo( () => shortcutLabelForCommand(keybindings, "chat.newLocal") ?? @@ -1520,64 +1442,6 @@ export default function Sidebar() { [keybindings], ); - const handleDesktopUpdateButtonClick = useCallback(() => { - const bridge = window.desktopBridge; - if (!bridge || !desktopUpdateState) return; - if (desktopUpdateButtonDisabled || desktopUpdateButtonAction === "none") return; - - if (desktopUpdateButtonAction === "download") { - void bridge - .downloadUpdate() - .then((result) => { - if (result.completed) { - toastManager.add({ - type: "success", - title: "Update downloaded", - description: "Restart the app from the update button to install it.", - }); - } - if (!shouldToastDesktopUpdateActionResult(result)) return; - const actionError = getDesktopUpdateActionError(result); - if (!actionError) return; - toastManager.add({ - type: "error", - title: "Could not download update", - description: actionError, - }); - }) - .catch((error) => { - toastManager.add({ - type: "error", - title: "Could not start update download", - description: error instanceof Error ? error.message : "An unexpected error occurred.", - }); - }); - return; - } - - if (desktopUpdateButtonAction === "install") { - void bridge - .installUpdate() - .then((result) => { - if (!shouldToastDesktopUpdateActionResult(result)) return; - const actionError = getDesktopUpdateActionError(result); - if (!actionError) return; - toastManager.add({ - type: "error", - title: "Could not install update", - description: actionError, - }); - }) - .catch((error) => { - toastManager.add({ - type: "error", - title: "Could not install update", - description: error instanceof Error ? error.message : "An unexpected error occurred.", - }); - }); - } - }, [desktopUpdateButtonAction, desktopUpdateButtonDisabled, desktopUpdateState]); - const expandThreadListForProject = useCallback((projectId: ProjectId) => { setExpandedThreadListsByProject((current) => { if (current.has(projectId)) return current; @@ -1635,29 +1499,6 @@ export default function Sidebar() { )} - {showArm64IntelBuildWarning && arm64IntelBuildWarningDescription ? ( - - - - Intel build on Apple Silicon - {arm64IntelBuildWarningDescription} - {desktopUpdateButtonAction !== "none" ? ( - - - - ) : null} - - - ) : null}
@@ -1806,66 +1647,7 @@ export default function Sidebar() { - {showDesktopUpdateButton && ( -
-
-
- - - {desktopUpdateButtonAction === "install" ? ( - <> - - Restart to update - - ) : desktopUpdateState?.status === "downloading" ? ( - <> - - - Downloading{typeof desktopUpdateState.downloadPercent === "number" ? ` (${Math.floor(desktopUpdateState.downloadPercent)}%)` : "…"} - - - ) : ( - <> - - Update available - - )} - - } - /> - {desktopUpdateTooltip} - - {desktopUpdateButtonAction === "download" && ( - - setUpdateDismissed(true)} - > - - - } - /> - Dismiss until next launch - - )} -
-
- )} + {isOnSettings ? ( diff --git a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx new file mode 100644 index 0000000000..15378fad21 --- /dev/null +++ b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx @@ -0,0 +1,197 @@ +import { DownloadIcon, RotateCwIcon, TriangleAlertIcon, XIcon } from "lucide-react"; +import { useCallback, useEffect, useState } from "react"; +import type { DesktopUpdateState } from "@t3tools/contracts"; +import { isElectron } from "../../env"; +import { toastManager } from "../ui/toast"; +import { + getArm64IntelBuildWarningDescription, + getDesktopUpdateActionError, + getDesktopUpdateButtonTooltip, + isDesktopUpdateButtonDisabled, + resolveDesktopUpdateButtonAction, + shouldShowArm64IntelBuildWarning, + shouldShowDesktopUpdateButton, + shouldToastDesktopUpdateActionResult, +} from "../desktopUpdate.logic"; +import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; + +export function SidebarUpdatePill() { + const [state, setState] = useState(null); + const [dismissed, setDismissed] = useState(false); + + useEffect(() => { + if (!isElectron) return; + const bridge = window.desktopBridge; + if ( + !bridge || + typeof bridge.getUpdateState !== "function" || + typeof bridge.onUpdateState !== "function" + ) { + return; + } + + let disposed = false; + let receivedSubscriptionUpdate = false; + const unsubscribe = bridge.onUpdateState((nextState) => { + if (disposed) return; + receivedSubscriptionUpdate = true; + setState(nextState); + }); + + void bridge + .getUpdateState() + .then((nextState) => { + if (disposed || receivedSubscriptionUpdate) return; + setState(nextState); + }) + .catch(() => undefined); + + return () => { + disposed = true; + unsubscribe(); + }; + }, []); + + const visible = isElectron && shouldShowDesktopUpdateButton(state) && !dismissed; + const tooltip = state ? getDesktopUpdateButtonTooltip(state) : "Update available"; + const disabled = isDesktopUpdateButtonDisabled(state); + const action = state ? resolveDesktopUpdateButtonAction(state) : "none"; + + const showArm64Warning = isElectron && shouldShowArm64IntelBuildWarning(state); + const arm64Description = + state && showArm64Warning ? getArm64IntelBuildWarningDescription(state) : null; + + const handleAction = useCallback(() => { + const bridge = window.desktopBridge; + if (!bridge || !state) return; + if (disabled || action === "none") return; + + if (action === "download") { + void bridge + .downloadUpdate() + .then((result) => { + if (result.completed) { + toastManager.add({ + type: "success", + title: "Update downloaded", + description: "Restart the app from the update button to install it.", + }); + } + if (!shouldToastDesktopUpdateActionResult(result)) return; + const actionError = getDesktopUpdateActionError(result); + if (!actionError) return; + toastManager.add({ + type: "error", + title: "Could not download update", + description: actionError, + }); + }) + .catch((error) => { + toastManager.add({ + type: "error", + title: "Could not start update download", + description: error instanceof Error ? error.message : "An unexpected error occurred.", + }); + }); + return; + } + + if (action === "install") { + void bridge + .installUpdate() + .then((result) => { + if (!shouldToastDesktopUpdateActionResult(result)) return; + const actionError = getDesktopUpdateActionError(result); + if (!actionError) return; + toastManager.add({ + type: "error", + title: "Could not install update", + description: actionError, + }); + }) + .catch((error) => { + toastManager.add({ + type: "error", + title: "Could not install update", + description: error instanceof Error ? error.message : "An unexpected error occurred.", + }); + }); + } + }, [action, disabled, state]); + + if (!visible && !showArm64Warning) return null; + + return ( +
+ {showArm64Warning && arm64Description && ( +
+ + {arm64Description} +
+ )} + {visible && ( +
+
+ + + {action === "install" ? ( + <> + + Restart to update + + ) : state?.status === "downloading" ? ( + <> + + + Downloading + {typeof state.downloadPercent === "number" + ? ` (${Math.floor(state.downloadPercent)}%)` + : "…"} + + + ) : ( + <> + + Update available + + )} + + } + /> + {tooltip} + + {action === "download" && ( + + setDismissed(true)} + > + + + } + /> + Dismiss until next launch + + )} +
+ )} +
+ ); +} From 2003d740fac2d65b7604ac960b01e95a34f34a23 Mon Sep 17 00:00:00 2001 From: Jono Kemball Date: Mon, 16 Mar 2026 00:06:41 +1300 Subject: [PATCH 3/4] feat(web): restore arm64 warning Alert UI in update pill Use the original Alert component with title and description for the arm64 Intel build warning, remove redundant action button since the update pill handles it, and match sidebar font size with text-xs. --- .../components/sidebar/SidebarUpdatePill.tsx | 98 +++++++++++-------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx index 15378fad21..d28ab096cf 100644 --- a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx +++ b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx @@ -13,51 +13,70 @@ import { shouldShowDesktopUpdateButton, shouldToastDesktopUpdateActionResult, } from "../desktopUpdate.logic"; +import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; export function SidebarUpdatePill() { - const [state, setState] = useState(null); + // TODO: REMOVE - hardcoded for testing arm64 warning + const [state, setState] = useState({ + enabled: true, + status: "available", + currentVersion: "1.0.0", + availableVersion: "1.2.3", + downloadedVersion: null, + downloadPercent: null, + checkedAt: new Date().toISOString(), + message: null, + errorContext: null, + hostArch: "arm64", + appArch: "x64", + runningUnderArm64Translation: true, + canRetry: false, + }); const [dismissed, setDismissed] = useState(false); - useEffect(() => { - if (!isElectron) return; - const bridge = window.desktopBridge; - if ( - !bridge || - typeof bridge.getUpdateState !== "function" || - typeof bridge.onUpdateState !== "function" - ) { - return; - } - - let disposed = false; - let receivedSubscriptionUpdate = false; - const unsubscribe = bridge.onUpdateState((nextState) => { - if (disposed) return; - receivedSubscriptionUpdate = true; - setState(nextState); - }); + // TODO: REMOVE - disabled for testing + // useEffect(() => { + // if (!isElectron) return; + // const bridge = window.desktopBridge; + // if ( + // !bridge || + // typeof bridge.getUpdateState !== "function" || + // typeof bridge.onUpdateState !== "function" + // ) { + // return; + // } + // + // let disposed = false; + // let receivedSubscriptionUpdate = false; + // const unsubscribe = bridge.onUpdateState((nextState) => { + // if (disposed) return; + // receivedSubscriptionUpdate = true; + // setState(nextState); + // }); + // + // void bridge + // .getUpdateState() + // .then((nextState) => { + // if (disposed || receivedSubscriptionUpdate) return; + // setState(nextState); + // }) + // .catch(() => undefined); + // + // return () => { + // disposed = true; + // unsubscribe(); + // }; + // }, []); - void bridge - .getUpdateState() - .then((nextState) => { - if (disposed || receivedSubscriptionUpdate) return; - setState(nextState); - }) - .catch(() => undefined); - - return () => { - disposed = true; - unsubscribe(); - }; - }, []); - - const visible = isElectron && shouldShowDesktopUpdateButton(state) && !dismissed; + // TODO: REMOVE - bypassed isElectron for testing + const visible = /* isElectron && */ shouldShowDesktopUpdateButton(state) && !dismissed; const tooltip = state ? getDesktopUpdateButtonTooltip(state) : "Update available"; const disabled = isDesktopUpdateButtonDisabled(state); const action = state ? resolveDesktopUpdateButtonAction(state) : "none"; - const showArm64Warning = isElectron && shouldShowArm64IntelBuildWarning(state); + // TODO: REMOVE - bypassed isElectron for testing + const showArm64Warning = /* isElectron && */ shouldShowArm64IntelBuildWarning(state); const arm64Description = state && showArm64Warning ? getArm64IntelBuildWarningDescription(state) : null; @@ -124,10 +143,11 @@ export function SidebarUpdatePill() { return (
{showArm64Warning && arm64Description && ( -
- - {arm64Description} -
+ + + Intel build on Apple Silicon + {arm64Description} + )} {visible && (
Date: Mon, 16 Mar 2026 00:08:23 +1300 Subject: [PATCH 4/4] chore(web): remove test mocks from SidebarUpdatePill Restore real bridge subscription and isElectron checks that were hardcoded for local testing. --- .../components/sidebar/SidebarUpdatePill.tsx | 88 ++++++++----------- 1 file changed, 35 insertions(+), 53 deletions(-) diff --git a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx index d28ab096cf..3b78f1b06f 100644 --- a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx +++ b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx @@ -17,66 +17,48 @@ import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; export function SidebarUpdatePill() { - // TODO: REMOVE - hardcoded for testing arm64 warning - const [state, setState] = useState({ - enabled: true, - status: "available", - currentVersion: "1.0.0", - availableVersion: "1.2.3", - downloadedVersion: null, - downloadPercent: null, - checkedAt: new Date().toISOString(), - message: null, - errorContext: null, - hostArch: "arm64", - appArch: "x64", - runningUnderArm64Translation: true, - canRetry: false, - }); + const [state, setState] = useState(null); const [dismissed, setDismissed] = useState(false); - // TODO: REMOVE - disabled for testing - // useEffect(() => { - // if (!isElectron) return; - // const bridge = window.desktopBridge; - // if ( - // !bridge || - // typeof bridge.getUpdateState !== "function" || - // typeof bridge.onUpdateState !== "function" - // ) { - // return; - // } - // - // let disposed = false; - // let receivedSubscriptionUpdate = false; - // const unsubscribe = bridge.onUpdateState((nextState) => { - // if (disposed) return; - // receivedSubscriptionUpdate = true; - // setState(nextState); - // }); - // - // void bridge - // .getUpdateState() - // .then((nextState) => { - // if (disposed || receivedSubscriptionUpdate) return; - // setState(nextState); - // }) - // .catch(() => undefined); - // - // return () => { - // disposed = true; - // unsubscribe(); - // }; - // }, []); + useEffect(() => { + if (!isElectron) return; + const bridge = window.desktopBridge; + if ( + !bridge || + typeof bridge.getUpdateState !== "function" || + typeof bridge.onUpdateState !== "function" + ) { + return; + } + + let disposed = false; + let receivedSubscriptionUpdate = false; + const unsubscribe = bridge.onUpdateState((nextState) => { + if (disposed) return; + receivedSubscriptionUpdate = true; + setState(nextState); + }); + + void bridge + .getUpdateState() + .then((nextState) => { + if (disposed || receivedSubscriptionUpdate) return; + setState(nextState); + }) + .catch(() => undefined); + + return () => { + disposed = true; + unsubscribe(); + }; + }, []); - // TODO: REMOVE - bypassed isElectron for testing - const visible = /* isElectron && */ shouldShowDesktopUpdateButton(state) && !dismissed; + const visible = isElectron && shouldShowDesktopUpdateButton(state) && !dismissed; const tooltip = state ? getDesktopUpdateButtonTooltip(state) : "Update available"; const disabled = isDesktopUpdateButtonDisabled(state); const action = state ? resolveDesktopUpdateButtonAction(state) : "none"; - // TODO: REMOVE - bypassed isElectron for testing - const showArm64Warning = /* isElectron && */ shouldShowArm64IntelBuildWarning(state); + const showArm64Warning = isElectron && shouldShowArm64IntelBuildWarning(state); const arm64Description = state && showArm64Warning ? getArm64IntelBuildWarningDescription(state) : null;