From f8a176cdc544ad471c6909843914f368f5cacc54 Mon Sep 17 00:00:00 2001 From: PerishCode Date: Thu, 9 Jul 2026 12:29:34 +0800 Subject: [PATCH 1/5] Add stable updater release notes --- .../scripts/release/github/stable-notes.sh | 3 +- apps/packaged/package.json | 2 +- apps/web/src/components/UpdaterPopup.tsx | 70 ++++++++++- apps/web/src/lib/updater.ts | 75 +++++++++++ .../web/src/styles/workspace/mention-home.css | 24 ++++ .../tests/components/UpdaterPopup.test.tsx | 72 +++++++++++ apps/web/tests/lib/updater.test.ts | 54 ++++++++ docs/CHANGELOG/v0.14.2/en.md | 10 ++ docs/CHANGELOG/v0.14.2/zh-CN.md | 10 ++ e2e/tests/packaged-smoke-workflow.test.ts | 13 ++ tools/release/src/metadata/prepare-stable.ts | 5 + tools/release/src/release-notes.ts | 117 ++++++++++++++++++ tools/release/src/storage/common.ts | 1 + tools/release/src/storage/publish-metadata.ts | 27 ++++ tools/release/src/storage/verify-metadata.ts | 15 +++ tools/release/tests/channel-prepare.test.ts | 42 ++++++- tools/serve/src/index.ts | 9 ++ tools/serve/src/updater-fixture.ts | 101 +++++++++++++++ .../tests/release-metadata-publish.test.ts | 19 +++ tools/serve/tests/updater-fixture.test.ts | 41 ++++++ 20 files changed, 705 insertions(+), 5 deletions(-) create mode 100644 docs/CHANGELOG/v0.14.2/en.md create mode 100644 docs/CHANGELOG/v0.14.2/zh-CN.md create mode 100644 tools/release/src/release-notes.ts diff --git a/.github/scripts/release/github/stable-notes.sh b/.github/scripts/release/github/stable-notes.sh index 4eab3dfadba..3aad78683a4 100644 --- a/.github/scripts/release/github/stable-notes.sh +++ b/.github/scripts/release/github/stable-notes.sh @@ -28,7 +28,8 @@ cat > "$notes_file" <) => string; function versionText(t: Translator, model: UpdaterModel): string { @@ -69,13 +75,14 @@ function updaterErrorCode(model: UpdaterModel): string | undefined { } export function UpdaterPopup() { - const t = useT(); + const { locale, t } = useI18n(); const wrapRef = useRef(null); const actionInFlightRef = useRef(false); const handoffWatchdogRef = useRef(null); const [model, setModel] = useState(() => deriveUpdaterModel(null)); const [panelOpen, setPanelOpen] = useState(false); const [installState, setInstallState] = useState('idle'); + const [releaseNotesState, setReleaseNotesState] = useState({ state: 'idle' }); const clearHandoffWatchdog = useCallback(() => { if (handoffWatchdogRef.current == null) return; @@ -126,6 +133,14 @@ export function UpdaterPopup() { const showControl = ready || installState !== 'idle'; const controlLabel = model.updateKind === 'payload' ? t('updater.installRestart') : t('updater.openInstaller'); const channelLabel = channelLabelFor(model.status?.channel); + const releaseNoteCandidates = useMemo( + () => releaseNoteCandidatesFromStatus(model.status, locale), + [locale, model.status], + ); + const releaseNoteCandidatesKey = useMemo( + () => releaseNoteCandidates.map((candidate) => `${candidate.format}:${candidate.url}`).join('|'), + [releaseNoteCandidates], + ); const analytics = useAnalytics(); const appVersionBefore = useAppVersion(); const versionProps = useMemo( @@ -165,6 +180,39 @@ export function UpdaterPopup() { }); }, [analytics.track, promptSurfaceKey, versionProps]); + useEffect(() => { + if (!panelOpen || releaseNoteCandidates.length === 0) { + setReleaseNotesState({ state: 'idle' }); + return; + } + + let cancelled = false; + setReleaseNotesState({ state: 'loading' }); + + const load = async () => { + for (const candidate of releaseNoteCandidates) { + try { + const response = await fetch(candidate.url, { + headers: { 'Cache-Control': 'no-cache' }, + }); + if (!response.ok) continue; + const text = await response.text(); + if (cancelled) return; + setReleaseNotesState({ candidate, state: 'ready', text }); + return; + } catch { + // Try the next metadata-provided fallback URL. + } + } + if (!cancelled) setReleaseNotesState({ state: 'empty' }); + }; + + void load(); + return () => { + cancelled = true; + }; + }, [panelOpen, releaseNoteCandidates, releaseNoteCandidatesKey]); + const close = useCallback(() => { if (installBusy) return; trackUpdateIndicatorClick(analytics.track, { @@ -316,6 +364,24 @@ export function UpdaterPopup() {

{t('updater.ready')}

{versionText(t, model)}

{channelLabel != null ? {channelLabel} : null} + {releaseNotesState.state === 'ready' ? ( +
+ {releaseNotesState.candidate.format === 'html' ? ( +