diff --git a/apps/desktop/src/main/services/updates/autoUpdateService.test.ts b/apps/desktop/src/main/services/updates/autoUpdateService.test.ts index 1b0ec7c82..b1d8da64c 100644 --- a/apps/desktop/src/main/services/updates/autoUpdateService.test.ts +++ b/apps/desktop/src/main/services/updates/autoUpdateService.test.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { compareUpdateVersions, createAutoUpdateService } from "./autoUpdateService"; +import { buildReleaseNotesUrl, compareUpdateVersions, createAutoUpdateService } from "./autoUpdateService"; import type { Logger } from "../logging/logger"; const electronAppMock = vi.hoisted(() => ({ isPackaged: false })); @@ -56,6 +56,14 @@ function expectCacheEmpty(updaterCacheDir: string): void { expect(fs.readdirSync(updaterCacheDir)).toEqual([]); } +describe("buildReleaseNotesUrl", () => { + it("points release notes at the docs changelog route", () => { + expect(buildReleaseNotesUrl("v1.2.11")).toBe("https://www.ade-app.dev/docs/changelog/v1.2.11"); + expect(buildReleaseNotesUrl("1.2.11", "https://staging.ade-app.dev/")).toBe("https://staging.ade-app.dev/docs/changelog/v1.2.11"); + expect(buildReleaseNotesUrl(" ", "https://www.ade-app.dev")).toBeNull(); + }); +}); + describe("createAutoUpdateService", () => { afterEach(() => { vi.useRealTimers(); @@ -179,14 +187,14 @@ describe("createAutoUpdateService", () => { expect(service.getSnapshot().recentlyInstalled).toEqual({ version: "1.2.3", installedAt: "2026-04-06T15:21:00.000Z", - releaseNotesUrl: "https://www.ade-app.dev/changelog/v1.2.3", + releaseNotesUrl: "https://www.ade-app.dev/docs/changelog/v1.2.3", }); expect(JSON.parse(fs.readFileSync(globalStatePath, "utf8"))).toEqual({ recentlyInstalledUpdate: { version: "1.2.3", installedAt: "2026-04-06T15:21:00.000Z", - releaseNotesUrl: "https://www.ade-app.dev/changelog/v1.2.3", + releaseNotesUrl: "https://www.ade-app.dev/docs/changelog/v1.2.3", }, }); expectCacheEmpty(updaterCacheDir); @@ -206,7 +214,7 @@ describe("createAutoUpdateService", () => { pendingInstallUpdate: { fromVersion: "1.2.2", targetVersion: "1.2.3", - releaseNotesUrl: "https://www.ade-app.dev/changelog/v1.2.3", + releaseNotesUrl: "https://www.ade-app.dev/docs/changelog/v1.2.3", requestedAt: "2026-04-06T15:20:00.000Z", }, }), "utf8"); @@ -262,7 +270,7 @@ describe("createAutoUpdateService", () => { bytesPerSecond: 128_000, transferredBytes: 6_240_000, totalBytes: 10_000_000, - releaseNotesUrl: "https://www.ade-app.dev/changelog/v1.2.3", + releaseNotesUrl: "https://www.ade-app.dev/docs/changelog/v1.2.3", }); updater.emit("update-downloaded", { @@ -273,7 +281,7 @@ describe("createAutoUpdateService", () => { status: "ready", version: "1.2.3", progressPercent: 100, - releaseNotesUrl: "https://www.ade-app.dev/changelog/v1.2.3", + releaseNotesUrl: "https://www.ade-app.dev/docs/changelog/v1.2.3", }); await expect(service.quitAndInstall()).resolves.toBe(true); @@ -283,7 +291,7 @@ describe("createAutoUpdateService", () => { pendingInstallUpdate: { fromVersion: "1.2.2", targetVersion: "1.2.3", - releaseNotesUrl: "https://www.ade-app.dev/changelog/v1.2.3", + releaseNotesUrl: "https://www.ade-app.dev/docs/changelog/v1.2.3", requestedAt: "2026-04-06T15:21:00.000Z", }, }); @@ -336,7 +344,7 @@ describe("createAutoUpdateService", () => { pendingInstallUpdate: { fromVersion: "1.2.2", targetVersion: "1.2.4", - releaseNotesUrl: "https://www.ade-app.dev/changelog/v1.2.4", + releaseNotesUrl: "https://www.ade-app.dev/docs/changelog/v1.2.4", requestedAt: "2026-04-06T15:21:00.000Z", }, }); @@ -424,7 +432,7 @@ describe("createAutoUpdateService", () => { expect(service.getSnapshot()).toMatchObject({ status: "ready", version: "1.2.4", - releaseNotesUrl: "https://www.ade-app.dev/changelog/v1.2.4", + releaseNotesUrl: "https://www.ade-app.dev/docs/changelog/v1.2.4", }); service.dispose(); @@ -624,7 +632,7 @@ describe("createAutoUpdateService", () => { pendingInstallUpdate: { fromVersion: "1.2.2", targetVersion: "1.2.3", - releaseNotesUrl: "https://www.ade-app.dev/changelog/v1.2.3", + releaseNotesUrl: "https://www.ade-app.dev/docs/changelog/v1.2.3", requestedAt: "2026-04-06T15:20:00.000Z", }, }), "utf8"); @@ -643,7 +651,7 @@ describe("createAutoUpdateService", () => { expect(service.getSnapshot().recentlyInstalled).toEqual({ version: "1.2.4", installedAt: "2026-04-06T15:21:00.000Z", - releaseNotesUrl: "https://www.ade-app.dev/changelog/v1.2.4", + releaseNotesUrl: "https://www.ade-app.dev/docs/changelog/v1.2.4", }); expectCacheEmpty(updaterCacheDir); diff --git a/apps/desktop/src/main/services/updates/autoUpdateService.ts b/apps/desktop/src/main/services/updates/autoUpdateService.ts index e9043ec2d..a1b338898 100644 --- a/apps/desktop/src/main/services/updates/autoUpdateService.ts +++ b/apps/desktop/src/main/services/updates/autoUpdateService.ts @@ -132,7 +132,7 @@ export function buildReleaseNotesUrl( const normalizedVersion = version.trim().replace(/^v/i, ""); const normalizedBaseUrl = baseUrl.trim().replace(/\/+$/, ""); if (!normalizedVersion || !normalizedBaseUrl) return null; - return `${normalizedBaseUrl}/changelog/${encodeURIComponent(`v${normalizedVersion}`)}`; + return `${normalizedBaseUrl}/docs/changelog/${encodeURIComponent(`v${normalizedVersion}`)}`; } function cloneRecentlyInstalledUpdate( @@ -219,10 +219,8 @@ function reconcilePersistedUpdateState(args: { nextState.recentlyInstalledUpdate = { version: args.currentVersion, installedAt: args.now, - releaseNotesUrl: - pendingInstall.targetVersion === args.currentVersion - ? pendingInstall.releaseNotesUrl - : buildReleaseNotesUrl(args.currentVersion, args.releaseNotesBaseUrl), + releaseNotesUrl: buildReleaseNotesUrl(args.currentVersion, args.releaseNotesBaseUrl) + ?? pendingInstall.releaseNotesUrl, }; cacheCleanupReason = "installed"; } else { diff --git a/apps/desktop/src/renderer/components/app/AutoUpdateControl.tsx b/apps/desktop/src/renderer/components/app/AutoUpdateControl.tsx index a458ecf6f..2106d1631 100644 --- a/apps/desktop/src/renderer/components/app/AutoUpdateControl.tsx +++ b/apps/desktop/src/renderer/components/app/AutoUpdateControl.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useState } from "react"; import * as Dialog from "@radix-ui/react-dialog"; -import { ArrowSquareOut, ArrowsClockwise, X } from "@phosphor-icons/react"; +import { ArrowSquareOut, ArrowsClockwise, CheckCircle, X } from "@phosphor-icons/react"; import type { AutoUpdateSnapshot } from "../../../shared/types"; import { Button } from "../ui/Button"; import { cn } from "../ui/cn"; @@ -118,6 +118,8 @@ export function AutoUpdateControl() { || isReadyOrInstalling; const downloadProgress = progressLabel(snapshot.progressPercent); const releaseNotesUrl = snapshot.recentlyInstalled?.releaseNotesUrl ?? null; + const installedVersion = snapshot.recentlyInstalled?.version ?? null; + const releaseNotesDisplayUrl = releaseNotesUrl?.replace(/^https?:\/\//, "") ?? null; function indicatorTitle(): string { switch (effectiveStatus) { @@ -189,57 +191,84 @@ export function AutoUpdateControl() { -
-
- - ADE updated - - - {snapshot.recentlyInstalled - ? `ADE restarted on v${snapshot.recentlyInstalled.version}.` - : "ADE finished installing the latest version."} - +
+
+
+
+
+
+
+
+ + ADE is up to date + + {installedVersion ? ( + + v{installedVersion} + + ) : null} +
+ + {installedVersion + ? `Restarted on v${installedVersion}.` + : "ADE finished installing the latest version."} + +
+
+ + +
- - - -
-
- The update is installed. You can reopen the release notes to see what changed in this build. -
+

+ The update is installed and ADE is ready again. + {releaseNotesUrl ? " Review what changed in this release." : null} +

-
- - {releaseNotesUrl ? ( + {releaseNotesDisplayUrl ? ( +
+
+ ) : null} + +
- ) : null} + {releaseNotesUrl ? ( + + ) : null} +
diff --git a/apps/desktop/src/renderer/index.css b/apps/desktop/src/renderer/index.css index fabf96140..c8fa5adc3 100644 --- a/apps/desktop/src/renderer/index.css +++ b/apps/desktop/src/renderer/index.css @@ -2059,6 +2059,7 @@ button:active, [role="button"]:active { } .ade-streaming-shimmer, .ade-streaming-shimmer::after, + .ade-update-installed-card, .ade-tool-bounce, .ade-fade-in, .ade-glow-pulse, @@ -3977,6 +3978,15 @@ button:active, [role="button"]:active { animation: ade-fade-slide-up 0.3s ease-out both; } +.ade-update-installed-card[data-state="open"] { + animation: ade-update-installed-card-in 180ms cubic-bezier(0.2, 0.8, 0.2, 1) both; +} + +@keyframes ade-update-installed-card-in { + from { opacity: 0; transform: translate(-50%, -48%) scale(0.98); } + to { opacity: 1; transform: translate(-50%, -50%) scale(1); } +} + /* Welcome card (onboarding): split-hero layout. */