From 60165979f0d067c65734c2d633e1b0c4c79b2dc9 Mon Sep 17 00:00:00 2001 From: Harshvardhan Vatsa Date: Mon, 20 Jul 2026 17:51:14 +0530 Subject: [PATCH] feat(x): scrollable release-notes box on the update card, notes on Windows too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The notes pane was a bare max-h clip: nothing signalled that more content sat below the fold, and Squirrel.Windows never supplies release notes at all, so Windows users only ever saw the static fallback line. Squirrel.Mac is no better in the edit-after-publish window — it snapshots the release body at download time, which is how v0.7.7 shipped a card showing two paragraphs of a ten-item release. - Render the notes in a bordered, inset scroll box (max-h + overflow-y) so the frame and scrollbar make the overflow visible on both platforms. - Backfill the notes from the GitHub release body after update-downloaded: gives Windows notes for the first time and replaces macOS's stale snapshot with the current body. On fetch failure the snapshot (or the fallback line) stands. - Strip the release body's leading "What's new" heading, which duplicated the card's own label. Co-Authored-By: Claude Fable 5 --- apps/x/apps/main/src/updater.ts | 32 ++++++++++++++++++- .../renderer/src/components/update-card.tsx | 14 ++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) 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}
) : (