diff --git a/apps/x/apps/main/src/updater.ts b/apps/x/apps/main/src/updater.ts index 66d5aee3..53369548 100644 --- a/apps/x/apps/main/src/updater.ts +++ b/apps/x/apps/main/src/updater.ts @@ -1,4 +1,4 @@ -import { app, autoUpdater, nativeImage, BrowserWindow } from "electron"; +import { app, autoUpdater, net, nativeImage, BrowserWindow } from "electron"; import { capture } from "@x/core/dist/analytics/posthog.js"; import type { ipc } from "@x/shared"; @@ -90,6 +90,12 @@ export function initUpdater(): void { releaseNotes: releaseNotes || undefined, }); showReadyBadge(); + // Squirrel.Windows never carries notes, and Squirrel.Mac's copy is a + // snapshot from download time — stale when the release body is edited + // after publish (update.electronjs.org caching widens that window). + // Refresh from the GitHub API; on failure the snapshot (or the card's + // static fallback line) stands. + void backfillReleaseNotes(releaseName || undefined); }); autoUpdater.on("error", (err) => { setStatus({ state: "error", error: err.message, lastCheckedAt: status.lastCheckedAt }); @@ -110,6 +116,30 @@ export function initUpdater(): void { setInterval(checkForUpdates, CHECK_INTERVAL_MS); } +/** + * Replace the staged update's release notes with the current GitHub release + * body. releaseName is "0.7.7" from Squirrel.Windows and "v0.7.7" from + * Squirrel.Mac — normalize to the tag form. + */ +async function backfillReleaseNotes(releaseName: string | undefined): Promise { + if (!releaseName) return; + try { + const tag = `v${releaseName.replace(/^v/, "")}`; + const res = await net.fetch(`https://api.github.com/repos/${REPO}/releases/tags/${tag}`, { + headers: { Accept: "application/vnd.github+json", "User-Agent": "Rowboat" }, + }); + if (!res.ok) return; + const { body } = (await res.json()) as { body?: string }; + const notes = body?.trim(); + // Re-check the state: the fetch raced user actions (quitAndInstall). + if (notes && status.state === "ready" && status.newVersion === releaseName) { + setStatus({ ...status, releaseNotes: notes }); + } + } catch { + // Offline or rate-limited — the Squirrel snapshot / fallback line stands. + } +} + /** * Manual "Check for updates". Only meaningful when idle or errored; * checking/downloading are already in flight and ready is already staged. diff --git a/apps/x/apps/renderer/src/components/update-card.tsx b/apps/x/apps/renderer/src/components/update-card.tsx index 3c9d5039..a0b93df0 100644 --- a/apps/x/apps/renderer/src/components/update-card.tsx +++ b/apps/x/apps/renderer/src/components/update-card.tsx @@ -55,6 +55,11 @@ export function UpdateCard() { if (!visible || status?.state !== "ready") return null const releaseUrl = version ? `${RELEASES_URL}/tag/v${version}` : `${RELEASES_URL}/latest` + // Release bodies usually open with their own "What's new" heading, which + // would duplicate the card's label above the box — drop it. + const releaseNotes = status.releaseNotes + ?.replace(/^\s*#{1,6}[ \t]*what[’']?s new[ \t]*\r?\n+/i, "") + .trim() return (
What's new
- {status.releaseNotes ? ( -
+ {releaseNotes ? ( + // A bordered, fixed-height scroll box (not a bare max-h clip): the + // frame and inset scrollbar signal there is more content below the + // fold on every platform. +
- {status.releaseNotes} + {releaseNotes}
) : (