Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions apps/desktop/src/main/services/updates/autoUpdateService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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");
Expand Down Expand Up @@ -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", {
Expand All @@ -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);
Expand All @@ -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",
},
});
Expand Down Expand Up @@ -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",
},
});
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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");
Expand All @@ -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);

Expand Down
8 changes: 3 additions & 5 deletions apps/desktop/src/main/services/updates/autoUpdateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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 {
Expand Down
115 changes: 72 additions & 43 deletions apps/desktop/src/renderer/components/app/AutoUpdateControl.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -189,57 +191,84 @@ export function AutoUpdateControl() {
<Dialog.Overlay className="fixed inset-0 z-[120] bg-black/55 backdrop-blur-sm" />
<Dialog.Content
className={cn(
"fixed left-1/2 top-1/2 z-[121] w-[min(92vw,440px)] -translate-x-1/2 -translate-y-1/2",
"border border-border bg-card p-5 shadow-2xl outline-none",
"ade-update-installed-card fixed left-1/2 top-1/2 z-[121] w-[min(92vw,480px)] -translate-x-1/2 -translate-y-1/2",
"overflow-hidden rounded-xl border border-white/[0.12] bg-[color:var(--ade-shell-surface,#121019)] text-fg shadow-2xl shadow-black/55 outline-none",
)}
>
<div className="flex items-start justify-between gap-4">
<div className="space-y-1">
<Dialog.Title className="font-mono text-[12px] font-semibold uppercase tracking-[1px] text-fg">
ADE updated
</Dialog.Title>
<Dialog.Description className="text-sm text-muted-fg">
{snapshot.recentlyInstalled
? `ADE restarted on v${snapshot.recentlyInstalled.version}.`
: "ADE finished installing the latest version."}
</Dialog.Description>
<div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-emerald-300/60 to-transparent" />
<div className="p-5 sm:p-6">
<div className="flex items-start justify-between gap-4">
<div className="flex min-w-0 items-start gap-3">
<div className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-emerald-300/25 bg-emerald-400/10 text-emerald-200 shadow-[0_0_24px_rgba(16,185,129,0.18)]">
<CheckCircle size={20} weight="fill" aria-hidden="true" />
</div>
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<Dialog.Title className="text-sm font-semibold text-fg">
ADE is up to date
</Dialog.Title>
{installedVersion ? (
<span className="rounded-full border border-white/[0.10] bg-white/[0.04] px-2 py-0.5 font-mono text-[10px] font-semibold text-muted-fg">
v{installedVersion}
</span>
) : null}
</div>
<Dialog.Description className="mt-1 text-sm text-muted-fg">
{installedVersion
? `Restarted on v${installedVersion}.`
: "ADE finished installing the latest version."}
</Dialog.Description>
</div>
</div>
<Dialog.Close asChild>
<button
type="button"
className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-fg transition-colors hover:bg-white/[0.06] hover:text-fg"
aria-label="Close update details"
>
<X size={14} weight="bold" />
</button>
</Dialog.Close>
</div>
<Dialog.Close asChild>
<button
type="button"
className="inline-flex h-7 w-7 items-center justify-center text-muted-fg transition-colors hover:text-fg"
aria-label="Close update details"
>
<X size={14} weight="bold" />
</button>
</Dialog.Close>
</div>

<div className="mt-3 text-[13px] leading-6 text-muted-fg">
The update is installed. You can reopen the release notes to see what changed in this build.
</div>
<p className="mt-4 text-[13px] leading-6 text-muted-fg">
The update is installed and ADE is ready again.
{releaseNotesUrl ? " Review what changed in this release." : null}
</p>

<div className="mt-5 flex items-center justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={dismissInstalledNotice}
>
Close
</Button>
{releaseNotesUrl ? (
{releaseNotesDisplayUrl ? (
<div className="mt-3 flex min-w-0 items-center gap-2 border-t border-white/[0.08] pt-3 text-[11px] text-muted-fg">
<ArrowSquareOut size={12} weight="bold" className="shrink-0 text-emerald-200/80" aria-hidden="true" />
<span className="truncate" title={releaseNotesUrl ?? undefined}>
{releaseNotesDisplayUrl}
</span>
</div>
) : null}

<div className="mt-5 flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-end">
<Button
type="button"
variant="primary"
onClick={() => {
void window.ade.app.openExternal(releaseNotesUrl);
dismissInstalledNotice();
}}
variant="outline"
className="w-full sm:w-auto"
onClick={dismissInstalledNotice}
>
<ArrowSquareOut size={12} weight="bold" />
Open release notes
Close
</Button>
) : null}
{releaseNotesUrl ? (
<Button
type="button"
variant="primary"
className="w-full sm:w-auto"
onClick={() => {
void window.ade.app.openExternal(releaseNotesUrl);
dismissInstalledNotice();
}}
>
<ArrowSquareOut size={12} weight="bold" />
Open release notes
</Button>
) : null}
</div>
</div>
</Dialog.Content>
</Dialog.Portal>
Expand Down
10 changes: 10 additions & 0 deletions apps/desktop/src/renderer/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

@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.
*/
Expand Down
Loading