diff --git a/apps/web/src/components/DesktopUpdateTooltip.tsx b/apps/web/src/components/DesktopUpdateTooltip.tsx new file mode 100644 index 00000000000..ee4c228e10b --- /dev/null +++ b/apps/web/src/components/DesktopUpdateTooltip.tsx @@ -0,0 +1,79 @@ +import type { DesktopUpdateState } from "@t3tools/contracts"; +import type { ComponentProps } from "react"; +import { ExternalLinkIcon } from "lucide-react"; + +import { + getDesktopUpdateLatestVersion, + getDesktopUpdateReleaseNotesUrl, +} from "./desktopUpdate.logic"; +import { readLocalApi } from "../localApi"; +import { stackedThreadToast, toastManager } from "./ui/toast"; +import { TooltipPopup } from "./ui/tooltip"; + +type DesktopUpdateTooltipPopupProps = { + state: DesktopUpdateState | null; + summary: string; + side?: ComponentProps["side"]; +}; + +function getDesktopUpdateTooltipTitle(state: DesktopUpdateState | null): string { + if (!state) return "Update Available"; + if (state.status === "downloaded") return "Restart to Update"; + if (state.status === "downloading") return "Downloading Update"; + if (state.status === "error") return "Update Needs Attention"; + if (state.status === "available") return "Update Available"; + return "Application Update"; +} + +function openReleaseNotes(state: DesktopUpdateState | null) { + const api = readLocalApi(); + const url = getDesktopUpdateReleaseNotesUrl(state); + void api?.shell.openExternal(url).catch((error: unknown) => { + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not open release notes", + description: error instanceof Error ? error.message : "Unable to open GitHub Releases.", + }), + ); + }); +} + +export function DesktopUpdateTooltipPopup({ + state, + summary, + side = "top", +}: DesktopUpdateTooltipPopupProps) { + const latestVersion = state ? getDesktopUpdateLatestVersion(state) : null; + + return ( + +
+
+
+ {getDesktopUpdateTooltipTitle(state)} +
+
{summary}
+
+
+ Current Version + + {state?.currentVersion ?? "Unknown"} + + Latest Version + + {latestVersion ?? "Unknown"} + +
+ +
+
+ ); +} diff --git a/apps/web/src/components/desktopUpdate.logic.test.ts b/apps/web/src/components/desktopUpdate.logic.test.ts index 454ecdfe0e9..732209c91e0 100644 --- a/apps/web/src/components/desktopUpdate.logic.test.ts +++ b/apps/web/src/components/desktopUpdate.logic.test.ts @@ -7,6 +7,8 @@ import { getDesktopUpdateActionError, getDesktopUpdateButtonTooltip, getDesktopUpdateInstallConfirmationMessage, + getDesktopUpdateLatestVersion, + getDesktopUpdateReleaseNotesUrl, isDesktopUpdateButtonDisabled, resolveDesktopUpdateButtonAction, shouldShowArm64IntelBuildWarning, @@ -290,3 +292,47 @@ describe("getDesktopUpdateButtonTooltip", () => { ); }); }); + +describe("desktop update release notes helpers", () => { + it("resolves the latest version from downloaded or available state", () => { + expect( + getDesktopUpdateLatestVersion({ + ...baseState, + availableVersion: "1.1.0", + downloadedVersion: "1.1.1", + }), + ).toBe("1.1.1"); + expect(getDesktopUpdateLatestVersion({ ...baseState, availableVersion: "1.1.0" })).toBe( + "1.1.0", + ); + }); + + it("links stable updates to their GitHub release tag", () => { + expect( + getDesktopUpdateReleaseNotesUrl({ + ...baseState, + channel: "latest", + availableVersion: "1.1.0", + }), + ).toBe("https://github.com/pingdotgg/t3code/releases/tag/v1.1.0"); + }); + + it("links nightly updates to their GitHub release tag", () => { + expect( + getDesktopUpdateReleaseNotesUrl({ + ...baseState, + channel: "nightly", + availableVersion: "1.1.0-nightly.20260501.17", + }), + ).toBe("https://github.com/pingdotgg/t3code/releases/tag/nightly-v1.1.0-nightly.20260501.17"); + }); + + it("falls back to the releases page when no latest version is known", () => { + expect(getDesktopUpdateReleaseNotesUrl(null)).toBe( + "https://github.com/pingdotgg/t3code/releases", + ); + expect(getDesktopUpdateReleaseNotesUrl(baseState)).toBe( + "https://github.com/pingdotgg/t3code/releases", + ); + }); +}); diff --git a/apps/web/src/components/desktopUpdate.logic.ts b/apps/web/src/components/desktopUpdate.logic.ts index 38983c810b5..379e67ba90a 100644 --- a/apps/web/src/components/desktopUpdate.logic.ts +++ b/apps/web/src/components/desktopUpdate.logic.ts @@ -2,6 +2,8 @@ import type { DesktopUpdateActionResult, DesktopUpdateState } from "@t3tools/con export type DesktopUpdateButtonAction = "download" | "install" | "none"; +const DESKTOP_UPDATE_RELEASES_URL = "https://github.com/pingdotgg/t3code/releases"; + export function resolveDesktopUpdateButtonAction( state: DesktopUpdateState, ): DesktopUpdateButtonAction { @@ -76,6 +78,20 @@ export function getDesktopUpdateButtonTooltip(state: DesktopUpdateState): string return "Up to date"; } +export function getDesktopUpdateLatestVersion(state: DesktopUpdateState): string | null { + return state.downloadedVersion ?? state.availableVersion; +} + +export function getDesktopUpdateReleaseNotesUrl(state: DesktopUpdateState | null): string { + const latestVersion = state ? getDesktopUpdateLatestVersion(state) : null; + if (!state || !latestVersion) { + return DESKTOP_UPDATE_RELEASES_URL; + } + + const tag = state.channel === "nightly" ? `nightly-v${latestVersion}` : `v${latestVersion}`; + return `${DESKTOP_UPDATE_RELEASES_URL}/tag/${encodeURIComponent(tag)}`; +} + export function getDesktopUpdateInstallConfirmationMessage( state: Pick, ): string { diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 8fc36d4a32b..19a759a7dae 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -21,6 +21,7 @@ import { isDesktopUpdateButtonDisabled, resolveDesktopUpdateButtonAction, } from "../../components/desktopUpdate.logic"; +import { DesktopUpdateTooltipPopup } from "../../components/DesktopUpdateTooltip"; import { ProviderModelPicker } from "../chat/ProviderModelPicker"; import { TraitsPicker } from "../chat/TraitsPicker"; import { resolveAndPersistPreferredEditor } from "../../editorPreferences"; @@ -304,7 +305,9 @@ function AboutVersionSection() { } /> - {buttonTooltip ? {buttonTooltip} : null} + {buttonTooltip ? ( + + ) : null} } /> diff --git a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx index d7e5b74d42d..8856efe5481 100644 --- a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx +++ b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx @@ -19,6 +19,7 @@ import { shouldToastDesktopUpdateActionResult, } from "../desktopUpdate.logic"; import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; +import { DesktopUpdateTooltipPopup } from "../DesktopUpdateTooltip"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; export function SidebarUpdatePill() { @@ -158,7 +159,7 @@ export function SidebarUpdatePill() { } /> - {tooltip} + {action === "download" && (